Skip to content

[Bug]: [mutate] Windows: the 5 s minimum per-mutant timeout is shorter than a child boot under Xdebug, so untested mutants are scored as killed #1968

Open

Description

@drpark

What Happened

On Windows, pest --mutate scores some mutants as killed when no test can kill them, because their child process times out before the covering test has even run.

MutationTest::calculateTimeout() gives each mutant run the initial suite duration plus max(5, 20%):

return (int) ($initialTestSuiteDuration + max(5, $initialTestSuiteDuration * 0.2));

When the targeted test set is small (a single test file, as when mutating one class), the initial suite takes ~2 s, so each child gets ~7 s. On Windows, booting a child Pest process (Laravel app) takes 4 to 7 s on its own, partly because children inherit XDEBUG_MODE=coverage and the --coverage-php=... argument (see the TODO: filter arguments to remove unnecessary stuff (Teamcity, Coverage, etc.) in MutationTest::start()). Some legitimate runs exceed the budget, hasFinished() maps the ProcessTimedOutException to Timeout, and the score counts Timeout as killed.

Expected: a mutant that no test kills is reported as untested, regardless of process start-up time.

How to Reproduce

  1. On Windows, with Xdebug, write a test that executes a class but asserts nothing about it:

    it('runs the code without checking anything', function () {
        (new SomeValueObject(120.5, 80.0))->format();
    
        expect(true)->toBeTrue();
    });
  2. XDEBUG_MODE=coverage php vendor/bin/pest tests/Unit/SanityTest.php --mutate --class='App\Domain\SomeValueObject' --covered-only --no-cache

Result: Mutations: 15 untested, 6 timeout, 0 tested, score 28.57 %. No test can kill any of these 21 mutants, so the expected score is 0 %.

With the minimum raised from 5 to 30 s on Windows (max(PHP_OS_FAMILY === 'Windows' ? 30 : 5, ...)), the same run reports 21 untested, 0 tested, score 0.00 %.

This was measured with the child-launch fix from #1916 applied (otherwise every mutant is scored as killed and this bug is invisible).

Sample Repository

None (private project). The reproduction above only needs any Laravel app with Pest and one class.

Pest Version

3.8.6 (pestphp/pest-plugin-mutate v3.0.5, symfony/process v7.4.8)

PHP Version

8.4.15 ZTS (x64), Xdebug 3.5.3

Operation System

Windows

Notes

Filed here because issues are disabled on pestphp/pest-plugin-mutate.

Related: #1843 (timed-out mutants scored as killed, different root cause: a deadlock) and #1916 (child process fails to launch on Windows, also scored as killed). pestphp/pest-plugin-mutate#45 would make all three visible instead of silent.

Possible fixes, from least to most invasive:

  • do not pass coverage arguments/env to the per-mutant children (the existing TODO), which removes most of the start-up cost;
  • a higher minimum timeout, or a --mutate-timeout option;
  • report Timeout separately from kills in the score.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions