From bdf961d7342f3d78c7e2883565f5797e22aff483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Gro=C3=9F?= Date: Sat, 31 Oct 2026 10:09:03 +0100 Subject: [PATCH] test: run the analyzer sweeps' rows in parallel, and leave the pragma pass to each rule's focused tests DroppedTaskVariantTests is one class of 1,838 rows, which xunit's default mode ran one after another. On CI it kept the test step going for two to seven minutes after every other project had finished (#245). The analyzer test project now runs with parallelMode "all" (xunit.runner.json), so every row of every theory is scheduled on its own. No test there shares state: each builds its own compilation. The four sweeps (DroppedTaskVariantTests, TypeArgumentConstraintKindTests, QueryDelegateConditionTests and TypeArgumentSiteRun, which TypeArgumentConstraintVariantTests runs) set VariantSweep.Behaviors, which skips the verifier's second run of each reporting case under '#pragma warning disable'. Whether a pragma silences a rule is the same for every shape, and the focused tests of each rule (IgnoredOutcomeAnalyzerTests, TypeArgumentConstraintAnalyzerTests, DefaultProducingCallAnalyzerTests) keep it. The verifier's generated-code pass already never ran: every analyzer configures generated-code analysis. Seeded twice, each reverted by its inverse edit: - CMTK0003 dropping the first element of an array's tasks: DroppedTaskVariantTests fails 2 rows under the new mode, so the sweep still catches its own defects. - CMTK0003 reporting a probe rule only below a leading '#pragma warning disable': IgnoredOutcomeAnalyzerTests fails 16 tests in the pragma pass, DroppedTaskVariantTests passes. The pass is now the focused tests'. Co-Authored-By: Claude Opus 5.5 --- .../AnalyzerTest.cs | 14 ++++++++++++++ .../CodoMetis.TypeKit.Analyzers.Tests.csproj | 11 +++++++++++ .../DroppedTaskVariantTests.cs | 1 + .../QueryDelegateConditionTests.cs | 1 + .../TypeArgumentConstraintKindTests.cs | 1 + .../Variants/TypeArgumentSiteRun.cs | 1 + .../xunit.runner.json | 4 ++++ 7 files changed, 33 insertions(+) create mode 100644 test/CodoMetis.TypeKit.Analyzers.Tests/xunit.runner.json diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/AnalyzerTest.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/AnalyzerTest.cs index 061fe502..9e98fadd 100644 --- a/test/CodoMetis.TypeKit.Analyzers.Tests/AnalyzerTest.cs +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/AnalyzerTest.cs @@ -31,6 +31,20 @@ public AnalyzerTest(string source, bool referenceTypeKit = true) } } +/// What a sweep over a variant catalogue leaves to the focused tests of its rule. +/// +/// The verifier runs a case that expects a diagnostic a second time, under #pragma warning disable for each id +/// it expects, and checks that nothing is left. Whether a pragma silences a rule is the compiler's, the same for every +/// shape, so a sweep paid for that pass once per row: 28% of DroppedTaskVariantTests' time (#245). The focused tests +/// of each rule keep it. The verifier's other pass, under an <auto-generated> header, never runs here: +/// every analyzer configures generated-code analysis (GeneratedCode.AnalysisFlags), and GeneratedCodeTests +/// holds what each rule does in generated code. +/// +internal static class VariantSweep +{ + public const TestBehaviors Behaviors = TestBehaviors.SkipSuppressionCheck; +} + /// A verifier run of one analyzer and its code fix: the source, then the source as the fix leaves it. internal sealed class CodeFixTest : CSharpCodeFixTest where TAnalyzer : DiagnosticAnalyzer, new() diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/CodoMetis.TypeKit.Analyzers.Tests.csproj b/test/CodoMetis.TypeKit.Analyzers.Tests/CodoMetis.TypeKit.Analyzers.Tests.csproj index a6513c17..e2871153 100644 --- a/test/CodoMetis.TypeKit.Analyzers.Tests/CodoMetis.TypeKit.Analyzers.Tests.csproj +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/CodoMetis.TypeKit.Analyzers.Tests.csproj @@ -23,4 +23,15 @@ + + + + + diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/DroppedTaskVariantTests.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/DroppedTaskVariantTests.cs index 4f3544e4..34b80ff7 100644 --- a/test/CodoMetis.TypeKit.Analyzers.Tests/DroppedTaskVariantTests.cs +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/DroppedTaskVariantTests.cs @@ -36,6 +36,7 @@ public Task A_dropped_task_is_reported_in_every_form_and_a_kept_one_never(string var (source, expected) = DroppedTaskShapes.Use(call, form); var test = new AnalyzerTest(source); + test.TestBehaviors = VariantSweep.Behaviors; test.TestState.Sources.Add(("Declarations.cs", DroppedTaskShapes.Declarations)); test.TestState.AdditionalReferences.Add(RealTesting.Reference); test.ExpectedDiagnostics.AddRange( diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/QueryDelegateConditionTests.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/QueryDelegateConditionTests.cs index c0f84da4..550e59e5 100644 --- a/test/CodoMetis.TypeKit.Analyzers.Tests/QueryDelegateConditionTests.cs +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/QueryDelegateConditionTests.cs @@ -133,6 +133,7 @@ private static Task Sweep(QueryReceiver receiver, string element, string paramet var (source, expected) = QueryDelegateShapes.Use(receiver, element, parameters, where); var test = new AnalyzerTest(TypeArgumentKinds.Candidates); + test.TestBehaviors = VariantSweep.Behaviors; test.TestState.Sources.Add(("Declarations.cs", QueryDelegateShapes.Declarations)); test.TestState.Sources.Add(("Use.cs", source)); test.ExpectedDiagnostics.AddRange(expected.Select(call => new DiagnosticResult("CMTK0009", DiagnosticSeverity.Warning).WithLocation(call.Location))); diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintKindTests.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintKindTests.cs index b295217c..7b28e0e1 100644 --- a/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintKindTests.cs +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintKindTests.cs @@ -215,6 +215,7 @@ public void The_catalogue_names_are_unique_and_its_pairs_reach_every_verdict() private static Task Run(string subject, string use, bool breaking) { var test = new AnalyzerTest(TypeArgumentKinds.Candidates + subject); + test.TestBehaviors = VariantSweep.Behaviors; test.TestState.Sources.Add(("Use.cs", use)); if (breaking) diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSiteRun.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSiteRun.cs index ca86c371..bf2c7bb5 100644 --- a/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSiteRun.cs +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSiteRun.cs @@ -40,6 +40,7 @@ public static Task Verify(TypeArgumentSubject subject, string siteNam var use = Fill(site.Use, subject, argument); var test = new AnalyzerTest(Fill(TypeArgumentSites.Subjects + subject.Declarations, subject, argument)); + test.TestBehaviors = VariantSweep.Behaviors; test.TestState.Sources.Add((site.File, reported ? use : Unmarked(use))); if (reported) diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/xunit.runner.json b/test/CodoMetis.TypeKit.Analyzers.Tests/xunit.runner.json new file mode 100644 index 00000000..82eb3ce9 --- /dev/null +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/v3/xunit.runner.schema.json", + "parallelMode": "all" +}