From 0c83ea74174cbe6fbb942e4c27bab961ad2ff4a3 Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sat, 22 Aug 2026 20:43:29 -0700 Subject: [PATCH 01/10] fix(test): pass --parallel so the full suite finishes instead of reading as hung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `bun run test` spawned `bun test --isolate ./tests/`. With `--isolate` and no `--parallel`, Bun re-evaluates the module graph once per file on a single core. Past ~900 files that stops looking slow and starts looking hung. Measured on this tree (902 files): without --parallel 1 h 29 m, zero output, ~57 % CPU, 8.5 MB RSS, killed with --parallel ~110-190 s, 10x PARALLEL The failure mode is what makes this worth fixing rather than documenting: there is no progress output, one core is pinned, and RSS stays tiny, so it reads as a deadlock. A contributor's reasonable conclusion is that the suite is broken. The stale "normally runs in about 210s" warning is updated for the same reason — that number predates the file count that made the flag necessary. `resolveBunTestArgs` is exported and pinned by tests so the flag cannot be dropped again silently, including the two easy-to-regress cases: a caller supplying `--parallel=N` must not be overridden, and an option-only argv such as `--timeout=30000` must still count as a full-suite run and keep `./tests/`. Gate: 14436 pass / 2 fail; both also fail on untouched upstream/dev at this commit (baseline: 4 fail, a superset). Zero regressions. Note on scope: this is the smallest change that makes the suite runnable. Two adjacent changes are deliberately left out and will be proposed separately — narrowing the exclusive-run lock to full-suite runs (a behavior change that lets two focused runs share one sandboxed HOME), and a `test:changed` script with the contributing-guide updates that go with it. Separately and not addressed here: `tests/key-login-live-update.test.ts` fails standalone and serially on a clean tree, so every full run is red by at least one test regardless of this change. --- bunfig.toml | 1 + scripts/test.ts | 31 +++++++++++++++++++++++++++++-- tests/test-runner.test.ts | 31 ++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/bunfig.toml b/bunfig.toml index 00cbc1231e..318845b44a 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -5,6 +5,7 @@ # so a bare `bun test` — or `bun test tests/` (a substring filter that also matches # devlog/opencode-cursor/tests/) — drags them in and reports hundreds of spurious failures. # `root` pins discovery to ./tests so every invocation stays on the real suite. +# File-level `--parallel` has no bunfig key; `scripts/test.ts` passes it for `bun run test`. # The npm script already uses `bun test ./tests/`; this makes a bare `bun test` behave the same. [test] root = "tests" diff --git a/scripts/test.ts b/scripts/test.ts index 5297a17722..7e055a700f 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -59,6 +59,33 @@ export function createIsolatedTestEnvironment( }; } +function hasCliFlag(requested: string[], name: string): boolean { + return requested.some(arg => arg === name || arg.startsWith(`${name}=`)); +} + +/** True for a filter-less `bun run test`. `--timeout` / `--dots` / `--parallel=N` still count. */ +function isFullSuiteRun(requested: string[]): boolean { + return !requested.some(arg => arg !== "-" && !arg.startsWith("-")); +} + +/** + * Default `bun test` argv for this repo. + * + * `--isolate` keeps a fresh global per file, and is the substring the exclusive-run pgrep + * matches. `--parallel` is what makes the suite finishable: with isolate alone Bun re-evaluates + * the module graph once per file on a single core, so past ~900 files the run stops looking slow + * and starts looking hung — measured here at 1 h 29 m with zero output, ~57 % CPU and 8.5 MB RSS, + * against ~110-190 s for the identical suite with `--parallel`. A caller-supplied `--parallel=N` + * is left alone. + */ +export function resolveBunTestArgs(requested: string[]): string[] { + const args = ["--isolate"]; + if (!hasCliFlag(requested, "--parallel")) args.push("--parallel"); + args.push(...requested); + if (isFullSuiteRun(requested)) args.push("./tests/"); + return args; +} + /** * Other `bun test` runners already on this machine. * @@ -141,7 +168,7 @@ if (import.meta.main) { await waitForExclusiveRun(process.pid); const startedAt = Date.now(); const child = Bun.spawnSync( - [process.execPath, "test", "--isolate", ...(requestedTests.length > 0 ? requestedTests : ["./tests/"])], + [process.execPath, "test", ...resolveBunTestArgs(requestedTests)], { env: isolated.env, stdin: "inherit", @@ -152,7 +179,7 @@ if (import.meta.main) { const elapsedSeconds = Math.round((Date.now() - startedAt) / 1000); if (requestedTests.length === 0 && elapsedSeconds > 600) { console.warn( - `[test] the suite took ${elapsedSeconds}s; it normally runs in about 210s on an idle machine. ` + `[test] the suite took ${elapsedSeconds}s; with --parallel it should finish in a few minutes on an idle machine. ` + "Check for another test runner, a busy CPU, or a test that started polling something real.", ); } diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index ef48654f15..253a9afb41 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { existsSync } from "node:fs"; import { isAbsolute, join } from "node:path"; -import { createIsolatedTestEnvironment } from "../scripts/test"; +import { createIsolatedTestEnvironment, resolveBunTestArgs } from "../scripts/test"; import { decodeWindowsIdentityPowerShellOutputForTests, windowsIdentityPowerShellCommandForTests, @@ -68,3 +68,32 @@ describe("test runner isolation", () => { }, ); }); + +/** + * Without `--parallel`, `--isolate` re-evaluates the module graph once per file on a single + * core. Past ~900 files that stops reading as slow and starts reading as hung: measured at + * 1 h 29 m with zero output, ~57 % CPU and 8.5 MB RSS, against ~110-190 s for the identical + * suite with the flag. These pin the argv so the flag cannot be dropped again silently. + */ +describe("bun test argv", () => { + test("a filter-less run gets isolate, parallel and the suite path", () => { + expect(resolveBunTestArgs([])).toEqual(["--isolate", "--parallel", "./tests/"]); + }); + + test("a file filter keeps isolate and parallel but no suite path", () => { + expect(resolveBunTestArgs(["tests/foo.test.ts"])) + .toEqual(["--isolate", "--parallel", "tests/foo.test.ts"]); + }); + + test("a caller-supplied concurrency is left alone", () => { + expect(resolveBunTestArgs(["--parallel=2"])) + .toEqual(["--isolate", "--parallel=2", "./tests/"]); + expect(resolveBunTestArgs(["--parallel"])) + .toEqual(["--isolate", "--parallel", "./tests/"]); + }); + + test("option-only arguments still count as a full suite run", () => { + expect(resolveBunTestArgs(["--timeout=30000"])) + .toEqual(["--isolate", "--parallel", "--timeout=30000", "./tests/"]); + }); +}); From a1d3e15e8b4f45779ce5d29ac00e85710f7f80d9 Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sat, 22 Aug 2026 21:33:14 -0700 Subject: [PATCH 02/10] fix(test): parse the -- delimiter and pin the flag end to end Two review findings. hasCliFlag/isFullSuiteRun read the whole argv, so `test -- --parallel=2` suppressed the default --parallel even though everything after -- is passed through, and a bare - was classified as an option so `test -` was treated as a full-suite run. The tests also asserted only resolveBunTestArgs output: reverting the spawn call to a hardcoded argv left every assertion green. A spawn test now runs the wrapper against a non-matching filter and asserts bun reports PARALLEL. --- scripts/test.ts | 10 ++++++++-- tests/test-runner.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/scripts/test.ts b/scripts/test.ts index 7e055a700f..956c81303d 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -60,12 +60,18 @@ export function createIsolatedTestEnvironment( } function hasCliFlag(requested: string[], name: string): boolean { - return requested.some(arg => arg === name || arg.startsWith(`${name}=`)); + const delimiterIndex = requested.indexOf("--"); + const wrapperArgs = delimiterIndex === -1 ? requested : requested.slice(0, delimiterIndex); + return wrapperArgs.some(arg => arg === name || arg.startsWith(`${name}=`)); } /** True for a filter-less `bun run test`. `--timeout` / `--dots` / `--parallel=N` still count. */ function isFullSuiteRun(requested: string[]): boolean { - return !requested.some(arg => arg !== "-" && !arg.startsWith("-")); + const delimiterIndex = requested.indexOf("--"); + const wrapperArgs = delimiterIndex === -1 ? requested : requested.slice(0, delimiterIndex); + const passedThrough = delimiterIndex === -1 ? [] : requested.slice(delimiterIndex + 1); + return passedThrough.length === 0 + && !wrapperArgs.some(arg => arg === "-" || !arg.startsWith("-")); } /** diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index 253a9afb41..11bf5723f9 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -83,6 +83,8 @@ describe("bun test argv", () => { test("a file filter keeps isolate and parallel but no suite path", () => { expect(resolveBunTestArgs(["tests/foo.test.ts"])) .toEqual(["--isolate", "--parallel", "tests/foo.test.ts"]); + expect(resolveBunTestArgs(["-"])) + .toEqual(["--isolate", "--parallel", "-"]); }); test("a caller-supplied concurrency is left alone", () => { @@ -96,4 +98,27 @@ describe("bun test argv", () => { expect(resolveBunTestArgs(["--timeout=30000"])) .toEqual(["--isolate", "--parallel", "--timeout=30000", "./tests/"]); }); + + test("arguments after the delimiter are passed through instead of parsed as wrapper flags", () => { + expect(resolveBunTestArgs(["--", "--parallel=2"])) + .toEqual(["--isolate", "--parallel", "--", "--parallel=2"]); + }); + + test("the wrapper passes parallel execution through to bun", () => { + const result = Bun.spawnSync([ + process.execPath, + join(import.meta.dir, "../scripts/test.ts"), + "--pass-with-no-tests", + join(import.meta.dir, "__no_matching_test_file__.test.ts"), + ], { + cwd: join(import.meta.dir, ".."), + env: { ...process.env, OCX_TEST_NO_QUEUE: "1" }, + stdout: "pipe", + stderr: "pipe", + }); + + const output = new TextDecoder().decode(result.stdout) + + new TextDecoder().decode(result.stderr); + expect(output).toContain("PARALLEL"); + }); }); From 560f464715064ea269fc2eb00328cfaeecd4c643 Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sat, 22 Aug 2026 22:56:19 -0700 Subject: [PATCH 03/10] fix(test): only required-value options consume the next argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two CodeRabbit findings, plus a regression the first attempt introduced. isFullSuiteRun read a space-separated option value as a file filter, so `bun run test --timeout 30000` silently stopped being a full-suite run and dropped ./tests/. Bun 1.4.0 accepts that form. The first fix over-corrected: treating every value-taking option as consuming the next argument swallowed the filter in ["--parallel", "tests/foo.test.ts"], so a focused run became a full-suite run — worse than the original bug, and silent. --parallel, --changed, --timings and --coverage take OPTIONAL values, which Bun expects attached with =. Now only required-value options consume the next argument, and all six boundary shapes are pinned as tests. The spawn test also asserted only that the output contained PARALLEL, so it could pass after a nonzero wrapper exit; it now asserts exitCode 0 first, against a real fixture file so a successful run is meaningful. --- scripts/test.ts | 82 ++++++++++++++++++++++++++++++++++++++- tests/test-runner.test.ts | 53 ++++++++++++++++++------- 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/scripts/test.ts b/scripts/test.ts index 956c81303d..3fcaf7b594 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -65,13 +65,91 @@ function hasCliFlag(requested: string[], name: string): boolean { return wrapperArgs.some(arg => arg === name || arg.startsWith(`${name}=`)); } +// Bun 1.4.0 builds `bun test` options from its test, runtime, transpiler, and base tables. +// Only required values consume the next argument. Optional values such as `--parallel=2` +// must stay attached so a bare option cannot hide the positional filter that follows it. +const BUN_TEST_OPTIONS_REQUIRING_VALUES = new Set([ + // Test options. + "--timeout", + "--rerun-each", + "--retry", + "--seed", + "--coverage-reporter", + "--coverage-dir", + "-t", + "--test-name-pattern", + "--grep", + "--reporter", + "--reporter-outfile", + "--max-concurrency", + "--path-ignore-patterns", + "--parallel-delay", + "--shard", + // Runtime options accepted by `bun test`. + "--watch-kill-signal", + "-r", + "--preload", + "--require", + "--import", + "--cpu-prof-name", + "--cpu-prof-dir", + "--cpu-prof-interval", + "--heap-prof-name", + "--heap-prof-dir", + "--heap-prof-interval", + "--install", + "-e", + "--eval", + "-p", + "--print", + "--port", + "--origin", + "--conditions", + "--fetch-preconnect", + "--max-http-header-size", + "--dns-result-order", + "--redirect-warnings", + "--disable-warning", + "--title", + "--unhandled-rejections", + "--console-depth", + "--user-agent", + "--cron-title", + "--cron-period", + "--trace-event-categories", + "--trace-event-file-pattern", + "--stack-trace-limit", + // Transpiler and base options accepted by `bun test`. + "--main-fields", + "--extension-order", + "--tsconfig-override", + "-d", + "--define", + "--drop", + "--feature", + "-l", + "--loader", + "--jsx-factory", + "--jsx-fragment", + "--jsx-import-source", + "--jsx-runtime", + "--env-file", + "--cwd", +]); + /** True for a filter-less `bun run test`. `--timeout` / `--dots` / `--parallel=N` still count. */ function isFullSuiteRun(requested: string[]): boolean { const delimiterIndex = requested.indexOf("--"); const wrapperArgs = delimiterIndex === -1 ? requested : requested.slice(0, delimiterIndex); const passedThrough = delimiterIndex === -1 ? [] : requested.slice(delimiterIndex + 1); - return passedThrough.length === 0 - && !wrapperArgs.some(arg => arg === "-" || !arg.startsWith("-")); + if (passedThrough.length > 0) return false; + + for (let index = 0; index < wrapperArgs.length; index++) { + const arg = wrapperArgs[index]; + if (arg === "-" || !arg.startsWith("-")) return false; + if (!arg.includes("=") && BUN_TEST_OPTIONS_REQUIRING_VALUES.has(arg)) index++; + } + return true; } /** diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index 11bf5723f9..810acd33c8 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { existsSync } from "node:fs"; +import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; import { isAbsolute, join } from "node:path"; import { createIsolatedTestEnvironment, resolveBunTestArgs } from "../scripts/test"; import { @@ -92,11 +93,26 @@ describe("bun test argv", () => { .toEqual(["--isolate", "--parallel=2", "./tests/"]); expect(resolveBunTestArgs(["--parallel"])) .toEqual(["--isolate", "--parallel", "./tests/"]); + expect(resolveBunTestArgs(["--parallel", "tests/foo.test.ts"])) + .toEqual(["--isolate", "--parallel", "tests/foo.test.ts"]); + expect(resolveBunTestArgs(["--parallel=2", "tests/foo.test.ts"])) + .toEqual(["--isolate", "--parallel=2", "tests/foo.test.ts"]); }); test("option-only arguments still count as a full suite run", () => { expect(resolveBunTestArgs(["--timeout=30000"])) .toEqual(["--isolate", "--parallel", "--timeout=30000", "./tests/"]); + expect(resolveBunTestArgs(["--timeout", "30000"])) + .toEqual(["--isolate", "--parallel", "--timeout", "30000", "./tests/"]); + expect(resolveBunTestArgs(["--timeout", "30000", "tests/foo.test.ts"])) + .toEqual(["--isolate", "--parallel", "--timeout", "30000", "tests/foo.test.ts"]); + expect(resolveBunTestArgs(["-t", "serial test"])).toEqual([ + "--isolate", + "--parallel", + "-t", + "serial test", + "./tests/", + ]); }); test("arguments after the delimiter are passed through instead of parsed as wrapper flags", () => { @@ -105,20 +121,27 @@ describe("bun test argv", () => { }); test("the wrapper passes parallel execution through to bun", () => { - const result = Bun.spawnSync([ - process.execPath, - join(import.meta.dir, "../scripts/test.ts"), - "--pass-with-no-tests", - join(import.meta.dir, "__no_matching_test_file__.test.ts"), - ], { - cwd: join(import.meta.dir, ".."), - env: { ...process.env, OCX_TEST_NO_QUEUE: "1" }, - stdout: "pipe", - stderr: "pipe", - }); + const fixtureRoot = mkdtempSync(join(tmpdir(), "opencodex-test-runner-")); + const fixturePath = join(fixtureRoot, "parallel-smoke.test.ts"); + writeFileSync(fixturePath, 'import { test } from "bun:test"; test("smoke", () => {});\n'); + try { + const result = Bun.spawnSync([ + process.execPath, + join(import.meta.dir, "../scripts/test.ts"), + fixturePath, + ], { + cwd: join(import.meta.dir, ".."), + env: { ...process.env, OCX_TEST_NO_QUEUE: "1" }, + stdout: "pipe", + stderr: "pipe", + }); - const output = new TextDecoder().decode(result.stdout) - + new TextDecoder().decode(result.stderr); - expect(output).toContain("PARALLEL"); + const output = new TextDecoder().decode(result.stdout) + + new TextDecoder().decode(result.stderr); + expect(result.exitCode).toBe(0); + expect(output).toContain("PARALLEL"); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } }); }); From 8d6425a4ddf942a0b619b0bd11a2fd3f727c5e14 Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sun, 23 Aug 2026 02:03:24 -0700 Subject: [PATCH 04/10] fix(test): parse separated timings paths --- scripts/test.ts | 1 + tests/test-runner.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/scripts/test.ts b/scripts/test.ts index 3fcaf7b594..7d1d73cdbe 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -85,6 +85,7 @@ const BUN_TEST_OPTIONS_REQUIRING_VALUES = new Set([ "--path-ignore-patterns", "--parallel-delay", "--shard", + "--timings", // Runtime options accepted by `bun test`. "--watch-kill-signal", "-r", diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index 810acd33c8..320d8402c7 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -106,6 +106,14 @@ describe("bun test argv", () => { .toEqual(["--isolate", "--parallel", "--timeout", "30000", "./tests/"]); expect(resolveBunTestArgs(["--timeout", "30000", "tests/foo.test.ts"])) .toEqual(["--isolate", "--parallel", "--timeout", "30000", "tests/foo.test.ts"]); + expect(resolveBunTestArgs(["--timings", ".bun-test-timings/current.json"])) + .toEqual([ + "--isolate", + "--parallel", + "--timings", + ".bun-test-timings/current.json", + "./tests/", + ]); expect(resolveBunTestArgs(["-t", "serial test"])).toEqual([ "--isolate", "--parallel", From 3187fd4befce4131ceb484fc973a84b765cae7f4 Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sun, 23 Aug 2026 02:15:26 -0700 Subject: [PATCH 05/10] test: cover config values and fixture execution --- scripts/test.ts | 2 ++ tests/test-runner.test.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/test.ts b/scripts/test.ts index 7d1d73cdbe..41d141b7fd 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -136,6 +136,8 @@ const BUN_TEST_OPTIONS_REQUIRING_VALUES = new Set([ "--jsx-runtime", "--env-file", "--cwd", + "-c", + "--config", ]); /** True for a filter-less `bun run test`. `--timeout` / `--dots` / `--parallel=N` still count. */ diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index 320d8402c7..408d5dcf89 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -114,6 +114,10 @@ describe("bun test argv", () => { ".bun-test-timings/current.json", "./tests/", ]); + for (const configFlag of ["-c", "--config"]) { + expect(resolveBunTestArgs([configFlag, "ci.bunfig.toml"])) + .toEqual(["--isolate", "--parallel", configFlag, "ci.bunfig.toml", "./tests/"]); + } expect(resolveBunTestArgs(["-t", "serial test"])).toEqual([ "--isolate", "--parallel", @@ -131,7 +135,11 @@ describe("bun test argv", () => { test("the wrapper passes parallel execution through to bun", () => { const fixtureRoot = mkdtempSync(join(tmpdir(), "opencodex-test-runner-")); const fixturePath = join(fixtureRoot, "parallel-smoke.test.ts"); - writeFileSync(fixturePath, 'import { test } from "bun:test"; test("smoke", () => {});\n'); + const markerPath = join(fixtureRoot, "executed.marker"); + writeFileSync( + fixturePath, + `import { test } from "bun:test"; import { writeFileSync } from "node:fs"; test("smoke", () => writeFileSync(${JSON.stringify(markerPath)}, "executed"));\n`, + ); try { const result = Bun.spawnSync([ process.execPath, @@ -148,6 +156,7 @@ describe("bun test argv", () => { + new TextDecoder().decode(result.stderr); expect(result.exitCode).toBe(0); expect(output).toContain("PARALLEL"); + expect(existsSync(markerPath)).toBe(true); } finally { rmSync(fixtureRoot, { recursive: true, force: true }); } From 87862c4be9408bad7a1ebd9b1ba63c16d1019012 Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sat, 22 Aug 2026 20:56:06 -0700 Subject: [PATCH 06/10] feat(test): add test:changed and make it the local check during implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `bun run test:changed`, which runs only the tests whose import graph touches the diff, and rewrites AGENTS.md, src/AGENTS.md and the eight contributing guides so the full suite is the PR-ready gate rather than the routine local one. `--changed` gets `--pass-with-no-tests` (a graph filter selecting nothing is a valid answer, not a failure) and must not receive a default `./tests/` path, or Bun treats the graph filter as unused. MEASURED CAVEAT, unresolved and worth deciding before this lands: `--changed=dev` compares against the LOCAL `dev` branch. On this machine that branch sits 296 commits behind upstream, so a 3-file change selected 753 of 902 test files — 13053 tests, 81s. The failure is silent in both directions: a stale `dev` quietly runs most of the suite, and a diverged one can select too little while the guides now say the full suite is no longer required for shared routing, config or server edits. That combination is the risk, not the graph filter itself, which selects correctly relative to whatever ref it is given. Left as `dev` deliberately rather than guessing a remote name: `origin` is a fork here and `upstream` is the real repo, while a direct contributor's layout is the reverse. Candidate fixes (pin a remote ref, fetch first, warn when the selection is implausibly large or small) are a repo-convention call. Stacked on the --parallel fix, which introduces resolveBunTestArgs. Gate: 14439 pass / 1 fail; that failure also fails on untouched upstream/dev at this commit. Zero regressions. --- AGENTS.md | 20 ++++++++++++------- docs-site/src/content/docs/contributing.md | 7 ++++--- docs-site/src/content/docs/fr/contributing.md | 7 ++++--- docs-site/src/content/docs/ja/contributing.md | 7 ++++--- docs-site/src/content/docs/ko/contributing.md | 7 ++++--- docs-site/src/content/docs/ru/contributing.md | 7 ++++--- docs-site/src/content/docs/tr/contributing.md | 8 ++++---- .../src/content/docs/zh-cn/contributing.md | 5 +++-- .../src/content/docs/zh-tw/contributing.md | 5 +++-- package.json | 1 + scripts/test.ts | 6 ++++++ src/AGENTS.md | 3 ++- tests/test-runner.test.ts | 12 +++++++++++ 13 files changed, 64 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8b0fcf01e3..359cbf4490 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -163,17 +163,23 @@ it binds you regardless of which mechanism is within reach. ```bash bun install bun run typecheck # bun x tsc --noEmit (strict) -bun run test # full tests/ suite +bun run test:changed # tests whose import graph touches the diff against `dev` +bun run test # full tests/ suite (PR-ready / explicit ask only) bun run lint:gui # GUI eslint bun run privacy:scan # credential/privacy scan used by CI bun run build:gui # Vite GUI build ``` During implementation, use the smallest focused checks that directly cover the -changed subsystem. Do not run repository-wide `bun run typecheck` or -`bun run test` for a scoped change unless the change affects shared runtime, -routing, config, server behavior, a focused result is failed or ambiguous, or -the user explicitly asks for full validation. +changed subsystem. Prefer `bun test tests/.test.ts` for a known file, or +`bun run test:changed` when the touch set is broader than one file. Do **not** +run repository-wide `bun run typecheck`, `bun run test`, or a bare `bun test` +with no file arguments for a scoped change. Shared runtime, routing, config, or +server edits are not an exception: `bun run test:changed` already selects every +test that imports those modules. The full suite is ~850 files; launching it +without an explicit ask blocks the machine for minutes and starves other work. +Run it only when a focused result failed or is ambiguous, or the user explicitly +asks for full validation. Before creating or updating a non-trivial PR as review-ready, or before approving such a PR, run `bun run typecheck` and `bun run test`. CI runs these @@ -274,8 +280,8 @@ reviewers (Codex, CodeRabbit). assumptions about a compile step, or code paths that break `bun run typecheck` / `bun run test`. - **Tests:** behavior changes in `src/` need a focused regression test near - the existing tests for that subsystem. Shared routing, adapter, config, or - server changes need the full suite green. + the existing tests for that subsystem. During implementation, `bun run test:changed` + (or the focused file) is the local check; the full suite is the PR-ready gate. - **Docs sync:** user-facing behavior changes should update `docs-site/` (and keep translated locales from contradicting the English source). - **Privacy:** `bun run privacy:scan` must stay green; never introduce logging diff --git a/docs-site/src/content/docs/contributing.md b/docs-site/src/content/docs/contributing.md index 093aac9d26..5ccf6a9db8 100644 --- a/docs-site/src/content/docs/contributing.md +++ b/docs-site/src/content/docs/contributing.md @@ -28,7 +28,8 @@ scripts so local commands match CI: ```bash bun run typecheck # strict TypeScript check -bun run test # complete tests/ suite +bun run test:changed # tests affected by the diff against `dev` +bun run test # complete tests/ suite (PR-ready / explicit ask) bun test tests/router.test.ts # focused test file bun run build:gui # Vite GUI build + package preparation bun run privacy:scan # credential/privacy scan used by CI @@ -37,8 +38,8 @@ bun run prepare:package # refresh package launchers/assets Most tests are flat `tests/*.test.ts` Bun tests. `tests/helpers/` contains shared fixtures and `tests/e2e-style/` contains broader native-parity scenarios. Keep a focused regression near the -existing tests for the subsystem you change; run the full suite for shared routing, adapters, config, -or server behavior. +existing tests for the subsystem you change. During implementation run that file or `bun run test:changed`; +run the complete suite with `bun run test` before marking a PR review-ready. The docs site you're reading lives in `docs-site/` (Astro + Starlight): diff --git a/docs-site/src/content/docs/fr/contributing.md b/docs-site/src/content/docs/fr/contributing.md index 408191cdbf..a006d77291 100644 --- a/docs-site/src/content/docs/fr/contributing.md +++ b/docs-site/src/content/docs/fr/contributing.md @@ -28,7 +28,8 @@ distincte. Utilisez les scripts enregistrés afin que les commandes locales corr ```bash bun run typecheck # strict TypeScript check -bun run test # complete tests/ suite +bun run test:changed # tests affected by the diff against `dev` +bun run test # complete tests/ suite (PR-ready / explicit ask) bun test tests/router.test.ts # focused test file bun run build:gui # Vite GUI build + package preparation bun run privacy:scan # credential/privacy scan used by CI @@ -37,8 +38,8 @@ bun run prepare:package # refresh package launchers/assets La plupart des tests Bun sont des fichiers plats `tests/*.test.ts`. `tests/helpers/` contient les fixtures partagées et `tests/e2e-style/` des scénarios plus larges de parité native. Placez une régression ciblée près -des tests existants du sous-système modifié. Exécutez la suite complète pour le routage partagé, les adaptateurs, -la configuration ou le comportement du serveur. +des tests existants du sous-système modifié. Pendant l’implémentation, lancez ce fichier ou `bun run test:changed` ; +la suite complète (`bun run test`) est le seuil review-ready d’une PR. Le site de documentation que vous lisez se trouve dans `docs-site/` (Astro + Starlight) : diff --git a/docs-site/src/content/docs/ja/contributing.md b/docs-site/src/content/docs/ja/contributing.md index 2d74d52f71..023ad44dbb 100644 --- a/docs-site/src/content/docs/ja/contributing.md +++ b/docs-site/src/content/docs/ja/contributing.md @@ -26,7 +26,8 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 厳密な TypeScript 検査 -bun run test # tests/ の全体スイート +bun run test:changed # `dev` との diff が影響するテスト +bun run test # tests/ の全体スイート (PR review-ready / 明示時) bun test tests/router.test.ts # 特定テストファイル bun run build:gui # Vite GUI ビルド + パッケージ準備 bun run privacy:scan # CI で使う資格情報/個人情報検査 @@ -35,8 +36,8 @@ bun run prepare:package # パッケージランチャー/asset 更新 ほとんどのテストは `tests/*.test.ts` に並んで配置された Bun テストです。共有 fixture は `tests/helpers/`、範囲の広いネイティブ等価性シナリオは `tests/e2e-style/` にあります。変更した -サブシステムの既存テストの近くに集中した回帰テストを追加してください。共有ルーティング、アダプター、設定、サーバー -動作を触った場合は全体スイートも実行します。 +サブシステムの既存テストの近くに集中した回帰テストを追加してください。実装中はそのファイルか `bun run test:changed` を実行し、 +全体スイートは PR を review-ready にする前、または明示されたときだけ実行します。 いま読んでいるドキュメントサイトは `docs-site/` にあります(Astro + Starlight)。 diff --git a/docs-site/src/content/docs/ko/contributing.md b/docs-site/src/content/docs/ko/contributing.md index 285445d8c0..2006ff3826 100644 --- a/docs-site/src/content/docs/ko/contributing.md +++ b/docs-site/src/content/docs/ko/contributing.md @@ -26,7 +26,8 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 엄격한 TypeScript 검사 -bun run test # tests/ 전체 스위트 +bun run test:changed # `dev` diff가 영향을 주는 테스트 +bun run test # tests/ 전체 스위트 (PR review-ready / 명시 요청 시) bun test tests/router.test.ts # 특정 테스트 파일 bun run build:gui # Vite GUI 빌드 + 패키지 준비 bun run privacy:scan # CI에서 쓰는 자격 증명/개인정보 검사 @@ -35,8 +36,8 @@ bun run prepare:package # 패키지 런처/asset 갱신 대부분의 테스트는 `tests/*.test.ts`에 나란히 놓인 Bun 테스트입니다. 공용 fixture는 `tests/helpers/`, 범위가 넓은 네이티브 동등성 시나리오는 `tests/e2e-style/`에 있습니다. 바꾼 -subsystem의 기존 테스트 근처에 집중된 회귀 테스트를 추가하세요. 공용 라우팅, 어댑터, 설정, 서버 -동작을 건드렸다면 전체 스위트도 실행합니다. +subsystem의 기존 테스트 근처에 집중된 회귀 테스트를 추가하세요. 구현 중에는 해당 파일 또는 `bun run test:changed`를 실행하고, +전체 스위트는 PR을 review-ready로 만들기 전이나 명시적으로 요청된 때만 실행합니다. 지금 읽고 있는 문서 사이트는 `docs-site/`에 있습니다(Astro + Starlight). diff --git a/docs-site/src/content/docs/ru/contributing.md b/docs-site/src/content/docs/ru/contributing.md index 6f71de7f8b..c2e1523937 100644 --- a/docs-site/src/content/docs/ru/contributing.md +++ b/docs-site/src/content/docs/ru/contributing.md @@ -25,7 +25,8 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # строгая проверка TypeScript -bun run test # полный набор tests/ +bun run test:changed # тесты, затронутые diff относительно `dev` +bun run test # полный набор tests/ (PR review-ready / явная просьба) bun test tests/router.test.ts # отдельный тестовый файл bun run build:gui # сборка GUI на Vite + подготовка пакета bun run privacy:scan # проверка учётных данных/приватности, используемая в CI @@ -34,8 +35,8 @@ bun run prepare:package # обновление лаунчеров/ре Большинство тестов — плоские Bun-тесты `tests/*.test.ts`. В `tests/helpers/` лежат общие fixtures, а в `tests/e2e-style/` — более широкие сценарии нативного паритета. Добавляйте сфокусированный -регрессионный тест рядом с существующими тестами изменяемой подсистемы; если затронуты общая -маршрутизация, адаптеры, конфигурация или поведение сервера, запускайте полный набор. +регрессионный тест рядом с существующими тестами изменяемой подсистемы. Во время работы запускайте этот файл или `bun run test:changed`; +полный набор — перед пометкой PR как review-ready или по явной просьбе. Сайт документации, который вы сейчас читаете, находится в `docs-site/` (Astro + Starlight): diff --git a/docs-site/src/content/docs/tr/contributing.md b/docs-site/src/content/docs/tr/contributing.md index a6691f02b3..9285ef4c47 100644 --- a/docs-site/src/content/docs/tr/contributing.md +++ b/docs-site/src/content/docs/tr/contributing.md @@ -32,7 +32,8 @@ Yerel komutların CI ile eşleşmesi için depodaki betikleri kullanın: ```bash bun run typecheck # katı TypeScript denetimi -bun run test # tests/ paketinin tamamı +bun run test:changed # `dev` farkının etkilediği testler +bun run test # tests/ paketinin tamamı (PR review-ready / açık istek) bun test tests/router.test.ts # odaklanmış test dosyası bun run build:gui # Vite GUI derlemesi + paket hazırlığı bun run privacy:scan # CI tarafından kullanılan kimlik/gizlilik taraması @@ -42,9 +43,8 @@ bun run prepare:package # paket başlatıcılarını ve varlıkların Testlerin çoğu düz `tests/*.test.ts` Bun testleridir. `tests/helpers/` paylaşılan test ortamlarını (fixtures) ve `tests/e2e-style/` daha geniş yerel parite senaryolarını içerir. Değiştirdiğiniz alt sistemin mevcut testlerinin -yakınında odaklanmış bir regresyon testi bulundurun; paylaşılan yönlendirme, -adaptörler, yapılandırma veya sunucu davranışları için test paketinin tamamını -çalıştırın. +yakınında odaklanmış bir regresyon testi bulundurun. Uygulama sırasında o dosyayı veya `bun run test:changed` komutunu çalıştırın; +tam paketi `bun run test` ile yalnızca PR review-ready yapılmadan önce veya açıkça istendiğinde çalıştırın. Okumakta olduğunuz dokümantasyon sitesi `docs-site/` (Astro + Starlight) dizinindedir: diff --git a/docs-site/src/content/docs/zh-cn/contributing.md b/docs-site/src/content/docs/zh-cn/contributing.md index 1559fbe015..5b5e043b59 100644 --- a/docs-site/src/content/docs/zh-cn/contributing.md +++ b/docs-site/src/content/docs/zh-cn/contributing.md @@ -25,7 +25,8 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 严格 TypeScript 检查 -bun run test # 完整 tests/ suite +bun run test:changed # 相对 `dev` 的 diff 影响到的测试 +bun run test # 完整 tests/ suite(PR review-ready / 明确要求时) bun test tests/router.test.ts # 聚焦单个测试文件 bun run build:gui # Vite GUI 构建 + package 准备 bun run privacy:scan # CI 使用的 credential/privacy 扫描 @@ -34,7 +35,7 @@ bun run prepare:package # 刷新 package launcher/asset 大多数测试是平铺在 `tests/*.test.ts` 下的 Bun test。`tests/helpers/` 存放共享 fixture, `tests/e2e-style/` 存放范围更广的原生一致性场景。请在对应 subsystem 的现有测试附近加入聚焦的 -回归测试;若改动涉及共享 routing、adapter、config 或 server 行为,还应运行完整 suite。 +回归测试。实现过程中跑该文件或 `bun run test:changed`;完整 suite 只在 PR 标记 review-ready 前,或明确要求时再跑。 你正在阅读的文档站点位于 `docs-site/`(Astro + Starlight): diff --git a/docs-site/src/content/docs/zh-tw/contributing.md b/docs-site/src/content/docs/zh-tw/contributing.md index 66e04e0c3b..442bef8956 100644 --- a/docs-site/src/content/docs/zh-tw/contributing.md +++ b/docs-site/src/content/docs/zh-tw/contributing.md @@ -25,7 +25,8 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 嚴格 TypeScript 檢查 -bun run test # 完整 tests/ suite +bun run test:changed # 相對 `dev` 的 diff 影響到的測試 +bun run test # 完整 tests/ suite(PR review-ready / 明確要求時) bun test tests/router.test.ts # 聚焦單個測試檔案 bun run build:gui # Vite GUI 建置 + package 準備 bun run privacy:scan # CI 使用的 credential/privacy 掃描 @@ -34,7 +35,7 @@ bun run prepare:package # 重新整理 package launcher/asset 大多數測試是平鋪在 `tests/*.test.ts` 下的 Bun test。`tests/helpers/` 存放共享 fixture, `tests/e2e-style/` 存放範圍更廣的原生一致性場景。請在對應 subsystem 的現有測試附近加入聚焦的 -迴歸測試;若改動涉及共享 routing、adapter、config 或 server 行為,還應執行完整 suite。 +迴歸測試。實作過程中跑該檔案或 `bun run test:changed`;完整 suite 只在 PR 標記 review-ready 前,或明確要求時再跑。 你正在閱讀的文件站點位於 `docs-site/`(Astro + Starlight): diff --git a/package.json b/package.json index 6793a45f48..4bcf6009d8 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "dev:gui": "cd gui && bun run dev", "start": "bun run src/cli/index.ts start", "test": "bun scripts/test.ts", + "test:changed": "bun scripts/test.ts --changed=dev", "typecheck": "bun x tsc --noEmit", "audit:high": "bun audit --audit-level=high && cd gui && bun audit --audit-level=high", "privacy:scan": "bun scripts/privacy-scan.ts", diff --git a/scripts/test.ts b/scripts/test.ts index 41d141b7fd..002e15d194 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -146,6 +146,7 @@ function isFullSuiteRun(requested: string[]): boolean { const wrapperArgs = delimiterIndex === -1 ? requested : requested.slice(0, delimiterIndex); const passedThrough = delimiterIndex === -1 ? [] : requested.slice(delimiterIndex + 1); if (passedThrough.length > 0) return false; + if (hasCliFlag(requested, "--changed")) return false; for (let index = 0; index < wrapperArgs.length; index++) { const arg = wrapperArgs[index]; @@ -169,6 +170,11 @@ export function resolveBunTestArgs(requested: string[]): string[] { const args = ["--isolate"]; if (!hasCliFlag(requested, "--parallel")) args.push("--parallel"); args.push(...requested); + // A graph filter that selects nothing is a valid answer, not a failure. + if (hasCliFlag(requested, "--changed") && !hasCliFlag(requested, "--pass-with-no-tests")) { + args.push("--pass-with-no-tests"); + } + // `--changed` must not receive a default path, or Bun treats the graph filter as unused. if (isFullSuiteRun(requested)) args.push("./tests/"); return args; } diff --git a/src/AGENTS.md b/src/AGENTS.md index ab7a5fef46..9347a655fe 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -23,6 +23,7 @@ This file applies to `src/` and inherits the repository-wide rules in `/AGENTS.m - Place focused regression coverage near the existing tests for the affected subsystem. - For focused behavior, run the relevant `bun test tests/.test.ts` and `bun run typecheck`. -- For shared routing, adapters, config, OAuth, or server behavior, also run `bun run test`. +- If the change set is broader than one file, run `bun run test:changed` instead of the full suite. +- Run `bun run test` only before marking a PR review-ready, or when the user explicitly asks for the full suite. - For logging, requests, credentials, account data, or fixtures, also run `bun run privacy:scan`. - Update `docs-site/` when the change affects user-visible behavior or configuration. diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index 408d5dcf89..1070431c70 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -99,6 +99,18 @@ describe("bun test argv", () => { .toEqual(["--isolate", "--parallel=2", "tests/foo.test.ts"]); }); + test("changed-mode gets no suite path and tolerates an empty selection", () => { + // A default `./tests/` would make Bun treat the graph filter as unused, and a graph filter + // that selects nothing is a valid answer rather than a failure. + expect(resolveBunTestArgs(["--changed=dev"])) + .toEqual(["--isolate", "--parallel", "--changed=dev", "--pass-with-no-tests"]); + }); + + test("a caller-supplied --pass-with-no-tests is not duplicated", () => { + expect(resolveBunTestArgs(["--changed=dev", "--pass-with-no-tests"])) + .toEqual(["--isolate", "--parallel", "--changed=dev", "--pass-with-no-tests"]); + }); + test("option-only arguments still count as a full suite run", () => { expect(resolveBunTestArgs(["--timeout=30000"])) .toEqual(["--isolate", "--parallel", "--timeout=30000", "./tests/"]); From dc3aff71fffeba2b2806b066ccc73b99e46ed0ef Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sat, 22 Aug 2026 21:33:14 -0700 Subject: [PATCH 07/10] fix(test): correct the changed-mode claims and refuse a silent empty green Three review findings, two of them BLOCKING. The guides claimed test:changed 'already selects every test that imports those modules', so shared routing/config/server edits no longer needed the full suite. Bun's --changed walks only the parsed module graph: subprocess, read-as-data and golden-file dependencies are invisible to it. The claim is replaced with that boundary, consistently across AGENTS.md, src/AGENTS.md and all eight guides. Measured on bun 1.4.0: an empty --changed selection runs 0 tests and exits 0, with or without --pass-with-no-tests. A stale or wrong ref therefore produced a green run that tested nothing. Changed mode now requires an explicit ref and refuses a zero-test selection when the diff against that ref is non-empty. Two behaviors rested on false premises, both measured false: --pass-with-no-tests was a no-op and is removed, and ./tests/ does not suppress the graph filter (--changed=HEAD~1 with and without it selected the same 1 file / 9 tests). The rewrite also forbade routine repository-wide typecheck while src/AGENTS.md still required it; the prohibition is now scoped to the full test suite. --- AGENTS.md | 20 +-- docs-site/src/content/docs/contributing.md | 9 +- docs-site/src/content/docs/fr/contributing.md | 9 +- docs-site/src/content/docs/ja/contributing.md | 9 +- docs-site/src/content/docs/ko/contributing.md | 9 +- docs-site/src/content/docs/ru/contributing.md | 9 +- docs-site/src/content/docs/tr/contributing.md | 10 +- .../src/content/docs/zh-cn/contributing.md | 7 +- .../src/content/docs/zh-tw/contributing.md | 7 +- scripts/test.ts | 129 ++++++++++++++++-- src/AGENTS.md | 6 +- tests/test-runner.test.ts | 43 ++++-- 12 files changed, 215 insertions(+), 52 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 359cbf4490..3bad5eed44 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,13 +173,14 @@ bun run build:gui # Vite GUI build During implementation, use the smallest focused checks that directly cover the changed subsystem. Prefer `bun test tests/.test.ts` for a known file, or `bun run test:changed` when the touch set is broader than one file. Do **not** -run repository-wide `bun run typecheck`, `bun run test`, or a bare `bun test` -with no file arguments for a scoped change. Shared runtime, routing, config, or -server edits are not an exception: `bun run test:changed` already selects every -test that imports those modules. The full suite is ~850 files; launching it -without an explicit ask blocks the machine for minutes and starves other work. -Run it only when a focused result failed or is ambiguous, or the user explicitly -asks for full validation. +run repository-wide `bun run test` or a bare `bun test` with no file arguments +for a scoped change. `bun run test:changed` follows Bun's parsed module graph: it +selects test files that import changed modules, but it cannot see dependencies +expressed through subprocesses, source files read as data, or golden/derived +files. Run the relevant focused tests explicitly for those paths; if no reliable +focused set covers them, run the full suite. The full suite is ~850 files, so +otherwise reserve it for a failed or ambiguous focused result, an explicit user +request, or the PR-ready gate below. Before creating or updating a non-trivial PR as review-ready, or before approving such a PR, run `bun run typecheck` and `bun run test`. CI runs these @@ -280,8 +281,9 @@ reviewers (Codex, CodeRabbit). assumptions about a compile step, or code paths that break `bun run typecheck` / `bun run test`. - **Tests:** behavior changes in `src/` need a focused regression test near - the existing tests for that subsystem. During implementation, `bun run test:changed` - (or the focused file) is the local check; the full suite is the PR-ready gate. + the existing tests for that subsystem. During implementation, run the relevant + focused files and use `bun run test:changed` for import-connected coverage as + described above; the full suite is the PR-ready gate. - **Docs sync:** user-facing behavior changes should update `docs-site/` (and keep translated locales from contradicting the English source). - **Privacy:** `bun run privacy:scan` must stay green; never introduce logging diff --git a/docs-site/src/content/docs/contributing.md b/docs-site/src/content/docs/contributing.md index 5ccf6a9db8..a365cea656 100644 --- a/docs-site/src/content/docs/contributing.md +++ b/docs-site/src/content/docs/contributing.md @@ -28,7 +28,7 @@ scripts so local commands match CI: ```bash bun run typecheck # strict TypeScript check -bun run test:changed # tests affected by the diff against `dev` +bun run test:changed # import-graph tests linked to the diff against `dev` bun run test # complete tests/ suite (PR-ready / explicit ask) bun test tests/router.test.ts # focused test file bun run build:gui # Vite GUI build + package preparation @@ -38,8 +38,11 @@ bun run prepare:package # refresh package launchers/assets Most tests are flat `tests/*.test.ts` Bun tests. `tests/helpers/` contains shared fixtures and `tests/e2e-style/` contains broader native-parity scenarios. Keep a focused regression near the -existing tests for the subsystem you change. During implementation run that file or `bun run test:changed`; -run the complete suite with `bun run test` before marking a PR review-ready. +existing tests for the subsystem you change. `test:changed` follows Bun's parsed module graph: it +selects test files that import changed modules, but it cannot see dependencies exercised through +subprocesses, source files read as data, or golden/derived files. Run the relevant focused tests +explicitly for those paths; if no reliable focused set covers them, run the complete suite. In all +cases, run the complete suite with `bun run test` before marking a PR review-ready. The docs site you're reading lives in `docs-site/` (Astro + Starlight): diff --git a/docs-site/src/content/docs/fr/contributing.md b/docs-site/src/content/docs/fr/contributing.md index a006d77291..bb53c0d77c 100644 --- a/docs-site/src/content/docs/fr/contributing.md +++ b/docs-site/src/content/docs/fr/contributing.md @@ -28,7 +28,7 @@ distincte. Utilisez les scripts enregistrés afin que les commandes locales corr ```bash bun run typecheck # strict TypeScript check -bun run test:changed # tests affected by the diff against `dev` +bun run test:changed # tests liés au diff par le graphe d’import de `dev` bun run test # complete tests/ suite (PR-ready / explicit ask) bun test tests/router.test.ts # focused test file bun run build:gui # Vite GUI build + package preparation @@ -38,8 +38,11 @@ bun run prepare:package # refresh package launchers/assets La plupart des tests Bun sont des fichiers plats `tests/*.test.ts`. `tests/helpers/` contient les fixtures partagées et `tests/e2e-style/` des scénarios plus larges de parité native. Placez une régression ciblée près -des tests existants du sous-système modifié. Pendant l’implémentation, lancez ce fichier ou `bun run test:changed` ; -la suite complète (`bun run test`) est le seuil review-ready d’une PR. +des tests existants du sous-système modifié. `test:changed` suit le graphe de modules analysé par Bun : il +sélectionne les fichiers de test qui importent un module modifié, mais ne voit pas les dépendances exercées par +des sous-processus, les fichiers source lus comme données ni les fichiers golden/dérivés. Exécutez explicitement +les tests ciblés pour ces chemins ; si aucun ensemble ciblé fiable ne les couvre, exécutez la suite complète. +Dans tous les cas, lancez `bun run test` avant de marquer une PR comme review-ready. Le site de documentation que vous lisez se trouve dans `docs-site/` (Astro + Starlight) : diff --git a/docs-site/src/content/docs/ja/contributing.md b/docs-site/src/content/docs/ja/contributing.md index 023ad44dbb..0017702d83 100644 --- a/docs-site/src/content/docs/ja/contributing.md +++ b/docs-site/src/content/docs/ja/contributing.md @@ -26,7 +26,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 厳密な TypeScript 検査 -bun run test:changed # `dev` との diff が影響するテスト +bun run test:changed # `dev` との差分に import graph でつながるテスト bun run test # tests/ の全体スイート (PR review-ready / 明示時) bun test tests/router.test.ts # 特定テストファイル bun run build:gui # Vite GUI ビルド + パッケージ準備 @@ -36,8 +36,11 @@ bun run prepare:package # パッケージランチャー/asset 更新 ほとんどのテストは `tests/*.test.ts` に並んで配置された Bun テストです。共有 fixture は `tests/helpers/`、範囲の広いネイティブ等価性シナリオは `tests/e2e-style/` にあります。変更した -サブシステムの既存テストの近くに集中した回帰テストを追加してください。実装中はそのファイルか `bun run test:changed` を実行し、 -全体スイートは PR を review-ready にする前、または明示されたときだけ実行します。 +サブシステムの既存テストの近くに集中した回帰テストを追加してください。`test:changed` が追跡するのは Bun が解析した +module graph です。変更した module を import するテストファイルは選択しますが、subprocess 経由で実行される依存関係、 +データとして読み込まれるソースファイル、golden/派生ファイルへの依存関係は検出できません。これらについては該当する +集中テストを明示的に実行し、信頼できる集中テストの組み合わせがない場合は全体スイートを実行してください。いずれの場合も、 +PR を review-ready にする前に `bun run test` で全体スイートを実行します。 いま読んでいるドキュメントサイトは `docs-site/` にあります(Astro + Starlight)。 diff --git a/docs-site/src/content/docs/ko/contributing.md b/docs-site/src/content/docs/ko/contributing.md index 2006ff3826..4831f1fb08 100644 --- a/docs-site/src/content/docs/ko/contributing.md +++ b/docs-site/src/content/docs/ko/contributing.md @@ -26,7 +26,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 엄격한 TypeScript 검사 -bun run test:changed # `dev` diff가 영향을 주는 테스트 +bun run test:changed # `dev` diff와 import graph로 연결된 테스트 bun run test # tests/ 전체 스위트 (PR review-ready / 명시 요청 시) bun test tests/router.test.ts # 특정 테스트 파일 bun run build:gui # Vite GUI 빌드 + 패키지 준비 @@ -36,8 +36,11 @@ bun run prepare:package # 패키지 런처/asset 갱신 대부분의 테스트는 `tests/*.test.ts`에 나란히 놓인 Bun 테스트입니다. 공용 fixture는 `tests/helpers/`, 범위가 넓은 네이티브 동등성 시나리오는 `tests/e2e-style/`에 있습니다. 바꾼 -subsystem의 기존 테스트 근처에 집중된 회귀 테스트를 추가하세요. 구현 중에는 해당 파일 또는 `bun run test:changed`를 실행하고, -전체 스위트는 PR을 review-ready로 만들기 전이나 명시적으로 요청된 때만 실행합니다. +subsystem의 기존 테스트 근처에 집중된 회귀 테스트를 추가하세요. `test:changed`는 Bun이 파싱한 module graph를 따라 +변경된 module을 import하는 테스트 파일을 선택하지만, subprocess를 통해 실행되는 의존성, 데이터로 읽는 소스 파일, +golden/파생 파일 의존성은 찾지 못합니다. 이런 경로는 관련 집중 테스트를 명시적으로 실행하고, 신뢰할 수 있는 집중 테스트 +집합으로 다룰 수 없으면 전체 스위트를 실행하세요. 어떤 경우든 PR을 review-ready로 만들기 전에는 `bun run test`로 +전체 스위트를 실행합니다. 지금 읽고 있는 문서 사이트는 `docs-site/`에 있습니다(Astro + Starlight). diff --git a/docs-site/src/content/docs/ru/contributing.md b/docs-site/src/content/docs/ru/contributing.md index c2e1523937..8623a2824f 100644 --- a/docs-site/src/content/docs/ru/contributing.md +++ b/docs-site/src/content/docs/ru/contributing.md @@ -25,7 +25,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # строгая проверка TypeScript -bun run test:changed # тесты, затронутые diff относительно `dev` +bun run test:changed # тесты, связанные с diff через граф импортов bun run test # полный набор tests/ (PR review-ready / явная просьба) bun test tests/router.test.ts # отдельный тестовый файл bun run build:gui # сборка GUI на Vite + подготовка пакета @@ -35,8 +35,11 @@ bun run prepare:package # обновление лаунчеров/ре Большинство тестов — плоские Bun-тесты `tests/*.test.ts`. В `tests/helpers/` лежат общие fixtures, а в `tests/e2e-style/` — более широкие сценарии нативного паритета. Добавляйте сфокусированный -регрессионный тест рядом с существующими тестами изменяемой подсистемы. Во время работы запускайте этот файл или `bun run test:changed`; -полный набор — перед пометкой PR как review-ready или по явной просьбе. +регрессионный тест рядом с существующими тестами изменяемой подсистемы. `test:changed` следует по графу +модулей, разобранному Bun: он выбирает тестовые файлы, импортирующие изменённые модули, но не видит зависимости, +запускаемые через подпроцессы, исходники, читаемые как данные, и golden/производные файлы. Для таких путей явно +запускайте соответствующие сфокусированные тесты; если надёжного сфокусированного набора нет, запускайте полный +набор. В любом случае перед пометкой PR как review-ready выполните `bun run test`. Сайт документации, который вы сейчас читаете, находится в `docs-site/` (Astro + Starlight): diff --git a/docs-site/src/content/docs/tr/contributing.md b/docs-site/src/content/docs/tr/contributing.md index 9285ef4c47..b16fbf4d6e 100644 --- a/docs-site/src/content/docs/tr/contributing.md +++ b/docs-site/src/content/docs/tr/contributing.md @@ -32,7 +32,7 @@ Yerel komutların CI ile eşleşmesi için depodaki betikleri kullanın: ```bash bun run typecheck # katı TypeScript denetimi -bun run test:changed # `dev` farkının etkilediği testler +bun run test:changed # `dev` farkına import grafiğiyle bağlı testler bun run test # tests/ paketinin tamamı (PR review-ready / açık istek) bun test tests/router.test.ts # odaklanmış test dosyası bun run build:gui # Vite GUI derlemesi + paket hazırlığı @@ -43,8 +43,11 @@ bun run prepare:package # paket başlatıcılarını ve varlıkların Testlerin çoğu düz `tests/*.test.ts` Bun testleridir. `tests/helpers/` paylaşılan test ortamlarını (fixtures) ve `tests/e2e-style/` daha geniş yerel parite senaryolarını içerir. Değiştirdiğiniz alt sistemin mevcut testlerinin -yakınında odaklanmış bir regresyon testi bulundurun. Uygulama sırasında o dosyayı veya `bun run test:changed` komutunu çalıştırın; -tam paketi `bun run test` ile yalnızca PR review-ready yapılmadan önce veya açıkça istendiğinde çalıştırın. +yakınında odaklanmış bir regresyon testi bulundurun. `test:changed`, Bun'ın ayrıştırdığı module grafiğini izler: +değişen module'leri import eden test dosyalarını seçer; ancak subprocess üzerinden kullanılan bağımlılıkları, +veri olarak okunan kaynak dosyalarını veya golden/türetilmiş dosya bağımlılıklarını göremez. Bu yollar için ilgili +odaklanmış testleri açıkça çalıştırın; güvenilir bir odaklanmış test kümesi yoksa tam paketi çalıştırın. Her durumda, +PR'ı review-ready olarak işaretlemeden önce `bun run test` komutunu çalıştırın. Okumakta olduğunuz dokümantasyon sitesi `docs-site/` (Astro + Starlight) dizinindedir: @@ -253,4 +256,3 @@ typecheck`, davranış için odaklanmış bir `bun test tests/.test.ts` veya çalışma zamanı probu, ardından etkilenen yüzeye uygun daha geniş kapılar. opencodex büyük partiler yerine küçük, doğrulanabilir commit'leri tercih eder. - diff --git a/docs-site/src/content/docs/zh-cn/contributing.md b/docs-site/src/content/docs/zh-cn/contributing.md index 5b5e043b59..a4e9ebbed2 100644 --- a/docs-site/src/content/docs/zh-cn/contributing.md +++ b/docs-site/src/content/docs/zh-cn/contributing.md @@ -25,7 +25,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 严格 TypeScript 检查 -bun run test:changed # 相对 `dev` 的 diff 影响到的测试 +bun run test:changed # 通过 import graph 与 `dev` diff 关联的测试 bun run test # 完整 tests/ suite(PR review-ready / 明确要求时) bun test tests/router.test.ts # 聚焦单个测试文件 bun run build:gui # Vite GUI 构建 + package 准备 @@ -35,7 +35,10 @@ bun run prepare:package # 刷新 package launcher/asset 大多数测试是平铺在 `tests/*.test.ts` 下的 Bun test。`tests/helpers/` 存放共享 fixture, `tests/e2e-style/` 存放范围更广的原生一致性场景。请在对应 subsystem 的现有测试附近加入聚焦的 -回归测试。实现过程中跑该文件或 `bun run test:changed`;完整 suite 只在 PR 标记 review-ready 前,或明确要求时再跑。 +回归测试。`test:changed` 只沿 Bun 解析出的 module graph 选择 import 了变更 module 的测试文件,无法发现 +通过 subprocess 执行、作为数据读取的 source file,或 golden/derived file 形成的依赖。对这些路径要明确运行 +对应的聚焦测试;如果没有可靠的聚焦测试集合能覆盖,就运行完整 suite。无论如何,在把 PR 标记为 review-ready +之前都要运行 `bun run test`。 你正在阅读的文档站点位于 `docs-site/`(Astro + Starlight): diff --git a/docs-site/src/content/docs/zh-tw/contributing.md b/docs-site/src/content/docs/zh-tw/contributing.md index 442bef8956..db0f24bc02 100644 --- a/docs-site/src/content/docs/zh-tw/contributing.md +++ b/docs-site/src/content/docs/zh-tw/contributing.md @@ -25,7 +25,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 嚴格 TypeScript 檢查 -bun run test:changed # 相對 `dev` 的 diff 影響到的測試 +bun run test:changed # 透過 import graph 與 `dev` diff 關聯的測試 bun run test # 完整 tests/ suite(PR review-ready / 明確要求時) bun test tests/router.test.ts # 聚焦單個測試檔案 bun run build:gui # Vite GUI 建置 + package 準備 @@ -35,7 +35,10 @@ bun run prepare:package # 重新整理 package launcher/asset 大多數測試是平鋪在 `tests/*.test.ts` 下的 Bun test。`tests/helpers/` 存放共享 fixture, `tests/e2e-style/` 存放範圍更廣的原生一致性場景。請在對應 subsystem 的現有測試附近加入聚焦的 -迴歸測試。實作過程中跑該檔案或 `bun run test:changed`;完整 suite 只在 PR 標記 review-ready 前,或明確要求時再跑。 +迴歸測試。`test:changed` 只沿 Bun 解析出的 module graph 選擇 import 了變更 module 的測試檔案,無法發現 +透過 subprocess 執行、當成資料讀取的 source file,或 golden/derived file 形成的依賴。對這些路徑要明確執行 +對應的聚焦測試;若沒有可靠的聚焦測試集合能涵蓋,就執行完整 suite。無論如何,在把 PR 標記為 review-ready +之前都要執行 `bun run test`。 你正在閱讀的文件站點位於 `docs-site/`(Astro + Starlight): diff --git a/scripts/test.ts b/scripts/test.ts index 002e15d194..1bfcc0431b 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -140,7 +140,97 @@ const BUN_TEST_OPTIONS_REQUIRING_VALUES = new Set([ "--config", ]); -/** True for a filter-less `bun run test`. `--timeout` / `--dots` / `--parallel=N` still count. */ +export interface ChangedRunPreflight { + comparisonRef: string; + changedFiles: string[]; +} + +function decodeOutput(output: Uint8Array | undefined): string { + return output ? new TextDecoder().decode(output) : ""; +} + +function changedComparisonRef(requested: string[]): string | null { + const changedArg = requested.find(arg => arg === "--changed" || arg.startsWith("--changed=")); + if (!changedArg) return null; + if (changedArg === "--changed" || changedArg === "--changed=") { + throw new Error( + "[test] changed mode requires an explicit comparison ref; use --changed= so the selection can be validated.", + ); + } + return changedArg.slice("--changed=".length); +} + +function gitOutput( + args: string[], + cwd: string, + env: Record, +): string { + const result = Bun.spawnSync(["git", ...args], { + cwd, + env, + stdout: "pipe", + stderr: "pipe", + }); + if (result.exitCode !== 0) { + const detail = decodeOutput(result.stderr).trim() || `exit ${result.exitCode ?? "unknown"}`; + throw new Error(`[test] git ${args[0]} failed while validating changed mode: ${detail}`); + } + return decodeOutput(result.stdout); +} + +/** Resolve changed mode and inventory the diff against that commit before invoking Bun. */ +export function inspectChangedRun( + requested: string[], + cwd: string = process.cwd(), + env: Record = process.env, +): ChangedRunPreflight | null { + const comparisonRef = changedComparisonRef(requested); + if (!comparisonRef) return null; + if (comparisonRef.startsWith("-")) { + throw new Error(`[test] --changed comparison ref ${JSON.stringify(comparisonRef)} is invalid.`); + } + + const resolved = Bun.spawnSync( + ["git", "rev-parse", "--verify", "--quiet", `${comparisonRef}^{commit}`], + { cwd, env, stdout: "pipe", stderr: "pipe" }, + ); + if (resolved.exitCode !== 0) { + throw new Error( + `[test] --changed comparison ref ${JSON.stringify(comparisonRef)} does not resolve to a commit.`, + ); + } + const comparisonCommit = decodeOutput(resolved.stdout).trim(); + if (!comparisonCommit) { + throw new Error( + `[test] --changed comparison ref ${JSON.stringify(comparisonRef)} resolved without a commit id.`, + ); + } + + const diff = gitOutput(["diff", "--name-only", comparisonCommit, "--"], cwd, env); + const changedFiles = [...new Set(diff.split("\n").filter(Boolean))]; + return { comparisonRef, changedFiles }; +} + +/** Refuse a successful changed-mode run when Bun silently selected no tests for a real diff. */ +export function changedSelectionFailure( + preflight: ChangedRunPreflight, + output: string, +): string | null { + if (preflight.changedFiles.length === 0) return null; + const summary = output + .replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "") + .match(/Ran\s+(\d+)\s+tests?\s+across\s+(\d+)\s+files?\b/i); + if (!summary) { + return `[test] could not validate --changed=${preflight.comparisonRef}: Bun did not emit a recognizable selection summary for a diff containing ${preflight.changedFiles.length} changed file(s).`; + } + if (Number(summary[1]) !== 0 || Number(summary[2]) !== 0) return null; + return `[test] --changed=${preflight.comparisonRef} selected 0 tests across 0 files, but the diff contains ${preflight.changedFiles.length} changed file(s). Bun follows only the parsed module graph; run the relevant focused tests for subprocess, read-as-data, or golden-file dependencies, or run the full suite.`; +} + +/** + * True for a filter-less `bun run test`: no file arguments and no `--changed`. + * `--timeout` / `--dots` / `--parallel=N` still count as full. + */ function isFullSuiteRun(requested: string[]): boolean { const delimiterIndex = requested.indexOf("--"); const wrapperArgs = delimiterIndex === -1 ? requested : requested.slice(0, delimiterIndex); @@ -170,11 +260,8 @@ export function resolveBunTestArgs(requested: string[]): string[] { const args = ["--isolate"]; if (!hasCliFlag(requested, "--parallel")) args.push("--parallel"); args.push(...requested); - // A graph filter that selects nothing is a valid answer, not a failure. - if (hasCliFlag(requested, "--changed") && !hasCliFlag(requested, "--pass-with-no-tests")) { - args.push("--pass-with-no-tests"); - } - // `--changed` must not receive a default path, or Bun treats the graph filter as unused. + // An explicit graph filter is not a filter-less full-suite invocation, so it does not need + // the wrapper's default suite path. Supplying the path would be redundant, not incorrect. if (isFullSuiteRun(requested)) args.push("./tests/"); return args; } @@ -254,10 +341,17 @@ async function waitForExclusiveRun(selfPid: number): Promise { } } -if (import.meta.main) { +async function main(): Promise { const isolated = createIsolatedTestEnvironment(); try { const requestedTests = process.argv.slice(2); + let changedRun: ChangedRunPreflight | null; + try { + changedRun = inspectChangedRun(requestedTests, process.cwd(), isolated.env); + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + return 1; + } await waitForExclusiveRun(process.pid); const startedAt = Date.now(); const child = Bun.spawnSync( @@ -265,10 +359,16 @@ if (import.meta.main) { { env: isolated.env, stdin: "inherit", - stdout: "inherit", - stderr: "inherit", + stdout: changedRun ? "pipe" : "inherit", + stderr: changedRun ? "pipe" : "inherit", }, ); + const stdout = decodeOutput(child.stdout); + const stderr = decodeOutput(child.stderr); + if (changedRun) { + if (stdout) process.stdout.write(stdout); + if (stderr) process.stderr.write(stderr); + } const elapsedSeconds = Math.round((Date.now() - startedAt) / 1000); if (requestedTests.length === 0 && elapsedSeconds > 600) { console.warn( @@ -276,8 +376,17 @@ if (import.meta.main) { + "Check for another test runner, a busy CPU, or a test that started polling something real.", ); } - process.exitCode = child.exitCode ?? 1; + const exitCode = child.exitCode ?? 1; + if (exitCode !== 0 || !changedRun) return exitCode; + const selectionFailure = changedSelectionFailure(changedRun, `${stdout}\n${stderr}`); + if (selectionFailure) { + console.error(selectionFailure); + return 1; + } + return 0; } finally { isolated.cleanup(); } } + +if (import.meta.main) process.exitCode = await main(); diff --git a/src/AGENTS.md b/src/AGENTS.md index 9347a655fe..8ce63b03f2 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -23,7 +23,9 @@ This file applies to `src/` and inherits the repository-wide rules in `/AGENTS.m - Place focused regression coverage near the existing tests for the affected subsystem. - For focused behavior, run the relevant `bun test tests/.test.ts` and `bun run typecheck`. -- If the change set is broader than one file, run `bun run test:changed` instead of the full suite. -- Run `bun run test` only before marking a PR review-ready, or when the user explicitly asks for the full suite. +- For broader import-connected changes, `bun run test:changed` selects importers from Bun's parsed module graph. + It does not cover dependencies exercised through subprocesses, source files read as data, or golden/derived + files. Run those focused tests explicitly, and run `bun run test` if no reliable focused set covers them. +- Also run `bun run test` before marking a PR review-ready, or when the user explicitly asks for the full suite. - For logging, requests, credentials, account data, or fixtures, also run `bun run privacy:scan`. - Update `docs-site/` when the change affects user-visible behavior or configuration. diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index 1070431c70..47ead13d6e 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -2,7 +2,12 @@ import { describe, expect, test } from "bun:test"; import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { isAbsolute, join } from "node:path"; -import { createIsolatedTestEnvironment, resolveBunTestArgs } from "../scripts/test"; +import { + changedSelectionFailure, + createIsolatedTestEnvironment, + inspectChangedRun, + resolveBunTestArgs, +} from "../scripts/test"; import { decodeWindowsIdentityPowerShellOutputForTests, windowsIdentityPowerShellCommandForTests, @@ -99,16 +104,38 @@ describe("bun test argv", () => { .toEqual(["--isolate", "--parallel=2", "tests/foo.test.ts"]); }); - test("changed-mode gets no suite path and tolerates an empty selection", () => { - // A default `./tests/` would make Bun treat the graph filter as unused, and a graph filter - // that selects nothing is a valid answer rather than a failure. + test("changed-mode stays explicitly filtered without redundant arguments", () => { expect(resolveBunTestArgs(["--changed=dev"])) - .toEqual(["--isolate", "--parallel", "--changed=dev", "--pass-with-no-tests"]); + .toEqual(["--isolate", "--parallel", "--changed=dev"]); + }); + + test("changed-mode requires an explicit, resolvable comparison ref", () => { + expect(() => inspectChangedRun(["--changed"])).toThrow("requires an explicit comparison ref"); + expect(() => inspectChangedRun(["--changed=refs/heads/definitely-missing-test-ref"])) + .toThrow("does not resolve to a commit"); + expect(inspectChangedRun(["--changed=HEAD"])?.comparisonRef).toBe("HEAD"); + }); + + test("rejects an empty changed selection when the diff is non-empty", () => { + expect(changedSelectionFailure( + { comparisonRef: "dev", changedFiles: ["src/router.ts"] }, + "Ran 0 tests across 0 files.", + )).toContain("selected 0 tests across 0 files"); + expect(changedSelectionFailure( + { comparisonRef: "dev", changedFiles: ["src/router.ts"] }, + "Ran 9 tests across 1 file.", + )).toBeNull(); + expect(changedSelectionFailure( + { comparisonRef: "HEAD", changedFiles: [] }, + "Ran 0 tests across 0 files.", + )).toBeNull(); }); - test("a caller-supplied --pass-with-no-tests is not duplicated", () => { - expect(resolveBunTestArgs(["--changed=dev", "--pass-with-no-tests"])) - .toEqual(["--isolate", "--parallel", "--changed=dev", "--pass-with-no-tests"]); + test("rejects an unrecognized changed-mode summary for a non-empty diff", () => { + expect(changedSelectionFailure( + { comparisonRef: "dev", changedFiles: ["src/router.ts"] }, + "0 pass\n0 fail", + )).toContain("did not emit a recognizable selection summary"); }); test("option-only arguments still count as a full suite run", () => { From eca791d51ef1553ba7918c3c5f6fc9fd9da41c3c Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sat, 22 Aug 2026 22:08:45 -0700 Subject: [PATCH 08/10] fix(test): resolve the changed-mode ref by existence and align the guides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two CodeRabbit findings. The previous pass updated only each contributing guide's test section, leaving the preparation section still telling contributors to run the full suite — the same document said both things. All nine guides now present the same command list and the same distinction. --changed=dev compared against the LOCAL dev branch, which nothing keeps fresh: measured here it sat 296 commits behind upstream, so a 3-file change selected 753 of 902 test files, and a diverged local dev would silently select too little while the guides had just relaxed the full-suite requirement. Rather than guessing a remote name — origin is a fork for some contributors and the canonical repo for others — the ref is now resolved by EXISTENCE: upstream/dev, then origin/dev, then local dev, reporting which was used. selectChangedComparisonRef takes an existence probe so the preference order is testable without real remotes. --- docs-site/src/content/docs/contributing.md | 9 ++- docs-site/src/content/docs/fr/contributing.md | 9 ++- docs-site/src/content/docs/ja/contributing.md | 9 ++- docs-site/src/content/docs/ko/contributing.md | 9 ++- docs-site/src/content/docs/ru/contributing.md | 9 ++- docs-site/src/content/docs/tr/contributing.md | 10 ++- .../src/content/docs/zh-cn/contributing.md | 9 ++- .../src/content/docs/zh-tw/contributing.md | 9 ++- scripts/test.ts | 67 ++++++++++++++++--- tests/test-runner.test.ts | 35 +++++++++- 10 files changed, 147 insertions(+), 28 deletions(-) diff --git a/docs-site/src/content/docs/contributing.md b/docs-site/src/content/docs/contributing.md index a365cea656..2e196b06b3 100644 --- a/docs-site/src/content/docs/contributing.md +++ b/docs-site/src/content/docs/contributing.md @@ -15,7 +15,9 @@ bun install bun run dev:proxy # proxy API in dev mode bun run dev:gui # dashboard dev server (another terminal) bun run typecheck # bun x tsc --noEmit -bun run test # bun test ./tests/ +bun run test:changed # routine import-graph test selection +bun test tests/router.test.ts # routine focused test +bun run test # complete suite (PR-ready / explicit ask) ``` `bun run dev` remains an alias for `bun run dev:proxy`. The dashboard dev server is `bun run dev:gui`; @@ -28,7 +30,7 @@ scripts so local commands match CI: ```bash bun run typecheck # strict TypeScript check -bun run test:changed # import-graph tests linked to the diff against `dev` +bun run test:changed # import-graph tests against the resolved dev ref bun run test # complete tests/ suite (PR-ready / explicit ask) bun test tests/router.test.ts # focused test file bun run build:gui # Vite GUI build + package preparation @@ -36,6 +38,9 @@ bun run privacy:scan # credential/privacy scan used by CI bun run prepare:package # refresh package launchers/assets ``` +`test:changed` reports and uses the first comparison ref that exists, in order: +`upstream/dev`, `origin/dev`, then local `dev`. + Most tests are flat `tests/*.test.ts` Bun tests. `tests/helpers/` contains shared fixtures and `tests/e2e-style/` contains broader native-parity scenarios. Keep a focused regression near the existing tests for the subsystem you change. `test:changed` follows Bun's parsed module graph: it diff --git a/docs-site/src/content/docs/fr/contributing.md b/docs-site/src/content/docs/fr/contributing.md index bb53c0d77c..dfd22be8db 100644 --- a/docs-site/src/content/docs/fr/contributing.md +++ b/docs-site/src/content/docs/fr/contributing.md @@ -15,7 +15,9 @@ bun install bun run dev:proxy # proxy API in dev mode bun run dev:gui # dashboard dev server (another terminal) bun run typecheck # bun x tsc --noEmit -bun run test # bun test ./tests/ +bun run test:changed # sélection courante via le graphe d’import +bun test tests/router.test.ts # test ciblé courant +bun run test # suite complète (PR-ready / demande explicite) ``` `bun run dev` reste un alias pour `bun run dev:proxy`. Le serveur de développement du tableau de bord est `bun run dev:gui` ; @@ -28,7 +30,7 @@ distincte. Utilisez les scripts enregistrés afin que les commandes locales corr ```bash bun run typecheck # strict TypeScript check -bun run test:changed # tests liés au diff par le graphe d’import de `dev` +bun run test:changed # tests liés par le graphe d’import à la ref dev résolue bun run test # complete tests/ suite (PR-ready / explicit ask) bun test tests/router.test.ts # focused test file bun run build:gui # Vite GUI build + package preparation @@ -36,6 +38,9 @@ bun run privacy:scan # credential/privacy scan used by CI bun run prepare:package # refresh package launchers/assets ``` +`test:changed` indique et utilise la première ref de comparaison existante, dans cet ordre : +`upstream/dev`, `origin/dev`, puis la ref locale `dev`. + La plupart des tests Bun sont des fichiers plats `tests/*.test.ts`. `tests/helpers/` contient les fixtures partagées et `tests/e2e-style/` des scénarios plus larges de parité native. Placez une régression ciblée près des tests existants du sous-système modifié. `test:changed` suit le graphe de modules analysé par Bun : il diff --git a/docs-site/src/content/docs/ja/contributing.md b/docs-site/src/content/docs/ja/contributing.md index 0017702d83..18a213bbac 100644 --- a/docs-site/src/content/docs/ja/contributing.md +++ b/docs-site/src/content/docs/ja/contributing.md @@ -12,7 +12,9 @@ bun install bun run dev:proxy # 開発モードのプロキシ API bun run dev:gui # ダッシュボード dev サーバー(別ターミナル) bun run typecheck # bun x tsc --noEmit -bun run test # bun test ./tests/ +bun run test:changed # 通常の import graph 選択 +bun test tests/router.test.ts # 通常の集中テスト +bun run test # 全体スイート (PR review-ready / 明示時) ``` `bun run dev` は引き続き `bun run dev:proxy` のエイリアスとして動作します。ダッシュボード dev サーバーは @@ -26,7 +28,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 厳密な TypeScript 検査 -bun run test:changed # `dev` との差分に import graph でつながるテスト +bun run test:changed # 解決済み dev ref の差分に import graph でつながるテスト bun run test # tests/ の全体スイート (PR review-ready / 明示時) bun test tests/router.test.ts # 特定テストファイル bun run build:gui # Vite GUI ビルド + パッケージ準備 @@ -34,6 +36,9 @@ bun run privacy:scan # CI で使う資格情報/個人情報検査 bun run prepare:package # パッケージランチャー/asset 更新 ``` +`test:changed` は、`upstream/dev`、`origin/dev`、ローカルの `dev` の順に最初に存在する +比較 ref を使用し、その ref を出力します。 + ほとんどのテストは `tests/*.test.ts` に並んで配置された Bun テストです。共有 fixture は `tests/helpers/`、範囲の広いネイティブ等価性シナリオは `tests/e2e-style/` にあります。変更した サブシステムの既存テストの近くに集中した回帰テストを追加してください。`test:changed` が追跡するのは Bun が解析した diff --git a/docs-site/src/content/docs/ko/contributing.md b/docs-site/src/content/docs/ko/contributing.md index 4831f1fb08..55a4826bc2 100644 --- a/docs-site/src/content/docs/ko/contributing.md +++ b/docs-site/src/content/docs/ko/contributing.md @@ -12,7 +12,9 @@ bun install bun run dev:proxy # 개발 모드 프록시 API bun run dev:gui # 대시보드 dev 서버(다른 터미널) bun run typecheck # bun x tsc --noEmit -bun run test # bun test ./tests/ +bun run test:changed # 일반 import graph 선택 +bun test tests/router.test.ts # 일반 집중 테스트 +bun run test # 전체 스위트 (PR review-ready / 명시 요청 시) ``` `bun run dev`는 계속 `bun run dev:proxy`의 별칭으로 동작합니다. 대시보드 dev 서버는 @@ -26,7 +28,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 엄격한 TypeScript 검사 -bun run test:changed # `dev` diff와 import graph로 연결된 테스트 +bun run test:changed # 결정된 dev ref의 diff와 import graph로 연결된 테스트 bun run test # tests/ 전체 스위트 (PR review-ready / 명시 요청 시) bun test tests/router.test.ts # 특정 테스트 파일 bun run build:gui # Vite GUI 빌드 + 패키지 준비 @@ -34,6 +36,9 @@ bun run privacy:scan # CI에서 쓰는 자격 증명/개인정보 bun run prepare:package # 패키지 런처/asset 갱신 ``` +`test:changed`는 `upstream/dev`, `origin/dev`, 로컬 `dev` 순으로 처음 존재하는 비교 ref를 +사용하고 그 ref를 출력합니다. + 대부분의 테스트는 `tests/*.test.ts`에 나란히 놓인 Bun 테스트입니다. 공용 fixture는 `tests/helpers/`, 범위가 넓은 네이티브 동등성 시나리오는 `tests/e2e-style/`에 있습니다. 바꾼 subsystem의 기존 테스트 근처에 집중된 회귀 테스트를 추가하세요. `test:changed`는 Bun이 파싱한 module graph를 따라 diff --git a/docs-site/src/content/docs/ru/contributing.md b/docs-site/src/content/docs/ru/contributing.md index 8623a2824f..b5aba16b86 100644 --- a/docs-site/src/content/docs/ru/contributing.md +++ b/docs-site/src/content/docs/ru/contributing.md @@ -12,7 +12,9 @@ bun install bun run dev:proxy # прокси-API в режиме разработки bun run dev:gui # dev-сервер дашборда (другой терминал) bun run typecheck # bun x tsc --noEmit -bun run test # bun test ./tests/ +bun run test:changed # обычный выбор по графу импортов +bun test tests/router.test.ts # обычный сфокусированный тест +bun run test # полный набор (PR review-ready / явная просьба) ``` `bun run dev` остаётся псевдонимом для `bun run dev:proxy`. Dev-сервер дашборда — `bun run dev:gui`; @@ -25,7 +27,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # строгая проверка TypeScript -bun run test:changed # тесты, связанные с diff через граф импортов +bun run test:changed # тесты, связанные с diff выбранной dev-ref bun run test # полный набор tests/ (PR review-ready / явная просьба) bun test tests/router.test.ts # отдельный тестовый файл bun run build:gui # сборка GUI на Vite + подготовка пакета @@ -33,6 +35,9 @@ bun run privacy:scan # проверка учётных данных bun run prepare:package # обновление лаунчеров/ресурсов пакета ``` +`test:changed` сообщает и использует первую существующую ref для сравнения в порядке: +`upstream/dev`, `origin/dev`, затем локальную `dev`. + Большинство тестов — плоские Bun-тесты `tests/*.test.ts`. В `tests/helpers/` лежат общие fixtures, а в `tests/e2e-style/` — более широкие сценарии нативного паритета. Добавляйте сфокусированный регрессионный тест рядом с существующими тестами изменяемой подсистемы. `test:changed` следует по графу diff --git a/docs-site/src/content/docs/tr/contributing.md b/docs-site/src/content/docs/tr/contributing.md index b16fbf4d6e..2f36ebf8c7 100644 --- a/docs-site/src/content/docs/tr/contributing.md +++ b/docs-site/src/content/docs/tr/contributing.md @@ -17,7 +17,9 @@ bun install bun run dev:proxy # geliştirme modunda proxy API bun run dev:gui # kontrol paneli geliştirme sunucusu (başka bir terminalde) bun run typecheck # bun x tsc --noEmit -bun run test # bun test ./tests/ +bun run test:changed # rutin import graph seçimi +bun test tests/router.test.ts # rutin odaklanmış test +bun run test # tam paket (PR review-ready / açık istek) ``` `bun run dev`, `bun run dev:proxy` komutunun bir takma adıdır. Kontrol paneli @@ -32,7 +34,7 @@ Yerel komutların CI ile eşleşmesi için depodaki betikleri kullanın: ```bash bun run typecheck # katı TypeScript denetimi -bun run test:changed # `dev` farkına import grafiğiyle bağlı testler +bun run test:changed # çözümlenen dev ref farkına import grafiğiyle bağlı testler bun run test # tests/ paketinin tamamı (PR review-ready / açık istek) bun test tests/router.test.ts # odaklanmış test dosyası bun run build:gui # Vite GUI derlemesi + paket hazırlığı @@ -40,6 +42,9 @@ bun run privacy:scan # CI tarafından kullanılan kimlik/gizlilik t bun run prepare:package # paket başlatıcılarını ve varlıklarını yenileme ``` +`test:changed`, karşılaştırma için sırasıyla `upstream/dev`, `origin/dev`, ardından yerel `dev` +ref'lerinden var olan ilkini kullanır ve çıktıda bildirir. + Testlerin çoğu düz `tests/*.test.ts` Bun testleridir. `tests/helpers/` paylaşılan test ortamlarını (fixtures) ve `tests/e2e-style/` daha geniş yerel parite senaryolarını içerir. Değiştirdiğiniz alt sistemin mevcut testlerinin @@ -255,4 +260,3 @@ Değişikliğinizi kanıtlayan en dar komutu çalıştırın — tipler için `b typecheck`, davranış için odaklanmış bir `bun test tests/.test.ts` veya çalışma zamanı probu, ardından etkilenen yüzeye uygun daha geniş kapılar. opencodex büyük partiler yerine küçük, doğrulanabilir commit'leri tercih eder. - diff --git a/docs-site/src/content/docs/zh-cn/contributing.md b/docs-site/src/content/docs/zh-cn/contributing.md index a4e9ebbed2..61c24a58d5 100644 --- a/docs-site/src/content/docs/zh-cn/contributing.md +++ b/docs-site/src/content/docs/zh-cn/contributing.md @@ -12,7 +12,9 @@ bun install bun run dev:proxy # 开发模式代理 API bun run dev:gui # 仪表盘 dev 服务器(另一个终端) bun run typecheck # bun x tsc --noEmit -bun run test # bun test ./tests/ +bun run test:changed # 日常 import graph 选择 +bun test tests/router.test.ts # 日常聚焦测试 +bun run test # 完整 suite(PR review-ready / 明确要求时) ``` `bun run dev` 继续作为 `bun run dev:proxy` 的别名。仪表盘 dev 服务器使用 `bun run dev:gui`; @@ -25,7 +27,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 严格 TypeScript 检查 -bun run test:changed # 通过 import graph 与 `dev` diff 关联的测试 +bun run test:changed # 通过 import graph 与已解析 dev ref 的 diff 关联的测试 bun run test # 完整 tests/ suite(PR review-ready / 明确要求时) bun test tests/router.test.ts # 聚焦单个测试文件 bun run build:gui # Vite GUI 构建 + package 准备 @@ -33,6 +35,9 @@ bun run privacy:scan # CI 使用的 credential/privacy 扫描 bun run prepare:package # 刷新 package launcher/asset ``` +`test:changed` 会按 `upstream/dev`、`origin/dev`、本地 `dev` 的顺序使用第一个实际存在的 +比较 ref,并在输出中报告该 ref。 + 大多数测试是平铺在 `tests/*.test.ts` 下的 Bun test。`tests/helpers/` 存放共享 fixture, `tests/e2e-style/` 存放范围更广的原生一致性场景。请在对应 subsystem 的现有测试附近加入聚焦的 回归测试。`test:changed` 只沿 Bun 解析出的 module graph 选择 import 了变更 module 的测试文件,无法发现 diff --git a/docs-site/src/content/docs/zh-tw/contributing.md b/docs-site/src/content/docs/zh-tw/contributing.md index db0f24bc02..7a00bb3dd9 100644 --- a/docs-site/src/content/docs/zh-tw/contributing.md +++ b/docs-site/src/content/docs/zh-tw/contributing.md @@ -12,7 +12,9 @@ bun install bun run dev:proxy # 開發模式代理 API bun run dev:gui # 儀表板 dev 伺服器(另一個終端) bun run typecheck # bun x tsc --noEmit -bun run test # bun test ./tests/ +bun run test:changed # 日常 import graph 選擇 +bun test tests/router.test.ts # 日常聚焦測試 +bun run test # 完整 suite(PR review-ready / 明確要求時) ``` `bun run dev` 繼續作為 `bun run dev:proxy` 的別名。儀表板 dev 伺服器使用 `bun run dev:gui`; @@ -25,7 +27,7 @@ bun run test # bun test ./tests/ ```bash bun run typecheck # 嚴格 TypeScript 檢查 -bun run test:changed # 透過 import graph 與 `dev` diff 關聯的測試 +bun run test:changed # 透過 import graph 與已解析 dev ref 的 diff 關聯的測試 bun run test # 完整 tests/ suite(PR review-ready / 明確要求時) bun test tests/router.test.ts # 聚焦單個測試檔案 bun run build:gui # Vite GUI 建置 + package 準備 @@ -33,6 +35,9 @@ bun run privacy:scan # CI 使用的 credential/privacy 掃描 bun run prepare:package # 重新整理 package launcher/asset ``` +`test:changed` 會依 `upstream/dev`、`origin/dev`、本機 `dev` 的順序使用第一個實際存在的 +比較 ref,並在輸出中回報該 ref。 + 大多數測試是平鋪在 `tests/*.test.ts` 下的 Bun test。`tests/helpers/` 存放共享 fixture, `tests/e2e-style/` 存放範圍更廣的原生一致性場景。請在對應 subsystem 的現有測試附近加入聚焦的 迴歸測試。`test:changed` 只沿 Bun 解析出的 module graph 選擇 import 了變更 module 的測試檔案,無法發現 diff --git a/scripts/test.ts b/scripts/test.ts index 1bfcc0431b..d12f0eb934 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -145,6 +145,13 @@ export interface ChangedRunPreflight { changedFiles: string[]; } +const changedComparisonRefs = ["upstream/dev", "origin/dev", "dev"] as const; + +/** Choose the highest-priority conventional dev ref without assuming which remote is canonical. */ +export function selectChangedComparisonRef(refExists: (ref: string) => boolean): string | null { + return changedComparisonRefs.find(refExists) ?? null; +} + function decodeOutput(output: Uint8Array | undefined): string { return output ? new TextDecoder().decode(output) : ""; } @@ -160,6 +167,20 @@ function changedComparisonRef(requested: string[]): string | null { return changedArg.slice("--changed=".length); } +function gitRefExists( + ref: string, + cwd: string, + env: Record, +): boolean { + const result = Bun.spawnSync(["git", "rev-parse", "--verify", "--quiet", `${ref}^{commit}`], { + cwd, + env, + stdout: "ignore", + stderr: "ignore", + }); + return result.exitCode === 0; +} + function gitOutput( args: string[], cwd: string, @@ -184,10 +205,21 @@ export function inspectChangedRun( cwd: string = process.cwd(), env: Record = process.env, ): ChangedRunPreflight | null { - const comparisonRef = changedComparisonRef(requested); - if (!comparisonRef) return null; - if (comparisonRef.startsWith("-")) { - throw new Error(`[test] --changed comparison ref ${JSON.stringify(comparisonRef)} is invalid.`); + const requestedComparisonRef = changedComparisonRef(requested); + if (!requestedComparisonRef) return null; + if (requestedComparisonRef.startsWith("-")) { + throw new Error( + `[test] --changed comparison ref ${JSON.stringify(requestedComparisonRef)} is invalid.`, + ); + } + + const comparisonRef = requestedComparisonRef === "dev" + ? selectChangedComparisonRef(ref => gitRefExists(ref, cwd, env)) + : requestedComparisonRef; + if (!comparisonRef) { + throw new Error( + `[test] --changed=dev could not resolve a comparison ref; none of ${changedComparisonRefs.join(", ")} exists.`, + ); } const resolved = Bun.spawnSync( @@ -256,13 +288,23 @@ function isFullSuiteRun(requested: string[]): boolean { * against ~110-190 s for the identical suite with `--parallel`. A caller-supplied `--parallel=N` * is left alone. */ -export function resolveBunTestArgs(requested: string[]): string[] { +export function resolveBunTestArgs( + requested: string[], + resolvedChangedRef?: string, +): string[] { + const effectiveRequested = resolvedChangedRef + ? requested.map(arg => ( + arg === "--changed" || arg.startsWith("--changed=") + ? `--changed=${resolvedChangedRef}` + : arg + )) + : requested; const args = ["--isolate"]; - if (!hasCliFlag(requested, "--parallel")) args.push("--parallel"); - args.push(...requested); + if (!hasCliFlag(effectiveRequested, "--parallel")) args.push("--parallel"); + args.push(...effectiveRequested); // An explicit graph filter is not a filter-less full-suite invocation, so it does not need // the wrapper's default suite path. Supplying the path would be redundant, not incorrect. - if (isFullSuiteRun(requested)) args.push("./tests/"); + if (isFullSuiteRun(effectiveRequested)) args.push("./tests/"); return args; } @@ -352,10 +394,17 @@ async function main(): Promise { console.error(error instanceof Error ? error.message : String(error)); return 1; } + if (changedRun) { + console.log(`[test] changed mode comparison ref: ${changedRun.comparisonRef}`); + } await waitForExclusiveRun(process.pid); const startedAt = Date.now(); const child = Bun.spawnSync( - [process.execPath, "test", ...resolveBunTestArgs(requestedTests)], + [ + process.execPath, + "test", + ...resolveBunTestArgs(requestedTests, changedRun?.comparisonRef), + ], { env: isolated.env, stdin: "inherit", diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index 47ead13d6e..5295cd5f75 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -7,6 +7,7 @@ import { createIsolatedTestEnvironment, inspectChangedRun, resolveBunTestArgs, + selectChangedComparisonRef, } from "../scripts/test"; import { decodeWindowsIdentityPowerShellOutputForTests, @@ -107,6 +108,36 @@ describe("bun test argv", () => { test("changed-mode stays explicitly filtered without redundant arguments", () => { expect(resolveBunTestArgs(["--changed=dev"])) .toEqual(["--isolate", "--parallel", "--changed=dev"]); + expect(resolveBunTestArgs(["--changed=dev"], "upstream/dev")) + .toEqual(["--isolate", "--parallel", "--changed=upstream/dev"]); + }); + + test("changed-mode prefers the first existing conventional dev ref", () => { + const selectFrom = (...existing: string[]) => { + const probed: string[] = []; + const selected = selectChangedComparisonRef(ref => { + probed.push(ref); + return existing.includes(ref); + }); + return { selected, probed }; + }; + + expect(selectFrom("upstream/dev", "origin/dev", "dev")).toEqual({ + selected: "upstream/dev", + probed: ["upstream/dev"], + }); + expect(selectFrom("origin/dev", "dev")).toEqual({ + selected: "origin/dev", + probed: ["upstream/dev", "origin/dev"], + }); + expect(selectFrom("dev")).toEqual({ + selected: "dev", + probed: ["upstream/dev", "origin/dev", "dev"], + }); + expect(selectFrom()).toEqual({ + selected: null, + probed: ["upstream/dev", "origin/dev", "dev"], + }); }); test("changed-mode requires an explicit, resolvable comparison ref", () => { @@ -118,9 +149,9 @@ describe("bun test argv", () => { test("rejects an empty changed selection when the diff is non-empty", () => { expect(changedSelectionFailure( - { comparisonRef: "dev", changedFiles: ["src/router.ts"] }, + { comparisonRef: "upstream/dev", changedFiles: ["src/router.ts"] }, "Ran 0 tests across 0 files.", - )).toContain("selected 0 tests across 0 files"); + )).toContain("--changed=upstream/dev selected 0 tests across 0 files"); expect(changedSelectionFailure( { comparisonRef: "dev", changedFiles: ["src/router.ts"] }, "Ran 9 tests across 1 file.", From e5113c04c792cc98b6918defaf6b1dd602033aa7 Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sun, 23 Aug 2026 02:11:17 -0700 Subject: [PATCH 09/10] fix(test): anchor changed mode at merge base --- AGENTS.md | 2 +- docs-site/src/content/docs/contributing.md | 7 +- docs-site/src/content/docs/fr/contributing.md | 7 +- docs-site/src/content/docs/ja/contributing.md | 5 +- docs-site/src/content/docs/ko/contributing.md | 5 +- docs-site/src/content/docs/ru/contributing.md | 7 +- docs-site/src/content/docs/tr/contributing.md | 5 +- .../src/content/docs/zh-cn/contributing.md | 7 +- .../src/content/docs/zh-tw/contributing.md | 7 +- scripts/test.ts | 30 +++---- tests/test-runner.test.ts | 90 +++++++++++++++++-- 11 files changed, 127 insertions(+), 45 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3bad5eed44..bf6ea9f486 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -163,7 +163,7 @@ it binds you regardless of which mechanism is within reach. ```bash bun install bun run typecheck # bun x tsc --noEmit (strict) -bun run test:changed # tests whose import graph touches the diff against `dev` +bun run test:changed # import-graph tests against the resolved `dev` merge base bun run test # full tests/ suite (PR-ready / explicit ask only) bun run lint:gui # GUI eslint bun run privacy:scan # credential/privacy scan used by CI diff --git a/docs-site/src/content/docs/contributing.md b/docs-site/src/content/docs/contributing.md index 2e196b06b3..9dac4255fb 100644 --- a/docs-site/src/content/docs/contributing.md +++ b/docs-site/src/content/docs/contributing.md @@ -30,7 +30,7 @@ scripts so local commands match CI: ```bash bun run typecheck # strict TypeScript check -bun run test:changed # import-graph tests against the resolved dev ref +bun run test:changed # import-graph tests against the resolved dev merge base bun run test # complete tests/ suite (PR-ready / explicit ask) bun test tests/router.test.ts # focused test file bun run build:gui # Vite GUI build + package preparation @@ -38,8 +38,9 @@ bun run privacy:scan # credential/privacy scan used by CI bun run prepare:package # refresh package launchers/assets ``` -`test:changed` reports and uses the first comparison ref that exists, in order: -`upstream/dev`, `origin/dev`, then local `dev`. +`test:changed` selects the first comparison ref that exists, in order: `upstream/dev`, +`origin/dev`, then local `dev`. It reports that ref and the exact `git merge-base HEAD ` +commit, then passes the merge-base SHA to Bun. Most tests are flat `tests/*.test.ts` Bun tests. `tests/helpers/` contains shared fixtures and `tests/e2e-style/` contains broader native-parity scenarios. Keep a focused regression near the diff --git a/docs-site/src/content/docs/fr/contributing.md b/docs-site/src/content/docs/fr/contributing.md index dfd22be8db..580d200908 100644 --- a/docs-site/src/content/docs/fr/contributing.md +++ b/docs-site/src/content/docs/fr/contributing.md @@ -30,7 +30,7 @@ distincte. Utilisez les scripts enregistrés afin que les commandes locales corr ```bash bun run typecheck # strict TypeScript check -bun run test:changed # tests liés par le graphe d’import à la ref dev résolue +bun run test:changed # tests liés au merge-base dev résolu bun run test # complete tests/ suite (PR-ready / explicit ask) bun test tests/router.test.ts # focused test file bun run build:gui # Vite GUI build + package preparation @@ -38,8 +38,9 @@ bun run privacy:scan # credential/privacy scan used by CI bun run prepare:package # refresh package launchers/assets ``` -`test:changed` indique et utilise la première ref de comparaison existante, dans cet ordre : -`upstream/dev`, `origin/dev`, puis la ref locale `dev`. +`test:changed` choisit la première ref de comparaison existante, dans cet ordre : `upstream/dev`, +`origin/dev`, puis la ref locale `dev`. Il indique cette ref et le commit exact obtenu par +`git merge-base HEAD `, puis transmet le SHA du merge-base à Bun. La plupart des tests Bun sont des fichiers plats `tests/*.test.ts`. `tests/helpers/` contient les fixtures partagées et `tests/e2e-style/` des scénarios plus larges de parité native. Placez une régression ciblée près diff --git a/docs-site/src/content/docs/ja/contributing.md b/docs-site/src/content/docs/ja/contributing.md index 18a213bbac..e0a6fc7d19 100644 --- a/docs-site/src/content/docs/ja/contributing.md +++ b/docs-site/src/content/docs/ja/contributing.md @@ -28,7 +28,7 @@ bun run test # 全体スイート (PR review-ready / 明示 ```bash bun run typecheck # 厳密な TypeScript 検査 -bun run test:changed # 解決済み dev ref の差分に import graph でつながるテスト +bun run test:changed # 解決済み dev merge-base との差分テスト bun run test # tests/ の全体スイート (PR review-ready / 明示時) bun test tests/router.test.ts # 特定テストファイル bun run build:gui # Vite GUI ビルド + パッケージ準備 @@ -37,7 +37,8 @@ bun run prepare:package # パッケージランチャー/asset 更新 ``` `test:changed` は、`upstream/dev`、`origin/dev`、ローカルの `dev` の順に最初に存在する -比較 ref を使用し、その ref を出力します。 +比較 ref を選びます。その ref と `git merge-base HEAD ` で得た正確な commit を出力し、 +merge-base の SHA を Bun に渡します。 ほとんどのテストは `tests/*.test.ts` に並んで配置された Bun テストです。共有 fixture は `tests/helpers/`、範囲の広いネイティブ等価性シナリオは `tests/e2e-style/` にあります。変更した diff --git a/docs-site/src/content/docs/ko/contributing.md b/docs-site/src/content/docs/ko/contributing.md index 55a4826bc2..42c6b846b7 100644 --- a/docs-site/src/content/docs/ko/contributing.md +++ b/docs-site/src/content/docs/ko/contributing.md @@ -28,7 +28,7 @@ bun run test # 전체 스위트 (PR review-ready / 명시 ```bash bun run typecheck # 엄격한 TypeScript 검사 -bun run test:changed # 결정된 dev ref의 diff와 import graph로 연결된 테스트 +bun run test:changed # 결정된 dev merge-base와 연결된 테스트 bun run test # tests/ 전체 스위트 (PR review-ready / 명시 요청 시) bun test tests/router.test.ts # 특정 테스트 파일 bun run build:gui # Vite GUI 빌드 + 패키지 준비 @@ -37,7 +37,8 @@ bun run prepare:package # 패키지 런처/asset 갱신 ``` `test:changed`는 `upstream/dev`, `origin/dev`, 로컬 `dev` 순으로 처음 존재하는 비교 ref를 -사용하고 그 ref를 출력합니다. +선택합니다. 그 ref와 `git merge-base HEAD `로 구한 정확한 commit을 출력하고, +merge-base SHA를 Bun에 전달합니다. 대부분의 테스트는 `tests/*.test.ts`에 나란히 놓인 Bun 테스트입니다. 공용 fixture는 `tests/helpers/`, 범위가 넓은 네이티브 동등성 시나리오는 `tests/e2e-style/`에 있습니다. 바꾼 diff --git a/docs-site/src/content/docs/ru/contributing.md b/docs-site/src/content/docs/ru/contributing.md index b5aba16b86..7740f02d8f 100644 --- a/docs-site/src/content/docs/ru/contributing.md +++ b/docs-site/src/content/docs/ru/contributing.md @@ -27,7 +27,7 @@ bun run test # полный набор (PR review-ready / ```bash bun run typecheck # строгая проверка TypeScript -bun run test:changed # тесты, связанные с diff выбранной dev-ref +bun run test:changed # тесты относительно выбранной dev merge-base bun run test # полный набор tests/ (PR review-ready / явная просьба) bun test tests/router.test.ts # отдельный тестовый файл bun run build:gui # сборка GUI на Vite + подготовка пакета @@ -35,8 +35,9 @@ bun run privacy:scan # проверка учётных данных bun run prepare:package # обновление лаунчеров/ресурсов пакета ``` -`test:changed` сообщает и использует первую существующую ref для сравнения в порядке: -`upstream/dev`, `origin/dev`, затем локальную `dev`. +`test:changed` выбирает первую существующую ref для сравнения в порядке: `upstream/dev`, +`origin/dev`, затем локальную `dev`. Команда сообщает эту ref и точный commit из +`git merge-base HEAD `, после чего передаёт SHA merge-base в Bun. Большинство тестов — плоские Bun-тесты `tests/*.test.ts`. В `tests/helpers/` лежат общие fixtures, а в `tests/e2e-style/` — более широкие сценарии нативного паритета. Добавляйте сфокусированный diff --git a/docs-site/src/content/docs/tr/contributing.md b/docs-site/src/content/docs/tr/contributing.md index 2f36ebf8c7..e2572b2e50 100644 --- a/docs-site/src/content/docs/tr/contributing.md +++ b/docs-site/src/content/docs/tr/contributing.md @@ -34,7 +34,7 @@ Yerel komutların CI ile eşleşmesi için depodaki betikleri kullanın: ```bash bun run typecheck # katı TypeScript denetimi -bun run test:changed # çözümlenen dev ref farkına import grafiğiyle bağlı testler +bun run test:changed # çözümlenen dev merge-base farkına bağlı testler bun run test # tests/ paketinin tamamı (PR review-ready / açık istek) bun test tests/router.test.ts # odaklanmış test dosyası bun run build:gui # Vite GUI derlemesi + paket hazırlığı @@ -43,7 +43,8 @@ bun run prepare:package # paket başlatıcılarını ve varlıkların ``` `test:changed`, karşılaştırma için sırasıyla `upstream/dev`, `origin/dev`, ardından yerel `dev` -ref'lerinden var olan ilkini kullanır ve çıktıda bildirir. +ref'lerinden var olan ilkini seçer. Bu ref'i ve `git merge-base HEAD ` ile bulunan kesin +commit'i bildirir, ardından merge-base SHA'sını Bun'a geçirir. Testlerin çoğu düz `tests/*.test.ts` Bun testleridir. `tests/helpers/` paylaşılan test ortamlarını (fixtures) ve `tests/e2e-style/` daha geniş yerel diff --git a/docs-site/src/content/docs/zh-cn/contributing.md b/docs-site/src/content/docs/zh-cn/contributing.md index 61c24a58d5..a81470c961 100644 --- a/docs-site/src/content/docs/zh-cn/contributing.md +++ b/docs-site/src/content/docs/zh-cn/contributing.md @@ -27,7 +27,7 @@ bun run test # 完整 suite(PR review-ready / 明确要 ```bash bun run typecheck # 严格 TypeScript 检查 -bun run test:changed # 通过 import graph 与已解析 dev ref 的 diff 关联的测试 +bun run test:changed # 针对已解析 dev merge-base 的 import graph 测试 bun run test # 完整 tests/ suite(PR review-ready / 明确要求时) bun test tests/router.test.ts # 聚焦单个测试文件 bun run build:gui # Vite GUI 构建 + package 准备 @@ -35,8 +35,9 @@ bun run privacy:scan # CI 使用的 credential/privacy 扫描 bun run prepare:package # 刷新 package launcher/asset ``` -`test:changed` 会按 `upstream/dev`、`origin/dev`、本地 `dev` 的顺序使用第一个实际存在的 -比较 ref,并在输出中报告该 ref。 +`test:changed` 会按 `upstream/dev`、`origin/dev`、本地 `dev` 的顺序选择第一个实际存在的 +比较 ref,并报告该 ref 及 `git merge-base HEAD ` 得到的精确 commit,然后把 merge-base +SHA 传给 Bun。 大多数测试是平铺在 `tests/*.test.ts` 下的 Bun test。`tests/helpers/` 存放共享 fixture, `tests/e2e-style/` 存放范围更广的原生一致性场景。请在对应 subsystem 的现有测试附近加入聚焦的 diff --git a/docs-site/src/content/docs/zh-tw/contributing.md b/docs-site/src/content/docs/zh-tw/contributing.md index 7a00bb3dd9..3b8a484e1d 100644 --- a/docs-site/src/content/docs/zh-tw/contributing.md +++ b/docs-site/src/content/docs/zh-tw/contributing.md @@ -27,7 +27,7 @@ bun run test # 完整 suite(PR review-ready / 明確要 ```bash bun run typecheck # 嚴格 TypeScript 檢查 -bun run test:changed # 透過 import graph 與已解析 dev ref 的 diff 關聯的測試 +bun run test:changed # 針對已解析 dev merge-base 的 import graph 測試 bun run test # 完整 tests/ suite(PR review-ready / 明確要求時) bun test tests/router.test.ts # 聚焦單個測試檔案 bun run build:gui # Vite GUI 建置 + package 準備 @@ -35,8 +35,9 @@ bun run privacy:scan # CI 使用的 credential/privacy 掃描 bun run prepare:package # 重新整理 package launcher/asset ``` -`test:changed` 會依 `upstream/dev`、`origin/dev`、本機 `dev` 的順序使用第一個實際存在的 -比較 ref,並在輸出中回報該 ref。 +`test:changed` 會依 `upstream/dev`、`origin/dev`、本機 `dev` 的順序選擇第一個實際存在的 +比較 ref,並回報該 ref 與 `git merge-base HEAD ` 得到的精確 commit,再把 merge-base +SHA 傳給 Bun。 大多數測試是平鋪在 `tests/*.test.ts` 下的 Bun test。`tests/helpers/` 存放共享 fixture, `tests/e2e-style/` 存放範圍更廣的原生一致性場景。請在對應 subsystem 的現有測試附近加入聚焦的 diff --git a/scripts/test.ts b/scripts/test.ts index d12f0eb934..633a92cfbc 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -142,6 +142,7 @@ const BUN_TEST_OPTIONS_REQUIRING_VALUES = new Set([ export interface ChangedRunPreflight { comparisonRef: string; + comparisonCommit: string; changedFiles: string[]; } @@ -222,25 +223,22 @@ export function inspectChangedRun( ); } - const resolved = Bun.spawnSync( - ["git", "rev-parse", "--verify", "--quiet", `${comparisonRef}^{commit}`], - { cwd, env, stdout: "pipe", stderr: "pipe" }, - ); - if (resolved.exitCode !== 0) { + if (!gitRefExists(comparisonRef, cwd, env)) { throw new Error( `[test] --changed comparison ref ${JSON.stringify(comparisonRef)} does not resolve to a commit.`, ); } - const comparisonCommit = decodeOutput(resolved.stdout).trim(); + + const comparisonCommit = gitOutput(["merge-base", "HEAD", comparisonRef], cwd, env).trim(); if (!comparisonCommit) { throw new Error( - `[test] --changed comparison ref ${JSON.stringify(comparisonRef)} resolved without a commit id.`, + `[test] --changed comparison ref ${JSON.stringify(comparisonRef)} has no merge base with HEAD.`, ); } const diff = gitOutput(["diff", "--name-only", comparisonCommit, "--"], cwd, env); const changedFiles = [...new Set(diff.split("\n").filter(Boolean))]; - return { comparisonRef, changedFiles }; + return { comparisonRef, comparisonCommit, changedFiles }; } /** Refuse a successful changed-mode run when Bun silently selected no tests for a real diff. */ @@ -253,10 +251,10 @@ export function changedSelectionFailure( .replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "") .match(/Ran\s+(\d+)\s+tests?\s+across\s+(\d+)\s+files?\b/i); if (!summary) { - return `[test] could not validate --changed=${preflight.comparisonRef}: Bun did not emit a recognizable selection summary for a diff containing ${preflight.changedFiles.length} changed file(s).`; + return `[test] could not validate --changed=${preflight.comparisonCommit} (${preflight.comparisonRef} merge base): Bun did not emit a recognizable selection summary for a diff containing ${preflight.changedFiles.length} changed file(s).`; } if (Number(summary[1]) !== 0 || Number(summary[2]) !== 0) return null; - return `[test] --changed=${preflight.comparisonRef} selected 0 tests across 0 files, but the diff contains ${preflight.changedFiles.length} changed file(s). Bun follows only the parsed module graph; run the relevant focused tests for subprocess, read-as-data, or golden-file dependencies, or run the full suite.`; + return `[test] --changed=${preflight.comparisonCommit} (${preflight.comparisonRef} merge base) selected 0 tests across 0 files, but the diff contains ${preflight.changedFiles.length} changed file(s). Bun follows only the parsed module graph; run the relevant focused tests for subprocess, read-as-data, or golden-file dependencies, or run the full suite.`; } /** @@ -290,12 +288,12 @@ function isFullSuiteRun(requested: string[]): boolean { */ export function resolveBunTestArgs( requested: string[], - resolvedChangedRef?: string, + comparisonCommit?: string, ): string[] { - const effectiveRequested = resolvedChangedRef + const effectiveRequested = comparisonCommit ? requested.map(arg => ( arg === "--changed" || arg.startsWith("--changed=") - ? `--changed=${resolvedChangedRef}` + ? `--changed=${comparisonCommit}` : arg )) : requested; @@ -395,7 +393,9 @@ async function main(): Promise { return 1; } if (changedRun) { - console.log(`[test] changed mode comparison ref: ${changedRun.comparisonRef}`); + console.log( + `[test] changed mode comparison ref: ${changedRun.comparisonRef}; merge base: ${changedRun.comparisonCommit}`, + ); } await waitForExclusiveRun(process.pid); const startedAt = Date.now(); @@ -403,7 +403,7 @@ async function main(): Promise { [ process.execPath, "test", - ...resolveBunTestArgs(requestedTests, changedRun?.comparisonRef), + ...resolveBunTestArgs(requestedTests, changedRun?.comparisonCommit), ], { env: isolated.env, diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index 5295cd5f75..a9ddfa899e 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -15,6 +15,37 @@ import { windowsIdentityPowerShellSpawnOptionsForTests, } from "../src/codex/user-identity"; +function runGit(cwd: string, ...args: string[]): string { + const result = Bun.spawnSync(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" }); + if (result.exitCode !== 0) { + throw new Error(new TextDecoder().decode(result.stderr)); + } + return new TextDecoder().decode(result.stdout).trim(); +} + +function commitFixture(cwd: string, path: string, contents: string, message: string): string { + writeFileSync(join(cwd, path), contents); + runGit(cwd, "add", path); + runGit( + cwd, + "-c", + "user.name=OpenCodex Test", + "-c", + "user.email=test@opencodex.invalid", + "commit", + "-m", + message, + ); + return runGit(cwd, "rev-parse", "HEAD"); +} + +function initChangedRunFixture(): { cwd: string; base: string } { + const cwd = mkdtempSync(join(tmpdir(), "opencodex-changed-ref-")); + runGit(cwd, "init", "--quiet"); + const base = commitFixture(cwd, "base.txt", "base\n", "base"); + return { cwd, base }; +} + describe("test runner isolation", () => { test("redirects user homes to a disposable root", () => { const isolated = createIsolatedTestEnvironment({ PATH: "/test/bin", HOME: "/real/home" }); @@ -108,8 +139,9 @@ describe("bun test argv", () => { test("changed-mode stays explicitly filtered without redundant arguments", () => { expect(resolveBunTestArgs(["--changed=dev"])) .toEqual(["--isolate", "--parallel", "--changed=dev"]); - expect(resolveBunTestArgs(["--changed=dev"], "upstream/dev")) - .toEqual(["--isolate", "--parallel", "--changed=upstream/dev"]); + const mergeBase = "0123456789abcdef0123456789abcdef01234567"; + expect(resolveBunTestArgs(["--changed=dev"], mergeBase)) + .toEqual(["--isolate", "--parallel", `--changed=${mergeBase}`]); }); test("changed-mode prefers the first existing conventional dev ref", () => { @@ -144,27 +176,69 @@ describe("bun test argv", () => { expect(() => inspectChangedRun(["--changed"])).toThrow("requires an explicit comparison ref"); expect(() => inspectChangedRun(["--changed=refs/heads/definitely-missing-test-ref"])) .toThrow("does not resolve to a commit"); - expect(inspectChangedRun(["--changed=HEAD"])?.comparisonRef).toBe("HEAD"); + const inspected = inspectChangedRun(["--changed=HEAD"]); + expect(inspected?.comparisonRef).toBe("HEAD"); + expect(inspected?.comparisonCommit).toBe(runGit(process.cwd(), "rev-parse", "HEAD")); + }); + + test("changed-mode uses the shared merge base for behind, ahead, and diverged refs", () => { + const fixtures: string[] = []; + try { + const behind = initChangedRunFixture(); + fixtures.push(behind.cwd); + runGit(behind.cwd, "branch", "candidate", behind.base); + commitFixture(behind.cwd, "head.txt", "head\n", "head ahead of candidate"); + expect(inspectChangedRun(["--changed=candidate"], behind.cwd)).toMatchObject({ + comparisonRef: "candidate", + comparisonCommit: behind.base, + changedFiles: ["head.txt"], + }); + + const ahead = initChangedRunFixture(); + fixtures.push(ahead.cwd); + const candidateTip = commitFixture(ahead.cwd, "candidate.txt", "candidate\n", "candidate ahead"); + runGit(ahead.cwd, "branch", "candidate", candidateTip); + runGit(ahead.cwd, "checkout", "--quiet", "--detach", ahead.base); + expect(inspectChangedRun(["--changed=candidate"], ahead.cwd)).toMatchObject({ + comparisonRef: "candidate", + comparisonCommit: ahead.base, + changedFiles: [], + }); + + const diverged = initChangedRunFixture(); + fixtures.push(diverged.cwd); + runGit(diverged.cwd, "checkout", "--quiet", "-b", "candidate"); + commitFixture(diverged.cwd, "candidate.txt", "candidate\n", "candidate side"); + runGit(diverged.cwd, "checkout", "--quiet", "--detach", diverged.base); + commitFixture(diverged.cwd, "head.txt", "head\n", "head side"); + expect(inspectChangedRun(["--changed=candidate"], diverged.cwd)).toMatchObject({ + comparisonRef: "candidate", + comparisonCommit: diverged.base, + changedFiles: ["head.txt"], + }); + } finally { + for (const fixture of fixtures) rmSync(fixture, { recursive: true, force: true }); + } }); test("rejects an empty changed selection when the diff is non-empty", () => { expect(changedSelectionFailure( - { comparisonRef: "upstream/dev", changedFiles: ["src/router.ts"] }, + { comparisonRef: "upstream/dev", comparisonCommit: "base-sha", changedFiles: ["src/router.ts"] }, "Ran 0 tests across 0 files.", - )).toContain("--changed=upstream/dev selected 0 tests across 0 files"); + )).toContain("--changed=base-sha (upstream/dev merge base) selected 0 tests across 0 files"); expect(changedSelectionFailure( - { comparisonRef: "dev", changedFiles: ["src/router.ts"] }, + { comparisonRef: "dev", comparisonCommit: "base-sha", changedFiles: ["src/router.ts"] }, "Ran 9 tests across 1 file.", )).toBeNull(); expect(changedSelectionFailure( - { comparisonRef: "HEAD", changedFiles: [] }, + { comparisonRef: "HEAD", comparisonCommit: "head-sha", changedFiles: [] }, "Ran 0 tests across 0 files.", )).toBeNull(); }); test("rejects an unrecognized changed-mode summary for a non-empty diff", () => { expect(changedSelectionFailure( - { comparisonRef: "dev", changedFiles: ["src/router.ts"] }, + { comparisonRef: "dev", comparisonCommit: "base-sha", changedFiles: ["src/router.ts"] }, "0 pass\n0 fail", )).toContain("did not emit a recognizable selection summary"); }); From 9593646f3798e1870ef8de4387ce09274422802d Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sun, 23 Aug 2026 11:36:17 -0700 Subject: [PATCH 10/10] fix(test): preserve changed args after delimiter --- AGENTS.md | 9 +++++---- scripts/test.ts | 10 +++++++--- tests/test-runner.test.ts | 4 ++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bf6ea9f486..cc9851178c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -174,13 +174,14 @@ During implementation, use the smallest focused checks that directly cover the changed subsystem. Prefer `bun test tests/.test.ts` for a known file, or `bun run test:changed` when the touch set is broader than one file. Do **not** run repository-wide `bun run test` or a bare `bun test` with no file arguments -for a scoped change. `bun run test:changed` follows Bun's parsed module graph: it +for a scoped change by default. `bun run test:changed` follows Bun's parsed module graph: it selects test files that import changed modules, but it cannot see dependencies expressed through subprocesses, source files read as data, or golden/derived files. Run the relevant focused tests explicitly for those paths; if no reliable -focused set covers them, run the full suite. The full suite is ~850 files, so -otherwise reserve it for a failed or ambiguous focused result, an explicit user -request, or the PR-ready gate below. +focused set covers them, the full suite is required even for a scoped change. +That indirect-dependency case is the explicit exception to the scoped-change +default. The full suite is ~850 files, so otherwise reserve it for a failed or +ambiguous focused result, an explicit user request, or the PR-ready gate below. Before creating or updating a non-trivial PR as review-ready, or before approving such a PR, run `bun run typecheck` and `bun run test`. CI runs these diff --git a/scripts/test.ts b/scripts/test.ts index 633a92cfbc..db83966785 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -158,7 +158,9 @@ function decodeOutput(output: Uint8Array | undefined): string { } function changedComparisonRef(requested: string[]): string | null { - const changedArg = requested.find(arg => arg === "--changed" || arg.startsWith("--changed=")); + const delimiterIndex = requested.indexOf("--"); + const wrapperArgs = delimiterIndex === -1 ? requested : requested.slice(0, delimiterIndex); + const changedArg = wrapperArgs.find(arg => arg === "--changed" || arg.startsWith("--changed=")); if (!changedArg) return null; if (changedArg === "--changed" || changedArg === "--changed=") { throw new Error( @@ -290,9 +292,11 @@ export function resolveBunTestArgs( requested: string[], comparisonCommit?: string, ): string[] { + const delimiterIndex = requested.indexOf("--"); const effectiveRequested = comparisonCommit - ? requested.map(arg => ( - arg === "--changed" || arg.startsWith("--changed=") + ? requested.map((arg, index) => ( + (delimiterIndex === -1 || index < delimiterIndex) + && (arg === "--changed" || arg.startsWith("--changed=")) ? `--changed=${comparisonCommit}` : arg )) diff --git a/tests/test-runner.test.ts b/tests/test-runner.test.ts index a9ddfa899e..0ddd326998 100644 --- a/tests/test-runner.test.ts +++ b/tests/test-runner.test.ts @@ -274,6 +274,10 @@ describe("bun test argv", () => { test("arguments after the delimiter are passed through instead of parsed as wrapper flags", () => { expect(resolveBunTestArgs(["--", "--parallel=2"])) .toEqual(["--isolate", "--parallel", "--", "--parallel=2"]); + const mergeBase = "0123456789abcdef0123456789abcdef01234567"; + expect(resolveBunTestArgs(["--", "--changed=fixture"], mergeBase)) + .toEqual(["--isolate", "--parallel", "--", "--changed=fixture"]); + expect(inspectChangedRun(["--", "--changed=fixture"])).toBeNull(); }); test("the wrapper passes parallel execution through to bun", () => {