97 lines
3.6 KiB
JavaScript
97 lines
3.6 KiB
JavaScript
// Full fresh-DB proof, including concurrent runners and failure/signal cleanup.
|
|
const assert = require('node:assert/strict');
|
|
const { spawn } = require('node:child_process');
|
|
const {
|
|
mkdtempSync,
|
|
writeFileSync,
|
|
readFileSync,
|
|
existsSync,
|
|
} = require('node:fs');
|
|
const { tmpdir } = require('node:os');
|
|
const { join, resolve } = require('node:path');
|
|
const cwd = resolve(__dirname, '..');
|
|
const evidence = mkdtempSync(join(tmpdir(), 'wealthledger-sqlite-regression-'));
|
|
const sentinel = join(evidence, 'user.db');
|
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths stay in runner-created temporary directories or explicitly selected local test builds.
|
|
writeFileSync(sentinel, 'fictional user database: must never be opened');
|
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths stay in runner-created temporary directories or explicitly selected local test builds.
|
|
const before = readFileSync(sentinel);
|
|
const databases = new Set();
|
|
async function check(name, overrides = {}, signal) {
|
|
const child = spawn(process.execPath, ['test/sqlite.cjs', '--all'], {
|
|
cwd,
|
|
env: {
|
|
...process.env,
|
|
DATABASE_URL: `file:${sentinel}`,
|
|
RUST_LOG: 'warn',
|
|
LOG_LEVEL: 'error',
|
|
...overrides,
|
|
},
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
});
|
|
let log = '';
|
|
let sent = false;
|
|
child.stdout.on('data', (chunk) => {
|
|
log += chunk;
|
|
if (signal && !sent && log.includes('SQLite ')) {
|
|
sent = true;
|
|
child.kill(signal);
|
|
}
|
|
});
|
|
child.stderr.on('data', (chunk) => {
|
|
log += chunk;
|
|
});
|
|
const code = await new Promise((resolve, reject) => {
|
|
child.once('error', reject);
|
|
child.once('close', resolve);
|
|
});
|
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths stay in runner-created temporary directories or explicitly selected local test builds.
|
|
writeFileSync(join(evidence, `${name}.log`), log);
|
|
const dirs = [...log.matchAll(/^SQLite .+: (.+)$/gm)].map(
|
|
(match) => match[1],
|
|
);
|
|
assert.ok(dirs.length > 0, 'runner must allocate a database');
|
|
for (const dir of dirs) {
|
|
assert.ok(!databases.has(dir), 'runners must never share databases');
|
|
databases.add(dir);
|
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths stay in runner-created temporary directories or explicitly selected local test builds.
|
|
assert.ok(!existsSync(dir), `runner must clean up ${dir}`);
|
|
}
|
|
assert.deepEqual(
|
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths stay in runner-created temporary directories or explicitly selected local test builds.
|
|
readFileSync(sentinel),
|
|
before,
|
|
'user database must be untouched',
|
|
);
|
|
const expected =
|
|
signal === 'SIGINT'
|
|
? 130
|
|
: signal === 'SIGTERM'
|
|
? 143
|
|
: overrides.PRISMA_SCHEMA_ENGINE_BINARY
|
|
? 1
|
|
: 0;
|
|
assert.equal(code, expected, `See ${name}.log`);
|
|
console.log(`PASS ${name}: ${dirs.length} isolated databases`);
|
|
}
|
|
(async () => {
|
|
console.log(`Evidence: ${evidence}`);
|
|
for (let i = 1; i <= 3; i++) await check(`sequential-${i}`);
|
|
const parallel = await Promise.allSettled([
|
|
check('parallel-1'),
|
|
check('parallel-2', { RUST_LOG: 'error' }),
|
|
]);
|
|
for (const result of parallel) {
|
|
if (result.status === 'rejected') throw result.reason;
|
|
}
|
|
await check('migration-failure', {
|
|
PRISMA_SCHEMA_ENGINE_BINARY: join(evidence, 'missing-engine'),
|
|
});
|
|
await check('sigint', {}, 'SIGINT');
|
|
await check('sigterm', {}, 'SIGTERM');
|
|
console.log(`PASS regression: ${evidence}`);
|
|
})().catch((error) => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|