testutil/TestUtil.cs (219 lines of code) (raw):

/* * Copyright (c) 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Xunit; namespace GoogleCloudSamples { public class TestUtil { public static string RandomName() { using (RandomNumberGenerator rng = new RNGCryptoServiceProvider()) { string legalChars = "abcdefhijklmnpqrstuvwxyz"; byte[] randomByte = new byte[1]; char[] randomChars = new char[20]; int nextChar = 0; while (nextChar < randomChars.Length) { rng.GetBytes(randomByte); if (legalChars.Contains((char)randomByte[0])) randomChars[nextChar++] = (char)randomByte[0]; } return new string(randomChars); } } } // TODO: Remove this class and use // Transient Fault Handling Application Block: // https://msdn.microsoft.com/en-us/library/dn440719(v=pandp.60).aspx public class RetryRobot { public int FirstRetryDelayMs { get; set; } = 1000; public float DelayMultiplier { get; set; } = 2; public int MaxTryCount { get; set; } = 7; public IEnumerable<Type> RetryWhenExceptions { get; set; } = new Type[0]; public Func<Exception, bool> ShouldRetry { get; set; } private static readonly Random _random = new Random(); private static readonly object _lock = new object(); /// <summary> /// Retry action when assertion fails. /// </summary> /// <param name="func"></param> public T Eventually<T>(Func<T> func) { int delayMs = FirstRetryDelayMs; for (int i = 0; ; ++i) { try { return func(); } catch (Exception e) when (ShouldCatch(e) && i < MaxTryCount) { int jitteredDelayMs; lock(_lock) { jitteredDelayMs = delayMs/2 + (int)(_random.NextDouble() * delayMs); } Thread.Sleep(jitteredDelayMs); delayMs *= (int)DelayMultiplier; } } } public void Eventually(Action action) => Eventually(() => { action(); return 0; }); public async Task<T> Eventually<T>(Func<Task<T>> asyncFunc) { int delayMs = FirstRetryDelayMs; for (int i = 0; ; ++i) { try { return await asyncFunc(); } catch (Exception e) when (ShouldCatch(e) && i < MaxTryCount) { int jitteredDelayMs; lock (_lock) { jitteredDelayMs = delayMs / 2 + (int)(_random.NextDouble() * delayMs); } await Task.Delay(jitteredDelayMs); delayMs *= (int)DelayMultiplier; } } } public async Task Eventually(Func<Task> action) { await Eventually(async () => { await action(); return 0; }); } private bool ShouldCatch(Exception e) { if (ShouldRetry != null) return ShouldRetry(e); foreach (Type exceptionType in RetryWhenExceptions) { if (exceptionType.IsAssignableFrom(e.GetType())) return true; } return false; } } public struct ConsoleOutput { public int ExitCode; public string Stdout; public void AssertSucceeded() { Assert.True(0 == ExitCode, $"Exit code: {ExitCode}\n{Stdout}"); } }; public class CommandLineRunner { // Use a lock to protect globally-shared stdout. private static readonly object s_lock = new object(); public Func<string[], int> Main { get; set; } public Action<string[]> VoidMain { get; set; } public string Command { get; set; } /// <summary>Runs executable with the provided arguments</summary> /// <returns>The console output of this program</returns> public ConsoleOutput Run(params string[] arguments) { lock (s_lock) { Console.Write($"{Command} "); Console.WriteLine(string.Join(" ", arguments)); TextWriter consoleOut = Console.Out; ThreadSafeStringWriter stringOut = new ThreadSafeStringWriter(); Console.SetOut(stringOut); try { int exitCode = 0; if (null == VoidMain) exitCode = Main(arguments); else VoidMain(arguments); ConsoleOutput consoleOutput = new ConsoleOutput() { ExitCode = exitCode, Stdout = stringOut.ToString() }; Console.Write(consoleOutput.Stdout); return consoleOutput; } finally { Console.SetOut(consoleOut); } } } public ConsoleOutput RunWithStdIn(string stdIn, params string[] arguments) { lock (s_lock) { Console.Write($"{Command} "); Console.WriteLine(string.Join(" ", arguments)); TextWriter consoleOut = Console.Out; TextReader consoleIn = Console.In; ThreadSafeStringWriter stringOut = new ThreadSafeStringWriter(); Console.SetOut(stringOut); Console.SetIn(new StringReader(stdIn)); try { int exitCode = 0; if (null == VoidMain) exitCode = Main(arguments); else VoidMain(arguments); ConsoleOutput consoleOutput = new ConsoleOutput() { ExitCode = exitCode, Stdout = stringOut.ToString() }; Console.Write(consoleOutput.Stdout); return consoleOutput; } finally { Console.SetOut(consoleOut); Console.SetIn(consoleIn); } } } internal class ThreadSafeStringWriter : StringWriter { private readonly object _lock = new object(); public override void Write(char value) { lock (_lock) { base.Write(value); } } public override void Write(char[] buffer, int index, int count) { lock (_lock) { base.Write(buffer, index, count); } } public override void Write(string value) { lock (_lock) { base.Write(value); } } public override string ToString() { lock (_lock) { return base.ToString(); } } } } }