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
21 changes: 17 additions & 4 deletions TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@
}

// Find the data source method
var dataSourceMethod = targetType.GetMembers(methodName)

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.

Check warning on line 783 in TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Possible null reference argument for parameter 'name' in 'ImmutableArray<ISymbol> INamespaceOrTypeSymbol.GetMembers(string name)'.
.OfType<IMethodSymbol>()
.FirstOrDefault();

Expand Down Expand Up @@ -1833,10 +1833,8 @@
{
var constructorArgs = attributeData.ConstructorArguments;

// Get ProceedOnFailure from named arguments
var proceedOnFailure = attributeData.NamedArguments
.FirstOrDefault(na => na.Key == "ProceedOnFailure")
.Value.Value as bool? ?? false;
// Extract ProceedOnFailure property value
var proceedOnFailure = GetProceedOnFailureValue(attributeData);

// Handle the different constructor overloads of DependsOnAttribute
if (constructorArgs.Length == 1)
Expand Down Expand Up @@ -1946,6 +1944,21 @@
}
}

private static bool GetProceedOnFailureValue(AttributeData attributeData)
{
// Look for ProceedOnFailure property in named arguments
foreach (var namedArg in attributeData.NamedArguments)
{
if (namedArg.Key == "ProceedOnFailure" && namedArg.Value.Value is bool proceedOnFailure)
{
return proceedOnFailure;
}
}

// Default value is false
return false;
}

private static string GetDefaultValueString(IParameterSymbol parameter)
{
if (!parameter.HasExplicitDefaultValue)
Expand Down
24 changes: 24 additions & 0 deletions TUnit.Core/Exceptions/TestDependencyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace TUnit.Core.Exceptions;

/// <summary>
/// Exception thrown when a test dependency has failed and the test cannot proceed
/// </summary>
public class TestDependencyException : TUnitException
{
/// <summary>
/// Name of the dependency that failed
/// </summary>
public string DependencyName { get; }

/// <summary>
/// Whether the test should proceed despite the dependency failure
/// </summary>
public bool ProceedOnFailure { get; }

public TestDependencyException(string dependencyName, bool proceedOnFailure)
: base($"Dependency failed: {dependencyName}")
{
DependencyName = dependencyName;
ProceedOnFailure = proceedOnFailure;
}
}
23 changes: 17 additions & 6 deletions TUnit.Core/TestDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,40 @@ public sealed class TestDependency : IEquatable<TestDependency>

public int MethodGenericArity { get; init; }

/// <summary>
/// Gets or sets a value indicating whether this test should proceed even if its dependencies have failed.
/// When set to false (default), the test will be skipped if any of its dependencies failed.
/// When set to true, the test will run even if its dependencies failed.
/// </summary>
public bool ProceedOnFailure { get; init; }

public static TestDependency FromMethodName(string methodName)
public static TestDependency FromMethodName(string methodName, bool proceedOnFailure = false)
{
return new TestDependency { MethodName = methodName };
return new TestDependency
{
MethodName = methodName,
ProceedOnFailure = proceedOnFailure
};
}

public static TestDependency FromClass(Type classType)
public static TestDependency FromClass(Type classType, bool proceedOnFailure = false)
{
return new TestDependency
{
ClassType = classType,
ClassGenericArity = classType.IsGenericTypeDefinition ? classType.GetGenericArguments().Length : 0
ClassGenericArity = classType.IsGenericTypeDefinition ? classType.GetGenericArguments().Length : 0,
ProceedOnFailure = proceedOnFailure
};
}

public static TestDependency FromClassAndMethod(Type classType, string methodName)
public static TestDependency FromClassAndMethod(Type classType, string methodName, bool proceedOnFailure = false)
{
return new TestDependency
{
ClassType = classType,
ClassGenericArity = classType.IsGenericTypeDefinition ? classType.GetGenericArguments().Length : 0,
MethodName = methodName
MethodName = methodName,
ProceedOnFailure = proceedOnFailure
};
}

Expand Down
4 changes: 2 additions & 2 deletions TUnit.Engine.Tests/DependsOnTestsWithoutProceedOnFailure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ await RunTestsWithFilter(
result => result.ResultSummary.Outcome.ShouldBe("Failed"),
result => result.ResultSummary.Counters.Total.ShouldBe(2),
result => result.ResultSummary.Counters.Passed.ShouldBe(0),
result => result.ResultSummary.Counters.Failed.ShouldBe(2),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
result => result.ResultSummary.Counters.Failed.ShouldBe(1),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(1)
]);
}
}
39 changes: 39 additions & 0 deletions TUnit.Engine/Services/SingleTestExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.TestHost;
using TUnit.Core;
using TUnit.Core.Exceptions;
using TUnit.Core.Logging;
using TUnit.Engine.Exceptions;
using TUnit.Engine.Extensions;
Expand Down Expand Up @@ -99,6 +100,8 @@ await PropertyInjectionService.InjectPropertiesAsync(

await _eventReceiverOrchestrator.InitializeAllEligibleObjectsAsync(test.Context, cancellationToken);

CheckDependenciesAndThrowIfShouldSkip(test);

var classContext = test.Context.ClassContext;
var assemblyContext = classContext.AssemblyContext;
var sessionContext = assemblyContext.TestSessionContext;
Expand Down Expand Up @@ -126,6 +129,11 @@ await PropertyInjectionService.InjectPropertiesAsync(
await ExecuteTestWithHooksAsync(test, instance, cancellationToken);
}
}
catch (TestDependencyException e)
{
test.Context.SkipReason = e.Message;
return await HandleSkippedTestInternalAsync(test, cancellationToken);
}
catch (Exception ex)
{
HandleTestFailure(test, ex);
Expand Down Expand Up @@ -449,4 +457,35 @@ private static void RestoreHookContexts(TestContext context)
ClassHookContext.Current = context.ClassContext;
}
}

private void CheckDependenciesAndThrowIfShouldSkip(AbstractExecutableTest test)
{
var failedDependenciesNotAllowingProceed = new List<string>();

foreach (var dependency in test.Dependencies)
{
// Check if the dependency has failed or timed out
if (dependency.Test.State == TestState.Failed || dependency.Test.State == TestState.Timeout)
{
// If this dependency doesn't allow proceeding on failure, add it to the list
if (!dependency.ProceedOnFailure)
{
var dependencyName = GetDependencyDisplayName(dependency.Test);
failedDependenciesNotAllowingProceed.Add(dependencyName);
}
}
}

// Only throw if there are dependencies that don't allow proceeding
if (failedDependenciesNotAllowingProceed.Count > 0)
{
var dependencyNames = string.Join(", ", failedDependenciesNotAllowingProceed);
throw new TestDependencyException(dependencyNames, false);
}
}

private string GetDependencyDisplayName(AbstractExecutableTest dependency)
{
return dependency.Context?.GetDisplayName() ?? $"{dependency.Context?.TestDetails.ClassType.Name}.{dependency.Context?.TestDetails.TestName}" ?? "Unknown";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1344,9 +1344,9 @@ namespace
public override int GetHashCode() { }
public bool Matches(.TestMetadata test, .TestMetadata? dependentTest = null) { }
public override string ToString() { }
public static .TestDependency FromClass( classType) { }
public static .TestDependency FromClassAndMethod( classType, string methodName) { }
public static .TestDependency FromMethodName(string methodName) { }
public static .TestDependency FromClass( classType, bool proceedOnFailure = false) { }
public static .TestDependency FromClassAndMethod( classType, string methodName, bool proceedOnFailure = false) { }
public static .TestDependency FromMethodName(string methodName, bool proceedOnFailure = false) { }
}
public class TestDetails
{
Expand Down Expand Up @@ -1772,6 +1772,12 @@ namespace .Exceptions
public TUnitException(string? message) { }
public TUnitException(string? message, ? innerException) { }
}
public class TestDependencyException : .
{
public TestDependencyException(string dependencyName, bool proceedOnFailure) { }
public string DependencyName { get; }
public bool ProceedOnFailure { get; }
}
public class TestFailedInitializationException :
{
public TestFailedInitializationException(string? message, ? innerException) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1344,9 +1344,9 @@ namespace
public override int GetHashCode() { }
public bool Matches(.TestMetadata test, .TestMetadata? dependentTest = null) { }
public override string ToString() { }
public static .TestDependency FromClass( classType) { }
public static .TestDependency FromClassAndMethod( classType, string methodName) { }
public static .TestDependency FromMethodName(string methodName) { }
public static .TestDependency FromClass( classType, bool proceedOnFailure = false) { }
public static .TestDependency FromClassAndMethod( classType, string methodName, bool proceedOnFailure = false) { }
public static .TestDependency FromMethodName(string methodName, bool proceedOnFailure = false) { }
}
public class TestDetails
{
Expand Down Expand Up @@ -1772,6 +1772,12 @@ namespace .Exceptions
public TUnitException(string? message) { }
public TUnitException(string? message, ? innerException) { }
}
public class TestDependencyException : .
{
public TestDependencyException(string dependencyName, bool proceedOnFailure) { }
public string DependencyName { get; }
public bool ProceedOnFailure { get; }
}
public class TestFailedInitializationException :
{
public TestFailedInitializationException(string? message, ? innerException) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1267,9 +1267,9 @@ namespace
public override int GetHashCode() { }
public bool Matches(.TestMetadata test, .TestMetadata? dependentTest = null) { }
public override string ToString() { }
public static .TestDependency FromClass( classType) { }
public static .TestDependency FromClassAndMethod( classType, string methodName) { }
public static .TestDependency FromMethodName(string methodName) { }
public static .TestDependency FromClass( classType, bool proceedOnFailure = false) { }
public static .TestDependency FromClassAndMethod( classType, string methodName, bool proceedOnFailure = false) { }
public static .TestDependency FromMethodName(string methodName, bool proceedOnFailure = false) { }
}
public class TestDetails
{
Expand Down Expand Up @@ -1683,6 +1683,12 @@ namespace .Exceptions
public TUnitException(string? message) { }
public TUnitException(string? message, ? innerException) { }
}
public class TestDependencyException : .
{
public TestDependencyException(string dependencyName, bool proceedOnFailure) { }
public string DependencyName { get; }
public bool ProceedOnFailure { get; }
}
public class TestFailedInitializationException :
{
public TestFailedInitializationException(string? message, ? innerException) { }
Expand Down
Loading