Candidate on 56639c99b: waiting gains launchable, and a message to a role
outside launch.instances gets a notice saying the launch is refused and a
talk line saying launch.by can't launch it. Mutants 8/8 killed; gate green
(bus, cli, business, webui and every scripts/test-*.sh).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
53 lines
3.3 KiB
Python
53 lines
3.3 KiB
Python
import subprocess, sys, os, signal
|
|
# Row 152 (#1646): one mutant per new check. Each applies to its source in the worktree, runs the
|
|
# owning test file, and the source is restored after every mutant.
|
|
W=os.path.expanduser('~/dewey-scratch/r152/wt')
|
|
OUT=os.path.expanduser('~/dewey-scratch/r152/mut')
|
|
os.makedirs(OUT, exist_ok=True)
|
|
BROKER='packages/bus/src/broker.mjs'
|
|
CLI='packages/cli/src/cli.mjs'
|
|
BUS=('packages/bus', 'tests/launch-notice.test.mjs')
|
|
VERBS=('packages/cli', 'tests/verbs.test.mjs')
|
|
LAUNCHABLE='const launchable = launch ? launch.instances.includes(to) : null;'
|
|
TOLD="const told = by !== null && to !== by && sender !== by && sender !== 'tester';"
|
|
LINE=' if (w.launchable === false) return `${w.role} isn\'t running and isn\'t in the launch block, so ${w.launcher} can\'t launch it\\n`;\n'
|
|
M={
|
|
# Every target counts as launchable.
|
|
'B1-always-launchable': (BROKER, *BUS, LAUNCHABLE, 'const launchable = launch ? true : null;'),
|
|
# No launch block reads as false instead of null.
|
|
'B2-no-block-false': (BROKER, *BUS, LAUNCHABLE, 'const launchable = launch ? launch.instances.includes(to) : false;'),
|
|
# launch.by itself counts as launchable.
|
|
'B3-launcher-launchable': (BROKER, *BUS, LAUNCHABLE, 'const launchable = launch ? launch.instances.includes(to) || to === launch.by : null;'),
|
|
# launch.by isn't told when the target isn't launchable (the call BUILD.md argues against).
|
|
'B4-not-told-unlaunchable': (BROKER, *BUS, TOLD, TOLD[:-1] + ' && launchable !== false;'),
|
|
# The notice keeps row 146's launch text for every target.
|
|
'B5-launch-text-always': (BROKER, *BUS, ' launchable\n ? `', ' true\n ? `'),
|
|
# The field is dropped from the result.
|
|
'B6-field-dropped': (BROKER, *BUS, 'this.#recipient(s.business, by, null) !== null, launchable };', 'this.#recipient(s.business, by, null) !== null };'),
|
|
# talk drops the launch-block line and prints "has been told".
|
|
'C1-no-launch-block-line': (CLI, *VERBS, LINE, ''),
|
|
# talk adds launch.by's restart line after the launch-block line.
|
|
'C2-restart-after-line': (CLI, *VERBS, LINE, LINE.replace('can\'t launch it\\n`;', 'can\'t launch it\\n` + (w.launcherRunning ? "" : restart(w.launcher, " either"));')),
|
|
}
|
|
env=dict(os.environ, TMPDIR=os.path.expanduser('~/dewey-scratch/tmp'))
|
|
origs={p: open(os.path.join(W,p)).read() for p in (BROKER, CLI)}
|
|
try:
|
|
for name,(path,pkg,test,a,b) in M.items():
|
|
orig=origs[path]
|
|
assert orig.count(a)==1, name
|
|
assert a!=b, name
|
|
open(os.path.join(W,path),'w').write(orig.replace(a,b))
|
|
p=subprocess.Popen(['node','--test','--test-reporter=spec',test],cwd=os.path.join(W,pkg),env=env,
|
|
stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,start_new_session=True)
|
|
try:
|
|
out,_=p.communicate(timeout=300)
|
|
except subprocess.TimeoutExpired:
|
|
os.killpg(p.pid, signal.SIGKILL); out,_=p.communicate(); out+='\nTIMEOUT\n'
|
|
fails=sorted({l.strip()[2:].split(' (')[0] for l in out.splitlines() if l.lstrip().startswith('✖') and 'failing tests' not in l})
|
|
print(name, 'KILLED' if p.returncode!=0 else 'SURVIVED', 'rc=%d'%p.returncode, '| '+' | '.join(fails))
|
|
open(os.path.join(OUT,f'{name}.log'),'w').write(out)
|
|
open(os.path.join(W,path),'w').write(orig)
|
|
finally:
|
|
for path,orig in origs.items(): open(os.path.join(W,path),'w').write(orig)
|
|
print('restored', all(open(os.path.join(W,p)).read()==o for p,o in origs.items()))
|