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
62 changes: 58 additions & 4 deletions .github/actions/setup-rust-build-cache/tests/fixture.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ 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"
workflow="${CACHE_WORKFLOW_FILE:-$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'
[[ "$seed" == 'bash .github/actions/setup-rust-build-cache/tests/seed-compiler-cache.sh' ]] || fail 'seed does not verify compiler publication'
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
Expand All @@ -41,7 +40,62 @@ 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'
[[ "$(yq -r '.jobs."cache-restore-seed".steps[] | select(.id == "cache") | .with."save-target"' "$workflow")" == false ]] || fail 'seed relies on unverified post-job saving'
for job in fixture-release store-free-release; do
actual=$(JOB_ID="$job" yq -r '.jobs[strenv(JOB_ID)].needs // [] | length' "$workflow")
[[ "$actual" == 0 ]] || fail "$job depends on cache probe availability"
done
# Exercise both publication surfaces against their actual workflow wiring.
for kind in source target; do
if [[ "$kind" == source ]]; then
expected_path=$(yq -r '.runs.steps[] | select(.id == "cargo-sources") | .with.path' "$ACTION_DIR/action.yml")
else
# shellcheck disable=SC2016 # Literal GitHub output reference.
expected_path='${{ steps.cache.outputs.cargo-target-dir }}'
fi
expected_key="\${{ steps.cache.outputs.$kind-cache-key }}"
retry_condition="steps.$kind-verify-1.outputs.cache-hit != 'true'"
for step in "$kind-save-1" "$kind-verify-1" "$kind-save-2" "$kind-verify-2"; do
actual=$(STEP_ID="$step" yq -r '.jobs."cache-restore-seed".steps[] | select(.id == strenv(STEP_ID)) | .with.path' "$workflow")
[[ "$actual" == "$expected_path" ]] || fail "$step uses a different cache path"
actual=$(STEP_ID="$step" yq -r '.jobs."cache-restore-seed".steps[] | select(.id == strenv(STEP_ID)) | .with.key' "$workflow")
[[ "$actual" == "$expected_key" ]] || fail "$step uses a different cache key"
actual=$(STEP_ID="$step" yq -r '.jobs."cache-restore-seed".steps[] | select(.id == strenv(STEP_ID)) | .if // ""' "$workflow")
if [[ "$step" == *-2 ]]; then
[[ "$actual" == "$retry_condition" ]] || fail "$step retries without a first-attempt miss"
else
[[ -z "$actual" ]] || fail "$step skips the initial publication attempt"
fi
done
for attempt in 1 2; do
STEP_ID="$kind-save-$attempt" yq -e '.jobs."cache-restore-seed".steps[] | select(.id == strenv(STEP_ID)) | .uses == "actions/cache/save@v5"' "$workflow" >/dev/null || fail 'missing explicit cache save'
STEP_ID="$kind-verify-$attempt" yq -e '.jobs."cache-restore-seed".steps[] | select(.id == strenv(STEP_ID)) | .with."lookup-only" == true' "$workflow" >/dev/null || fail 'cache publication is not verified'
done
STEP_ID="$kind-verify-2" yq -e '.jobs."cache-restore-seed".steps[] | select(.id == strenv(STEP_ID)) | .with."fail-on-cache-miss" == true' "$workflow" >/dev/null || fail 'exhausted publication retries stay green'
# The action can return successfully before honoring fail-on-cache-miss.
# Require actual exact-hit output, including when availability checks fail.
publication_step=".jobs.\"cache-restore-seed\".steps[] | select(.name == \"Require exact $kind seed publication\")"
publication_assertion=$(yq -r "$publication_step | .run" "$workflow")
[[ -n "$publication_assertion" && "$publication_assertion" != null ]] || fail 'missing exact publication assertion'
first_hit="\${{ steps.$kind-verify-1.outputs.cache-hit }}"
second_hit="\${{ steps.$kind-verify-2.outputs.cache-hit }}"
[[ "$(yq -r "$publication_step | .env.CACHE_SEED_FIRST_HIT" "$workflow")" == "$first_hit" ]] || fail 'publication assertion ignores first lookup output'
[[ "$(yq -r "$publication_step | .env.CACHE_SEED_SECOND_HIT" "$workflow")" == "$second_hit" ]] || fail 'publication assertion ignores second lookup output'
[[ "$(yq -r "$publication_step | .if // \"\"" "$workflow")" == '' ]] || fail 'publication assertion can be skipped after successful lookup steps'
[[ "$(yq -r "$publication_step | .\"continue-on-error\" // false" "$workflow")" == false ]] || fail 'publication assertion failure is ignored'
for first in '' false true partial; do
for second in '' false true partial; do
status=0
CACHE_SEED_FIRST_HIT="$first" CACHE_SEED_SECOND_HIT="$second" \
bash -e -o pipefail -c "$publication_assertion" >"$WORK_DIR/publication.log" 2>&1 || status=$?
if [[ "$first" == true || "$second" == true ]]; then
[[ "$status" == 0 ]] || fail 'publication assertion rejects a verified exact hit'
else
[[ "$status" != 0 ]] || fail "publication assertion accepts unverified outputs '$first'/'$second'"
fi
done
done
done
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"
Expand Down
62 changes: 62 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,62 @@
#!/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"
CACHE_WORKFLOW_FILE="$WORK_DIR/workflow.yml" bash "$ACTION_DIR/tests/fixture.sh" >"$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
printf 'source and target publication workflow mutation contracts passed\n'
2 changes: 2 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,5 @@ 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/publication.sh"
bash "$ACTION_DIR/tests/seed.sh"
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/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
}
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() {
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)
printf 'Compiler seed for %s (attempt %s): %s\n' "$target" "$attempt" "$stats"
}

compiler_hit() {
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
}

for target in "$host" wasm32-wasip1; do
for attempt in 1 2 3; do
build_fresh
if compiler_hit; 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 jq -e '(.stats.cache_writes // 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; then
build_fresh
if compiler_hit; 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