dotnet-stryker-netx 3.3.13

dotnet tool install --global dotnet-stryker-netx --version 3.3.13
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local dotnet-stryker-netx --version 3.3.13
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=dotnet-stryker-netx&version=3.3.13
                    
nuke :add-package dotnet-stryker-netx --version 3.3.13
                    

Stryker.NET-X

Stryker.NET-X is a port of Stryker.NET 4.14.1 to C# 14 / .NET 10 — fully .slnx-aware — with an extended mutation-operator and feature catalogue that rivals PIT, cargo-mutants, and mutmut.

NuGet License

Stryker.NET-X is a fork of Stryker.NET that targets .NET 10, eliminates the Buildalyzer dependency in favour of Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace, supports the modern .slnx (XML-based) solution format, and ships a substantially expanded mutation-operator catalogue plus a MutationProfile opt-in surface for tunable noise/aggression. All public CLI flags and the stryker-config.json schema remain 1:1 backwards-compatible with upstream Stryker.NET 4.14.1.

What's new in Stryker.NET-X

Fully backwards-compatible: existing users see zero behavioral change unless they opt into a stronger mutation profile.

  • --mutation-profile flag (Defaults | Stronger | All) — orthogonal to --mutation-level; controls which mutators run (not just which mutations). Since v3.1.0 a stronger profile auto-bumps a too-low mutation level (ADR-025).
  • 24 net-new mutators (50 total) across 6 batches (typed-driven, PIT-1, PIT-2 + cargo-mutants, .NET-greenfield, spec-gap closure, filter pipeline + operator completion).
  • 5 equivalent-mutant filters in the pipeline (IdentityArithmetic, IdempotentBoolean, ConservativeDefaultsEquality, RoslynDiagnostics — the mutmut-style pre-filter — and RoslynSemanticDiagnostics with O(1) speculative binding).
  • Operator hierarchy + [MutationProfileMembership] attribute on every mutator.
  • SemanticModel-driven type-aware mutators — used by TypeDrivenReturn, ArgumentPropagation, MemberVariable, MethodBodyReplacement.
  • Equivalent-Mutant Filter pipeline as a first-class stage.
  • Production-hardened CLI surface (v3.2.x, reporter-driven): --break-after <phase> diagnostic runs, --test-case-filter / --test-filter, --all-projects multi-project mutation, --tool-version / -T, solution-mode heartbeat diagnostics, multi-line // Stryker disable scoping.
  • Type-aware literal emission (v3.3.x): both constant-emitting mutators (InlineConstants, ConstantReplacement) emit typed SyntaxFactory.Literal(T) for {int, uint, long, ulong, float, double, decimal} instead of lossy double round-trips — eliminates whole classes of CS0029/CS0266 Safe-Mode warnings (ADR-047/049).

Why this fork exists

Upstream Stryker.NET 4.14.1 (released 2026-04-10) does not run on .NET 9 / .NET 10 projects nor on .slnx solutions because:

  1. Buildalyzer 8.0 transitive dependency cannot parse .NET 10 MSBuild structures. Buildalyzer 9.0 — the fix — landed eight days after Stryker.NET 4.14.1.
  2. MsBuildHelper falls back to vswhere/MsBuild.exe paths that don't exist on .NET-10-SDK-only machines.
  3. .slnx support is missing from upstream's SolutionFile parser.

stryker-netx replaces the Buildalyzer pipeline entirely with Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace, gives the CLI a parallel Microsoft.VisualStudio.SolutionPersistence-based .slnx reader, and renames the tool to avoid colliding with the upstream package.

Disclaimer

stryker-netx is an independent community fork. It is NOT affiliated with, endorsed by, or sponsored by the official Stryker.NET project, the Stryker Mutator team, or Info Support BV. The "Stryker" name is used here descriptively (Stryker-Mutator-compatible tooling) and not as a trademark assertion.

For the official Stryker.NET project see https://github.com/stryker-mutator/stryker-net.

Compatibility

Component Supported
.NET Runtime (the tool itself) .NET 10
Test-project Target Frameworks net8.0, net9.0, net10.0
C# Language Version (in user code) C# 12, 13, 14
Solution formats .sln, .slnx
Test runners VsTest
Test frameworks xUnit, MSTest 2/3, NUnit
OS Windows ✓, Linux ✓ (CI), macOS (best-effort)

Microsoft Testing Platform (MTP) and TUnit (which is MTP-exclusive) are not yet supported — Stryker drives tests through VsTest only, and MTP projects are rejected at analysis time. An MTP runner is roadmapped (see stryker-net#3094 and ADR-044). Note: on the .NET 10 SDK the VsTest target for MTP was removed entirely, so MTP-based projects — including TUnit, which has no VsTest adapter — cannot currently be mutated.

Installation

dotnet tool install -g dotnet-stryker-netx

Pin a specific version:

dotnet tool install -g dotnet-stryker-netx --version 3.3.12

Quickstart

In your test project directory:

cd /path/to/your/Tests
dotnet stryker-netx

Or against a solution from the solution directory:

cd /path/to/your/solution
dotnet stryker-netx --solution YourApp.slnx

⚠️ Solution-mode requires the working directory to match the solution directory. Passing --solution path/to/elsewhere.slnx while cd'd into a different directory (e.g. tests/MyApp.Tests/) does not activate whole-solution mutation mode — stryker-netx falls back to single-project mode. To enable solution-wide discovery, cd to the solution directory first. (Heartbeat logs emitted every 30s during long analysis + initial-test-run phases will tell you whether the tool is making progress; see ADR-043.)

Or with a stryker-config.json:

dotnet stryker-netx --config-file stryker-config.json

CLI flags, configuration schema, and reporter outputs are mostly compatible with upstream Stryker.NET configuration docs.

Mutation Profiles (v2.0.0)

--mutation-profile controls which mutators are active during a run. It is independent of --mutation-level (which controls which mutations a given mutator emits).

Profile Active mutators Use case
Defaults (default) 26 v1.x mutators only Drop-in v1.x parity. Same behavior as upstream Stryker.NET.
Stronger Defaults + 17 type-aware / catalogue-closing mutators (= 43 total) Catch more bugs while keeping noise manageable.
All Stronger + 7 most-aggressive operators (UoiMutator, NakedReceiver, ExceptionSwap, ArgumentPropagation, AsSpanAsMemory, MethodBodyReplacement, SpanReadOnlySpanDeclaration) (= 50 total) Maximum coverage; expect mutation volume to grow ~3-5× and runtime accordingly.

Set via CLI:

dotnet stryker-netx --mutation-profile Stronger
dotnet stryker-netx --mutation-profile All

Or in stryker-config.json:

{
  "stryker-config": {
    "mutation-profile": "Stronger"
  }
}

Excluding tests from the run (--test-filter)

Mixed unit + integration test suites can pass a dotnet test --filter-style expression to exclude container-gated or otherwise slow tests from both the initial test run and per-mutation runs:

# Both names accepted — the canonical flag matches the JSON config key,
# the alias matches `dotnet test --filter` and the wider ecosystem.
dotnet stryker-netx --test-filter      "Category!=Integration"
dotnet stryker-netx --test-case-filter "Category!=Integration"

Or in stryker-config.json:

{
  "stryker-config": {
    "test-case-filter": "Category!=Integration"
  }
}

The expression is forwarded verbatim to the underlying VsTest test host (via TestRunCriteria.TestCaseFilter and the <TestCaseFilter> runsettings element). Syntax matches Microsoft's selective-unit-tests guide — Category=Unit, Priority!=High, FullyQualifiedName~Namespace.Sub, &/| boolean combinators. Currently supported on the VsTest runner only; MTP-runner forwarding remains roadmapped (see ADR-044).

Diagnostic-only runs (--break-after, v3.2.18+)

Verify project / build / test-discovery configuration without paying for the full per-mutant test loop. Useful when iterating on --project, --solution, --mutate, or --test-filter arguments against a large suite.

dotnet stryker-netx --break-after analysis            # ~30 s — verify project discovery only
dotnet stryker-netx --break-after build               # build the projects, then exit
dotnet stryker-netx --break-after initial-test-run    # baseline test run, then exit (no mutations)
dotnet stryker-netx --break-after mutation-generation # generate mutants + flush partial HTML report, then exit (no per-mutant test loop)

Or via stryker-config.json:

{ "stryker-config": { "break-after": "mutation-generation" } }

The Aisess team's pain point (Anomalies Report § 10 wishlist #9): a 3 600-test suite paid ≈ 9 min per diagnostic run before any actionable output appeared. With --break-after, the same diagnostic finishes in ≈ 30 s. See ADR-046 §C.

Operator catalogue (v2.4.0)

Family v1.x mutators (Defaults) Stronger additions All-only additions
Arithmetic BinaryExpression, Math Aod (operator deletion), InlineConstants, ConstantReplacement (PIT CRCR, v2.1) —
Relational BinaryExpression, RelationalPattern RorMatrix (full 5-replacement matrix) —
Logical / Boolean Boolean, NegateCondition, ConditionalExpression, BinaryPattern — —
Unary PrefixUnary, PostfixUnary — UoiMutator
Strings String, StringEmpty, StringMethod, StringMethodToConstant, InterpolatedString — —
Collections / LINQ Linq, Collection, Initializer, ArrayCreation TypeDrivenReturn (cargo-mutants C2) —
Object construction ObjectCreation ConstructorNull (PIT CONSTRUCTOR_CALLS) —
Method calls (covered indirectly) — NakedReceiver (PIT EXP_NAKED_RECEIVER), ArgumentPropagation (PIT EXP_ARGUMENT_PROPAGATION, v2.0.1)
Pattern matching IsPatternExpression, RelationalPattern, BinaryPattern MatchGuard (cargo-mutants C4), SwitchArmDeletion (cargo-mutants C3, v2.0.1) —
Records (covered by ObjectCreation/Initializer) WithExpression (cargo-mutants C5) —
Member variables (covered by AssignmentExpression) MemberVariable (PIT EXP_MEMBER_VARIABLE, v2.0.1) —
Method bodies (covered by Block/Statement) — MethodBodyReplacement (cargo-mutants C1, v2.0.1)
Async / await (none in v1.x) AsyncAwait, AsyncAwaitResult (v2.3 — spec-faithful .Result variant), ConfigureAwait (v2.0.1), TaskWhenAllToWhenAny (v2.0.1) —
DateTime (none in v1.x) DateTime, DateTimeAddSign (v2.0.1) —
Span / Memory (none in v1.x) SpanMemory AsSpanAsMemory (v2.0.1), SpanReadOnlySpanDeclaration (v2.1)
Exceptions (none in v1.x) — ExceptionSwap
Other Block, Statement, Assignment, Checked, Regex, NullCoalescing — —

Total: 26 (Defaults) + 17 (Stronger) + 7 (All-only) = 50 mutators (v3.3.13; the two generic-constraint mutators were removed in Sprint 188 — generic constraints are compile-time-only and can never yield a killable mutant, see ADR-062).

The equivalent-mutant filter pipeline ships 5 filters (v2.4.0): IdentityArithmeticFilter, IdempotentBooleanFilter, ConservativeDefaultsEqualityFilter, RoslynDiagnosticsEquivalenceFilter (v2.1 — parser-error pre-filter), and RoslynSemanticDiagnosticsEquivalenceFilter (v2.4 — uses Roslyn speculative-binding for O(1) per-mutation semantic-error pre-filtering, catches type-checking errors that the parser-only v2.1 filter misses).

After v2.1.0, the catalogue closes essentially all operator-shaped recommendations from the comparison spec. Operator-level gaps from PIT/cargo-mutants/mutmut are exhausted.

Mutation execution model (updated v2.2.0)

Stryker.NET (and stryker-netx) compile all mutations into a single assembly with runtime ActiveMutationId switching. There is no per-mutant compile to optimize away. The actual cost driver is test-host process spawn per batch — already mitigated by the existing --coverage-analysis flag (default perTest), which skips uncovered mutants and reuses the test host across covered mutants.

The --engine Recompile|HotSwap flag introduced in v2.0.0 was based on a wrong mental model of this cost structure. v2.2.0 walks it back per ADR-021:

  • The flag is still accepted for backwards compatibility (no breaking change for existing scripts/configs).
  • Both values are treated identically — they have no functional effect.
  • A deprecation warning is logged when the flag is supplied explicitly.
  • The HotSwapEngine and RecompileEngine implementation classes have been deleted.
  • The MutationEngine enum, IMutationEngine interface, IStrykerOptions.MutationEngine property, and MutationEngineInput config class are marked [Obsolete] as v2.x source-compat shims; v3.0 may hard-remove them.

For real performance tuning, use --coverage-analysis perTest (default) or --coverage-analysis all — see OptimizationModes documentation. A future incremental-mutation-testing direction (file-watcher + change-driven re-run) is tracked in ADR-022 (Proposed), without commitment.

Migration from upstream Stryker.NET

If you already use dotnet-stryker, switching is a two-step rename:

  1. Uninstall the upstream tool, install stryker-netx:
    dotnet tool uninstall -g dotnet-stryker
    dotnet tool install -g dotnet-stryker-netx
    
  2. In your scripts / CI workflows, replace dotnet stryker with dotnet stryker-netx.

That's it. stryker-config.json, CLI flags, and reporter output formats are unchanged. No config file edits required.

Migration from stryker-netx v1.x to v2.0.0

See MIGRATION-v1-to-v2.md. Short version: no breaking changes for the default profile. To opt into the expanded catalogue, add --mutation-profile Stronger or --mutation-profile All.

Known limitations (v3.3.x)

  • NetFramework projects (legacy packages.config style — <TargetFramework>net48</TargetFramework>) require nuget.exe restore of the .sln before invocation, because dotnet msbuild -restore only handles <PackageReference> style. CI's windows-latest runner ships nuget.exe; local-only blocked unless nuget.exe is on PATH. (Carried forward from v1.0.)
  • JsonReport reporter uses hybrid source-gen + custom-converter serialization as of v2.3.0. Source-gen JsonReportSerializerContext provides JsonTypeInfo for the entry types JsonReport / IJsonReport; custom polymorphic converters (SourceFileConverter, JsonMutantConverter, etc.) handle interface-typed properties at runtime. Net effect: AOT-trim-progress, not AOT-trim-complete. Full AOT-trim deferred to v3.0 per ADR-024 — requires flattening IJsonReport / ISourceFile / IJsonMutant (7 interfaces, 34 referencing files) to concrete types, which is a v3.0-cadence breaking change.
  • Validation framework count-based assertions in integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs are principled-skip as of v2.3.0 per ADR-023. Counts were hardcoded for upstream Stryker.NET 4.14.1's exact mutator output; our v2.x catalogue (50 mutators vs upstream's 26) legitimately produces different counts; manual reconciliation would be a Sisyphean treadmill driftung with every Operator-Addition. The 11 affected [Fact] tests carry [Fact(Skip = "...")] with link to ADR-023. Fixture-level reconciliation is intentionally not roadmapped.
  • HotSwap engine — removed in v2.2.0 per ADR-021. The --engine flag is accepted as a deprecated no-op shim; both Recompile and HotSwap are treated identically with a deprecation warning.
  • AsyncAwait family — both variants ship: AsyncAwaitMutator (v2.0.0) emits await x → x.GetAwaiter().GetResult() (unwraps exceptions); AsyncAwaitResultMutator (v2.3.0) emits await x → x.Result (wraps in AggregateException). Tests asserting on specific exception types may pass under one and fail under the other — having both maximises kill-detection sensitivity.
  • Generic-constraint mutators removed (v3.3.13, Sprint 188) — GenericConstraintMutator (v2.0.0, drop-all) and GenericConstraintLoosenMutator (v2.1.0, per-clause) were removed per ADR-062. Generic constraints are compile-time-only and produce no IL, so a constraint mutation can only compile-error or be equivalent — never a killable mutant — and the constraint-relevant body logic is already covered by the operator mutators.
  • SpanMemoryMutator semantics — emits span.Slice(start, length) → span.Slice(0, length), a stryker-netx-specific variant. AsSpanAsMemoryMutator (v2.0.1) handles invocation-site AsSpan() ↔ AsMemory(); SpanReadOnlySpanDeclarationMutator (v2.1) handles declaration-site Span<T> ↔ ReadOnlySpan<T>. All three coexist.
  • Coverage-driven mutation skip (mutmut-style) — already shipped via OptimizationModes.SkipUncoveredMutants and CoverageBasedTest (v1.x). Use --coverage-analysis perTest (the default) or --coverage-analysis all to enable; mutants for lines without test coverage are flagged NoCoverage and never run.
  • testhost.exe keeps source-project DLLs file-locked during a run (v3.3.0 doc-note). VsTest's testhost.exe loads the test-output DLLs with the default file-share mode and only releases the handles when the Stryker process tree exits. Any concurrent dotnet build of the same solution will fail with MSB3027 retry-exhausted. Workaround: use --output <path-outside-source-tree> so MSBuild's target-copy doesn't collide with the locked test-output DLLs, or simply don't run a concurrent dotnet build while Stryker is mid-run. Not a stryker-netx code path — assembly-loading is internal to vstest/testhost.
  • Cold-run wall-clock variance (v3.3.0 doc-note). The very first mutation run on a fresh machine pays JIT-compilation + NuGet symbol-load amortization in the initial test phase — typically 4-5× slower than warm runs. Subsequent runs stabilize at the warm timing. CI runners starting cold every time will see this on every run; consider pre-warming the tool with a no-op --version call earlier in the same job.
  • Per-mutant coverage attribution is class-fixture-bound (v3.3.0 doc-note, see ADR-048). When a test constructs its own SUT instance inside the test body (e.g., to inject a Mock<ILogger<T>> per-test for assertion of log emissions), --coverage-analysis perTest may fail to attribute mutations on that SUT's methods to the test. The recommended pattern is to wire the Mock<ILogger> at the class-fixture level so every test reuses the same SUT instance. SUT-instance-aware coverage capture is roadmapped for v3.4+.
  • dotnet-tools.json rollForward pinning (general advice for bug filers). If your dotnet-tools.json pins a specific Stryker version with "rollForward": false, the in-tool upgrade banner does not roll your local install forward — only the announcement. Before filing a bug, run dotnet tool update --local dotnet-stryker-netx --version <latest> (or edit dotnet-tools.json and dotnet tool restore) and re-verify against the latest released version.

Project status

Current: v3.3.13 (Sprint 188, 2026-06) — production, published on NuGet.org. 188 sprints, 62 ADRs, ~2,200 tests green, 174 tagged releases. Full release history: GitHub Releases.

Era Sprints Versions Outcome
Architecture & design 0 — 12 founding ADRs, FRs/NFRs, test stack chosen
Port 1–4 v1.0.0-preview.1 → v1.0.0 Buildalyzer fully removed (MSBuildWorkspace), all 11+6 projects on .NET 10, C# 14 modernization, integration suite vendored, CI pipeline
Operator expansion 5–14 v2.0.0 → v2.1.0 Mutation profiles (Defaults/Stronger/All), 52-mutator catalogue (PIT + cargo-mutants + mutmut parity), SemanticModel-driven mutators, equivalence-filter pipeline
Consolidation 15–17 v2.2.0 → v2.4.0 HotSwap walk-back (ADR-021), RoslynSemanticDiagnostics filter (speculative binding), catalogue closure — operator-shaped gaps vs PIT/cargo-mutants/mutmut exhausted
Hardening 18–24 v2.5.0 → v2.11.0 Full mutator/filter unit-test coverage, FsCheck properties, NetFramework CI, nightly dogfood workflow (Stryker on Stryker)
Upstream-test-suite port 25–138 v2.12.0 → v3.0.24 MSTest→xUnit/FluentAssertions migration of the upstream suites (~1,200 dogfood tests), architectural-deferral elimination, release pipeline repair + first public NuGet push (Sprint 138)
Production bug-report era 139–169 v3.0.25 → v3.3.1 Three external reporter teams (Calculator, Aisess, filesystem-mcp-server) drove ADR-025…049: profile auto-bump, syntax-slot validation layers, .slnx filter defense, disable-comment scoping, --test-case-filter, --break-after, type-aware literal emission
CI reanimation 170 v3.3.2 NuGet-audit advisory bump (Nerdbank.MessagePack), nightly dogfood schedule repair (ADR-050), workspace + documentation refresh
Self-audit & hardening 171–184 v3.3.3 → v3.3.9 Dogfood-config netx layout + MatchesFilter fix (#270); internal 360° source audit (~430 files, 139 findings) driving ADR-053…058 — CE-noise reduction, score-integrity filters, crash/hang robustness, config reach, RegisterCoverage perf (11×)
External 360° black/white-box fixes 185–188 v3.3.10 → v3.3.13 Independent test team's 7 bugs (ADR-059…062): equivalence-filter correctness (method groups, unsigned-zero, arithmetic identities), MathMutator semantic-model path, --solution cwd-independence, README/MTP compatibility, MethodBodyReplacement injection; the two generic-constraint mutators removed as a compile-time-only model limit (ADR-062, INJ-001 closed)

See _docs/ for per-sprint lessons and the architecture specification for all ADRs.

Building from source

Requires .NET SDK 10.0.100+ (global.json pins the 10.0.1xx band with rollForward: latestFeature).

dotnet build stryker-netx.slnx
dotnet test stryker-netx.slnx
dotnet pack src/Stryker.CLI/Stryker.CLI.csproj -c Release -p:PackageVersion=$YOUR_VERSION

Contributing

See CONTRIBUTING.md. All contributions require DCO sign-off (git commit -s).

License

Licensed under the Apache License 2.0. See NOTICE for attribution to the original Stryker.NET project and its authors.

References

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
3.3.13 836 6/15/2026
Loading failed