Candidate against 817ddfea6: git identity in the sandbox environment,
commit field in checkout results, one prompt line. Gate green, 19 of 19
mutants killed. The candidate itself is not committed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
104 lines
5.8 KiB
Python
104 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
# Row 104 (#1598) mutants. Each takes out or bends one new check; the named
|
|
# test files must fail on it. Each file is restored from its .orig after the
|
|
# run, and the candidate's diff must hash the same at the end as at the start.
|
|
import hashlib, os, signal, subprocess, sys, time
|
|
|
|
WT = os.path.expanduser("~/darkwing-scratch/r104/wt")
|
|
OUT = os.path.expanduser("~/darkwing-scratch/r104/mutants")
|
|
ENV = dict(os.environ, TMPDIR=os.path.expanduser("~/darkwing-scratch/r104/tmp"))
|
|
os.makedirs(OUT, exist_ok=True)
|
|
os.makedirs(ENV["TMPDIR"], exist_ok=True)
|
|
|
|
SB, CO, BU, SE, LA = (
|
|
"packages/seat/src/sandbox.mjs", "packages/seat/src/checkout.mjs", "packages/harness/src/bundle.mjs",
|
|
"packages/seat/src/session.mjs", "packages/cli/src/launcher.mjs")
|
|
T_SB, T_CO, T_BU, T_SE, T_LA = (
|
|
"packages/seat/tests/sandbox.test.mjs", "packages/seat/tests/checkout.test.mjs", "packages/harness/tests/bundle.test.mjs",
|
|
"packages/seat/tests/session.test.mjs", "packages/cli/tests/launcher.test.mjs")
|
|
|
|
M = [
|
|
("m01 sandboxArgv drops the identity setenv", SB,
|
|
'"/tmp", ...identity, "--chdir"', '"/tmp", "--chdir"', [T_SB, T_LA]),
|
|
("m02 gitIdentity checks nothing", SB,
|
|
'if (typeof id !== "string" || !ID_PATTERN.test(id)) throw', 'if (false) throw', [T_SB]),
|
|
("m03 gitIdentity checks the type only", SB,
|
|
'typeof id !== "string" || !ID_PATTERN.test(id)', 'typeof id !== "string"', [T_SB]),
|
|
("m04 the email drops the business", SB,
|
|
'`${role}@${business}.mosaic.invalid`', '`${role}@mosaic.invalid`', [T_SB, T_LA]),
|
|
("m05 clone() returns no commit", CO,
|
|
'return { outcome: "cloned", branch: at.branch, commit };', 'return { outcome: "cloned", branch: at.branch };', [T_CO]),
|
|
("m06 clone() skips the HEAD check", CO,
|
|
'if (!head.ok) throw new CheckoutError(project, `the new clone', 'if (false) throw new CheckoutError(project, `the new clone', [T_CO]),
|
|
("m07 update() returns no commit", CO,
|
|
'branch: at.branch, commit: read.ok ? after : null }', 'branch: at.branch }', [T_CO]),
|
|
("m08 update() has no null fallback", CO,
|
|
'commit: read.ok ? after : null }', 'commit: after }', [T_CO]),
|
|
("m09 a kept clone gets a commit field", CO,
|
|
'if (problem) return { outcome: "kept", reason: problem };', 'if (problem) return { outcome: "kept", reason: problem, commit: null };', [T_CO]),
|
|
("m10 a kept clone gets a git run", CO,
|
|
'if (problem) return { outcome: "kept", reason: problem };',
|
|
'if (problem) { await g([...inClone(dest), "rev-parse", "HEAD"]); return { outcome: "kept", reason: problem }; }', [T_CO]),
|
|
("m11 the prompt drops the commit", BU,
|
|
'(c.commit ? `commit ${c.commit}` : ', '(c.commit ? "a commit" : ', [T_BU, T_LA]),
|
|
("m12 the prompt has no unreadable-commit text", BU,
|
|
'(c.commit ? `commit ${c.commit}` : "a commit the host couldn\'t read")', '`commit ${c.commit}`', [T_BU]),
|
|
("m13 the prompt drops the branch", BU,
|
|
'on branch ${c.branch} at ${at(c)}', 'at ${at(c)}', [T_BU, T_LA]),
|
|
("m14 the prompt drops the config warning", BU,
|
|
"Don't set git config in a clone. Any key that \\`git clone\\` didn't write, user.name and user.email included, makes every later launch keep that clone as it is. ", "", [T_BU]),
|
|
("m15 the prompt drops the launch-only refresh", BU,
|
|
"The host fetches from its origin and fast-forwards a clean clone only at a launch, never during a session; the origin is outside this sandbox. ", "", [T_BU]),
|
|
("m16 spawnSession passes no identity", SE,
|
|
'{ business, role, bwrap }', '{ bwrap }', [T_SE, T_LA]),
|
|
("m17 spawnSession fixes the role", SE,
|
|
'{ business, role, bwrap }', '{ business, role: "coder", bwrap }', [T_LA]),
|
|
("m18 the launcher passes the definition role", LA,
|
|
'business: b.id, role: instance, bwrap', 'business: b.id, role: "pm", bwrap', [T_LA]),
|
|
("m19 the launcher passes another business", LA,
|
|
'business: b.id, role: instance, bwrap', 'business: "x", role: instance, bwrap', [T_LA]),
|
|
]
|
|
|
|
def diffhash():
|
|
d = subprocess.run(["git", "-C", WT, "diff", "--", ".", ":!node_modules"], capture_output=True).stdout
|
|
return hashlib.sha256(d).hexdigest()
|
|
|
|
def run(test, log):
|
|
pkg = os.path.join(WT, *test.split("/")[:2])
|
|
with open(log, "ab") as f:
|
|
f.write(f"== {test}\n".encode()); f.flush()
|
|
p = subprocess.Popen(["node", "--test", os.path.relpath(os.path.join(WT, test), pkg)], cwd=pkg, env=ENV,
|
|
stdout=f, stderr=subprocess.STDOUT, start_new_session=True)
|
|
try:
|
|
return p.wait(timeout=600)
|
|
except subprocess.TimeoutExpired:
|
|
os.killpg(p.pid, signal.SIGKILL); p.wait(); return "timeout"
|
|
|
|
for _, path, *_ in M:
|
|
if os.path.exists(os.path.join(WT, path + ".orig")):
|
|
sys.exit(f"STOP {path}.orig exists")
|
|
start = diffhash()
|
|
print(f"start diff {start[:16]} {time.strftime('%H:%M:%S', time.gmtime())}", flush=True)
|
|
bad = []
|
|
only = set(sys.argv[1:])
|
|
for name, path, old, new, tests in M:
|
|
if only and name.split()[0] not in only: continue
|
|
full = os.path.join(WT, path)
|
|
src = open(full).read()
|
|
if src.count(old) != 1:
|
|
print(f"{name}: ANCHOR {src.count(old)}", flush=True); bad.append(name); continue
|
|
os.rename(full, full + ".orig")
|
|
try:
|
|
open(full, "w").write(src.replace(old, new))
|
|
log = os.path.join(OUT, name.split()[0] + ".log")
|
|
open(log, "w").close()
|
|
rcs = [run(t, log) for t in tests]
|
|
finally:
|
|
os.replace(full + ".orig", full)
|
|
killed = all(rc not in (0, "timeout") for rc in rcs)
|
|
print(f"{name}: {'killed' if killed else 'SURVIVED'} {list(zip([t.split('/')[-1] for t in tests], rcs))}", flush=True)
|
|
if not killed: bad.append(name)
|
|
end = diffhash()
|
|
print(f"end diff {end[:16]} {'same' if end == start else 'CHANGED'} {time.strftime('%H:%M:%S', time.gmtime())}", flush=True)
|
|
print("MUTANTS_OK" if not bad and end == start else f"MUTANTS_BAD {bad}")
|