test.ts
Raw
1import { readdirSync } from 'fs';
2import { spawn } from 'child_process';
3import path from 'path';
4
5const testsDir = path.resolve('tests');
6const files = readdirSync(testsDir)
7 .filter(f => f.endsWith('.test.ts'))
8 .sort();
9
10const STALL_TIMEOUT = 20_000; // kill if no output for 20s
11const MAX_RETRIES = 2;
12// retry logic needed because tests get randomly get stuck on startup with bun
13// strace shows bun completely spinning in futex and not doing anything else
14function runTest(filePath: string): Promise<boolean> {
15 return new Promise((resolve) => {
16 const child = spawn('bun', ['test', '--bail=1', '--timeout', '30000', filePath], {
17 stdio: ['ignore', 'pipe', 'pipe'],
18 });
19
20 let timer = setTimeout(onStall, STALL_TIMEOUT);
21
22 function onStall() {
23 console.error(`\n[test-runner] stall detected, killing ${path.basename(filePath)} (no output for ${STALL_TIMEOUT / 1000}s)`);
24 child.kill('SIGKILL');
25 }
26
27 function resetTimer() {
28 clearTimeout(timer);
29 timer = setTimeout(onStall, STALL_TIMEOUT);
30 }
31
32 child.stdout!.on('data', (chunk: Buffer) => {
33 process.stdout.write(chunk);
34 resetTimer();
35 });
36 child.stderr!.on('data', (chunk: Buffer) => {
37 process.stderr.write(chunk);
38 resetTimer();
39 });
40
41 child.on('close', (code) => {
42 clearTimeout(timer);
43 resolve(code === 0);
44 });
45 });
46}
47
48let passed = 0;
49let failed = 0;
50
51for (const file of files) {
52 const filePath = path.join(testsDir, file);
53 let ok = false;
54
55 for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
56 if (attempt > 1) {
57 console.log(`[test-runner] retrying ${file} (attempt ${attempt}/${MAX_RETRIES})`);
58 }
59 ok = await runTest(filePath);
60 if (ok) break;
61 }
62
63 if (ok) passed++;
64 else failed++;
65}
66
67console.log(`\n${passed + failed} test files: ${passed} passed${failed ? `, ${failed} failed` : ''}`);
68if (failed > 0) process.exit(1);
69