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
Original file line number Diff line number Diff line change
Expand Up @@ -877,3 +877,273 @@ internal static class TUnit_TestProject_TimeoutCancellationTokenTests_MatrixTest
global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.TimeoutCancellationTokenTests), new TUnit_TestProject_TimeoutCancellationTokenTests_MatrixTest__int_CancellationToken_TestSource());
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#pragma warning disable

#nullable enable
namespace TUnit.Generated;
internal sealed class TUnit_TestProject_TimeoutDoesNotFireTests_QuickTestDoesNotTimeout__CancellationToken_TestSource : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource
{
public async global::System.Collections.Generic.IAsyncEnumerable<global::TUnit.Core.TestMetadata> GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default)
{
var metadata = new global::TUnit.Core.TestMetadata<global::TUnit.TestProject.TimeoutDoesNotFireTests>
{
TestName = "QuickTestDoesNotTimeout",
TestClassType = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests),
TestMethodName = "QuickTestDoesNotTimeout",
Dependencies = global::System.Array.Empty<global::TUnit.Core.TestDependency>(),
AttributeFactory = static () =>
[
new global::TUnit.Core.TestAttribute(),
new global::TUnit.Core.MethodDataSourceAttribute("DataSource"),
new global::TUnit.Core.TimeoutAttribute(30_000),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.CategoryAttribute("Timeout Cancellation Token Tests")
],
DataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
ClassDataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.MethodDataSourceAttribute("DataSource")
{
Factory = (dataGeneratorMetadata) =>
{
async global::System.Collections.Generic.IAsyncEnumerable<global::System.Func<global::System.Threading.Tasks.Task<object?[]?>>> Factory()
{
var result = global::TUnit.TestProject.TimeoutDoesNotFireTests.DataSource();
if (result is global::System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
yield return () => global::System.Threading.Tasks.Task.FromResult(global::TUnit.Core.Helpers.DataSourceHelpers.ToObjectArray(item));
}
}
else
{
yield return () => global::System.Threading.Tasks.Task.FromResult(global::TUnit.Core.Helpers.DataSourceHelpers.ToObjectArray(result));
}
}
return Factory();
}
},
},
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
PropertyInjections = global::System.Array.Empty<global::TUnit.Core.PropertyInjectionData>(),
InheritanceDepth = 0,
FilePath = @"",
LineNumber = 86,
MethodMetadata = new global::TUnit.Core.MethodMetadata
{
Type = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests),
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests)),
Name = "QuickTestDoesNotTimeout",
GenericTypeCount = 0,
ReturnType = typeof(global::System.Threading.Tasks.Task),
ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)),
Parameters = new global::TUnit.Core.ParameterMetadata[]
{
new global::TUnit.Core.ParameterMetadata(typeof(global::System.Threading.CancellationToken))
{
Name = "cancellationToken",
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.CancellationToken)),
IsNullable = false,
ReflectionInfo = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests).GetMethod("QuickTestDoesNotTimeout", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { typeof(global::System.Threading.CancellationToken) }, null)!.GetParameters()[0]
}
},
Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.TimeoutDoesNotFireTests", static () =>
{
var classMetadata = new global::TUnit.Core.ClassMetadata
{
Type = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests),
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests)),
Name = "TimeoutDoesNotFireTests",
Namespace = "TUnit.TestProject",
Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", static () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }),
Parameters = new global::TUnit.Core.ParameterMetadata[]
{
new global::TUnit.Core.ParameterMetadata(typeof(int))
{
Name = "value",
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(int)),
IsNullable = false,
ReflectionInfo = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests).GetConstructor(new global::System.Type[] { typeof(int) })!.GetParameters()[0]
}
},
Properties = global::System.Array.Empty<global::TUnit.Core.PropertyMetadata>(),
Parent = null
};
foreach (var prop in classMetadata.Properties)
{
prop.ClassMetadata = classMetadata;
prop.ContainingTypeMetadata = classMetadata;
}
return classMetadata;
})
},
InstanceFactory = (typeArgs, args) =>
{
return new global::TUnit.TestProject.TimeoutDoesNotFireTests(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]));
},
InvokeTypedTest = static (instance, args, cancellationToken) =>
{
try
{
return new global::System.Threading.Tasks.ValueTask(instance.QuickTestDoesNotTimeout(cancellationToken));
}
catch (global::System.Exception ex)
{
return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(ex));
}
},
};
metadata.UseRuntimeDataGeneration(testSessionId);
yield return metadata;
yield break;
}
}
internal static class TUnit_TestProject_TimeoutDoesNotFireTests_QuickTestDoesNotTimeout__CancellationToken_ModuleInitializer
{
[global::System.Runtime.CompilerServices.ModuleInitializer]
public static void Initialize()
{
global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests), new TUnit_TestProject_TimeoutDoesNotFireTests_QuickTestDoesNotTimeout__CancellationToken_TestSource());
}
}


// ===== FILE SEPARATOR =====

// <auto-generated/>
#pragma warning disable

#nullable enable
namespace TUnit.Generated;
internal sealed class TUnit_TestProject_CancellationTokenTriggeredTests_CancellationTokenIsTriggered__CancellationToken_TestSource : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource
{
public async global::System.Collections.Generic.IAsyncEnumerable<global::TUnit.Core.TestMetadata> GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default)
{
var metadata = new global::TUnit.Core.TestMetadata<global::TUnit.TestProject.CancellationTokenTriggeredTests>
{
TestName = "CancellationTokenIsTriggered",
TestClassType = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests),
TestMethodName = "CancellationTokenIsTriggered",
Dependencies = global::System.Array.Empty<global::TUnit.Core.TestDependency>(),
AttributeFactory = static () =>
[
new global::TUnit.Core.TestAttribute(),
new global::TUnit.Core.MethodDataSourceAttribute("DataSource"),
new global::TUnit.Core.TimeoutAttribute(5_000),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Failure),
new global::TUnit.Core.CategoryAttribute("Timeout Cancellation Token Tests")
],
DataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
ClassDataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.MethodDataSourceAttribute("DataSource")
{
Factory = (dataGeneratorMetadata) =>
{
async global::System.Collections.Generic.IAsyncEnumerable<global::System.Func<global::System.Threading.Tasks.Task<object?[]?>>> Factory()
{
var result = global::TUnit.TestProject.CancellationTokenTriggeredTests.DataSource();
if (result is global::System.Collections.IEnumerable enumerable && !(result is string))
{
foreach (var item in enumerable)
{
yield return () => global::System.Threading.Tasks.Task.FromResult(global::TUnit.Core.Helpers.DataSourceHelpers.ToObjectArray(item));
}
}
else
{
yield return () => global::System.Threading.Tasks.Task.FromResult(global::TUnit.Core.Helpers.DataSourceHelpers.ToObjectArray(result));
}
}
return Factory();
}
},
},
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
PropertyInjections = global::System.Array.Empty<global::TUnit.Core.PropertyInjectionData>(),
InheritanceDepth = 0,
FilePath = @"",
LineNumber = 106,
MethodMetadata = new global::TUnit.Core.MethodMetadata
{
Type = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests),
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests)),
Name = "CancellationTokenIsTriggered",
GenericTypeCount = 0,
ReturnType = typeof(global::System.Threading.Tasks.Task),
ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)),
Parameters = new global::TUnit.Core.ParameterMetadata[]
{
new global::TUnit.Core.ParameterMetadata(typeof(global::System.Threading.CancellationToken))
{
Name = "cancellationToken",
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.CancellationToken)),
IsNullable = false,
ReflectionInfo = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests).GetMethod("CancellationTokenIsTriggered", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { typeof(global::System.Threading.CancellationToken) }, null)!.GetParameters()[0]
}
},
Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.CancellationTokenTriggeredTests", static () =>
{
var classMetadata = new global::TUnit.Core.ClassMetadata
{
Type = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests),
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests)),
Name = "CancellationTokenTriggeredTests",
Namespace = "TUnit.TestProject",
Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", static () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }),
Parameters = new global::TUnit.Core.ParameterMetadata[]
{
new global::TUnit.Core.ParameterMetadata(typeof(int))
{
Name = "value",
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(int)),
IsNullable = false,
ReflectionInfo = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests).GetConstructor(new global::System.Type[] { typeof(int) })!.GetParameters()[0]
}
},
Properties = global::System.Array.Empty<global::TUnit.Core.PropertyMetadata>(),
Parent = null
};
foreach (var prop in classMetadata.Properties)
{
prop.ClassMetadata = classMetadata;
prop.ContainingTypeMetadata = classMetadata;
}
return classMetadata;
})
},
InstanceFactory = (typeArgs, args) =>
{
return new global::TUnit.TestProject.CancellationTokenTriggeredTests(TUnit.Core.Helpers.CastHelper.Cast<int>(args[0]));
},
InvokeTypedTest = static (instance, args, cancellationToken) =>
{
try
{
return new global::System.Threading.Tasks.ValueTask(instance.CancellationTokenIsTriggered(cancellationToken));
}
catch (global::System.Exception ex)
{
return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(ex));
}
},
};
metadata.UseRuntimeDataGeneration(testSessionId);
yield return metadata;
yield break;
}
}
internal static class TUnit_TestProject_CancellationTokenTriggeredTests_CancellationTokenIsTriggered__CancellationToken_ModuleInitializer
{
[global::System.Runtime.CompilerServices.ModuleInitializer]
public static void Initialize()
{
global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests), new TUnit_TestProject_CancellationTokenTriggeredTests_CancellationTokenIsTriggered__CancellationToken_TestSource());
}
}
3 changes: 2 additions & 1 deletion TUnit.Engine/Building/TestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ public async Task<IEnumerable<AbstractExecutableTest>> BuildTestsFromMetadataAsy
TestMetadata = metadata.MethodMetadata,
Events = new TestContextEvents(),
StateBag = new ConcurrentDictionary<string, object?>(),
DataSourceAttribute = methodDataSource
DataSourceAttribute = methodDataSource,
InitializedAttributes = testBuilderContext.InitializedAttributes // Preserve attributes from parent context
};

classData = DataUnwrapper.Unwrap(await classDataFactory() ?? []);
Expand Down
3 changes: 3 additions & 0 deletions TUnit.Engine/TestExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ private static async ValueTask ExecuteTestAsync(AbstractExecutableTest executabl
// Set the test start time when we actually begin executing the test
executableTest.Context.TestStart = DateTimeOffset.UtcNow;

// Set the cancellation token on the context so source-generated tests can access it
executableTest.Context.CancellationToken = cancellationToken;

if (executableTest.Context.InternalDiscoveredTest?.TestExecutor is { } testExecutor)
{
await testExecutor.ExecuteTest(executableTest.Context,
Expand Down
52 changes: 52 additions & 0 deletions TUnit.TestProject/TimeoutCancellationTokenTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,55 @@

public class FiveSecondTimeout() : TimeoutAttribute(5_000);
}

// Positive test: Timeout does NOT fire for quick tests with data sources
[MethodDataSource(nameof(DataSource))]
[Timeout(30_000)] // Long timeout (30 seconds)
[EngineTest(ExpectedResult.Pass)]
[Category("Timeout Cancellation Token Tests")]
public class TimeoutDoesNotFireTests(int value)
{
[Test]
public async Task QuickTestDoesNotTimeout(CancellationToken cancellationToken)
{
await Assert.That(value).IsEqualTo(1);
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); // Short delay
}

public static IEnumerable<int> DataSource()
{
yield return 1;
}
}

// Test to verify cancellation token is actually triggered when timeout fires
[MethodDataSource(nameof(DataSource))]
[Timeout(5_000)]
[EngineTest(ExpectedResult.Failure)]
[Category("Timeout Cancellation Token Tests")]
public class CancellationTokenTriggeredTests(int value)

Check warning on line 104 in TUnit.TestProject/TimeoutCancellationTokenTests.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Parameter 'value' is unread.

Check warning on line 104 in TUnit.TestProject/TimeoutCancellationTokenTests.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Parameter 'value' is unread.

Check warning on line 104 in TUnit.TestProject/TimeoutCancellationTokenTests.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Parameter 'value' is unread.
{
[Test]
public async Task CancellationTokenIsTriggered(CancellationToken cancellationToken)
{
var fired = false;
cancellationToken.Register(() => fired = true);

try
{
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
}
catch (OperationCanceledException)
{
// Expected - timeout should trigger cancellation
}

// Verify token was cancelled
await Assert.That(fired).IsTrue();
}

public static IEnumerable<int> DataSource()
{
yield return 1;
}
}
Loading