diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 43bbc64fd39..467616a1f8a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -269,11 +269,56 @@ jobs: - name: "Tests" run: "make tests" + mutation-testing-changed-files: + name: "Mutation Testing: changed files" + runs-on: "ubuntu-latest" + if: github.event_name == 'pull_request' + + permissions: + contents: read + + env: + # The branch the diff is taken against - the branch this file lives on. + # Flipped by the "Open X.Y.x" commit, like the push branch above. + MUTATION_TESTING_BASE_BRANCH: "2.2.x" + + outputs: + base-branch: ${{ env.MUTATION_TESTING_BASE_BRANCH }} + any: ${{ steps.changed-files.outputs.any }} + files: ${{ steps.changed-files.outputs.files }} + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: "Checkout" + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + persist-credentials: false + + # The same files Infection picks with --git-diff-base and its default AM + # filter over its source directories + - name: "Find changed source files" + id: changed-files + run: | + git fetch --depth=1 origin "$MUTATION_TESTING_BASE_BRANCH" + base=$(git merge-base "origin/$MUTATION_TESTING_BASE_BRANCH" HEAD || echo "origin/$MUTATION_TESTING_BASE_BRANCH") + files=$(git diff "$base" --diff-filter=AM --name-only -- src build/PHPStan/Build | grep '\.php$' || true) + echo "$files" + { + if [ -n "$files" ]; then echo "any=true"; else echo "any=false"; fi + echo 'files<> "$GITHUB_OUTPUT" + mutation-testing: name: "Mutation Testing" runs-on: "ubuntu-latest" - needs: ["tests"] - if: github.event_name == 'pull_request' + needs: ["tests", "mutation-testing-changed-files"] + if: github.event_name == 'pull_request' && needs.mutation-testing-changed-files.outputs.any == 'true' strategy: fail-fast: false @@ -328,11 +373,6 @@ jobs: > infection.json5 jq < infection.json5 - - name: "Determine default branch" - id: default-branch - run: | - echo "name=$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')" >> "$GITHUB_OUTPUT" - - name: "Restore result cache" uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 with: @@ -341,10 +381,34 @@ jobs: restore-keys: | result-cache-v1-${{ matrix.php-version }}- + # Infection reads the coverage report of every source file it lists, and + # the whole of src/ outgrew the runner's memory - only the mutated files + # need it + - name: "Restrict coverage to the changed source files" + env: + CHANGED_FILES: ${{ needs.mutation-testing-changed-files.outputs.files }} + run: | + echo "$CHANGED_FILES" > "$RUNNER_TEMP/changed-files.txt" + php -- "$RUNNER_TEMP/changed-files.txt" <<'PHP' + load('phpunit.xml'); + $include = $document->getElementsByTagName('source')->item(0)->getElementsByTagName('include')->item(0); + while ($include->firstChild !== null) { + $include->removeChild($include->firstChild); + } + foreach (file($argv[1], FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $file) { + $include->appendChild($document->createElement('file', $file)); + } + $document->save('phpunit-mutation-testing.xml'); + PHP + grep -A5 "