Repository navigation
Finished test class instances stay reachable until the test session ends #7260
Description
Problem
After a test finishes and its class instance has been disposed, TestContext.Metadata.TestDetails.ClassInstance still references the instance, and the session keeps every TestContext (TestSessionContext → AssemblyHookContext → ClassHookContext → its list of TestContexts). As a result, every object a finished test stored in an instance field remains reachable until the whole session ends.
That is harmless for small fields but expensive for suites where each test owns a heavy resource in a field (for example a per-test WebApplicationFactory, its host and DI container, an EF Core model, or large buffers). Disposal releases unmanaged resources, but the managed graph stays on the heap, so working set grows linearly with the number of completed tests.
Minimal repro (TUnit 1.76.0, net10.0)
public class Holder
{
private byte[]? _payload;
[Test]
[MatrixDataSource]
public async Task Allocate([MatrixRange<int>(1, 20)] int n)
{
_payload = new byte[50 * 1024 * 1024];
Random.Shared.NextBytes(_payload);
await Task.Yield();
}
[After(TestSession)]
public static void Report()
{
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Console.WriteLine($"RETAINED_MB={GC.GetTotalMemory(true) / (1024 * 1024)}");
}
}Output: RETAINED_MB=1000, meaning all 20 finished instances (20 x 50 MB) are still reachable at session end. Expected: close to 0, since every test finished and its instance was disposed.
A heap dump of a larger suite shows this root chain for a finished test's instance:
TestSessionContext
-> List<AssemblyHookContext> -> AssemblyHookContext
-> List<ClassHookContext> -> ClassHookContext
-> List<TestContext> -> TestContext (finished test)
-> <test class instance> -> <instance field> -> ...
Proposed fix
Once the instance has been disposed (after DisposeTestInstanceWithSpanAsync in TestCoordinator), replace TestDetails.ClassInstance with a sentinel such as PlaceholderInstance.Instance, so result reporting keeps the test metadata without keeping user state alive. Any other per-test references to user objects that are no longer needed after the test completes (for example the tracked-object sets) could be cleared at the same point.
Acceptance
- The repro above reports roughly 0 MB retained.
- A regression test asserts that a finished test's class instance is collectable (weak reference) before the session ends.
Activity
fix: release finished test instances and per-test objects before sess…