name: CI
on:
pull_request:
push:
branches:
- main
permissions:
actions: read
contents: read
pull-requests: read
env:
CARGO_TERM_COLOR: always
jobs:
reuse-pr-ci:
name: Reuse successful PR CI
if: github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
tested: ${{ steps.check.outputs.tested }}
steps:
- name: Check whether this tree passed PR CI
id: check
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
COMMIT: ${{ github.sha }}
run: |
echo "tested=false" >> "$GITHUB_OUTPUT"
max_attempts=6
retry_delay=10
for attempt in $(seq 1 "$max_attempts"); do
echo "::group::Reuse lookup attempt $attempt of $max_attempts"
commit_json="$(
gh api "repos/$REPO/git/commits/$COMMIT" 2>/dev/null || true
)"
main_tree="$(jq -r '.tree.sha // empty' <<<"${commit_json:-null}")"
parent_count="$(jq -r '.parents | length' <<<"${commit_json:-null}")"
pr="$(
gh api "repos/$REPO/commits/$COMMIT/pulls" \
--jq 'map(select(.merged_at != null)) | sort_by(.merged_at) | last' \
2>/dev/null || true
)"
head_sha="$(jq -r '.head.sha // empty' <<<"${pr:-null}")"
head_repo="$(jq -r '.head.repo.full_name // empty' <<<"${pr:-null}")"
source="associated merged PR"
# Commit-to-PR associations are eventually consistent after a merge.
# A normal two-parent merge records the exact PR head as parent 2,
# which provides a safe fallback while that index catches up.
if [ -z "$head_sha" ] || [ -z "$head_repo" ]; then
if [ "$parent_count" = "2" ]; then
head_sha="$(jq -r '.parents[1].sha // empty' <<<"$commit_json")"
head_repo="$REPO"
source="second merge parent"
fi
fi
if [ -z "$main_tree" ]; then
reason="main commit metadata is not available yet"
elif [ -z "$head_sha" ] || [ -z "$head_repo" ]; then
reason="no merged PR association or two-parent merge fallback is available yet"
else
echo "Candidate $head_sha resolved from $source"
pr_tree="$(
gh api "repos/$head_repo/git/commits/$head_sha" --jq '.tree.sha' \
2>/dev/null || true
)"
if [ -z "$pr_tree" ] && [ "$head_repo" != "$REPO" ]; then
# GitHub retains a merged fork head as an object in the base
# repository even if the fork or branch has since been deleted.
pr_tree="$(
gh api "repos/$REPO/git/commits/$head_sha" --jq '.tree.sha' \
2>/dev/null || true
)"
fi
if [ -z "$pr_tree" ]; then
reason="candidate commit metadata is not available yet"
elif [ "$main_tree" != "$pr_tree" ]; then
echo "Main tree $main_tree differs from candidate tree $pr_tree"
echo "::endgroup::"
echo "::notice title=PR CI not reused::The main and PR trees differ."
exit 0
else
echo "Main and candidate share tree $main_tree"
passed="$(
gh api \
--method GET \
-f event=pull_request \
-f head_sha="$head_sha" \
-f status=completed \
-f per_page=100 \
"repos/$REPO/actions/workflows/ci.yml/runs" \
--jq '[.workflow_runs[] | select(.conclusion == "success")] | length > 0' \
2>/dev/null || true
)"
if [ "$passed" = "true" ]; then
echo "Successful pull-request CI found for $head_sha"
echo "::endgroup::"
echo "tested=true" >> "$GITHUB_OUTPUT"
echo "::notice title=PR CI reused::Skipping duplicate CI for tree $main_tree."
exit 0
fi
reason="a successful pull-request CI run is not available yet"
fi
fi
echo "$reason"
echo "::endgroup::"
if [ "$attempt" -lt "$max_attempts" ]; then
sleep "$retry_delay"
fi
done
echo "::notice title=PR CI not reused::No reusable successful PR run was found after $max_attempts attempts; running CI."
test:
name: Tests and quality gates
needs: reuse-pr-ci
if: >-
always() &&
(github.event_name != 'push' || needs.reuse-pr-ci.outputs.tested != 'true')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install development Rust
uses: dtolnay/rust-toolchain@1.97.1
with:
components: clippy, rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: Build documentation
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --workspace --no-deps
- name: Run all tests, including container tests
run: cargo test --workspace -- --include-ignored --test-threads=1
benches:
name: Benchmarks
needs: reuse-pr-ci
if: >-
always() &&
(github.event_name != 'push' || needs.reuse-pr-ci.outputs.tested != 'true')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install development Rust
uses: dtolnay/rust-toolchain@1.97.1
- name: Run all benchmarks
run: cargo bench --workspace
msrv:
name: MSRV 1.88
needs: reuse-pr-ci
if: >-
always() &&
(github.event_name != 'push' || needs.reuse-pr-ci.outputs.tested != 'true')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install minimum supported Rust
uses: dtolnay/rust-toolchain@1.88.0
- name: Check every workspace target
run: cargo check --workspace --all-targets
package:
name: Package crates
needs: reuse-pr-ci
if: >-
always() &&
(github.event_name != 'push' || needs.reuse-pr-ci.outputs.tested != 'true')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install development Rust
uses: dtolnay/rust-toolchain@1.97.1
- name: Verify publishable packages
run: cargo package --workspace
fuzz:
name: Fuzz ${{ matrix.target }}
needs: reuse-pr-ci
if: >-
always() &&
(github.event_name != 'push' || needs.reuse-pr-ci.outputs.tested != 'true')
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- frontend_codec
- backend_codec
- pre_startup
- scram
- runtime_fsm
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
- name: Install cargo-fuzz
run: cargo install cargo-fuzz --locked
- name: Run fuzz target
run: cargo +nightly fuzz run --fuzz-dir fuzz ${{ matrix.target }} -- -max_total_time=60