--- # DESCRIPTION: Github actions config # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 name: reusable-test on: workflow_call: inputs: archive: description: "Name of the repository archive artifact from reusable-build" required: true type: string cc: description: "Compiler to use: 'gcc' or 'clang'" required: true type: string dev-gcov: description: "Collect gcov coverage data from the test run" required: false default: false type: boolean reloc: description: "Relocate the installation before testing" required: false default: false type: boolean runs-on: description: "Runner to test on, e.g. ubuntu-24.04" required: true type: string suite: description: "Test suite to run, e.g. dist-vlt-0" required: true type: string permissions: contents: read actions: read env: CCACHE_COMPILERCHECK: content CCACHE_DIR: ${{ github.workspace }}/.ccache CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} GH_TOKEN: ${{ github.token }} defaults: run: shell: bash working-directory: repo jobs: test: runs-on: ${{ inputs.runs-on }} name: Test steps: - name: Download repository archive uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.archive }} path: ${{ github.workspace }} - name: Unpack repository archive working-directory: ${{ github.workspace }} run: | tar --zstd -x -f ${{ inputs.archive }} ls -lsha # Test-job ccache is stored as an artifact, not actions/cache # - pull_request: restore this PR's own previous run; if none, fall back to the PR's target branch. # - branch push: restore that branch's own cache # Artifact names are a global, unauthenticated namespace, so a PR could # upload a "-branch-" artifact impersonating a branch. Branch-scoped # restores are therefore provenance-checked below (must come from a same-repo # push to that branch); the pr- scope is not (a PR can only affect its own # unmerged cache). - name: Compute ccache artifact name # Pass github refs via env, not ${{ }} interpolation, so a branch name # containing shell metacharacters can't break or inject into the script. env: BASE_REF: ${{ github.event.pull_request.base.ref }} REF_NAME: ${{ github.ref_name }} run: | base="ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.suite }}" sanitize() { printf '%s' "$1" | tr '/' '_'; } # artifact names cannot contain '/' if [ "${{ github.event_name }}" = pull_request ]; then echo "CCACHE_ARTIFACT=${base}-pr-${{ github.event.pull_request.number }}" >> "$GITHUB_ENV" echo "CCACHE_ARTIFACT_BRANCH=" >> "$GITHUB_ENV" echo "CCACHE_FALLBACK=${base}-branch-$(sanitize "$BASE_REF")" >> "$GITHUB_ENV" echo "CCACHE_FALLBACK_BRANCH=${BASE_REF}" >> "$GITHUB_ENV" else echo "CCACHE_ARTIFACT=${base}-branch-$(sanitize "$REF_NAME")" >> "$GITHUB_ENV" echo "CCACHE_ARTIFACT_BRANCH=${REF_NAME}" >> "$GITHUB_ENV" fi - name: Restore ccache from artifact continue-on-error: true # a cold start is fine; never fail the job here run: | set -euo pipefail mkdir -p "$CCACHE_DIR" # Return newest non-expired artifact named $1 (empty if none). When # $2 (branch) is non-empty, require provenance: the artifact must come from # a same-repo run (head_repository_id == repository_id) whose head branch # is $2, so a fork PR cannot forge a branch-scoped artifact. newest_run_id() { want="$1" br="$2" gh api \ "/repos/$GITHUB_REPOSITORY/actions/artifacts?name=$1&per_page=100" \ --jq '.artifacts[] | select(.expired == false and .name == env.want) | select(env.br == "" or (.workflow_run.head_branch == env.br and .workflow_run.head_repository_id == .workflow_run.repository_id)) | [.created_at, (.workflow_run.id | tostring)] | @tsv' \ | sort | tail -n1 | cut -f2 } restore() { # $1=name $2=branch(empty ok); returns 0 on a successful restore local name="$1" br="$2" rid tarball [ -n "$name" ] || return 1 rid="$(newest_run_id "$name" "$br")" || return 1 [ -n "$rid" ] || return 1 echo "Restoring ccache from '$name' (run $rid)" gh run download "$rid" --name "$name" --dir "$RUNNER_TEMP/ccache-dl" || return 1 tarball="$(find "$RUNNER_TEMP/ccache-dl" -name ccache.tar.zst -print -quit)" [ -n "$tarball" ] || return 1 tar -I zstd -x -f "$tarball" -C "$CCACHE_DIR" } if restore "${CCACHE_ARTIFACT:-}" "${CCACHE_ARTIFACT_BRANCH:-}"; then echo "Restored primary ccache" exit 0 fi if restore "${CCACHE_FALLBACK:-}" "${CCACHE_FALLBACK_BRANCH:-}"; then echo "Restored fallback ccache" exit 0 fi echo "No ccache artifact found; starting cold" - name: Install test dependencies run: | ./ci/ci-install.bash test - name: Set up Python venv uses: ./repo/.github/actions/setup-venv with: save: ${{ github.ref == 'refs/heads/master' }} - name: Test id: run-test continue-on-error: true run: | source .venv/bin/activate ./ci/ci-test.bash \ --suite ${{ inputs.suite }} \ ${{ inputs.reloc && format('--reloc {0}/reloc', github.workspace) || '' }} - name: Combine code coverage data if: ${{ inputs.dev-gcov }} run: | make coverage-combine mv obj_coverage/verilator.info obj_coverage/verilator-${{ inputs.suite }}.info ls -lsha obj_coverage - name: Upload code coverage data if: ${{ inputs.dev-gcov }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/repo/obj_coverage/verilator-${{ inputs.suite }}.info name: code-coverage-${{ inputs.suite }} - name: Pack ccache if: ${{ !cancelled() && env.CCACHE_ARTIFACT != '' }} run: tar -I 'zstd -T0' -cf "$GITHUB_WORKSPACE/ccache.tar.zst" -C "$CCACHE_DIR" . - name: Save ccache artifact if: ${{ !cancelled() && env.CCACHE_ARTIFACT != '' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ env.CCACHE_ARTIFACT }} path: ${{ github.workspace }}/ccache.tar.zst retention-days: 3 overwrite: true compression-level: 0 - name: Fail job if a test failed if: ${{ steps.run-test.outcome == 'failure' && !cancelled() }} run: |- echo "Click on '> Tests' arrow above (or on other steps), to expand it, and see the failure reasons" exit 1