HORIZON HASKELLDocslts/ghc-9.14.x45dd5372026-10-11Search names, modules, packages, or :: a typeCtrl K

GHC 9.14.1 · lts/ghc-9.14.x · 45dd537 · 2026-10-11

Modulesydtest-0.33.0.0Haskell2010

Test.Syd.MutationMode.Common

Utilities and types shared by the in-tree mutation child entry points (Test.Syd.MutationMode.Single, Test.Syd.MutationMode.SingleCoverage) and the out-of-tree mutation driver (sydtest-mutation-driver).

Everything in this module is parent-vs-child neutral: it lives in sydtest because both the child entry points and the driver need it.

  • 9 types
  • 16 values
  • Packagesydtest-0.33.0.0
  • Exports25
  • LanguageHaskell2010
  • LicenceLicenseRef-OtherLicense
  • SourceCommon.hs

Mutation outcomes

11 declarations
datadata MutationResult
#

Constructors

  • MutationUncovered UncoveredMutation
  • MutationKilled AugmentedMutationRecord
  • MutationTimedOut TimedOutMutation

    At least one suite's child exceeded its monotonic-clock timeout, and still did when given another go. Counted as killed in the overall score, and also recorded separately in the report: a mutation that turns a loop into one that never ends is one the suite noticed, which is what a kill means. The retry is what makes that safe to say, since a single overrun on a busy machine would not have meant it.

  • MutationSurvived SurvivedMutation
  • MutationSkipped SkippedMutation

    The mutation was not tested because an earlier mutation in the same group already failed (survived or was uncovered). Within-group fail-fast records every remaining alternative as MutationSkipped without spawning a child.

  • MutationControlPassed AugmentedMutationRecord

    A control (no-op) mutation survived, as it must: the control passed. A control changes no behaviour, so its survival confirms the harness reports a non-diff correctly. Not counted in the killed/survived score.

  • MutationControlFailed ControlFailedMutation

    A control (no-op) mutation was killed: the control failed. A no-op cannot be legitimately killed, so this means the mutation testing is unsound (a flaky/nondeterministic suite or a harness bug). Treated as a failure of the whole run, like a survivor.

Instances2Eq, Show
datadata SuiteOutcome
#

Per-suite outcome of running a single mutation child.

Constructors

  • SuiteKilled (Maybe (Path Rel File))

    Child exited non-zero — mutation killed by this suite. The optional log path points at the captured stdout/stderr, which is kept only when the kill is itself the finding: a killed control. Keeping it for every kill would mean a log per mutation in the manifest, which is the bulk of a run and says nothing a passing suite does not.

  • SuiteSurvived (Maybe (Path Rel File))

    Child exited zero — mutation survived in this suite. The optional log path points at the captured stdout/stderr (when a report dir is configured).

  • SuiteTimedOut Word (Maybe (Path Rel File))

    Child exceeded its monotonic-clock timeout and was terminated by the parent. Carries the elapsed microseconds and (optionally) the log path.

Instances2Eq, Show
  • Eq SuiteOutcomeDefined in sydtest-0.33.0.0 · Test.Syd.MutationMode.Common
  • Show SuiteOutcomeDefined in sydtest-0.33.0.0 · Test.Syd.MutationMode.Common
datadata OutcomeTally
#

Per-outcome-kind counts produced by tallyGroups. Each OutcomeTimedOut bumps both tallyKilled and tallyTimedOut because a timed-out mutation is treated as killed for scoring; tallyTimedOut carries the separate count for visibility.

Constructors

Wrap a per-suite runner so that any synchronous exception escaping it is treated as SuiteKilled. A mutation that makes the child process unrunnable (e.g. BlockedIndefinitelyOnMVar / image: loop propagating from waitExitCode, an IOException from log-file IO, a parse failure on the child's output) is conceptually indistinguishable from a mutation that makes the child crash — both mean the test detected the mutation.

SomeAsyncException is re-thrown so Ctrl-C and other cancellations still propagate out of the runner.

valueretryingIO
  1. :: Word

    Initial number of retries (0 = no retries; one attempt total).

  2. -> (String -> Word -> IO ())

    Called once per retry, with the reason and the number of retries still remaining after the current attempt.

  3. -> IO (Either String a)

    The action to run.

  4. -> IO (Either String a)
#

Retry an IO action that produces a tagged failure reason.

retryingIO retriesLeft onRetry action runs action; on Left it calls onRetry with the reason and the number of retries remaining, then runs action again with a decremented counter. When retriesLeft reaches zero, the final Left is returned unchanged (so the caller can decide how to surface the failure).

A retriesLeft of 0 means "no retries, one attempt". A retriesLeft of 3 means "up to 4 total attempts".

The action itself decides what counts as a transient failure: returning Right bypasses retry, returning Left triggers it.

Synchronous exceptions thrown by action are not caught here — they are not, in the coverage-child use case, the kind of failure we want to retry through. Use handle inside action if you need to convert exceptions to Left.

A mutation result that should trip within-group fail-fast: the test suite did not detect the mutation (survivor) or no test reaches the mutation site (uncovered). Timeouts count as killed, so they do not trip; MutationSkipped is itself a consequence of a prior failure and does not trip again.

valuerunOneGroup
  1. :: Bool

    Whether to throw MutationFailFast after the first failing result.

  2. -> (AugmentedMutationRecord -> IO MutationResult)

    How to run one mutation. Receives the record being tested.

  3. -> (MutationResult -> IO ())

    Called once per produced MutationResult (in source order) so the caller can stream results into an accumulator.

  4. -> [AugmentedMutationRecord]

    Records of this group in source order.

  5. -> IO ()
#

Run one mutation group: walk records sequentially, calling the supplied run for each record, and once one record's result is isMutationFailure record every remaining record as MutationSkipped instead of running it.

Returns the results in source order. When globalFailFast is True and a non-skipped result is a failure, MutationFailFast is thrown after the group's results have been written to the accumulator so a partial report can still be assembled by the caller.

Exceptions

2 declarations
datadata MutationFailFast
#

Thrown inside a mapConcurrently worker to abort the mutation run when fail-fast is on and a surviving or uncovered mutation is observed. The sibling workers are cancelled by mapConcurrently on the first exception.

Instances2Show, Exception
datadata CoverageFailFast
#

Thrown inside a coverage mapConcurrently worker to abort the run when a coverage child reports that its baseline test failed (exit code 2) and fail-fast is on. Distinct from MutationFailFast because the cause is different — the suite was already red before any mutation ran, so the mutation scores would be meaningless.

Instances2Show, Exception

Reporting

5 declarations

Render the per-mutation progress line emitted as each mutation is tested. index/total are this mutation's 1-based position in the run and the total mutation count, shown as a coloured [X/Y] counter. The verbose flag controls detail: in concise mode (the default) this is the single locating line [X/Y] Testing mutation <operator> at <file>:<line>:<cols>, so a long run shows steady progress — including how far along it is — without flooding the log; in verbose/debug mode the full source diff of the mutation follows — the same block the report prints for a survivor.

valuerenderUnifiedDiff
  1. :: Int
  2. -> [Text]
  3. -> [Text]
  4. -> [Text]
  5. -> [Text]
  6. -> [[Chunk]]
#

Render a unified-diff hunk with intra-line colouring. Used by both renderMutationRecord and the runtime mutation report.

startLine is the 1-based source line of the first srcLines entry; the @@@ -a,b +c,d @@ header is computed from there and the lengths of context, source, and mutated lines.

Per-survivor mitigation guidance, appended under a surviving mutation in the report: the exact disable annotation for this mutation (using its operator and, when known, its enclosing binding) and any mitigation hint the operator attached (e.g. an equivalent-mutant suppression).

Coverage progress events

6 declarations

Why the coverage phase was skipped.

Constructors

  • CoverageSkipNoMutations

    The mutation manifest contained no records (every instrumentable module was disabled, e.g. via {-# ANN module (DisableMutations ...) #-}).

  • CoverageSkipNoTests

    The test spec produced no leaf tests, so there is nothing to run coverage on.

Timing utilities

1 declaration

Difference of two getMonotonicTimeNSec readings expressed in microseconds. The monotonic clock is not affected by NTP slew or step, so timing comparisons here are robust against system-clock changes that getCurrentTime would have observed.