Skip to content

ci(mutation): disable Stryker mutators on static initializer tables - #229

Merged
samtrion merged 2 commits into
mainfrom
fix/stryker-static-initializer-mutants-never-killed
Oct 29, 2026
Merged

samtrion merged 2 commits into
mainfrom
fix/stryker-static-initializer-mutants-never-killed

Conversation

@samtrion

@samtrion samtrion commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Problem

In the latest mutation run (run 40488214820) the score is 59.95% (Killed 793, Timeout 5, Survived 491, NoCoverage 42). 202 of the 491 survivors are mutants inside static field initializers (static=true in the report: 202 Survived / 3 Killed / 3 CompileError):

File Static survivors
Helpers/CSharpKeywords.cs 127
Usage/RequireCancellationCheckCodeFixProvider.cs (CollectionExpressionTargetTypes) 46
Maintainability/AvoidRedundantCollectionClearAnalyzer.cs (AllowedCollectionMetadataNames) 11
Helpers/CancellationTokenCallSites.cs 5
DiagnosticDescriptors, UseLangwordSuppressor, RequireNumericLiteralSuffixSuppressor, TargetFrameworkMonikers, others 13

They survive although existing tests would fail if the value were really changed: "System.Collections.ArrayList" -> "" and "System.Collections.Generic.List1" -> ""survive whileAvoidRedundantCollectionClearAnalyzerTestshasArrayList_ImmediateClear_Reportsand about 20Listtests. The three killed static mutants are inDiagnosticIds.HelpLink`, an expression-bodied static method that is evaluated at test time, not a field initializer.

Root cause

Consistent with the Stryker 5.0.0 source and not verified by a dedicated run: the MTP runner (preview, log: "Microsoft Test Platform testrunner is currently in preview", "Starting per-test coverage capture for MTP runner") switches the active mutant by writing the mutant id into a warm test host and only restarts the host after a timeout. A static field initializer has therefore already run before the mutant is read, so the mutation never takes effect. Static mutants are run against the whole suite and still survive; this is a tool artefact, not missing tests. Local confirmation was not possible: the repository does not build on the maintainer's Windows machine (NetEvolve.Analyzer.Dogfood.dll copy error, MSB3030), so Stryker aborts with "Failed to analyze project builds". The branch run below is the confirmation.

Change

// Stryker disable all : ... / // Stryker restore all pragma pairs placed directly around the individual static table declarations only (7 files, comments only, no behaviour change):

  • CSharpKeywords: all five tables
  • RequireCancellationCheckCodeFixProvider.CollectionExpressionTargetTypes
  • AvoidRedundantCollectionClearAnalyzer.AllowedCollectionMetadataNames
  • CancellationTokenCallSites.SupportedAsyncReturnTypeMetadataNames
  • UseLangwordSuppressor.Suppression, RequireNumericLiteralSuffixSuppressor.Suppression
  • TargetFrameworkMonikers.UnifiedTfmPattern

Surrounding logic in those classes stays mutated. DiagnosticIds is untouched (its lazy members are killable). DiagnosticDescriptors fields are not covered (the per-field mutants there were not identified), so the expected effect is slightly below the full 202. This repository has no earlier "Stryker disable" usage, so this introduces a new convention.

Before / After

Metric Before (run 40488214820) After (run 40815422362)
Mutation score 59.95% (798/1331) 70.37% (798/1134)
Killed / Timeout 793 / 5 793 / 5 (unchanged)
Survived 491 294
NoCoverage 42 42
Ignored by the new pragma 0 198
Mutants created / compile errors 2110 / 474 2110 / 474
Duration 25 min about 13 min

Run: https://github.com/dailydevops/analyzer/actions/runs/40815422362

198 of the 202 static survivors moved to Ignored (the rest are outside the scoped tables, e.g. DiagnosticDescriptors). The Killed count did not change, so no real test strength was lost or gained. Duration is not a controlled comparison (different runner load). The score gain is purely a denominator change.

Risk / what this does not change

  • Trade-off: a mutation-test pragma on these tables hides any real regression in them from mutation testing, for example a wrongly edited keyword. The ordinary unit tests for the tables stay and still guard them.
  • It is a workaround. Remove the pragmas once Stryker's MTP runner handles static initializer mutants. An upstream issue and a pipeline-level fix (the same problem affects other repos using the MTP runner, e.g. defaults, frameshift) are separate work, not part of this PR.
  • Per-test-isolation coverage (coverage-analysis perTestInIsolation) was not tried; mutation runs would still use the warm host.
  • No production or test code behaviour changes.

Stryker 5.0.0 with the Microsoft Testing Platform runner (preview) leaves
mutants in static field initializers as Survived: the mutant is switched on
in a warm test host after the type initializer already ran, so no test can
fail. In the latest run 202 of 491 survivors were static mutants (CSharpKeywords
127, CollectionExpressionTargetTypes 46, AllowedCollectionMetadataNames 11,
SupportedAsyncReturnTypeMetadataNames 5, others), even though tests such as
ArrayList_ImmediateClear_Reports would fail if the entry were dropped.

Scope the disable/restore pragmas to the individual static field declarations
so the surrounding logic stays mutated. Remove once the MTP runner handles
static mutants.
@codecov

codecov Bot commented Oct 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.72%. Comparing base (8c86c2b) to head (785b989).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #229   +/-   ##
=======================================
  Coverage   94.72%   94.72%           
=======================================
  Files          48       48           
  Lines        2557     2557           
  Branches      449      449           
=======================================
  Hits         2422     2422           
  Misses         58       58           
  Partials       77       77           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@samtrion
samtrion merged commit afea9ed into main Oct 29, 2026
9 checks passed
@samtrion
samtrion deleted the fix/stryker-static-initializer-mutants-never-killed branch October 29, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant