diff --git a/projects/packages/account-protection/changelog/update-cleanup-tests-for-phpunit-12 b/projects/packages/account-protection/changelog/update-cleanup-tests-for-phpunit-12 new file mode 100644 index 0000000000000..b2726b762f6a3 --- /dev/null +++ b/projects/packages/account-protection/changelog/update-cleanup-tests-for-phpunit-12 @@ -0,0 +1,5 @@ +Significance: patch +Type: removed +Comment: Remove unneeded `error_log()` in test. + + diff --git a/projects/packages/account-protection/tests/php/Email_Service_Test.php b/projects/packages/account-protection/tests/php/Email_Service_Test.php index afe030124db5c..a88311fc1df21 100644 --- a/projects/packages/account-protection/tests/php/Email_Service_Test.php +++ b/projects/packages/account-protection/tests/php/Email_Service_Test.php @@ -70,8 +70,6 @@ public function test_resend_auth_mail_sends_mail_and_remembers_2fa_token_success $result = $sut->resend_auth_email( $user->ID, $transient_data, $my_token ); - error_log( print_r( $result, true ) ); - // Verify the mail was sent $this->assertTrue( $result, 'Resending auth mail should return true as success indicator.' ); diff --git a/projects/packages/roles/changelog/update-cleanup-tests-for-phpunit-12 b/projects/packages/roles/changelog/update-cleanup-tests-for-phpunit-12 new file mode 100644 index 0000000000000..adcd03f6a6ae1 --- /dev/null +++ b/projects/packages/roles/changelog/update-cleanup-tests-for-phpunit-12 @@ -0,0 +1,5 @@ +Significance: patch +Type: fixed +Comment: `->getMockBuilder()` no longer creates specifically-named classes. And we don't need one here, just an arbitrary object to be passed through. + + diff --git a/projects/packages/roles/tests/php/Roles_Test.php b/projects/packages/roles/tests/php/Roles_Test.php index bd2d013c4fea9..73ec355932da8 100644 --- a/projects/packages/roles/tests/php/Roles_Test.php +++ b/projects/packages/roles/tests/php/Roles_Test.php @@ -92,7 +92,9 @@ public function test_current_user_to_role_with_no_match() { * Test translating an user to a role by role. */ public function test_user_to_role_with_role() { - $user_mock = $this->getMockBuilder( 'WP_User' )->getMock(); + $user_mock = (object) array(); + '@phan-var \WP_User $user_mock'; // Not really, but it doesn't matter. + Functions\when( 'user_can' )->alias( function ( $user, $cap ) use ( $user_mock ) { return $user_mock === $user && 'administrator' === $cap; @@ -106,7 +108,9 @@ function ( $user, $cap ) use ( $user_mock ) { * Test translating an user to a role by capablity. */ public function test_user_to_role_with_capability() { - $user_mock = $this->getMockBuilder( 'WP_User' )->getMock(); + $user_mock = (object) array(); + '@phan-var \WP_User $user_mock'; // Not really, but it doesn't matter. + Functions\when( 'user_can' )->alias( function ( $user, $cap ) use ( $user_mock ) { return $user_mock === $user && 'edit_others_posts' === $cap; @@ -120,7 +124,9 @@ function ( $user, $cap ) use ( $user_mock ) { * Test translating an user to a role with no match. */ public function test_user_to_role_with_no_match() { - $user_mock = $this->getMockBuilder( 'WP_User' )->getMock(); + $user_mock = (object) array(); + '@phan-var \WP_User $user_mock'; // Not really, but it doesn't matter. + Functions\when( 'user_can' )->justReturn( false ); $this->assertFalse( $this->roles->translate_user_to_role( $user_mock ) ); diff --git a/projects/plugins/crm/changelog/update-cleanup-tests-for-phpunit-12 b/projects/plugins/crm/changelog/update-cleanup-tests-for-phpunit-12 new file mode 100644 index 0000000000000..ec0e04a0a2e01 --- /dev/null +++ b/projects/plugins/crm/changelog/update-cleanup-tests-for-phpunit-12 @@ -0,0 +1,5 @@ +Significance: patch +Type: added +Comment: Update `WP_UnitTestCase_Fix` with a simple annotation parser for `@expectedDeprecated` and `@expectedIncorrectUsage`. + + diff --git a/projects/plugins/crm/tests/php/WP_UnitTestCase_Fix.php b/projects/plugins/crm/tests/php/WP_UnitTestCase_Fix.php index 46162ff58c22f..4f36172165968 100644 --- a/projects/plugins/crm/tests/php/WP_UnitTestCase_Fix.php +++ b/projects/plugins/crm/tests/php/WP_UnitTestCase_Fix.php @@ -6,12 +6,95 @@ */ // phpcs:disable Generic.Classes.DuplicateClassName.Found, Generic.Files.OneObjectStructurePerFile.MultipleFound +// phpcs:disable WordPress.NamingConventions.ValidFunctionName, WordPress.NamingConventions.ValidVariableName namespace Automattic\Jetpack\PHPUnit; use PHPUnit\Metadata\Annotation\Parser\Registry as AnnotationRegistry; +use ReflectionClass; +use ReflectionFunctionAbstract; +use ReflectionMethod; if ( explode( '.', \PHPUnit\Runner\Version::id() )[0] >= 10 ) { + if ( ! class_exists( AnnotationRegistry::class ) ) { + + /** + * Doc block annotation extraction for getAnnotations in PHPUnit 12. + * + * Adapted from code in PHPUnit 11 `PHPUnit\Metadata\Annotation\Parser\Registry` and `PHPUnit\Metadata\Annotation\Parser\DocBlock`. + */ + class WP_UnitTestCase_Fix_GetAnnotations { + /** + * Cache. + * @var array + */ + private static $cache; + + /** + * Get annotations for a class method. + * + * @param string $className Class name. + * @param string $methodName Method name. + * @return array Annotation data. + */ + public static function getAnnotations( $className, $methodName ) { + if ( ! isset( self::$cache['class'][ $className ] ) ) { + self::$cache['class'][ $className ] = self::getForReflector( new ReflectionClass( $className ) ); + } + if ( ! isset( self::$cache['method'][ $className ][ $methodName ] ) ) { + self::$cache['method'][ $className ][ $methodName ] = self::getForReflector( new ReflectionMethod( $className, $methodName ) ); + } + return array( + 'method' => self::$cache['method'][ $className ][ $methodName ], + 'class' => self::$cache['class'][ $className ], + ); + } + + /** + * Extract annotations from a Reflection object. + * + * @param ReflectionClass|ReflectionFunctionAbstract $reflector Reflection object. + * @return array Annotation data. + */ + private static function getForReflector( $reflector ) { + $annotations = array(); + + if ( $reflector instanceof ReflectionClass ) { + $annotations = array_merge( + $annotations, + ...array_map( + static function ( $trait ) { + return self::parseDocBlock( (string) $trait->getDocComment() ); }, + array_values( $reflector->getTraits() ) + ) + ); + } + + return array_merge( + $annotations, + self::parseDocBlock( (string) $reflector->getDocComment() ) + ); + } + + /** + * Extract annotations from a doc block comment. + * + * @param string $docblock Doc block. + * @return array Annotation data for `@expectedDeprecated` and `@expectedIncorrectUsage`. + */ + private static function parseDocBlock( $docblock ) { + $annotations = array(); + $docblock = substr( (string) $docblock, 3, -2 ); + if ( preg_match_all( '/@(?PexpectedDeprecated|expectedIncorrectUsage)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docblock, $matches, PREG_SET_ORDER ) ) { + foreach ( $matches as $m ) { + $annotations[ $m['name'] ][] = $m['value']; + } + } + return $annotations; + } + } + } + trait WP_UnitTestCase_Fix { /** @@ -19,18 +102,22 @@ trait WP_UnitTestCase_Fix { * * @return array Method and class annotations, at minimum `@expectedDeprecated` and `@expectedIncorrectUsage`. */ - public function getAnnotations() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid - return array( - 'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(), - 'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(), - ); + public function getAnnotations() { + if ( class_exists( AnnotationRegistry::class ) ) { + return array( + 'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(), + 'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(), + ); + } + + return WP_UnitTestCase_Fix_GetAnnotations::getAnnotations( static::class, $this->name() ); } /** * Obsolete method where PHPUnit is mis-processing the doc comment to see a `@group` that doesn't exist. * This redefinition hides the "bad" doc comment from PHPUnit. */ - protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found, WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found parent::checkRequirements(); } } diff --git a/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12 b/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12 new file mode 100644 index 0000000000000..3c27e5bde3885 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12 @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: Update `WP_UnitTestCase_Fix` with a simple annotation parser for `@expectedDeprecated` and `@expectedIncorrectUsage`. + + diff --git a/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12#2 b/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12#2 new file mode 100644 index 0000000000000..123b50040f312 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12#2 @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: `->getMockBuilder()` no longer creates specifically-named classes. And we don't really need a mock here anyway, just for some class by the name to exist. + + diff --git a/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12#3 b/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12#3 new file mode 100644 index 0000000000000..a9d2d46a2d35c --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-cleanup-tests-for-phpunit-12#3 @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: PHPUnit 12 makes `TestCase::__construct()` final. Use `set_up()` instead. + + diff --git a/projects/plugins/jetpack/tests/php/WP_UnitTestCase_Fix.php b/projects/plugins/jetpack/tests/php/WP_UnitTestCase_Fix.php index 607f85dfbd97b..8141e1b584c4f 100644 --- a/projects/plugins/jetpack/tests/php/WP_UnitTestCase_Fix.php +++ b/projects/plugins/jetpack/tests/php/WP_UnitTestCase_Fix.php @@ -6,12 +6,96 @@ */ // phpcs:disable Generic.Classes.DuplicateClassName.Found, Generic.Files.OneObjectStructurePerFile.MultipleFound +// phpcs:disable WordPress.NamingConventions.ValidFunctionName, WordPress.NamingConventions.ValidVariableName namespace Automattic\Jetpack\PHPUnit; use PHPUnit\Metadata\Annotation\Parser\Registry as AnnotationRegistry; +use ReflectionClass; +use ReflectionFunctionAbstract; +use ReflectionMethod; if ( explode( '.', \PHPUnit\Runner\Version::id() )[0] >= 10 ) { + if ( ! class_exists( AnnotationRegistry::class ) ) { + + /** + * Doc block annotation extraction for getAnnotations in PHPUnit 12. + * + * Adapted from code in PHPUnit 11 `PHPUnit\Metadata\Annotation\Parser\Registry` and `PHPUnit\Metadata\Annotation\Parser\DocBlock`. + */ + class WP_UnitTestCase_Fix_GetAnnotations { + /** + * Cache. + * + * @var array + */ + private static $cache; + + /** + * Get annotations for a class method. + * + * @param string $className Class name. + * @param string $methodName Method name. + * @return array Annotation data. + */ + public static function getAnnotations( $className, $methodName ) { + if ( ! isset( self::$cache['class'][ $className ] ) ) { + self::$cache['class'][ $className ] = self::getForReflector( new ReflectionClass( $className ) ); + } + if ( ! isset( self::$cache['method'][ $className ][ $methodName ] ) ) { + self::$cache['method'][ $className ][ $methodName ] = self::getForReflector( new ReflectionMethod( $className, $methodName ) ); + } + return array( + 'method' => self::$cache['method'][ $className ][ $methodName ], + 'class' => self::$cache['class'][ $className ], + ); + } + + /** + * Extract annotations from a Reflection object. + * + * @param ReflectionClass|ReflectionFunctionAbstract $reflector Reflection object. + * @return array Annotation data. + */ + private static function getForReflector( $reflector ) { + $annotations = array(); + + if ( $reflector instanceof ReflectionClass ) { + $annotations = array_merge( + $annotations, + ...array_map( + static function ( $trait ) { + return self::parseDocBlock( (string) $trait->getDocComment() ); }, + array_values( $reflector->getTraits() ) + ) + ); + } + + return array_merge( + $annotations, + self::parseDocBlock( (string) $reflector->getDocComment() ) + ); + } + + /** + * Extract annotations from a doc block comment. + * + * @param string $docblock Doc block. + * @return array Annotation data for `@expectedDeprecated` and `@expectedIncorrectUsage`. + */ + private static function parseDocBlock( $docblock ) { + $annotations = array(); + $docblock = substr( (string) $docblock, 3, -2 ); + if ( preg_match_all( '/@(?PexpectedDeprecated|expectedIncorrectUsage)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docblock, $matches, PREG_SET_ORDER ) ) { + foreach ( $matches as $m ) { + $annotations[ $m['name'] ][] = $m['value']; + } + } + return $annotations; + } + } + } + trait WP_UnitTestCase_Fix { /** @@ -19,18 +103,22 @@ trait WP_UnitTestCase_Fix { * * @return array Method and class annotations, at minimum `@expectedDeprecated` and `@expectedIncorrectUsage`. */ - public function getAnnotations() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid - return array( - 'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(), - 'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(), - ); + public function getAnnotations() { + if ( class_exists( AnnotationRegistry::class ) ) { + return array( + 'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(), + 'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(), + ); + } + + return WP_UnitTestCase_Fix_GetAnnotations::getAnnotations( static::class, $this->name() ); } /** * Obsolete method where PHPUnit is mis-processing the doc comment to see a `@group` that doesn't exist. * This redefinition hides the "bad" doc comment from PHPUnit. */ - protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found, WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found parent::checkRequirements(); } } diff --git a/projects/plugins/jetpack/tests/php/modules/widgets/Jetpack_Display_Posts_Widget_Test.php b/projects/plugins/jetpack/tests/php/modules/widgets/Jetpack_Display_Posts_Widget_Test.php index a6b676e53dfad..79689499c73c6 100644 --- a/projects/plugins/jetpack/tests/php/modules/widgets/Jetpack_Display_Posts_Widget_Test.php +++ b/projects/plugins/jetpack/tests/php/modules/widgets/Jetpack_Display_Posts_Widget_Test.php @@ -15,10 +15,10 @@ class Jetpack_Display_Posts_Widget_Test extends WP_UnitTestCase { private $inst; /** - * Jetpack_Display_Posts_Widget_Test constructor. + * Setup function. */ - public function __construct( ...$args ) { - parent::__construct( ...$args ); // @phan-suppress-current-line PhanParamTooFewUnpack - Should be ok. + public function set_up() { + parent::set_up(); $this->inst = new Jetpack_Display_Posts_Widget(); } diff --git a/projects/plugins/jetpack/tests/php/sync/Jetpack_Sync_Functions_Test.php b/projects/plugins/jetpack/tests/php/sync/Jetpack_Sync_Functions_Test.php index 0dd467d67dfaf..6ca0a332544b2 100644 --- a/projects/plugins/jetpack/tests/php/sync/Jetpack_Sync_Functions_Test.php +++ b/projects/plugins/jetpack/tests/php/sync/Jetpack_Sync_Functions_Test.php @@ -1300,8 +1300,8 @@ public function test_get_hosting_provider_by_known_class() { $this->assertFalse( $functions->get_hosting_provider_by_known_class() ); // Fake that the class exists for the test. - // @phan-suppress-next-line PhanUndeclaredClassReference - $this->getMockBuilder( '\\WPaaS\\Plugin' )->getMock(); + // @phan-suppress-next-line PhanUndeclaredClassReference -- We're defining it here, Phan. 🙄 + class_alias( static::class, \WPaaS\Plugin::class ); $this->assertEquals( 'gd-managed-wp', $functions->get_hosting_provider_by_known_class() ); } diff --git a/projects/plugins/wpcomsh/changelog/update-cleanup-tests-for-phpunit-12 b/projects/plugins/wpcomsh/changelog/update-cleanup-tests-for-phpunit-12 new file mode 100644 index 0000000000000..ec0e04a0a2e01 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/update-cleanup-tests-for-phpunit-12 @@ -0,0 +1,5 @@ +Significance: patch +Type: added +Comment: Update `WP_UnitTestCase_Fix` with a simple annotation parser for `@expectedDeprecated` and `@expectedIncorrectUsage`. + + diff --git a/projects/plugins/wpcomsh/tests/WP_UnitTestCase_Fix.php b/projects/plugins/wpcomsh/tests/WP_UnitTestCase_Fix.php index 64a5dbaa51eaa..bc5b742033929 100644 --- a/projects/plugins/wpcomsh/tests/WP_UnitTestCase_Fix.php +++ b/projects/plugins/wpcomsh/tests/WP_UnitTestCase_Fix.php @@ -6,12 +6,96 @@ */ // phpcs:disable Generic.Classes.DuplicateClassName.Found, Generic.Files.OneObjectStructurePerFile.MultipleFound +// phpcs:disable WordPress.NamingConventions.ValidFunctionName, WordPress.NamingConventions.ValidVariableName namespace Automattic\Jetpack\PHPUnit; use PHPUnit\Metadata\Annotation\Parser\Registry as AnnotationRegistry; +use ReflectionClass; +use ReflectionFunctionAbstract; +use ReflectionMethod; if ( explode( '.', \PHPUnit\Runner\Version::id() )[0] >= 10 ) { + if ( ! class_exists( AnnotationRegistry::class ) ) { + + /** + * Doc block annotation extraction for getAnnotations in PHPUnit 12. + * + * Adapted from code in PHPUnit 11 `PHPUnit\Metadata\Annotation\Parser\Registry` and `PHPUnit\Metadata\Annotation\Parser\DocBlock`. + */ + class WP_UnitTestCase_Fix_GetAnnotations { + /** + * Cache. + * + * @var array + */ + private static $cache; + + /** + * Get annotations for a class method. + * + * @param string $className Class name. + * @param string $methodName Method name. + * @return array Annotation data. + */ + public static function getAnnotations( $className, $methodName ) { + if ( ! isset( self::$cache['class'][ $className ] ) ) { + self::$cache['class'][ $className ] = self::getForReflector( new ReflectionClass( $className ) ); + } + if ( ! isset( self::$cache['method'][ $className ][ $methodName ] ) ) { + self::$cache['method'][ $className ][ $methodName ] = self::getForReflector( new ReflectionMethod( $className, $methodName ) ); + } + return array( + 'method' => self::$cache['method'][ $className ][ $methodName ], + 'class' => self::$cache['class'][ $className ], + ); + } + + /** + * Extract annotations from a Reflection object. + * + * @param ReflectionClass|ReflectionFunctionAbstract $reflector Reflection object. + * @return array Annotation data. + */ + private static function getForReflector( $reflector ) { + $annotations = array(); + + if ( $reflector instanceof ReflectionClass ) { + $annotations = array_merge( + $annotations, + ...array_map( + static function ( $trait ) { + return self::parseDocBlock( (string) $trait->getDocComment() ); }, + array_values( $reflector->getTraits() ) + ) + ); + } + + return array_merge( + $annotations, + self::parseDocBlock( (string) $reflector->getDocComment() ) + ); + } + + /** + * Extract annotations from a doc block comment. + * + * @param string $docblock Doc block. + * @return array Annotation data for `@expectedDeprecated` and `@expectedIncorrectUsage`. + */ + private static function parseDocBlock( $docblock ) { + $annotations = array(); + $docblock = substr( (string) $docblock, 3, -2 ); + if ( preg_match_all( '/@(?PexpectedDeprecated|expectedIncorrectUsage)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docblock, $matches, PREG_SET_ORDER ) ) { + foreach ( $matches as $m ) { + $annotations[ $m['name'] ][] = $m['value']; + } + } + return $annotations; + } + } + } + trait WP_UnitTestCase_Fix { /** @@ -19,18 +103,22 @@ trait WP_UnitTestCase_Fix { * * @return array Method and class annotations, at minimum `@expectedDeprecated` and `@expectedIncorrectUsage`. */ - public function getAnnotations() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid - return array( - 'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(), - 'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(), - ); + public function getAnnotations() { + if ( class_exists( AnnotationRegistry::class ) ) { + return array( + 'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(), + 'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(), + ); + } + + return WP_UnitTestCase_Fix_GetAnnotations::getAnnotations( static::class, $this->name() ); } /** * Obsolete method where PHPUnit is mis-processing the doc comment to see a `@group` that doesn't exist. * This redefinition hides the "bad" doc comment from PHPUnit. */ - protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found, WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found parent::checkRequirements(); } }