Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ 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
}
# shellcheck source=compiler-stats.sh
source "$(dirname -- "${BASH_SOURCE[0]}")/compiler-stats.sh"
cd "$fixture"
host=$(rustc -vV | sed -n 's/^host: //p')
[[ -n "$host" ]] || exit 1
Expand All @@ -22,7 +24,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,61 @@
#!/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
}
# shellcheck source=compiler-stats.sh
source "$(dirname -- "${BASH_SOURCE[0]}")/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