From 15d663fc3f47cd75f4fc924db7c653d98765e688 Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Thu, 22 Oct 2026 07:17:35 -0400 Subject: [PATCH] Test runner uses the repo's pinned Bun, and refuses an older one instead of silently skipping tests (#31981) * chore(test): run-vitest launches the pinned Bun, and refuses a Bun older than config/.bun-version An older Bun silently runs no tests in files that reach newer node builtins, so a local comparison could skip them on both sides. Claude-Session: W5b * chore(test): an older Bun ends run-vitest by exit code, so its message is not cut off Claude-Session: W5b --- config/scripts/pinned-bun-executable.mjs | 60 +++++++++++++++---- config/scripts/pinned-bun-executable.test.mjs | 30 ++++++++++ config/scripts/run-vitest.mjs | 53 ++++++++++------ 3 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 config/scripts/pinned-bun-executable.test.mjs diff --git a/config/scripts/pinned-bun-executable.mjs b/config/scripts/pinned-bun-executable.mjs index 58d73364629..f5b1463a157 100644 --- a/config/scripts/pinned-bun-executable.mjs +++ b/config/scripts/pinned-bun-executable.mjs @@ -6,6 +6,23 @@ export function readPinnedBunVersion(root) { return readFileSync(path.join(root, 'config', '.bun-version'), 'utf8').trim() } +/** `bun --version` of `program`, or null when it does not run. */ +export function probeBunVersion(program, root, runProcessSync) { + try { + const probe = runProcessSync({ + program, + args: ['--version'], + cwd: root, + env: process.env, + timeoutMs: 2000, + maxOutputBytes: 4096 + }) + return probe.code === 0 && !probe.signal && !probe.timedOut ? probe.stdout.trim() : null + } catch { + return null + } +} + /** * Absolute path of the `bun` devDependency when it runs and is exactly the version in * config/.bun-version, otherwise null. Its binary is only real once the postinstall ran. @@ -15,21 +32,40 @@ export function resolvePinnedBunExecutable(root, runProcessSync) { const manifestPath = createRequire(path.join(root, 'package.json')).resolve('bun/package.json') const { bin } = JSON.parse(readFileSync(manifestPath, 'utf8')) const executable = path.join(path.dirname(manifestPath), bin.bun) - const probe = runProcessSync({ - program: executable, - args: ['--version'], - cwd: root, - env: process.env, - timeoutMs: 2000, - maxOutputBytes: 4096 - }) - return probe.code === 0 && - !probe.signal && - !probe.timedOut && - probe.stdout.trim() === readPinnedBunVersion(root) + return probeBunVersion(executable, root, runProcessSync) === readPinnedBunVersion(root) ? executable : null } catch { return null } } + +const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/ + +// Why not src/shared/app-version.ts: importing .ts from here warns on every test run and CLI build. +function compareVersionCores(left, right) { + const [a, b] = [VERSION.exec(left), VERSION.exec(right)] + return Number(a[1]) - Number(b[1]) || Number(a[2]) - Number(b[2]) || Number(a[3]) - Number(b[3]) +} + +/** + * Whether a Bun of version `found` may run the tests pinned to `pinned`. Only an older Bun is + * refused (it silently runs no tests in files that reach newer builtins); a newer one passes. + */ +export function checkBunVersion(found, pinned) { + if (!found || !VERSION.test(found) || !VERSION.test(pinned)) { + return { + ok: true, + message: `Could not read the Bun version (found "${found ?? ''}", pinned ${pinned}); continuing.` + } + } + if (compareVersionCores(found, pinned) >= 0) { + return { ok: true } + } + return { + ok: false, + message: + `Bun ${found} is older than the pinned ${pinned} (config/.bun-version). ` + + `Run \`pnpm install\` to install the pinned Bun, or \`bun upgrade\`.` + } +} diff --git a/config/scripts/pinned-bun-executable.test.mjs b/config/scripts/pinned-bun-executable.test.mjs new file mode 100644 index 00000000000..398ac906348 --- /dev/null +++ b/config/scripts/pinned-bun-executable.test.mjs @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest' +import { checkBunVersion } from './pinned-bun-executable.mjs' + +describe('checkBunVersion', () => { + it('refuses an older Bun and says how to get the pinned one', () => { + const result = checkBunVersion('1.3.14', '1.4.3') + expect(result.ok).toBe(false) + expect(result.message).toContain('Bun 1.3.14 is older than the pinned 1.4.3') + expect(result.message).toContain('pnpm install') + }) + + it('compares each part as a number', () => { + expect(checkBunVersion('1.9.0', '1.10.0').ok).toBe(false) + expect(checkBunVersion('1.10.0', '1.9.0')).toEqual({ ok: true }) + }) + + it('passes an equal or newer Bun', () => { + expect(checkBunVersion('1.4.3', '1.4.3')).toEqual({ ok: true }) + expect(checkBunVersion('1.4.4', '1.4.3')).toEqual({ ok: true }) + expect(checkBunVersion('2.0.0', '1.4.3')).toEqual({ ok: true }) + }) + + it('warns and continues when a version cannot be read', () => { + for (const found of [null, '', 'bun: command not found', '1.4']) { + const result = checkBunVersion(found, '1.4.3') + expect(result.ok).toBe(true) + expect(result.message).toContain('Could not read the Bun version') + } + }) +}) diff --git a/config/scripts/run-vitest.mjs b/config/scripts/run-vitest.mjs index 082ced138be..0cca629306d 100644 --- a/config/scripts/run-vitest.mjs +++ b/config/scripts/run-vitest.mjs @@ -1,24 +1,41 @@ import { resolve } from 'node:path' import { runProcessSync } from '@orca/process-host' +import { + checkBunVersion, + probeBunVersion, + readPinnedBunVersion, + resolvePinnedBunExecutable +} from './pinned-bun-executable.mjs' const root = resolve(import.meta.dirname, '../..') -try { - const result = runProcessSync({ - program: 'bun', - args: ['--bun', resolve(root, 'node_modules/vitest/vitest.mjs'), ...process.argv.slice(2)], - cwd: root, - env: { - ...process.env, - ORCA_BACKGROUND_LAUNCH: '1', - ORCA_TEST_NODE_EXECUTABLE: process.execPath, - ORCA_TEST_NODE_VERSION: process.versions.node - }, - stdio: 'inherit', - timeoutMs: null - }) +const pinnedBun = resolvePinnedBunExecutable(root, runProcessSync) +const bunCheck = pinnedBun + ? { ok: true } + : checkBunVersion(probeBunVersion('bun', root, runProcessSync), readPinnedBunVersion(root)) +if (bunCheck.message) { + console.error(bunCheck.message) +} +if (!bunCheck.ok) { + process.exitCode = 1 +} else { + try { + const result = runProcessSync({ + program: pinnedBun ?? 'bun', + args: ['--bun', resolve(root, 'node_modules/vitest/vitest.mjs'), ...process.argv.slice(2)], + cwd: root, + env: { + ...process.env, + ORCA_BACKGROUND_LAUNCH: '1', + ORCA_TEST_NODE_EXECUTABLE: process.execPath, + ORCA_TEST_NODE_VERSION: process.versions.node + }, + stdio: 'inherit', + timeoutMs: null + }) - process.exitCode = result.code ?? 1 -} catch (error) { - console.error('Could not start Vitest. Install the Bun version in config/.bun-version.', error) - process.exitCode = 1 + process.exitCode = result.code ?? 1 + } catch (error) { + console.error('Could not start Vitest. Install the Bun version in config/.bun-version.', error) + process.exitCode = 1 + } }