Created
April 15, 2021 08:31
-
-
Save nohwnd/433f23dfda4cae4df574c0085a0717f7 to your computer and use it in GitHub Desktop.
Writing output per test in MSTest
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
using System; | |
using System.Collections.Concurrent; | |
using System.Diagnostics; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using System.Linq; | |
using System.Collections.Generic; | |
using System.IO; | |
[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] | |
namespace TestProject1 | |
{ | |
// This would be part of test framework | |
// I am just using it as static storage | |
// and using the callbacks to mimic this is | |
// built in functionality | |
static class TestRunContext | |
{ | |
internal static readonly StringBuilder NonTestOutput = new StringBuilder(); | |
} | |
[TestClass] | |
public class UnitTest1 | |
{ | |
static ConcurrentDictionary<string, StringBuilder> TestOutputs = new ConcurrentDictionary<string, StringBuilder>(); | |
private static bool Test2Flag; | |
private static Stopwatch _sw = Stopwatch.StartNew(); | |
public TestContext TestContext { get; set; } | |
[AssemblyInitialize()] | |
public static void AssemblyInit(TestContext _) | |
{ | |
// capture output from all non-task bound calls to Console.WriteLine | |
AsyncLocalConsoleWriter.State.Value = TestRunContext.NonTestOutput; | |
Console.SetOut(AsyncLocalConsoleWriter.Instance); | |
} | |
[TestInitialize()] | |
public void Initialize() | |
{ | |
// capture output from the current test | |
string testMethodName = TestContext.TestName; | |
var sb = TestOutputs.GetOrAdd(testMethodName, new StringBuilder()); | |
// this should not be necessary per test method, but it is | |
// maybe someting in the framework already sets the Out | |
Console.SetOut(AsyncLocalConsoleWriter.Instance); | |
AsyncLocalConsoleWriter.State.Value = sb; | |
} | |
[TestCleanup()] | |
public void Cleanup() | |
{ | |
var test = TestContext; | |
} | |
[AssemblyCleanup()] | |
public static void AssemblyCleanup() | |
{ | |
Console.Out.Flush(); | |
var o = String.Join("\n", TestOutputs.Select((s) => $"{s.Key}:\n{s.Value}\n")); | |
var addo = String.Join("\n", AsyncLocalConsoleWriter.AdditionalOutputs.Select((s, i) => $"Output {i}:\n{s}\n")); | |
// write it to the target directory | |
var path = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "output.txt"); | |
try | |
{ | |
File.WriteAllText(path, $"Test outputs:\n{o}\n" + | |
$"Non test output:\n{TestRunContext.NonTestOutput}\n" + | |
$"Additional output:\n{TestRunContext.NonTestOutput}\n" + | |
$"All output:\n{AsyncLocalConsoleWriter.AllOutput}\n"); | |
} | |
catch (Exception ex) | |
{ | |
var a = ex; | |
} | |
var p = true; | |
} | |
[TestMethod] | |
public async Task TestMethod1() | |
{ | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t1-14444"); | |
while (!Test2Flag) | |
{ | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t1-wait"); | |
await Task.Delay(100); | |
} | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t1-2"); | |
// BUG: when you throw directly after the message is not captured and goes | |
// to standard output, or is lost. This is not specific to this prototype, | |
// it happens in normal execution as well. | |
// Maybe console flushing issue? | |
throw new Exception("aaa"); | |
} | |
[TestMethod] | |
public async Task TestMethod2() | |
{ | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t2-1"); | |
await Task.Delay(200); | |
Test2Flag = true; | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t2-2"); | |
} | |
[TestMethod] | |
public Task TestMethod3() | |
{ | |
Thread.Sleep(50); | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t3-1"); | |
while (!Test2Flag) | |
{ | |
Thread.Sleep(100); | |
} | |
Thread.Sleep(2000); | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t3-2"); | |
return Task.CompletedTask; | |
} | |
[TestMethod] | |
public void TestMethod4() | |
{ | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t4-1"); | |
Console.WriteLine($"{_sw.ElapsedMilliseconds:0000} t4-2"); | |
} | |
} | |
public class AsyncLocalConsoleWriter : StringWriter | |
{ | |
private AsyncLocalConsoleWriter() { } | |
public static AsyncLocalConsoleWriter Instance = new AsyncLocalConsoleWriter(); | |
public static List<StringBuilder> AdditionalOutputs { get; } = new List<StringBuilder>(); | |
public static AsyncLocal<StringBuilder> State { get; } = new AsyncLocal<StringBuilder>(); | |
public static StringBuilder AllOutput = new StringBuilder(); | |
public override Encoding Encoding => throw new NotImplementedException(); | |
public override void WriteLine(string value) | |
{ | |
AllOutput.AppendLine(value); | |
if (State?.Value == null) | |
{ | |
var sb = new StringBuilder(); | |
AdditionalOutputs.Add(sb); | |
State.Value = sb; | |
} | |
State.Value.AppendLine(value); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment