Skip to content

test --changed: bind test files to the module graph by position, not by path - #46583

Open
robobun wants to merge 6 commits into
mainfrom
robobun/75220310/test-changed-symlinked-tests
Open

robobun wants to merge 6 commits into
mainfrom
robobun/75220310/test-changed-symlinked-tests

Conversation

@robobun

@robobun robobun commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • bun test --changed skips a test file that bun test finds through a symbolic link. It prints --changed: 1 changed file, but no test files are affected and exits 0.
  • Cause: filter (ChangedFilesFilter.rs:251 on main) matches test files to the module graph by path. bun test names the file through the link, the graph by its real path.

Fix

  • scan_module_graph_from_cli reports the graph node of each entry point by position. The path lookup is gone.
  • A file with several test paths is selected under one: its real path, as on main, or else the smallest. Discovery order has no effect.
  • filter frees the scan's log, which has a message for each unresolved test file.
  • Verified: test/cli/test/test-changed.test.ts (20 new cases, 13 fail on main). Self-reviewed twice: 62 of 70 concerns addressed (Notes).

Background

  • --changed gives all test files to the bundler as entry points, scans their imports, then walks backward from each changed file.
  • Considered: a realpath of each test file in filter (keeps the path compare), real paths in test discovery (filters match the link name, positional paths have no discovery entry).

Downsides

  • Cost without a link: none (132 instructions less per test file, bun build +0). Binary: +1,792 bytes.
  • A test that main skipped now runs, so a green --changed run can fail.
  • Not fixed: a link that changes its target, a link to a file with no JS extension, --preserve-symlinks (see Notes).
Notes

Source of the report. An internal fuzz run of bun test --changed. No user reported it and there is no issue.

Repro.

git init -q -b main . && mkdir -p shared-tests packages/a/tests
echo 'export const limit = 1;' > shared-tests/limits.ts
printf 'import { test, expect } from "bun:test";\nimport { limit } from "./limits";\ntest("the limit", () => { expect(limit).toBe(1); });\n' > shared-tests/contract.test.ts
ln -s ../../../shared-tests packages/a/tests/shared
git add -A && git -c user.name=t -c user.email=t@example.com commit -q -m init
echo 'export const limit = 2;' > shared-tests/limits.ts
cd packages/a
bun test            # exit 1: 0 pass, 1 fail
bun test --changed  # main: exit 0, "--changed: 1 changed file, but no test files are affected"

Forms that main skips and this PR selects (each is a case in the test file):

  • The test directory is a link: a change to an import, a change to the test file, [test] root in bunfig.toml, two selected files with --parallel.
  • A positional path through a link: ./l/t.test.ts, ./l, an absolute path, and an absolute path through a link above the repository (no link in the repository).
  • The test file is a link: a change to an import, a change to the target.
  • Two links to a directory whose real path is outside the walk.
  • --changed --watch: the restart after a change to the import of a linked test.
  • By code reading only, not run: a Windows working directory reached through a junction, and ./A.test.ts for a.test.ts on a volume that ignores case.

Mechanism. enqueue_entry_item already returned the source index of each entry point, and enqueue_entry_points_normal dropped it. The body of that loop is now enqueue_entry_point_normal::<SCAN>. bun build and Bun.build call it with SCAN = false. The scan calls it with SCAN = true, which also finds the index when an earlier entry point is the same file, and marks that entry point as a duplicate. An entry point that does not resolve keeps None, so the positions cannot shift.

A file with several test paths. A walk can find one file through a link and under its real path, or through two links. Only when the scan marks a duplicate, filter fixes one path for each file (kept_paths): the test path that is equal to the path in the graph, or else the smallest test path by strings::order, the order that --shard sorts by. The graph selects the file under that path only. A path that git names is selected by its name, as before. Where main selected a file, this PR selects the same path. A path that the command line names twice is selected twice, as on main.

The second version of this PR kept the path that the walk found first. Below its root the walk follows the order of the directory listing, and --shard sorts the selected paths. With a link a to a directory z beside m.test.ts, that version ran z/limit.test.ts in shard 1/2 where main runs m.test.ts. The documentation says that the shards "cover each file exactly once with no coordination" (docs/test/parallel.mdx). That version also selected ./t.test.ts ./t.test.ts once (1/2) where main selects it twice.

The log of the scan. Transpiler::resolve_entry_point adds a message to the log for an entry point that does not resolve. The log of the scan is in the CLI arena, so nothing dropped it: LeakSanitizer reports 685 bytes in 2 allocations, on main too (bun test --changed ./a.test.ts ./package.json/nope.test.ts). filter now calls clear_and_free on it.

--preserve-symlinks. The scan never has the flag (src/bundler/options.rs:1759), and the run reads the real paths that the scan left in the directory cache of the resolver. So after the scan the run loads one module for a file found under two paths. The second version of this PR selected both paths under the flag: it printed running 2/3 test files and then Ran 1 test across 2 files. That exception is removed. The selection under the flag is now the selection without it, as on main. #46007 makes the bundler read the flag and #44835 makes the run forget what the scan cached. What the flag selects is a decision for after those two.

Measurements. Release builds of 5c6d4e2, the merge-base at that time, and of this PR at ca917e2. The later merge of main (8a0e49e) changed two calls in the entry loop (#46834) and nothing in filter. The numbers are not measured again after it. gdb 16.3 (catch syscall, breakpoints, and a perf_event_open instruction counter on the main thread). strace, perf, valgrind and bloaty are not installed in the build container.

  • Run with no link: +0 access, +0 newfstatat, +0 open, no readlink. openat and getdents64 move by 1 between runs of the same binary.
  • Allocations inside filter, without the scan: +0. One mi_zalloc became one mi_malloc (the vector with one slot for each test file).
  • Instructions for each test file, no link: filter after the scan 1,087 to 942. The entry loop of the scan: +14. Net: 132 less.
  • A file found twice (3 test files, one of them under two paths): one more allocation, a slot for each source. filter after the scan: 2,838 to 2,628 instructions.
  • Instructions for each changed path: 291 to 288 in the graph, 174 to 174 outside it.
  • bun build with 16 entry points: 12,330 to 12,330 instructions for each entry point in the entry loop. diff -r of the two output directories: identical.
  • size text: 107,635,854 to 107,637,646 (+1,792). filter 8,239 to 8,357 bytes, kept_paths 736 bytes. scan_module_graph_from_cli 843 to 1,811 bytes, because it now holds the entry loop. enqueue_entry_item and append_paths do not change.
  • LeakSanitizer, release-asan build with the options of the test runner: all 41 cases of the test file pass. The whole file, on 4 CPUs: 12 of 12 runs pass on the release build and 5 of 5 on the release-asan build.
  • Each clause of the rule has a case that fails without it. With no rule, 5 cases fail. With the smallest path in every case, 2 fail. With the first path in place of the smallest, 1 fails.

Limits, not fixed here.

  • A change that points a tracked link at another target, or that replaces a file with a link. git names the link and the graph names the target, so the importers are not selected. The first version of this PR followed such a link through the resolver's directory cache. The self-review showed that the result was not deterministic: with a link and its target both imported, the same tree and diff selected the test in 24 of 60 runs. It also selected every importer of a directory behind a new link. That half is removed. A deterministic version needs an lstat where the changed set is built, and a decision on what a changed directory link selects.
  • A test file that is a link to a file with no JS or TS extension (t.test.ts -> real/x.bak). The run parses it as a test. The scan reads it with the file loader and sees no import. bun test fails and bun test --changed prints "no test files are affected", on main and with this PR. The loader of the scan is the subject of test --changed: follow bare specifiers that resolve to local files #44836.
  • --preserve-symlinks: see above.
  • A scan that fails (for example --tsconfig-override with a file that does not exist) prints --changed: failed to build module graph (BuildFailed); running all tests. On that path main keeps the scan transpiler and its log (LeakSanitizer: about 33 KB in 921 allocations) and the 8 threads of the scan's pool (14 threads during the run, 6 without). This PR does not change that path. bundler: make the enqueue functions unable to fail and check every graph.ast append #39855 has the teardown of the pool for the error exits of scan_module_graph_from_cli, and bundler: remove unsafe from bundle_v2, ParseTask, LinkerContext and the bundler thread pool #40438 adds Drop for BundleV2.
  • With --parallel the file header shows the path that discovery found. A serial run shows the real path. This PR does not change that.
  • From a subdirectory, git ls-files --others lists only the subtree of the working directory (ChangedFilesFilter.rs:542), so an untracked file outside it is not in the changed set.
  • A tsconfig paths alias under packages = external returns before the real-path step of the resolver (resolver.rs:1199-1232). A change to a file that a test reaches only through an alias that crosses a link selects nothing, and bun build --packages external bundles that file twice. test --changed: follow bare specifiers that resolve to local files #44836 changes this path.
  • On Windows, a subst or mapped drive can make git's spelling of the root differ from the graph's.
  • A change to tsconfig paths, package.json imports, a bunfig preload or .env.test selects nothing. test --changed: follow bare specifiers that resolve to local files #44836 asks the same question.

The same skip in other commands, not changed here.

  • bun lint --changed and bun lint --staged skip a changed file that the run reaches through a link to a directory. In the repro above, with debugger; added to shared-tests/limits.ts: bun lint in packages/a reports it, and bun lint --changed=HEAD prints "No changed files to lint" and exits 0.
  • bun build --app with two route files that are links to one file writes the module of another file for 7 of 8 routes and exits 0. The two bake entry loops (enqueue_entry_points_dev_server, enqueue_entry_points_bake_production) drop the index that the scan now keeps.

Other open PRs on these lines. #46007 (the bundler reads preserve_symlinks), #44835 (the run forgets what the scan cached), #44836 (bare specifiers that resolve to local files), #46262 (HTML imports and every copy of a changed file; its notes list the linked test file as a gap with no PR), #45938 (barrel mode off for the scan), #44487 (a directory link back to a directory of the walk), #43361 (a test file listed once when two path arguments select it: the case "a test file named twice is selected twice" then becomes 1/1), #41030 (one committed repository for each case of this test file: its copy needs verbatimSymlinks for the link cases), #39855 and #40438 (teardown of a bundle). #45248 deletes PathToSourceIndexMap::get_path, so the lookup of a duplicate entry point calls get(path.text): it compiles before and after that PR.

Windows. 13 of the 18 link cases need only a link to a directory. They run on Windows with a junction that .gitignore hides from git. The 5 cases with a link to a file do not run there.

vitest, the model for --changed (pristine installs, node 26.3.0): 4.1.9 selects a linked test when one of its imports changes and skips it when the test file changes. 5.0.5 skips both.

Self-review. Two rounds, 35 concerns each.

  • Round 1: 21 concerns were about a half that followed a changed link. They are addressed by its removal. 8 more are addressed. 6 are not done: a line that clears preserve_symlinks for the scan (no effect until CLI: fixes for bun run, flags, bunfig.toml, .env, --filter, bun init and completions #46007), a seeded generator of link layouts, a case for a Windows working directory behind a junction, a case for a volume that ignores case, tracking issues for the limits, and the order of landing relative to test --changed: follow bare specifiers that resolve to local files #44836.
  • Round 2: 33 addressed. The path of a file found twice no longer depends on the order of discovery, and a case with --shard shows it. The --preserve-symlinks exception and its case are gone. A path named twice is selected twice again. Two links with no real path select the smallest path. The link cases pin the status line, the files that ran and the exit code in one assertion, and make the repository with three awaited git processes. A commit that released the scan when it fails is not in this PR: bundler: make the enqueue functions unable to fail and check every graph.ast append #39855 has the teardown, and the check of that commit (a count of pool threads) failed on the fixed build under load. The limits, the two other commands and the other open PRs are listed above.
  • Round 2, rejected: no rule at all (each path of an affected file selected, as a full run lists them). A file under its real path and a link would then run once for each path with --parallel or --isolate, where main runs it once.
  • Round 2, not done: a seeded generator in the tree. It is a test-only change of about 300 lines that three open --changed PRs could share.

Suites run. Debug build: test/cli/test/test-changed.test.ts (41 pass). Release build of this PR: test-changed (41 pass), test-shard (29 pass), parallel (46 pass, 1 fail: "--parallel --dots prints one status character per test" also fails on the release build of the merge-base here, 4 of 4 runs). Release-asan build with LeakSanitizer: test-changed (41 pass). Earlier, on the first commits of this PR, which have the same bundle_v2.rs: test/bundler/ cli, bundler_files, bundler_html, bundler_plugin, bundler_edgecase, bun-build-api, cargo clippy for bun_bundler and bun_runtime, and cargo check for x86_64-pc-windows-msvc and aarch64-apple-darwin.


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/test/test-changed.test.ts

A test file that the walk finds through a symbolic link has its real path
in the module graph, so the lookup of the walk path found no node and the
test was never selected. The scan now reports the source index of each
entry point by position, and the lookup by path is gone.

The scan never keeps the paths of links: git names a file by its real
path.
@robobun

robobun commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:10 AM PT - Oct 27th, 2026

✅ @robobun, your commit ac5beadfe921360c01fa6809ad033d4638f6dae4 passed in Build #133524! 🎉


🧪   To try this PR locally:

bunx bun-pr 46583

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

bun-46583 --bun

@robobun

robobun commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Reproduced on a release build of main (5c6d4e2). In a repository where packages/a/tests/shared is a link to shared-tests/, a change to shared-tests/limits.ts makes bun test in packages/a exit 1. bun test --changed prints --changed: 1 changed file, but no test files are affected and exits 0. With this PR it runs the test and exits 1. The commands are in the Notes of #46583.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 0dcecdf and c988981.

Files selected for processing: 3
  • src/bundler/bundle_v2.rs
  • src/runtime/cli/test/ChangedFilesFilter.rs
  • test/cli/test/test-changed.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Walkthrough

The CLI graph scan now records a source index for each entry point. The bun test --changed filter uses those results to select paths, including when paths resolve to the same graph source. Tests cover linked paths, unresolved entries, duplicate arguments, and watch-mode reruns.

Changes

Changed-test scan

Layer / File(s) Summary
Collect entry-point source indexes
src/bundler/bundle_v2.rs
The CLI graph scan accepts one output slot per entry point. Scan-specific enqueueing records indexes for resolved entries, including entries already in the graph.
Map changed-test entries to graph nodes
src/runtime/cli/test/ChangedFilesFilter.rs
The filter uses the scan’s per-entry-point results instead of mapping paths after the scan. For paths that share a graph source, it prefers the graph-path match or otherwise the lexically smallest path. Changed test paths remain selected.
Test linked-path selection
test/cli/test/test-changed.test.ts
Tests cover duplicate arguments, linked files and directories, unresolved paths, path variants, shard selection, and watch-mode reruns after an imported file changes.

Suggested reviewers: jarred-sumner, dylan-conway, alii

Priority: Normal

Merge Risk: Minimal · up to c9889

No actionable merge-blocking issue was established; the change is ready for normal checks.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: binding test files to module-graph entries by position instead of path for test --changed.
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.
Description check Passed The description follows the repository template with both required sections, "What does this PR do?" (Problem, Fix, Background, Downsides) and "How did you verify your code works?" (test results, repr…
Full details: Description check

Explanation

The description follows the repository template with both required sections, "What does this PR do?" (Problem, Fix, Background, Downsides) and "How did you verify your code works?" (test results, repro, measurements, suites run). It states the cause, the fix, the verification, and the known limits. The only gap is that the verification relies on a platform-specific test file whose Windows-only cases are deferred to CI, which the bot evidence flags as "no test proof" on this machine. That is disclosed in the description, so the description is complete.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline findings, I checked the SCAN=false path of the split entry loop against the old one: every continue became return Ok(None), ? propagation and is_entry_point: true are unchanged, and both bun build callers of enqueue_entry_points_normal are untouched, so bun build/Bun.build behavior is preserved. The scan's duplicate-file fallback keys by the same path.text that enqueue_entry_item checked before its early return, and the scan error path still drains the pool. I also looked at whether the watch test's " across " needle could match the first run's summary; it slices from before, so it cannot.

Extended reasoning...

The change splits the bundler's entry-point enqueue loop into a reserve step plus a const-generic per-entry helper, adds a positional out-slice variant for the --changed scan, and rewires ChangedFilesFilter.rs to bind test files to graph nodes by position instead of by path, with a table-driven symlink suite and a watch case added to test-changed.test.ts. It touches no security-sensitive surface (no auth, injection, or data exposure), only CLI test selection and the bundler's entry-point setup. The findings posted inline (a behavior change for files found twice under --parallel/--isolate, a dead preserve_symlinks line, and the watch test's timeout) are what keep this from an approve; the refactor itself traced as behavior-preserving for the non-scan callers.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟡 src/runtime/cli/test/ChangedFilesFilter.rs — Monorepo users running bun test --changed --parallel (or --isolate) from a root that reaches a shared test directory both directly and through a link now get that test file run twice; on the base branch it ran once. The scan's SCAN fallback at src/bundler/bundle_v2.rs:3650-3655 returns the same source index for the link-path slot, so both slots pass the affected check at src/runtime/cli/test/ChangedFilesFilter.rs:295-296 and both copies are kept. Fix: dedupe the kept list by source index (or real path) before returning so a file selected under two names runs once; keeping it is a user-visible regression for --changed --parallel, where runtime and failure counts double.

    Why this was flagged

    Trigger: a checkout where bun test walks a test file under its real path and again through a directory link (the PR's own row at test/cli/test/test-changed.test.ts:528-538), run with --changed --parallel or --changed --isolate. On the base, slot_to_source was filled by path_to_index.get(tf) (old ChangedFilesFilter.rs:249-251), which missed the link-path slot, so only the real-path slot was kept and the file ran once. Now enqueue_entry_point_normal::<true> at src/bundler/bundle_v2.rs:3650-3655 looks the duplicate up in path_to_source_index_map and writes the same index into the second slot; ChangedFilesFilter.rs:295-296 keeps both. With --parallel, test_command runs each kept path in its own worker, so the same tests execute twice and any failure is reported twice. The dismissal relied on the author's statement that plain bun test already lists the file twice; that is a different command, and for the --changed population this is a strict change from the base. Remedy: collapse kept slots that share a source index before returning test_files[0..write].

    Verification: Triggered by bun test --changed --parallel (or --isolate) where the walk reaches the same test file both directly and through a link. The SCAN fallback at src/bundler/bundle_v2.rs:3650-3655 gives the link-path slot the same source index as the real-path slot, so keep at src/runtime/cli/test/ChangedFilesFilter.rs:292-301 is true for both slots and both run; on the base only the real-path slot ran.

Comment thread test/cli/test/test-changed.test.ts Resolved
A test file that the walk finds under two paths is one module for the
run, unless the run keeps the paths of links. The scan marks the later
entry points of a file, and only the first one is selected through the
graph.

The log of the scan is in the CLI arena, so a message for a test file
that does not resolve was never freed.
@robobun

robobun commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. What changed in 94c2831:

  • A file found under two paths ran twice with --parallel and --isolate. Fixed. The scan now marks a later entry point of a file that is already in the graph, and only the first one is selected through the graph. bun test --changed prints running 1/3 test files for the layout of the review again, and 2/5 with --parallel on two shared tests. With --preserve-symlinks the run loads the two paths as two modules, so both are selected there. New cases: "runs once", "run once with --parallel", "runs once when it changes", and the --preserve-symlinks one.
  • args.preserve_symlinks = None is dead today. Removed. It goes with CLI: fixes for bun run, flags, bunfig.toml, .env, --filter, bun init and completions #46007.
  • Timeout of the new watch case. Not changed. The reason is in the thread.

The same push fixes the failure on the ASAN lane: the log of the scan kept a message for a test file that does not resolve (685 bytes under LeakSanitizer, on main too). filter now frees it.

Comment thread src/bundler/bundle_v2.rs Resolved
Comment thread src/bundler/bundle_v2.rs Resolved
Comment thread src/bundler/bundle_v2.rs Resolved

@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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding, I also checked the SCAN = false instantiation of the split entry loop in src/bundler/bundle_v2.rs against the old enqueue_entry_points_normal body: the plugin, FileMap, and resolve-failure early exits land on the same continue points and the same enqueue_entry_item calls, so bun build / Bun.build behavior is unchanged. I also traced the new duplicate keep rule in ChangedFilesFilter.rs through link-first and real-path-first discovery order and with --preserve-symlinks on and off, and the zip in enqueue_entry_points_for_scan (lengths only ever match at its single caller) — none of those turned up a problem.

Extended reasoning...

The change splits the bundler's shared entry-point loop into a const-generic per-entry function used by both bun build and the --changed scan, adds a positional ScannedEntryPoint out-slice, and rewrites the keep decision in ChangedFilesFilter.rs plus a large symlink test matrix. It touches no security-sensitive surface (no auth, injection, or untrusted parsing), though it does touch the hot bun build entry loop. The one surviving finding is a pre-existing leak on the scan-failure fallback path; the dead preserve_symlinks = None line flagged last round is gone from HEAD. Because the refactor reaches a shared bundler path and the new keep rule changes which of two paths to one file is selected, a human look is still worthwhile.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟣 src/runtime/cli/test/ChangedFilesFilter.rs — nit, pre-existing: maintainers still get a leaked scan log and transpiler on the fallback path, even though the success path now frees both. When scan_module_graph_from_cli returns Err at src/runtime/cli/test/ChangedFilesFilter.rs:186, the function returns at :194 without touching scan_transpiler. The log messages that BundleV2::init or Transpiler::init added before the failure stay allocated for the life of the process. Fix: release the scan's heap-owned state on every exit of filter after the arena allocations at :137-150, calling scan_transpiler.deinit() and log clear_and_free() in the Err arm too, so the new release at :320-323 has no unpaired sibling path.

    Why this was flagged

    Trigger: bun test --changed where the scan fails after the transpiler is built, for example BundleV2::init failing or the scan log already holding an error so src/bundler/bundle_v2.rs:4655 returns Err(crate::Error::BuildFailed). filter reaches the Err arm at…

    Verification: pre-existing. Trigger: bun test --changed when BundleV2::scan_module_graph_from_cli returns Err. The Err arm at src/runtime/cli/test/ChangedFilesFilter.rs:180-194 never calls scan_transpiler.deinit() nor log_mut().clear_and_free(); both releases exist only on the success path at :313 and :315. The base branch has the identical Err arm, so merging makes nothing worse.

… of discovery

A file that several test paths name was selected under the path that the
walk found first. The walk follows the order of a directory listing, and
`--shard` sorts the selected paths, so one tree and one change gave two
shard splits on two checkouts. main does not depend on that order.

The graph now selects such a file under the path that the graph has for
it, as on main, or else under the smallest of its test paths. A path that
git names is selected by that name, as before. The `--preserve-symlinks`
exception is gone: after the scan, the run loads one module for the two
paths.
@robobun

robobun commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

ca917e2 reworks one rule of this PR, and this comment also answers the second review.

The fallback path (the finding of the second review). It is real, it is on main, and this PR does not change it.

A file found under two paths (987cf47 had this wrong). 987cf47 selected such a file under the path that the walk found first. Below its root the walk follows the order of the directory listing, and --shard sorts the selected paths.

  • With a link a to a directory z beside m.test.ts, 987cf47 ran z/limit.test.ts in shard 1/2. main runs m.test.ts there. Two checkouts that list a and z in another order would not agree on the shards.
  • bun test --changed ./t.test.ts ./t.test.ts was 1/2. main gives 2/2.
  • Now the graph selects the file under the path that the graph has for it, as main does, or else under the smallest test path. The order of discovery has no effect. New cases: the shard of a file found through a link first, two links with no real path in the walk, and a path named twice.

--preserve-symlinks. 987cf47 selected both paths under the flag and said that the run loads two modules. It does not: the scan has no such flag and the run reads the real paths that the scan cached, so the run printed running 2/3 test files and then Ran 1 test across 2 files. The exception and its case are removed. The selection under the flag is the selection without it, as on main. #46007 and #44835 change both halves, and the rule for the flag is a decision for after them.

The PR body has the new numbers (no link: 132 instructions less per test file, +1,792 bytes) and the limits.

@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.

Code review found no issues

No high-confidence issues detected in this change.

…exMap::get`

`get_path(path)` is `get(path.text)`. The other lookups in bundle_v2.rs
that have a resolved path call `get(path.text)`, and this one now does too.
@robobun

robobun commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

c988981 is a one-token change. It does not change behavior.

The lookup of a duplicate entry point called PathToSourceIndexMap::get_path(path). #45248 deletes that method. The two PRs merge with no textual conflict, so main would stop compiling after the second of them merges. The lookup now calls get(path.text), which is the body of get_path and the form of the other lookups in bundle_v2.rs. It compiles before and after #45248.

test/cli/test/test-changed.test.ts on a debug build of c988981: 41 pass.

@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.

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Merged main (8a0e49e) into this branch.

  • One conflict, in src/bundler/bundle_v2.rs. This PR moves the loop body of enqueue_entry_points_normal into enqueue_entry_point_normal<const SCAN: bool>. Main (98a4365, Fix use-after-free of BuildMessage.position.file: real lifetimes for Source, Path, ImportRecord and resolve results #46834) changed two calls in that body: resolve_entry_point takes a dot_slash buffer, and enqueue_entry_item takes &Result<'_>.
  • The resolution keeps the function and the control flow of this PR, with the two calls as on main: resolve_entry_point(entry_point, &dot_slash) and enqueue_entry_item(&resolved, ..). The FileMap call passes &file_map_result.
  • Checked by reading: the duplicate lookup for SCAN reads resolved.path_const() after enqueue_entry_item. On main that function never writes to the result, so the key is the same as before.
  • cargo check --workspace passes on the merged tree (Linux x64).
  • Test, on a local debug build that contains this merge and the main merges of five other open PRs, with --timeout 30000: test/cli/test/test-changed.test.ts (41 pass).

@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.

Code review found no issues

No high-confidence issues detected in this change.

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.

2 participants