From acb1333a850d88665ced0ec7a6b045500fcdf31a Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 19 Sep 2026 19:46:04 +0100 Subject: [PATCH] CI: Auto label PRs with pr-blocked: improve-coverage --- .github/workflows/coverage.yml | 10 ++++++ .github/workflows/pr-automation.yml | 50 ++++++++++++++++++++++++++++- ci/ci-coverage-report.bash | 33 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0e37ae466..19b077e01 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -164,6 +164,16 @@ jobs: path: report-artifact name: coverage-report + - name: Upload coverage status + if: ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + path: repo/coverage-status.txt + name: coverage-status + overwrite: true + # Not written if the coverage could not be read, see the script above + if-no-files-found: ignore + - name: Upload PR notification if: ${{ github.event_name == 'pull_request' }} uses: ./repo/.github/actions/upload-pr-notification diff --git a/.github/workflows/pr-automation.yml b/.github/workflows/pr-automation.yml index b7c3a33a9..492a017f9 100644 --- a/.github/workflows/pr-automation.yml +++ b/.github/workflows/pr-automation.yml @@ -10,7 +10,7 @@ on: pull_request_target: # 'pull_request' can have no write permissions types: [opened, synchronize, reopened, closed] workflow_run: # To react to the result of a workflow on a pull request - workflows: ["Regression"] + workflows: ["Code coverage", "Regression"] types: [completed] permissions: @@ -113,3 +113,51 @@ jobs: else echo "No change to PR #${PR}" fi + + coverage-complete: + name: Coverage complete + if: | + github.event_name == 'workflow_run' + && github.event.workflow_run.name == 'Code coverage' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-slim + env: + GH_TOKEN: ${{ github.token }} + steps: + - name: "Set the 'pr-blocked: improve-coverage' label from the result" + env: + SHA: ${{ github.event.workflow_run.head_sha }} + LABEL: "pr-blocked: improve-coverage" + run: |- + # The run tells us which pull request it was for, and whether every + # line its patch touches is covered + if ! gh run download "${{ github.event.workflow_run.id }}" --name coverage-status; then + echo "Run has no 'coverage-status' artifact" + exit 0 + fi + STATUS=$(cat coverage-status.txt) + PR=${STATUS%% *} + COVERED=${STATUS##* } + # Its state and head as they are now, and whether it carries the + # label already + read -r STATE HEAD LABELLED < <(gh pr view "${PR}" \ + --repo "${{ github.repository }}" \ + --json state,headRefOid,labels \ + --jq '"\(.state) \(.headRefOid) \(any(.labels[]; .name == env.LABEL))"') + # Only act on a change, so a run does not reapply what is already set + if [ "${STATE}" != OPEN ]; then + # Closing removes all 'pr*' labels, do not put one back on + echo "PR #${PR} is ${STATE}" + elif [ "${HEAD}" != "${SHA}" ]; then + # Pushed to since, so this result is stale and a new run will decide + echo "PR #${PR} has moved on from ${SHA}" + elif [ "${COVERED}" = false ] && [ "${LABELLED}" = false ]; then + echo "Line coverage incomplete, blocking PR #${PR}" + gh pr edit "${PR}" --repo "${{ github.repository }}" --add-label "${LABEL}" + elif [ "${COVERED}" = true ] && [ "${LABELLED}" = true ]; then + echo "Line coverage complete, unblocking PR #${PR}" + gh pr edit "${PR}" --repo "${{ github.repository }}" --remove-label "${LABEL}" + else + echo "No change to PR #${PR}" + fi diff --git a/ci/ci-coverage-report.bash b/ci/ci-coverage-report.bash index 68a77db9c..1760bcec8 100755 --- a/ci/ci-coverage-report.bash +++ b/ci/ci-coverage-report.bash @@ -98,3 +98,36 @@ SUMMARY_TEMPLATE echo "Workflow [#${RUN_NUM}](${RUN_URL}) report: [${RUN_ID}](${REPORT_URL})" > ${NOTIFICATION_DIR}/hist.txt fi + +############################################################################### +# Create the coverage status +############################################################################### + +# 'pr-automation.yml' labels the pull request from this. Note it is only +# written when the coverage could actually be determined, so that a report we +# could not read leaves the labels of the pull request alone. + +if [ -f ${COVERAGE_DIR}/empty-patch ]; then + # Nothing to cover, so count it as covered + COVERED=true +else + # Take the counts, and not the percentage, which is rounded. The line reads + # for example " lines......: 100.00% (60 of 60 lines)" + COUNTS=$(sed -nE 's/^ *lines\.*: *[0-9.]+% \(([0-9]+) of ([0-9]+) lines\).*/\1 \2/p' \ + ${MAKE_LOG} | tail -n 1) + HIT=${COUNTS%% *} + TOTAL=${COUNTS##* } + if [ -z "${TOTAL}" ]; then + echo "Could not read the line coverage from ${MAKE_LOG}" >&2 + COVERED= + elif [ "${HIT}" = "${TOTAL}" ]; then + COVERED=true + else + COVERED=false + fi +fi + +if [ -n "${COVERED}" ]; then + echo "Line coverage complete: ${COVERED}" + echo "${PR_NUMBER} ${COVERED}" > coverage-status.txt +fi