Apply user priority: save/recovery/privacy/custody assurance after all rules. Add quick types/lint, concise failure-preserving reporter, verbose mode, actual empty-execution guard, signal handling and focused verify name selection; compact canonical context/handoffs without mass moves. Tooling15, six negative CLI, named/empty source-bound smokes,206 doc links; same15-case278B vs2455B,2.35s vs2.36s no compute speedclaim. Native Advanced Ix/CHOAM47-card Stronghold profile: actual firstMentat/HMS/sixeffects,18native/four legalpolicies,five genuine2..6games3129actions/no rejection, human390px LAUDR2WNv14 bank2/personal1 plan and real battle. Earlier162/6513 checks before assurance amendment; finalquick25/build/guide pass. No new preservation/privacy audit or public/deployed/full-mode claim.
275 lines
8.3 KiB
TypeScript
275 lines
8.3 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
import {
|
|
cpSync,
|
|
mkdtempSync,
|
|
mkdirSync,
|
|
writeFileSync,
|
|
readFileSync,
|
|
rmSync,
|
|
symlinkSync,
|
|
statSync,
|
|
existsSync,
|
|
} from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import {
|
|
privateOutputDirectory,
|
|
runVerification,
|
|
sourceSnapshot,
|
|
} from '../tools/verification';
|
|
|
|
void test('focused wrapper cannot turn a filename into a list-only or test-skipping option', (t) => {
|
|
const { root, area } = fixture(t),
|
|
out = join(area, 'run');
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
'--import',
|
|
import.meta.resolve('tsx'),
|
|
new URL('../tools/verify.ts', import.meta.url).pathname,
|
|
'--out',
|
|
out,
|
|
'focused',
|
|
'--focus=--list',
|
|
],
|
|
{ cwd: root, encoding: 'utf8', timeout: 15000 },
|
|
);
|
|
assert.equal(result.status, 1);
|
|
assert.equal(existsSync(out), false);
|
|
});
|
|
|
|
void test('focused verification executes only matching names and cannot certify an empty selection', (t) => {
|
|
const { root, area } = fixture(t);
|
|
mkdirSync(join(root, 'tools'));
|
|
mkdirSync(join(root, 'tests'));
|
|
for (const tool of [
|
|
'verify.ts',
|
|
'verification.ts',
|
|
'test.ts',
|
|
'test-discovery.ts',
|
|
'test-reporter.ts',
|
|
])
|
|
cpSync(new URL(`../tools/${tool}`, import.meta.url), join(root, 'tools', tool));
|
|
symlinkSync(
|
|
fileURLToPath(new URL('../node_modules', import.meta.url)),
|
|
join(root, 'node_modules'),
|
|
'dir',
|
|
);
|
|
writeFileSync(
|
|
join(root, 'package.json'),
|
|
JSON.stringify({
|
|
type: 'module',
|
|
scripts: { test: 'node --import tsx tools/test.ts' },
|
|
}),
|
|
);
|
|
writeFileSync(
|
|
join(root, 'tests/rules.test.mjs'),
|
|
`import test from 'node:test';
|
|
test('chosen case', () => console.log('selected case executed'));
|
|
test('excluded case', () => { throw new Error('excluded failure'); });\n`,
|
|
);
|
|
const env = { ...process.env };
|
|
delete env.NODE_TEST_CONTEXT;
|
|
for (const [name, status] of [
|
|
['chosen case', 'passed'],
|
|
['no matching case', 'failed'],
|
|
]) {
|
|
const out = join(area, status);
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
'--import',
|
|
import.meta.resolve('tsx'),
|
|
'tools/verify.ts',
|
|
'--out',
|
|
out,
|
|
'focused',
|
|
'--focus',
|
|
'rules',
|
|
'--name',
|
|
name,
|
|
],
|
|
{ cwd: root, env, encoding: 'utf8', timeout: 15_000 },
|
|
);
|
|
assert.ifError(result.error);
|
|
assert.equal(result.status, status === 'passed' ? 0 : 1);
|
|
const report = JSON.parse(readFileSync(join(out, 'report.json'), 'utf8'));
|
|
assert.equal(report.status, status);
|
|
const log = readFileSync(join(out, 'focused.log'), 'utf8');
|
|
if (status === 'passed') assert.match(log, /selected case executed/);
|
|
else assert.doesNotMatch(log, /selected case executed/);
|
|
assert.doesNotMatch(log, /excluded failure/);
|
|
}
|
|
});
|
|
|
|
function fixture(t: test.TestContext) {
|
|
const area = mkdtempSync(join(tmpdir(), 'dune-verification-'));
|
|
t.after(() => rmSync(area, { recursive: true, force: true }));
|
|
const root = join(area, 'repo');
|
|
mkdirSync(root);
|
|
const git = (...args: string[]) =>
|
|
execFileSync('git', args, { cwd: root, stdio: 'pipe' });
|
|
git('init', '-q');
|
|
writeFileSync(join(root, '.gitignore'), 'private.txt\n');
|
|
writeFileSync(join(root, 'source.txt'), 'original');
|
|
git('add', '.');
|
|
git(
|
|
'-c',
|
|
'user.name=Verification',
|
|
'-c',
|
|
'user.email=verification@example.invalid',
|
|
'commit',
|
|
'-qm',
|
|
'fixture',
|
|
);
|
|
return { area, root };
|
|
}
|
|
|
|
void test('interrupted checks fail even when a child handles the signal with exit zero', (t) => {
|
|
const { root, area } = fixture(t);
|
|
for (const [signal, exitCode] of [
|
|
['SIGINT', 130],
|
|
['SIGTERM', 143],
|
|
] as const) {
|
|
const out = join(area, signal),
|
|
script = join(area, `${signal}.mts`);
|
|
const steps = [
|
|
{
|
|
name: 'interrupted',
|
|
command: process.execPath,
|
|
args: [
|
|
'-e',
|
|
`process.on('${signal}',()=>process.exit(0));process.kill(process.ppid,'${signal}');setInterval(()=>{},1000);`,
|
|
],
|
|
},
|
|
{ name: 'must-not-run', command: process.execPath, args: ['-e', ''] },
|
|
];
|
|
writeFileSync(
|
|
script,
|
|
`import { runVerification } from ${JSON.stringify(new URL('../tools/verification.ts', import.meta.url).href)};\nawait runVerification(${JSON.stringify(root)}, ${JSON.stringify(out)}, ${JSON.stringify(steps)});\n`,
|
|
);
|
|
execFileSync(
|
|
process.execPath,
|
|
['--import', import.meta.resolve('tsx'), script],
|
|
{ cwd: root, stdio: 'pipe', timeout: 15000 },
|
|
);
|
|
const report = JSON.parse(readFileSync(join(out, 'report.json'), 'utf8'));
|
|
assert.equal(report.status, 'failed');
|
|
assert.equal(report.steps.length, 1);
|
|
assert.equal(report.steps[0].exitCode, exitCode);
|
|
}
|
|
});
|
|
|
|
void test('fingerprints include untracked source and ignore private local material', (t) => {
|
|
const { root } = fixture(t),
|
|
before = sourceSnapshot(root);
|
|
writeFileSync(join(root, 'private.txt'), 'never include this');
|
|
assert.deepEqual(sourceSnapshot(root), before);
|
|
writeFileSync(join(root, 'new.ts'), 'new source');
|
|
assert.notEqual(sourceSnapshot(root).tree, before.tree);
|
|
rmSync(join(root, 'new.ts'));
|
|
rmSync(join(root, 'source.txt'));
|
|
assert.notEqual(sourceSnapshot(root).tree, before.tree);
|
|
});
|
|
|
|
void test('verification preserves real child failures, stops later work and stores private logs', async (t) => {
|
|
const { root, area } = fixture(t),
|
|
out = join(area, 'run');
|
|
const result = await runVerification(root, out, [
|
|
{
|
|
name: 'first',
|
|
command: process.execPath,
|
|
args: ['-e', 'console.log("first complete")'],
|
|
},
|
|
{
|
|
name: 'failure',
|
|
command: process.execPath,
|
|
args: ['-e', 'console.error("failure detail");process.exit(7)'],
|
|
},
|
|
{
|
|
name: 'must-not-run',
|
|
command: process.execPath,
|
|
args: ['-e', 'process.exit(0)'],
|
|
},
|
|
]);
|
|
assert.equal(result.status, 'failed');
|
|
assert.deepEqual(
|
|
result.steps.map((s) => s.exitCode),
|
|
[0, 7],
|
|
);
|
|
assert.match(
|
|
readFileSync(join(out, 'failure.log'), 'utf8'),
|
|
/failure detail/,
|
|
);
|
|
assert.equal(
|
|
JSON.parse(readFileSync(join(out, 'report.json'), 'utf8')).status,
|
|
'failed',
|
|
);
|
|
assert.equal(statSync(out).mode & 0o777, 0o700);
|
|
assert.equal(statSync(join(out, 'report.json')).mode & 0o777, 0o600);
|
|
await assert.rejects(
|
|
runVerification(root, out, [
|
|
{ name: 'repeat', command: process.execPath, args: ['-e', ''] },
|
|
]),
|
|
);
|
|
assert.match(
|
|
readFileSync(join(out, 'failure.log'), 'utf8'),
|
|
/failure detail/,
|
|
);
|
|
});
|
|
|
|
void test('green processes cannot certify source changed during verification', async (t) => {
|
|
const { root, area } = fixture(t);
|
|
const result = await runVerification(root, join(area, 'run'), [
|
|
{
|
|
name: 'edit',
|
|
command: process.execPath,
|
|
args: ['-e', 'require("node:fs").writeFileSync("source.txt", "changed")'],
|
|
},
|
|
]);
|
|
assert.equal(result.steps[0].exitCode, 0);
|
|
assert.equal(result.status, 'source-changed');
|
|
assert.notEqual(result.before.tree, result.after?.tree);
|
|
});
|
|
|
|
void test('unchanged successful checks pass and missing executables fail', async (t) => {
|
|
const { root, area } = fixture(t);
|
|
assert.equal(
|
|
(
|
|
await runVerification(root, join(area, 'good'), [
|
|
{ name: 'ok', command: process.execPath, args: ['-e', ''] },
|
|
])
|
|
).status,
|
|
'passed',
|
|
);
|
|
assert.equal(
|
|
(
|
|
await runVerification(root, join(area, 'bad'), [
|
|
{ name: 'missing', command: join(area, 'missing-command'), args: [] },
|
|
])
|
|
).status,
|
|
'failed',
|
|
);
|
|
});
|
|
|
|
void test('output refuses checkout paths, symlink redirects and existing results', (t) => {
|
|
const { root, area } = fixture(t);
|
|
assert.throws(
|
|
() => privateOutputDirectory(root, join(root, 'results')),
|
|
/outside/,
|
|
);
|
|
symlinkSync(root, join(area, 'redirect'), 'dir');
|
|
assert.throws(
|
|
() => privateOutputDirectory(root, join(area, 'redirect/results')),
|
|
/outside/,
|
|
);
|
|
const out = join(area, 'out');
|
|
privateOutputDirectory(root, out);
|
|
writeFileSync(join(out, 'sentinel'), 'keep');
|
|
assert.throws(() => privateOutputDirectory(root, out));
|
|
assert.equal(readFileSync(join(out, 'sentinel'), 'utf8'), 'keep');
|
|
});
|