Skip to content

test(compile): lock in auto-opt freshness on runtime/stdlib source edits - #7155

Merged
proggeramlug merged 2 commits into
PerryTS:mainfrom
jdalton:fix/auto-opt-freshness-source-hash
Aug 1, 2026
Merged

test(compile): lock in auto-opt freshness on runtime/stdlib source edits#7155
proggeramlug merged 2 commits into
PerryTS:mainfrom
jdalton:fix/auto-opt-freshness-source-hash

Conversation

@jdalton

@jdalton jdalton commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Someone editing the Perry runtime or standard library needs perry compile to pick up that edit. The failure mode this guards against is the worst kind: the compile succeeds, prints nothing unusual, and produces a binary built from the code as it was before the edit. Anyone debugging in that state is reading output from a build that does not match their source, and the only way out is knowing to delete a cached archive by hand.

That guarantee already holds on main. This pull request is test coverage only, so that it cannot quietly stop holding. It adds two regression tests and changes no product code.

Why staleness is possible at all, and how it is already prevented

perry compile caches compiled archives of the runtime and standard library under target/perry-auto-<hash>/release/, so that a normal compile does not rebuild them every time. Deciding whether a cached archive is still usable is the freshness gate's job.

The obvious way to decide is to compare modification times: if the source is newer than the archive, rebuild. That test is wrong more often than it looks, because modification times can lie. A git checkout, a cp -p, or a continuous-integration cache restore can all hand back source files whose timestamps are older than a cached archive built from different content.

The gate therefore keys the build stamp on a content fingerprint of every source tree that ends up in the archives, computed by auto_optimized_source_fingerprint. That function was added in #5930 to close #5892. Because the fingerprint is over content rather than timestamps, a source edit invalidates the cache even when the timestamps say otherwise.

Confirming the reported symptom no longer reproduces

The report was that after editing crates/perry-runtime, perry compile produced a binary from the old runtime, and the only workaround was to manually rm the cached archive. That was hit during garbage-collector iteration work.

Checked on origin/main with a prebuilt perry binary, which is after #5930 landed. The bug does not reproduce:

  • Editing a file under crates/perry-runtime/src/gc/ and running perry compile prints auto-optimize: rebuilding runtime+stdlib and rebuilds the archive, with no manual rm needed.
  • Making the same edit but backdating the file's modification time to 2020, so it is older than the archive and simulates a cache restore, still rebuilds. That rebuild is driven by the content fingerprint, which is exactly the case a timestamp-only gate would miss.
  • Rebuilding with no edit at all is a fast no-op and leaves the archive's modification time untouched, so the cache hit path still works.
The coverage gap this closes

The correctness fix is already in place; what was missing was a test that pins it for the runtime and standard-library crates specifically. Those are the crates a runtime developer edits most often, and they were the least covered.

The existing fingerprint tests only exercised the routed perry-ext-* crates. The one test that did touch the runtime, ..._are_stale_when_runtime_source_is_newer, exercises the modification-time half of the gate rather than the content fingerprint. So the property that actually saved the reported case had no direct test behind it.

The two new tests

source_fingerprint_tracks_runtime_and_stdlib_content_not_mtimes asserts that a content edit to either perry-runtime or perry-stdlib rotates the fingerprint, and that rewriting byte-identical content does not. The second half is what keeps the no-edit fast path honest.

runtime_source_edit_rotates_build_stamp_and_fails_freshness asserts that a runtime edit rotates the build stamp, so that archives stamped for the pre-edit source correctly fail auto_optimized_archives_are_fresh.

cargo test -p perry --bins optimized_libs reports 25 passed and 0 failed.

Note on the performance trade-off

The fingerprint reads the runtime and standard-library source content on every compile, and it is deliberately not cached by modification time. A cache keyed on modification times would reintroduce precisely the cache-restore staleness that #5930 fixed.

The read is small relative to the cost of a compile, and the no-edit path remains a fast no-op, which the new tests assert.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@proggeramlug, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c6de65c-181d-4c7b-8c95-b353c6935d46

📥 Commits

Reviewing files that changed from the base of the PR and between 75d7979 and d6ce397.

📒 Files selected for processing (2)
  • changelog.d/7155-auto-opt-freshness-runtime-source-test.md
  • crates/perry/src/commands/compile/optimized_libs/tests.rs
📝 Walkthrough

Walkthrough

The change adds regression coverage for content-based freshness of optimized runtime and standard-library archives. Identical-byte rewrites preserve fingerprints, while source edits rotate fingerprints and invalidate archives built from the previous source state.

Changes

Optimized library freshness

Layer / File(s) Summary
Source fingerprint behavior
crates/perry/src/commands/compile/optimized_libs/tests.rs
Tests verify that identical-byte rewrites preserve perry-runtime and perry-stdlib fingerprints, while source edits change them.
Archive freshness invalidation
crates/perry/src/commands/compile/optimized_libs/tests.rs, changelog.d/7155-auto-opt-freshness-runtime-source-test.md
An end-to-end test verifies that runtime source edits change the build stamp and reject archives stamped for the previous source state. The changelog documents the behavior and regression coverage.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • PerryTS/perry#7021: Related auto-optimized library freshness and cache validation changes.
  • PerryTS/perry#7064: Related validation of reused optimized runtime and standard-library artifacts after source changes.

Suggested labels: tooling

Suggested reviewers: proggeramlug

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The changes test runtime and stdlib freshness, but linked issue [#5892] requires resolving a full cargo-test hang in an HTTP callback test. Fix the requirements in [#5892], or replace the linked issue with the issue that tracks auto-optimize freshness regression coverage.
Out of Scope Changes check ⚠️ Warning The PR adds auto-optimize freshness tests, which are unrelated to the linked issue [#5892] about a full cargo-test hang. Remove the unrelated freshness tests or update the linked issue and PR scope to cover auto-optimize freshness.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly identifies the added regression coverage for compile freshness after runtime and standard-library source edits.
Description check ✅ Passed The description clearly explains the purpose, implementation context, tests added, verification result, and performance trade-off.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@jdalton
jdalton force-pushed the fix/auto-opt-freshness-source-hash branch from 975db96 to 75d7979 Compare August 1, 2026 01:31
jdalton and others added 2 commits August 1, 2026 10:57
The auto-optimize build stamp is keyed on a content fingerprint of every
source tree that lands in the runtime/stdlib archives (added in PerryTS#5930 to
close PerryTS#5892), so a source edit invalidates a cached
target/perry-auto-<hash> archive even when mtimes lie (git checkout, cp -p,
CI cache restore). That guarantee had direct test coverage only for the
routed perry-ext-* crates, not for perry-runtime / perry-stdlib themselves --
the crates a runtime dev edits most, and the ones behind the GC-iteration
"perry compile silently reused a stale libperry_runtime.a" trap.

Add two regression tests:
- source_fingerprint_tracks_runtime_and_stdlib_content_not_mtimes: a content
  edit to perry-runtime or perry-stdlib rotates the fingerprint, while
  rewriting identical bytes does not (keeps the no-edit rebuild a fast no-op).
- runtime_source_edit_rotates_build_stamp_and_fails_freshness: ties it to the
  gate the driver consults -- a runtime edit rotates the build stamp so
  archives stamped for the pre-edit source fail auto_optimized_archives_are_fresh,
  no manual rm needed.
Both tests added by this PR passed with their subject sabotaged, which is
the "a gate must assert its subject was live" trap CLAUDE.md documents.

Measured before this commit:
- Deleting the build-stamp comparison in `auto_optimized_archives_are_fresh`
  (`Ok(stamp) if stamp == expected_build_stamp` -> `Ok(_)`) left
  `runtime_source_edit_rotates_build_stamp_and_fails_freshness` GREEN. The
  test planted the archives BEFORE the source edit, so the edited file's own
  mtime was newer than the archives and the mtime half of the gate rejected
  them on its own — the stamp never had to be consulted.
- Making the source fingerprint content-blind (hash the path, skip
  `fs::read`) left the same test GREEN, because its "content edit" created a
  NEW file (`crates/perry-runtime/src/gc.rs`) and a path-set-only hash still
  rotates when a path is added.

Two surgical changes, no new assertions:
- Both tests now edit an EXISTING runtime source file in place
  (`crates/perry-runtime/src/lib.rs`) instead of adding `gc.rs`, so the
  assertion rides on the file-content hash, not on the path set.
- `runtime_source_edit_rotates_build_stamp_and_fails_freshness` plants the
  archives + stamp AFTER the edit, so every source is older than them and
  the mtime half votes "fresh". The stamp mismatch is then the only thing
  that can answer "stale" — which is also the real scenario the content
  fingerprint exists for (git checkout / cp -p / CI cache restore hand back
  sources whose mtimes never advance past a cached archive).

Verified red-then-green: with either sabotage applied both tests now FAIL;
with the tree clean `cargo test --bins -p perry optimized_libs` is 28/28.
@proggeramlug
proggeramlug force-pushed the fix/auto-opt-freshness-source-hash branch from 75d7979 to d6ce397 Compare August 1, 2026 09:06
@proggeramlug
proggeramlug merged commit b68fd83 into PerryTS:main Aug 1, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Full cargo-test hangs in issue_4903_listen_callback_deferred on every SHA containing #5831 (180-min timeout; blocks nightly + release tag gates)

2 participants