pestphp / pestphp/pest

[Bug]: StateGenerator TypeError with AfterLastTestMethodErrored in parallel mode

Open Beginner friendly
#1,623 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
11.7k
Forks
538
Avg merge
4d 11h
Merged PRs (30d)
8

Description

Pest Version

v4.3.2

PHP Version

8.4

Operation System

macOS / Linux

Description

When running tests in parallel mode (--parallel), if any test triggers an AfterLastTestMethodErrored event (e.g., errors during teardown/cleanup), Pest crashes with a TypeError:

TypeError: NunoMaduro\Collision\Adapters\Phpunit\TestResult::fromBeforeFirstTestMethodErrored(): 
Argument #1 ($event) must be of type PHPUnit\Event\Test\BeforeFirstTestMethodErrored, 
PHPUnit\Event\Test\AfterLastTestMethodErrored given, 
called in vendor/pestphp/pest/src/Support/StateGenerator.php on line 34
Root Cause

In src/Support/StateGenerator.php, the code assumes all non-Errored events from testErroredEvents() are BeforeFirstTestMethodErrored:

foreach ($testResult->testErroredEvents() as $testResultEvent) {
    if ($testResultEvent instanceof Errored) {
        // ... handle Errored
    } else {
        // @phpstan-ignore-next-line
        $state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent));
    }
}

However, testErroredEvents() can also return AfterLastTestMethodErrored events, which are then incorrectly passed to fromBeforeFirstTestMethodErrored().

Suggested Fix

Add an explicit instanceof check for BeforeFirstTestMethodErrored:

use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;

foreach ($testResult->testErroredEvents() as $testResultEvent) {
    if ($testResultEvent instanceof Errored) {
        $state->add(TestResult::fromPestParallelTestCase(
            $testResultEvent->test(),
            TestResult::FAIL,
            $testResultEvent->throwable()
        ));
    } elseif ($testResultEvent instanceof BeforeFirstTestMethodErrored) {
        $state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent));
    }
    // AfterLastTestMethodErrored events can be skipped or handled separately
}
Steps to Reproduce
  1. Create a test that causes an error during teardown (e.g., database constraint violation in afterEach)
  2. Run tests with --parallel flag
  3. Observe the TypeError crash
Workaround

We've created a composer patch that fixes this issue:

--- a/src/Support/StateGenerator.php
+++ b/src/Support/StateGenerator.php
@@ -10,6 +10,7 @@
 use PHPUnit\Event\Code\TestDoxBuilder;
 use PHPUnit\Event\Code\TestMethod;
 use PHPUnit\Event\Code\ThrowableBuilder;
+use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
 use PHPUnit\Event\Test\Errored;
 use PHPUnit\Event\TestData\TestDataCollection;
 use PHPUnit\Framework\SkippedWithMessageException;
@@ -29,10 +30,11 @@
                     TestResult::FAIL,
                     $testResultEvent->throwable()
                 ));
-            } else {
-                // @phpstan-ignore-next-line
+            } elseif ($testResultEvent instanceof BeforeFirstTestMethodErrored) {
                 $state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent));
             }
+            // AfterLastTestMethodErrored events are intentionally skipped
         }

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/Support/StateGenerator.php and inspect the loop over testErroredEvents() and its event type handling. Reproduce the failure with --parallel and a teardown error, then verify that BeforeFirstTestMethodErrored events still produce results while AfterLastTestMethodErrored events no longer cause a TypeError.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
testing
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.