diff --git a/co-phpunit b/co-phpunit index 53832d0..496616e 100755 --- a/co-phpunit +++ b/co-phpunit @@ -13,19 +13,10 @@ if (!version_compare(PHP_VERSION, PHP_VERSION, '=')) { fwrite(STDERR, sprintf('%s declares an invalid value for PHP_VERSION.' . PHP_EOL . 'This breaks fundamental functionality such as version_compare().' . PHP_EOL . 'Please use a different PHP interpreter.' . PHP_EOL, PHP_BINARY)); die(1); } -if (version_compare('8.1.0', PHP_VERSION, '>')) { - fwrite(STDERR, sprintf('This version of PHPUnit requires PHP >= 8.1.' . PHP_EOL . 'You are using PHP %s (%s).' . PHP_EOL, PHP_VERSION, PHP_BINARY)); +if (version_compare('8.2.0', PHP_VERSION, '>')) { + fwrite(STDERR, sprintf('This version of PHPUnit requires PHP >= 8.2.' . PHP_EOL . 'You are using PHP %s (%s).' . PHP_EOL, PHP_VERSION, PHP_BINARY)); die(1); } -$requiredExtensions = ['dom', 'json', 'libxml', 'mbstring', 'tokenizer', 'xml', 'xmlwriter']; -$unavailableExtensions = array_filter($requiredExtensions, static function ($extension) { - return !extension_loaded($extension); -}); -if ([] !== $unavailableExtensions) { - fwrite(STDERR, sprintf('PHPUnit requires the "%s" extensions, but the "%s" %s not available.' . PHP_EOL, implode('", "', $requiredExtensions), implode('", "', $unavailableExtensions), count($unavailableExtensions) === 1 ? 'extension is' : 'extensions are')); - die(1); -} -unset($requiredExtensions, $unavailableExtensions); if (!ini_get('date.timezone')) { ini_set('date.timezone', 'UTC'); } @@ -45,6 +36,19 @@ if (!defined('PHPUNIT_COMPOSER_INSTALL')) { fwrite(STDERR, 'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL . ' composer install' . PHP_EOL . PHP_EOL . 'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL); die(1); } +require PHPUNIT_COMPOSER_INSTALL; +$requiredExtensions = ['dom', 'json', 'libxml', 'mbstring', 'tokenizer', 'xml', 'xmlwriter']; +$unavailableExtensions = array_filter($requiredExtensions, static function ($extension) { + return !extension_loaded($extension); +}); +// Workaround for https://github.com/sebastianbergmann/phpunit/issues/5662 +if (!function_exists('ctype_alnum')) { + $unavailableExtensions[] = 'ctype'; +} +if ([] !== $unavailableExtensions) { + fwrite(STDERR, sprintf('PHPUnit requires the "%s" extensions, but the "%s" %s not available.' . PHP_EOL, implode('", "', $requiredExtensions), implode('", "', $unavailableExtensions), count($unavailableExtensions) === 1 ? 'extension is' : 'extensions are')); + die(1); +} (function () { $prepend = null; foreach ($_SERVER["argv"] as $index => $argv) { @@ -68,12 +72,12 @@ if (!defined('PHPUNIT_COMPOSER_INSTALL')) { require $prepend; } })(); -require PHPUNIT_COMPOSER_INSTALL; +unset($requiredExtensions, $unavailableExtensions); $code = 0; Swoole\Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL, 'exit_condition' => function () { return Swoole\Coroutine::stats()['coroutine_num'] === 0; }]); -Swoole\Coroutine\run(function () use(&$code) { +Swoole\Coroutine\run(function () use (&$code) { $code = (new PHPUnit\TextUI\Application())->run($_SERVER['argv']); Swoole\Timer::clearAll(); Hyperf\Coordinator\CoordinatorManager::until(Hyperf\Coordinator\Constants::WORKER_EXIT)->resume(); diff --git a/composer.json b/composer.json index 4e81fac..e4d78e9 100644 --- a/composer.json +++ b/composer.json @@ -10,21 +10,21 @@ "testing" ], "require": { - "php": ">=8.1", - "hyperf/codec": "~3.1.0", - "hyperf/collection": "~3.1.0", - "hyperf/context": "~3.1.0", - "hyperf/contract": "~3.1.0", - "hyperf/coordinator": "~3.1.0", - "hyperf/coroutine": "~3.1.0", - "hyperf/http-message": "~3.1.0", - "hyperf/http-server": "~3.1.0", - "hyperf/macroable": "~3.1.0", - "hyperf/stringable": "~3.1.0", - "hyperf/support": "~3.1.0", - "phpunit/phpunit": "^10.0", + "php": ">=8.2", + "hyperf/codec": "~3.2.0", + "hyperf/collection": "~3.2.0", + "hyperf/context": "~3.2.0", + "hyperf/contract": "~3.2.0", + "hyperf/coordinator": "~3.2.0", + "hyperf/coroutine": "~3.2.0", + "hyperf/http-message": "~3.2.0", + "hyperf/http-server": "~3.2.0", + "hyperf/macroable": "~3.2.0", + "hyperf/stringable": "~3.2.0", + "hyperf/support": "~3.2.0", + "phpunit/phpunit": "^11.0", "psr/container": "^1.0 || ^2.0", - "symfony/http-foundation": "^5.4 || ^6.0" + "symfony/http-foundation": "^6.0 || ^7.0" }, "suggest": { "fakerphp/faker": "Required to use Faker feature.(^1.23)" @@ -32,7 +32,10 @@ "autoload": { "psr-4": { "Hyperf\\Testing\\": "src/" - } + }, + "files": [ + "phpunit-patch.php" + ] }, "autoload-dev": { "psr-4": { diff --git a/phpunit-patch.php b/phpunit-patch.php new file mode 100644 index 0000000..1e1c5d3 --- /dev/null +++ b/phpunit-patch.php @@ -0,0 +1,35 @@ +findFile(TestCase::class)) { + $content = file_get_contents($file); + $replace = 'public function runBare'; + if (strpos($content, $find = 'final ' . $replace) !== false) { + $content = str_replace($find, $replace, $content); + file_put_contents($file, $content); + } + } +})(); diff --git a/src/AssertableJsonString.php b/src/AssertableJsonString.php index 550017d..beeeaac 100644 --- a/src/AssertableJsonString.php +++ b/src/AssertableJsonString.php @@ -255,7 +255,7 @@ public function assertPath($path, $expect) * Assert that the response has a given JSON structure. * * @param null|array $responseData - * @return $this + * @return static */ public function assertStructure(?array $structure = null, $responseData = null) { diff --git a/src/Attributes/NonCoroutine.php b/src/Attributes/NonCoroutine.php new file mode 100644 index 0000000..7446b39 --- /dev/null +++ b/src/Attributes/NonCoroutine.php @@ -0,0 +1,20 @@ +realTestName); + if ($this->isCoroutineEnabled()) { + $exception = null; - $testResult = null; - $exception = null; + /* @phpstan-ignore-next-line */ + \Swoole\Coroutine\run(function () use (&$exception) { + try { + parent::runBare(); + } catch (Throwable $e) { + $exception = $e; + } finally { + Timer::clearAll(); + CoordinatorManager::until(Constants::WORKER_EXIT)->resume(); + } + }); - /* @phpstan-ignore-next-line */ - \Swoole\Coroutine\run(function () use (&$testResult, &$exception, $arguments) { - try { - $this->invokeBeforeHookMethods(); - $testResult = $this->{$this->realTestName}(...$arguments); - } catch (Throwable $e) { - $exception = $e; - } finally { - $this->invokeAfterHookMethods(); - Timer::clearAll(); - CoordinatorManager::until(Constants::WORKER_EXIT)->resume(); + if ($exception) { + throw $exception; } - }); - if ($exception) { - throw $exception; + return; } - return $testResult; + parent::runBare(); } - final protected function runTest(): mixed + private function isCoroutineEnabled(): bool { - if (extension_loaded('swoole') && Coroutine::getCid() === -1 && $this->enableCoroutine) { - $this->realTestName = $this->name(); - parent::setName('runTestsInCoroutine'); + if (! extension_loaded('swoole') || Coroutine::getCid() !== -1) { + return false; } - /* @phpstan-ignore-next-line */ - return parent::runTest(); - } - - private function invokeBeforeHookMethods(): void - { - if (method_exists($this, 'beforeTestInCoroutine')) { - call_user_func([$this, 'beforeTestInCoroutine']); + $refClass = new ReflectionClass(static::class); + foreach ($refClass->getAttributes(NonCoroutine::class) as $attribute) { + return false; } - } - private function invokeAfterHookMethods(): void - { - if (method_exists($this, 'afterTestInCoroutine')) { - call_user_func([$this, 'afterTestInCoroutine']); + $refMethod = $refClass->getMethod($this->name()); + foreach ($refMethod->getAttributes(NonCoroutine::class) as $attribute) { + return false; } + + return true; } } diff --git a/src/Constraint/ArraySubset.php b/src/Constraint/ArraySubset.php index 3c70473..a49077d 100644 --- a/src/Constraint/ArraySubset.php +++ b/src/Constraint/ArraySubset.php @@ -15,6 +15,7 @@ use ArrayObject; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use Traversable; @@ -94,7 +95,7 @@ public function evaluate($other, string $description = '', bool $returnResult = */ public function toString(): string { - return 'has the subset ' . $this->exporter()->export($this->subset); + return 'has the subset ' . Exporter::export($this->subset); } /** diff --git a/src/Http/TestResponse.php b/src/Http/TestResponse.php index 44d5f06..82ad7b2 100644 --- a/src/Http/TestResponse.php +++ b/src/Http/TestResponse.php @@ -53,18 +53,14 @@ public function __construct(protected ResponseInterface $response) /** * Handle dynamic calls into macros or pass missing methods to the base response. - * - * @param string $method - * @param array $args - * @return mixed */ - public function __call($method, $args) + public function __call(string $name, array $arguments): mixed { - if (static::hasMacro($method)) { - return $this->macroCall($method, $args); + if (static::hasMacro($name)) { + return $this->macroCall($name, $arguments); } - return $this->response->{$method}(...$args); + return $this->response->{$name}(...$arguments); } /** diff --git a/src/TestCase.php b/src/TestCase.php index 558e196..e3d736e 100644 --- a/src/TestCase.php +++ b/src/TestCase.php @@ -20,15 +20,14 @@ /** * @internal - * @coversNothing */ abstract class TestCase extends \PHPUnit\Framework\TestCase { use Concerns\InteractsWithContainer; + use Concerns\InteractsWithDatabase; use Concerns\InteractsWithModelFactory; use Concerns\MakesHttpRequests; use Concerns\RunTestsInCoroutine; - use Concerns\InteractsWithDatabase; /** * The callbacks that should be run after the application is created. diff --git a/tests/AttributeOnClassTest.php b/tests/AttributeOnClassTest.php new file mode 100644 index 0000000..31bd149 --- /dev/null +++ b/tests/AttributeOnClassTest.php @@ -0,0 +1,35 @@ +assertFalse(Coroutine::inCoroutine()); + } +} diff --git a/tests/AttributeOnMethodTest.php b/tests/AttributeOnMethodTest.php new file mode 100644 index 0000000..436fc3b --- /dev/null +++ b/tests/AttributeOnMethodTest.php @@ -0,0 +1,40 @@ +assertFalse(Coroutine::inCoroutine()); + } + + public function testWithoutNonCoroutineAttribute() + { + $this->assertTrue(Coroutine::inCoroutine()); + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 5a1f9a1..107d3f1 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -45,7 +45,6 @@ /** * @internal - * @coversNothing */ #[CoversNothing] class ClientTest extends TestCase diff --git a/tests/DebugTest.php b/tests/DebugTest.php index ab7df68..852e3a5 100644 --- a/tests/DebugTest.php +++ b/tests/DebugTest.php @@ -19,7 +19,6 @@ /** * @internal - * @coversNothing */ #[CoversNothing] class DebugTest extends TestCase diff --git a/tests/HttpClientTest.php b/tests/HttpClientTest.php index c5a8b79..39b6fb5 100644 --- a/tests/HttpClientTest.php +++ b/tests/HttpClientTest.php @@ -23,7 +23,6 @@ /** * @internal - * @coversNothing */ #[CoversNothing] class HttpClientTest extends TestCase