From 205d48d4a2bed427bd525e839a571ace5f7a5591 Mon Sep 17 00:00:00 2001 From: Luca Tumedei Date: Wed, 19 Jun 2024 09:32:36 +0200 Subject: [PATCH] feat(PHPUnit) bare minimum PHPUnit 10/11 support The Core PHPUnit suite supports, at most, PHPUnit 9.5. This code scaffolds a first attempt at supporting PHPUnit version 10 and 11, the curent latest one, adding as little destruction as possible. This will not likely deal with all the intricacies of it, but will open the door to supporting the latest versions of Codeception and its modules supporting the latest versions of PHPUnit. --- Makefile | 1 + composer.json | 2 +- .../includes/abstract-testcase.php.patch | 48 +++++++++++++++- .../includes/abstract-testcase.php | 35 ++++++++---- src/Module/WPFilesystem.php | 1 + src/TestCase/WPTestCase.php | 57 ++++++++++++++----- .../WPBrowser/Extension/SymlinkerTest.php | 2 + 7 files changed, 117 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index 8f958856b..fea1505a0 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ clean: clean_tmp: rm -rf var/_output var/_tmp + rm -f var/_output/tmp/_monkeypatch var/_output/tmp/*.sqlite var/_output/tmp/*.sqlite_snapshot .PHONY: clean_tmp update_core_phpunit_includes: diff --git a/composer.json b/composer.json index fac292a68..1974572f9 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "codeception/codeception": "^5.0", "codeception/module-asserts": "^2.0 || ^3.0", "codeception/module-phpbrowser": "^2.0 || ^3.0", - "codeception/module-webdriver": "^2.0 || ^3.0", + "codeception/module-webdriver": "^2.0 || ^3.0 || ^4.0", "codeception/module-db": "^2.0 || ^3.0", "codeception/module-filesystem": "^2.0 || ^3.0", "codeception/module-cli": "^2.0 || ^3.0", diff --git a/config/patches/core-phpunit/includes/abstract-testcase.php.patch b/config/patches/core-phpunit/includes/abstract-testcase.php.patch index 38ac46168..04164b689 100644 --- a/config/patches/core-phpunit/includes/abstract-testcase.php.patch +++ b/config/patches/core-phpunit/includes/abstract-testcase.php.patch @@ -1,5 +1,5 @@ diff --git a/includes/core-phpunit/includes/abstract-testcase.php b/includes/core-phpunit/includes/abstract-testcase.php -index f2978644..22427643 100644 +index f2978644..5c823a53 100644 --- a/includes/core-phpunit/includes/abstract-testcase.php +++ b/includes/core-phpunit/includes/abstract-testcase.php @@ -1,5 +1,7 @@ @@ -64,7 +64,49 @@ index f2978644..22427643 100644 if ( method_exists( $class, 'wpTearDownAfterClass' ) ) { call_user_func( array( $class, 'wpTearDownAfterClass' ) ); -@@ -651,7 +659,7 @@ public function expectedDeprecated() { +@@ -543,16 +551,31 @@ public function wp_die_handler( $message, $title, $args ) { + * @since 3.7.0 + */ + public function expectDeprecated() { +- if ( method_exists( $this, 'getAnnotations' ) ) { +- // PHPUnit < 9.5.0. +- $annotations = $this->getAnnotations(); +- } else { +- // PHPUnit >= 9.5.0. +- $annotations = \PHPUnit\Util\Test::parseTestMethodAnnotations( +- static::class, +- $this->getName( false ) +- ); +- } ++ if ( method_exists( $this, 'getAnnotations' ) ) { ++ // PHPUnit < 9.5.0. ++ $annotations = $this->getAnnotations(); ++ } else if( version_compare(tests_get_phpunit_version(),'10.0.0','<')) { ++ // PHPUnit >= 9.5.0 < 10.0.0. ++ $annotations = \PHPUnit\Util\Test::parseTestMethodAnnotations( ++ static::class, ++ $this->getName( false ) ++ ); ++ } else { ++ // PHPUnit >= 10.0.0. ++ if (method_exists(static::class, $this->name())) { ++ $reflectionMethod = new \ReflectionMethod(static::class, $this->name()); ++ $docBlock = \PHPUnit\Metadata\Annotation\Parser\DocBlock::ofMethod($reflectionMethod); ++ $annotations = [ ++ 'method' => $docBlock->symbolAnnotations(), ++ 'class' => [], ++ ]; ++ } else { ++ $annotations = [ ++ 'method' => null, ++ 'class' => [], ++ ]; ++ } ++ } + + foreach ( array( 'class', 'method' ) as $depth ) { + if ( ! empty( $annotations[ $depth ]['expectedDeprecated'] ) ) { +@@ -651,7 +674,7 @@ public function expectedDeprecated() { * * @since 4.2.0 */ @@ -73,7 +115,7 @@ index f2978644..22427643 100644 $this->expectedDeprecated(); } -@@ -1660,4 +1668,9 @@ public static function touch( $file ) { +@@ -1660,4 +1683,9 @@ public static function touch( $file ) { touch( $file ); } diff --git a/includes/core-phpunit/includes/abstract-testcase.php b/includes/core-phpunit/includes/abstract-testcase.php index 4064cefca..5c823a53b 100644 --- a/includes/core-phpunit/includes/abstract-testcase.php +++ b/includes/core-phpunit/includes/abstract-testcase.php @@ -551,16 +551,31 @@ public function wp_die_handler( $message, $title, $args ) { * @since 3.7.0 */ public function expectDeprecated() { - if ( method_exists( $this, 'getAnnotations' ) ) { - // PHPUnit < 9.5.0. - $annotations = $this->getAnnotations(); - } else { - // PHPUnit >= 9.5.0. - $annotations = \PHPUnit\Util\Test::parseTestMethodAnnotations( - static::class, - $this->getName( false ) - ); - } + if ( method_exists( $this, 'getAnnotations' ) ) { + // PHPUnit < 9.5.0. + $annotations = $this->getAnnotations(); + } else if( version_compare(tests_get_phpunit_version(),'10.0.0','<')) { + // PHPUnit >= 9.5.0 < 10.0.0. + $annotations = \PHPUnit\Util\Test::parseTestMethodAnnotations( + static::class, + $this->getName( false ) + ); + } else { + // PHPUnit >= 10.0.0. + if (method_exists(static::class, $this->name())) { + $reflectionMethod = new \ReflectionMethod(static::class, $this->name()); + $docBlock = \PHPUnit\Metadata\Annotation\Parser\DocBlock::ofMethod($reflectionMethod); + $annotations = [ + 'method' => $docBlock->symbolAnnotations(), + 'class' => [], + ]; + } else { + $annotations = [ + 'method' => null, + 'class' => [], + ]; + } + } foreach ( array( 'class', 'method' ) as $depth ) { if ( ! empty( $annotations[ $depth ]['expectedDeprecated'] ) ) { diff --git a/src/Module/WPFilesystem.php b/src/Module/WPFilesystem.php index 32c0843da..6d3b4bad8 100644 --- a/src/Module/WPFilesystem.php +++ b/src/Module/WPFilesystem.php @@ -377,6 +377,7 @@ public function dontSeeUploadedFileFound(string $file, int|string $date = null): if (method_exists(Assert::class, 'assertFileDoesNotExist')) { Assert::assertFileDoesNotExist($this->getUploadsPath($file, $date)); } else { + // @phpstan-ignore-next-line PHPUnit checked above. Assert::assertFileNotExists($this->getUploadsPath($file, $date)); } } diff --git a/src/TestCase/WPTestCase.php b/src/TestCase/WPTestCase.php index 9444c952a..c849ebb6c 100644 --- a/src/TestCase/WPTestCase.php +++ b/src/TestCase/WPTestCase.php @@ -93,10 +93,18 @@ class WPTestCase extends Unit */ protected $tester; - // Backup, and reset, globals between tests. + /** + * Backup, and reset, globals between tests. + * + * @var bool + */ protected $backupGlobals = false; - // A list of globals that should not be backed up: they are handled by the Core test case. + /** + * A list of globals that should not be backed up: they are handled by the Core test case. + * + * @var string[] + */ protected $backupGlobalsExcludeList = [ 'wpdb', 'wp_query', @@ -131,10 +139,18 @@ class WPTestCase extends Unit '_wpTestsBackupStaticAttributesExcludeList' ]; - // Backup, and reset, static class attributes between tests. + /** + * Backup, and reset, static class attributes between tests. + * + * @var bool + */ protected $backupStaticAttributes = false; - // A list of static attributes that should not be backed up as they are wired to explode when doing so. + /** + * A list of static attributes that should not be backed up as they are wired to explode when doing so. + * + * @var array + */ protected $backupStaticAttributesExcludeList = [ // WordPress 'WP_Block_Type_Registry' => ['instance'], @@ -156,6 +172,8 @@ class WPTestCase extends Unit /** * @param array $data + * @param string $dataName + * @throws ReflectionException */ public function __construct(?string $name = null, array $data = [], $dataName = '') { @@ -223,7 +241,8 @@ public function __construct(?string $name = null, array $data = [], $dataName = ); } - parent::__construct($name, $data, $dataName); + // @phpstan-ignore-next-line PHPUnit < 10.0.0 will require the three parameters. + parent::__construct($name ?: 'testMethod', $data, $dataName); } /** @@ -236,12 +255,13 @@ public function __construct(?string $name = null, array $data = [], $dataName = */ private static array $coreTestCaseMap = []; - private static function getCoreTestCase(): WP_UnitTestCase + private static function getCoreTestCase(?string $name = null): WP_UnitTestCase { if (isset(self::$coreTestCaseMap[static::class])) { return self::$coreTestCaseMap[static::class]; } - $coreTestCase = new class extends WP_UnitTestCase { + $methodName = $name ?: 'coreTestCase'; + $coreTestCase = new class ($methodName) extends WP_UnitTestCase { use WPUnitTestCasePolyfillsTrait; }; $coreTestCase->setCalledClass(static::class); @@ -332,7 +352,7 @@ public static function __callStatic(string $name, array $arguments): mixed */ public function __call(string $name, array $arguments): mixed { - $coreTestCase = self::getCoreTestCase(); + $coreTestCase = self::getCoreTestCase($name); $reflectionMethod = new ReflectionMethod($coreTestCase, $name); $reflectionMethod->setAccessible(true); return $reflectionMethod->invokeArgs($coreTestCase, $arguments); @@ -373,15 +393,11 @@ public function __get(string $name): mixed return $this->{$name} ?? null; } - $coreTestCase = self::getCoreTestCase(); + $coreTestCase = self::getCoreTestCase('__get'); $reflectionProperty = new ReflectionProperty($coreTestCase, $name); $reflectionProperty->setAccessible(true); $value = $reflectionProperty->getValue($coreTestCase); -// if (is_array($value)) { -// return new ArrayReflectionPropertyAccessor($reflectionProperty, $coreTestCase); -// } - return $value; } @@ -396,7 +412,7 @@ public function __set(string $name, mixed $value): void return; } - $coreTestCase = self::getCoreTestCase(); + $coreTestCase = self::getCoreTestCase('__set'); $reflectionProperty = new ReflectionProperty($coreTestCase, $name); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($coreTestCase, $value); @@ -411,9 +427,20 @@ public function __isset(string $name): bool return isset($this->{$name}); } - $coreTestCase = self::getCoreTestCase(); + $coreTestCase = self::getCoreTestCase('__isset'); $reflectionProperty = new ReflectionProperty($coreTestCase, $name); $reflectionProperty->setAccessible(true); return $reflectionProperty->isInitialized($coreTestCase); } + + public function getName(bool $withDataSet = true): string + { + if (method_exists(parent::class, 'getName')) { + // PHPUnit < 10.0.0. + return parent::getName($withDataSet); + } + + // PHPUnit >= 10.0.0. + return $withDataSet ? $this->nameWithDataSet() : $this->name(); + } } diff --git a/tests/unit/lucatume/WPBrowser/Extension/SymlinkerTest.php b/tests/unit/lucatume/WPBrowser/Extension/SymlinkerTest.php index 634d06596..558388470 100644 --- a/tests/unit/lucatume/WPBrowser/Extension/SymlinkerTest.php +++ b/tests/unit/lucatume/WPBrowser/Extension/SymlinkerTest.php @@ -8,6 +8,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Extension\Symlinker; use lucatume\WPBrowser\Tests\Traits\LoopIsolation; +use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\WordPress\Installation; use PHPUnit\Framework\Assert; @@ -15,6 +16,7 @@ class SymlinkerTest extends Unit { use LoopIsolation; + use TmpFilesCleanup; public function test_exists(): void {