diff --git a/.github/scripts/stage-codecov.sh b/.github/scripts/stage-codecov.sh deleted file mode 100644 index 431993aad13..00000000000 --- a/.github/scripts/stage-codecov.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Stage coverage + test-result reports for the deferred Codecov upload -# (.github/workflows/codecov-upload.yml). Fork PRs get no CODECOV_TOKEN on the -# pull_request event, so the build stages its reports as a `codecov-` -# artifact instead of uploading inline; the workflow_run job re-uploads them with -# a token (see codecov-upload.yml / #6685). -# -# Produces ./cc/coverage/ (coverage reports), ./cc/results/ (JUnit XMLs), and the -# pr/sha/branch identifier files. Directory structure is preserved so same-basename -# reports (e.g. amber's per-module jacoco.xml across ~8 modules) do not collide; -# the deferred job points `directory:` at cc/coverage and cc/results so Codecov's -# recursive search finds every file under each. -# -# Inputs (env): -# CC_COVERAGE space-separated list of coverage sources — each a literal file or -# a `find -path` glob (use * which matches across /), e.g. -# "*/target/scala-2.13/jacoco/report/jacoco.xml". May be empty for a -# test-results-only flag. -# CC_RESULTS same, for JUnit test-result XMLs. -# CC_PR PR number (empty on push builds) -# CC_SHA head commit sha -# CC_BRANCH head branch -set -uo pipefail - -mkdir -p cc/coverage cc/results - -copy_into() { - dest="$1"; shift - for src in "$@"; do - if [ -e "$src" ]; then - # literal existing path - cp --parents "$src" "$dest/" 2>/dev/null || cp "$src" "$dest/" 2>/dev/null || true - else - # treat as a find -path glob (matches across / since find's * does) - find . -path "./$src" -o -path "$src" 2>/dev/null \ - | while IFS= read -r f; do - [ -f "$f" ] && cp --parents "$f" "$dest/" 2>/dev/null || true - done - fi - done -} - -# shellcheck disable=SC2086 # intentional word-splitting into separate globs -copy_into cc/coverage ${CC_COVERAGE:-} -# shellcheck disable=SC2086 -copy_into cc/results ${CC_RESULTS:-} - -printf '%s' "${CC_PR:-}" > cc/pr-number.txt -printf '%s' "${CC_SHA:-}" > cc/commit-sha.txt -printf '%s' "${CC_BRANCH:-}" > cc/branch.txt - -echo "Staged coverage files:" -find cc/coverage -type f 2>/dev/null | sed 's/^/ /' || true -echo "Staged result files:" -find cc/results -type f 2>/dev/null | sed 's/^/ /' || true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34eeff6ae3a..1355a4dbbce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -137,36 +137,44 @@ jobs: run: ./bin/licensing/check_binary_deps.py ${{ inputs.mode == 'PR' && '--ignore-transitive-version' || '' }} npm frontend/dist/3rdpartylicenses.json - name: Run frontend unit tests run: yarn --cwd frontend run test:ci + - name: Upload frontend coverage to Codecov + if: matrix.os == 'ubuntu-latest' && always() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./frontend/coverage/**/lcov.info + flags: frontend + fail_ci_if_error: false + - name: Upload frontend unit test results to Codecov + # vitest.config.ts adds a `junit` reporter that writes to junit.xml + # in the working dir; the @angular/build:unit-test runner forwards + # vitest config through unchanged. + if: matrix.os == 'ubuntu-latest' && !cancelled() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./frontend/junit.xml + flags: frontend + report_type: test_results + disable_search: true + fail_ci_if_error: false - name: Install Playwright Chromium run: yarn --cwd frontend playwright install ${{ matrix.os == 'ubuntu-latest' && '--with-deps' || '' }} chromium - name: Run frontend browser-mode tests run: yarn --cwd frontend ng run gui:test-browser - # Codecov upload is deferred to codecov-upload.yml (workflow_run) so fork PRs - # get authenticated uploads and a reliable PR comment — see that file and - # #6685. Stage this flag's reports as the `codecov-frontend` artifact instead - # of uploading inline. The `!cancelled()` guard is a status-check function, so - # it overrides the implicit success() and staging still runs when the unit or - # browser tests fail (matching the old always()/!cancelled() steps); it only - # skips a cancelled run. Skipped on backport builds (different ref + would - # collide on the artifact name across the target matrix). - - name: Stage frontend coverage for deferred Codecov upload - if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == '' && !cancelled() - shell: bash - env: - CC_COVERAGE: frontend/coverage/gui/lcov.info - CC_RESULTS: frontend/junit.xml frontend/junit-browser.xml - CC_PR: ${{ github.event.pull_request.number }} - CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }} - run: bash .github/scripts/stage-codecov.sh - - name: Upload frontend Codecov artifact - if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == '' && !cancelled() - uses: actions/upload-artifact@v4 + - name: Upload frontend browser-mode test results to Codecov + # vitest.browser.config.ts emits junit-browser.xml (distinct from + # the unit-test report). Same `frontend` flag — Codecov merges + # multi-file uploads under one flag. + if: matrix.os == 'ubuntu-latest' && !cancelled() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - name: codecov-frontend - path: cc/ - retention-days: 1 - if-no-files-found: warn + token: ${{ secrets.CODECOV_TOKEN }} + files: ./frontend/junit-browser.xml + flags: frontend + report_type: test_results + disable_search: true + fail_ci_if_error: false amber: # The amber job runs the cross-cutting Scala lints (scalafmtCheckAll, @@ -292,29 +300,28 @@ jobs: "WorkflowCore/jacoco" \ "WorkflowOperator/jacoco" \ "WorkflowExecutionService/jacoco" - - name: Stage amber and common coverage for deferred Codecov upload - # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an - # authenticated upload — see #6685. Multi-module globs: the staging script - # preserves the directory tree so the ~8 same-basename jacoco.xml (and the - # per-spec test-reports) don't collide when flattened. `!cancelled()` keeps - # staging on test failure; skipped on backport builds (artifact-name clash). - if: inputs.backport_target_branch == '' && !cancelled() - shell: bash - env: - CC_COVERAGE: "*/target/scala-2.13/jacoco/report/jacoco.xml" - CC_RESULTS: "*/target/test-reports/*.xml" - CC_PR: ${{ github.event.pull_request.number }} - CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }} - run: bash .github/scripts/stage-codecov.sh - - name: Upload amber Codecov artifact - if: inputs.backport_target_branch == '' && !cancelled() - uses: actions/upload-artifact@v4 + - name: Upload amber and common coverage to Codecov + if: always() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - name: codecov-amber - path: cc/ - retention-days: 1 - if-no-files-found: warn + token: ${{ secrets.CODECOV_TOKEN }} + files: ./**/target/scala-2.13/jacoco/report/jacoco.xml + flags: amber + fail_ci_if_error: false + - name: Upload amber and common test results to Codecov + # ScalaTest writes one JUnit-XML per spec under each module's + # target/test-reports/ (configured ThisBuild in build.sbt). Glob + # picks them up from every module that ran in the jacoco invocation + # above. `!cancelled()` so test failures still upload (the point + # of Test Analytics). + if: ${{ !cancelled() }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./**/target/test-reports/*.xml + flags: amber + report_type: test_results + fail_ci_if_error: false amber-integration: # Runs Scala tests tagged @org.apache.texera.amber.tags.IntegrationTest — @@ -649,30 +656,18 @@ jobs: # --junit-xml feeds the Test Analytics upload below. run: | cd amber && pytest -m integration --junit-xml=junit-integration.xml -sv - - name: Stage amber-integration test results for deferred Codecov upload - # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an - # authenticated upload — see #6685. Results-only flag (no jacoco). Staged - # on the ubuntu leg only: the integration specs run on both ubuntu and - # macOS, so staging both would clash on the `codecov-amber-integration` - # artifact name (upload-artifact@v4 rejects duplicates); the ubuntu run is - # representative for Test Analytics. Skipped on backport builds. - if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == '' && !cancelled() - shell: bash - env: - CC_COVERAGE: "" - CC_RESULTS: "*/target/test-reports/*.xml amber/junit-integration.xml" - CC_PR: ${{ github.event.pull_request.number }} - CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }} - run: bash .github/scripts/stage-codecov.sh - - name: Upload amber-integration Codecov artifact - if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == '' && !cancelled() - uses: actions/upload-artifact@v4 + - name: Upload amber integration test results to Codecov + # Two separate uploads because the ScalaTest and pytest runs each + # produce their own JUnit-XMLs and Codecov keys uploads by flag. + # `!cancelled()` so test failures still upload. + if: ${{ !cancelled() }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - name: codecov-amber-integration - path: cc/ - retention-days: 1 - if-no-files-found: warn + token: ${{ secrets.CODECOV_TOKEN }} + files: ./**/target/test-reports/*.xml,./amber/junit-integration.xml + flags: amber-integration + report_type: test_results + fail_ci_if_error: false platform: # Per-service build, test, and license check for the non-amber Scala @@ -773,30 +768,27 @@ jobs: check_exit=1 fi exit "$check_exit" - - name: Stage ${{ matrix.service }} coverage for deferred Codecov upload - # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an - # authenticated upload — see #6685. Per-service flag. Skipped on backport - # builds (artifact-name clash across the target matrix). - if: inputs.backport_target_branch == '' && !cancelled() - shell: bash - env: - CC_COVERAGE: "${{ matrix.service }}/target/scala-2.13/jacoco/report/jacoco.xml" - CC_RESULTS: "${{ matrix.service }}/target/test-reports/*.xml" - CC_PR: ${{ github.event.pull_request.number }} - CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }} - run: bash .github/scripts/stage-codecov.sh - - name: Upload ${{ matrix.service }} Codecov artifact - # Adding a service to the matrix above also needs a matching - # { flag: , coverage: true } row in codecov-upload.yml, or this - # artifact is downloaded by nothing and the service's coverage is dropped. - if: inputs.backport_target_branch == '' && !cancelled() - uses: actions/upload-artifact@v4 + - name: Upload ${{ matrix.service }} coverage to Codecov + # Per-service flag so each matrix entry has its own Codecov view + # rather than being merged into one umbrella `platform` flag. + if: always() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - name: codecov-${{ matrix.service }} - path: cc/ - retention-days: 1 - if-no-files-found: warn + token: ${{ secrets.CODECOV_TOKEN }} + files: ./${{ matrix.service }}/target/scala-2.13/jacoco/report/jacoco.xml + flags: ${{ matrix.service }} + fail_ci_if_error: false + - name: Upload ${{ matrix.service }} test results to Codecov + # Per-service Test Analytics, mirroring the coverage upload flag. + if: ${{ !cancelled() }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./${{ matrix.service }}/target/test-reports/*.xml + flags: ${{ matrix.service }} + report_type: test_results + disable_search: true + fail_ci_if_error: false platform-integration: # Boot smoke test for the platform services (mirrors amber-integration: an @@ -1023,27 +1015,28 @@ jobs: # test PR comments and flaky-test detection on main. run: | cd amber && pytest -m "not integration" --cov=src/main/python --cov-report=xml --junit-xml=junit.xml -sv - - name: Stage pyamber coverage for deferred Codecov upload - # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an - # authenticated upload — see #6685. 3.12 leg only (the leg that produced - # coverage.xml / the pip-licenses snapshot). Skipped on backport builds. - if: matrix.python-version == '3.12' && inputs.backport_target_branch == '' && !cancelled() - shell: bash - env: - CC_COVERAGE: amber/coverage.xml - CC_RESULTS: amber/junit.xml - CC_PR: ${{ github.event.pull_request.number }} - CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }} - run: bash .github/scripts/stage-codecov.sh - - name: Upload pyamber Codecov artifact - if: matrix.python-version == '3.12' && inputs.backport_target_branch == '' && !cancelled() - uses: actions/upload-artifact@v4 + - name: Upload pyamber coverage to Codecov + if: matrix.python-version == '3.12' && always() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./amber/coverage.xml + flags: pyamber + fail_ci_if_error: false + - name: Upload pyamber test results to Codecov + # Test Analytics ingestion. Runs on the same 3.12 leg that uploads + # coverage to keep one canonical source per flag. `!cancelled()` + # rather than `always()` so we still upload on test failure (the + # whole point of Test Analytics) but skip cancelled runs. + if: matrix.python-version == '3.12' && !cancelled() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - name: codecov-pyamber - path: cc/ - retention-days: 1 - if-no-files-found: warn + token: ${{ secrets.CODECOV_TOKEN }} + files: ./amber/junit.xml + flags: pyamber + report_type: test_results + disable_search: true + fail_ci_if_error: false agent-service: if: ${{ inputs.run_agent_service }} @@ -1092,29 +1085,26 @@ jobs: # Test Analytics upload below can feed Codecov's failing-test PR # comments and flaky-test detection on main. run: bun test --coverage --coverage-reporter=lcov --reporter=junit --reporter-outfile=junit.xml - - name: Stage agent-service coverage for deferred Codecov upload - # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an - # authenticated upload — see #6685. ubuntu leg only. working-directory is - # pinned to the workspace root (this job otherwise defaults to - # agent-service/) so the staged cc/ lands where the artifact upload expects. - if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == '' && !cancelled() - shell: bash - working-directory: ${{ github.workspace }} - env: - CC_COVERAGE: agent-service/coverage/lcov.info - CC_RESULTS: agent-service/junit.xml - CC_PR: ${{ github.event.pull_request.number }} - CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }} - run: bash .github/scripts/stage-codecov.sh - - name: Upload agent-service Codecov artifact - if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == '' && !cancelled() - uses: actions/upload-artifact@v4 + - name: Upload agent-service coverage to Codecov + if: matrix.os == 'ubuntu-latest' && always() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./agent-service/coverage/lcov.info + flags: agent-service + fail_ci_if_error: false + - name: Upload agent-service test results to Codecov + # Test Analytics ingestion. Runs on the same ubuntu leg that + # uploads coverage. `!cancelled()` so test failures still upload. + if: matrix.os == 'ubuntu-latest' && !cancelled() + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - name: codecov-agent-service - path: cc/ - retention-days: 1 - if-no-files-found: warn + token: ${{ secrets.CODECOV_TOKEN }} + files: ./agent-service/junit.xml + flags: agent-service + report_type: test_results + disable_search: true + fail_ci_if_error: false infra: # Generic project-tooling job — runs the lightweight (no docker, no diff --git a/.github/workflows/codecov-upload.yml b/.github/workflows/codecov-upload.yml deleted file mode 100644 index b43e22906e9..00000000000 --- a/.github/workflows/codecov-upload.yml +++ /dev/null @@ -1,213 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Deferred, authenticated Codecov upload for coverage produced by "Required Checks". -# -# Why this exists (reported on #6685): a PR from a fork never receives -# secrets.CODECOV_TOKEN — the build runs on the `pull_request` event and GitHub -# withholds secrets from fork runs. An inline upload from the build therefore uses -# Codecov's tokenless path, which is rate-limited; under a burst of fork-PR uploads -# Codecov processes the report but silently drops the PR comment. This workflow runs -# on `workflow_run`, in the base-repo context where the token IS available, and -# re-uploads the staged coverage/test-results artifacts with authentication so the -# comment posts reliably even in bursts. -# -# Each build job stages its reports as a `codecov-` artifact with a uniform -# layout — coverage/ (coverage reports) and results/ (JUnit XMLs) plus pr-number / -# commit-sha / branch identifier files. The matrix below re-uploads each flag with -# `directory:` scoping so Codecov's search is confined to that flag's files (this is -# what lets the amber flag's ~8 same-basename module jacoco.xml reports upload without -# a fragile explicit file list). Codecov requires one upload per flag (flags in a -# single upload apply to all its files, and carryforward needs the flag at upload -# time), so the flags cannot be collapsed into fewer calls — the win here is the -# token, not fewer uploads. -# -# NOTE: `workflow_run` only fires from the copy of this file on the default branch, so -# this has NO effect until merged to main. After merge, exercise it manually via the -# workflow_dispatch entry below, passing a finished "Required Checks" run id. - -name: Codecov Upload - -on: - workflow_run: - workflows: ["Required Checks"] - types: - - completed - workflow_dispatch: - inputs: - run_id: - description: "Finished 'Required Checks' run id to pull the codecov-* artifacts from" - required: true - type: string - -permissions: - contents: read - actions: read # download-artifact needs this to read artifacts from another run - -jobs: - upload: - # Skip only a cancelled/skipped upstream; still run on failure so partial - # coverage is reported (mirrors the build's always()/!cancelled() staging). - if: >- - github.event_name == 'workflow_dispatch' || - (github.event.workflow_run.conclusion != 'cancelled' && - github.event.workflow_run.conclusion != 'skipped') - runs-on: ubuntu-latest - # Job-level so the group can key on matrix.flag: one upload pass per source - # run + flag, and a re-run of the source supersedes the previous attempt. - concurrency: - group: codecov-upload-${{ github.event.workflow_run.id || inputs.run_id }}-${{ matrix.flag }} - cancel-in-progress: false - strategy: - fail-fast: false - matrix: - # flag = artifact suffix (codecov-); coverage=false for the - # test-results-only flag (amber-integration has no coverage report). - # - # KEEP IN SYNC with the coverage-producing jobs in build.yml: one row per - # codecov- artifact staged there — frontend, amber, amber-integration, - # pyamber, agent-service, and every service in build.yml's `platform` matrix. - # A flag staged in build.yml but missing here is downloaded by nothing and - # its coverage is silently dropped (the reverse — a row here with no artifact - # — is safe: the download continue-on-errors and the leg skips). - include: - - { flag: frontend, coverage: true } - - { flag: amber, coverage: true } - - { flag: amber-integration, coverage: false } - - { flag: pyamber, coverage: true } - - { flag: agent-service, coverage: true } - - { flag: config-service, coverage: true } - - { flag: access-control-service, coverage: true } - - { flag: file-service, coverage: true } - - { flag: computing-unit-managing-service, coverage: true } - - { flag: workflow-compiling-service, coverage: true } - - { flag: notebook-migration-service, coverage: true } - steps: - - name: Resolve source run id - id: src - shell: bash - run: echo "run_id=${{ github.event.workflow_run.id || inputs.run_id }}" >> "$GITHUB_OUTPUT" - - name: Download staged coverage - id: dl - continue-on-error: true # absent when this flag's job was label-gated out - # Layout: stage-codecov.sh roots the artifact at cc/ (coverage/, results/, - # and the pr-number/commit-sha/branch txt files), so downloading to path: cc - # restores cc/coverage/**, cc/results/**, and cc/pr-number.txt — the paths - # the steps below reference. Keep that rooting if the staging script changes. - uses: actions/download-artifact@v4 - with: - name: codecov-${{ matrix.flag }} - path: cc - run-id: ${{ steps.src.outputs.run_id }} - github-token: ${{ github.token }} - - name: Read coverage metadata - if: steps.dl.outcome == 'success' - id: meta - shell: bash - # Artifact content comes from a (possibly fork) PR build, so sanitize each - # value to its expected charset before handing it to the Codecov CLI. - run: | - echo "pr=$(cat cc/pr-number.txt 2>/dev/null | tr -cd '0-9')" >> "$GITHUB_OUTPUT" - echo "sha=$(cat cc/commit-sha.txt 2>/dev/null | tr -cd '0-9a-fA-F')" >> "$GITHUB_OUTPUT" - echo "branch=$(cat cc/branch.txt 2>/dev/null | tr -cd '[:alnum:]._/:-')" >> "$GITHUB_OUTPUT" - - name: Upload ${{ matrix.flag }} coverage to Codecov - # fail_ci_if_error: true so a genuine upload failure fails this leg and - # trips the notify-failure job below (Codecov posts its own comment only on - # success, so a failed coverage upload would otherwise leave the PR with no - # report and no signal). A label-gated-absent flag is not a failure — the - # download continue-on-errors and this step is skipped via the guards above. - if: steps.dl.outcome == 'success' && matrix.coverage && hashFiles('cc/coverage/**') != '' - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 - with: - token: ${{ secrets.CODECOV_TOKEN }} - directory: cc/coverage - flags: ${{ matrix.flag }} - fail_ci_if_error: true - override_commit: ${{ steps.meta.outputs.sha }} - override_branch: ${{ steps.meta.outputs.branch }} - override_pr: ${{ steps.meta.outputs.pr }} - - name: Upload ${{ matrix.flag }} test results to Codecov - # fail_ci_if_error: true here too (per review) so a test-results upload error - # also trips notify-failure instead of being silently dropped. Still guarded - # on hashFiles, so an absent / label-gated flag simply skips. - if: steps.dl.outcome == 'success' && hashFiles('cc/results/**') != '' - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 - with: - token: ${{ secrets.CODECOV_TOKEN }} - directory: cc/results - flags: ${{ matrix.flag }} - report_type: test_results - fail_ci_if_error: true - override_commit: ${{ steps.meta.outputs.sha }} - override_branch: ${{ steps.meta.outputs.branch }} - override_pr: ${{ steps.meta.outputs.pr }} - - notify-failure: - # If any upload above failed (coverage or test-results), leave a comment on the - # PR so a human can re-dispatch it. Codecov posts its own comment on SUCCESS, so - # we only cover the failure case (per #6730 review). A delayed / queued upload - # posts nothing — the absence of a Codecov comment then signals a stuck run. - needs: upload - if: ${{ failure() && github.event_name == 'workflow_run' }} - runs-on: ubuntu-latest - permissions: - contents: read - actions: read # download-artifact from the source run - pull-requests: write # comment on the PR - steps: - - name: Download coverage metadata - id: dl - continue-on-error: true - uses: actions/download-artifact@v4 - with: - pattern: codecov-* - path: cc-all - merge-multiple: true # every flag's artifact carries the same pr-number.txt - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ github.token }} - - name: Resolve PR number - id: meta - shell: bash - run: echo "pr=$(cat cc-all/pr-number.txt 2>/dev/null | tr -cd '0-9')" >> "$GITHUB_OUTPUT" - - name: Comment upload failure on the PR - if: steps.meta.outputs.pr != '' - uses: actions/github-script@v9 - with: - github-token: ${{ github.token }} - script: | - const MARKER = ''; - const pr = Number('${{ steps.meta.outputs.pr }}'); - const srcRunId = '${{ github.event.workflow_run.id }}'; - const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - const body = [ - MARKER, - '⚠️ **Codecov upload failed.**', - '', - `The deferred \`Codecov Upload\` workflow ([run](${runUrl})) failed, so the coverage report for this PR may be missing. This does **not** block merge.`, - '', - 'To recover: re-run the failed jobs in the run above, or manually dispatch the **Codecov Upload** workflow with', - `\`run_id\`: \`${srcRunId}\`.`, - ].join('\n'); - const { owner, repo } = context.repo; - const comments = await github.paginate(github.rest.issues.listComments, { - owner, repo, issue_number: pr, per_page: 100, - }); - const prior = comments.find(c => c.body && c.body.includes(MARKER)); - if (prior) { - await github.rest.issues.updateComment({ owner, repo, comment_id: prior.id, body }); - } else { - await github.rest.issues.createComment({ owner, repo, issue_number: pr, body }); - }