Browse Source

test: Introduce Infection for mutation tests (#7874)

Greg Korba 11 months ago
parent
commit
a6098f8f22
5 changed files with 59 additions and 5 deletions
  1. 23 3
      .github/workflows/ci.yml
  2. 2 0
      .gitignore
  3. 8 0
      composer.json
  4. 24 0
      infection.json5.dist
  5. 2 2
      tests/Tokenizer/TokensTest.php

+ 23 - 3
.github/workflows/ci.yml

@@ -107,6 +107,11 @@ jobs:
             run-tests: 'yes'
             collect-code-coverage: 'yes'
 
+          - operating-system: 'ubuntu-20.04'
+            php-version: '8.3'
+            job-description: 'mutation tests'
+            run-mutation-tests: 'yes'
+
           - operating-system: 'windows-latest'
             php-version: '8.3'
             job-description: 'Fixer on Windows'
@@ -151,12 +156,16 @@ jobs:
       - name: Checkout code
         uses: actions/checkout@v4
 
+      - name: Remove Infection requirement if not needed
+        if: matrix.run-mutation-tests != 'yes'
+        run: composer remove --dev infection/infection --no-update
+
       - name: Setup PHP with Composer deps
         uses: ./.github/composite-actions/setup-php-with-composer-deps
         with:
           os: ${{ runner.os }}
           php: ${{ matrix.php-version }}
-          php-coverage: ${{ matrix.collect-code-coverage }}
+          php-coverage: ${{ matrix.collect-code-coverage || matrix.run-mutation-tests }}
           composer-flags: ${{ matrix.composer-flags }}
           composer-flex-with-symfony-version: ${{ matrix.execute-flex-with-symfony-version }}
 
@@ -169,8 +178,8 @@ jobs:
           PHP_CS_FIXER_FUTURE_MODE: 1
         run: php php-cs-fixer fix --config .php-cs-fixer.php-highest.php -q
 
-      - name: Disable time limit for tests when collecting coverage
-        if: matrix.collect-code-coverage == 'yes'
+      - name: Custom PHPUnit config with v10 alignments
+        if: matrix.collect-code-coverage == 'yes' || matrix.run-mutation-tests
         run: sed -e 's/enforceTimeLimit="true"/enforceTimeLimit="false"/g' -e 's/coverage/source/g' phpunit.xml.dist > phpunit.xml
 
       - name: Disable time limit for tests under Windows # due to https://github.com/sebastianbergmann/phpunit/issues/5589
@@ -203,6 +212,14 @@ jobs:
           FAST_LINT_TEST_CASES: 1
         run: vendor/bin/paraunit coverage --testsuite unit --pass-through=--exclude-group=covers-nothing --clover=build/logs/clover.xml
 
+      - name: Run mutation tests (Infection)
+        if: matrix.run-mutation-tests == 'yes'
+        env:
+          FAST_LINT_TEST_CASES: 1
+        run: |
+          git fetch origin $GITHUB_BASE_REF
+          vendor/bin/infection --threads=max --map-source-class-to-test --git-diff-lines --git-diff-base=origin/$GITHUB_BASE_REF --ignore-msi-with-no-mutations --only-covered
+
       - name: Upload coverage results to Coveralls
         if: matrix.run-tests == 'yes' && matrix.collect-code-coverage == 'yes'
         env:
@@ -241,6 +258,9 @@ jobs:
       - name: Checkout code
         uses: actions/checkout@v4
 
+      - name: Remove Infection
+        run: composer remove --dev infection/infection --no-update
+
       - name: Setup PHP with Composer deps
         uses: ./.github/composite-actions/setup-php-with-composer-deps
         with:

+ 2 - 0
.gitignore

@@ -5,8 +5,10 @@
 /compose.override.yaml
 /composer.lock
 /dev-tools/bin/
+/dev-tools/infection/
 /dev-tools/phpstan/cache/
 /dev-tools/vendor/
+/infection.json5
 /php-cs-fixer.phar
 /php-cs-fixer.phar.asc
 /phpstan.neon

+ 8 - 0
composer.json

@@ -40,6 +40,7 @@
     },
     "require-dev": {
         "facile-it/paraunit": "^1.3 || ^2.0",
+        "infection/infection": "^0.27.11",
         "justinrainbow/json-schema": "^5.2",
         "keradus/cli-executor": "^2.1",
         "mikey179/vfsstream": "^1.6.11",
@@ -88,6 +89,7 @@
         "cs:fix": "@php php-cs-fixer fix",
         "cs:fix:parallel": "echo '🔍 Will run in batches of 50 files.'; if [[ -f .php-cs-fixer.php ]]; then FIXER_CONFIG=.php-cs-fixer.php; else FIXER_CONFIG=.php-cs-fixer.dist.php; fi; php php-cs-fixer list-files --config=$FIXER_CONFIG | xargs -n 50 -P 8 php php-cs-fixer fix --config=$FIXER_CONFIG --path-mode intersection 2> /dev/null",
         "docs": "@php dev-tools/doc.php",
+        "infection": "@test:mutation",
         "install-tools": "@composer --working-dir=dev-tools install",
         "mess-detector": "@php dev-tools/vendor/bin/phpmd . ansi dev-tools/mess-detector/phpmd.xml --exclude vendor/*,dev-tools/vendor/*,dev-tools/phpstan/*,tests/Fixtures/*",
         "normalize": [
@@ -132,6 +134,10 @@
             "Composer\\Config::disableProcessTimeout",
             "paraunit run --testsuite integration"
         ],
+        "test:mutation": [
+            "Composer\\Config::disableProcessTimeout",
+            "infection --threads=max --only-covered --min-covered-msi=80"
+        ],
         "test:short-open-tag": [
             "Composer\\Config::disableProcessTimeout",
             "@php -d short_open_tag=1 ./vendor/bin/phpunit --do-not-cache-result --testsuite short-open-tag"
@@ -152,6 +158,7 @@
         "cs:fix": "Fix coding standards",
         "cs:fix:parallel": "Fix coding standards in naive parallel mode (using xargs)",
         "docs": "Regenerate docs",
+        "infection": "Alias for 'test:mutation'",
         "install-tools": "Install DEV tools",
         "mess-detector": "Analyse code with Mess Detector",
         "normalize": "Run normalization for composer.json files",
@@ -168,6 +175,7 @@
         "test:all": "Run Unit and Integration tests (but *NOT* Smoke tests)",
         "test:coverage": "Run tests that provide code coverage",
         "test:integration": "Run Integration tests",
+        "test:mutation": "Run mutation tests",
         "test:short-open-tag": "Run tests with \"short_open_tag\" enabled",
         "test:smoke": "Run Smoke tests",
         "test:unit": "Run Unit tests",

+ 24 - 0
infection.json5.dist

@@ -0,0 +1,24 @@
+{
+    "$schema": "vendor/infection/infection/resources/schema.json",
+    "testFrameworkOptions": "--testsuite=unit",
+    "source": {
+        "directories": [
+            "src"
+        ]
+    },
+    "mutators": {
+        "@default": true,
+        "Throw_": {
+            "ignore": [
+                // It makes `tests/Fixtures/cache-file-handler/cache-file` unreadable (permissions)
+                "PhpCsFixer\\Cache\\FileHandler"
+            ]
+        },
+        "LogicalNot": {
+            "ignore": [
+                // Causes modifications in `tests/Fixtures/FixerTest/fix/*.php`
+                "PhpCsFixer\\Runner\\Runner::fixFile"
+            ]
+        }
+    }
+}

+ 2 - 2
tests/Tokenizer/TokensTest.php

@@ -1701,8 +1701,8 @@ $bar;',
         }
 
         yield 'overlapping inserts of bunch of comments ' => [
-            Tokens::fromCode(sprintf("<?php\n%s/* line 1 */\n%s/* line 2 */\n%s/* line 3 */%s", $sets[0]['content'], $sets[1]['content'], $sets[2]['content'], $sets[3]['content'])),
-            Tokens::fromCode("<?php\n/* line 1 */\n/* line 2 */\n/* line 3 */"),
+            Tokens::fromCode(sprintf("<?php\n%s/* line #1 */\n%s/* line #2 */\n%s/* line #3 */%s", $sets[0]['content'], $sets[1]['content'], $sets[2]['content'], $sets[3]['content'])),
+            Tokens::fromCode("<?php\n/* line #1 */\n/* line #2 */\n/* line #3 */"),
             [1 => $sets[0]['tokens'], 3 => $sets[1]['tokens'], 5 => $sets[2]['tokens'], 6 => $sets[3]['tokens']],
         ];
     }