diff --git a/.gitattributes b/.gitattributes index 3089fa5..67be74b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ tests export-ignore .php-cs-fixer.dist.php export-ignore psalm-autoload.php export-ignore testo.php export-ignore +infection.json5 export-ignore composer.lock export-ignore composer-require-* export-ignore Makefile export-ignore diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..46d652c --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,49 @@ +--- + +name: Mutation Tests + +on: # yamllint disable-line rule:truthy + pull_request: + push: + branches: [master] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + mutation: + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ“ฆ Check out the codebase + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: ๐Ÿ› ๏ธ Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: xdebug + + - name: ๐Ÿ“ฅ Install dependencies with composer + uses: ramsey/composer-install@v3 + with: + dependency-versions: highest + + # Not the Phar: its bundled testo/bridge-infection writes the unscoped `\Infection\StreamWrapper\IncludeInterceptor` + # into the mutant bootstrap, while the Phar ships the class as `Infected\...`, so every mutant fails with + # "class not found". Infection needs PHP 8.3+, so it is required here only, not in composer.json. + - name: ๐Ÿงฌ Install Infection + run: | + composer config allow-plugins.infection/extension-installer true + composer require --dev --no-interaction --no-progress infection/infection testo/bridge-infection + + - name: ๐Ÿงช Run mutation testing + run: vendor/bin/infection --threads=max --logger-github --show-mutations=0 --min-msi=80 + env: + STRYKER_DASHBOARD_API_KEY: ${{ github.event_name == 'push' && secrets.STRYKER_DASHBOARD_API_KEY || '' }} diff --git a/README.md b/README.md index b49a221..6365260 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ composer require --dev spiral/code-style [![Latest Version on Packagist](https://img.shields.io/packagist/v/spiral/code-style.svg?style=flat-square&logo=packagist)](https://packagist.org/packages/spiral/code-style) [![License](https://img.shields.io/packagist/l/spiral/code-style.svg?style=flat-square)](LICENSE.md) [![Total Downloads](https://img.shields.io/packagist/dt/spiral/code-style.svg?style=flat-square)](https://packagist.org/packages/spiral/code-style) +[![Mutation testing badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fspiral%2Fcode-style%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/spiral/code-style/master) Create a configuration file `.php-cs-fixer.dist.php` in the root of your project: diff --git a/infection.json5 b/infection.json5 new file mode 100644 index 0000000..c33705e --- /dev/null +++ b/infection.json5 @@ -0,0 +1,21 @@ +{ + "$schema": "https://raw.githubusercontent.com/infection/infection/master/resources/schema.json", + "source": { + "directories": ["src"], + // The rule set is configuration data: a mutated rule only changes the style, and pinning every rule in a + // unit test would just duplicate the list + "excludes": ["Rules/DefaultRules.php"] + }, + "testFramework": "testo", + // Acceptance tests run php-cs-fixer in a subprocess: they add no coverage, so they can't kill a mutant + "testFrameworkOptions": "--suite=Unit", + "tmpDir": "runtime", + "timeout": 10, + "logs": { + "text": "runtime/infection.log", + "github": true, + "stryker": { + "report": "master" + } + } +}