From ee427891029e6e266c3aa3aa1e3f454868a1712f Mon Sep 17 00:00:00 2001 From: Richard Anabeto Opoku Date: Tue, 31 Mar 2026 17:17:25 +0000 Subject: [PATCH 1/3] run in coroutines fix for feature tests. --- .../Testing/Concerns/RunTestsInCoroutine.php | 32 ++++++++++++------- src/foundation/src/Testing/TestCase.php | 9 ++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/foundation/src/Testing/Concerns/RunTestsInCoroutine.php b/src/foundation/src/Testing/Concerns/RunTestsInCoroutine.php index e6d256528..2b82c54b6 100644 --- a/src/foundation/src/Testing/Concerns/RunTestsInCoroutine.php +++ b/src/foundation/src/Testing/Concerns/RunTestsInCoroutine.php @@ -14,6 +14,13 @@ use function Hypervel\Coroutine\run; /** + * Wraps each test method in a Swoole coroutine so that database connections, + * channels, and other coroutine-dependent APIs work correctly during tests. + * + * PHPUnit 10.5 made runTest() private, so we can no longer override it. + * Instead, we swap the test method name during setUp() — which runs before + * PHPUnit's private runTest() calls $this->{$this->name}(). + * * @method string name() */ trait RunTestsInCoroutine @@ -24,9 +31,22 @@ trait RunTestsInCoroutine protected string $realTestName = ''; + /** + * Swap the test method name so PHPUnit's private runTest() calls + * runTestsInCoroutine() instead of the real test method. + * The real test method is then executed inside a Swoole coroutine. + */ + protected function setUpCoroutineTest(): void + { + if (Coroutine::getCid() === -1 && $this->enableCoroutine) { + $this->realTestName = $this->name(); + $this->setName('runTestsInCoroutine'); + } + } + final protected function runTestsInCoroutine(...$arguments) { - parent::setName($this->realTestName); + $this->setName($this->realTestName); $testResult = null; $exception = null; @@ -56,16 +76,6 @@ final protected function runTestsInCoroutine(...$arguments) return $testResult; } - final protected function runTest(): mixed - { - if (Coroutine::getCid() === -1 && $this->enableCoroutine) { - $this->realTestName = $this->name(); - parent::setName('runTestsInCoroutine'); - } - - return parent::runTest(); - } - protected function invokeSetupInCoroutine(): void { if (method_exists($this, 'setUpInCoroutine')) { diff --git a/src/foundation/src/Testing/TestCase.php b/src/foundation/src/Testing/TestCase.php index 24f8af7df..b05671348 100644 --- a/src/foundation/src/Testing/TestCase.php +++ b/src/foundation/src/Testing/TestCase.php @@ -70,6 +70,15 @@ protected function setUp(): void } $this->setUpHasRun = true; + + // Swap the test method name for coroutine wrapping. + // This must happen AFTER setUp completes but BEFORE PHPUnit's + // private runTest() calls $this->{$this->name}(). + // PHPUnit 10.5 made runTest() private, so we can no longer + // override it — the name swap in setUp() is the only hook point. + if (method_exists($this, 'setUpCoroutineTest')) { + $this->setUpCoroutineTest(); + } } /** From 81a45d5bcf3782a2637fa070754a3715313d3911 Mon Sep 17 00:00:00 2001 From: Richard Anabeto Opoku Date: Tue, 31 Mar 2026 17:35:28 +0000 Subject: [PATCH 2/3] tests to validate the coroutine testing changes. --- .../Concerns/RunTestsInCoroutineTest.php | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/Foundation/Testing/Concerns/RunTestsInCoroutineTest.php diff --git a/tests/Foundation/Testing/Concerns/RunTestsInCoroutineTest.php b/tests/Foundation/Testing/Concerns/RunTestsInCoroutineTest.php new file mode 100644 index 000000000..afc90c12b --- /dev/null +++ b/tests/Foundation/Testing/Concerns/RunTestsInCoroutineTest.php @@ -0,0 +1,146 @@ +assertSame(-1, Coroutine::getCid()); + $this->assertSame('myTestMethod', $testCase->name()); + + $method = new ReflectionMethod($testCase, 'setUpCoroutineTest'); + $method->invoke($testCase); + + $this->assertSame('runTestsInCoroutine', $testCase->name()); + + $realName = new ReflectionProperty($testCase, 'realTestName'); + $this->assertSame('myTestMethod', $realName->getValue($testCase)); + } + + public function testSetUpCoroutineTestDoesNotSwapWhenCoroutineDisabled() + { + $testCase = new CoroutineDisabledTestStub('myTestMethod'); + + $method = new ReflectionMethod($testCase, 'setUpCoroutineTest'); + $method->invoke($testCase); + + $this->assertSame('myTestMethod', $testCase->name()); + } + + public function testSetUpCoroutineTestIsNoOpInsideCoroutine() + { + $testCase = new CoroutineTestStub('myTestMethod'); + + \Hypervel\Coroutine\run(function () use ($testCase) { + $this->assertGreaterThan(-1, Coroutine::getCid()); + + $method = new ReflectionMethod($testCase, 'setUpCoroutineTest'); + $method->invoke($testCase); + + $this->assertSame('myTestMethod', $testCase->name()); + }); + } + + public function testRunTestsInCoroutineExecutesInCoroutine() + { + $testCase = new CoroutineTestStub('myTestMethod'); + + $setUp = new ReflectionMethod($testCase, 'setUpCoroutineTest'); + $setUp->invoke($testCase); + + $run = new ReflectionMethod($testCase, 'runTestsInCoroutine'); + $run->invoke($testCase); + + $this->assertTrue($testCase->executedInCoroutine); + } + + public function testRunTestsInCoroutineRestoresOriginalName() + { + $testCase = new CoroutineTestStub('myTestMethod'); + + $setUp = new ReflectionMethod($testCase, 'setUpCoroutineTest'); + $setUp->invoke($testCase); + + $this->assertSame('runTestsInCoroutine', $testCase->name()); + + $run = new ReflectionMethod($testCase, 'runTestsInCoroutine'); + $run->invoke($testCase); + + $this->assertSame('myTestMethod', $testCase->name()); + } + + public function testRunTestsInCoroutinePropagatesExceptions() + { + $testCase = new CoroutineExceptionTestStub('throwingMethod'); + + $setUp = new ReflectionMethod($testCase, 'setUpCoroutineTest'); + $setUp->invoke($testCase); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Test exception from coroutine'); + + $run = new ReflectionMethod($testCase, 'runTestsInCoroutine'); + $run->invoke($testCase); + } +} + +/** + * @internal + */ +class CoroutineTestStub extends \PHPUnit\Framework\TestCase +{ + use RunTestsInCoroutine; + + public bool $executedInCoroutine = false; + + public function myTestMethod(): void + { + $this->executedInCoroutine = Coroutine::getCid() > -1; + } +} + +/** + * @internal + */ +class CoroutineDisabledTestStub extends \PHPUnit\Framework\TestCase +{ + use RunTestsInCoroutine; + + public function __construct(string $name) + { + parent::__construct($name); + $this->enableCoroutine = false; + } + + public function myTestMethod(): void + { + } +} + +/** + * @internal + */ +class CoroutineExceptionTestStub extends \PHPUnit\Framework\TestCase +{ + use RunTestsInCoroutine; + + public function throwingMethod(): void + { + throw new \RuntimeException('Test exception from coroutine'); + } +} From fb1ce58040674d6549d70f2e6cd2fca543eaf62a Mon Sep 17 00:00:00 2001 From: Richard Anabeto Opoku Date: Tue, 31 Mar 2026 17:44:26 +0000 Subject: [PATCH 3/3] linting. --- .../Testing/Concerns/RunTestsInCoroutineTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/Foundation/Testing/Concerns/RunTestsInCoroutineTest.php b/tests/Foundation/Testing/Concerns/RunTestsInCoroutineTest.php index afc90c12b..29a20de16 100644 --- a/tests/Foundation/Testing/Concerns/RunTestsInCoroutineTest.php +++ b/tests/Foundation/Testing/Concerns/RunTestsInCoroutineTest.php @@ -8,6 +8,7 @@ use Hypervel\Tests\TestCase; use ReflectionMethod; use ReflectionProperty; +use RuntimeException; use Swoole\Coroutine; /** @@ -91,7 +92,7 @@ public function testRunTestsInCoroutinePropagatesExceptions() $setUp = new ReflectionMethod($testCase, 'setUpCoroutineTest'); $setUp->invoke($testCase); - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Test exception from coroutine'); $run = new ReflectionMethod($testCase, 'runTestsInCoroutine'); @@ -101,6 +102,7 @@ public function testRunTestsInCoroutinePropagatesExceptions() /** * @internal + * @coversNothing */ class CoroutineTestStub extends \PHPUnit\Framework\TestCase { @@ -116,6 +118,7 @@ public function myTestMethod(): void /** * @internal + * @coversNothing */ class CoroutineDisabledTestStub extends \PHPUnit\Framework\TestCase { @@ -134,6 +137,7 @@ public function myTestMethod(): void /** * @internal + * @coversNothing */ class CoroutineExceptionTestStub extends \PHPUnit\Framework\TestCase { @@ -141,6 +145,6 @@ class CoroutineExceptionTestStub extends \PHPUnit\Framework\TestCase public function throwingMethod(): void { - throw new \RuntimeException('Test exception from coroutine'); + throw new RuntimeException('Test exception from coroutine'); } }