From 6d0e5f77ee0308686209c6760a9f92317197344f Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sat, 25 Apr 2026 12:47:25 +0100
Subject: [PATCH 1/3] perf(build): trim test TFMs and skip viewer dump by
default (#5646)
Cuts cold-build time on TUnit.Dev.slnx by reducing per-project work
that contributors don't need.
- TestProject.props now defaults to net10.0 only. Three projects that
snapshot output across consumer TFMs (Core.SourceGenerator.Tests,
Assertions.SourceGenerator.Tests, PublicAPI) keep the legacy
net472;net8.0;net9.0;net10.0 set explicitly.
- EmitCompilerGeneratedFiles is now opt-in via
-p:EmitCompilerGeneratedFiles=true. The Compile Remove for
SourceGeneratedViewer\ stays unconditional so stale folders from
prior opt-in builds don't poison the implicit C# glob.
- Library.props gates GenerateDocumentationFile on
IsPacking/GeneratePackageOnBuild/ContinuousIntegrationBuild so
local incremental dev builds skip XML doc emission.
- CI workflow's AOT publish step gets --no-restore (restore already
ran earlier in the job).
- Playground project removed (was a scratch project, not used by any
test or build target, and triggered VS load failures because of
missing TargetFramework/OutputType).
- CONTRIBUTING.md gains a "Building TUnit Locally" section pointing
contributors at TUnit.Dev.slnx, MSBUILDUSESERVER, and the new
EmitCompilerGeneratedFiles opt-in.
---
.github/CONTRIBUTING.md | 32 +++++++++++
.github/workflows/dotnet.yml | 2 +-
Library.props | 4 +-
Playground/GenericTestExample.cs | 29 ----------
Playground/Playground.csproj | 31 ----------
Playground/Program.cs | 56 -------------------
Playground/TestConstructorDemo.cs | 40 -------------
...it.Assertions.SourceGenerator.Tests.csproj | 5 ++
.../TUnit.Core.SourceGenerator.Tests.csproj | 5 ++
TUnit.PublicAPI/TUnit.PublicAPI.csproj | 7 ++-
TUnit.slnx | 1 -
TestProject.props | 33 +++++++----
12 files changed, 74 insertions(+), 171 deletions(-)
delete mode 100644 Playground/GenericTestExample.cs
delete mode 100644 Playground/Playground.csproj
delete mode 100644 Playground/Program.cs
delete mode 100644 Playground/TestConstructorDemo.cs
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index e2fd24a4a7..848a4c3333 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -104,6 +104,38 @@ The relevant files are at `docs > docs > tutorials-[basics|extras|assertions]`
If want to provide sample code for complicated or useful different test suite set-ups, that's also very welcome, as this can help other users get started a lot quicker!
+## Building TUnit Locally
+
+TUnit ships several solution files. Pick the right one for your task — the
+full `TUnit.slnx` is large enough that a cold build can take 20+ minutes,
+which is rarely what contributors actually need:
+
+| Solution | Projects | When to use |
+| ---------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
+| `TUnit.Dev.slnx` | 44 | **Recommended for contributors.** Drops the per-Roslyn-version generator variants and example projects. Open this in Visual Studio / Rider for day-to-day work. |
+| `TUnit.CI.slnx` | 73 | What CI builds. Use only when you're verifying CI-specific changes. |
+| `TUnit.slnx` | 96 | Full shipping graph including all per-Roslyn-version analyzer/generator variants. Slow; rarely needed locally. |
+
+### Faster repeat builds
+
+Set `MSBUILDUSESERVER=1` in your shell to keep the MSBuild process alive
+between invocations — significant speedup on warm/incremental builds.
+
+```bash
+export MSBUILDUSESERVER=1 # bash/zsh
+$env:MSBUILDUSESERVER = '1' # PowerShell
+```
+
+### Inspecting generated source
+
+Source-generated files are not written to disk by default (saves significant
+disk I/O on every build). To dump them under each test project's
+`SourceGeneratedViewer\` folder, build with:
+
+```bash
+dotnet build -p:EmitCompilerGeneratedFiles=true
+```
+
### Code Contributions
When contributing code to TUnit, please keep these important requirements in mind:
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 49bc76207f..0901e12900 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -136,7 +136,7 @@ jobs:
- name: Publish AOT
shell: bash
run: >-
- dotnet publish TUnit.TestProject/TUnit.TestProject.csproj -c Release
+ dotnet publish TUnit.TestProject/TUnit.TestProject.csproj -c Release --no-restore
--use-current-runtime -p:Aot=true -o TESTPROJECT_AOT --framework net10.0
"-p:Version=${{ steps.gitversion.outputs.semVer }}"
"-p:AssemblyVersion=${{ steps.gitversion.outputs.assemblySemVer }}"
diff --git a/Library.props b/Library.props
index 89f2601f4c..c5a6e3b88c 100644
--- a/Library.props
+++ b/Library.props
@@ -4,7 +4,9 @@
true
- true
+
+ true
$(NoWarn);CS1591;CS1570;CS1572;CS1573;CS1574;CS1584;CS1587;CS1658;CS1734;CS0419
diff --git a/Playground/GenericTestExample.cs b/Playground/GenericTestExample.cs
deleted file mode 100644
index bfe03ef821..0000000000
--- a/Playground/GenericTestExample.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using TUnit.Core;
-
-namespace Playground;
-
-public abstract class GenericTestExample
-{
- [Test]
- public void GenericTest()
- {
- // This is a test in a generic class
- Console.WriteLine($"Running test with type: {typeof(T).Name}");
- }
-
- [Test]
- [Arguments(5)]
- [Arguments("hello")]
- public void GenericTestWithArguments(TArg value)
- {
- // This is a generic method with arguments - types should be inferred from Arguments
- Console.WriteLine($"Running test with value: {value} of type: {typeof(TArg).Name}");
- }
-}
-
-// Test instantiation with different types
-[InheritsTests]
-public class IntGenericTests : GenericTestExample { }
-
-[InheritsTests]
-public class StringGenericTests : GenericTestExample { }
diff --git a/Playground/Playground.csproj b/Playground/Playground.csproj
deleted file mode 100644
index 87298026d4..0000000000
--- a/Playground/Playground.csproj
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
-
-
-
diff --git a/Playground/Program.cs b/Playground/Program.cs
deleted file mode 100644
index 16d26bfbea..0000000000
--- a/Playground/Program.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using Testcontainers.PostgreSql;
-using Testcontainers.Redis;
-using TUnit.Core;
-using TUnit.Engine.Extensions;
-using Xunit;
-
-namespace Playground;
-
-public static class Program
-{
-#pragma warning disable TUnit0034
- public static async Task Main(string[] args)
-#pragma warning restore TUnit0034
- {
- var builder = await Microsoft.Testing.Platform.Builder.TestApplication.CreateBuilderAsync(args);
-
- builder.AddTUnit();
-
- using var app = await builder.BuildAsync();
-
- return await app.RunAsync();
- }
-}
-
-public class Tests
-{
- [Fact]
- public void Test()
- {
- var one = "1";
-
- Xunit.Assert.Equal("1", one);
- }
-}
-
-public class Hooks
-{
- public static PostgreSqlContainer PostgreSqlContainer { get; } = new PostgreSqlBuilder().Build();
- public static RedisContainer RedisContainer { get; } = new RedisBuilder().Build();
-
- [Before(HookType.Assembly)]
- public static async Task Before()
- {
- await PostgreSqlContainer.StartAsync();
- await RedisContainer.StartAsync();
- }
-
- [After(HookType.Assembly)]
- public static async Task After()
- {
- await PostgreSqlContainer.StopAsync();
- await PostgreSqlContainer.DisposeAsync();
- await RedisContainer.StopAsync();
- await RedisContainer.DisposeAsync();
- }
-}
diff --git a/Playground/TestConstructorDemo.cs b/Playground/TestConstructorDemo.cs
deleted file mode 100644
index 9556339e95..0000000000
--- a/Playground/TestConstructorDemo.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using TUnit.Core;
-
-namespace Playground;
-
-// This class should trigger TUnit0052 warning about multiple constructors
-public class MultipleConstructorsDemo
-{
- public MultipleConstructorsDemo()
- {
- }
-
- public MultipleConstructorsDemo(string value)
- {
- }
-
- [Test]
- public void ExampleTest()
- {
- // This should trigger warning TUnit0052
- }
-}
-
-// This class should work fine with TestConstructor
-public class TestConstructorDemo
-{
- public TestConstructorDemo()
- {
- }
-
- [TestConstructor]
- public TestConstructorDemo(string value)
- {
- }
-
- [Test]
- public void ExampleTestWithConstructor()
- {
- // This should use the marked constructor without warnings
- }
-}
\ No newline at end of file
diff --git a/TUnit.Assertions.SourceGenerator.Tests/TUnit.Assertions.SourceGenerator.Tests.csproj b/TUnit.Assertions.SourceGenerator.Tests/TUnit.Assertions.SourceGenerator.Tests.csproj
index c2a0c85500..38cec4121f 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/TUnit.Assertions.SourceGenerator.Tests.csproj
+++ b/TUnit.Assertions.SourceGenerator.Tests/TUnit.Assertions.SourceGenerator.Tests.csproj
@@ -1,5 +1,10 @@
+
+
+ net472;net8.0;net9.0;net10.0
+
+
diff --git a/TUnit.Core.SourceGenerator.Tests/TUnit.Core.SourceGenerator.Tests.csproj b/TUnit.Core.SourceGenerator.Tests/TUnit.Core.SourceGenerator.Tests.csproj
index b870ac5405..b06f5148e8 100644
--- a/TUnit.Core.SourceGenerator.Tests/TUnit.Core.SourceGenerator.Tests.csproj
+++ b/TUnit.Core.SourceGenerator.Tests/TUnit.Core.SourceGenerator.Tests.csproj
@@ -1,5 +1,10 @@
+
+
+ net472;net8.0;net9.0;net10.0
+
+
diff --git a/TUnit.PublicAPI/TUnit.PublicAPI.csproj b/TUnit.PublicAPI/TUnit.PublicAPI.csproj
index b1aa4e0f2e..93fea6c669 100644
--- a/TUnit.PublicAPI/TUnit.PublicAPI.csproj
+++ b/TUnit.PublicAPI/TUnit.PublicAPI.csproj
@@ -1,7 +1,12 @@
+
+
+ net472;net8.0;net9.0;net10.0
+
+
-
+
Release
diff --git a/TUnit.slnx b/TUnit.slnx
index 3a13bcae2a..e5eae1e0f4 100644
--- a/TUnit.slnx
+++ b/TUnit.slnx
@@ -101,7 +101,6 @@
-
diff --git a/TestProject.props b/TestProject.props
index b9b42a2bc7..d6c6860e3d 100644
--- a/TestProject.props
+++ b/TestProject.props
@@ -9,7 +9,11 @@
Condition="'$(MSBuildProjectExtension)' == '.fsproj'" />
- net472;net8.0;net9.0;net10.0
+
+ net10.0
Exe
@@ -25,24 +29,31 @@
-
-
-
-
-
-
-
+
- true
- SourceGeneratedViewer
+ false
+ SourceGeneratedViewer
+
+
+
+
+
+
+
+
+
+
+
From 9be994c85a21748440a8fb63e3a90b2de52bdfcc Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sat, 25 Apr 2026 12:56:20 +0100
Subject: [PATCH 2/3] fix(build): clean stale SourceGeneratedViewer dirs on
opt-out
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
PR review feedback: when a contributor switches from
-p:EmitCompilerGeneratedFiles=true back to the default (off), the
viewer folder previously stayed on disk forever — accumulating stale
generator output. The unconditional already prevents
compile errors, but the files keep growing.
Drop the EmitCompilerGeneratedFiles==true guard from the
CleanSourceGeneratedViewer target. The folder gets cleaned whenever it
exists on a local build:
- opt-in: fresh per-build dump (unchanged behavior)
- opt-out: one-time cleanup of stale dirs from a prior opt-in run
- CI: skipped (ephemeral runners — nothing to clean)
---
TestProject.props | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/TestProject.props b/TestProject.props
index d6c6860e3d..269d2b50a5 100644
--- a/TestProject.props
+++ b/TestProject.props
@@ -46,8 +46,12 @@
+
+ Condition="'$(ContinuousIntegrationBuild)' != 'true' AND Exists('$(ProjectDir)SourceGeneratedViewer')">
From 813774e2fc433cba957d6ff188267cdc1bafac1c Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sat, 25 Apr 2026 13:08:05 +0100
Subject: [PATCH 3/3] revert: drop --no-restore from AOT publish step
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The earlier `dotnet restore TUnit.CI.slnx` is RID-agnostic, but
`dotnet publish --use-current-runtime` needs RID-specific assets
(e.g. osx-arm64, linux-x64) in project.assets.json. Without
--no-restore, publish does its own RID-aware restore. With it, the
publish fails on macOS / non-x64 runners with NETSDK1047.
The publish step still gets a warm NuGet cache from the prior
restore — it just has to do the RID-specific resolve work itself.
---
.github/workflows/dotnet.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 0901e12900..49bc76207f 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -136,7 +136,7 @@ jobs:
- name: Publish AOT
shell: bash
run: >-
- dotnet publish TUnit.TestProject/TUnit.TestProject.csproj -c Release --no-restore
+ dotnet publish TUnit.TestProject/TUnit.TestProject.csproj -c Release
--use-current-runtime -p:Aot=true -o TESTPROJECT_AOT --framework net10.0
"-p:Version=${{ steps.gitversion.outputs.semVer }}"
"-p:AssemblyVersion=${{ steps.gitversion.outputs.assemblySemVer }}"