test: the installer's own-temp-file cleanup is exercised, and the ratchet coverage test names setup and scripts

An mv shim first on PATH refuses only the launch-file target, so the temp
file exists when the move fails: the run removes that file alone (a sibling
from a concurrent run survives), says so, prints no GSTACK_LAUNCH_FILE line
and keeps the earlier file, exit code and trailer. The ratchet coverage test
asserts setup, scripts/build.sh and scripts/sandbox-doctor.sh are scanned.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-26 04:44:19 +00:00
1 parent 48cff6682f
commit 93b793c6ef
2 files changed
+29 -4

No files matched your search

+25
View File
@@ -477,6 +477,31 @@ describe('gstack-capy-install: launch PATH, Bun reuse, CI pins, setup skip, verd
fs.chmodSync(dir, 0o755);
}
});
// protects: when the launch file's temp file IS created and the final mv fails, write_launch_file
// removes its own temp file and nothing else (a sibling temp file from a concurrent run survives),
// the run says so, prints no GSTACK_LAUNCH_FILE line and keeps the earlier launch file, exit code
// and trailer. fails_when: the `|| { rm -f "$tmp"; return 1; }` cleanup goes (the temp file stays),
// or a glob cleanup returns (the sibling disappears). why_existing_misses: the unwritable-directory
// case above fails at mktemp, before any temp file exists. seam: an mv shim first on PATH that
// refuses only the launch-file target.
test('a launch-file move that fails after the temp file exists removes only that temp file', () => {
const f = fixture();
const first = run(f, ['--for', 'autoplan']);
expect(first.status, first.stdout + first.stderr).toBe(0);
const launchFile = first.stdout.match(/^GSTACK_LAUNCH_FILE: (.+)$/m)?.[1] ?? '';
const before = fs.readFileSync(launchFile, 'utf8');
const sibling = `${launchFile}.tmp.OTHER1`;
fs.writeFileSync(sibling, '');
script(path.join(f.stub, 'mv'), [`for a in "$@"; do [ "$a" = "${launchFile}" ] && exit 1; done`, 'exec /bin/mv "$@"']);
const c = run(f, ['--check', '--for', 'autoplan']);
expect(c.status, c.stdout + c.stderr).toBe(0);
expect(c.stdout).toContain(`gstack-capy-install: could not write the launch file ${launchFile}`);
expect(c.stdout).not.toMatch(/^GSTACK_LAUNCH_FILE:/m);
expect(lastLine(c.stdout)).toBe('gstack: ok 1.2.3 for=autoplan');
expect(fs.readdirSync(path.dirname(launchFile)).filter(n => n.includes('.tmp.'))).toEqual([path.basename(sibling)]);
expect(fs.readFileSync(launchFile, 'utf8')).toBe(before);
});
});
// Plan A1/A3/A5/A6/A-f: install and --check resolve through one contract,
+4 -4
View File
@@ -111,13 +111,13 @@ function markdownTargets(): string[] {
/^capy\/skill\/gstack\/[^/]+\.md$/.test(rel));
}
/** Every shell helper under bin/, the setup script and the install scripts callers run first. */
/** Every shell helper under bin/, the setup script and the scripts/*.sh build and doctor scripts. */
function shellTargets(): string[] {
const candidates = [...fs.readdirSync(path.join(ROOT, 'bin')).map(name => path.join('bin', name)), 'setup',
...fs.readdirSync(path.join(ROOT, 'scripts')).filter(name => name.endsWith('.sh')).map(name => path.join('scripts', name))];
return candidates.filter(rel => {
const full = path.join(ROOT, rel);
return fs.existsSync(full) && fs.statSync(full).isFile() && isShell(rel, fs.readFileSync(full, 'utf8'));
return fs.statSync(full).isFile() && isShell(rel, fs.readFileSync(full, 'utf8'));
});
}
@@ -164,7 +164,7 @@ describe('launch-env echo ratchet', () => {
for (const want of ['ship/SKILL.md', 'ship/sections/documentation.md', 'review/SKILL.md', 'qa/SKILL.md',
'document-release/SKILL.md', 'autoplan/DRIVER.md', 'capy/skill/gstack/SKILL.md']) expect(md).toContain(want);
const sh = shellTargets();
for (const want of ['bin/gstack-capy-install', 'bin/gstack-capy-session', 'bin/gstack-skill-start']) expect(sh).toContain(want);
for (const want of ['bin/gstack-capy-install', 'bin/gstack-capy-session', 'bin/gstack-skill-start', 'setup', 'scripts/build.sh', 'scripts/sandbox-doctor.sh']) expect(sh).toContain(want);
});
test('no skill fence prints the environment', () => {
@@ -172,7 +172,7 @@ describe('launch-env echo ratchet', () => {
expect(violations.map(v => `${v.file}:${v.line} [${v.rule}] ${v.text}`)).toEqual([]);
});
test('no shell helper under bin/ prints the environment', () => {
test('no shell helper under bin/, setup or install script prints the environment', () => {
const violations = shellTargets().flatMap(file => scanText(file, fs.readFileSync(path.join(ROOT, file), 'utf8'), 'whole'));
expect(violations.map(v => `${v.file}:${v.line} [${v.rule}] ${v.text}`)).toEqual([]);
});