Packet agents/rocko/work/queue-78/ (packet manifest 9e067f40, 32 files, candidate manifest 3016dbf1), Rocko's BUILD-LOG and SESSIONS appends. Committed by Sage for Rocko, who has no credential. Co-Authored-By: Claude Opus 5.5 <[email protected]>
96 lines
4.2 KiB
Diff
96 lines
4.2 KiB
Diff
diff --git a/agents/rocko/work/queue-78/mutants.sh b/agents/rocko/work/queue-78/mutants.sh
|
|
new file mode 100755
|
|
index 00000000..0909b5b5
|
|
--- /dev/null
|
|
+++ b/agents/rocko/work/queue-78/mutants.sh
|
|
@@ -0,0 +1,43 @@
|
|
+#!/usr/bin/env bash
|
|
+# Row 78 (#1572): rerun the mutants against a tree. Usage: mutants.sh <tree>.
|
|
+# Each mutant makes one check in scripts/bus-service.sh skip the default pin
|
|
+# (no --claude) while still applying to --claude, runs the cli host tests,
|
|
+# and restores the file from a saved copy, so the tree need not be
|
|
+# committed. Prints "killed" or "SURVIVED" per mutant; exit 1 if any
|
|
+# survives, a sed doesn't change the file, or the unmutated run fails.
|
|
+set -euo pipefail
|
|
+TREE="$(cd "${1:?usage: mutants.sh <tree>}" && pwd)"
|
|
+cd "$TREE"
|
|
+SCRIPT=scripts/bus-service.sh
|
|
+TESTS=(packages/cli/tests/host.test.mjs)
|
|
+run_tests() { node --test "${TESTS[@]}" >"$1" 2>&1; }
|
|
+
|
|
+# name | sed expression
|
|
+MUTANTS=(
|
|
+ 'default-skips-exec-check|s/^ \[ -f "\$bin" \] && \[ -x "\$bin" \] || die / [ "$CLAUDE_SET" = 0 ] || { [ -f "$bin" ] \&\& [ -x "$bin" ]; } || die /'
|
|
+ 'default-skips-char-check|s/^ case "\$bin" in \*\[!A-Za-z0-9\/._+-\]\*) die / case "$bin" in *[!A-Za-z0-9\/._+-]*) [ "$CLAUDE_SET" = 0 ] || die /'
|
|
+)
|
|
+
|
|
+OUT="${OUT:-$TREE/.mutants}"
|
|
+mkdir -p "$OUT"
|
|
+run_tests "$OUT/baseline.log" || { echo "baseline run failed: $OUT/baseline.log" >&2; exit 1; }
|
|
+echo "baseline: pass"
|
|
+status=0
|
|
+for m in "${MUTANTS[@]}"; do
|
|
+ IFS='|' read -r name expr <<<"$m"
|
|
+ cp -p "$SCRIPT" "$OUT/orig"
|
|
+ sed -i -e "$expr" "$SCRIPT"
|
|
+ if cmp -s "$OUT/orig" "$SCRIPT"; then
|
|
+ echo "$name: NOT APPLIED (sed matched nothing)"; status=1; continue
|
|
+ fi
|
|
+ diff -u "$OUT/orig" "$SCRIPT" >"$OUT/$name.diff" || true
|
|
+ if run_tests "$OUT/$name.log"; then
|
|
+ echo "$name: SURVIVED"; status=1
|
|
+ else
|
|
+ echo "$name: killed ($(grep -E '^✖' "$OUT/$name.log" | grep -v 'failing tests' | sed 's/ ([0-9.]*ms)$//' | sort -u | tr '\n' ';'))"
|
|
+ fi
|
|
+ cp -p "$OUT/orig" "$SCRIPT"
|
|
+ cmp -s "$OUT/orig" "$SCRIPT" || { echo "restore failed: $SCRIPT" >&2; exit 1; }
|
|
+ rm -f "$OUT/orig"
|
|
+done
|
|
+exit "$status"
|
|
diff --git a/packages/cli/tests/host.test.mjs b/packages/cli/tests/host.test.mjs
|
|
index 13a621ae..6f0961af 100644
|
|
--- a/packages/cli/tests/host.test.mjs
|
|
+++ b/packages/cli/tests/host.test.mjs
|
|
@@ -611,3 +611,41 @@ test("bus-service.sh refuses a directory pin and keeps a symlink default unresol
|
|
assert.equal(pinned(r.stdout), join(linkBin, "claude"));
|
|
assert.ok(!r.stdout.includes(real), "the target never reaches the unit");
|
|
});
|
|
+
|
|
+// Row 78 (#1572): the default from `command -v claude` takes the same checks
|
|
+// as --claude. PATH holds only the test's dirs, so a real claude elsewhere on
|
|
+// the host can't stand in for the one under test.
|
|
+test("bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space", (t) => {
|
|
+ const dir = tmp(t);
|
|
+ const script = join(REPO, "scripts", "bus-service.sh");
|
|
+ const tools = join(dir, "tools");
|
|
+ mkdirSync(tools);
|
|
+ symlinkSync(process.execPath, join(tools, "node"));
|
|
+ for (const tool of ["dirname", "sed", "readlink"]) {
|
|
+ const at = ["/usr/bin", "/bin"].map((d) => join(d, tool)).find((p) => existsSync(p));
|
|
+ symlinkSync(at, join(tools, tool));
|
|
+ }
|
|
+ const bash = ["/usr/bin/bash", "/bin/bash"].find((p) => existsSync(p));
|
|
+ const run = (bin) => spawnSync(bash, [script, "render"], { encoding: "utf8", env: { PATH: `${bin}:${tools}`, HOME: dir } });
|
|
+ const stand = (bin, mode) => {
|
|
+ mkdirSync(bin);
|
|
+ writeFileSync(join(bin, "claude"), '#!/bin/sh\necho "9.9.9 (Claude Code)"\n');
|
|
+ chmodSync(join(bin, "claude"), mode);
|
|
+ return bin;
|
|
+ };
|
|
+
|
|
+ // The only claude on PATH has no execute bit: `command -v` still returns
|
|
+ // it, and the -x check refuses it.
|
|
+ let r = run(stand(join(dir, "noexec-bin"), 0o644));
|
|
+ assert.equal(r.status, 1);
|
|
+ assert.match(r.stderr, /not an executable file/);
|
|
+ assert.equal(r.stdout, "");
|
|
+
|
|
+ // claude in a PATH dir with a space: systemd would split the pin there.
|
|
+ const spaced = stand(join(dir, "spaced bin"), 0o755);
|
|
+ r = run(spaced);
|
|
+ assert.equal(r.status, 1);
|
|
+ assert.match(r.stderr, /cannot carry/);
|
|
+ assert.ok(r.stderr.includes(join(spaced, "claude")), r.stderr);
|
|
+ assert.equal(r.stdout, "");
|
|
+});
|