@orangepro/orangepro-mcp 0.2.46 → 0.2.47
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/local/autoProve.js +68 -4
- package/dist/local/generate/generator.js +12 -3
- package/dist/local/operations.js +9 -2
- package/dist/local/viz/behaviorReportData.js +4 -1
- package/package.json +1 -1
- package/scripts/spikes/dynamic-proof-spike.mjs +36 -2
- package/scripts/spikes/go-dynamic-proof-spike.mjs +22 -10
- package/scripts/spikes/java-dynamic-proof-spike.mjs +22 -10
package/dist/local/autoProve.js
CHANGED
|
@@ -17,6 +17,7 @@ import { basename, dirname, join, posix, relative, resolve, sep } from "node:pat
|
|
|
17
17
|
import { generateTests, readDeclaredDeps, unresolvedLocalImports } from "./generate/generator.js";
|
|
18
18
|
import { GENERATED_DIR, runHintsFor } from "./generate/runHints.js";
|
|
19
19
|
import { rankRiskGaps } from "./score/risk.js";
|
|
20
|
+
import { globToRegExp, loadRiskConfig } from "./score/riskConfig.js";
|
|
20
21
|
import { resolveProviderConfig } from "./localConfig.js";
|
|
21
22
|
import { buildProvider, DeterministicProvider } from "./generate/providers.js";
|
|
22
23
|
import { resolveContained } from "./reprove/paths.js";
|
|
@@ -371,6 +372,14 @@ const GEN_WINDOW = 5;
|
|
|
371
372
|
// file (every eligible symbol × every importing test) cannot starve the shared budget before
|
|
372
373
|
// a provable hard-edge symbol is tried. Small K; promote to a flag if a repo needs a wider sweep.
|
|
373
374
|
const EXISTING_LANE_MAX_WEAK_PER_SYMBOL = 3;
|
|
375
|
+
// A runner that crashes without returning a result (no JSON) fails for the whole
|
|
376
|
+
// project root, not for one test: test-specific problems come back as a classified
|
|
377
|
+
// result. Three crashes in a row in one root stop further calls there instead of
|
|
378
|
+
// walking every remaining linked target (litellm-scale repos queue 1,000+).
|
|
379
|
+
const RUNNER_CRASH_LIMIT = 3;
|
|
380
|
+
const RUNNER_CRASH_RE = /did not return JSON/;
|
|
381
|
+
// Out of disk space is machine-wide: every later sandbox copy fails the same way.
|
|
382
|
+
const DISK_FULL_RE = /\bENOSPC\b|no space left on device/i;
|
|
374
383
|
const RUNNER_ROOT_MARKERS = {
|
|
375
384
|
typescript: ["package.json"],
|
|
376
385
|
javascript: ["package.json"],
|
|
@@ -867,6 +876,13 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
867
876
|
let nonPythonCountedAttempts = 0;
|
|
868
877
|
const pythonPreflightGreen = new Set();
|
|
869
878
|
const pythonBlockedCounts = new Map();
|
|
879
|
+
// classification.rank_exclude_paths removes paths from ranking; proof follows the
|
|
880
|
+
// same scope so excluded areas never cost runner calls or sandbox copies.
|
|
881
|
+
const proofExcludeRe = loadRiskConfig(sourceRoot).config.classification.rank_exclude_paths.map(globToRegExp);
|
|
882
|
+
const excludedTargets = new Set();
|
|
883
|
+
const runnerCrashes = new Map();
|
|
884
|
+
const crashBlocked = new Map();
|
|
885
|
+
let diskFull;
|
|
870
886
|
const changed = opts.changedFiles && opts.changedFiles.length > 0 ? new Set(opts.changedFiles) : null;
|
|
871
887
|
const reader = fileReaderFor(sourceRoot); // R-2: source scan for the node:sqlite env profile
|
|
872
888
|
// Fix 3: hard TESTED_BY/COVERS pairs first, weak MAY_* pairs after and capped per symbol.
|
|
@@ -884,6 +900,12 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
884
900
|
continue;
|
|
885
901
|
if (changed && !changed.has(symbolFileOf(node)))
|
|
886
902
|
continue;
|
|
903
|
+
if (diskFull)
|
|
904
|
+
break;
|
|
905
|
+
if (proofExcludeRe.length > 0 && proofExcludeRe.some((re) => re.test(symbolFileOf(node)))) {
|
|
906
|
+
excludedTargets.add(symId);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
887
909
|
// Fix 1: already Proven for the CURRENT code (fingerprint match under #162) → skip. Re-closing
|
|
888
910
|
// it would overstate newly-proven and append a redundant closed cert every run.
|
|
889
911
|
if (alreadyProven.has(symId))
|
|
@@ -894,6 +916,12 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
894
916
|
const targetFileRel = symbolFileOf(node);
|
|
895
917
|
const targetLanguageName = language ?? targetLanguage(symId);
|
|
896
918
|
const runnerRoot = projectRoot ?? proofRunnerRoot(sourceRoot, targetFileRel, testRel);
|
|
919
|
+
const crashKey = `${targetLanguageName}\u0000${runnerRoot}`;
|
|
920
|
+
const crashBlock = crashBlocked.get(crashKey);
|
|
921
|
+
if (crashBlock) {
|
|
922
|
+
crashBlock.skipped++;
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
897
925
|
// A prior candidate in this exact runner project hit a classified project-wide
|
|
898
926
|
// toolchain/environment failure. Preserve the budget and explain the skip.
|
|
899
927
|
const isPython = isPythonFile(targetFileRel);
|
|
@@ -980,9 +1008,22 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
980
1008
|
attempted--;
|
|
981
1009
|
if (!isPython)
|
|
982
1010
|
nonPythonCountedAttempts--;
|
|
983
|
-
|
|
1011
|
+
const message = redactSecrets(errMsg(e));
|
|
1012
|
+
reportProgress(`proof skipped: ${symId}: ${message}`);
|
|
1013
|
+
if (DISK_FULL_RE.test(message)) {
|
|
1014
|
+
diskFull = message;
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
if (RUNNER_CRASH_RE.test(message)) {
|
|
1018
|
+
const crashes = (runnerCrashes.get(crashKey) ?? 0) + 1;
|
|
1019
|
+
runnerCrashes.set(crashKey, crashes);
|
|
1020
|
+
if (crashes >= RUNNER_CRASH_LIMIT) {
|
|
1021
|
+
crashBlocked.set(crashKey, { language: targetLanguageName, root: runnerRoot, skipped: 0, lastError: message.slice(0, 200) });
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
984
1024
|
continue;
|
|
985
1025
|
}
|
|
1026
|
+
runnerCrashes.delete(crashKey);
|
|
986
1027
|
const { classification, reason, category } = classifyProof(result, { sourceRoot, targetFileRel });
|
|
987
1028
|
const baselineGreen = baselineGreenOf(result);
|
|
988
1029
|
if (isPython) {
|
|
@@ -1036,7 +1077,28 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
1036
1077
|
project_root: root,
|
|
1037
1078
|
blocked_by: block.blockedBy
|
|
1038
1079
|
}));
|
|
1039
|
-
|
|
1080
|
+
for (const block of crashBlocked.values()) {
|
|
1081
|
+
skipped.push({
|
|
1082
|
+
title: `${block.language} project ${block.root}`,
|
|
1083
|
+
reason: `the proof runner crashed ${RUNNER_CRASH_LIMIT} times in a row without returning a result; ${block.skipped} remaining linked target${block.skipped === 1 ? " was" : "s were"} not attempted in this root. Last error: ${block.lastError}`,
|
|
1084
|
+
language: block.language,
|
|
1085
|
+
project_root: block.root
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
if (excludedTargets.size > 0) {
|
|
1089
|
+
const n = excludedTargets.size;
|
|
1090
|
+
skipped.push({
|
|
1091
|
+
title: "Excluded paths",
|
|
1092
|
+
reason: `${n} linked target${n === 1 ? " is" : "s are"} under classification.rank_exclude_paths and ${n === 1 ? "was" : "were"} not attempted.`
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
if (diskFull) {
|
|
1096
|
+
skipped.push({
|
|
1097
|
+
title: "Disk full",
|
|
1098
|
+
reason: `the disk ran out of space while preparing a proof sandbox; all remaining proof attempts were stopped. Free disk space and rerun. (${diskFull.slice(0, 200)})`
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
return { attempts, needsSetup, skipped, proven, attempted, nonPythonAttempted: nonPythonCountedAttempts, provenSymbols, ...(diskFull ? { diskFull } : {}) };
|
|
1040
1102
|
}
|
|
1041
1103
|
/**
|
|
1042
1104
|
* Drive prove for the top provable TS/JS targets. Two lanes: (1) prove the repo's OWN
|
|
@@ -1083,12 +1145,14 @@ export async function autoProve(root, opts, deps) {
|
|
|
1083
1145
|
const genLimit = Math.max(1, Math.min(MAX_AUTO_LIMIT, Math.floor(opts.proof_gen_limit ?? DEFAULT_PROOF_GEN_LIMIT)));
|
|
1084
1146
|
// ── Lane 1: existing associated tests — NO key required, runs FIRST (PR 1.5). ──
|
|
1085
1147
|
const ex = proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, proveLoop, proveDeps, alreadyProven, projectBlocked, existingLimit, genLimit, autoLimit);
|
|
1086
|
-
if (opts.existingOnly) {
|
|
1148
|
+
if (opts.existingOnly || ex.diskFull) {
|
|
1087
1149
|
const status = ex.proven > 0 ? "proven-run" : ex.attempted > 0 ? "ran-no-proof" : "no-targets";
|
|
1088
1150
|
return {
|
|
1089
1151
|
ran: ex.attempted > 0,
|
|
1090
1152
|
status,
|
|
1091
|
-
reason:
|
|
1153
|
+
reason: ex.diskFull
|
|
1154
|
+
? "Proof stopped: the disk ran out of space while preparing a proof sandbox. Free disk space and rerun."
|
|
1155
|
+
: "existing-tests-only: generation disabled.",
|
|
1092
1156
|
attempted: ex.attempted,
|
|
1093
1157
|
proven: ex.proven,
|
|
1094
1158
|
needs_setup: ex.needsSetup,
|
|
@@ -26,7 +26,7 @@ const MAX_RELATED_FILES = 4;
|
|
|
26
26
|
const SYMBOL_EXCERPT_CONTEXT_LINES = 3;
|
|
27
27
|
const MAX_EXCERPT_CHARS = 8000;
|
|
28
28
|
const MAX_TARGET_TYPE_EXCERPT_CHARS = 5000;
|
|
29
|
-
const STATIC_CHECK_TIMEOUT_MS =
|
|
29
|
+
const STATIC_CHECK_TIMEOUT_MS = 10000;
|
|
30
30
|
// Large Go monorepos can spend tens of seconds populating a cold module cache
|
|
31
31
|
// before the target package is compiled. Keep the check authoritative instead
|
|
32
32
|
// of downgrading valid generated tests while dependencies are still downloading.
|
|
@@ -71,6 +71,11 @@ function testFilesForSourceFile(graph, sourceFile) {
|
|
|
71
71
|
}
|
|
72
72
|
return [...out];
|
|
73
73
|
}
|
|
74
|
+
/** `Model provider HTTP 429: {...body...}` → `Model provider HTTP 429 (response body omitted)`. */
|
|
75
|
+
export function persistedProviderError(message) {
|
|
76
|
+
const match = /^(Model provider HTTP \d+):/.exec(message);
|
|
77
|
+
return match ? `${match[1]} (response body omitted)` : message;
|
|
78
|
+
}
|
|
74
79
|
/**
|
|
75
80
|
* Strip a Markdown code fence (and any prose around it) from a model body. Real
|
|
76
81
|
* BYOK models frequently wrap the test in ```ts … ``` with a prose preamble/suffix;
|
|
@@ -2389,14 +2394,18 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2389
2394
|
}
|
|
2390
2395
|
catch (callErr) {
|
|
2391
2396
|
const msg = redactSecrets(callErr instanceof Error ? callErr.message : String(callErr));
|
|
2397
|
+
// The terminal warning keeps the provider's response for diagnosis. Persisted
|
|
2398
|
+
// drafts and missing-evidence rows end up in shareable reports, so they carry
|
|
2399
|
+
// only the status line, never the provider's response body (account/billing text).
|
|
2400
|
+
const persisted = persistedProviderError(msg);
|
|
2392
2401
|
warnings.push(`V5 planning call failed for "${gc.ctx.behavior_title}": ${msg} — no test emitted.`);
|
|
2393
2402
|
missing.push({
|
|
2394
2403
|
external_id: behavior.external_id,
|
|
2395
2404
|
title: gc.ctx.behavior_title,
|
|
2396
|
-
reason: `V5 planning call failed: ${
|
|
2405
|
+
reason: `V5 planning call failed: ${persisted}`,
|
|
2397
2406
|
needed: ["a reachable model provider"]
|
|
2398
2407
|
});
|
|
2399
|
-
emitManualPlanningFallback(`V5 planning call failed: ${
|
|
2408
|
+
emitManualPlanningFallback(`V5 planning call failed: ${persisted}.`);
|
|
2400
2409
|
continue;
|
|
2401
2410
|
}
|
|
2402
2411
|
try {
|
package/dist/local/operations.js
CHANGED
|
@@ -1687,7 +1687,9 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
|
|
|
1687
1687
|
// Proof is minted only by opProveLoop's UNCHANGED oracle; no key ⇒ no files, no proof.
|
|
1688
1688
|
reportProgress(opts.noAuto
|
|
1689
1689
|
? "auto-prove: disabled (--no-auto)"
|
|
1690
|
-
:
|
|
1690
|
+
: opts.ai === false
|
|
1691
|
+
? "auto-prove: proving with existing tests only (--no-ai: no test generation)"
|
|
1692
|
+
: "auto-prove: driving generate → prove on the top provable targets", { current: 6, total: 8 });
|
|
1691
1693
|
let autoProveResult;
|
|
1692
1694
|
try {
|
|
1693
1695
|
const scopedFiles = autoProveChangedScope(loadGraph(workspacePaths(root).graphPath), changed, opts.baseRef);
|
|
@@ -1699,7 +1701,12 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
|
|
|
1699
1701
|
provider: providerOpts.provider,
|
|
1700
1702
|
model: providerOpts.model,
|
|
1701
1703
|
prompt_version: opts.promptVersion,
|
|
1702
|
-
changedFiles
|
|
1704
|
+
changedFiles,
|
|
1705
|
+
// --no-ai means no model calls anywhere in the run. The generation lane is gated
|
|
1706
|
+
// only by a key in the environment, so with a real provider configured it must
|
|
1707
|
+
// honor the flag explicitly. Without a key (or with the offline deterministic
|
|
1708
|
+
// provider) no model is called anyway and the released no-key path is unchanged.
|
|
1709
|
+
existingOnly: opts.ai === false && generationProviderConfigured && !deterministicGeneration
|
|
1703
1710
|
}, { ...providerDeps, proveLoop: opProveLoop });
|
|
1704
1711
|
autoProveResult = await runAutoProve(scopedFiles);
|
|
1705
1712
|
if (scopedFiles && autoProveResult.attempted === 0 && !opts.baseRef && !opts.noAuto) {
|
|
@@ -516,7 +516,10 @@ export function riskContext(risk, churnWindowDays, churnMeta, sameSinkAs) {
|
|
|
516
516
|
: "change history unavailable";
|
|
517
517
|
const tier = risk.detection_tier ?? "";
|
|
518
518
|
const evidence = tier === "candidate"
|
|
519
|
-
|
|
519
|
+
// Candidate = a name-level signal only. The analyzer did not credit an asserted
|
|
520
|
+
// call; it cannot claim the test never calls the code (stored-result asserts and
|
|
521
|
+
// pytest.raises are not yet credited for Python).
|
|
522
|
+
? "no confirmed test link (a similarly-named test exists, but no asserted call to it was recognized)"
|
|
520
523
|
: tier === "associated" ? "a test calls it but nothing proves it fails when broken" : "no test links here";
|
|
521
524
|
const sinkShort = sink ? sink.split(".").pop() ?? sink : "";
|
|
522
525
|
// Line 1 — the consequence, in plain English, from the signals only.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangepro/orangepro-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.47",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OrangePro (`opro`) — a local-first, BYOK CLI + MCP server that builds an evidence graph from a local checkout, ingests runtime coverage, and generates grounded tests. Metadata-only exports; no source upload; generated tests stay local.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -646,8 +646,33 @@ function copyRuntimeAliasTarget(absTarget, dest, isFile) {
|
|
|
646
646
|
});
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
-
|
|
649
|
+
// A failed copy (e.g. ENOSPC) must not leave a partial multi-GB sandbox behind:
|
|
650
|
+
// remove this copy's temp dir before rethrowing.
|
|
651
|
+
function copyFixtureRoot(root, label, options) {
|
|
650
652
|
const tmpRoot = mkdtempSync(path.join(tmpdir(), `opro-dynamic-proof-${label}-`));
|
|
653
|
+
try {
|
|
654
|
+
return populateFixtureRoot(tmpRoot, root, options);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Directories that are never JS test inputs but can be very large when the analyzed
|
|
662
|
+
// root is a polyglot repo: Python environments and caches, and Rust build output
|
|
663
|
+
// (`target/` next to a Cargo.toml). Copying them made each sandbox several GB.
|
|
664
|
+
const NON_JS_HEAVY_DIRS = new Set([".venv", "venv", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache", ".tox"]);
|
|
665
|
+
function isNonJsHeavyDir(source, name) {
|
|
666
|
+
if (!NON_JS_HEAVY_DIRS.has(name) && name !== "target") {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
if (!lstatSync(source).isDirectory()) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
return name !== "target" || existsSync(path.join(path.dirname(source), "Cargo.toml"));
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function populateFixtureRoot(tmpRoot, root, { linkNodeModules, workspaceRoot }) {
|
|
651
676
|
const monoRoot = path.join(tmpRoot, "mono");
|
|
652
677
|
// On any policy violation, mirror nothing → package lands alone at tmpRoot/mono with its
|
|
653
678
|
// dangling extends / unresolved alias → baseline fails to transform/resolve → honest
|
|
@@ -730,6 +755,9 @@ function copyFixtureRoot(root, label, { linkNodeModules, workspaceRoot }) {
|
|
|
730
755
|
if (source !== root && lstatSync(source).isSymbolicLink()) {
|
|
731
756
|
return false;
|
|
732
757
|
}
|
|
758
|
+
if (source !== root && isNonJsHeavyDir(source, name)) {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
733
761
|
return name !== "node_modules" && name !== ".git" && name !== ".orangepro";
|
|
734
762
|
}
|
|
735
763
|
});
|
|
@@ -2283,7 +2311,13 @@ function main() {
|
|
|
2283
2311
|
const mochaBin = args.mochaBin ? path.resolve(args.mochaBin) : defaultMochaBin(root, workspaceRoot);
|
|
2284
2312
|
|
|
2285
2313
|
const baselineCopy = copyFixtureRoot(root, "baseline", { linkNodeModules: args.linkNodeModules, workspaceRoot });
|
|
2286
|
-
|
|
2314
|
+
let mutantCopy;
|
|
2315
|
+
try {
|
|
2316
|
+
mutantCopy = copyFixtureRoot(root, "mutant", { linkNodeModules: args.linkNodeModules, workspaceRoot });
|
|
2317
|
+
} catch (error) {
|
|
2318
|
+
rmSync(baselineCopy.tmpRoot, { recursive: true, force: true });
|
|
2319
|
+
throw error;
|
|
2320
|
+
}
|
|
2287
2321
|
try {
|
|
2288
2322
|
const baseline = runTest({ runner, repoRoot: baselineCopy.repoRoot, monoRoot: baselineCopy.monoRoot, testRel, vitestBin, jestBin, mochaBin, vitestConfigRel, jestConfigRel, timeoutMs, testEnv, aliases: baselineCopy.aliases });
|
|
2289
2323
|
mutateMethod(path.join(mutantCopy.repoRoot, targetRel), args.method, replacementBody);
|
|
@@ -126,16 +126,22 @@ function resolveInside(root, relOrAbs) {
|
|
|
126
126
|
function copyModuleRoot(root, label) {
|
|
127
127
|
const tmpRoot = mkdtempSync(path.join(tmpdir(), `opro-go-proof-${label}-`));
|
|
128
128
|
const repoRoot = path.join(tmpRoot, "module");
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
try {
|
|
130
|
+
cpSync(root, repoRoot, {
|
|
131
|
+
recursive: true,
|
|
132
|
+
filter(source) {
|
|
133
|
+
const name = path.basename(source);
|
|
134
|
+
if (source !== root && lstatSync(source).isSymbolicLink()) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return name !== "node_modules" && name !== ".git" && name !== ".orangepro";
|
|
135
138
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
// A failed copy (e.g. ENOSPC) must not leave a partial sandbox behind.
|
|
142
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
139
145
|
return { tmpRoot, repoRoot };
|
|
140
146
|
}
|
|
141
147
|
|
|
@@ -558,7 +564,13 @@ function main() {
|
|
|
558
564
|
const pkgPath = pkgDirRel === "." ? "./" : `./${pkgDirRel.split(path.sep).join("/")}`;
|
|
559
565
|
|
|
560
566
|
const baselineCopy = copyModuleRoot(root, "baseline");
|
|
561
|
-
|
|
567
|
+
let mutantCopy;
|
|
568
|
+
try {
|
|
569
|
+
mutantCopy = copyModuleRoot(root, "mutant");
|
|
570
|
+
} catch (error) {
|
|
571
|
+
rmSync(baselineCopy.tmpRoot, { recursive: true, force: true });
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
562
574
|
// The mutant sandbox's package directory: Go prints failure-frame files as
|
|
563
575
|
// basenames relative to it, so FIX 2 resolves the test source there.
|
|
564
576
|
const pkgDirAbs = path.join(mutantCopy.repoRoot, path.dirname(targetRel));
|
|
@@ -106,16 +106,22 @@ function resolveInside(root, relOrAbs) {
|
|
|
106
106
|
function copyModuleRoot(root, label) {
|
|
107
107
|
const tmpRoot = mkdtempSync(path.join(tmpdir(), `opro-java-proof-${label}-`));
|
|
108
108
|
const repoRoot = path.join(tmpRoot, "module");
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
try {
|
|
110
|
+
cpSync(root, repoRoot, {
|
|
111
|
+
recursive: true,
|
|
112
|
+
filter(source) {
|
|
113
|
+
const name = path.basename(source);
|
|
114
|
+
if (source !== root && lstatSync(source).isSymbolicLink()) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return name !== "node_modules" && name !== ".git" && name !== ".orangepro" && name !== "target";
|
|
115
118
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// A failed copy (e.g. ENOSPC) must not leave a partial sandbox behind.
|
|
122
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
119
125
|
return { tmpRoot, repoRoot };
|
|
120
126
|
}
|
|
121
127
|
|
|
@@ -506,7 +512,13 @@ function main() {
|
|
|
506
512
|
const timeoutMs = parseTimeoutMs(args.timeoutMs);
|
|
507
513
|
|
|
508
514
|
const baselineCopy = copyModuleRoot(root, "baseline");
|
|
509
|
-
|
|
515
|
+
let mutantCopy;
|
|
516
|
+
try {
|
|
517
|
+
mutantCopy = copyModuleRoot(root, "mutant");
|
|
518
|
+
} catch (error) {
|
|
519
|
+
rmSync(baselineCopy.tmpRoot, { recursive: true, force: true });
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
510
522
|
try {
|
|
511
523
|
const baseline = runSurefire({
|
|
512
524
|
repoRoot: baselineCopy.repoRoot,
|