An independent review of abc3083 found 15 defects, the two worst verified
in scratch repos. Each fix started with a test that failed.
- Proof of red could be "proven" by the environment: a start point
unpacked with git archive had no node_modules and no .git, so any test
failed there. The start point is now a git clone --shared checked out at
start_sha, gitignored top-level entries are linked in, and a control run
(start point + the change) must pass, or the result is not-run.
- The Test first file was never committed (the template lists it apart
from Files:) and leaked into the next step's diff. The step's scope is
now Files: + test files, staged at the checkpoint.
- New files outside Files: were invisible to every check. Every untracked
file is registered with --intent-to-add; a revert unregisters them.
- The floor guard paired renames (foo.test.js -> .bak read as clean) and
missed skipIf and node's t.skip/t.todo; prose files no longer count as
stubs.
- Phase 7.5 audited from the last step's start_sha; it now uses the run's
session_start_sha.
- The done line is written after the checkpoint and checked with --commit.
- fix_rounds never passes 5; attempts are never reset; re-control keeps
the spec/quality verdicts and earlier floor-guard rulings; the closed
stop list maps every existing stop; failed vs stopped made consistent;
the controller's effort is said, not assumed.
Tests: 1470 (1468 pass, 0 fail, 2 skipped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
4.8 KiB
JavaScript
93 lines
4.8 KiB
JavaScript
// tests/lib/done-line.test.mjs
|
|
// The done line (P9 (4)): a step's "done" is written by a script, and only when
|
|
// the test command exits 0. Exit 0 = written, 1 = the command failed (nothing
|
|
// written), 2 = could not run (nothing written, never green).
|
|
|
|
import { test } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = join(HERE, '..', '..');
|
|
const CLI = join(ROOT, 'lib', 'verification', 'done-line.mjs');
|
|
|
|
function sh(cwd, cmd, args) {
|
|
const r = spawnSync(cmd, args, { cwd, encoding: 'utf8' });
|
|
assert.equal(r.status, 0, `${cmd} ${args.join(' ')}: ${r.stderr}`);
|
|
return r.stdout.trim();
|
|
}
|
|
|
|
function repo() {
|
|
const dir = mkdtempSync(join(tmpdir(), 'done-line-'));
|
|
sh(dir, 'git', ['init', '-q']);
|
|
sh(dir, 'git', ['config', 'user.email', 't@example.invalid']);
|
|
sh(dir, 'git', ['config', 'user.name', 'test']);
|
|
mkdirSync(join(dir, 'tests'));
|
|
writeFileSync(join(dir, 'tests', 'ok.test.mjs'), "import { test } from 'node:test';\ntest('ok', () => {});\n");
|
|
writeFileSync(join(dir, 'tests', 'bad.test.mjs'), "import { test } from 'node:test';\ntest('bad', () => { throw new Error('red'); });\n");
|
|
sh(dir, 'git', ['add', 'tests']);
|
|
sh(dir, 'git', ['commit', '-q', '-m', 'tests']);
|
|
return dir;
|
|
}
|
|
|
|
const run = (args) => spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8' });
|
|
|
|
test('done line: the test command exits 0 -> one DONE line appended, exit 0', () => {
|
|
const dir = repo();
|
|
const report = join(dir, 'step-3.report.md');
|
|
try {
|
|
writeFileSync(report, '# Step 3 report\n');
|
|
const r = run(['--cwd', dir, '--step', '3', '--cmd', 'node --test tests/ok.test.mjs', '--report', report]);
|
|
assert.equal(r.status, 0, r.stdout + r.stderr);
|
|
const head = sh(dir, 'git', ['rev-parse', 'HEAD']);
|
|
const lines = readFileSync(report, 'utf8').split('\n').filter((l) => l.startsWith('DONE '));
|
|
assert.equal(lines.length, 1);
|
|
assert.match(lines[0], new RegExp(`^DONE step 3: \`node --test tests/ok\\.test\\.mjs\` exit 0 at ${head} \\d{4}-\\d{2}-\\d{2}T`));
|
|
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('done line: the test command fails -> nothing written, exit 1', () => {
|
|
const dir = repo();
|
|
const report = join(dir, 'step-3.report.md');
|
|
try {
|
|
writeFileSync(report, '# Step 3 report\n');
|
|
const r = run(['--cwd', dir, '--step', '3', '--cmd', 'node --test tests/bad.test.mjs', '--report', report]);
|
|
assert.equal(r.status, 1, r.stdout + r.stderr);
|
|
assert.equal(readFileSync(report, 'utf8'), '# Step 3 report\n');
|
|
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('done line: a command outside the test-runner allowlist is not run -> exit 2, nothing written', () => {
|
|
const dir = repo();
|
|
const report = join(dir, 'step-3.report.md');
|
|
try {
|
|
for (const cmd of ['echo done', 'node --test tests/bad.test.mjs || true', 'true']) {
|
|
const r = run(['--cwd', dir, '--step', '3', '--cmd', cmd, '--report', report]);
|
|
assert.equal(r.status, 2, `${cmd}: ${r.stdout}${r.stderr}`);
|
|
}
|
|
assert.equal(existsSync(report), false);
|
|
assert.equal(run(['--cwd', dir, '--cmd', 'node --test tests/ok.test.mjs', '--report', report]).status, 2, 'no --step');
|
|
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('done line: --check finds the line for that step only', () => {
|
|
const dir = repo();
|
|
const report = join(dir, 'step-3.report.md');
|
|
try {
|
|
assert.equal(run(['--check', '--step', '3', '--report', report]).status, 1, 'no report file = not done');
|
|
writeFileSync(report, '# Step 3 report\nDONE step 3 (typed by hand)\n');
|
|
assert.equal(run(['--check', '--step', '3', '--report', report]).status, 1, 'a hand-typed line is not the script line');
|
|
// A well-formed line typed by hand, at a sha that is not the step's commit.
|
|
writeFileSync(report, `# Step 3 report\nDONE step 3: \`node --test tests/ok.test.mjs\` exit 0 at ${'0'.repeat(40)} 2026-10-06T12:00:00.000Z\n`);
|
|
const head = sh(dir, 'git', ['rev-parse', 'HEAD']);
|
|
assert.equal(run(['--check', '--step', '3', '--report', report, '--commit', head]).status, 1, 'the line must be at the step commit');
|
|
run(['--cwd', dir, '--step', '3', '--cmd', 'node --test tests/ok.test.mjs', '--report', report]);
|
|
assert.equal(run(['--check', '--step', '3', '--report', report]).status, 0);
|
|
assert.equal(run(['--check', '--step', '3', '--report', report, '--commit', head]).status, 0);
|
|
assert.equal(run(['--check', '--step', '4', '--report', report]).status, 1);
|
|
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
});
|