Candidate packet for #1626: build.patch on 0bdd573ff, candidate manifest,
targeted gate (588/0 over plugins, business, bus, webui, cli) and 42/42
mutants killed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
77 lines
6.4 KiB
Python
77 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Row 131: one fault per check. Sequential process groups; whole-group timeout cleanup.
|
|
--dry checks every mutation site matches and changes source, and runs nothing."""
|
|
import json,os,pathlib,signal,subprocess,sys
|
|
args=[a for a in sys.argv[1:] if a!='--dry'];DRY='--dry' in sys.argv
|
|
ROOT=pathlib.Path(args[0] if args else os.path.expanduser('~/dewey-scratch/r131/wt'))
|
|
OUT=pathlib.Path(__file__).resolve().parent/'mutants'
|
|
LT='packages/plugins/tests/loader.test.mjs';KT='packages/plugins/tests/kinds.test.mjs';PT=[KT,LT]
|
|
ST='packages/business/tests/plugin-settings.test.mjs';CT='packages/cli/tests/stack-copy.test.mjs'
|
|
cases=[]
|
|
def add(id,path,old,new,test):cases.append(dict(id=id,path=path,old=old,new=new,test=test if isinstance(test,list) else [test]))
|
|
p='packages/plugins/src/kinds.mjs'
|
|
add('01-name-pattern',p,'if (typeof name !== "string" || !NAME.test(name)) {','if (typeof name !== "string") {',PT)
|
|
add('02-list-cardinality',p,'} else if (!Array.isArray(setting)) {','} else if (false) {',PT)
|
|
add('03-one-cardinality',p,'if (typeof setting !== "string") fail(','if (false) fail(',KT)
|
|
add('04-no-duplicates',p,'if (seen.has(name)) fail(','if (false) fail(',PT)
|
|
add('05-known-kind',p,'if (!Object.hasOwn(kinds, kind)) {','if (false) {',KT)
|
|
add('06-plugins-object',p,'if (value === null || typeof value !== "object" || Array.isArray(value)) {','if (value === null) {',KT)
|
|
add('07-registry-exports',p,'exports: Object.freeze(["mayLaunch", "mustStop"])','exports: Object.freeze(["mayLaunch"])',PT)
|
|
add('08-registry-version',p,'kind: "usage-source", interfaceVersion: 1,','kind: "usage-source", interfaceVersion: 2,',PT)
|
|
p='packages/plugins/src/loader.mjs'
|
|
add('09-missing-directory',p,'if (e.code === "ENOENT" || e.code === "ENOTDIR") refuse("missing", `${path} is missing`);','/* missing not mapped */',LT)
|
|
add('10-component-type',p,'if (directory ? !st.isDirectory() : !st.isFile()) {','if (false) {',LT)
|
|
add('11-symlink-test',p,'if (st.isSymbolicLink()) refuse("symlink", `${path} is a symlink`);','/* symlinks allowed */',LT)
|
|
add('12-plugins-dir-checked',p,' component(ROOT, true);\n','',LT)
|
|
add('13-kind-dir-checked',p,' component(kindDir, true);\n','',LT)
|
|
add('14-name-dir-checked',p,' component(nameDir, true);\n','',LT)
|
|
add('15-index-checked',p,' component(index, false);\n','',LT)
|
|
add('16-realpath-before-import',p,'if (real !== expected) refuse(','if (false) refuse(',LT)
|
|
add('17-load-failure',p,'refuse("load-failed", `${spec.kind} ${name} failed to load: ${e.message}`);','throw e;',LT)
|
|
add('18-export-present',p,'if (!Object.hasOwn(module, key)) refuse(','if (false) refuse(',LT)
|
|
add('19-export-function',p,'if (typeof module[key] !== "function") refuse(','if (false) refuse(',LT)
|
|
add('20-interface-version',p,'if (module.interfaceVersion !== spec.interfaceVersion) {','if (false) {',LT)
|
|
add('21-version-strict',p,'if (module.interfaceVersion !== spec.interfaceVersion) {','if (module.interfaceVersion != spec.interfaceVersion) {',LT)
|
|
add('22-keep-old-setting',p,'if (ok) current.set(kind, Object.freeze(next));','current.set(kind, Object.freeze(next));',LT)
|
|
add('23-first-load-nothing-on',p,'if (ok) current.set(kind, Object.freeze(next));','if (ok || current.get(kind).length === 0) current.set(kind, Object.freeze(next));',LT)
|
|
add('24-refusal-logged',p,' log(entry);\n','',LT)
|
|
add('25-log-names-module',p,'name: e.plugin ?? name, code','name, code',LT)
|
|
add('26-log-kept',p,'kept: kind === null ? loaded() : current.has(kind) ? current.get(kind).map(m => m.name) : [],','kept: [],',LT)
|
|
add('27-whole-setting-object',p,'if (plugins === null || typeof plugins !== "object" || Array.isArray(plugins)) {','if (plugins === null || typeof plugins !== "object") {',LT)
|
|
add('28-unknown-kind-logged',p,'refused(kind, null, new PluginError("unknown-kind"','void (kind, null, new PluginError("unknown-kind"',LT)
|
|
add('29-absent-key-off',p,'if (Object.hasOwn(plugins, kind)) names = namesFor(','if (!Object.hasOwn(plugins, kind)) continue; names = namesFor(',LT)
|
|
add('30-loads-serialized',p,'const run = queue.then(() => apply(plugins));','const run = apply(plugins);',LT)
|
|
add('31-log-required',p,'if (typeof log !== "function") throw','if (false) throw',LT)
|
|
add('42-modules-known-kind',p,'if (!current.has(kind)) refuse("unknown-kind"','if (false) refuse("unknown-kind"',LT)
|
|
p='packages/business/src/business.mjs'
|
|
add('32-schema-key',p,'"roles", "launch", "plugins"];','"roles", "launch"];',[ST,LT])
|
|
add('33-schema-check',p,'structuredClone(checkPlugins(document.plugins, `${file} plugins`, { fail: (_code, message) => refuse(message) }))','structuredClone(document.plugins)',ST)
|
|
add('34-schema-returned',p,'launch, plugins, definitions,','launch, definitions,',[ST,LT])
|
|
add('35-schema-copy',p,' : structuredClone(checkPlugins(',' : (checkPlugins(',ST)
|
|
p='packages/business/src/plugin-settings.mjs'
|
|
add('36-setter-strict-keys',p,'Object.keys(args).some(key => key !== "revision" && key !== "plugins")','false',ST)
|
|
add('37-setter-revision-format',p,'if (typeof args.revision !== "string" || !/^[a-f0-9]{64}$/.test(args.revision)) {','if (false) {',ST)
|
|
add('38-setter-checks-names',p,' checkPlugins(args.plugins, "plugins");\n','',ST)
|
|
add('39-setter-actor-logged',p,'{ revision: args.revision, actor }','{ revision: args.revision, actor: "host" }',ST)
|
|
add('40-setter-writes-plugins',p,' next.plugins = structuredClone(args.plugins);\n','',ST)
|
|
add('41-copy-ships-plugins','scripts/stack-copy.sh',' packages/plugins\n','',CT)
|
|
results=[]
|
|
if not DRY:OUT.mkdir(exist_ok=True)
|
|
for c in cases:
|
|
file=ROOT/c['path'];before=file.read_text();matches=before.count(c['old'])
|
|
if not matches:raise RuntimeError('missing mutation site '+c['id'])
|
|
after=before.replace(c['old'],c['new'])
|
|
if after==before:raise RuntimeError('no change '+c['id'])
|
|
if DRY:print(c['id'],'sites',matches,flush=True);continue
|
|
file.write_text(after)
|
|
try:
|
|
env={**os.environ,'DOCKER_HOST':'unix:///nonexistent.sock'}
|
|
with (OUT/(c['id']+'.log')).open('w') as log:
|
|
p=subprocess.Popen(['node','--test',*c['test']],cwd=ROOT,env=env,stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
|
|
try:rc=p.wait(timeout=300)
|
|
except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL);p.wait();rc='timeout'
|
|
verdict='KILLED' if isinstance(rc,int) and rc!=0 else 'SURVIVED' if rc==0 else 'TIMEOUT'
|
|
row={k:c[k] for k in ['id','path','test']};row.update(result=verdict,exit=rc,sites=matches);results.append(row);print(c['id'],verdict,rc,flush=True)
|
|
finally:file.write_text(before)
|
|
if not DRY:(OUT/'results.json').write_text(json.dumps(results,indent=2)+'\n')
|