Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions TUnit.Core/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protected Context? Parent

public static Context Current =>
TestContext.Current as Context
?? TestBuildContext.Current as Context
?? ClassHookContext.Current as Context
?? AssemblyHookContext.Current as Context
?? TestSessionContext.Current as Context
Expand Down Expand Up @@ -67,13 +68,13 @@ public void AddAsyncLocalValues()
#endif
}

public string GetStandardOutput()
public virtual string GetStandardOutput()
{
if (_outputBuilder.Length == 0)
{
return string.Empty;
}

_outputLock.EnterReadLock();

try
Expand All @@ -86,7 +87,7 @@ public string GetStandardOutput()
}
}

public string GetErrorOutput()
public virtual string GetErrorOutput()
{
if (_errorOutputBuilder.Length == 0)
{
Expand Down
45 changes: 45 additions & 0 deletions TUnit.Core/Models/TestBuildContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace TUnit.Core;

/// <summary>
/// Context for capturing output during test building and data source initialization.
/// This context is active during the test building phase, before TestContext is created.
/// Output captured here is transferred to the TestContext when it's created.
/// </summary>
public sealed class TestBuildContext : Context, IDisposable
{
private static readonly AsyncLocal<TestBuildContext?> _current = new();

public static new TestBuildContext? Current
{
get => _current.Value;
internal set => _current.Value = value;
}

public TestBuildContext() : base(null)
{
}

/// <summary>
/// Gets the captured standard output during test building.
/// </summary>
public string GetCapturedOutput() => GetStandardOutput();

/// <summary>
/// Gets the captured error output during test building.
/// </summary>
public string GetCapturedErrorOutput() => GetErrorOutput();

internal override void SetAsyncLocalContext()
{
Current = this;
}

/// <summary>
/// Clears the current TestBuildContext.
/// </summary>
public new void Dispose()
{
Current = null;
base.Dispose();
}
}
46 changes: 43 additions & 3 deletions TUnit.Core/TestContext.Output.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void ITestOutput.AttachArtifact(Artifact artifact)
}

string ITestOutput.GetStandardOutput() => GetOutput();
string ITestOutput.GetErrorOutput() => GetErrorOutput();
string ITestOutput.GetErrorOutput() => GetOutputError();

void ITestOutput.WriteLine(string message)
{
Expand All @@ -46,7 +46,47 @@ void ITestOutput.WriteError(string message)
_errorWriter.WriteLine(message);
}

internal string GetOutput() => _outputWriter?.ToString() ?? string.Empty;
/// <summary>
/// Gets the combined build-time and execution-time standard output.
/// </summary>
public override string GetStandardOutput()
{
return GetOutput();
}

/// <summary>
/// Gets the combined build-time and execution-time error output.
/// </summary>
public override string GetErrorOutput()
{
return GetOutputError();
}

internal string GetOutput()
{
var buildOutput = _buildTimeOutput ?? string.Empty;
var baseOutput = base.GetStandardOutput(); // Get output from base class (Context)
var writerOutput = _outputWriter?.ToString() ?? string.Empty;

internal new string GetErrorOutput() => _errorWriter?.ToString() ?? string.Empty;
// Combine all three sources: build-time, base class output, and writer output
var parts = new[] { buildOutput, baseOutput, writerOutput }
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();

return parts.Length == 0 ? string.Empty : string.Join(Environment.NewLine, parts);
}

internal string GetOutputError()
{
var buildErrorOutput = _buildTimeErrorOutput ?? string.Empty;
var baseErrorOutput = base.GetErrorOutput(); // Get error output from base class (Context)
var writerErrorOutput = _errorWriter?.ToString() ?? string.Empty;

// Combine all three sources: build-time error, base class error output, and writer error output
var parts = new[] { buildErrorOutput, baseErrorOutput, writerErrorOutput }
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();

return parts.Length == 0 ? string.Empty : string.Join(Environment.NewLine, parts);
}
}
13 changes: 12 additions & 1 deletion TUnit.Core/TestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public TestContext(string testName, IServiceProvider serviceProvider, ClassHookC

private StringWriter? _errorWriter;

private string? _buildTimeOutput;
private string? _buildTimeErrorOutput;

public static new TestContext? Current
{
get => TestContexts.Value;
Expand Down Expand Up @@ -152,5 +155,13 @@ internal override void SetAsyncLocalContext()
internal ConcurrentDictionary<int, HashSet<object>> TrackedObjects =>
_trackedObjects ??= new();


/// <summary>
/// Sets the output captured during test building phase.
/// This output is prepended to the test's execution output.
/// </summary>
internal void SetBuildTimeOutput(string? output, string? errorOutput)
{
_buildTimeOutput = output;
_buildTimeErrorOutput = errorOutput;
}
}
16 changes: 16 additions & 0 deletions TUnit.Engine/Building/TestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ public async Task<IEnumerable<AbstractExecutableTest>> BuildTestsFromMetadataAsy

try
{
// Create a context for capturing output during test building
using var buildContext = new TestBuildContext();
TestBuildContext.Current = buildContext;

// Handle GenericTestMetadata with ConcreteInstantiations
if (metadata is GenericTestMetadata { ConcreteInstantiations.Count: > 0 } genericMetadata)
{
Expand Down Expand Up @@ -508,6 +512,18 @@ public async Task<IEnumerable<AbstractExecutableTest>> BuildTestsFromMetadataAsy
tests.Add(test);
}
}

// Transfer captured build-time output to all test contexts
var capturedOutput = buildContext.GetCapturedOutput();
var capturedErrorOutput = buildContext.GetCapturedErrorOutput();

if (!string.IsNullOrEmpty(capturedOutput) || !string.IsNullOrEmpty(capturedErrorOutput))
{
foreach (var test in tests)
{
test.Context.SetBuildTimeOutput(capturedOutput, capturedErrorOutput);
}
}
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ namespace
public void AddAsyncLocalValues() { }
public void Dispose() { }
public . GetDefaultLogger() { }
public string GetErrorOutput() { }
public string GetStandardOutput() { }
public virtual string GetErrorOutput() { }
public virtual string GetStandardOutput() { }
public void RestoreExecutionContext() { }
}
public class ContextProvider : .
Expand Down Expand Up @@ -1241,6 +1241,14 @@ namespace
{
public TestAttribute([.] string file = "", [.] int line = 0) { }
}
public sealed class TestBuildContext : .Context,
{
public TestBuildContext() { }
public new static .TestBuildContext? Current { get; }
public new void Dispose() { }
public string GetCapturedErrorOutput() { }
public string GetCapturedOutput() { }
}
public class TestBuilderContext : <.TestBuilderContext>
{
public TestBuilderContext() { }
Expand Down Expand Up @@ -1291,6 +1299,8 @@ namespace
public static string? OutputDirectory { get; }
public static .<string, .<string>> Parameters { get; }
public static string WorkingDirectory { get; set; }
public override string GetErrorOutput() { }
public override string GetStandardOutput() { }
public static .TestContext? GetById(string id) { }
}
public class TestContextEvents : .
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ namespace
public void AddAsyncLocalValues() { }
public void Dispose() { }
public . GetDefaultLogger() { }
public string GetErrorOutput() { }
public string GetStandardOutput() { }
public virtual string GetErrorOutput() { }
public virtual string GetStandardOutput() { }
public void RestoreExecutionContext() { }
}
public class ContextProvider : .
Expand Down Expand Up @@ -1241,6 +1241,14 @@ namespace
{
public TestAttribute([.] string file = "", [.] int line = 0) { }
}
public sealed class TestBuildContext : .Context,
{
public TestBuildContext() { }
public new static .TestBuildContext? Current { get; }
public new void Dispose() { }
public string GetCapturedErrorOutput() { }
public string GetCapturedOutput() { }
}
public class TestBuilderContext : <.TestBuilderContext>
{
public TestBuilderContext() { }
Expand Down Expand Up @@ -1291,6 +1299,8 @@ namespace
public static string? OutputDirectory { get; }
public static .<string, .<string>> Parameters { get; }
public static string WorkingDirectory { get; set; }
public override string GetErrorOutput() { }
public override string GetStandardOutput() { }
public static .TestContext? GetById(string id) { }
}
public class TestContextEvents : .
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ namespace
public void AddAsyncLocalValues() { }
public void Dispose() { }
public . GetDefaultLogger() { }
public string GetErrorOutput() { }
public string GetStandardOutput() { }
public virtual string GetErrorOutput() { }
public virtual string GetStandardOutput() { }
public void RestoreExecutionContext() { }
}
public class ContextProvider : .
Expand Down Expand Up @@ -1241,6 +1241,14 @@ namespace
{
public TestAttribute([.] string file = "", [.] int line = 0) { }
}
public sealed class TestBuildContext : .Context,
{
public TestBuildContext() { }
public new static .TestBuildContext? Current { get; }
public new void Dispose() { }
public string GetCapturedErrorOutput() { }
public string GetCapturedOutput() { }
}
public class TestBuilderContext : <.TestBuilderContext>
{
public TestBuilderContext() { }
Expand Down Expand Up @@ -1291,6 +1299,8 @@ namespace
public static string? OutputDirectory { get; }
public static .<string, .<string>> Parameters { get; }
public static string WorkingDirectory { get; set; }
public override string GetErrorOutput() { }
public override string GetStandardOutput() { }
public static .TestContext? GetById(string id) { }
}
public class TestContextEvents : .
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,8 @@ namespace
public void AddAsyncLocalValues() { }
public void Dispose() { }
public . GetDefaultLogger() { }
public string GetErrorOutput() { }
public string GetStandardOutput() { }
public virtual string GetErrorOutput() { }
public virtual string GetStandardOutput() { }
public void RestoreExecutionContext() { }
}
public class ContextProvider : .
Expand Down Expand Up @@ -1196,6 +1196,14 @@ namespace
{
public TestAttribute([.] string file = "", [.] int line = 0) { }
}
public sealed class TestBuildContext : .Context,
{
public TestBuildContext() { }
public new static .TestBuildContext? Current { get; }
public new void Dispose() { }
public string GetCapturedErrorOutput() { }
public string GetCapturedOutput() { }
}
public class TestBuilderContext : <.TestBuilderContext>
{
public TestBuilderContext() { }
Expand Down Expand Up @@ -1246,6 +1254,8 @@ namespace
public static string? OutputDirectory { get; }
public static .<string, .<string>> Parameters { get; }
public static string WorkingDirectory { get; set; }
public override string GetErrorOutput() { }
public override string GetStandardOutput() { }
public static .TestContext? GetById(string id) { }
}
public class TestContextEvents : .
Expand Down
Loading
Loading