@fairfox/polly 0.91.0 → 0.92.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/polly.js +3 -3
- package/dist/cli/polly.js.map +3 -3
- package/dist/gallery/gallery.css +2 -2
- package/dist/gallery/gallery.js +248 -241
- package/dist/src/background/index.js +27 -11
- package/dist/src/background/index.js.map +2 -2
- package/dist/src/background/message-router.js +27 -11
- package/dist/src/background/message-router.js.map +2 -2
- package/dist/src/client/index.js +56 -56
- package/dist/src/client/index.js.map +4 -4
- package/dist/src/elysia/index.js +16 -7
- package/dist/src/elysia/index.js.map +4 -4
- package/dist/src/elysia/plugin.d.ts +4 -4
- package/dist/src/index.js +27 -11
- package/dist/src/index.js.map +2 -2
- package/dist/src/mesh.js +2 -2
- package/dist/src/mesh.js.map +3 -3
- package/dist/src/peer.js +6 -6
- package/dist/src/peer.js.map +3 -3
- package/dist/src/polly-ui/index.css +2 -2
- package/dist/src/polly-ui/markdown.js +1844 -1010
- package/dist/src/polly-ui/markdown.js.map +5 -5
- package/dist/src/polly-ui/styles.css +2 -2
- package/dist/src/shared/adapters/index.js +27 -11
- package/dist/src/shared/adapters/index.js.map +2 -2
- package/dist/src/shared/lib/context-helpers.js +27 -11
- package/dist/src/shared/lib/context-helpers.js.map +2 -2
- package/dist/src/shared/lib/message-bus.js +27 -11
- package/dist/src/shared/lib/message-bus.js.map +2 -2
- package/dist/src/shared/lib/resource.js +17 -1
- package/dist/src/shared/lib/resource.js.map +2 -2
- package/dist/src/shared/lib/state.js +17 -1
- package/dist/src/shared/lib/state.js.map +2 -2
- package/dist/src/shared/state/app-state.js +17 -1
- package/dist/src/shared/state/app-state.js.map +2 -2
- package/dist/tools/bdd/src/cli.js +20 -2
- package/dist/tools/bdd/src/cli.js.map +4 -4
- package/dist/tools/bdd/src/index.js +20 -2
- package/dist/tools/bdd/src/index.js.map +3 -3
- package/dist/tools/init/templates/pwa/package.json.template +2 -3
- package/dist/tools/mutate/src/args.d.ts +25 -3
- package/dist/tools/mutate/src/cli.js +3238 -312
- package/dist/tools/mutate/src/cli.js.map +32 -12
- package/dist/tools/mutate/src/config.d.ts +13 -2
- package/dist/tools/mutate/src/define.d.ts +76 -0
- package/dist/tools/mutate/src/engine/ast.d.ts +13 -0
- package/dist/tools/mutate/src/engine/baseline.d.ts +20 -0
- package/dist/tools/mutate/src/engine/cache.d.ts +32 -0
- package/dist/tools/mutate/src/engine/coverage.d.ts +60 -0
- package/dist/tools/mutate/src/engine/generate.d.ts +23 -0
- package/dist/tools/mutate/src/engine/graph.d.ts +11 -0
- package/dist/tools/mutate/src/engine/index.d.ts +16 -0
- package/dist/tools/mutate/src/engine/junit.d.ts +38 -0
- package/dist/tools/mutate/src/engine/operators.d.ts +14 -0
- package/dist/tools/mutate/src/engine/preload.d.ts +10 -0
- package/dist/tools/mutate/src/engine/preload.js +180 -0
- package/dist/tools/mutate/src/engine/preload.js.map +10 -0
- package/dist/tools/mutate/src/engine/probes.d.ts +12 -0
- package/dist/tools/mutate/src/engine/report-writer.d.ts +44 -0
- package/dist/tools/mutate/src/engine/runner.d.ts +31 -0
- package/dist/tools/mutate/src/engine/stray-writes.d.ts +25 -0
- package/dist/tools/mutate/src/engine/typecheck.d.ts +37 -0
- package/dist/tools/mutate/src/engine/types.d.ts +191 -0
- package/dist/tools/mutate/src/engine/verify-callsites.d.ts +9 -0
- package/dist/tools/mutate/src/engine/workdir.d.ts +16 -0
- package/dist/tools/mutate/src/engine-config.d.ts +7 -0
- package/dist/tools/mutate/src/index.d.ts +7 -3
- package/dist/tools/mutate/src/index.js +2821 -193
- package/dist/tools/mutate/src/index.js.map +26 -7
- package/dist/tools/mutate/src/ingest.d.ts +30 -1
- package/dist/tools/mutate/src/init.d.ts +2 -1
- package/dist/tools/mutate/src/migrate.d.ts +32 -0
- package/dist/tools/mutate/src/report.d.ts +3 -0
- package/dist/tools/mutate/src/run.d.ts +17 -8
- package/dist/tools/mutate/src/verify-matrix.d.ts +12 -15
- package/dist/tools/quality/src/cli.js +9 -9
- package/dist/tools/quality/src/cli.js.map +4 -4
- package/dist/tools/quality/src/index.js +9 -9
- package/dist/tools/quality/src/index.js.map +4 -4
- package/dist/tools/quality/src/no-tautology-ensures.d.ts +2 -2
- package/dist/tools/quality/src/plugins/import-checks.d.ts +1 -1
- package/dist/tools/test/src/adapters/index.js +3 -2
- package/dist/tools/test/src/adapters/index.js.map +3 -3
- package/dist/tools/test/src/coverage-policy/cli.d.ts +2 -2
- package/dist/tools/test/src/coverage-policy/cli.js +217 -17
- package/dist/tools/test/src/coverage-policy/cli.js.map +7 -5
- package/dist/tools/test/src/coverage-policy/index.d.ts +1 -1
- package/dist/tools/test/src/coverage-policy/index.js +213 -12
- package/dist/tools/test/src/coverage-policy/index.js.map +7 -5
- package/dist/tools/test/src/coverage-policy/mutate-targets.d.ts +29 -12
- package/dist/tools/test/src/e2e-mesh/index.js +6 -6
- package/dist/tools/test/src/e2e-mesh/index.js.map +4 -4
- package/dist/tools/test/src/e2e-relay/index.js +230 -226
- package/dist/tools/test/src/e2e-relay/index.js.map +7 -7
- package/dist/tools/test/src/index.js +3 -2
- package/dist/tools/test/src/index.js.map +3 -3
- package/dist/tools/test/src/visual/index.js +684 -511
- package/dist/tools/test/src/visual/index.js.map +11 -11
- package/dist/tools/verify/src/cli.js +17 -2
- package/dist/tools/verify/src/cli.js.map +4 -4
- package/dist/tools/verify/src/config.d.ts +1 -1
- package/dist/tools/verify/src/config.js +68 -3
- package/dist/tools/verify/src/config.js.map +4 -4
- package/dist/tools/verify/src/primitives/index.d.ts +34 -0
- package/dist/tools/visualize/src/cli.js +16 -1
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +29 -39
- package/dist/tools/verify/src/stryker/index.d.ts +0 -32
- package/dist/tools/verify/src/stryker/index.js +0 -86
- package/dist/tools/verify/src/stryker/index.js.map +0 -10
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import type { MutateArgs } from "./args.ts";
|
|
2
|
+
import { type MutationConfig } from "./define.ts";
|
|
3
|
+
export interface LoadedMutationConfig {
|
|
4
|
+
path: string;
|
|
5
|
+
/** "stryker": read from a Stryker JSON through the migration mapping (deprecated). */
|
|
6
|
+
format: "polly" | "stryker";
|
|
7
|
+
config: MutationConfig;
|
|
8
|
+
/** Migration notes (stryker format only). */
|
|
9
|
+
notes: string[];
|
|
10
|
+
}
|
|
2
11
|
export interface MutateConfig {
|
|
3
12
|
cwd: string;
|
|
4
|
-
/** The
|
|
5
|
-
|
|
13
|
+
/** The config the bun engine runs from. Null if none was found or it failed to load. */
|
|
14
|
+
mutation: LoadedMutationConfig | null;
|
|
15
|
+
/** Why a Stryker config found as the fallback could not be mapped (fatal only when the engine runs). */
|
|
16
|
+
mutationError: string | null;
|
|
6
17
|
/** mutation-testing-report-schema JSON the analysis reads. */
|
|
7
18
|
reportPath: string;
|
|
8
19
|
/** Intermediate SQLite db (":memory:" by default; a path persists it). */
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ContractsMode, CoverageMode, EngineMode, MutatorName } from "./engine/types.ts";
|
|
2
|
+
export declare const MUTATION_CONFIG_FILE = "polly.mutate.ts";
|
|
3
|
+
export interface MutationTests {
|
|
4
|
+
/** Test-file globs. The engine runs only the files whose imports reach a target. */
|
|
5
|
+
include: string[];
|
|
6
|
+
/** Project preloads, passed to `bun test` before the injection preload. */
|
|
7
|
+
preload?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface CommandRunner {
|
|
10
|
+
kind: "command";
|
|
11
|
+
/**
|
|
12
|
+
* The command, run once per mutant in the config dir; non-zero exit kills
|
|
13
|
+
* the mutant. Every Bun process it starts gets the injection preload via
|
|
14
|
+
* BUN_OPTIONS. No test selection, fast mode only.
|
|
15
|
+
*/
|
|
16
|
+
argv: string[];
|
|
17
|
+
}
|
|
18
|
+
interface MutationCommon {
|
|
19
|
+
/** Files to mutate: globs, `!` exclusions, and `file.ts:10-20` line ranges. */
|
|
20
|
+
targets: string[];
|
|
21
|
+
/** fast stops at the first failing test; matrix records every killer. Default fast. */
|
|
22
|
+
mode?: EngineMode;
|
|
23
|
+
/**
|
|
24
|
+
* dynamic: one probed pass over every test file finds the tests that run
|
|
25
|
+
* each mutant; a mutant no test runs is NoCoverage. static: the test files
|
|
26
|
+
* that import the target. Default dynamic; the command runner is always static.
|
|
27
|
+
*/
|
|
28
|
+
coverage?: CoverageMode;
|
|
29
|
+
/** Parallel test processes. Default: cores − 1. */
|
|
30
|
+
concurrency?: number;
|
|
31
|
+
/** Per-mutant timeout = baseline × factor + extraMs. Default 2 / 3000. */
|
|
32
|
+
timeout?: {
|
|
33
|
+
factor: number;
|
|
34
|
+
extraMs: number;
|
|
35
|
+
};
|
|
36
|
+
mutators?: {
|
|
37
|
+
exclude?: MutatorName[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Report mutants the project's TypeScript config rejects as CompileError,
|
|
41
|
+
* never run (TypeScript 7 through the `typescript-7` dependency). Default true.
|
|
42
|
+
*/
|
|
43
|
+
typecheck?: boolean;
|
|
44
|
+
/** Report mutants inside requires/ensures/… as Ignored (polly#143). Default true. */
|
|
45
|
+
ignoreVerifyCallsites?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* POLLY_CONTRACTS for every test process: a false requires/ensures throws,
|
|
48
|
+
* so each contract kills the mutants that falsify it. off keeps them
|
|
49
|
+
* no-ops; ensures enforces postconditions only. Default all.
|
|
50
|
+
*/
|
|
51
|
+
contracts?: ContractsMode;
|
|
52
|
+
/** `polly mutate run` exits 1 when the score is below `break`. */
|
|
53
|
+
thresholds?: {
|
|
54
|
+
break?: number;
|
|
55
|
+
};
|
|
56
|
+
/** Report path. Default reports/mutation/mutation.json. */
|
|
57
|
+
report?: string;
|
|
58
|
+
/** Verdict log. Default .polly/test-debt/decisions.jsonl. */
|
|
59
|
+
decisions?: string;
|
|
60
|
+
}
|
|
61
|
+
export type MutationConfig = MutationCommon & ({
|
|
62
|
+
runner?: {
|
|
63
|
+
kind: "bun-test";
|
|
64
|
+
};
|
|
65
|
+
tests: MutationTests;
|
|
66
|
+
} | {
|
|
67
|
+
runner: CommandRunner;
|
|
68
|
+
tests?: undefined;
|
|
69
|
+
});
|
|
70
|
+
export declare function defineMutation(config: MutationConfig): MutationConfig;
|
|
71
|
+
export declare function isContractsMode(v: unknown): v is ContractsMode;
|
|
72
|
+
/** Validate a loaded config's default export. `where` names the file in errors. */
|
|
73
|
+
export declare function parseMutationConfig(raw: unknown, where: string): MutationConfig;
|
|
74
|
+
/** Import a `polly.mutate.ts` (or `.js`) and validate its default export. */
|
|
75
|
+
export declare function loadMutationConfig(path: string): Promise<MutationConfig>;
|
|
76
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface AstNode {
|
|
2
|
+
type: string;
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare function isAstNode(value: unknown): value is AstNode;
|
|
8
|
+
/** The child node under `key`, or undefined when absent or not a node. */
|
|
9
|
+
export declare function child(node: AstNode, key: string): AstNode | undefined;
|
|
10
|
+
export declare function children(node: AstNode, key: string): AstNode[];
|
|
11
|
+
export declare function stringField(node: AstNode, key: string): string | undefined;
|
|
12
|
+
/** `foo(...)` → "foo"; `verify.foo(...)` → "foo"; computed access is not resolved. */
|
|
13
|
+
export declare function calleeName(call: AstNode): string | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ContractKill } from "./junit.ts";
|
|
2
|
+
import type { EngineConfig, TestRef } from "./types.ts";
|
|
3
|
+
import { type Workdirs } from "./workdir.ts";
|
|
4
|
+
export interface Baseline {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
ms: number;
|
|
7
|
+
reason?: string;
|
|
8
|
+
/** The target's tests need the project cwd, so its mutants run there. */
|
|
9
|
+
inTree: boolean;
|
|
10
|
+
/** Matrix mode: the tests that ran. */
|
|
11
|
+
tests: TestRef[];
|
|
12
|
+
/** Matrix mode: test files that reported at least one case. */
|
|
13
|
+
reportedFiles: Set<string>;
|
|
14
|
+
/** Contracts on: the tests (or the command) that fail through a contract. */
|
|
15
|
+
contractViolations?: ContractKill[];
|
|
16
|
+
}
|
|
17
|
+
export declare function measureBaseline(cfg: EngineConfig, workdirs: Workdirs | null, preloads: string[], target: {
|
|
18
|
+
file: string;
|
|
19
|
+
covering: string[];
|
|
20
|
+
}, log: (line: string) => void): Promise<Baseline>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Coverage } from "./coverage.ts";
|
|
2
|
+
import type { Mutant, MutantResult, MutantStatus, TestRef } from "./types.ts";
|
|
3
|
+
/** Bump when the runner's semantics change in a way the key does not capture. */
|
|
4
|
+
export declare const CACHE_VERSION = 2;
|
|
5
|
+
export interface CachedResult {
|
|
6
|
+
sig: string;
|
|
7
|
+
status: MutantStatus;
|
|
8
|
+
statusReason?: string;
|
|
9
|
+
durationMs?: number;
|
|
10
|
+
killedBy?: string[];
|
|
11
|
+
coveredBy?: string[];
|
|
12
|
+
killedByContract?: string[];
|
|
13
|
+
contractOnly?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface CacheEntry {
|
|
16
|
+
baselineMs: number;
|
|
17
|
+
inTree: boolean;
|
|
18
|
+
tests: TestRef[];
|
|
19
|
+
results: CachedResult[];
|
|
20
|
+
}
|
|
21
|
+
export declare function mutantSig(m: Pick<Mutant, "start" | "end" | "replacement" | "mutatorName">): string;
|
|
22
|
+
export declare function cacheKey(input: {
|
|
23
|
+
mutants: readonly Mutant[];
|
|
24
|
+
closure: readonly string[];
|
|
25
|
+
projectFiles: readonly string[];
|
|
26
|
+
settings: unknown;
|
|
27
|
+
}): Promise<string>;
|
|
28
|
+
export declare function readCache(dir: string, key: string): Promise<CacheEntry | null>;
|
|
29
|
+
export declare function writeCache(dir: string, key: string, entry: CacheEntry): void;
|
|
30
|
+
export declare function writeCoverageCache(dir: string, key: string, coverage: Coverage): void;
|
|
31
|
+
export declare function readCoverageCache(dir: string, key: string): Promise<Coverage | null>;
|
|
32
|
+
export declare function cachedResult(r: MutantResult): CachedResult;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ContractsMode, Mutant, TestRef } from "./types.ts";
|
|
2
|
+
import { type Workdirs } from "./workdir.ts";
|
|
3
|
+
/** Bump when attribution changes; part of the coverage cache key. */
|
|
4
|
+
export declare const COVERAGE_VERSION = 1;
|
|
5
|
+
export interface MutantCoverage {
|
|
6
|
+
/** Test ids that ran the mutant's code inside the test body or its hooks. */
|
|
7
|
+
tests: string[];
|
|
8
|
+
/** Test files to run whole: hits between tests, unordered files, static mutants. */
|
|
9
|
+
files: string[];
|
|
10
|
+
}
|
|
11
|
+
export type TargetCoverage = {
|
|
12
|
+
kind: "dynamic";
|
|
13
|
+
mutants: Record<string, MutantCoverage>;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "static";
|
|
16
|
+
reason: string;
|
|
17
|
+
};
|
|
18
|
+
export interface CoverageTest extends TestRef {
|
|
19
|
+
/** The full name `bun test -t` matches. */
|
|
20
|
+
filterName: string;
|
|
21
|
+
}
|
|
22
|
+
export interface Coverage {
|
|
23
|
+
targets: TargetCoverage[];
|
|
24
|
+
/** Every test the pass ran (not skipped), by id. */
|
|
25
|
+
tests: Record<string, CoverageTest>;
|
|
26
|
+
/** Test file → ids of its tests. */
|
|
27
|
+
byFile: Record<string, string[]>;
|
|
28
|
+
}
|
|
29
|
+
export interface CoverageTarget {
|
|
30
|
+
file: string;
|
|
31
|
+
/** Mutants to probe (ignored ones are left out). */
|
|
32
|
+
mutants: readonly Mutant[];
|
|
33
|
+
/** Static selection: test files whose import closure reaches the target. */
|
|
34
|
+
covering: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
export interface CoverageRun {
|
|
37
|
+
cwd: string;
|
|
38
|
+
testFiles: readonly string[];
|
|
39
|
+
targets: readonly CoverageTarget[];
|
|
40
|
+
/** Project preloads, then the engine preload. */
|
|
41
|
+
preloads: string[];
|
|
42
|
+
concurrency: number;
|
|
43
|
+
/** Scratch cwds (workdir.ts); null runs in `cwd`. */
|
|
44
|
+
workdirs: Workdirs | null;
|
|
45
|
+
/** POLLY_CONTRACTS for the pass, as for every other run. */
|
|
46
|
+
contracts: ContractsMode;
|
|
47
|
+
log: (line: string) => void;
|
|
48
|
+
}
|
|
49
|
+
/** Contiguous slices, so neighbouring files still share a process as in a full run. */
|
|
50
|
+
export declare function chunk<T>(items: readonly T[], n: number): T[][];
|
|
51
|
+
/** Run the pass. Null when a process wrote no data: every target then keeps static selection. */
|
|
52
|
+
export declare function collectCoverage(run: CoverageRun): Promise<Coverage | null>;
|
|
53
|
+
export interface Selection {
|
|
54
|
+
/** Test files to pass to `bun test`; empty means NoCoverage. */
|
|
55
|
+
files: string[];
|
|
56
|
+
/** `bun test -t`, when every selected test is named. */
|
|
57
|
+
namePattern?: string;
|
|
58
|
+
coveredBy: string[];
|
|
59
|
+
}
|
|
60
|
+
export declare function selectionFor(cov: MutantCoverage, coverage: Coverage): Selection;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ContractSite, Mutant, MutatorName, Position } from "./types.ts";
|
|
2
|
+
export interface GenerateOptions {
|
|
3
|
+
ignoreVerifyCallsites?: boolean;
|
|
4
|
+
/** Keep only mutants that start inside this 1-based inclusive line range. */
|
|
5
|
+
lines?: {
|
|
6
|
+
from: number;
|
|
7
|
+
to: number;
|
|
8
|
+
};
|
|
9
|
+
/** Prefix for mutant ids, so ids stay unique across files in one report. */
|
|
10
|
+
idPrefix?: string;
|
|
11
|
+
/** Mutators whose edits are dropped (polly.mutate.ts `mutators.exclude`). */
|
|
12
|
+
exclude?: readonly MutatorName[];
|
|
13
|
+
}
|
|
14
|
+
export interface GenerateResult {
|
|
15
|
+
mutants: Mutant[];
|
|
16
|
+
/** Mutants whose mutated text no longer parses. Reported, never run. */
|
|
17
|
+
compileErrors: Mutant[];
|
|
18
|
+
/** Every requires/ensures call, whole file, whatever `lines` and the ignore flag say. */
|
|
19
|
+
contracts: ContractSite[];
|
|
20
|
+
}
|
|
21
|
+
export declare function lineIndex(src: string): (offset: number) => Position;
|
|
22
|
+
export declare function applyMutant(src: string, m: Pick<Mutant, "start" | "end" | "replacement">): string;
|
|
23
|
+
export declare function generateMutants(file: string, src: string, opts?: GenerateOptions): GenerateResult;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ImportGraph {
|
|
2
|
+
testFiles: string[];
|
|
3
|
+
/** file → files it imports (absolute, symlink-resolved). */
|
|
4
|
+
imports: Map<string, string[]>;
|
|
5
|
+
}
|
|
6
|
+
export declare function findTestFiles(cwd: string, include: string[]): Promise<string[]>;
|
|
7
|
+
export declare function buildImportGraph(testFiles: string[]): Promise<ImportGraph>;
|
|
8
|
+
/** Test files whose import closure contains `target`, sorted. */
|
|
9
|
+
export declare function coveringTests(graph: ImportGraph, target: string): string[];
|
|
10
|
+
/** Every project file `files` load, themselves included, sorted. */
|
|
11
|
+
export declare function closureOf(graph: ImportGraph, files: readonly string[]): string[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EngineConfig, EngineResult, MutantResult } from "./types.ts";
|
|
2
|
+
export { collectCoverage, selectionFor } from "./coverage.ts";
|
|
3
|
+
export { applyMutant, generateMutants } from "./generate.ts";
|
|
4
|
+
export { buildImportGraph, coveringTests, findTestFiles } from "./graph.ts";
|
|
5
|
+
export { toMutationReport, writeReport } from "./report-writer.ts";
|
|
6
|
+
export { newPaths, snapshotWorktree } from "./stray-writes.ts";
|
|
7
|
+
export { typecheckMutants } from "./typecheck.ts";
|
|
8
|
+
export type * from "./types.ts";
|
|
9
|
+
export { VERIFY_PRIMITIVES } from "./verify-callsites.ts";
|
|
10
|
+
export interface EngineHooks {
|
|
11
|
+
log?: (line: string) => void;
|
|
12
|
+
progress?: (done: number, total: number) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function runEngine(cfg: EngineConfig, hooks?: EngineHooks): Promise<EngineResult>;
|
|
15
|
+
/** Stryker's score: detected / (detected + undetected); ignored and invalid mutants do not count. */
|
|
16
|
+
export declare function mutationScore(results: readonly MutantResult[]): number | null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface JunitCase {
|
|
2
|
+
/** Absolute path of the test file. */
|
|
3
|
+
file: string;
|
|
4
|
+
/** describe path and test name, outermost first, joined with " > ". */
|
|
5
|
+
name: string;
|
|
6
|
+
/** The same path joined with spaces: the full name `bun test -t` matches. */
|
|
7
|
+
filterName: string;
|
|
8
|
+
line: number;
|
|
9
|
+
status: "passed" | "failed" | "skipped";
|
|
10
|
+
/** A failed case: the `<failure>` message attribute and body, decoded. */
|
|
11
|
+
failureMessage?: string;
|
|
12
|
+
}
|
|
13
|
+
/** `cwd` is the directory `bun test` ran in: Bun writes `file` relative to it when it can. */
|
|
14
|
+
export declare function parseJunit(xml: string, cwd: string): JunitCase[];
|
|
15
|
+
/** `<file relative to the project>#<full name>@<line>`. */
|
|
16
|
+
export declare function testId(projectCwd: string, file: string, name: string, line: number): string;
|
|
17
|
+
/**
|
|
18
|
+
* The marker a ContractViolation's message starts with (verify primitives):
|
|
19
|
+
* `[polly-contract ensures /abs/handlers.ts:50] the author's message`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CONTRACT_MARKER: RegExp;
|
|
22
|
+
/** Contract ids (`<file relative to the project>:<line>`) named in a failure text, sorted unique. */
|
|
23
|
+
export declare function contractsIn(text: string, projectCwd: string): string[];
|
|
24
|
+
export interface ContractKill {
|
|
25
|
+
/** junit.ts `testId` of the failed case. */
|
|
26
|
+
test: string;
|
|
27
|
+
contracts: string[];
|
|
28
|
+
}
|
|
29
|
+
/** Each failed case whose failure names a contract, with the contracts it names. */
|
|
30
|
+
export declare function contractKills(cases: readonly JunitCase[], projectCwd: string): ContractKill[];
|
|
31
|
+
export declare const LOAD_FAILURE_NAME = "(file produced no results)";
|
|
32
|
+
export declare function loadFailureId(projectCwd: string, file: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The tests that detected a mutant: every failed case, plus a stand-in for
|
|
35
|
+
* each file the baseline reported that this run did not (it threw while
|
|
36
|
+
* loading, or the process died first). Empty when the process failed for a reason no test file explains.
|
|
37
|
+
*/
|
|
38
|
+
export declare function killers(cases: readonly JunitCase[], baselineFiles: ReadonlySet<string>, projectCwd: string): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type AstNode } from "./ast.ts";
|
|
2
|
+
import type { MutatorName } from "./types.ts";
|
|
3
|
+
export interface OperatorContext {
|
|
4
|
+
src: string;
|
|
5
|
+
parent: AstNode | undefined;
|
|
6
|
+
/** The key under which `node` hangs off `parent` (e.g. "test", "key"). */
|
|
7
|
+
key: string;
|
|
8
|
+
/** Every ancestor, outermost first; the last entry is `parent`. */
|
|
9
|
+
ancestors: readonly AstNode[];
|
|
10
|
+
emit(start: number, end: number, replacement: string, mutator: MutatorName): void;
|
|
11
|
+
}
|
|
12
|
+
type Operator = (node: AstNode, ctx: OperatorContext) => void;
|
|
13
|
+
export declare const OPERATORS: readonly Operator[];
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mutated text, with the line count kept: a replacement that drops lines
|
|
3
|
+
* (an emptied block, object or arrow body) is followed by a comment holding
|
|
4
|
+
* them, so a contract after it still reports its own `file:line`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function mutatedText(src: string, span: {
|
|
7
|
+
start: number;
|
|
8
|
+
end: number;
|
|
9
|
+
replacement: string;
|
|
10
|
+
}): string;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// tools/mutate/src/engine/preload.ts
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { plugin } from "bun";
|
|
4
|
+
var target = process.env["POLLY_MUTATE_FILE"];
|
|
5
|
+
var rawSpan = process.env["POLLY_MUTATE_SPAN"];
|
|
6
|
+
var coveragePlan = process.env["POLLY_MUTATE_COVERAGE"];
|
|
7
|
+
var coverageOut = process.env["POLLY_MUTATE_COVERAGE_OUT"];
|
|
8
|
+
function loaderFor(file) {
|
|
9
|
+
if (file.endsWith(".tsx"))
|
|
10
|
+
return "tsx";
|
|
11
|
+
if (file.endsWith(".jsx"))
|
|
12
|
+
return "jsx";
|
|
13
|
+
return /\.[cm]?ts$/.test(file) ? "ts" : "js";
|
|
14
|
+
}
|
|
15
|
+
function exactly(paths) {
|
|
16
|
+
const escaped = paths.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
17
|
+
return new RegExp(`^(?:${escaped.join("|")})$`);
|
|
18
|
+
}
|
|
19
|
+
function parseSpan(raw) {
|
|
20
|
+
const value = JSON.parse(raw);
|
|
21
|
+
if (typeof value === "object" && value !== null && "start" in value && "end" in value && "replacement" in value && typeof value.start === "number" && typeof value.end === "number" && typeof value.replacement === "string") {
|
|
22
|
+
return { start: value.start, end: value.end, replacement: value.replacement };
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`polly mutate preload: malformed POLLY_MUTATE_SPAN: ${raw}`);
|
|
25
|
+
}
|
|
26
|
+
function newlines(s) {
|
|
27
|
+
let n = 0;
|
|
28
|
+
for (let i = 0;i < s.length; i++)
|
|
29
|
+
if (s.charCodeAt(i) === 10)
|
|
30
|
+
n++;
|
|
31
|
+
return n;
|
|
32
|
+
}
|
|
33
|
+
function mutatedText(src, span) {
|
|
34
|
+
const lost = newlines(src.slice(span.start, span.end)) - newlines(span.replacement);
|
|
35
|
+
const pad = lost > 0 ? `/*${`
|
|
36
|
+
`.repeat(lost)}*/` : "";
|
|
37
|
+
return src.slice(0, span.start) + span.replacement + pad + src.slice(span.end);
|
|
38
|
+
}
|
|
39
|
+
if (target && rawSpan) {
|
|
40
|
+
const span = parseSpan(rawSpan);
|
|
41
|
+
const src = await Bun.file(target).text();
|
|
42
|
+
const contents = mutatedText(src, span);
|
|
43
|
+
const loader = loaderFor(target);
|
|
44
|
+
plugin({
|
|
45
|
+
name: "polly-mutate",
|
|
46
|
+
setup(build) {
|
|
47
|
+
build.onLoad({ filter: exactly([target]) }, () => ({ contents, loader }));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
var CONTRACT_VIOLATIONS_KEY = "__pollyContractViolations";
|
|
52
|
+
function violationMessages() {
|
|
53
|
+
const list = globalThis[CONTRACT_VIOLATIONS_KEY];
|
|
54
|
+
if (!Array.isArray(list))
|
|
55
|
+
return [];
|
|
56
|
+
const messages = list.map((v) => v instanceof Error ? v.message : String(v));
|
|
57
|
+
list.length = 0;
|
|
58
|
+
return messages;
|
|
59
|
+
}
|
|
60
|
+
var contracts = process.env["POLLY_CONTRACTS"];
|
|
61
|
+
if (contracts === "ensures" || contracts === "all") {
|
|
62
|
+
try {
|
|
63
|
+
const { afterEach, beforeEach } = await import("bun:test");
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
for (const m of violationMessages()) {
|
|
66
|
+
process.stderr.write(`polly mutate: contract violated outside any test: ${m}
|
|
67
|
+
`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
const messages = violationMessages();
|
|
72
|
+
if (messages.length > 0)
|
|
73
|
+
throw new Error(messages.join(`
|
|
74
|
+
`));
|
|
75
|
+
});
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
function isStringList(v) {
|
|
79
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
80
|
+
}
|
|
81
|
+
function parsePlan(raw) {
|
|
82
|
+
const value = JSON.parse(raw);
|
|
83
|
+
if (typeof value === "object" && value !== null && "targets" in value && "tests" in value && Array.isArray(value.targets) && isStringList(value.tests)) {
|
|
84
|
+
const targets = [];
|
|
85
|
+
for (const t of value.targets) {
|
|
86
|
+
if (typeof t === "object" && t !== null && "file" in t && "code" in t && typeof t.file === "string" && typeof t.code === "string") {
|
|
87
|
+
targets.push({ file: t.file, code: t.code });
|
|
88
|
+
} else
|
|
89
|
+
throw new Error("polly mutate preload: malformed coverage plan target");
|
|
90
|
+
}
|
|
91
|
+
return { targets, tests: value.tests };
|
|
92
|
+
}
|
|
93
|
+
throw new Error("polly mutate preload: malformed coverage plan");
|
|
94
|
+
}
|
|
95
|
+
if (coveragePlan && coverageOut) {
|
|
96
|
+
const plan = parsePlan(readFileSync(coveragePlan, "utf8"));
|
|
97
|
+
const { afterAll, afterEach, beforeEach } = await import("bun:test");
|
|
98
|
+
const files = plan.tests.map(() => ({
|
|
99
|
+
load: new Set,
|
|
100
|
+
between: new Set,
|
|
101
|
+
tests: [],
|
|
102
|
+
overlapped: false
|
|
103
|
+
}));
|
|
104
|
+
const outside = new Set;
|
|
105
|
+
const served = new Set;
|
|
106
|
+
let current = outside;
|
|
107
|
+
let file;
|
|
108
|
+
let running = 0;
|
|
109
|
+
Object.assign(globalThis, {
|
|
110
|
+
__pollyMutateHit(probe) {
|
|
111
|
+
current.add(probe);
|
|
112
|
+
},
|
|
113
|
+
__pollyMutateFile(index) {
|
|
114
|
+
file = files[index];
|
|
115
|
+
running = 0;
|
|
116
|
+
current = file?.load ?? outside;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
if (!file)
|
|
121
|
+
return;
|
|
122
|
+
running++;
|
|
123
|
+
if (running > 1)
|
|
124
|
+
file.overlapped = true;
|
|
125
|
+
const hits = new Set;
|
|
126
|
+
file.tests.push(hits);
|
|
127
|
+
current = hits;
|
|
128
|
+
});
|
|
129
|
+
afterEach(() => {
|
|
130
|
+
if (!file)
|
|
131
|
+
return;
|
|
132
|
+
running--;
|
|
133
|
+
current = file.between;
|
|
134
|
+
});
|
|
135
|
+
afterAll(() => {
|
|
136
|
+
const list = (s) => [...s];
|
|
137
|
+
writeFileSync(coverageOut, JSON.stringify({
|
|
138
|
+
served: list(served),
|
|
139
|
+
outside: list(outside),
|
|
140
|
+
files: files.map((f) => ({
|
|
141
|
+
load: list(f.load),
|
|
142
|
+
between: list(f.between),
|
|
143
|
+
tests: f.tests.map(list),
|
|
144
|
+
overlapped: f.overlapped
|
|
145
|
+
}))
|
|
146
|
+
}));
|
|
147
|
+
});
|
|
148
|
+
const targetIndex = new Map(plan.targets.map((t, i) => [t.file, i]));
|
|
149
|
+
const testIndex = new Map(plan.tests.map((t, i) => [t, i]));
|
|
150
|
+
plugin({
|
|
151
|
+
name: "polly-mutate-coverage",
|
|
152
|
+
setup(build) {
|
|
153
|
+
for (const i of testIndex.values()) {
|
|
154
|
+
build.module(`polly-mutate-mark:${i}`, () => ({
|
|
155
|
+
contents: `globalThis.__pollyMutateFile?.(${i});`,
|
|
156
|
+
loader: "js"
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
build.onLoad({ filter: exactly([...targetIndex.keys(), ...testIndex.keys()]) }, (args) => {
|
|
160
|
+
const loader = loaderFor(args.path);
|
|
161
|
+
const t = targetIndex.get(args.path);
|
|
162
|
+
const planned = t === undefined ? undefined : plan.targets[t];
|
|
163
|
+
if (t !== undefined && planned) {
|
|
164
|
+
served.add(t);
|
|
165
|
+
return { contents: planned.code, loader };
|
|
166
|
+
}
|
|
167
|
+
const src = readFileSync(args.path, "utf8");
|
|
168
|
+
const mark = `import "polly-mutate-mark:${testIndex.get(args.path)}";`;
|
|
169
|
+
const cut = src.startsWith("#!") ? src.indexOf(`
|
|
170
|
+
`) + 1 : 0;
|
|
171
|
+
return { contents: src.slice(0, cut) + mark + src.slice(cut), loader };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
export {
|
|
177
|
+
mutatedText
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
//# debugId=679EED02066D37BE64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../tools/mutate/src/engine/preload.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * The engine's preload, passed to `bun test --preload` by the runner. Two modes.\n *\n * Mutant (POLLY_MUTATE_FILE + POLLY_MUTATE_SPAN): a Bun plugin whose onLoad\n * answers for exactly that file with the mutated text. Nothing is copied and\n * no other file is touched. With neither variable set it does nothing, which\n * is how the runner measures each target's baseline.\n *\n * Coverage (POLLY_MUTATE_COVERAGE, a plan written by coverage.ts): every\n * target loads with its probes (probes.ts), every test file loads behind a\n * marker import that names it, and global beforeEach/afterEach hooks split\n * the hits into buckets — while a file loads, inside each test (by ordinal),\n * and between tests. A global afterAll writes the buckets to\n * POLLY_MUTATE_COVERAGE_OUT; coverage.ts matches the ordinals to the junit\n * report. The marker is an import, not a statement, because a test file's\n * imports run before its first statement.\n *\n * A separate build entrypoint: the runner spawns it by path, so it must exist\n * as its own file in dist (see build-lib.ts). It imports only built-ins.\n *\n * CLI `--preload` adds to a bunfig `[test].preload`, so the project's own\n * preloads still run. One limit: a file that a project preload imports is\n * already loaded before this plugin registers, so it cannot be mutated.\n *\n * Contracts (POLLY_CONTRACTS, set by the runner): the verify primitives\n * record each violation on globalThis before throwing, because the message\n * bus turns a handler's throw into `{ success: false }` and the test may\n * never see it. A global afterEach fails the test that left a violation\n * behind; a beforeEach clears what module load or beforeAll left, since that\n * belongs to no test. Under the command runner `bun:test` is absent and the\n * BDD runner drains the list itself.\n */\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { plugin } from \"bun\";\n\nconst target = process.env[\"POLLY_MUTATE_FILE\"];\nconst rawSpan = process.env[\"POLLY_MUTATE_SPAN\"];\nconst coveragePlan = process.env[\"POLLY_MUTATE_COVERAGE\"];\nconst coverageOut = process.env[\"POLLY_MUTATE_COVERAGE_OUT\"];\n\nfunction loaderFor(file: string): \"tsx\" | \"jsx\" | \"ts\" | \"js\" {\n if (file.endsWith(\".tsx\")) return \"tsx\";\n if (file.endsWith(\".jsx\")) return \"jsx\";\n return /\\.[cm]?ts$/.test(file) ? \"ts\" : \"js\";\n}\n\nfunction exactly(paths: readonly string[]): RegExp {\n const escaped = paths.map((p) => p.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"));\n return new RegExp(`^(?:${escaped.join(\"|\")})$`);\n}\n\nfunction parseSpan(raw: string): { start: number; end: number; replacement: string } {\n const value: unknown = JSON.parse(raw);\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"start\" in value &&\n \"end\" in value &&\n \"replacement\" in value &&\n typeof value.start === \"number\" &&\n typeof value.end === \"number\" &&\n typeof value.replacement === \"string\"\n ) {\n return { start: value.start, end: value.end, replacement: value.replacement };\n }\n throw new Error(`polly mutate preload: malformed POLLY_MUTATE_SPAN: ${raw}`);\n}\n\nfunction newlines(s: string): number {\n let n = 0;\n for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 10) n++;\n return n;\n}\n\n/**\n * The mutated text, with the line count kept: a replacement that drops lines\n * (an emptied block, object or arrow body) is followed by a comment holding\n * them, so a contract after it still reports its own `file:line`.\n */\nexport function mutatedText(\n src: string,\n span: { start: number; end: number; replacement: string }\n): string {\n const lost = newlines(src.slice(span.start, span.end)) - newlines(span.replacement);\n const pad = lost > 0 ? `/*${\"\\n\".repeat(lost)}*/` : \"\";\n return src.slice(0, span.start) + span.replacement + pad + src.slice(span.end);\n}\n\nif (target && rawSpan) {\n const span = parseSpan(rawSpan);\n const src = await Bun.file(target).text();\n const contents = mutatedText(src, span);\n const loader = loaderFor(target);\n plugin({\n name: \"polly-mutate\",\n setup(build) {\n build.onLoad({ filter: exactly([target]) }, () => ({ contents, loader }));\n },\n });\n}\n\nconst CONTRACT_VIOLATIONS_KEY = \"__pollyContractViolations\";\n\nfunction violationMessages(): string[] {\n const list = (globalThis as Record<string, unknown>)[CONTRACT_VIOLATIONS_KEY];\n if (!Array.isArray(list)) return [];\n const messages = list.map((v) => (v instanceof Error ? v.message : String(v)));\n list.length = 0;\n return messages;\n}\n\nconst contracts = process.env[\"POLLY_CONTRACTS\"];\nif (contracts === \"ensures\" || contracts === \"all\") {\n try {\n const { afterEach, beforeEach } = await import(\"bun:test\");\n beforeEach(() => {\n for (const m of violationMessages()) {\n process.stderr.write(`polly mutate: contract violated outside any test: ${m}\\n`);\n }\n });\n afterEach(() => {\n const messages = violationMessages();\n if (messages.length > 0) throw new Error(messages.join(\"\\n\"));\n });\n } catch {\n // Not under `bun test` (the command runner): the BDD runner drains the list.\n }\n}\n\ninterface CoveragePlan {\n targets: { file: string; code: string }[];\n tests: string[];\n}\n\nfunction isStringList(v: unknown): v is string[] {\n return Array.isArray(v) && v.every((x) => typeof x === \"string\");\n}\n\nfunction parsePlan(raw: string): CoveragePlan {\n const value: unknown = JSON.parse(raw);\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"targets\" in value &&\n \"tests\" in value &&\n Array.isArray(value.targets) &&\n isStringList(value.tests)\n ) {\n const targets: CoveragePlan[\"targets\"] = [];\n for (const t of value.targets) {\n if (\n typeof t === \"object\" &&\n t !== null &&\n \"file\" in t &&\n \"code\" in t &&\n typeof t.file === \"string\" &&\n typeof t.code === \"string\"\n ) {\n targets.push({ file: t.file, code: t.code });\n } else throw new Error(\"polly mutate preload: malformed coverage plan target\");\n }\n return { targets, tests: value.tests };\n }\n throw new Error(\"polly mutate preload: malformed coverage plan\");\n}\n\ninterface FileHits {\n load: Set<number>;\n between: Set<number>;\n tests: Set<number>[];\n /** A test began while another was running (concurrent tests): ordinals mean nothing. */\n overlapped: boolean;\n}\n\nif (coveragePlan && coverageOut) {\n const plan = parsePlan(readFileSync(coveragePlan, \"utf8\"));\n const { afterAll, afterEach, beforeEach } = await import(\"bun:test\");\n const files: FileHits[] = plan.tests.map(() => ({\n load: new Set(),\n between: new Set(),\n tests: [],\n overlapped: false,\n }));\n const outside = new Set<number>();\n const served = new Set<number>();\n let current = outside;\n let file: FileHits | undefined;\n let running = 0;\n\n Object.assign(globalThis, {\n __pollyMutateHit(probe: number) {\n current.add(probe);\n },\n __pollyMutateFile(index: number) {\n file = files[index];\n running = 0;\n current = file?.load ?? outside;\n },\n });\n beforeEach(() => {\n if (!file) return;\n running++;\n if (running > 1) file.overlapped = true;\n const hits = new Set<number>();\n file.tests.push(hits);\n current = hits;\n });\n afterEach(() => {\n if (!file) return;\n running--;\n current = file.between;\n });\n afterAll(() => {\n const list = (s: Set<number>) => [...s];\n writeFileSync(\n coverageOut,\n JSON.stringify({\n served: list(served),\n outside: list(outside),\n files: files.map((f) => ({\n load: list(f.load),\n between: list(f.between),\n tests: f.tests.map(list),\n overlapped: f.overlapped,\n })),\n })\n );\n });\n\n const targetIndex = new Map(plan.targets.map((t, i) => [t.file, i]));\n const testIndex = new Map(plan.tests.map((t, i) => [t, i]));\n plugin({\n name: \"polly-mutate-coverage\",\n setup(build) {\n for (const i of testIndex.values()) {\n build.module(`polly-mutate-mark:${i}`, () => ({\n contents: `globalThis.__pollyMutateFile?.(${i});`,\n loader: \"js\",\n }));\n }\n // Synchronous on purpose: an async onLoad makes the module async, and\n // `require()` of an async module throws (mock.module factories use require).\n build.onLoad({ filter: exactly([...targetIndex.keys(), ...testIndex.keys()]) }, (args) => {\n const loader = loaderFor(args.path);\n const t = targetIndex.get(args.path);\n const planned = t === undefined ? undefined : plan.targets[t];\n if (t !== undefined && planned) {\n served.add(t);\n return { contents: planned.code, loader };\n }\n const src = readFileSync(args.path, \"utf8\");\n const mark = `import \"polly-mutate-mark:${testIndex.get(args.path)}\";`;\n // After a shebang line, and on the same line as the code: no line moves.\n const cut = src.startsWith(\"#!\") ? src.indexOf(\"\\n\") + 1 : 0;\n return { contents: src.slice(0, cut) + mark + src.slice(cut), loader };\n });\n },\n });\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "679EED02066D37BE64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Mutant } from "./types.ts";
|
|
2
|
+
export declare const HIT = "globalThis.__pollyMutateHit?.";
|
|
3
|
+
export interface Instrumented {
|
|
4
|
+
code: string;
|
|
5
|
+
/** Mutant ids per probe, indexed by `probe - firstProbe`. */
|
|
6
|
+
probes: string[][];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Probe calls numbered from `firstProbe`. Null when the result does not
|
|
10
|
+
* parse (the target then keeps static selection).
|
|
11
|
+
*/
|
|
12
|
+
export declare function instrument(file: string, src: string, mutants: readonly Mutant[], firstProbe: number): Instrumented | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MutationReport, ReportContracts } from "../ingest.ts";
|
|
2
|
+
import type { ContractsMode, EngineResult } from "./types.ts";
|
|
3
|
+
export declare const ENGINE_NAME = "polly mutate (bun engine)";
|
|
4
|
+
export interface WrittenReport extends MutationReport {
|
|
5
|
+
thresholds: {
|
|
6
|
+
high: number;
|
|
7
|
+
low: number;
|
|
8
|
+
};
|
|
9
|
+
framework: {
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
|
12
|
+
files: Record<string, {
|
|
13
|
+
language: string;
|
|
14
|
+
source: string;
|
|
15
|
+
mutants: {
|
|
16
|
+
id: string;
|
|
17
|
+
mutatorName: string;
|
|
18
|
+
replacement: string;
|
|
19
|
+
status: string;
|
|
20
|
+
statusReason?: string;
|
|
21
|
+
location: {
|
|
22
|
+
start: {
|
|
23
|
+
line: number;
|
|
24
|
+
column: number;
|
|
25
|
+
};
|
|
26
|
+
end: {
|
|
27
|
+
line: number;
|
|
28
|
+
column: number;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
killedBy: string[];
|
|
32
|
+
coveredBy: string[];
|
|
33
|
+
killedByContract: string[];
|
|
34
|
+
contractOnly?: boolean;
|
|
35
|
+
}[];
|
|
36
|
+
}>;
|
|
37
|
+
contracts: ReportContracts;
|
|
38
|
+
}
|
|
39
|
+
export interface ReportMeta {
|
|
40
|
+
contracts: ContractsMode;
|
|
41
|
+
runner: "bun-test" | "command";
|
|
42
|
+
}
|
|
43
|
+
export declare function toMutationReport(result: EngineResult, cwd: string, meta: ReportMeta): Promise<WrittenReport>;
|
|
44
|
+
export declare function writeReport(report: WrittenReport, path: string): Promise<void>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ContractsMode, EngineRunner } from "./types.ts";
|
|
2
|
+
export interface BunTestRun {
|
|
3
|
+
cwd: string;
|
|
4
|
+
files: string[];
|
|
5
|
+
/** Every `--preload`, in order; the mutant preload goes last. */
|
|
6
|
+
preloads: string[];
|
|
7
|
+
env?: Record<string, string>;
|
|
8
|
+
/** POLLY_CONTRACTS for the process (both runner kinds). Unset: off. */
|
|
9
|
+
contracts?: ContractsMode;
|
|
10
|
+
bail: boolean;
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
/** Capture the tail of stderr (baselines only; mutant runs discard output). */
|
|
13
|
+
captureStderr?: boolean;
|
|
14
|
+
/** Ask for Bun's junit report and return its XML (matrix mode). */
|
|
15
|
+
junit?: boolean;
|
|
16
|
+
/** `bun test -t`: run only the tests whose full name matches. */
|
|
17
|
+
namePattern?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface BunTestOutcome {
|
|
20
|
+
exitCode: number;
|
|
21
|
+
timedOut: boolean;
|
|
22
|
+
ms: number;
|
|
23
|
+
stderrTail?: string;
|
|
24
|
+
/** The junit XML; undefined when not asked for or not written (a crash). */
|
|
25
|
+
junitXml?: string;
|
|
26
|
+
}
|
|
27
|
+
/** The preload as built (dist) or as source (monorepo / bun-run-from-source). */
|
|
28
|
+
export declare function preloadPath(): string;
|
|
29
|
+
export declare function runBunTest(run: BunTestRun, runner?: EngineRunner): Promise<BunTestOutcome>;
|
|
30
|
+
/** Run `fn` over `items` with at most `concurrency` in flight; results keep input order. */
|
|
31
|
+
export declare function pool<T, R>(items: readonly T[], concurrency: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
|