|
| 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