#!/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}")