Skip to content

Commit 9fa92b8

Browse files
authored
Add test for negative category filter behavior with explicit tests (#3190) (#3559)
1 parent 0a314cb commit 9fa92b8

File tree

3 files changed

+115
-5
lines changed

3 files changed

+115
-5
lines changed

TUnit.Engine.Tests/ExplicitTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,4 @@ await RunTestsWithFilter(
6868
]);
6969
}
7070

71-
72-
73-
}
71+
}

TUnit.Engine/Services/TestFilterService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ public IReadOnlyCollection<AbstractExecutableTest> FilterTests(ITestExecutionFil
4141
}
4242
}
4343

44-
if (filteredTests.Count > 0 && filteredExplicitTests.Count > 0)
44+
if (filteredTests.Count > 0)
4545
{
46-
logger.LogTrace($"Filter matched both explicit and non-explicit tests. Excluding {filteredExplicitTests.Count} explicit tests.");
46+
logger.LogTrace($"Filter matched {filteredTests.Count} non-explicit tests. Excluding {filteredExplicitTests.Count} explicit tests.");
4747
return filteredTests;
4848
}
4949

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
namespace TUnit.TestProject.Bugs._3190;
5+
6+
// This file replicates the issue from GitHub issue #3190:
7+
// When ANY test has [Explicit], negative category filters stop working correctly.
8+
// Expected: /*/*/*/*[Category!=Performance] should exclude all Performance tests
9+
// Actual bug: It runs all non-explicit tests INCLUDING those with Performance category
10+
//
11+
// ROOT CAUSE ANALYSIS:
12+
// The filter evaluation logic is incorrectly handling [Explicit] tests. The presence of
13+
// explicit tests is somehow interfering with negative category filter evaluation.
14+
//
15+
// CORRECT DESIGN PRINCIPLE (Two-Stage Filtering):
16+
//
17+
// Stage 1: Pre-Filter for [Explicit]
18+
// Create initial candidate list:
19+
// - START WITH: All non-explicit tests
20+
// - ADD explicit tests ONLY IF: They are positively and specifically selected
21+
// ✓ Specific name match: /*/MyExplicitTest
22+
// ✓ Positive property: /*/*/*/*[Category=Nightly]
23+
// ✗ Wildcard: /*/*/*/* (too broad - not a specific selection)
24+
// ✗ Negative filter: /*/*/*/*[Category!=Performance] (not a positive selection)
25+
//
26+
// Stage 2: Main Filter
27+
// Apply the full filter logic (including negations) to the candidate list from Stage 1.
28+
//
29+
// WHY THIS IS CORRECT:
30+
// - [Explicit] means "opt-in only" - never run unless specifically requested
31+
// - Test behavior should be local to the test itself, not dependent on sibling tests
32+
// - Aligns with industry standards (NUnit, etc.)
33+
// - Prevents "last non-explicit test" disaster scenario where deleting one test
34+
// changes the behavior of 99 unrelated explicit tests
35+
//
36+
// EXPECTED BEHAVIOR FOR THIS TEST:
37+
// Filter: /*/*/*/*[Category!=Performance]
38+
//
39+
// Stage 1 Result (candidate list):
40+
// - TestClass1.TestMethod1 ✓ (not explicit)
41+
// - TestClass1.TestMethod2 ✓ (not explicit)
42+
// - TestClass2.TestMethod1 ✓ (not explicit)
43+
// - TestClass2.TestMethod2 ✗ (explicit - wildcard doesn't positively select it)
44+
// - TestClass3.RegularTestWithoutCategory ✓ (not explicit)
45+
//
46+
// Stage 2 Result (after applying [Category!=Performance]):
47+
// - TestClass1.TestMethod1 ✗ (has Performance category)
48+
// - TestClass1.TestMethod2 ✓ (no Performance category) ← SHOULD RUN
49+
// - TestClass2.TestMethod1 ✗ (has Performance category)
50+
// - TestClass3.RegularTestWithoutCategory ✓ (no Performance category) ← SHOULD RUN
51+
//
52+
// FINAL: 2 tests should run
53+
54+
public class TestClass1
55+
{
56+
[Test]
57+
[Category("Performance")]
58+
public Task TestMethod1()
59+
{
60+
// This test has Performance category
61+
// With filter [Category!=Performance], this should be EXCLUDED
62+
Console.WriteLine("TestClass1.TestMethod1 executed (has Performance category)");
63+
return Task.CompletedTask;
64+
}
65+
66+
[Test]
67+
[Property("CI", "false")]
68+
public Task TestMethod2()
69+
{
70+
// This test has CI property but NOT Performance category
71+
// With filter [Category!=Performance], this should be INCLUDED
72+
Console.WriteLine("TestClass1.TestMethod2 executed (no Performance category)");
73+
return Task.CompletedTask;
74+
}
75+
}
76+
77+
public class TestClass2
78+
{
79+
[Test]
80+
[Category("Performance")]
81+
[Property("CI", "true")]
82+
public Task TestMethod1()
83+
{
84+
// This test has BOTH Performance category and CI property
85+
// With filter [Category!=Performance], this should be EXCLUDED
86+
Console.WriteLine("TestClass2.TestMethod1 executed (has Performance category)");
87+
return Task.CompletedTask;
88+
}
89+
90+
[Test]
91+
[Explicit]
92+
public Task TestMethod2()
93+
{
94+
// This test is marked Explicit - the trigger for the bug
95+
// With any wildcard filter, this should NOT run unless explicitly requested
96+
// But its presence causes negative category filters to malfunction
97+
Console.WriteLine("TestClass2.TestMethod2 executed (Explicit test - should not run with wildcard filter!)");
98+
throw new NotImplementedException("Explicit test should not run with wildcard filter!");
99+
}
100+
}
101+
102+
public class TestClass3
103+
{
104+
[Test]
105+
public Task RegularTestWithoutCategory()
106+
{
107+
// This test has no Performance category and is not Explicit
108+
// With filter [Category!=Performance], this should be INCLUDED
109+
Console.WriteLine("TestClass3.RegularTestWithoutCategory executed (no Performance category)");
110+
return Task.CompletedTask;
111+
}
112+
}

0 commit comments

Comments
 (0)