From b3d5d9bebeb100e172d09f9f08a9a9e0c04ffd5a Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 9 Oct 2026 23:53:09 +0400 Subject: [PATCH] ci: run mutation tests with Infection test: check that TTLs are rounded to the nearest microsecond Infection is installed in the workflow rather than from the phar or require-dev: the 0.35.6 phar's Testo adapter calls the unscoped IncludeInterceptor, and Infection needs PHP 8.3+ while the package supports 8.2. The three remaining escaped mutants are equivalent: rounding or casting the already integral DateInterval product. Assisted-By: Claude Opus 5.5 --- .gitattributes | 1 + .github/workflows/mutation.yml | 45 ++++++++++++++++++++++++++++++++++ README.md | 1 + infection.json5 | 16 ++++++++++++ tests/src/LockTest.php | 2 ++ 5 files changed, 65 insertions(+) create mode 100644 .github/workflows/mutation.yml create mode 100644 infection.json5 diff --git a/.gitattributes b/.gitattributes index 89decca..e772283 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,4 +7,5 @@ /.php-cs-fixer.dist.php export-ignore /psalm.xml export-ignore /psalm.xml.dist export-ignore +/infection.json5 export-ignore /UPGRADING.md export-ignore diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..018e523 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,45 @@ +name: Mutation Tests + +on: # yamllint disable-line rule:truthy + pull_request: + push: + branches: [1.x] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + mutation: + name: Infection + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: sockets + coverage: xdebug + ini-values: memory_limit=-1, zend.assertions=1 + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + with: + dependency-versions: highest + + # The Infection phar is unusable with Testo: its bundled adapter calls the + # unscoped \Infection\StreamWrapper\IncludeInterceptor. Infection requires + # PHP 8.3+, so it is installed here instead of in require-dev. + - name: Install Infection + run: | + composer config allow-plugins.infection/extension-installer true + composer require --dev --no-interaction --no-progress -W infection/infection testo/bridge-infection + + - name: Run Infection + run: vendor/bin/infection --threads=max --logger-github --show-mutations=0 --min-msi=90 --min-covered-msi=90 + env: + STRYKER_DASHBOARD_API_KEY: ${{ github.event_name == 'push' && secrets.STRYKER_DASHBOARD_API_KEY || '' }} diff --git a/README.md b/README.md index e868ff4..89bbccf 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ [![Psalm Level](https://shepherd.dev/github/roadrunner-php/lock/level.svg)](https://shepherd.dev/github/roadrunner-php/lock) [![Type Coverage](https://shepherd.dev/github/roadrunner-php/lock/coverage.svg)](https://shepherd.dev/github/roadrunner-php/lock) +[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Froadrunner-php%2Flock%2F1.x)](https://dashboard.stryker-mutator.io/reports/github.com/roadrunner-php/lock/1.x) diff --git a/infection.json5 b/infection.json5 new file mode 100644 index 0000000..4574167 --- /dev/null +++ b/infection.json5 @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/infection/infection/0.35.6/resources/schema.json", + "source": { + "directories": ["src"] + }, + "testFramework": "testo", + "tmpDir": "runtime", + "timeout": 10, + "logs": { + "text": "runtime/infection.log", + "github": true, + "stryker": { + "report": "1.x" + } + } +} diff --git a/tests/src/LockTest.php b/tests/src/LockTest.php index b95a21f..4311340 100644 --- a/tests/src/LockTest.php +++ b/tests/src/LockTest.php @@ -69,6 +69,8 @@ public static function lockDataProvider(): \Generator yield 'float' => [0.000_01, 10, 0.000_004, 4,]; + yield 'float rounded to microseconds' => [0.000_001_6, 2, 0.000_001_4, 1,]; + yield 'date-interval' => [ new \DateInterval('PT10S'), 10_000_000,