Skip to content

Commit 93f684f

Browse files
authored
feat: add support for enabling/disabling TUnit source generation via project properties (#3635)
* feat: add support for enabling/disabling TUnit source generation via project properties * feat: add guidance for optimizing build performance in reflection mode
1 parent 0c57efc commit 93f684f

File tree

7 files changed

+152
-11
lines changed

7 files changed

+152
-11
lines changed

TUnit.Core.SourceGenerator/CodeGenerators/AssemblyLoaderGenerator.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,27 @@ public class AssemblyLoaderGenerator : IIncrementalGenerator
1717
];
1818
public void Initialize(IncrementalGeneratorInitializationContext context)
1919
{
20+
var enabledProvider = context.AnalyzerConfigOptionsProvider
21+
.Select((options, _) =>
22+
{
23+
options.GlobalOptions.TryGetValue("build_property.EnableTUnitSourceGeneration", out var value);
24+
return !string.Equals(value, "false", StringComparison.OrdinalIgnoreCase);
25+
});
26+
2027
var provider = context.CompilationProvider
21-
.WithComparer(new PreventCompilationTriggerOnEveryKeystrokeComparer());
28+
.WithComparer(new PreventCompilationTriggerOnEveryKeystrokeComparer())
29+
.Combine(enabledProvider);
30+
31+
context.RegisterSourceOutput(provider, (sourceProductionContext, data) =>
32+
{
33+
var (compilation, isEnabled) = data;
34+
if (!isEnabled)
35+
{
36+
return;
37+
}
2238

23-
context.RegisterSourceOutput(provider, (sourceProductionContext, source) => GenerateCode(sourceProductionContext, source));
39+
GenerateCode(sourceProductionContext, compilation);
40+
});
2441
}
2542

2643
private void GenerateCode(SourceProductionContext context, Compilation compilation)

TUnit.Core.SourceGenerator/CodeGenerators/DisableReflectionScannerGenerator.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,27 @@ public class DisableReflectionScannerGenerator : IIncrementalGenerator
99
{
1010
public void Initialize(IncrementalGeneratorInitializationContext context)
1111
{
12+
var enabledProvider = context.AnalyzerConfigOptionsProvider
13+
.Select((options, _) =>
14+
{
15+
options.GlobalOptions.TryGetValue("build_property.EnableTUnitSourceGeneration", out var value);
16+
return !string.Equals(value, "false", StringComparison.OrdinalIgnoreCase);
17+
});
18+
1219
var provider = context.CompilationProvider
13-
.WithComparer(new PreventCompilationTriggerOnEveryKeystrokeComparer());
20+
.WithComparer(new PreventCompilationTriggerOnEveryKeystrokeComparer())
21+
.Combine(enabledProvider);
22+
23+
context.RegisterSourceOutput(provider, (sourceProductionContext, data) =>
24+
{
25+
var (_, isEnabled) = data;
26+
if (!isEnabled)
27+
{
28+
return;
29+
}
1430

15-
context.RegisterSourceOutput(provider, (sourceProductionContext, _) => GenerateCode(sourceProductionContext));
31+
GenerateCode(sourceProductionContext);
32+
});
1633
}
1734

1835
private void GenerateCode(SourceProductionContext context)

TUnit.Core.SourceGenerator/CodeGenerators/DynamicTestsGenerator.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,31 @@ public class DynamicTestsGenerator : IIncrementalGenerator
1111
{
1212
public void Initialize(IncrementalGeneratorInitializationContext context)
1313
{
14+
var enabledProvider = context.AnalyzerConfigOptionsProvider
15+
.Select((options, _) =>
16+
{
17+
options.GlobalOptions.TryGetValue("build_property.EnableTUnitSourceGeneration", out var value);
18+
return !string.Equals(value, "false", StringComparison.OrdinalIgnoreCase);
19+
});
20+
1421
var standardTests = context.SyntaxProvider
1522
.ForAttributeWithMetadataName(
1623
"TUnit.Core.DynamicTestBuilderAttribute",
1724
predicate: static (_, _) => true,
1825
transform: static (ctx, _) => GetSemanticTargetForTestMethodGeneration(ctx))
19-
.Where(static m => m is not null);
26+
.Where(static m => m is not null)
27+
.Combine(enabledProvider);
28+
29+
context.RegisterSourceOutput(standardTests, (sourceContext, data) =>
30+
{
31+
var (testData, isEnabled) = data;
32+
if (!isEnabled)
33+
{
34+
return;
35+
}
2036

21-
context.RegisterSourceOutput(standardTests, (sourceContext, data) => GenerateTests(sourceContext, data!));
37+
GenerateTests(sourceContext, testData!);
38+
});
2239
}
2340

2441
static DynamicTestSourceDataModel? GetSemanticTargetForTestMethodGeneration(GeneratorAttributeSyntaxContext context)

TUnit.Core.SourceGenerator/CodeGenerators/LanguageVersionCheckGenerator.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ public class LanguageVersionCheckGenerator : IIncrementalGenerator
88
{
99
public void Initialize(IncrementalGeneratorInitializationContext context)
1010
{
11+
var enabledProvider = context.AnalyzerConfigOptionsProvider
12+
.Select((options, _) =>
13+
{
14+
options.GlobalOptions.TryGetValue("build_property.EnableTUnitSourceGeneration", out var value);
15+
return !string.Equals(value, "false", StringComparison.OrdinalIgnoreCase);
16+
});
17+
1118
var settings = context.CompilationProvider
1219
.Select((c, _) =>
1320
{
@@ -16,10 +23,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
1623
: null;
1724

1825
return csharpVersion;
19-
});
26+
})
27+
.Combine(enabledProvider);
2028

21-
context.RegisterSourceOutput(settings, static (sourceProductionContext, languageVersion) =>
29+
context.RegisterSourceOutput(settings, static (sourceProductionContext, data) =>
2230
{
31+
var (languageVersion, isEnabled) = data;
32+
33+
if (!isEnabled)
34+
{
35+
return;
36+
}
37+
2338
if (languageVersion is null)
2439
{
2540
return;

TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,31 @@ public class StaticPropertyInitializationGenerator : IIncrementalGenerator
1515
{
1616
public void Initialize(IncrementalGeneratorInitializationContext context)
1717
{
18+
var enabledProvider = context.AnalyzerConfigOptionsProvider
19+
.Select((options, _) =>
20+
{
21+
options.GlobalOptions.TryGetValue("build_property.EnableTUnitSourceGeneration", out var value);
22+
return !string.Equals(value, "false", StringComparison.OrdinalIgnoreCase);
23+
});
24+
1825
var testClasses = context.SyntaxProvider
1926
.CreateSyntaxProvider(
2027
predicate: static (s, _) => s is ClassDeclarationSyntax,
2128
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx))
2229
.Where(static t => t is not null)
23-
.Collect();
30+
.Collect()
31+
.Combine(enabledProvider);
32+
33+
context.RegisterSourceOutput(testClasses, (sourceProductionContext, data) =>
34+
{
35+
var (classes, isEnabled) = data;
36+
if (!isEnabled)
37+
{
38+
return;
39+
}
2440

25-
context.RegisterSourceOutput(testClasses, (sourceProductionContext, testClasses) =>
26-
GenerateStaticPropertyInitialization(sourceProductionContext, testClasses.Where(t => t != null).ToImmutableArray()!));
41+
GenerateStaticPropertyInitialization(sourceProductionContext, classes.Where(t => t != null).ToImmutableArray()!);
42+
});
2743
}
2844

2945
private static INamedTypeSymbol? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)

TUnit.Core/TUnit.Core.props

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,14 @@
3838
<EnableTUnitPolyfills Condition="'$(EnableTUnitPolyfills)' == '' and ('$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' or '$(TargetFrameworkIdentifier)' == '.NETFramework')">true</EnableTUnitPolyfills>
3939
</PropertyGroup>
4040

41+
<PropertyGroup>
42+
<!-- Enable TUnit source generation (defaults to true). Set to false when using reflection mode to skip source generation and improve build performance. -->
43+
<EnableTUnitSourceGeneration Condition="'$(EnableTUnitSourceGeneration)' == ''">true</EnableTUnitSourceGeneration>
44+
</PropertyGroup>
45+
4146
<ItemGroup>
4247
<CompilerVisibleProperty Include="EnableTUnitPolyfills" />
48+
<CompilerVisibleProperty Include="EnableTUnitSourceGeneration" />
4349
</ItemGroup>
4450

4551
</Project>

docs/docs/execution/engine-modes.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,59 @@ Alternatively, you can configure this in a `.runsettings` file:
9191
</RunSettings>
9292
```
9393

94+
### Optimizing Build Performance in Reflection Mode
95+
96+
When using reflection mode exclusively (via `[assembly: ReflectionMode]`), you can improve build performance by disabling source generation entirely. Since the generated code won't be used at runtime in reflection mode, skipping source generation reduces compile times.
97+
98+
Add this MSBuild property to your test project file (`.csproj`):
99+
100+
```xml
101+
<PropertyGroup>
102+
<EnableTUnitSourceGeneration>false</EnableTUnitSourceGeneration>
103+
</PropertyGroup>
104+
```
105+
106+
**Example: bUnit Test Project with Optimized Build**
107+
```xml
108+
<Project Sdk="Microsoft.NET.Sdk.Razor">
109+
<PropertyGroup>
110+
<TargetFramework>net9.0</TargetFramework>
111+
<!-- Disable source generation since we're using reflection mode -->
112+
<EnableTUnitSourceGeneration>false</EnableTUnitSourceGeneration>
113+
</PropertyGroup>
114+
115+
<ItemGroup>
116+
<PackageReference Include="bunit" Version="1.x.x" />
117+
<PackageReference Include="TUnit" Version="1.x.x" />
118+
</ItemGroup>
119+
</Project>
120+
```
121+
122+
Then in your code:
123+
```csharp
124+
// Enable reflection mode for Razor component testing
125+
[assembly: ReflectionMode]
126+
127+
namespace MyApp.Tests;
128+
129+
public class CounterComponentTests : TestContext
130+
{
131+
[Test]
132+
public void CounterStartsAtZero()
133+
{
134+
var cut = RenderComponent<Counter>();
135+
cut.Find("p").TextContent.ShouldBe("Current count: 0");
136+
}
137+
}
138+
```
139+
140+
**Benefits:**
141+
- **Faster Builds**: Eliminates source generator execution at compile time
142+
- **Reduced Compiler Overhead**: Less work for the compiler to do
143+
- **Clear Intent**: Explicitly indicates the project uses reflection mode
144+
145+
**Note:** This optimization is only beneficial when you're exclusively using reflection mode. If you're using source generation mode (the default), keep `EnableTUnitSourceGeneration` set to `true` (or omit it entirely, as `true` is the default).
146+
94147
## Native AOT Support
95148

96149
When publishing with Native AOT, TUnit's source generation mode provides additional benefits:

0 commit comments

Comments
 (0)