|
10 | 10 | shellcheck: |
11 | 11 | runs-on: ubuntu-latest |
12 | 12 | steps: |
13 | | - - uses: actions/checkout@v6 |
| 13 | + - uses: actions/checkout@v6.0.2 |
14 | 14 | - name: Shellcheck |
15 | 15 | run: shellcheck lib/cache-restore.sh lib/cache-save.sh |
16 | 16 |
|
|
23 | 23 | actionlint: |
24 | 24 | runs-on: ubuntu-latest |
25 | 25 | steps: |
26 | | - - uses: actions/checkout@v6 |
| 26 | + - uses: actions/checkout@v6.0.2 |
27 | 27 | - name: Run actionlint |
28 | 28 | run: | |
29 | 29 | bash <(curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) |
|
32 | 32 | integration-test: |
33 | 33 | runs-on: ubuntu-latest |
34 | 34 | steps: |
35 | | - - uses: actions/checkout@v6 |
| 35 | + - uses: actions/checkout@v6.0.2 |
36 | 36 |
|
37 | 37 | - name: Restore against non-existent cache dir (cold start) |
38 | 38 | id: restore-cold |
@@ -346,6 +346,9 @@ jobs: |
346 | 346 | || (echo "Existing-entry guard failed: expected 'first', got '$content'" && exit 1) |
347 | 347 | echo "Sequential idempotent save: OK" |
348 | 348 |
|
| 349 | + # The "v2:" prefix in these marker assertions is the canonical marker |
| 350 | + # version from lib/cache-restore.sh (MARKER_VERSION). If that constant |
| 351 | + # is ever bumped, the literals below must be bumped in lockstep. |
349 | 352 | - name: Restore writes v2 marker |
350 | 353 | run: | |
351 | 354 | mkdir -p /tmp/test-marker-src |
@@ -425,3 +428,66 @@ jobs: |
425 | 428 | [ "$(cat /tmp/local-cache/entries/concurrent-restore-key/data.txt)" = "shared" ] \ |
426 | 429 | || (echo "Modifying A corrupted cache entry" && exit 1) |
427 | 430 | echo "Concurrent restore isolation: OK" |
| 431 | +
|
| 432 | + # A second prefix restore against an already-populated target must hit |
| 433 | + # the marker-skip branch in cache-restore.sh (is_current on the |
| 434 | + # is_exact="false" path). Existing tests cover exact-hit-skip and |
| 435 | + # prefix-match-first-time, but not this second-call-is-skipped case — |
| 436 | + # which is the steady-state behavior for the README's restore → install |
| 437 | + # → save pattern when the caller's exact key is slightly newer than the |
| 438 | + # fallback. If this branch regresses, the restore silently starts doing |
| 439 | + # full rsync work on every invocation instead of constant-time work. |
| 440 | + - name: Second prefix restore against populated target is a marker-skip |
| 441 | + run: | |
| 442 | + mkdir -p /tmp/test-prefix-marker-src |
| 443 | + echo "v1-content" > /tmp/test-prefix-marker-src/data.txt |
| 444 | + sh lib/cache-save.sh /tmp/test-prefix-marker-src prefix-marker-v1 /tmp/local-cache |
| 445 | +
|
| 446 | + # First prefix restore populates the target with marker "v2:prefix-marker-v1". |
| 447 | + # The caller's exact key (v2) doesn't exist; restore-keys "prefix-marker-" |
| 448 | + # resolves to the only matching entry (v1). |
| 449 | + sh lib/cache-restore.sh /tmp/test-prefix-marker-dst prefix-marker-v2 /tmp/local-cache "prefix-marker-" |
| 450 | + marker=$(cat /tmp/test-prefix-marker-dst/.local-cache-restore) |
| 451 | + [ "$marker" = "v2:prefix-marker-v1" ] \ |
| 452 | + || (echo "Expected marker 'v2:prefix-marker-v1' after first prefix restore, got '$marker'" && exit 1) |
| 453 | +
|
| 454 | + # Modify the restored content — if the second restore skips via |
| 455 | + # marker-match, this modification survives (no rsync overwrite). |
| 456 | + # Same pattern as the "Second restore with same key is skipped" test, |
| 457 | + # but on the prefix-resolution branch. |
| 458 | + echo "modified-between-restores" > /tmp/test-prefix-marker-dst/data.txt |
| 459 | + sh lib/cache-restore.sh /tmp/test-prefix-marker-dst prefix-marker-v2 /tmp/local-cache "prefix-marker-" |
| 460 | + content=$(cat /tmp/test-prefix-marker-dst/data.txt) |
| 461 | + [ "$content" = "modified-between-restores" ] \ |
| 462 | + || (echo "Second prefix restore did not hit marker-skip: expected 'modified-between-restores', got '$content'" && exit 1) |
| 463 | + echo "Prefix restore marker-skip: OK" |
| 464 | +
|
| 465 | + # When a caller runs the README's canonical pattern (restore → install |
| 466 | + # → save on the same path) and the restore was a prefix hit, the target |
| 467 | + # ends up with a .local-cache-restore marker pointing at the matched |
| 468 | + # entry. The save step must exclude that marker from the new entry on |
| 469 | + # disk, or else every cache entry that was ever populated via a prefix |
| 470 | + # hit permanently carries a stale marker for the entry it was cloned |
| 471 | + # from. Harmless at runtime but pollutes cache entries with stale |
| 472 | + # metadata. |
| 473 | + - name: Save after prefix restore excludes restore marker |
| 474 | + run: | |
| 475 | + mkdir -p /tmp/test-save-exclude-src |
| 476 | + echo "source" > /tmp/test-save-exclude-src/data.txt |
| 477 | + sh lib/cache-save.sh /tmp/test-save-exclude-src save-exclude-v1 /tmp/local-cache |
| 478 | +
|
| 479 | + # Prefix restore leaves the marker file in the target. |
| 480 | + sh lib/cache-restore.sh /tmp/test-save-exclude-dst save-exclude-v2 /tmp/local-cache "save-exclude-" |
| 481 | + [ -f /tmp/test-save-exclude-dst/.local-cache-restore ] \ |
| 482 | + || (echo "Expected marker in target after prefix restore" && exit 1) |
| 483 | +
|
| 484 | + # Save under the caller's exact key. The new entry must not contain |
| 485 | + # the stale marker from the prefix hit. |
| 486 | + sh lib/cache-save.sh /tmp/test-save-exclude-dst save-exclude-v2 /tmp/local-cache |
| 487 | + [ -d /tmp/local-cache/entries/save-exclude-v2 ] \ |
| 488 | + || (echo "Expected new entry save-exclude-v2" && exit 1) |
| 489 | + [ ! -f /tmp/local-cache/entries/save-exclude-v2/.local-cache-restore ] \ |
| 490 | + || (echo "rsync did not exclude marker — new entry contains stale marker from prefix source" && exit 1) |
| 491 | + [ -f /tmp/local-cache/entries/save-exclude-v2/data.txt ] \ |
| 492 | + || (echo "Expected data.txt in new entry" && exit 1) |
| 493 | + echo "Save excludes restore marker: OK" |
0 commit comments