Skip to content

Cooperative hooks

Some mutants don’t fail — they hang. An inflated delay or a broken completion condition makes a test wait forever. Mutato’s outer timeout and respawn always catch these, but that’s the slow path. A test author can convert many of them into fast in-process kills by referencing the optional Mutato.Testing package and wiring hooks where the tests already control time or waiting.

The library is inert under a normal test run — the hooks only engage when the Mutato runner is driving. Both shipped assemblies are strong-named, so strong-named test projects don’t need NoWarn CS8002.

  • MutationTesting.Report(magnitude) — report the size of an operation about to start (a delay in ms, a retry count, …). The baseline pass learns each test’s legitimate maximum; a mutant whose report exceeds that ceiling throws a catchable exception at the request site.
  • MutationTesting.Checkpoint() — a wall-clock deadline check for hand-rolled wait/poll loops.
  • MutationTesting.IsActive / IsMutantActive — gate hooks so they only engage under the runner.
  • MutationTimeGuard.Wrap(timeProvider) — a ready-made delegating TimeProvider adapter.

The Polly validation uses exactly this pattern. Polly.Core.Tests adds a roughly ten-line GuardedFakeTimeProvider : FakeTimeProvider that reports each timer request, and its new FakeTimeProvider() call sites construct the subclass instead:

sealed class GuardedFakeTimeProvider : FakeTimeProvider
{
public override ITimer CreateTimer(
TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
{
// Report the delay so an inflated-delay mutant dies at the request site
// instead of hanging until the outer timeout.
MutationTesting.Report(dueTime.TotalMilliseconds);
return base.CreateTimer(callback, state, dueTime, period);
}
}

Every .Advance() call and identity assertion keeps working because the guarded provider is a FakeTimeProvider.