Skip to content

test(filter-workspace): run the cases concurrently and assert the exact output - #41033

Open
robobun wants to merge 1 commit into
mainfrom
robobun/3f7d2eb7/filter-workspace-test-speed
Open

robobun wants to merge 1 commit into
mainfrom
robobun/3f7d2eb7/filter-workspace-test-speed

Conversation

@robobun

@robobun robobun commented Aug 31, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • test/cli/run/filter-workspace.test.ts took 13 s on the x64-asan lane (build 108747). 88 tests ran one at a time through spawnSync, and the fixture scripts slept 100 ms and 300 ms on fixed timers.
  • Most assertions were toMatch(/scripta/) on stdout. Nothing checked stderr.

Fix

  • Every test spawns with Bun.spawn inside describe.concurrent. Scripts whose content does not matter are shell builtins (echo, cat, seq). The "run in parallel" and cycle tests synchronize on marker files. The dependency-order tests pair a bun child with a plain cat, so a run that starts both together reads a missing file.
  • Assertions compare the sorted stdout lines and the whole stderr to exact values. The --elide-lines tests check the final renderer frame, whose lines are 1 to 20, so elision of the wrong end fails. Error cases pin the exact messages.
  • Debug build, same machine: 23.6 s before, 7.3 to 7.7 s after (5 runs). Release bun: 1.96 s before, 0.13 s after.
  • Verified: 23 debug runs on Linux and 5 release runs on Windows pass.

Background

  • bun run --filter runs one script per matched package, in parallel unless one depends on another. A pipe gets <package> <script>: <line> per line, then <package> <script>: Exited with code N. Packages interleave, so the tests sort the lines.
  • With FORCE_COLOR on POSIX the terminal renderer repaints the whole run on every output chunk. A pipe keeps every repaint, so only the last frame is stable.
  • [run] bun = true puts a node shim on PATH. All bun processes share the shim directory, and a debug build deletes it first (install: stop debug builds from wiping the shared node shim dir on every start #37384 fixes that). The four shim tests stay serial.
Notes

Assertion changes, test by test:

  • Every --filter run: sorted(stdout) equals the exact list of <package> <script>: <line> and Exited with code N lines, and stderr equals the exact text ("", or the malformed2 warning for the shared fixture). Before: toMatch on a few words, stderr unchecked, the absence checks (antipattern) are covered by the exact list.
  • Path patterns (./packages/*, ./**, ./packages/malformed1): the lines of the package without a name appear with an empty package name ( present: malformed1).
  • "no package has the script" (notpresent, x): stderr is exactly the malformed2 warning plus error: No workspace packages matched the filter "*". Before: a regex on part of it.
  • "filter that matched nothing": stderr is exactly the warning plus warn: No workspace packages matched the filter "typo".
  • --elide-lines (default, 15, 0) and the two bunfig elide tests: the last synchronized-update frame, with ANSI escapes stripped and the duration replaced, equals the exact frame text, including [N lines elided] and the kept lines N+1 to 20. Before: "at least 20 log_line somewhere in the byte stream", which test(filter-workspace): assert --elide-lines on the final redraw frame #35620 found to be chunk-timing dependent. The default case no longer passes --elide-lines 10, so it tests the default. On Windows the plain output is compared instead.
  • "terminal output reports how long a script took": the final frame is exactly dep0 script $ exit 0 and └─ Done in <time>.
  • "run in parallel" and the cycle test: both scripts print <me> saw <other> only when both were alive at the same time (marker files, 10 s deadline). Before: x...y...x interleaving of 100 ms sleeps.
  • "pre and post scripts": the three steps chain through files written by the previous step (echo > out.txt, cat out.txt && echo > post.txt, cat post.txt).
  • "dependency order" (two cases) and "dependency order inside a relation": the dependent runs cat ../<dep>/out.txt, the dependency writes it from a bun script. Exact output, no 100 ms sleep.
  • "detect cycle of length > 2": the third package is named dep2 (it was dep1, a typo, so the cycle had length 2 and two packages shared a name).
  • "nonzero exit code": exact lines for Exited with code 0 and Exited with code 23.
  • "symlink loop": exact lines, cyc twice.
  • "bad package.json warning": exact stderr line with the path.
  • selectors: test.each over twelve (patterns, expected packages) rows, exact output, stderr "".
  • --parallel: exact stdout (api:present | out-api) and exact stderr status lines with the duration replaced.
  • bare ...: stderr is exactly error: --filter "..." is missing a workspace name or path.
  • SIGINT test: stdout is exactly pkg-bg go: Signaled with code SIGINT, stderr "", exit 130. Before: expect.any(String).
  • detached child test: exact stdout, stderr "".
  • bunfig [run] bun = true (three forms) and --bun: exact lines. Malformed bunfig: inline snapshot of the full error.

Fixture changes: the unused index.js files and the sleep.js scripts are gone from the shared workspace. The --elide-lines fixtures run seq 1 20 instead of a bun script with twenty console.log calls. The >8 characters case writes a fixed string instead of a timestamp.

Flake found while testing: with four copies of the file running at once on the debug build, one [run] bun = true run printed Bun is undefined. create_fake_temporary_node_executable in src/install/lib.rs deletes the shared /tmp/bun-node-debug directory before it writes the shim again (debug builds only), so a concurrent run can remove the node link between another run's setup and its script's PATH lookup. #37384 removes that delete. Until then the four shim tests in this file run one at a time. No failure in 23 single-copy debug runs, and none in 8 four-copy runs of the final file (the one failure was in 12 four-copy runs of the version with concurrent shim tests).

Timings per test with the debug build on this machine: the echo runs take 130 to 230 ms each, the tests with a bun child 380 to 600 ms. The release bun runs the whole file in 0.13 s. The per-test concurrency under ASAN is 5 (max_concurrency default in src/options_types/context.rs), so the child count matters as much as the parallelism.

The rewrite touches every test in the file, so the open PRs that add cases to it (#35620, #35573, #36656, #38101, #40546, #39650, #29941, #35580, #37131) will need their cases re-added in the new shape. #35620's final-frame approach is what the --elide-lines tests here do.

Also run: USE_SYSTEM_BUN=1 bun test test/cli/run/filter-workspace.test.ts (release, 87 pass), the file 5 times with USE_SYSTEM_BUN=1 on windows-x64 (85 pass, 4 skipped as before; 3.5 s before, 0.62 s after), and tsc --noEmit on the file with the test tsconfig (clean).


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/run/filter-workspace.test.ts

…ct output

The file ran 88 tests one at a time through spawnSync, and its fixture
scripts waited on fixed timers (three 100 ms sleeps per script, 300 ms
keep-alives). On the ASAN lane that took 13 s.

Every test now spawns with Bun.spawn inside describe.concurrent. Scripts
whose content does not matter are shell builtins (echo, cat, seq) instead
of a bun child. The "run in parallel" and cycle tests synchronize the two
scripts on marker files instead of sleeping. The dependency-order tests
give the dependency a runtime start and the dependent a plain cat, so a
run that started both together reads a missing file.

Assertions compare the sorted stdout lines and the full stderr to exact
values. The --elide-lines tests read the final renderer frame and check
which lines were kept. The error cases pin the exact messages. The
cycle-of-three test names its third package dep2, so the cycle is a real
three-cycle. The "elides by default" case no longer passes --elide-lines.

The four tests that put bun behind the `node` shim stay serial: every bun
process shares the shim directory and a debug build deletes it before it
writes the shim again.
@robobun

robobun commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Reproduced the slow run locally with the debug build: bun bd test test/cli/run/filter-workspace.test.ts took 23.6 s on main (88 tests, all through spawnSync). With this branch the same command takes 7.3 to 7.7 s over 5 runs, and 23 debug runs plus 5 Windows runs of the file pass. The PR body lists every assertion change.

@coderabbitai

coderabbitai Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b3351d6b-7cbc-492f-9367-a5d5cbd53678

📥 Commits

Reviewing files that changed from the base of the PR and between 2a0fda9 and d123ea4.

📒 Files selected for processing (1)
  • test/cli/run/filter-workspace.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 2 remain after this review.


Walkthrough

The workspace filter test suite now uses asynchronous Bun.spawn execution. It adds coverage for selector resolution, dependency ordering, concurrency, terminal output, process cleanup, symlink discovery, signal handling, and bunfig precedence.

Changes

Workspace filter tests

Layer / File(s) Summary
Async harness and workspace fixtures
test/cli/run/filter-workspace.test.ts
Shared asynchronous helpers, output sorting, terminal synchronization, completion lines, and workspace fixtures were added.
Selector resolution and diagnostics
test/cli/run/filter-workspace.test.ts
Tests cover wildcard, directory, recursive, relation, negation, unnamed-package, and workspace-relative selectors. Exact warnings and exit statuses are asserted.
Concurrent and dependency-ordered execution
test/cli/run/filter-workspace.test.ts
Tests cover concurrent scripts, pre/main/post ordering, dependency traversal, cycles, missing scripts, failures, and relation selections.
Terminal output and process lifecycle
test/cli/run/filter-workspace.test.ts
Tests cover output elision, terminal completion frames, symlink recursion, timing, detached children, and SIGINT handling.
Bunfig configuration precedence
test/cli/run/filter-workspace.test.ts
Tests cover [run] bun, CLI precedence, configured elision counts, overrides, and malformed boolean diagnostics.

Suggested reviewers: jarred-sumner, dylan-conway

Merge Risk: ⚪ Minimal · up to d123e

This test-only change improves execution speed and assertion precision without introducing a supported production risk; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main changes: concurrent test execution and exact output assertions.
Description check ✅ Passed The description explains the problem, implementation, verification steps, test coverage, and known concurrency constraint. It does not use the exact template headings, but it provides the required inf…
Full details: Description check

Explanation

The description explains the problem, implementation, verification steps, test coverage, and known concurrency constraint. It does not use the exact template headings, but it provides the required information in equivalent sections.


Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:05 AM PT - Aug 31st, 2026

✅ @robobun, your commit d123ea4ad4d3418d95d8e02e03e0e08d5b6c6a60 passed in Build #108808! 🎉


🧪   To try this PR locally:

bunx bun-pr 41033

That installs a local version of the PR into your bun-41033 executable, so you can run:

bun-41033 --bun

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs. Because it rewrites every test in the file — swapping regex substring checks for exact-output assertions and replacing timing-based concurrency checks with new mechanisms — a human pass to confirm no coverage was silently narrowed and to coordinate with the nine open PRs it conflicts with would still be worthwhile.

What was reviewed:

  • The run()/sorted()/finished() helpers and every call site — the exact sorted-line comparisons are strictly tighter than the old toMatch/antipattern pairs, and stderr is now asserted everywhere it was previously ignored.
  • The rendezvous script (bounded 10s poll, no fixed sleep) and the write.js-vs-cat dependency-order asymmetry — both replace 100ms/300ms sleeps with observable conditions.
  • lastFrame() parsing of the \x1b[?2026h synchronized-update marker and the Windows non-TTY branch in the elide tests; seq/cat/echo are Bun-shell builtins so the Windows path holds.
  • The length-3 cycle fix (dep2 was previously a duplicate dep1), the [run] bun = true tests staying serial per the shim-directory race note, and resource cleanup (using on every tempDir, detached-child pids reaped in both output-timing tests).
Extended reasoning...

Overview

This PR is a full rewrite of test/cli/run/filter-workspace.test.ts (1291 → 999 lines, every one of the ~88 tests touched). It replaces spawnSync + toMatch(/regex/) helpers with an async Bun.spawn wrapper and exact sorted-line/stderr assertions, moves almost everything into describe.concurrent, drops the 100ms/300ms sleep-based interleave/order checks in favor of a filesystem rendezvous (bounded poll) and a runtime-start-vs-cat asymmetry, and rewrites the --elide-lines tests to compare the terminal renderer's final synchronized-update frame exactly instead of grepping the byte stream. Fixtures are trimmed (unused index.js/sleep.js removed; seq 1 20 replaces a 20-line bun script). No runtime code is changed.

Security risks

None. The change is test-only, spawns only the local bunExe() against temp-dir fixtures, contacts no network, and touches no auth/crypto/permissions paths.

Level of scrutiny

Moderate-to-high, driven entirely by size and coverage semantics rather than risk. REVIEW.md's "never silently weaken, skip, or delete an existing test" rule applies to every case here since each was replaced rather than edited in place — I traced the old target_pattern/antipattern pairs into the new exact sorted(stdout).toEqual(finished(...)) shapes and found them uniformly tighter (the exact list subsumes both the positive and negative regex checks), but a maintainer should independently spot-check that the pinned error/warning strings, the --parallel renderer format, and the lastFrame output are contracts they're comfortable locking down. The change also lands squarely on the "large" side of the approval guidelines.

Other factors

The PR description states this rewrite conflicts with nine open PRs that add cases to the same file — merging needs human coordination on sequencing. The changed file is not covered by CODEOWNERS. There are no prior reviews or outstanding objections in the timeline. The author reports 23 debug Linux runs and 5 Windows release runs green, plus USE_SYSTEM_BUN=1 passing on both platforms; the harness conventions (bunExe/bunEnv, tempDir with using, Promise.all pipe drain, stdout-before-exitCode ordering, normalizeBunSnapshot) are followed throughout, and the four [run] bun = true shim tests are deliberately kept serial with a comment naming the debug-build shim-directory race (#37384). The bug hunt ran to a dry streak with no findings and no ruled-out candidates.

@robobun

robobun commented Sep 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Update: #41197 is closed in favor of #41325. #41325 rewrites the terminal renderer that this file pins. The status moves to the header line, for example dep0 script $ exit 0 [<time>], and the └─ Done in <time> footer goes away. Whichever PR lands second needs to update those assertions.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant