Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/php.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,37 @@ jobs:
# binary's GPG signature endpoint failing) must not fail the build.
fail_ci_if_error: false
flags: unittests

mutation-tests:
name: Mutation tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Create docker network
run: |
docker network create frontend

# The GITHUB_* variables are passed into the container so Infection's
# CI detection works: --logger-github emits inline PR annotations for
# escaped mutants, and the Stryker dashboard logger (configured in
# infection.json5) publishes the report on pushes to develop. The
# minimum mutation score is enforced via minCoveredMsi in
# infection.json5.
- name: Run Infection
env:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
run: |
docker compose run --rm phpfpm composer install
docker compose run --rm \
-e XDEBUG_MODE=coverage \
-e GITHUB_ACTIONS \
-e GITHUB_REF \
-e GITHUB_HEAD_REF \
-e GITHUB_SHA \
-e GITHUB_REPOSITORY \
-e GITHUB_EVENT_NAME \
-e GITHUB_RUN_ID \
-e GITHUB_SERVER_URL \
-e STRYKER_DASHBOARD_API_KEY \
phpfpm vendor/bin/infection --logger-github
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ unit.xml

# Local Claude Code instructions (not part of the published package)
/CLAUDE.md

###> infection/infection ###
/infection.log
/infection.html
###< infection/infection ###
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Dev: mutation testing with [Infection](https://infection.github.io/)
(`task test:mutation`). The minimum mutation score is configured in
`infection.json5` and enforced in CI; escaped mutants are annotated inline
on pull requests, and results for `develop` are published to the Stryker
dashboard (mutation score badge in the README). No effect on the published
package.

### Changed

- CI: bumped `codecov/codecov-action` from `v5` to `v7` (restores Codecov's
Expand Down
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[![PHP Version](https://img.shields.io/packagist/php-v/itk-dev/openid-connect-bundle.svg?style=flat-square&colorB=%238892BF)](https://www.php.net/downloads)
[![Build Status](https://img.shields.io/github/actions/workflow/status/itk-dev/openid-connect-bundle/php.yaml?branch=develop&label=CI&logo=github&style=flat-square)](https://github.com/itk-dev/openid-connect-bundle/actions/workflows/php.yaml?query=branch%3Adevelop)
[![Codecov Code Coverage](https://img.shields.io/codecov/c/gh/itk-dev/openid-connect-bundle?label=codecov&logo=codecov&style=flat-square)](https://codecov.io/gh/itk-dev/openid-connect-bundle)
[![Mutation Score](https://img.shields.io/endpoint?style=flat-square&label=mutation%20score&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fitk-dev%2Fopenid-connect-bundle%2Fdevelop)](https://dashboard.stryker-mutator.io/reports/github.com/itk-dev/openid-connect-bundle/develop)
[![Read License](https://img.shields.io/packagist/l/itk-dev/openid-connect-bundle.svg?style=flat-square&colorB=darkcyan)](https://github.com/itk-dev/openid-connect-bundle/blob/master/LICENSE.md)
[![Package downloads on Packagist](https://img.shields.io/packagist/dt/itk-dev/openid-connect-bundle.svg?style=flat-square&colorB=darkmagenta)](https://packagist.org/packages/itk-dev/openid-connect-bundle/stats)

Expand Down Expand Up @@ -472,6 +473,27 @@ task test:matrix
This runs PHPUnit with coverage for each combination and prints a summary of
pass/fail results.

### Mutation Testing

Line coverage shows which code the tests *execute*; mutation testing shows
which code they actually *verify*. [Infection](https://infection.github.io/)
applies small changes (mutants) to the source code — flipping a comparison,
removing a method call — and runs the test suite against each one. If the
tests still pass, the mutant "escaped": a potential bug the tests would not
catch.

```shell
task test:mutation
```

The minimum mutation score (`minCoveredMsi`) is defined in `infection.json5`
and enforced both locally and in CI — no command line flags needed. CI
annotates escaped mutants inline on pull requests, and results for `develop`
are published to the
[Stryker dashboard](https://dashboard.stryker-mutator.io/reports/github.com/itk-dev/openid-connect-bundle/develop),
which also feeds the mutation score badge above. Detailed reports are written
to `infection.log` and `infection.html` on each run.

### PHPStan Static Analysis

```shell
Expand Down Expand Up @@ -510,7 +532,8 @@ Run `task --list` to see all available tasks.

## CI

GitHub Actions are used to run the test suite and code style checks on all PRs.
GitHub Actions are used to run the test suite, mutation tests and code style
checks on all PRs.

## Versioning

Expand Down
6 changes: 6 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ tasks:
cmds:
- "{{.DOCKER_COMPOSE}} exec -e XDEBUG_MODE=coverage phpfpm vendor/bin/phpunit --coverage-text --coverage-clover=coverage/unit.xml"

test:mutation:
desc: Run Infection mutation testing (threshold configured in infection.json5)
cmds:
- "{{.DOCKER_COMPOSE}} exec -e XDEBUG_MODE=coverage phpfpm vendor/bin/infection"

test:run:
desc: "Run tests for a PHP version and dependency set (e.g. task test:run PHP=8.4 DEPS=lowest)"
vars:
Expand Down Expand Up @@ -250,3 +255,4 @@ tasks:
- task: lint
- task: analyze:php
- task: test:matrix
- task: test:mutation
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"require-dev": {
"ergebnis/composer-normalize": "^2.28",
"friendsofphp/php-cs-fixer": "^3.11",
"infection/infection": "*",
"phpstan/phpstan": "^2.1.41",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
Expand All @@ -51,6 +52,7 @@
"config": {
"allow-plugins": {
"ergebnis/composer-normalize": true,
"infection/extension-installer": true,
"symfony/runtime": true
},
"sort-packages": true
Expand Down
23 changes: 23 additions & 0 deletions infection.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "vendor/infection/infection/resources/schema.json",
"source": {
"directories": ["src"]
},
"threads": "max",
// Minimum mutation score for covered code; enforced locally and in CI.
// Baseline measured at 80% — ratchet up as surviving mutants are killed.
"minCoveredMsi": 78,
"logs": {
"text": "infection.log",
"html": "infection.html",
// Publishes results for the develop branch to the Stryker dashboard
// (feeds the README badge). Requires STRYKER_DASHBOARD_API_KEY; only
// active on CI, skipped locally and on non-matching branches.
"stryker": {
"report": "develop"
}
},
"mutators": {
"@default": true
}
}
Loading