Skip to content

Instantly share code, notes, and snippets.

@nohwnd
Created April 15, 2021 08:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nohwnd/433f23dfda4cae4df574c0085a0717f7 to your computer and use it in GitHub Desktop.
Save nohwnd/433f23dfda4cae4df574c0085a0717f7 to your computer and use it in GitHub Desktop.
Writing output per test in MSTest
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