Skip to content

Commit 88e6508

Browse files
thomhurstclaude
andauthored
fix: add support for [GenerateGenericTest] on generic classes (#4431) (#4439)
* fix: add support for [GenerateGenericTest] on generic classes (#4431) ## Summary - Fix generic class handling in reflection mode by supporting [GenerateGenericTest] attribute - Fix init-only property setters for generic types (UnsafeAccessor doesn't work with open generics) ## Changes - **ReflectionTestDataCollector.cs**: Handle [GenerateGenericTest] attributes that explicitly specify type arguments for generic test classes - **TestMetadataGenerator.cs**: Use reflection-based setter for init-only properties on generic types instead of UnsafeAccessor ## Test cases Added test files in TUnit.TestProject/Bugs/4431/ demonstrating: - Generic class with [GenerateGenericTest] attribute - Generic class with [ClassDataSource] property injection - Various combinations of generic classes/methods with data sources Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address review feedback - AOT compatibility and snapshot tests - Replace reflection-based setter for init-only properties on generic types with NotSupportedException (AOT-compatible) - Add missing verified files for GenericPropertyInjection tests - Add source generator test for generic method with data source Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 82d75ff commit 88e6508

11 files changed

Lines changed: 6439 additions & 88 deletions

TUnit.Core.SourceGenerator.Tests/GenericMethodWithDataSourceTests.Generic_Method_With_MethodDataSource_Should_Generate_Tests.verified.txt

Lines changed: 3257 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using TUnit.Core.SourceGenerator.Tests.Options;
2+
3+
namespace TUnit.Core.SourceGenerator.Tests;
4+
5+
internal class GenericMethodWithDataSourceTests : TestsBase
6+
{
7+
[Test]
8+
public Task Generic_Method_With_MethodDataSource_Should_Generate_Tests() => RunTest(Path.Combine(Git.RootDirectory.FullName,
9+
"TUnit.TestProject",
10+
"Bugs",
11+
"4431",
12+
"ComprehensiveGenericTests.cs"),
13+
async generatedFiles =>
14+
{
15+
// This test verifies that generic methods with [GenerateGenericTest] AND [MethodDataSource]
16+
// generate proper test metadata including both the type instantiations and the data source
17+
18+
// Find all files related to GenericMethod_With_DataSource
19+
var matchingFiles = generatedFiles.Where(f =>
20+
f.Contains("GenericMethod_With_DataSource") &&
21+
f.Contains("TestSource")).ToList();
22+
23+
// Debug: output the matching file count
24+
await Assert.That(matchingFiles.Count).IsGreaterThanOrEqualTo(1)
25+
.Because($"At least one test source should be generated for GenericMethod_With_DataSource. Found {matchingFiles.Count} matching files.");
26+
27+
// Debug: output if any file contains Int32
28+
var hasInt32 = matchingFiles.Any(f => f.Contains("Int32"));
29+
var hasDouble = matchingFiles.Any(f => f.Contains("Double"));
30+
31+
// Debug: Look at ConcreteInstantiations contents
32+
foreach (var file in matchingFiles)
33+
{
34+
if (file.Contains("ConcreteInstantiations"))
35+
{
36+
var idx = file.IndexOf("ConcreteInstantiations");
37+
var snippet = file.Substring(idx, Math.Min(500, file.Length - idx));
38+
Console.WriteLine($"[DEBUG] ConcreteInstantiations snippet: {snippet}");
39+
}
40+
}
41+
42+
// Find the file WITH concrete instantiations (uses typeof(int) and typeof(double) syntax)
43+
var genericMethodWithDataSourceFile = matchingFiles.FirstOrDefault(f =>
44+
f.Contains("typeof(int)") && f.Contains("typeof(double)"));
45+
46+
await Assert.That(genericMethodWithDataSourceFile).IsNotNull()
47+
.Because("A test source should be generated for GenericMethod_With_DataSource with concrete type instantiations (int and double)");
48+
49+
// If we found the file, verify it has both type instantiations
50+
if (genericMethodWithDataSourceFile != null)
51+
{
52+
// Verify it includes the MethodDataSource
53+
await Assert.That(genericMethodWithDataSourceFile).Contains("MethodDataSourceAttribute")
54+
.Because("Should include the MethodDataSource attribute in DataSources");
55+
await Assert.That(genericMethodWithDataSourceFile).Contains("GetStrings")
56+
.Because("Should reference the GetStrings method");
57+
}
58+
59+
// Look for FullyGeneric_With_DataSources test generation (class+method generic with data source)
60+
var fullyGenericFiles = generatedFiles.Where(f =>
61+
f.Contains("FullyGeneric_With_DataSources") &&
62+
f.Contains("TestSource")).ToList();
63+
64+
await Assert.That(fullyGenericFiles.Count).IsGreaterThanOrEqualTo(1)
65+
.Because("At least one test source should be generated for FullyGeneric_With_DataSources");
66+
67+
// Find the file with concrete instantiations (uses typeof(int) or typeof(double) syntax)
68+
var fullyGenericFile = fullyGenericFiles.FirstOrDefault(f =>
69+
f.Contains("ConcreteInstantiations") &&
70+
(f.Contains("typeof(int)") || f.Contains("typeof(double)")));
71+
72+
// Verify it has the data source
73+
if (fullyGenericFile != null)
74+
{
75+
await Assert.That(fullyGenericFile).Contains("MethodDataSourceAttribute")
76+
.Because("Should include the MethodDataSource attribute in DataSources");
77+
await Assert.That(fullyGenericFile).Contains("GetBooleans")
78+
.Because("Should reference the GetBooleans method");
79+
}
80+
});
81+
}

0 commit comments

Comments
 (0)