Lines 1-32javascript
1// mutation/probe.mjs — the minimal-decisive-probe: confirm a hollow/shadow test oracle by EXECUTION.
3// A hollow test (a tautological / shadow oracle whose expected value re-runs or re-derives the code it
4// tests) is, by definition, one that SURVIVES a non-equivalent change to the code under test. This is
5// extreme mutation testing / "pseudo-tested method" detection (Niedermayr/Juergens/Wagner 2016;
6// Descartes/Vera-Pérez/Monperrus/Baudry 2018), scoped to ONE function feeding ONE flagged assertion:
7// gut that function with a gross, guaranteed-non-equivalent return and run ONLY the one test. If a
8// deliberately-broken SUT does not break the test, the oracle is HOLLOW.
10// Pairs with the cheap static shadowOracleGuard, which is a high-recall CANDIDATE generator (it over-
11// flags — fixture-builders, formatters, equivalence tests). The probe is the precision gate: it clears
12// those false positives (a broken SUT makes a real test fail) and confirms the genuinely hollow ones.
13import { execSync } from 'node:child_process';
14import { mkdtempSync, cpSync, readFileSync, writeFileSync, rmSync, existsSync, symlinkSync } from 'node:fs';
HighChild Process
Package source references child process execution.
mutation/probe.mjsView on unpkg · L12 15import { tmpdir } from 'node:os';
16import { join } from 'node:path';
17import { codeOnly } from '../checker/lexer.mjs';
19// A mutation can induce an infinite loop (e.g. gutting a loop's termination condition), which without a
20// bound would hang the probe — and any CI / agent loop calling it — forever. Cap each test run; override
21// with GUTCHECK_PROBE_TIMEOUT_MS. A run that exceeds
22// it is treated as "not green" (the mutant is caught, or the unmutated test is too slow to probe →
23// INCONCLUSIVE at the baseline).
24const PROBE_TIMEOUT_MS = Number(process.env.GUTCHECK_PROBE_TIMEOUT_MS) || 60000;
26// Locate a named function's BODY and return a replacement descriptor, or null if it can't be located/
27// parsed (so the caller reports INCONCLUSIVE, never a wrong verdict). Handles `function NAME(` incl.
28// generators (`function* NAME` / `async function* NAME`), `def NAME(`, `NAME = function` / `NAME: function`
29// (a `=` binding or a `:` object property), parenthesised and bare single-param arrows (`NAME = x =>`),
30// block- and expression-bodied. All structural scanning is on a string/comment MASK (codeOnly blanks them
31// to spaces, positions preserved) so braces/parens/`=>` inside string literals or comments can't fool it,
32// and the parameter list is paren-balanced before the body is located so a default-param arrow