diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a651de9..e82be0e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,14 +9,8 @@ on: - "scripts/**" - "src/**" - "tests/**" + - "uv.lock" pull_request: - paths: - - ".github/workflows/test.yml" - - "examples/**" - - "pyproject.toml" - - "scripts/**" - - "src/**" - - "tests/**" workflow_dispatch: concurrency: @@ -27,8 +21,49 @@ permissions: contents: read jobs: + # Decide whether the matrix needs to run. On push the trigger is already + # path-filtered, so this is only meaningful for pull_request, where the + # workflow must run on every PR (so the required Test check always reports) + # but the expensive matrix should only run when test-relevant files change. + # Uses plain git rather than a third-party changed-files action. + changes: + name: changes + runs-on: tenki-standard-small-2c-4g + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + + - name: Detect test-relevant changes + id: filter + run: | + set -euo pipefail + base='${{ github.event.pull_request.base.sha }}' + head='${{ github.event.pull_request.head.sha }}' + if [ -z "${base}" ]; then + # Not a pull_request (push / workflow_dispatch): the push trigger is + # already path-filtered, so run the matrix. + echo "code=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + changed=$(git diff --name-only "${base}...${head}" -- \ + .github/workflows/test.yml examples pyproject.toml scripts src tests uv.lock) + if [ -n "${changed}" ]; then + echo "Test-relevant files changed:" + echo "${changed}" + echo "code=true" >> "${GITHUB_OUTPUT}" + else + echo "No test-relevant files changed." + echo "code=false" >> "${GITHUB_OUTPUT}" + fi + protobuf-compatibility: name: protobuf ${{ matrix.protobuf }} + needs: changes + if: ${{ needs.changes.outputs.code == 'true' }} runs-on: tenki-standard-small-2c-4g strategy: fail-fast: false @@ -63,3 +98,32 @@ jobs: - name: Test run: python ${{ matrix.warning_args }} -m pytest + + # Aggregate gate: this job's name is the required status check ("Test"). It + # runs on every PR and reports once. It passes when no test-relevant files + # changed (the matrix was skipped) and otherwise only when the matrix passed. + test: + name: Test + runs-on: tenki-standard-small-2c-4g + needs: [changes, protobuf-compatibility] + if: always() + steps: + - name: Require the matrix to pass when it ran + run: | + set -euo pipefail + changes_result='${{ needs.changes.result }}' + code='${{ needs.changes.outputs.code }}' + matrix_result='${{ needs.protobuf-compatibility.result }}' + echo "changes=${changes_result} code=${code} matrix=${matrix_result}" + if [ "${changes_result}" != "success" ]; then + echo "::error::change detection did not succeed" + exit 1 + fi + if [ "${code}" != "true" ]; then + echo "No test-relevant files changed; nothing to test." + exit 0 + fi + if [ "${matrix_result}" != "success" ]; then + echo "::error::protobuf compatibility matrix did not pass" + exit 1 + fi