// Coverage over the stock reference PDS and the spaces alpha together, // failing under 100% of lines or functions: what `just cov` runs. // // node harness/cov.mjs // // The tests are built once, instrumented, and each test binary is run // against both PDSes, several at a time, rather than one after another // as `cargo test` would: one harness for each PDS, shared by every binary // run against it, as `just pds` is. COV_JOBS sets how many run at once; // COV_CLEAN=1 builds every workspace crate again first. import { spawn, spawnSync } from 'node:child_process' import { mkdirSync, openSync, writeFileSync } from 'node:fs' import { availableParallelism } from 'node:os' import { dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') const [spaces, ignore] = process.argv.slice(2) const cargo = ['run', 'stable', 'cargo'] const jobs = Number(process.env.COV_JOBS) || Math.max(2, Math.floor(availableParallelism() / 2)) const started = Date.now() const took = () => `${((Date.now() - started) / 1000).toFixed(0)}s` /// Runs `rustup run stable cargo `, its output shown, failing with it. const run = (args, env = process.env, opts = {}) => { const r = spawnSync('rustup', [...cargo, ...args], { cwd: root, env, stdio: 'inherit', ...opts }) if (r.status !== 0) process.exit(r.status ?? 1) return r } // What cargo-llvm-cov sets to build and run instrumented, in its own // target directory, as `cargo llvm-cov` uses. const target = join(root, 'target/llvm-cov-target') const shown = spawnSync('rustup', [...cargo, 'llvm-cov', 'show-env', '--sh'], { cwd: root, env: { ...process.env, CARGO_TARGET_DIR: target }, encoding: 'utf8', }) const env = { ...process.env, CARGO_TARGET_DIR: target } for (const [, k, v] of shown.stdout.matchAll(/^export ([A-Z_0-9]+)=(.*)$/gm)) env[k] = v.replace(/^'(.*)'$/, '$1') // Only the last run's counts are cleared, so only what changed is built // again. A binary left from an older build adds no counts, only regions // none ran, so it can fail the gate but never pass it: COV_CLEAN=1 clears // them too, as after a miss that makes no sense. run(['llvm-cov', 'clean', process.env.COV_CLEAN ? '--workspace' : '--profraw-only']) // The harnesses start while the tests build. mkdirSync(join(root, 'target'), { recursive: true }) const harness = (name, devEnv) => { const file = join(root, `target/cov-net-${name}.json`) // What the PDS logs, which tests provoke on purpose, is kept out of the way. const log = openSync(join(root, `target/cov-net-${name}.log`), 'w') const child = spawn('node', [join(root, 'harness/pds.mjs'), '--accounts', 'seed'], { cwd: root, env: { ...process.env, KNOTLESS_DEV_ENV: devEnv }, stdio: ['pipe', 'pipe', log], }) const up = new Promise((ok, fail) => { let out = '' child.stdout.on('data', (d) => { out += d if (!out.includes('\n')) return writeFileSync(file, out) ok(file) }) child.on('exit', (code) => fail(new Error(`the ${name} harness exited with ${code}: target/cov-net-${name}.log`))) }) return { up, stop: () => child.stdin.end() } } const nets = { stock: harness('stock', ''), spaces: harness('spaces', spaces) } const stop = () => Object.values(nets).forEach((n) => n.stop()) process.on('exit', stop) // Every test binary, and the package it is in: cargo runs each there. const built = spawnSync('rustup', [...cargo, 'test', '--workspace', '--no-run', '--message-format=json'], { cwd: root, env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], maxBuffer: 1 << 28, }) if (built.status !== 0) process.exit(built.status ?? 1) const tests = built.stdout .split('\n') .filter((l) => l.startsWith('{')) .map((l) => JSON.parse(l)) .filter((m) => m.reason === 'compiler-artifact' && m.profile?.test && m.executable) .map((m) => ({ exe: m.executable, dir: dirname(m.manifest_path), name: `${m.target.name} (${m.target.kind[0]})` })) console.log(`built ${tests.length} test binaries in ${took()}`) const files = Object.fromEntries(await Promise.all(Object.entries(nets).map(async ([k, n]) => [k, await n.up]))) // The integration tests first: they take longest, so start soonest. const slow = (t) => (t.name.endsWith('(test)') ? 0 : 1) const runs = Object.keys(files) .flatMap((net) => tests.map((t) => ({ ...t, net }))) .sort((a, b) => slow(a) - slow(b)) /// One binary against one harness, its output kept to show if it fails. const one = (t) => new Promise((ok) => { const child = spawn(t.exe, [], { cwd: t.dir, env: { ...env, KNOTLESS_TEST_NET: files[t.net], KNOTLESS_DEV_ENV: t.net === 'spaces' ? spaces : '', CARGO_MANIFEST_DIR: t.dir, // As .cargo/config.toml sets them for cargo: where the answers' // TypeScript types are written. TS_RS_EXPORT_DIR: join(root, 'web/src/lib/api'), TS_RS_LARGE_INT: 'number', }, stdio: ['ignore', 'pipe', 'pipe'], }) let out = '' child.stdout.on('data', (d) => (out += d)) child.stderr.on('data', (d) => (out += d)) child.on('exit', (code) => ok({ ...t, code, out })) }) const failed = [] let next = 0 await Promise.all( Array.from({ length: jobs }, async () => { while (next < runs.length) { const r = await one(runs[next++]) const line = r.out.match(/^test result: .*$/m)?.[0] ?? `exited with ${r.code}` console.log(`${r.code === 0 ? 'ok ' : 'FAIL'} ${r.net.padEnd(6)} ${r.name}: ${line}`) if (r.code !== 0) failed.push(r) } }), ) stop() for (const r of failed) console.log(`\n---- ${r.net} ${r.name}\n${r.out}`) if (failed.length) process.exit(1) console.log(`ran ${runs.length} in ${took()}`) run(['llvm-cov', 'report', '--ignore-filename-regex', ignore, '--fail-under-lines', '100', '--fail-under-functions', '100', '--show-missing-lines']) console.log(`coverage in ${took()}`)