Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ expected="$(realpath "${RUNNER_TEMP:?}")/edgezero-rust-cache/cache-restore-smoke
printf '::error::compiler probe must use its confined fixture target directory\n' >&2
exit 1
}
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=compiler-stats.sh
source "$script_dir/compiler-stats.sh"
cd "$fixture"
host=$(rustc -vV | sed -n 's/^host: //p')
[[ -n "$host" ]] || exit 1
Expand All @@ -22,7 +25,7 @@ for target in "$host" wasm32-wasip1; do
cargo build --locked --lib --target "$target"
stats=$(sccache --show-stats --stats-format json)
printf 'Compiler reuse for %s: %s\n' "$target" "$stats"
jq -e '(.stats.cache_hits.counts.Rust // 0) > 0 and (.stats.cache_errors.counts.Rust // 0) == 0 and (.stats.cache_read_errors // 0) == 0 and (.stats.cache_write_errors // 0) == 0' <<<"$stats" >/dev/null || {
compiler_hit "$stats" || {
printf '::error::expected error-free Rust compiler-cache reuse on the first build for %s\n' "$target" >&2
exit 1
}
Expand Down
10 changes: 10 additions & 0 deletions .github/actions/setup-rust-build-cache/tests/compiler-stats.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Shared sccache stats predicates for the compiler seed and reuse probe.
compiler_error_free='(.stats.cache_errors.counts.Rust // 0) == 0 and (.stats.cache_read_errors // 0) == 0 and (.stats.cache_write_errors // 0) == 0'
# Any Rust miss means some unit, possibly the one just written, was not read back.
compiler_hit() {
jq -e "(.stats.cache_hits.counts.Rust // 0) > 0 and (.stats.cache_misses.counts.Rust // 0) == 0 and $compiler_error_free" <<<"$1" >/dev/null
}
compiler_wrote() {
jq -e "(.stats.cache_writes // 0) > 0 and $compiler_error_free" <<<"$1" >/dev/null
}
20 changes: 0 additions & 20 deletions .github/actions/setup-rust-build-cache/tests/fixture.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,6 @@ fi
grep -Fxq 'fixture-id=417-1' "$WORK_DIR/seed/output" || fail 'seed does not export fixture identity'
[[ "$(grep -c 'cargo build' "$ACTION_DIR/tests/assert-compiler-reuse.sh")" == 1 ]] || fail 'reuse probe primes its own cache'
if grep -q 'cargo clean' "$ACTION_DIR/tests/assert-compiler-reuse.sh"; then fail 'reuse probe accepts same-job rebuilds'; fi
workflow="$ACTION_DIR/../../workflows/deploy-action.yml"
seed=$(yq -r '.jobs."cache-restore-seed".steps[] | select(.name == "Seed native and WASM compiler outputs") | .run' "$workflow")
# shellcheck disable=SC2016 # Assert the literal shell command in workflow metadata.
[[ "$seed" == *'cargo build --locked --lib --target "$host"'* && "$seed" == *'cargo build --locked --lib --target wasm32-wasip1'* ]] || fail 'seed job does not compile both targets'
for job in cache-restore-seed cache-restore-smoke cache-new-revision-smoke; do
JOB_ID="$job" yq -e '.jobs[strenv(JOB_ID)].steps[] | select(.name == "Install native and WASM compiler targets") | .run == "bash .github/actions/setup-rust-build-cache/tests/setup-toolchain.sh"' "$workflow" >/dev/null || fail "$job does not share its Rust toolchain"
done
# shellcheck disable=SC2016 # GitHub expressions are literal metadata contracts.
expected_identity='${{ needs.cache-restore-seed.outputs.fixture-id }}'
for job in cache-restore-smoke cache-new-revision-smoke; do
actual=$(JOB_ID="$job" yq -r '.jobs[strenv(JOB_ID)].steps[] | select(.id == "fixture") | .env.CACHE_FIXTURE_ID' "$workflow")
[[ "$actual" == "$expected_identity" ]] || fail "$job does not reuse the seed fixture identity"
done
[[ "$(yq -r '.jobs."cache-restore-seed".steps[] | select(.id == "cache") | .with."save-target"' "$workflow")" == true ]] || fail 'seed does not opt into saving'
for job in fixture-release store-free-release; do
actual=$(JOB_ID="$job" yq -r '.jobs[strenv(JOB_ID)].steps[] | select(.uses == "./.github/actions/setup-rust-build-cache") | .with."cache-target"' "$workflow")
[[ "$actual" == false ]] || fail "$job enables full target caching for a disposable fixture"
actual=$(JOB_ID="$job" yq -r '.jobs[strenv(JOB_ID)].steps[] | select(.uses == "./.github/actions/setup-rust-build-cache") | (.with."save-target" // false)' "$workflow")
[[ "$actual" == false ]] || fail "$job opts into saving full target caches"
done
fixture_script="$ACTION_DIR/../deploy-fastly/tests/make-smoke-fixture.sh"
grep -Fq 'GIT_AUTHOR_DATE=2000-01-01T00:00:00Z GIT_COMMITTER_DATE=2000-01-01T00:00:00Z' "$fixture_script" || fail 'application fixture dates are not deterministic'
grep -Fq '.edgezero-source-revision' "$fixture_script" || fail 'application fixture omits its path dependency revision'
Expand Down
65 changes: 65 additions & 0 deletions .github/actions/setup-rust-build-cache/tests/publication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Mutate the workflow itself so contract checks cannot merely test a detached if.
set -euo pipefail
ACTION_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/edgezero-cache-publication-test.XXXXXX")
trap 'rm -rf -- "$WORK_DIR"' EXIT
# shellcheck source=common.sh
source "$ACTION_DIR/tests/common.sh"
workflow="$ACTION_DIR/../../workflows/deploy-action.yml"

reject_mutation() {
Comment thread
prk-Jr marked this conversation as resolved.
Resolved
local name="$1" expression="$2" diagnostic="$3" status=0
yq "$expression" "$workflow" >"$WORK_DIR/workflow.yml"
bash "$ACTION_DIR/tests/workflow.sh" "$WORK_DIR/workflow.yml" >"$WORK_DIR/log" 2>&1 || status=$?
[[ "$status" == 1 ]] || fail "workflow guard accepted mutation: $name (exit $status)"
grep -Fq "$diagnostic" "$WORK_DIR/log" || {
cat "$WORK_DIR/log" >&2
fail "mutation failed for an unrelated reason: $name"
}
}

for kind in source target; do
publication=".jobs.\"cache-restore-seed\".steps[] | select(.name == \"Require exact $kind seed publication\")"
reject_mutation 'missing final gate' \
".jobs.\"cache-restore-seed\".steps |= map(select(.name != \"Require exact $kind seed publication\"))" \
'missing exact publication assertion'
reject_mutation 'hardcoded first hit' \
"($publication | .env.CACHE_SEED_FIRST_HIT) = \"true\"" \
'publication assertion ignores first lookup output'
reject_mutation 'hardcoded second hit' \
"($publication | .env.CACHE_SEED_SECOND_HIT) = \"true\"" \
'publication assertion ignores second lookup output'
reject_mutation 'skipped final gate' \
"($publication | .if) = \"false\"" \
'publication assertion can be skipped'
reject_mutation 'ignored final failure' \
"($publication | .\"continue-on-error\") = true" \
'publication assertion failure is ignored'
reject_mutation 'gate always succeeds' \
"($publication | .run) = \"true\"" \
'publication assertion accepts unverified outputs'
reject_mutation 'gate always fails' \
"($publication | .run) = \"false\"" \
'publication assertion rejects a verified exact hit'
for step in "$kind-save-1" "$kind-verify-1" "$kind-save-2" "$kind-verify-2"; do
for input in path key; do
reject_mutation "$step wrong $input" \
"(.jobs.\"cache-restore-seed\".steps[] | select(.id == \"$step\") | .with.$input) = \"wrong\"" \
"$step uses a different cache $input"
done
if [[ "$step" == *-2 ]]; then
reject_mutation "$step wrong retry condition" \
"(.jobs.\"cache-restore-seed\".steps[] | select(.id == \"$step\") | .if) = \"false\"" \
"$step retries without a first-attempt miss"
else
reject_mutation "$step skipped initial attempt" \
"(.jobs.\"cache-restore-seed\".steps[] | select(.id == \"$step\") | .if) = \"false\"" \
"$step skips the initial publication attempt"
fi
done
done
reject_mutation 'seed skips action save path' \
'(.jobs."cache-restore-seed".steps[] | select(.id == "cache") | .with."save-target") = false' \
'seed no longer exercises the action save path'
printf 'source and target publication workflow mutation contracts passed\n'
5 changes: 5 additions & 0 deletions .github/actions/setup-rust-build-cache/tests/review.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ action="$ACTION_DIR/action.yml"
[[ "$(yq -r '.inputs."save-target".default' "$action")" == false ]] || fail 'target saves are not opt-in'
[[ "$(yq -r '.runs.steps[] | select(.id == "cargo-target") | .uses' "$action")" == actions/cache/restore@v5 ]] || fail 'default target step is not restore-only'
[[ "$(yq -r '.runs.steps[] | select(.id == "cargo-target-save") | .uses' "$action")" == actions/cache@v5 ]] || fail 'writer does not use the post-job save hook'
[[ "$(yq -r '.runs.steps[] | select(.id == "cargo-target-save") | .if' "$action")" == *"inputs['save-target'] == 'true'"* ]] || fail 'writer is not selected by save-target'
target_hit=$(yq -r '.outputs."target-cache-hit".value' "$action")
for step in cargo-target cargo-target-save; do
[[ "$target_hit" == *"steps.$step.outputs.cache-hit == 'true'"* ]] || fail "target-cache-hit ignores $step"
done
for step in cargo-target cargo-target-save; do
STEP_ID="$step" yq -e '.runs.steps[] | select(.id == strenv(STEP_ID)) | .with."restore-keys" == null' "$action" >/dev/null || fail 'target fallback weakens revision isolation'
done
Expand Down
3 changes: 3 additions & 0 deletions .github/actions/setup-rust-build-cache/tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,6 @@ printf 'setup-rust-build-cache action tests passed\n'
bash "$ACTION_DIR/tests/revision.sh"
bash "$ACTION_DIR/tests/review.sh"
bash "$ACTION_DIR/tests/fixture.sh"
bash "$ACTION_DIR/tests/workflow.sh"
bash "$ACTION_DIR/tests/publication.sh"
bash "$ACTION_DIR/tests/seed.sh"
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail

# Called by deploy-action.yml; seed.sh covers publication and cleanup failures.
# Publish each target before downstream jobs test first-build compiler reuse.
fixture="${GITHUB_WORKSPACE:?}/cache-restore-fixture"
target_dir=$(realpath "${CARGO_TARGET_DIR:?}")
Comment thread
prk-Jr marked this conversation as resolved.
Resolved
expected="$(realpath "${RUNNER_TEMP:?}")/edgezero-rust-cache/cache-restore-smoke-v2/target"
[[ "$target_dir" == "$expected" ]] || {
printf '::error::compiler seed must use its confined fixture target directory\n' >&2
exit 1
}
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=compiler-stats.sh
source "$script_dir/compiler-stats.sh"
cd "$fixture"
host=$(rustc -vV | sed -n 's/^host: //p')
[[ "$host" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]] || {
printf '::error::rustc must report a safe host target component\n' >&2
exit 1
}
# Every build must reach the remote cache, including the read-back build.
build_fresh() {
Comment thread
prk-Jr marked this conversation as resolved.
Resolved
local phase="$1" debug_dir="$target_dir/$target/debug"
[[ ! -L "$target_dir/$target" && ! -L "$debug_dir" ]] || {
printf '::error::compiler seed cleanup must not follow target symlinks\n' >&2
exit 1
}
rm -rf -- "$debug_dir"
sccache --zero-stats
cargo build --locked --lib --target "$target"
stats=$(sccache --show-stats --stats-format json)
Comment thread
prk-Jr marked this conversation as resolved.
Resolved
printf 'Compiler seed for %s (attempt %s, %s): %s\n' "$target" "$attempt" "$phase" "$stats"
# Unreadable stats are not a cache outage; fail without backoff.
jq -e '.stats | type == "object"' <<<"$stats" >/dev/null 2>&1 || {
printf '::error::sccache returned unreadable stats for %s\n' "$target" >&2
exit 1
}
}

for target in "$host" wasm32-wasip1; do
for attempt in 1 2 3; do
build_fresh publish
if compiler_hit "$stats"; then
break
fi
# The pinned backend can report a successful write despite failed upload
# finalization. Only a fresh build's remote hit verifies publication.
if compiler_wrote "$stats"; then
build_fresh read-back
if compiler_hit "$stats"; then
break
fi
fi
if [[ "$attempt" == 3 ]]; then
printf '::error::failed to verify compiler cache publication for %s after three attempts\n' "$target" >&2
exit 1
fi
printf '::warning::compiler cache seed failed; retrying after backoff\n'
sleep "$((attempt * 60))"
done
done
Loading
Loading