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
34 changes: 20 additions & 14 deletions src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
private const string InstanceMethodDataSourceAttributeName = "global::TUnit.Core.InstanceMethodDataSourceAttribute";

private static readonly DiagnosticDescriptor GenerationErrorDescriptor = new(
GeneratorDiagnosticIds.TestMetadataGenerationFailed,

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

View workflow job for this annotation

GitHub Actions / modularpipeline (sv-SE)

Enable analyzer release tracking for the analyzer project containing rule 'TUNIT0999' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Enable analyzer release tracking for the analyzer project containing rule 'TUNIT0999' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Enable analyzer release tracking for the analyzer project containing rule 'TUNIT0999' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Enable analyzer release tracking for the analyzer project containing rule 'TUNIT0999' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Enable analyzer release tracking for the analyzer project containing rule 'TUNIT0999' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
"Source Generation Error",
"Failed to generate test metadata for {0}.{1}: {2}",
"TUnit",
Expand Down Expand Up @@ -995,7 +995,7 @@
// type could satisfy: explain where the type argument came from (#7116).
if (violationSource is null
&& specificAttr?.AttributeClass is { } dataSourceClass
&& (dataSourceClass.Name is "MethodDataSourceAttribute" or "InstanceMethodDataSourceAttribute" || DataSourceAttributeHelper.IsTypedDataSourceAttribute(dataSourceClass))
&& (IsMethodDataSourceAttribute(dataSourceClass) || DataSourceAttributeHelper.IsTypedDataSourceAttribute(dataSourceClass))
&& TryFindConstraintViolation(testMethod.MethodSymbol.TypeParameters, methodTypeArgs, testMethod.CompilationContext.Compilation,
out _, out var violatingIndex, testMethod.TypeSymbol, classTypeArgs)
&& violatingIndex >= 0
Expand Down Expand Up @@ -1049,17 +1049,18 @@
if (specificAttr is not null && GenericMethodInferencePolicy.JoinsDuplicate(
isLiteralRow: specificAttr.AttributeClass?.IsOrInherits("global::TUnit.Core.ArgumentsAttribute") == true,
joinsInferredSources: !isClassRow && (joinsDuplicateMethodSources || scopesClassMethodSources
|| (specificAttr.AttributeClass?.Name is "MethodDataSourceAttribute" or "InstanceMethodDataSourceAttribute"
|| (IsMethodDataSourceAttribute(specificAttr.AttributeClass)
&& !HasExplicitGenericTestTypes(testMethod)))))
{
existing.AddRow(specificAttr, isClassRow);
}

// Literal rows and inferred static or instance sources join the existing class/method pair.
// Explicit method types retain reflection mode's duplicate-source behavior.
if (methodRow?.AttributeClass?.IsOrInherits("global::TUnit.Core.ArgumentsAttribute") == true
|| (methodRow?.AttributeClass?.Name is "MethodDataSourceAttribute" or "InstanceMethodDataSourceAttribute"
&& !HasExplicitGenericTestTypes(testMethod)))
if (methodRow is not null
&& (methodRow.AttributeClass?.IsOrInherits("global::TUnit.Core.ArgumentsAttribute") == true
|| (IsMethodDataSourceAttribute(methodRow.AttributeClass)
&& !HasExplicitGenericTestTypes(testMethod))))
{
existing.AddRow(methodRow, isClassRow: false);
}
Expand Down Expand Up @@ -1227,7 +1228,7 @@
// the same instantiation first. This helper is called only for explicit method types.
foreach (var source in testMethod.MethodAttributes)
{
if (source.AttributeClass?.Name == "InstanceMethodDataSourceAttribute")
if (source.AttributeClass?.GloballyQualifiedNonGeneric() == InstanceMethodDataSourceAttributeName)
{
existing.AddRow(source, isClassRow: false);
}
Expand All @@ -1252,13 +1253,13 @@
// Buckets are independent: an attribute may match more than one (e.g.
// MethodDataSourceAttribute is also an IDataSourceAttribute), matching the original
// behaviour where each `.Where(...)` scan was evaluated separately.
switch (attributeClass.Name)
switch (attributeClass.GloballyQualifiedNonGeneric())
{
case "ArgumentsAttribute" when attributeClass.IsOrInherits("global::TUnit.Core.ArgumentsAttribute"):
case "global::TUnit.Core.ArgumentsAttribute":
(methodArgumentsBucket ??= []).Add(attribute);
break;
case "MethodDataSourceAttribute":
case "InstanceMethodDataSourceAttribute":
case "global::TUnit.Core.MethodDataSourceAttribute":
case InstanceMethodDataSourceAttributeName:
(methodDataSourceBucket ??= []).Add(attribute);
break;
}
Expand Down Expand Up @@ -1466,7 +1467,7 @@
foreach (var source in typedDataSourceBucket ?? Enumerable.Empty<AttributeData>())
{
if (source.AttributeClass?.IsOrInherits("global::TUnit.Core.ArgumentsAttribute") != true
&& source.AttributeClass?.Name != "MethodDataSourceAttribute"
&& source.AttributeClass?.GloballyQualifiedNonGeneric() != "global::TUnit.Core.MethodDataSourceAttribute"
&& (uninferredMethodDataSources is null || !uninferredMethodDataSources.Contains(source)))
{
(uninferredMethodDataSources ??= []).Add(source);
Expand Down Expand Up @@ -2058,7 +2059,7 @@
{
foreach (var attribute in uncovered)
{
if (attribute.AttributeClass?.Name is "MethodDataSourceAttribute" or "InstanceMethodDataSourceAttribute"
if (IsMethodDataSourceAttribute(attribute.AttributeClass)
&& InferTypesFromMethodDataSourceRowType(testMethod, attribute) is { } rowTypes
&& FindInaccessibleType(rowTypes, testMethod.CompilationContext.Compilation) is { } inaccessibleType)
{
Expand Down Expand Up @@ -7150,6 +7151,10 @@
return null;
}

private static bool IsMethodDataSourceAttribute(INamedTypeSymbol? attributeClass) =>
attributeClass?.GloballyQualifiedNonGeneric() is
"global::TUnit.Core.MethodDataSourceAttribute" or InstanceMethodDataSourceAttributeName;

private static bool HasExplicitGenericTestTypes(TestMethodMetadata testMethod) =>
testMethod.MethodAttributes.Any(static attribute =>
attribute.AttributeClass?.IsOrInherits("global::TUnit.Core.GenerateGenericTestAttribute") is true);
Expand All @@ -7164,7 +7169,7 @@
{
// Reflection mode runs instance sources on the explicit method types when supplied, without
// adding declared-row instantiations. Preserve conversions enabled by those explicit types.
if (mdsAttr.AttributeClass?.Name == "InstanceMethodDataSourceAttribute"
if (mdsAttr.AttributeClass?.GloballyQualifiedNonGeneric() == InstanceMethodDataSourceAttributeName
&& HasExplicitGenericTestTypes(testMethod))
{
return null;
Expand Down Expand Up @@ -7209,7 +7214,8 @@
var compilation = testMethod.CompilationContext.Compilation;
foreach (var attribute in testMethod.TypeSymbol.GetAttributes())
{
if (attribute.AttributeClass is not { Name: "MethodDataSourceAttribute", IsGenericType: false }
if (attribute.AttributeClass is not { IsGenericType: false } attributeClass
|| attributeClass.GloballyQualifiedNonGeneric() != "global::TUnit.Core.MethodDataSourceAttribute"
|| attribute.ConstructorArguments is not [{ Value: string memberName }])
{
continue;
Expand Down
16 changes: 16 additions & 0 deletions tests/TUnit.Core.SourceGenerator.Tests/Bugs/7648/Tests7648.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace TUnit.Core.SourceGenerator.Tests;

internal class Tests7648 : TestsBase
{
[Test]
public Task Test() => RunTest(Path.Combine(
Git.TestsDirectory.FullName, "TUnit.TestProject", "Bugs", "_7648", "MethodSourceIdentityTests.cs"),
async generatedFiles =>
{
var generated = string.Join("\n", generatedFiles);
await Assert.That(generated).DoesNotContain("StaticSource<string>");
await Assert.That(generated).DoesNotContain("InstanceSource<string>");
await Assert.That(generated).Contains("StaticSource<int>");
await Assert.That(generated).Contains("InstanceSource<int>");
});
}
162 changes: 162 additions & 0 deletions tests/TUnit.Core.SourceGenerator.Tests/Tests7648.Test.verified.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// <auto-generated/>
#pragma warning disable

#nullable enable
namespace TUnit.Generated;
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("TUnit", "VERSION_SCRUBBED")]
internal static class TUnit_TestProject_Bugs__7648_MethodSourceIdentityTests_StaticSource__T_TestSource
{
private static readonly global::TUnit.Core.ClassMetadata __classMetadata = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests", new global::TUnit.Core.ClassMetadata
{
Type = typeof(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests),
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests)),
Name = "MethodSourceIdentityTests",
Namespace = "TUnit.TestProject.Bugs._7648",
Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", "TestsBase`1"),
Parameters = global::System.Array.Empty<global::TUnit.Core.ParameterMetadata>(),
Properties = global::System.Array.Empty<global::TUnit.Core.PropertyMetadata>(),
Parent = null
});
private static readonly global::System.Type __classType = typeof(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests);
private static readonly global::TUnit.Core.MethodMetadata __mm_0 = global::TUnit.Core.MethodMetadataFactory.Create("StaticSource", __classType, typeof(global::System.Threading.Tasks.Task), __classMetadata, genericTypeCount: 1, parameters: global::TUnit.Core.ParameterMetadataFactory.ForGenericMethod(typeof(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests), "StaticSource", false, 1, new global::TUnit.Core.ParameterMetadata[]
{
global::TUnit.Core.ParameterMetadataFactory.Create(typeof(object), "value", new global::TUnit.Core.GenericParameter(0, true, "T"), false)
}));
private static global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests __CreateInstance_0(global::System.Type[] typeArgs, object?[] args)
{
return new global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests();
}
private static global::System.Threading.Tasks.ValueTask __Invoke_0(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests instance, int methodIndex, object?[] args, global::System.Threading.CancellationToken cancellationToken)
{
try
{
return new global::System.Threading.Tasks.ValueTask(instance.StaticSource<int>(global::TUnit.Core.Helpers.CastHelper.Cast<int>(args[0])));
}
catch (global::System.Exception ex)
{
return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(ex));
}
}
private static global::System.Attribute[] __Attributes_0(int groupIndex)
{
return new global::System.Attribute[]
{
new global::TUnit.Core.TestAttribute(),
new global::TUnit.TestProject.Bugs._7648.MethodDataSourceAttribute("UnrelatedRows"),
new global::TUnit.TestProject.Bugs._7648.InstanceMethodDataSourceAttribute("UnrelatedRows"),
new global::TUnit.Core.MethodDataSourceAttribute("RealRows"),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass)
};
}
public static readonly global::TUnit.Core.TestEntry<global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests>[] Entries_0 = new global::TUnit.Core.TestEntry<global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests>[]
{
global::TUnit.Core.TestEntryFactory.Create<global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests>(
methodName: "StaticSource<int>",
fullyQualifiedName: "TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests.StaticSource<int>",
methodTypeArgumentsIdentity: "<int>",
filePath: "",
lineNumber: 24,
hasDataSource: true,
testDataSources: new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.MethodDataSourceAttribute("RealRows")
{
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromEnumerable(dataGeneratorMetadata, static () => global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests.RealRows()),
},
},
methodMetadata: __mm_0,
createInstance: __CreateInstance_0,
invokeBody: __Invoke_0,
methodIndex: 0,
createAttributes: __Attributes_0,
attributeGroupIndex: 0),
};
}
internal static partial class TUnit_TestRegistration
{
static readonly int _r_TUnit_TestProject_Bugs__7648_MethodSourceIdentityTests_StaticSource__T_TestSource_0 = global::TUnit.Core.SourceRegistrar.RegisterEntries<global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests>(static () => TUnit_TestProject_Bugs__7648_MethodSourceIdentityTests_StaticSource__T_TestSource.Entries_0);
}


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

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

#nullable enable
namespace TUnit.Generated;
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("TUnit", "VERSION_SCRUBBED")]
internal static class TUnit_TestProject_Bugs__7648_MethodSourceIdentityTests_InstanceSource__T_TestSource
{
private static readonly global::TUnit.Core.ClassMetadata __classMetadata = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests", new global::TUnit.Core.ClassMetadata
{
Type = typeof(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests),
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests)),
Name = "MethodSourceIdentityTests",
Namespace = "TUnit.TestProject.Bugs._7648",
Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", "TestsBase`1"),
Parameters = global::System.Array.Empty<global::TUnit.Core.ParameterMetadata>(),
Properties = global::System.Array.Empty<global::TUnit.Core.PropertyMetadata>(),
Parent = null
});
private static readonly global::System.Type __classType = typeof(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests);
private static readonly global::TUnit.Core.MethodMetadata __mm_0 = global::TUnit.Core.MethodMetadataFactory.Create("InstanceSource", __classType, typeof(global::System.Threading.Tasks.Task), __classMetadata, genericTypeCount: 1, parameters: global::TUnit.Core.ParameterMetadataFactory.ForGenericMethod(typeof(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests), "InstanceSource", false, 1, new global::TUnit.Core.ParameterMetadata[]
{
global::TUnit.Core.ParameterMetadataFactory.Create(typeof(object), "value", new global::TUnit.Core.GenericParameter(0, true, "T"), false)
}));
private static global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests __CreateInstance_0(global::System.Type[] typeArgs, object?[] args)
{
return new global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests();
}
private static global::System.Threading.Tasks.ValueTask __Invoke_0(global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests instance, int methodIndex, object?[] args, global::System.Threading.CancellationToken cancellationToken)
{
try
{
return new global::System.Threading.Tasks.ValueTask(instance.InstanceSource<int>(global::TUnit.Core.Helpers.CastHelper.Cast<int>(args[0])));
}
catch (global::System.Exception ex)
{
return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(ex));
}
}
private static global::System.Attribute[] __Attributes_0(int groupIndex)
{
return new global::System.Attribute[]
{
new global::TUnit.Core.TestAttribute(),
new global::TUnit.TestProject.Bugs._7648.MethodDataSourceAttribute("UnrelatedRows"),
new global::TUnit.TestProject.Bugs._7648.InstanceMethodDataSourceAttribute("UnrelatedRows"),
new global::TUnit.Core.InstanceMethodDataSourceAttribute("InstanceRows"),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass)
};
}
public static readonly global::TUnit.Core.TestEntry<global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests>[] Entries_0 = new global::TUnit.Core.TestEntry<global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests>[]
{
global::TUnit.Core.TestEntryFactory.Create<global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests>(
methodName: "InstanceSource<int>",
fullyQualifiedName: "TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests.InstanceSource<int>",
methodTypeArgumentsIdentity: "<int>",
filePath: "",
lineNumber: 34,
hasDataSource: true,
testDataSources: new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.InstanceMethodDataSourceAttribute("InstanceRows")
{
Factory = static dataGeneratorMetadata => global::TUnit.Core.Helpers.DataSourceFactories.FromInstanceEnumerable(dataGeneratorMetadata, static instance => (object?)((global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests)instance).InstanceRows() ?? global::System.Array.Empty<object>()),
},
},
methodMetadata: __mm_0,
createInstance: __CreateInstance_0,
invokeBody: __Invoke_0,
methodIndex: 0,
createAttributes: __Attributes_0,
attributeGroupIndex: 0),
};
}
internal static partial class TUnit_TestRegistration
{
static readonly int _r_TUnit_TestProject_Bugs__7648_MethodSourceIdentityTests_InstanceSource__T_TestSource_0 = global::TUnit.Core.SourceRegistrar.RegisterEntries<global::TUnit.TestProject.Bugs._7648.MethodSourceIdentityTests>(static () => TUnit_TestProject_Bugs__7648_MethodSourceIdentityTests_InstanceSource__T_TestSource.Entries_0);
}
18 changes: 18 additions & 0 deletions tests/TUnit.Engine.Tests/Issue7648Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Shouldly;
using TUnit.Engine.Tests.Enums;

namespace TUnit.Engine.Tests;

public class Issue7648Tests(TestMode testMode) : InvokableTestBase(testMode)
{
[Test]
public async Task Unrelated_Method_Source_Attributes_Do_Not_Add_Rows()
{
await RunTestsWithFilter("/*/TUnit.TestProject.Bugs._7648/MethodSourceIdentityTests/*",
[
result => result.ResultSummary.Counters.Total.ShouldBe(2),
result => result.ResultSummary.Counters.Passed.ShouldBe(2),
result => result.ResultSummary.Counters.Failed.ShouldBe(0)
]);
}
}
Loading
Loading