also looking at this
test(cli): run the CLI in one process instead of two
#1192
1 file
+39
-27
| @@ -19,39 +19,51 @@ afterAll(async () => { | ||
| 19 | 19 | await deleteMigrationFiles(EXAMPLES_MODELS_PATH, undefined, { dialect: 'postgres' }) |
| 20 | 20 | }) |
| 21 | 21 | |
| 22 | function runCli(args: string[]) { | |
| 23 | const outputDir = mkdtempSync(join(tmpdir(), 'qb-cli-output-')) | |
| 24 | const runnerPath = join(outputDir, 'runner.ts') | |
| 25 | const resultPath = join(outputDir, 'result.json') | |
| 26 | writeFileSync(runnerPath, ` | |
| 27 | const proc = Bun.spawnSync({ | |
| 28 | cmd: ${JSON.stringify([CLI_PATH, ...args])}, | |
| 29 | cwd: ${JSON.stringify(process.cwd())}, | |
| 30 | env: { ...process.env }, | |
| 31 | stdout: 'pipe', | |
| 32 | stderr: 'pipe', | |
| 33 | }) | |
| 34 | const decoder = new TextDecoder() | |
| 35 | await Bun.write(${JSON.stringify(resultPath)}, JSON.stringify({ | |
| 36 | code: proc.exitCode, | |
| 37 | stdout: decoder.decode(proc.stdout).trim(), | |
| 38 | stderr: decoder.decode(proc.stderr).trim(), | |
| 39 | })) | |
| 40 | `) | |
| 41 | const runner = spawnSync(process.execPath, [runnerPath], { | |
| 22 | /** | |
| 23 | * Run the compiled CLI and return its exit code and captured output. | |
| 24 | * | |
| 25 | * One process. This used to be two: it wrote a runner script to a temp dir, | |
| 26 | * spawned Bun to execute it, had that process `Bun.spawnSync` the CLI and write | |
| 27 | * the result to a JSON file, then read the file back and deleted the directory. | |
| 28 | * Every one of the 64 tests here paid a whole extra Bun startup plus a | |
| 29 | * filesystem round-trip for it, under bun:test's default 5000ms budget and with | |
| 30 | * no explicit timeout anywhere in the file. | |
| 31 | * | |
| 32 | * That is what made this the suite's flakiest file. Under load the budget | |
| 33 | * killed the OUTER process mid-flight, so the failure surfaced as | |
| 34 | * `CLI test runner exited null` - a message about the harness, naming neither | |
| 35 | * the command nor the timeout - and `bin/qbx --version` was never the thing | |
| 36 | * that was slow. | |
| 37 | * | |
| 38 | * The indirection bought nothing. It arrived in 8b227d0, the commit that also | |
| 39 | * switched CLI_PATH from `bin/cli.ts` to the compiled `bin/qbx`, with a | |
| 40 | * one-line message and no comment, so it reads as scaffolding for that switch | |
| 41 | * rather than a workaround: `bin/qbx` is a native executable, and node's | |
| 42 | * `spawnSync` runs it directly from inside bun:test without complaint. | |
| 43 | * | |
| 44 | * `status` is null when a signal killed the process, which no longer happens | |
| 45 | * here but would otherwise be reported as a passing exit code by callers that | |
| 46 | * check `[0, 1]`, so it is surfaced rather than coerced. | |
| 47 | */ | |
| 48 | function runCli(args: string[]): { code: number | null, stdout: string, stderr: string } { | |
| 49 | const out = spawnSync(CLI_PATH, args, { | |
| 42 | 50 | cwd: process.cwd(), |
| 43 | 51 | encoding: 'utf8', |
| 44 | 52 | env: { ...process.env }, |
| 45 | 53 | }) |
| 46 | 54 | |
| 47 | try { | |
| 48 | if (runner.status !== 0) | |
| 49 | throw new Error(`CLI test runner exited ${runner.status}: ${runner.stderr || runner.stdout}`) | |
| 55 | // ENOENT means nobody built the CLI. Worth separating from a non-zero exit: | |
| 56 | // `bun run test` builds first, a bare `bun test` does not. | |
| 57 | if (out.error) | |
| 58 | throw new Error(`CLI did not run (${CLI_PATH}): ${out.error.message}. Run \`bun run build\`.`) | |
| 50 | 59 | |
| 51 | return JSON.parse(readFileSync(resultPath, 'utf8')) as { code: number, stdout: string, stderr: string } | |
| 52 | } | |
| 53 | finally { | |
| 54 | rmSync(outputDir, { recursive: true, force: true }) | |
| 60 | if (out.signal) | |
| 61 | throw new Error(`CLI was killed by ${out.signal}: ${args.join(' ')}`) | |
| 62 | ||
| 63 | return { | |
| 64 | code: out.status, | |
| 65 | stdout: (out.stdout ?? '').trim(), | |
| 66 | stderr: (out.stderr ?? '').trim(), | |
| 55 | 67 | } |
| 56 | 68 | } |
| 57 | 69 | |