From 69fb3de4cea95b8e881bd29986ac3a5aef8f90c6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 4 Dec 2020 09:40:35 +0100 Subject: [PATCH 01/39] Travis: add build against PHP 8.0 PHP 8.0 has been branched off two months ago, so `nightly` is now PHP 8.1 and in the mean time PHP 8.0 was released last week. As of today, there is a PHP 8.0 image available on Travis. This PR adds a new build against PHP 8.0 to the matrix and, as PHP 8.0 has been released, that build is not allowed to fail. --- .travis.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0d890fc..7d2583f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ php: - 7.2 - 7.3 - 7.4 + - 8.0 - "nightly" # Define the stages used. @@ -58,14 +59,7 @@ before_install: install: # Set up environment using Composer. - - | - if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then - # Not all PHPUnit dependencies have stable releases yet allowing for PHP 8.0. - travis_retry composer install --no-interaction --ignore-platform-reqs - else - # Do a normal dev install in all other cases. - travis_retry composer install --no-interaction - fi + - travis_retry composer install --no-interaction script: From 63e003dd2612add93ce6b73f1e6268172495b831 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 27 Apr 2021 06:01:39 +0200 Subject: [PATCH 02/39] Composer: update PHP Parallel Lint Update version constraint for PHP Parallel Lint after new release. Refs: * https://github.com/php-parallel-lint/PHP-Parallel-Lint/releases/tag/v1.3.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ece8171..da3eaf9 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.2.0", + "php-parallel-lint/php-parallel-lint": "^1.3.0", "php-parallel-lint/php-console-highlighter": "^0.5", "yoast/yoastcs": "^2.1.0" }, From b819f3d287b321628400e2c807f68430831930a6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 27 Apr 2021 06:19:51 +0200 Subject: [PATCH 03/39] CS/QA: rename a function parameter ... to prevent using a reserved keyword as a parameter name. While this isn't forbidden, in PHP 8.0+ with named parameters this can lead to very confusing code, so better to use another name. --- phpunitpolyfills-autoload.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 3246497..15af0cf 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -16,17 +16,17 @@ class Autoload { /** * Loads a class. * - * @param string $class The name of the class to load. + * @param string $className The name of the class to load. * * @return bool */ - public static function load( $class ) { + public static function load( $className ) { // Only load classes belonging to this library. - if ( \stripos( $class, 'Yoast\PHPUnitPolyfills' ) !== 0 ) { + if ( \stripos( $className, 'Yoast\PHPUnitPolyfills' ) !== 0 ) { return false; } - switch ( $class ) { + switch ( $className ) { case 'Yoast\PHPUnitPolyfills\Polyfills\AssertNumericType': self::loadAssertNumericType(); return true; @@ -89,7 +89,7 @@ public static function load( $class ) { $file = \realpath( __DIR__ . \DIRECTORY_SEPARATOR . 'src' . \DIRECTORY_SEPARATOR - . \strtr( \substr( $class, 23 ), '\\', \DIRECTORY_SEPARATOR ) . '.php' + . \strtr( \substr( $className, 23 ), '\\', \DIRECTORY_SEPARATOR ) . '.php' ); if ( \file_exists( $file ) === true ) { From 775db5ea9dc5dd479b66d487385e9edb10c3fdf9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 27 Apr 2021 06:10:53 +0200 Subject: [PATCH 04/39] CI/QA: fix testVersion for PHPCompatibility This library supports PHP >= 5.5, but the default `testVersion` as set in YoastCS is `5.6-`. Overruling the `testVersion` from within a custom ruleset is currently not possible. This is a known issue in PHPCS itself and a fix is expected to be included with PHPCS 4.x. In the mean time, as a work-around, the `testVersion` can be overruled from the command-line. --- .phpcs.xml.dist | 3 --- composer.json | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 305fa4b..93bc4ab 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -33,9 +33,6 @@ ############################################################################# --> - - - diff --git a/composer.json b/composer.json index ece8171..e4ef6c1 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git --exclude src/TestCases/TestCasePHPUnitGte8.php --exclude src/TestListeners/TestListenerDefaultImplementationPHPUnitGte7.php" ], "check-cs": [ - "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs" + "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --runtime-set testVersion 5.5-" ], "fix-cs": [ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" From 1ad00068b200cae608cba4ff11664b197a45b264 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 3 Jun 2021 07:20:01 +0200 Subject: [PATCH 05/39] CI: switch to GitHub Actions - step 1: sniff stage This commit: * Adds a GH Actions workflow for the CI checks which were previously run on Travis in the `sniff` stage. While these aren't 100% CS (= code style) checks, for the badge and workflow display, `CS` still seemed the most descriptive name. * Removes that part of the `.travis.yml` configuration. * Adds a "Build Status" badge in the Readme to use the results from this particular GH Actions runs. Notes: Builds will run on all pushes and on pull requests and can be manually triggered. --- .gitattributes | 1 + .github/workflows/cs.yml | 42 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 18 ----------------- README.md | 1 + 4 files changed, 44 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/cs.yml diff --git a/.gitattributes b/.gitattributes index a6c007a..68fde82 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,6 +9,7 @@ /.gitignore export-ignore /.travis.yml export-ignore /.cache/ export-ignore +/.github/ export-ignore /.phpcs.xml.dist export-ignore /phpunit.xml.dist export-ignore /tests/ export-ignore diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml new file mode 100644 index 0000000..60e905b --- /dev/null +++ b/.github/workflows/cs.yml @@ -0,0 +1,42 @@ +name: CS + +on: + # Run on all pushes and on all pull requests. + push: + pull_request: + # Allow manually triggering the workflow. + workflow_dispatch: + +jobs: + checkcs: + name: 'Basic CS and QA checks' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + tools: cs2pr + + # Install dependencies and handle caching in one go. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies + uses: "ramsey/composer-install@v1" + + # Validate the composer.json file. + # @link https://getcomposer.org/doc/03-cli.md#validate + - name: Validate Composer installation + run: composer validate --no-check-all --strict + + # Check the code-style consistency of the PHP files. + - name: Check PHP code style + continue-on-error: true + run: composer check-cs -- --report-full --report-checkstyle=./phpcs-report.xml + + - name: Show PHPCS results in PR + run: cs2pr ./phpcs-report.xml diff --git a/.travis.yml b/.travis.yml index 7d2583f..8e999dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,6 @@ cache: - $HOME/.composer/cache/files # Cache directory for more recent Composer versions. - $HOME/.cache/composer/files - # Cache CI tooling cache files. - - .cache php: - 5.6 @@ -24,25 +22,9 @@ php: - 8.0 - "nightly" -# Define the stages used. -stages: - - name: sniff - - name: test - jobs: fast_finish: true include: - #### SNIFF STAGE #### - - stage: sniff - php: 7.4 - script: - # Validate the composer.json file. - # @link https://getcomposer.org/doc/03-cli.md#validate - - composer validate --no-check-all --strict - - # Check the code style of the code base. - - composer check-cs - - stage: test php: 5.5 dist: trusty diff --git a/README.md b/README.md index 12247ee..665880e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ PHPUnit Polyfills ===================================================== [![Version](https://poser.pugx.org/yoast/phpunit-polyfills/version)](//packagist.org/packages/yoast/phpunit-polyfills) +[![CS Build Status](https://github.com/Yoast/PHPUnit-Polyfills/actions/workflows/cs.yml/badge.svg)](https://github.com/Yoast/PHPUnit-Polyfills/actions/workflows/cs.yml) [![Travis Build Status](https://travis-ci.com/Yoast/PHPUnit-Polyfills.svg?branch=main)](https://travis-ci.com/Yoast/PHPUnit-Polyfills/branches) [![Minimum PHP Version](https://img.shields.io/packagist/php-v/yoast/phpunit-polyfills.svg?maxAge=3600)](https://packagist.org/packages/yoast/phpunit-polyfills) [![License: BSD3](https://poser.pugx.org/yoast/phpunit-polyfills/license)](https://github.com/Yoast/PHPUnit-Polyfills/blob/master/LICENSE) From 6f664c69713cd61768bc43f58ba13c6f6039c2e8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 3 Jun 2021 07:28:59 +0200 Subject: [PATCH 06/39] CI: switch to GitHub Actions - step 2: test and lint stage This commit: * Adds a GH Actions workflow for the CI checks which were previously run on Travis in the `test` stage. * Removes the, now redundant, `.travis.yml` configuration. * Updates the `.gitattributes` file. * Updates the "Build Status" badge in the Readme to use the results from the GH `Test` Actions runs. Notes: As there is one job which are "allowed to fail" (`experimental` = true), the build status _may_ unfortunately show as "failed" when that job would fail, even though all non-experimental jobs have succeeded. This is a known issue in GHA: https://github.com/actions/toolkit/issues/399 --- .gitattributes | 1 - .github/workflows/test.yml | 60 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 58 ------------------------------------ README.md | 2 +- 4 files changed, 61 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.gitattributes b/.gitattributes index 68fde82..074a6f2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,7 +7,6 @@ # /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore /.cache/ export-ignore /.github/ export-ignore /.phpcs.xml.dist export-ignore diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..690a228 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,60 @@ +name: Test + +on: + # Run on all pushes and on all pull requests. + push: + pull_request: + # Allow manually triggering the workflow. + workflow_dispatch: + +jobs: + #### TEST STAGE #### + test: + runs-on: ubuntu-latest + + strategy: + matrix: + php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] + experimental: [false] + + include: + - php: '8.1' + experimental: true + + name: "Tests: PHP ${{ matrix.php }}" + + continue-on-error: ${{ matrix.experimental }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + # Install dependencies and handle caching in one go. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies for PHP < 8.1 + if: ${{ matrix.php < 8.1 }} + uses: "ramsey/composer-install@v1" + + # For PHP 8.1 and above, we need to install with ignore platform reqs as not all dependencies allow it yet. + - name: Install Composer dependencies for PHP >= 8.1 + if: ${{ matrix.php >= 8.1 }} + uses: "ramsey/composer-install@v1" + with: + composer-options: --ignore-platform-reqs + + - name: "Lint PHP files against parse errors - PHP < 7.0" + if: ${{ matrix.php < 7.0 }} + run: composer lint-lt71 + + - name: "Lint PHP files against parse errors - PHP >= 7.0" + if: ${{ matrix.php >= 7.0 }} + run: composer lint + + - name: Run the unit tests + run: composer test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8e999dd..0000000 --- a/.travis.yml +++ /dev/null @@ -1,58 +0,0 @@ -os: linux -dist: xenial - -language: php - -## Cache composer and apt downloads. -cache: - apt: true - directories: - # Cache directory for older Composer versions. - - $HOME/.composer/cache/files - # Cache directory for more recent Composer versions. - - $HOME/.cache/composer/files - -php: - - 5.6 - - 7.0 - - 7.1 - - 7.2 - - 7.3 - - 7.4 - - 8.0 - - "nightly" - -jobs: - fast_finish: true - include: - - stage: test - php: 5.5 - dist: trusty - - allow_failures: - # Allow failures for unstable builds. - - php: "nightly" - - -before_install: - # Speed up build time by disabling Xdebug when its not needed. - - phpenv config-rm xdebug.ini || echo 'No xdebug config.' - - -install: - # Set up environment using Composer. - - travis_retry composer install --no-interaction - - -script: - # Lint PHP files against parse errors. - - | - if [[ ${TRAVIS_PHP_VERSION:0:1} != "5" ]]; then - composer lint - else - # Special case for PHP < 7.0. - composer lint-lt71 - fi - - # Run the unit tests. - - composer test diff --git a/README.md b/README.md index 665880e..80af9f7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ PHPUnit Polyfills [![Version](https://poser.pugx.org/yoast/phpunit-polyfills/version)](//packagist.org/packages/yoast/phpunit-polyfills) [![CS Build Status](https://github.com/Yoast/PHPUnit-Polyfills/actions/workflows/cs.yml/badge.svg)](https://github.com/Yoast/PHPUnit-Polyfills/actions/workflows/cs.yml) -[![Travis Build Status](https://travis-ci.com/Yoast/PHPUnit-Polyfills.svg?branch=main)](https://travis-ci.com/Yoast/PHPUnit-Polyfills/branches) +[![Test Build Status](https://github.com/Yoast/PHPUnit-Polyfills/actions/workflows/test.yml/badge.svg)](https://github.com/Yoast/PHPUnit-Polyfills/actions/workflows/test.yml) [![Minimum PHP Version](https://img.shields.io/packagist/php-v/yoast/phpunit-polyfills.svg?maxAge=3600)](https://packagist.org/packages/yoast/phpunit-polyfills) [![License: BSD3](https://poser.pugx.org/yoast/phpunit-polyfills/license)](https://github.com/Yoast/PHPUnit-Polyfills/blob/master/LICENSE) From bfdad9827f77d8b845457cdfaed3dd1f03552734 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 3 Jun 2021 08:29:03 +0200 Subject: [PATCH 07/39] README: add FAQ section covering functionality removed from PHPUnit ... with alternatives which can be used to replace that functionality. --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 80af9f7..3bcd27a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit - [TestCases](#testcases) - [TestListener](#testlistener) * [Using this library](#using-this-library) +* [Frequently Asked Questions](#frequently-asked-questions) * [Contributing](#contributing) * [License](#license) @@ -493,6 +494,23 @@ class FooTest extends TestCase ``` +Frequently Asked Questions +------- + +### Q: Will this package polyfill functionality which was removed from PHPUnit ? + +As a rule of thumb, removed functionality will not be polyfilled in this package. + +For frequently used, removed PHPUnit functionality, "helpers" may be provided. These _helpers_ are only intended as an interim solution to allow users of this package more time to refactor their tests away from the removed functionality. + +#### Removed functionality without PHPUnit native replacement + +| PHPUnit | Removed | Issue | Remarks | +|---------|-----------------------|-----------|------------------------| +| 9.0 | `assertArraySubset()` | [#1](https://github.com/Yoast/PHPUnit-Polyfills/issues/1) | The [`dms/phpunit-arraysubset-asserts`](https://packagist.org/packages/dms/phpunit-arraysubset-asserts) package polyfills this functionality.
As of [version 0.3.0](https://github.com/rdohms/phpunit-arraysubset-asserts/releases/tag/v0.3.0) this package can be installed in combination with PHP 5.4 - current and PHPUnit 4.8.36/5.7.21 - current.
Alternatively, tests can be refactored using the patterns outlined in [issue #1](https://github.com/Yoast/PHPUnit-Polyfills/issues/1). +| 9.0 | `assertAttribute*()` | [#2](https://github.com/Yoast/PHPUnit-Polyfills/issues/2) | Refactor the tests to not directly test private/protected properties.
As an interim solution, the [`Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper`](#yoastphpunitpolyfillshelpersassertattributehelper) trait is available. + + Contributing ------- Contributions to this project are welcome. Clone the repo, branch off from `develop`, make your changes, commit them and send in a pull request. From aa1db102180f9c11b41b59f03b80b59ae71d6d05 Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Sat, 5 Jun 2021 09:51:35 +0100 Subject: [PATCH 08/39] Handle strings containing empty strings assertions PHPUnit 6.4.2 added a fix to gracefully handle the case when an empty string was passed to `assertStringContainsString()` and variant assertions. Versions earlier than that gave a "mb_strpos(): Empty delimiter" error instead. This change adds a fix to also handle that case, when using PHPUnit-Polyfills with versions of PHPUnit earlier than 6.4.2, and passing an empty needle string. The use of asserting true is true stops the test from being marked as risky, which is what would happen if we just `return true;`. --- src/Polyfills/AssertStringContains.php | 24 +++++++++++++ tests/Polyfills/AssertStringContainsTest.php | 36 ++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/Polyfills/AssertStringContains.php b/src/Polyfills/AssertStringContains.php index 599d18e..e3a682d 100644 --- a/src/Polyfills/AssertStringContains.php +++ b/src/Polyfills/AssertStringContains.php @@ -24,6 +24,12 @@ trait AssertStringContains { * @return void */ public static function assertStringContainsString( $needle, $haystack, $message = '' ) { + // Replicate fix added to PHPUnit 6.4.2. + if ( '' === $needle ) { + static::assertTrue( true, $message ); + return; + } + static::assertContains( $needle, $haystack, $message ); } @@ -37,6 +43,12 @@ public static function assertStringContainsString( $needle, $haystack, $message * @return void */ public static function assertStringContainsStringIgnoringCase( $needle, $haystack, $message = '' ) { + // Replicate fix added to PHPUnit 6.4.2. + if ( '' === $needle ) { + static::assertTrue( true, $message ); + return; + } + static::assertContains( $needle, $haystack, $message, true ); } @@ -50,6 +62,12 @@ public static function assertStringContainsStringIgnoringCase( $needle, $haystac * @return void */ public static function assertStringNotContainsString( $needle, $haystack, $message = '' ) { + // Replicate fix added to PHPUnit 6.4.2. + if ( '' === $needle ) { + static::assertTrue( true, $message ); + return; + } + static::assertNotContains( $needle, $haystack, $message ); } @@ -63,6 +81,12 @@ public static function assertStringNotContainsString( $needle, $haystack, $messa * @return void */ public static function assertStringNotContainsStringIgnoringCase( $needle, $haystack, $message = '' ) { + // Replicate fix added to PHPUnit 6.4.2. + if ( '' === $needle ) { + static::assertTrue( true, $message ); + return; + } + static::assertNotContains( $needle, $haystack, $message, true ); } } diff --git a/tests/Polyfills/AssertStringContainsTest.php b/tests/Polyfills/AssertStringContainsTest.php index ad063a2..abb58d5 100644 --- a/tests/Polyfills/AssertStringContainsTest.php +++ b/tests/Polyfills/AssertStringContainsTest.php @@ -49,4 +49,40 @@ public function testAssertStringNotContainsString() { public function testAssertStringNotContainsStringIgnoringCase() { $this->assertStringNotContainsStringIgnoringCase( 'Baz', 'foobar' ); } + + /** + * Verify availability of the assertStringContainsString() method with empty string. + * + * @return void + */ + public function testAssertStringContainsEmptyString() { + $this->assertStringContainsString( '', 'foobar' ); + } + + /** + * Verify availability of the assertStringContainsStringIgnoringCase() method with empty string. + * + * @return void + */ + public function testAssertStringContainsEmptyStringIgnoringCase() { + self::assertStringContainsStringIgnoringCase( '', 'foobar' ); + } + + /** + * Verify availability of the assertStringNotContainsString() method with empty string. + * + * @return void + */ + public function testAssertStringNotContainsEmptyString() { + self::assertStringNotContainsString( '', 'foobar' ); + } + + /** + * Verify availability of the assertStringNotContainsStringIgnoringCase() method with empty string. + * + * @return void + */ + public function testAssertStringNotContainsEmptyStringIgnoringCase() { + $this->assertStringNotContainsStringIgnoringCase( '', 'foobar' ); + } } From 3ba9692b02796df5f04fa5566f4d1778cdbe9e51 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 5 Jun 2021 18:11:51 +0200 Subject: [PATCH 09/39] Allow for using the polyfills on PHP 5.4 There was only one PHP 5.5 specific syntax being used in this library - `::class` -. This was the only blocker for allowing tests using the polyfills to run on PHP 5.4. This blocker has now been removed, making the polyfills available on PHP 5.4 in combination with PHPUnit 4.8.x as well. --- .github/workflows/test.yml | 2 +- README.md | 4 +-- composer.json | 4 +-- phpunitpolyfills-autoload.php | 28 +++++++++---------- src/Polyfills/ExpectPHPException.php | 13 +++------ tests/Helpers/AssertAttributesHelperTest.php | 8 ++++-- .../ExpectExceptionMessageMatchesTest.php | 2 +- tests/Polyfills/ExpectExceptionTest.php | 22 +++++++-------- tests/TestCases/TestCaseTestTrait.php | 2 +- tests/TestListeners/TestListenerTest.php | 2 +- 10 files changed, 41 insertions(+), 46 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 690a228..da63ce9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] + php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] experimental: [false] include: diff --git a/README.md b/README.md index 3bcd27a..1fc7322 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit Requirements ------------------------------------------- -* PHP 5.5 or higher. +* PHP 5.4 or higher. * PHPUnit 4.8 - 9.x (automatically required via Composer). @@ -456,7 +456,7 @@ class FooTest extends TestCase ### Use with PHPUnit < 5.7.0 -If your library still needs to support PHP 5.5 and therefore needs PHPUnit 4 for testing, there are a few caveats when using the traits stand-alone as we then enter "double-polyfill" territory. +If your library still needs to support PHP < 5.6 and therefore needs PHPUnit 4 for testing, there are a few caveats when using the traits stand-alone as we then enter "double-polyfill" territory. To prevent "conflicting method names" errors when a trait is `use`d multiple times in a class, the traits offered here do not attempt to solve this. diff --git a/composer.json b/composer.json index e41f246..e339d9f 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "php": ">=5.5", + "php": ">=5.4", "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "require-dev": { @@ -53,7 +53,7 @@ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git --exclude src/TestCases/TestCasePHPUnitGte8.php --exclude src/TestListeners/TestListenerDefaultImplementationPHPUnitGte7.php" ], "check-cs": [ - "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --runtime-set testVersion 5.5-" + "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --runtime-set testVersion 5.4-" ], "fix-cs": [ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 15af0cf..6d46ae9 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -2,8 +2,6 @@ namespace Yoast\PHPUnitPolyfills; -use PHPUnit\Framework\Assert; -use PHPUnit\Framework\TestCase; use PHPUnit\Runner\Version as PHPUnit_Version; if ( \class_exists( 'Yoast\PHPUnitPolyfills\Autoload', false ) === false ) { @@ -108,7 +106,7 @@ public static function load( $className ) { * @return void */ public static function loadAssertNumericType() { - if ( \method_exists( Assert::class, 'assertNan' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertNan' ) === false ) { // PHPUnit < 5.0.0. require_once __DIR__ . '/src/Polyfills/AssertNumericType.php'; return; @@ -141,7 +139,7 @@ public static function loadExpectException() { \class_alias( 'PHPUnit_Framework_Exception', 'PHPUnit\Framework\Exception' ); } - if ( \method_exists( TestCase::class, 'expectException' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\TestCase', 'expectException' ) === false ) { // PHPUnit < 5.2.0. require_once __DIR__ . '/src/Polyfills/ExpectException.php'; return; @@ -158,7 +156,7 @@ public static function loadExpectException() { * @return void */ public static function loadAssertFileDirectory() { - if ( \method_exists( Assert::class, 'assertIsReadable' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertIsReadable' ) === false ) { // PHPUnit < 5.6.0. require_once __DIR__ . '/src/Polyfills/AssertFileDirectory.php'; return; @@ -175,7 +173,7 @@ public static function loadAssertFileDirectory() { * @return void */ public static function loadExpectExceptionObject() { - if ( \method_exists( TestCase::class, 'expectExceptionObject' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\TestCase', 'expectExceptionObject' ) === false ) { // PHPUnit < 6.4.0. require_once __DIR__ . '/src/Polyfills/ExpectExceptionObject.php'; return; @@ -192,7 +190,7 @@ public static function loadExpectExceptionObject() { * @return void */ public static function loadAssertIsType() { - if ( \method_exists( Assert::class, 'assertIsArray' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertIsArray' ) === false ) { // PHPUnit < 7.5.0. require_once __DIR__ . '/src/Polyfills/AssertIsType.php'; return; @@ -209,7 +207,7 @@ public static function loadAssertIsType() { * @return void */ public static function loadAssertStringContains() { - if ( \method_exists( Assert::class, 'assertStringContainsString' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertStringContainsString' ) === false ) { // PHPUnit < 7.5.0. require_once __DIR__ . '/src/Polyfills/AssertStringContains.php'; return; @@ -226,7 +224,7 @@ public static function loadAssertStringContains() { * @return void */ public static function loadAssertEqualsSpecializations() { - if ( \method_exists( Assert::class, 'assertEqualsWithDelta' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertEqualsWithDelta' ) === false ) { // PHPUnit < 7.5.0. require_once __DIR__ . '/src/Polyfills/AssertEqualsSpecializations.php'; return; @@ -277,7 +275,7 @@ public static function loadExpectPHPException() { \class_alias( 'PHPUnit_Framework_Error_Deprecated', 'PHPUnit\Framework\Error\Deprecated' ); } - if ( \method_exists( TestCase::class, 'expectErrorMessage' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\TestCase', 'expectErrorMessage' ) === false ) { // PHPUnit < 8.4.0. require_once __DIR__ . '/src/Polyfills/ExpectPHPException.php'; return; @@ -294,7 +292,7 @@ public static function loadExpectPHPException() { * @return void */ public static function loadExpectExceptionMessageMatches() { - if ( \method_exists( TestCase::class, 'expectExceptionMessageMatches' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\TestCase', 'expectExceptionMessageMatches' ) === false ) { // PHPUnit < 8.4.0. require_once __DIR__ . '/src/Polyfills/ExpectExceptionMessageMatches.php'; return; @@ -311,7 +309,7 @@ public static function loadExpectExceptionMessageMatches() { * @return void */ public static function loadAssertFileEqualsSpecializations() { - if ( \method_exists( Assert::class, 'assertFileEqualsIgnoringCase' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertFileEqualsIgnoringCase' ) === false ) { // PHPUnit < 8.5.0. require_once __DIR__ . '/src/Polyfills/AssertFileEqualsSpecializations.php'; return; @@ -328,7 +326,7 @@ public static function loadAssertFileEqualsSpecializations() { * @return void */ public static function loadAssertionRenames() { - if ( \method_exists( Assert::class, 'assertMatchesRegularExpression' ) === false ) { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertMatchesRegularExpression' ) === false ) { // PHPUnit < 9.1.0. require_once __DIR__ . '/src/Polyfills/AssertionRenames.php'; return; @@ -344,7 +342,7 @@ public static function loadAssertionRenames() { * @return void */ public static function loadTestCase() { - if ( \class_exists( PHPUnit_Version::class ) === false + if ( \class_exists( '\PHPUnit\Runner\Version' ) === false || \version_compare( PHPUnit_Version::id(), '8.0.0', '<' ) ) { // PHPUnit < 8.0.0. @@ -362,7 +360,7 @@ public static function loadTestCase() { * @return void */ public static function loadTestListenerDefaultImplementation() { - if ( \class_exists( PHPUnit_Version::class ) === false ) { + if ( \class_exists( '\PHPUnit\Runner\Version' ) === false ) { /* * Alias one particular PHPUnit 4/5 class to its PHPUnit >= 6 name. * diff --git a/src/Polyfills/ExpectPHPException.php b/src/Polyfills/ExpectPHPException.php index dde1acf..a264010 100644 --- a/src/Polyfills/ExpectPHPException.php +++ b/src/Polyfills/ExpectPHPException.php @@ -2,11 +2,6 @@ namespace Yoast\PHPUnitPolyfills\Polyfills; -use PHPUnit\Framework\Error\Deprecated; -use PHPUnit\Framework\Error\Error; -use PHPUnit\Framework\Error\Notice; -use PHPUnit\Framework\Error\Warning; - /** * Polyfill the `TestCase::expectDeprecation*()`, `TestCase::expectNotice*()`, * `TestCase::expectWarning*()` and `TestCase::expectError*()` methods @@ -28,7 +23,7 @@ trait ExpectPHPException { * @return void */ public function expectDeprecation() { - $this->expectException( Deprecated::class ); + $this->expectException( '\PHPUnit\Framework\Error\Deprecated' ); } /** @@ -59,7 +54,7 @@ public function expectDeprecationMessageMatches( $regularExpression ) { * @return void */ public function expectNotice() { - $this->expectException( Notice::class ); + $this->expectException( '\PHPUnit\Framework\Error\Notice' ); } /** @@ -90,7 +85,7 @@ public function expectNoticeMessageMatches( $regularExpression ) { * @return void */ public function expectWarning() { - $this->expectException( Warning::class ); + $this->expectException( '\PHPUnit\Framework\Error\Warning' ); } /** @@ -121,7 +116,7 @@ public function expectWarningMessageMatches( $regularExpression ) { * @return void */ public function expectError() { - $this->expectException( Error::class ); + $this->expectException( '\PHPUnit\Framework\Error\Error' ); } /** diff --git a/tests/Helpers/AssertAttributesHelperTest.php b/tests/Helpers/AssertAttributesHelperTest.php index ff89d8f..b32b0de 100644 --- a/tests/Helpers/AssertAttributesHelperTest.php +++ b/tests/Helpers/AssertAttributesHelperTest.php @@ -2,7 +2,6 @@ namespace Yoast\PHPUnitPolyfills\Tests\Helpers; -use ReflectionException; use Yoast\PHPUnitPolyfills\TestCases\TestCase; use Yoast\PHPUnitPolyfills\Tests\Helpers\Fixtures\ClassWithProperties; @@ -66,7 +65,7 @@ public function testOriginalStatePrivateProperty() { * @return void */ public function testOriginalStateDynamicProperty() { - $this->expectException( ReflectionException::class ); + $this->expectException( '\ReflectionException' ); $this->getPropertyValue( $this->instance, 'dynamic' ); } @@ -113,7 +112,10 @@ public function testPropertyValueOnceSetPrivateProperty() { public function testPropertyValueOnceSetDynamicProperty() { $this->instance->setProperties(); - $this->assertInstanceOf( ClassWithProperties::class, $this->getPropertyValue( $this->instance, 'dynamic' ) ); + $this->assertInstanceOf( + '\Yoast\PHPUnitPolyfills\Tests\Helpers\Fixtures\ClassWithProperties', + $this->getPropertyValue( $this->instance, 'dynamic' ) + ); $this->assertFalse( $this->getProperty( $this->instance, 'dynamic' )->isDefault() ); } } diff --git a/tests/Polyfills/ExpectExceptionMessageMatchesTest.php b/tests/Polyfills/ExpectExceptionMessageMatchesTest.php index 744fd81..887ecbd 100644 --- a/tests/Polyfills/ExpectExceptionMessageMatchesTest.php +++ b/tests/Polyfills/ExpectExceptionMessageMatchesTest.php @@ -25,7 +25,7 @@ class ExpectExceptionMessageMatchesTest extends TestCase { * @throws Exception For testing purposes. */ public function testExpectExceptionMessageMatches() { - $this->expectException( Exception::class ); // Needed for PHPUnit 4.x/5.x. + $this->expectException( '\Exception' ); // Needed for PHPUnit 4.x/5.x. $this->expectExceptionMessageMatches( '`^a poly[a-z]+ [a-zA-Z0-9_]+ me(s){2}age$`i' ); throw new Exception( 'A polymorphic exception message' ); diff --git a/tests/Polyfills/ExpectExceptionTest.php b/tests/Polyfills/ExpectExceptionTest.php index a01b7e0..b78f79d 100644 --- a/tests/Polyfills/ExpectExceptionTest.php +++ b/tests/Polyfills/ExpectExceptionTest.php @@ -31,7 +31,7 @@ class ExpectExceptionTest extends TestCase { * @throws Exception For testing purposes. */ public function testExpectException() { - $this->expectException( Exception::class ); + $this->expectException( '\Exception' ); throw new Exception( 'message' ); } @@ -44,7 +44,7 @@ public function testExpectException() { * @throws Exception For testing purposes. */ public function testExpectExceptionCode() { - $this->expectException( Exception::class ); + $this->expectException( '\Exception' ); $this->expectExceptionCode( 404 ); throw new Exception( '', 404 ); @@ -79,7 +79,7 @@ public function testExpectExceptionCodeException() { * @throws Exception For testing purposes. */ public function testExpectExceptionMessage() { - $this->expectException( Exception::class ); + $this->expectException( '\Exception' ); $this->expectExceptionMessage( 'message' ); throw new Exception( 'message' ); @@ -95,7 +95,7 @@ public function testExpectExceptionMessage() { */ public function testExpectExceptionMessageException() { $regex = '`^Argument #1 \([^)]+\) of [^:]+::expectExceptionMessage\(\) must be a string`'; - if ( \class_exists( PHPUnit_Version::class ) === true + if ( \class_exists( '\PHPUnit\Runner\Version' ) === true && \version_compare( PHPUnit_Version::id(), '7.0.0', '>=' ) ) { $regex = '`^Argument 1 passed to [^:]+::expectExceptionMessage\(\) must be of the type string`'; @@ -121,7 +121,7 @@ public function testExpectExceptionMessageException() { * @throws Exception For testing purposes. */ public function testExpectExceptionMessageAndCode() { - $this->expectException( Exception::class ); + $this->expectException( '\Exception' ); $this->expectExceptionMessage( 'message' ); $this->expectExceptionCode( 404 ); @@ -137,7 +137,7 @@ public function testExpectExceptionMessageAndCode() { */ public function testExpectExceptionMessageAndCodeFailOnCode() { $test = new ThrowExceptionTestCase( 'test' ); - $test->expectException( Exception::class ); + $test->expectException( '\Exception' ); $test->expectExceptionMessage( 'A runtime error occurred' ); $test->expectExceptionCode( 404 ); @@ -160,7 +160,7 @@ public function testExpectExceptionMessageAndCodeFailOnCode() { */ public function testExpectExceptionMessageAndCodeFailOnMsg() { $test = new ThrowExceptionTestCase( 'test' ); - $test->expectException( Exception::class ); + $test->expectException( '\Exception' ); $test->expectExceptionMessage( 'message' ); $test->expectExceptionCode( 999 ); @@ -188,7 +188,7 @@ public function testExpectExceptionMessageAndCodeFailOnMsg() { * @throws Exception For testing purposes. */ public function testExpectExceptionMessageRegExp() { - $this->expectException( Exception::class ); + $this->expectException( '\Exception' ); $this->expectExceptionMessageRegExp( '/^foo/' ); throw new Exception( 'foobar' ); @@ -248,7 +248,7 @@ public function testExpectExceptionMessageRegExpException() { * @throws Exception For testing purposes. */ public function testExpectExceptionMessageRegExpAndCode() { - $this->expectException( Exception::class ); + $this->expectException( '\Exception' ); $this->expectExceptionMessageRegExp( '/^foo/' ); $this->expectExceptionCode( 404 ); @@ -270,7 +270,7 @@ public function testExpectExceptionMessageRegExpAndCode() { */ public function testExpectExceptionMessageRegExpAndCodeFailOnCode() { $test = new ThrowExceptionTestCase( 'test' ); - $test->expectException( Exception::class ); + $test->expectException( '\Exception' ); $test->expectExceptionMessageRegExp( '/^A runtime/' ); $test->expectExceptionCode( 404 ); @@ -299,7 +299,7 @@ public function testExpectExceptionMessageRegExpAndCodeFailOnCode() { */ public function testExpectExceptionMessageRegExpAndCodeFailOnMsg() { $test = new ThrowExceptionTestCase( 'test' ); - $test->expectException( Exception::class ); + $test->expectException( '\Exception' ); $test->expectExceptionMessageRegExp( '/^foo/' ); $test->expectExceptionCode( 999 ); diff --git a/tests/TestCases/TestCaseTestTrait.php b/tests/TestCases/TestCaseTestTrait.php index 1e87117..067b570 100644 --- a/tests/TestCases/TestCaseTestTrait.php +++ b/tests/TestCases/TestCaseTestTrait.php @@ -83,7 +83,7 @@ public function testAvailabilityExpectPHPExceptionTrait() { * @throws Exception For testing purposes. */ public function testAvailabilityExpectExceptionMessageMatchesTrait() { - $this->expectException( Exception::class ); + $this->expectException( '\Exception' ); $this->expectExceptionMessageMatches( '`^a poly[a-z]+ [a-zA-Z0-9_]+ me(s){2}age$`i' ); throw new Exception( 'A polymorphic exception message' ); diff --git a/tests/TestListeners/TestListenerTest.php b/tests/TestListeners/TestListenerTest.php index b0aac41..ab905aa 100644 --- a/tests/TestListeners/TestListenerTest.php +++ b/tests/TestListeners/TestListenerTest.php @@ -42,7 +42,7 @@ class TestListenerTest extends TestCase { * @return void */ protected function set_up() { - if ( \class_exists( TestResult::class ) ) { + if ( \class_exists( '\PHPUnit\Framework\TestResult' ) ) { // PHPUnit 6.0.0+. $this->result = new TestResult(); } From 7b32021f828c77b0012f5352012789a5a445b33b Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Sat, 5 Jun 2021 23:14:32 +0100 Subject: [PATCH 10/39] Handle empty string assertions by skipping tests - The backfill trait only applies for PHPUnit < 7.5.0 - The empty string handling was added in PHPUnit 6.4.2. - Assertions with empty strings run with PHPUnit below 6.4.2 always need the fix, giving a skipped test. - Assertions with empty strings run with PHPUnit 6.4.2 or above, don't need the fix, unless using a negative assertion, since an empty string will always exist in another string, hence the NotContains will always fail. --- src/Polyfills/AssertStringContains.php | 63 +++++++++++++++++++++----- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/src/Polyfills/AssertStringContains.php b/src/Polyfills/AssertStringContains.php index e3a682d..6c60508 100644 --- a/src/Polyfills/AssertStringContains.php +++ b/src/Polyfills/AssertStringContains.php @@ -25,9 +25,8 @@ trait AssertStringContains { */ public static function assertStringContainsString( $needle, $haystack, $message = '' ) { // Replicate fix added to PHPUnit 6.4.2. - if ( '' === $needle ) { - static::assertTrue( true, $message ); - return; + if ( self::needsEmptyStringFix( $needle, false ) ) { + self::markTestSkipped( 'Asserting a string contains an empty string, which always "exists" in any other string.'); } static::assertContains( $needle, $haystack, $message ); @@ -44,9 +43,8 @@ public static function assertStringContainsString( $needle, $haystack, $message */ public static function assertStringContainsStringIgnoringCase( $needle, $haystack, $message = '' ) { // Replicate fix added to PHPUnit 6.4.2. - if ( '' === $needle ) { - static::assertTrue( true, $message ); - return; + if ( self::needsEmptyStringFix( $needle, false ) ) { + self::markTestSkipped( 'Asserting a string contains an empty string, which always "exists" in any other string.'); } static::assertContains( $needle, $haystack, $message, true ); @@ -63,9 +61,8 @@ public static function assertStringContainsStringIgnoringCase( $needle, $haystac */ public static function assertStringNotContainsString( $needle, $haystack, $message = '' ) { // Replicate fix added to PHPUnit 6.4.2. - if ( '' === $needle ) { - static::assertTrue( true, $message ); - return; + if ( self::needsEmptyStringFix( $needle, true ) ) { + self::markTestSkipped( 'Asserting a string contains an empty string, which always "exists" in any other string.'); } static::assertNotContains( $needle, $haystack, $message ); @@ -82,11 +79,53 @@ public static function assertStringNotContainsString( $needle, $haystack, $messa */ public static function assertStringNotContainsStringIgnoringCase( $needle, $haystack, $message = '' ) { // Replicate fix added to PHPUnit 6.4.2. - if ( '' === $needle ) { - static::assertTrue( true, $message ); - return; + if ( self::needsEmptyStringFix( $needle, true ) ) { + self::markTestSkipped( 'Asserting a string contains an empty string, which always "exists" in any other string.'); } static::assertNotContains( $needle, $haystack, $message, true ); } + + /** + * Decide if a fix for an empty needle string is needed. + * + * PHPUnit 6.4.2 added a fix to gracefully handle the case when an empty string was passed to + * `assertStringContainsString()` and variant assertions. Versions earlier than that gave a "mb_strpos(): Empty + * delimiter" error instead. + * + * If the needle string is empty, and the PHPUnit version is found to be lower than 6.4.2, or 6.4.2 or above and + * part of a NotContains assertion, then return true, which will result in tests marked as skipped. + * + * To not have tests marked as skipped, test authors should ensure the needle string + * passed to the assertion is not empty. + * + * @param string $needle_string String that should be looked for in haystack string. + * @param bool $is_negative Whether assertion is negative i.e. NotContains + * + * @return bool True unless the needle string is empty, or PHPUnit is 6.4.2 or later, or + * PHPUnit version can't be identified. + */ + protected static function needsEmptyStringFix( $needle_string, $is_negative ) { + if ( '' !== $needle_string ) { + return false; + } + + $phpunit_version = ''; + if ( class_exists( 'PHPUnit\Runner\Version' ) ) { + $phpunit_version = \PHPUnit\Runner\Version::id(); + } elseif ( class_exists( 'PHPUnit_Runner_Version' ) ) { + $phpunit_version = \PHPUnit_Runner_Version::id(); + } + + // If the version can't be identified, assume everything is good. + if ( '' === $phpunit_version ) { + return false; + } + + if ( ! $is_negative && version_compare( $phpunit_version, '6.4.2', '>=' ) ) { + return false; + } + + return true; + } } From 2d48948109fa8df752a9fa301009604962cfa94a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 6 Jun 2021 05:08:57 +0200 Subject: [PATCH 11/39] XTestCase: set to lowest possible visibility This aligns the visibility of the methods in the `XTestCase` class with the minimum acceptable visibility to allow the methods to work. This also ensures that the visibility used in the class now matches that of the code sample in the README. | Native method name | Annotation | Minimum visibility | |------------------------|----------------|--------------------| | `setUpBeforeClass()` | `@beforeClass` | `public` | | `setUp()` | `@before` | `protected` | | `tearDown()` | `@after` | `protected` | | `tearDownAfterClass()` | `@afterClass` | `public` | Fixes #10 --- src/TestCases/XTestCase.php | 4 ++-- tests/TestCases/XTestCaseTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/TestCases/XTestCase.php b/src/TestCases/XTestCase.php index a1451ff..18c1857 100644 --- a/src/TestCases/XTestCase.php +++ b/src/TestCases/XTestCase.php @@ -64,7 +64,7 @@ public static function setUpFixturesBeforeClass() { * * @return void */ - public function setUpFixtures() { + protected function setUpFixtures() { parent::setUp(); } @@ -77,7 +77,7 @@ public function setUpFixtures() { * * @return void */ - public function tearDownFixtures() { + protected function tearDownFixtures() { parent::tearDown(); } diff --git a/tests/TestCases/XTestCaseTest.php b/tests/TestCases/XTestCaseTest.php index a9c8871..321ce08 100644 --- a/tests/TestCases/XTestCaseTest.php +++ b/tests/TestCases/XTestCaseTest.php @@ -57,7 +57,7 @@ public static function setUpFixturesBeforeClass() { * * @return void */ - public function setUpFixtures() { + protected function setUpFixtures() { parent::setUpFixtures(); ++self::$before; @@ -70,7 +70,7 @@ public function setUpFixtures() { * * @return void */ - public function tearDownFixtures() { + protected function tearDownFixtures() { ++self::$after; parent::tearDownFixtures(); From fb3c6720f6a15dc9bfa12af76c580eea8258afef Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 6 Jun 2021 09:06:38 +0200 Subject: [PATCH 12/39] TestCaseTestTrait: add missing tests * Commit 057a83d34ab2b27f0b80f6f54d817590db75f7b4 added the `AssertNumericType` trait and a corresponding availability test to the `TestCaseTestTrait`, but the test name did not follow the naming convention used in the file. * Commit b5372989715ad406358e6924ded0a6e98b3e8014 added the `ExpectException` trait, but did not add a corresponding availability test to the `TestCaseTestTrait`. * Commit 347c76864e41cf254303fc1cca9f74bb2d41ec5c added the `AssertFileDirectory` trait, but did not add a corresponding availability test to the `TestCaseTestTrait`. Fixed now. --- tests/TestCases/TestCaseTestTrait.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/TestCases/TestCaseTestTrait.php b/tests/TestCases/TestCaseTestTrait.php index 067b570..14bc703 100644 --- a/tests/TestCases/TestCaseTestTrait.php +++ b/tests/TestCases/TestCaseTestTrait.php @@ -115,7 +115,31 @@ public function testAvailabilityAssertionRenamesTrait() { * * @return void */ - public function testAssertNan() { + public function testAvailabilityAssertNumericTypeTrait() { self::assertNan( \acos( 8 ) ); } + + /** + * Test availability of trait polyfilled PHPUnit methods [10]. + * + * @return void + * + * @throws Exception For testing purposes. + */ + public function testAvailabilityExpectExceptionTrait() { + $this->expectException( '\Exception' ); + $this->expectExceptionMessage( 'message' ); + + throw new Exception( 'message' ); + } + + /** + * Verify availability of trait polyfilled PHPUnit methods [11]. + * + * @return void + */ + public function testAvailabilityAssertFileDirectory() { + $path = __DIR__ . \DIRECTORY_SEPARATOR; + $this->assertDirectoryExists( $path ); + } } From 6b0c464ea9abcbb1feea589aeb3cff3ff084b90d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 7 Jun 2021 00:03:39 +0200 Subject: [PATCH 13/39] Tests: add bootstrap file ## Context The main reason to add a test bootstrap file is to allow for defining some `class_alias`-es for PHPUnit native classes which are only used in the test suite. This allows for some minor simplifications to the existing test code and for these same simplifications to be available for future test adjustments (which are needed for PHPUnit 10.0 support). ## Autoloading of the necessary files / Phar compatibility The XML configuration allows for only one bootstrap file, so adding a `bootstrap` file now means that the Composer `vendor/autoload.php` file has to be loaded from within the `tests/bootstrap.php` file. This presents a problem when running the tests via a PHPUnit Phar file as the loading of the Composer autoload will block the tests from running if the PHP version used to run the Phar file is not the same PHP version as the one used to run `composer install`. In that case, the test run will exit before it starts with an error along the lines of: ``` Fatal error: Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 7.3.0". You are running 7.0.33. in ./vendor/composer/platform_check.php on line 24 ``` To get round this conundrum, the bootstrap file will load the Composer autoload file conditionally: only when the tests are not run via a Phar. When the tests are run via a Phar, the Polyfill autoload file will be hard-required and a custom autoloader function will take care of loading test helper files which are not automatically loaded by PHPUnit, like the Fixture classes and the `Yoast\PHPUnitPolyfills\Tests\TestCases\TestCaseTestTrait`. ## Version checks in the `phpunitpolyfills-autoload.php` file As the `PHPUnit_Runner_Version` will be aliased to `PHPUnit\Runner\Version` in the test bootstrap file, it will - for the purposes of our tests - always exist. In PHPUnit itself, the `PHPUnit_Runner_Version` class existed prior to PHPUnit 6.0.0, the `PHPUnit\Runner\Version` class as of PHPUnit 6.0.0. They have no overlap in tagged releases. The Polyfill autoload file relies (relied) on only one of the class names existing, while with the change to the bootstrap file, this is no longer correct as in the context of PHPUnit 4.x and 5.x both class names will exist, while in the context of PHPUnit >= 6.0, only the namespaced name will exist. Either way, this means that we have to (and safely can) reverse the condition used in the `phpunitpolyfills-autoload.php` file from checking whether the PHPUnit 6.0+ class name does not exists to checking whether the PHPUnit < 6.0 class name _does_ exist. ## Other Includes removing work-arounds for the different class names in individual test classes., --- phpunit.xml.dist | 2 +- phpunitpolyfills-autoload.php | 4 +- tests/Polyfills/ExpectExceptionTest.php | 4 +- tests/TestListeners/TestListenerTest.php | 11 +--- tests/bootstrap.php | 64 ++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 tests/bootstrap.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4a9b3d8..ce26cd9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.2/phpunit.xsd" backupGlobals="true" - bootstrap="./vendor/autoload.php" + bootstrap="./tests/bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="true" colors="true" forceCoversAnnotation="true"> diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 6d46ae9..524aa95 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -342,7 +342,7 @@ public static function loadAssertionRenames() { * @return void */ public static function loadTestCase() { - if ( \class_exists( '\PHPUnit\Runner\Version' ) === false + if ( \class_exists( '\PHPUnit_Runner_Version' ) === true || \version_compare( PHPUnit_Version::id(), '8.0.0', '<' ) ) { // PHPUnit < 8.0.0. @@ -360,7 +360,7 @@ public static function loadTestCase() { * @return void */ public static function loadTestListenerDefaultImplementation() { - if ( \class_exists( '\PHPUnit\Runner\Version' ) === false ) { + if ( \class_exists( '\PHPUnit_Runner_Version' ) === true ) { /* * Alias one particular PHPUnit 4/5 class to its PHPUnit >= 6 name. * diff --git a/tests/Polyfills/ExpectExceptionTest.php b/tests/Polyfills/ExpectExceptionTest.php index b78f79d..73941a3 100644 --- a/tests/Polyfills/ExpectExceptionTest.php +++ b/tests/Polyfills/ExpectExceptionTest.php @@ -95,9 +95,7 @@ public function testExpectExceptionMessage() { */ public function testExpectExceptionMessageException() { $regex = '`^Argument #1 \([^)]+\) of [^:]+::expectExceptionMessage\(\) must be a string`'; - if ( \class_exists( '\PHPUnit\Runner\Version' ) === true - && \version_compare( PHPUnit_Version::id(), '7.0.0', '>=' ) - ) { + if ( \version_compare( PHPUnit_Version::id(), '7.0.0', '>=' ) ) { $regex = '`^Argument 1 passed to [^:]+::expectExceptionMessage\(\) must be of the type string`'; if ( \PHP_MAJOR_VERSION === 8 ) { $regex = '`^[^:]+::expectExceptionMessage\(\): Argument \#1 \([^)]+\) must be of type string`'; diff --git a/tests/TestListeners/TestListenerTest.php b/tests/TestListeners/TestListenerTest.php index ab905aa..6296a03 100644 --- a/tests/TestListeners/TestListenerTest.php +++ b/tests/TestListeners/TestListenerTest.php @@ -3,7 +3,6 @@ namespace Yoast\PHPUnitPolyfills\Tests\TestListeners; use PHPUnit\Framework\TestResult; -use PHPUnit_Framework_TestResult; use Yoast\PHPUnitPolyfills\TestCases\TestCase; use Yoast\PHPUnitPolyfills\Tests\TestListeners\Fixtures\Failure; use Yoast\PHPUnitPolyfills\Tests\TestListeners\Fixtures\Incomplete; @@ -42,15 +41,7 @@ class TestListenerTest extends TestCase { * @return void */ protected function set_up() { - if ( \class_exists( '\PHPUnit\Framework\TestResult' ) ) { - // PHPUnit 6.0.0+. - $this->result = new TestResult(); - } - else { - // PHPUnit < 6.0.0. - $this->result = new PHPUnit_Framework_TestResult(); - } - + $this->result = new TestResult(); $this->listener = new TestListenerImplementation(); $this->result->addListener( $this->listener ); diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..95883b1 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,64 @@ + Date: Sun, 6 Jun 2021 06:17:34 +0200 Subject: [PATCH 14/39] CI/QA: add experimental build against PHPUnit 10.0 (unreleased) This build is - for now - allowed to fail while support for PHPUnit 10.0 is being added to the repo. --- .github/workflows/test.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da63ce9..2c67e15 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,13 +15,19 @@ jobs: strategy: matrix: php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] + phpunit: ['auto'] experimental: [false] include: - php: '8.1' + phpunit: 'auto' experimental: true - name: "Tests: PHP ${{ matrix.php }}" + - php: '8.0' + phpunit: '^10.0' + experimental: true + + name: "Tests: PHP ${{ matrix.php }} - PHPUnit: ${{matrix.phpunit}}" continue-on-error: ${{ matrix.experimental }} @@ -35,6 +41,10 @@ jobs: php-version: ${{ matrix.php }} coverage: none + - name: 'Composer: set PHPUnit version for tests' + if: ${{ matrix.phpunit != 'auto' }} + run: composer require --no-update phpunit/phpunit:"${{ matrix.phpunit }}" + # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies - name: Install Composer dependencies for PHP < 8.1 From cbfc3ebb629c85c56bb0aeb6d74681312d43831d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 7 Jun 2021 00:33:22 +0200 Subject: [PATCH 15/39] AssertFileDirectoryTest: allow for running the tests on PHPUnit 10.0.0 Selectively skip some tests from the `AssertFileDirectoryTest` class. These tests test that a method which was available between PHPUnit 5.6.0 - 9.x is polyfilled correctly for PHPUnit < 5.6.0. As these methods have been removed in PHPUnit 10.0.0 in favour of assertions with better names, these tests will fail on PHPUnit 10.0.0. This is not an issue, as the methods with the new names have also been polyfilled and those tests _do_ pass on PHPUnit 10.0.0. So if people want their tests to be able to run on PHPUnit 10.0.0, they should use the new names of the methods and all will be good. This is already [documented as such in the README](https://github.com/Yoast/PHPUnit-Polyfills#use-with-phpunit--570). This affects the following methods: | Old name | New name | |------------------------------|---------------------------------| | `assertNotIsReadable()` | `assertIsNotReadable()` | | `assertNotIsWritable()` | `assertIsNotWritable()` | | `assertDirectoryNotExists()` | `assertDirectoryDoesNotExist()` | --- tests/Polyfills/AssertFileDirectoryTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Polyfills/AssertFileDirectoryTest.php b/tests/Polyfills/AssertFileDirectoryTest.php index 0685aaa..068f989 100644 --- a/tests/Polyfills/AssertFileDirectoryTest.php +++ b/tests/Polyfills/AssertFileDirectoryTest.php @@ -78,6 +78,8 @@ public function testAssertNotIsReadable() { * * This exception was thrown until PHP 7.0.0. Since PHP 7.0.0, a PHP native TypeError will * be thrown based on the type declaration. + * + * @requires PHPUnit < 9.99.99 */ public function testAssertNotIsReadableException() { try { @@ -160,6 +162,8 @@ public function testAssertNotIsWritable() { * * This exception was thrown until PHP 7.0.0. Since PHP 7.0.0, a PHP native TypeError will * be thrown based on the type declaration. + * + * @requires PHPUnit < 9.99.99 */ public function testAssertNotIsWritableException() { try { @@ -242,6 +246,8 @@ public function testAssertDirectoryNotExists() { * * This exception was thrown until PHP 7.0.0. Since PHP 7.0.0, a PHP native TypeError will * be thrown based on the type declaration. + * + * @requires PHPUnit < 9.99.99 */ public function testAssertDirectoryNotExistsException() { try { From 1c35de4febaa0085059289256fcabc41a68cd9e4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 6 Jun 2021 07:41:17 +0200 Subject: [PATCH 16/39] TestListenerTest: fix PHPUnit 10.0.0 compatibility The `$name` parameter for the `TestCase::__construct()` method has become mandatory and should contain the name of the test method to run. Ref: https://github.com/sebastianbergmann/phpunit/commit/705874f1b867fd99865e43cb5eaea4e6d141582f --- This PHPUnit 10.0.0 change will generally speaking not affect "normal" users as it is rare for them to declare `__construct()` methods in test classes or to instantiate test classes directly, as this is the responsibility of PHPUnit. In most cases, only test code testing for cross-version PHPUnit compatibility while using PHPUnit to run the tests will be affected. Even so, fixing this is straight-forward, as the `$name` parameter was already an optional parameter since way back when, so passing the parameter in allows the tests to run (and pass) cross-version. --- tests/TestListeners/TestListenerTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/TestListeners/TestListenerTest.php b/tests/TestListeners/TestListenerTest.php index 6296a03..669a5ef 100644 --- a/tests/TestListeners/TestListenerTest.php +++ b/tests/TestListeners/TestListenerTest.php @@ -53,7 +53,7 @@ protected function set_up() { * @return void */ public function testError() { - $test = new TestError(); + $test = new TestError( 'runTest' ); $test->run( $this->result ); $this->assertSame( 1, $this->listener->startTestCount, 'test start count failed' ); @@ -72,7 +72,7 @@ public function testError() { * @return void */ public function testWarning() { - $test = new Warning(); + $test = new Warning( 'runTest' ); $test->run( $this->result ); $this->assertSame( 1, $this->listener->startTestCount, 'test start count failed' ); @@ -86,7 +86,7 @@ public function testWarning() { * @return void */ public function testFailure() { - $test = new Failure(); + $test = new Failure( 'runTest' ); $test->run( $this->result ); $this->assertSame( 1, $this->listener->startTestCount, 'test start count failed' ); @@ -100,7 +100,7 @@ public function testFailure() { * @return void */ public function testIncomplete() { - $test = new Incomplete(); + $test = new Incomplete( 'runTest' ); $test->run( $this->result ); $this->assertSame( 1, $this->listener->startTestCount, 'test start count failed' ); @@ -118,7 +118,7 @@ public function testIncomplete() { * @return void */ public function testRisky() { - $test = new Risky(); + $test = new Risky( 'runTest' ); $test->run( $this->result ); $this->assertSame( 1, $this->listener->startTestCount, 'test start count failed' ); @@ -132,7 +132,7 @@ public function testRisky() { * @return void */ public function testSkipped() { - $test = new Skipped(); + $test = new Skipped( 'runTest' ); $test->run( $this->result ); $this->assertSame( 1, $this->listener->startTestCount, 'test start count failed' ); @@ -146,7 +146,7 @@ public function testSkipped() { * @return void */ public function testStartStop() { - $test = new Success(); + $test = new Success( 'runTest' ); $test->run( $this->result ); $this->assertSame( 1, $this->listener->startTestCount, 'test start count failed' ); From 79746be4c1bee1a7f3131b9f78e52ee315fb20f9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 7 Jun 2021 01:32:53 +0200 Subject: [PATCH 17/39] ExpectExceptionTest: fix PHPUnit 10.0.0 compatibility [1] ... by always passing a TestResult object to the `run()` method. The `$testResult` parameter was previously optional, but will become required as of PHPUnit 10.0.0. Also, the `run()` method used to return an instance of `TestResult`, but will now return `void`. Ref: https://github.com/sebastianbergmann/phpunit/commit/77e60150c2185da760692277aeb2b337686b1f30 --- Along the same lines as 25, this PHPUnit 10.0.0 change will generally speaking not affect "normal" users. In most cases, only test code testing for cross-version PHPUnit compatibility while using PHPUnit to run the tests will be affected. Even so, fixing this is straight-forward, as the `$result` parameter was already an optional parameter since way back when, so instantiating the `TestResult` and passing the parameter in allows the tests to run cross-version. --- tests/Polyfills/ExpectExceptionTest.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/Polyfills/ExpectExceptionTest.php b/tests/Polyfills/ExpectExceptionTest.php index 73941a3..befc568 100644 --- a/tests/Polyfills/ExpectExceptionTest.php +++ b/tests/Polyfills/ExpectExceptionTest.php @@ -5,6 +5,7 @@ use Exception; use PHPUnit\Framework\Exception as PHPUnit_Exception; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestResult; use PHPUnit\Runner\Version as PHPUnit_Version; use TypeError; use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; @@ -61,7 +62,8 @@ public function testExpectExceptionCode() { */ public function testExpectExceptionCodeException() { $test = new InvalidExceptionCodeTestCase( 'test' ); - $result = $test->run(); + $result = new TestResult(); + $test->run( $result ); $this->assertSame( 1, $result->errorCount() ); $this->assertSame( 1, \count( $result ) ); @@ -103,7 +105,8 @@ public function testExpectExceptionMessageException() { } $test = new InvalidExceptionMessageTestCase( 'test' ); - $result = $test->run(); + $result = new TestResult(); + $test->run( $result ); $this->assertSame( 1, $result->errorCount() ); $this->assertSame( 1, \count( $result ) ); @@ -139,7 +142,8 @@ public function testExpectExceptionMessageAndCodeFailOnCode() { $test->expectExceptionMessage( 'A runtime error occurred' ); $test->expectExceptionCode( 404 ); - $result = $test->run(); + $result = new TestResult(); + $test->run( $result ); $this->assertSame( 1, $result->failureCount() ); $this->assertSame( 1, \count( $result ) ); @@ -162,7 +166,8 @@ public function testExpectExceptionMessageAndCodeFailOnMsg() { $test->expectExceptionMessage( 'message' ); $test->expectExceptionCode( 999 ); - $result = $test->run(); + $result = new TestResult(); + $test->run( $result ); $this->assertSame( 1, $result->failureCount() ); $this->assertSame( 1, \count( $result ) ); @@ -272,7 +277,8 @@ public function testExpectExceptionMessageRegExpAndCodeFailOnCode() { $test->expectExceptionMessageRegExp( '/^A runtime/' ); $test->expectExceptionCode( 404 ); - $result = $test->run(); + $result = new TestResult(); + $test->run( $result ); $this->assertSame( 1, $result->failureCount() ); $this->assertSame( 1, \count( $result ) ); @@ -301,7 +307,8 @@ public function testExpectExceptionMessageRegExpAndCodeFailOnMsg() { $test->expectExceptionMessageRegExp( '/^foo/' ); $test->expectExceptionCode( 999 ); - $result = $test->run(); + $result = new TestResult(); + $test->run( $result ); $this->assertSame( 1, $result->failureCount() ); $this->assertSame( 1, \count( $result ) ); From 56ed5b849eb26ae9969cc81e873e786321ca237c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 7 Jun 2021 01:42:00 +0200 Subject: [PATCH 18/39] ExpectExceptionTest: fix PHPUnit 10.0.0 compatibility [2] The `TestCase::getStatusMessage()` method has been removed in favour of `TestStatus` objects. Ref: https://github.com/sebastianbergmann/phpunit/commit/98b7c199c251d874d8649f1f2191a8f3c1cacd0f --- Again: this PHPUnit 10.0.0 change will generally speaking not affect "normal" users. In most cases, only test code testing for cross-version PHPUnit compatibility while using PHPUnit to run the tests will be affected. Fixing this requires a feature-based toggle to retrieve the status message. --- tests/Polyfills/ExpectExceptionTest.php | 32 ++++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tests/Polyfills/ExpectExceptionTest.php b/tests/Polyfills/ExpectExceptionTest.php index befc568..5a00543 100644 --- a/tests/Polyfills/ExpectExceptionTest.php +++ b/tests/Polyfills/ExpectExceptionTest.php @@ -69,7 +69,7 @@ public function testExpectExceptionCodeException() { $this->assertSame( 1, \count( $result ) ); $this->assertMatchesRegularExpression( '`^Argument #1 \([^)]+\) of [^:]+::expectExceptionCode\(\) must be a integer or string`', - $test->getStatusMessage() + $this->getMessageContent( $test ) ); } @@ -110,7 +110,7 @@ public function testExpectExceptionMessageException() { $this->assertSame( 1, $result->errorCount() ); $this->assertSame( 1, \count( $result ) ); - $this->assertMatchesRegularExpression( $regex, $test->getStatusMessage() ); + $this->assertMatchesRegularExpression( $regex, $this->getMessageContent( $test ) ); } /** @@ -149,7 +149,7 @@ public function testExpectExceptionMessageAndCodeFailOnCode() { $this->assertSame( 1, \count( $result ) ); $this->assertSame( 'Failed asserting that 999 is equal to expected exception code 404.', - $test->getStatusMessage() + $this->getMessageContent( $test ) ); } @@ -173,7 +173,7 @@ public function testExpectExceptionMessageAndCodeFailOnMsg() { $this->assertSame( 1, \count( $result ) ); $this->assertSame( "Failed asserting that exception message 'A runtime error occurred' contains 'message'.", - $test->getStatusMessage() + $this->getMessageContent( $test ) ); } @@ -284,7 +284,7 @@ public function testExpectExceptionMessageRegExpAndCodeFailOnCode() { $this->assertSame( 1, \count( $result ) ); $this->assertSame( 'Failed asserting that 999 is equal to expected exception code 404.', - $test->getStatusMessage() + $this->getMessageContent( $test ) ); } @@ -314,7 +314,27 @@ public function testExpectExceptionMessageRegExpAndCodeFailOnMsg() { $this->assertSame( 1, \count( $result ) ); $this->assertSame( "Failed asserting that exception message 'A runtime error occurred' matches '/^foo/'.", - $test->getStatusMessage() + $this->getMessageContent( $test ) ); } + + /** + * Helper method to retrieve the status message in a PHPUnit cross-version compatible manner. + * + * @param TestCase $test The test object. + * + * @return string + */ + private function getMessageContent( $test ) { + if ( \method_exists( $test, 'getStatusMessage' ) === false ) { + // PHPUnit >= 10.0.0. + return $test->status()->message(); + } + else { + // PHPUnit < 10.0.0. + return $test->getStatusMessage(); + } + + return ''; + } } From 0611ef23b582fe5e79798ab99fb5aeef4ec2459c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 8 Jun 2021 18:33:29 +0200 Subject: [PATCH 19/39] AssertStringContains: finish off the fix for mb_strpos() warning As per https://github.com/Yoast/PHPUnit-Polyfills/pull/18#issuecomment-855319240. it is okay for the polyfills to work around this particular bug, but the work-around should not change the behaviour of the assertions. With that in mind, I've made the following changes: ### `AssertStringContains` trait * Removed the test skipping, in favour of emulating the PHPUnit >= 6.4.2 behaviour. A test may be doing multiple assertions. If this particular assertion would cause the test to be skipped, the other assertions (if after this assertion) would not be run anymore. This would be a change in behaviour, which is undesirable. * As the behaviour of the underlying assertion has not changed significantly between PHPUnit 6.4.2 < 7.5.0, the version check is not really needed and removing it simplifies the fix and makes it more performant while not impacting the functionality. * Moved the documentation for the fix to the class docblock as the `needsEmptyStringFix()` method has been removed. Notes: * There is no `return` after the calls to `fail()` as when the assertion fails the test, it throws an exception which will automatically stop the execution of the function. * Also note the use of `static::` rather than `self::` to call PHPUnit native functionality. This allows for existing method overloads in a child class of the PHPUnit native `TestCase` to be respected. ### Tests * Expanded the documentation for the tests so it will always be clear that these tests are for a specific fix in the polyfills. * Added a dataprovider for two of the tests to allow for testing with different haystacks, making sure a certain edge case doesn't give any issues. * Updated the tests for the `assertStringNotContainsString()` polyfills to expect a `PHPUnit\Framework\AssertionFailedError` exception. --- src/Polyfills/AssertStringContains.php | 81 ++++++----------- tests/Polyfills/AssertStringContainsTest.php | 91 +++++++++++++++++--- 2 files changed, 106 insertions(+), 66 deletions(-) diff --git a/src/Polyfills/AssertStringContains.php b/src/Polyfills/AssertStringContains.php index 6c60508..4f226ab 100644 --- a/src/Polyfills/AssertStringContains.php +++ b/src/Polyfills/AssertStringContains.php @@ -10,7 +10,15 @@ * Use of Assert::assertContains() and Assert::assertNotContains() with string haystacks was * deprecated in PHPUnit 7.5.0 and removed in PHPUnit 9.0.0. * + * Note: this polyfill accounts for a bug in PHPUnit < 6.4.2. + * Prior to PHPUnit 6.4.2, when the $needle was an empty string, a PHP native + * "mb_strpos(): Empty delimiter" warning would be thrown, which would result + * in the test failing. + * This polyfill prevents that warning and emulates the PHPUnit >= 6.4.2 behaviour. + * * @link https://github.com/sebastianbergmann/phpunit/issues/3422 + * @link https://github.com/sebastianbergmann/phpunit/issues/2520 + * @link https://github.com/sebastianbergmann/phpunit/pull/2778 */ trait AssertStringContains { @@ -24,9 +32,9 @@ trait AssertStringContains { * @return void */ public static function assertStringContainsString( $needle, $haystack, $message = '' ) { - // Replicate fix added to PHPUnit 6.4.2. - if ( self::needsEmptyStringFix( $needle, false ) ) { - self::markTestSkipped( 'Asserting a string contains an empty string, which always "exists" in any other string.'); + if ( $needle === '' ) { + static::assertSame( $needle, $needle, $message ); + return; } static::assertContains( $needle, $haystack, $message ); @@ -42,9 +50,9 @@ public static function assertStringContainsString( $needle, $haystack, $message * @return void */ public static function assertStringContainsStringIgnoringCase( $needle, $haystack, $message = '' ) { - // Replicate fix added to PHPUnit 6.4.2. - if ( self::needsEmptyStringFix( $needle, false ) ) { - self::markTestSkipped( 'Asserting a string contains an empty string, which always "exists" in any other string.'); + if ( $needle === '' ) { + static::assertSame( $needle, $needle, $message ); + return; } static::assertContains( $needle, $haystack, $message, true ); @@ -60,9 +68,12 @@ public static function assertStringContainsStringIgnoringCase( $needle, $haystac * @return void */ public static function assertStringNotContainsString( $needle, $haystack, $message = '' ) { - // Replicate fix added to PHPUnit 6.4.2. - if ( self::needsEmptyStringFix( $needle, true ) ) { - self::markTestSkipped( 'Asserting a string contains an empty string, which always "exists" in any other string.'); + if ( $needle === '' ) { + if ( $message === '' ) { + $message = "Failed asserting that '{$haystack}' does not contain \"{$needle}\"."; + } + + static::fail( $message ); } static::assertNotContains( $needle, $haystack, $message ); @@ -78,54 +89,14 @@ public static function assertStringNotContainsString( $needle, $haystack, $messa * @return void */ public static function assertStringNotContainsStringIgnoringCase( $needle, $haystack, $message = '' ) { - // Replicate fix added to PHPUnit 6.4.2. - if ( self::needsEmptyStringFix( $needle, true ) ) { - self::markTestSkipped( 'Asserting a string contains an empty string, which always "exists" in any other string.'); - } - - static::assertNotContains( $needle, $haystack, $message, true ); - } - - /** - * Decide if a fix for an empty needle string is needed. - * - * PHPUnit 6.4.2 added a fix to gracefully handle the case when an empty string was passed to - * `assertStringContainsString()` and variant assertions. Versions earlier than that gave a "mb_strpos(): Empty - * delimiter" error instead. - * - * If the needle string is empty, and the PHPUnit version is found to be lower than 6.4.2, or 6.4.2 or above and - * part of a NotContains assertion, then return true, which will result in tests marked as skipped. - * - * To not have tests marked as skipped, test authors should ensure the needle string - * passed to the assertion is not empty. - * - * @param string $needle_string String that should be looked for in haystack string. - * @param bool $is_negative Whether assertion is negative i.e. NotContains - * - * @return bool True unless the needle string is empty, or PHPUnit is 6.4.2 or later, or - * PHPUnit version can't be identified. - */ - protected static function needsEmptyStringFix( $needle_string, $is_negative ) { - if ( '' !== $needle_string ) { - return false; - } + if ( $needle === '' ) { + if ( $message === '' ) { + $message = "Failed asserting that '{$haystack}' does not contain \"{$needle}\"."; + } - $phpunit_version = ''; - if ( class_exists( 'PHPUnit\Runner\Version' ) ) { - $phpunit_version = \PHPUnit\Runner\Version::id(); - } elseif ( class_exists( 'PHPUnit_Runner_Version' ) ) { - $phpunit_version = \PHPUnit_Runner_Version::id(); + static::fail( $message ); } - // If the version can't be identified, assume everything is good. - if ( '' === $phpunit_version ) { - return false; - } - - if ( ! $is_negative && version_compare( $phpunit_version, '6.4.2', '>=' ) ) { - return false; - } - - return true; + static::assertNotContains( $needle, $haystack, $message, true ); } } diff --git a/tests/Polyfills/AssertStringContainsTest.php b/tests/Polyfills/AssertStringContainsTest.php index abb58d5..203ca0a 100644 --- a/tests/Polyfills/AssertStringContainsTest.php +++ b/tests/Polyfills/AssertStringContainsTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; /** * Availability test for the functions polyfilled by the AssertStringContains trait. @@ -13,6 +14,7 @@ class AssertStringContainsTest extends TestCase { use AssertStringContains; + use ExpectException; // Needed for PHPUnit < 5.2.0 support. /** * Verify availability of the assertStringContainsString() method. @@ -51,38 +53,105 @@ public function testAssertStringNotContainsStringIgnoringCase() { } /** - * Verify availability of the assertStringContainsString() method with empty string. + * Verify that the assertStringContainsString() method does not throw a mb_strpos() + * PHP error when passed an empty $needle. + * + * This was possible due to a bug which existed in PHPUnit < 6.4.2. + * + * @link https://github.com/Yoast/PHPUnit-Polyfills/issues/17 + * @link https://github.com/sebastianbergmann/phpunit/pull/2778/ + * + * @dataProvider dataHaystacks + * + * @param string $haystack Haystack. * * @return void */ - public function testAssertStringContainsEmptyString() { - $this->assertStringContainsString( '', 'foobar' ); + public function testAssertStringContainsStringEmptyNeedle( $haystack ) { + $this->assertStringContainsString( '', $haystack ); } /** - * Verify availability of the assertStringContainsStringIgnoringCase() method with empty string. + * Verify that the assertStringContainsStringIgnoringCase() method does not throw + * a mb_strpos() PHP error when passed an empty $needle. + * + * This was possible due to a bug which existed in PHPUnit < 6.4.2. + * + * @link https://github.com/Yoast/PHPUnit-Polyfills/issues/17 + * @link https://github.com/sebastianbergmann/phpunit/pull/2778/ * * @return void */ - public function testAssertStringContainsEmptyStringIgnoringCase() { + public function testAssertStringContainsStringIgnoringCaseEmptyNeedle() { self::assertStringContainsStringIgnoringCase( '', 'foobar' ); } /** - * Verify availability of the assertStringNotContainsString() method with empty string. + * Verify that the assertStringNotContainsString() method does not throw a mb_strpos() + * PHP error when passed an empty $needle. + * + * This was possible due to a bug which existed in PHPUnit < 6.4.2. + * + * @link https://github.com/Yoast/PHPUnit-Polyfills/issues/17 + * @link https://github.com/sebastianbergmann/phpunit/pull/2778/ + * + * @dataProvider dataHaystacks + * + * @param string $haystack Haystack. * * @return void */ - public function testAssertStringNotContainsEmptyString() { - self::assertStringNotContainsString( '', 'foobar' ); + public function testAssertStringNotContainsStringEmptyNeedle( $haystack ) { + $msg = "Failed asserting that '{$haystack}' does not contain \"\"."; + $exception = 'PHPUnit\Framework\AssertionFailedError'; + if ( \class_exists( 'PHPUnit_Framework_AssertionFailedError' ) ) { + // PHPUnit < 6. + $exception = 'PHPUnit_Framework_AssertionFailedError'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + self::assertStringNotContainsString( '', $haystack ); } /** - * Verify availability of the assertStringNotContainsStringIgnoringCase() method with empty string. + * Verify that the assertStringNotContainsStringIgnoringCase() method does not throw a mb_strpos() + * PHP error when passed an empty $needle. + * + * This was possible due to a bug which existed in PHPUnit < 6.4.2. + * + * @link https://github.com/Yoast/PHPUnit-Polyfills/issues/17 + * @link https://github.com/sebastianbergmann/phpunit/pull/2778/ * * @return void */ - public function testAssertStringNotContainsEmptyStringIgnoringCase() { - $this->assertStringNotContainsStringIgnoringCase( '', 'foobar' ); + public function testAssertStringNotContainsStringIgnoringCaseEmptyNeedle() { + $msg = 'Failed asserting that \'text with whitespace\' does not contain "".'; + $exception = 'PHPUnit\Framework\AssertionFailedError'; + if ( \class_exists( 'PHPUnit_Framework_AssertionFailedError' ) ) { + // PHPUnit < 6. + $exception = 'PHPUnit_Framework_AssertionFailedError'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $this->assertStringNotContainsStringIgnoringCase( '', 'text with whitespace' ); + } + + /** + * Data provider. + * + * @see testAssertStringContainsStringEmptyNeedle() For the array format. + * @see testAssertStringNotContainsStringEmptyNeedle() For the array format. + * + * @return array + */ + public function dataHaystacks() { + return [ + 'foobar as haystack' => [ 'foobar' ], + 'empty haystack' => [ '' ], + ]; } } From 804de38df717d726d26d5e74800d728e5f5cee20 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 8 Jun 2021 04:14:08 +0200 Subject: [PATCH 20/39] AssertClosedResource trait: polyfill the Assert::assertIs[Not]ClosedResource() methods PHPUnit 9.3.0 introduced the new `Assert::assertIsClosedResource()` and `Assert::assertIsNotClosedResource()` methods. This commit: * Adds two traits with the same name. One to polyfill the methods when not available in PHPUnit. The other to allow for `use`-ing the trait in PHPUnit versions in which the methods are already natively available. * Logic to the custom autoloader which will load the correct trait depending on the PHPUnit version used. * A `ResourceHelper` helper class containing the actual logic to determine whether something is a resource and whether that resource is open or closed. As the PHP native functionality used in PHPUnit itself was only added to PHP in PHP 7.2.0 and the polyfills are available to older PHP versions, this polyfill needs actual logic and doesn't just fall through to an underlying PHPUnit native method. With that in mind, an availability test is not sufficient to test this. Additionally, resources in various extensions may behave and identify differently, so this commit also adds extensive tests with a variety of functionality using resources in PHP. Note: the methods use `static::` to call the PHPUnit native functionality. This allows for existing method overloads in a child class of the PHPUnit native `TestCase` to be respected. Includes: * Adding information on the new polyfill to the README. * Adding the new polyfill to the existing `TestCases` classes. * Adding a few select exceptions to the PHPCS ruleset. --- .phpcs.xml.dist | 14 ++ README.md | 9 ++ phpunitpolyfills-autoload.php | 22 ++++ src/Helpers/ResourceHelper.php | 79 +++++++++++ src/Polyfills/AssertClosedResource.php | 48 +++++++ src/Polyfills/AssertClosedResource_Empty.php | 8 ++ src/TestCases/TestCasePHPUnitGte8.php | 2 + src/TestCases/TestCasePHPUnitLte7.php | 2 + src/TestCases/XTestCase.php | 2 + .../AssertClosedResourceBzip2Test.php | 73 +++++++++++ .../AssertClosedResourceCurlTest.php | 77 +++++++++++ .../Polyfills/AssertClosedResourceDirTest.php | 71 ++++++++++ .../AssertClosedResourceEnchantTest.php | 80 +++++++++++ .../AssertClosedResourceFileTest.php | 71 ++++++++++ .../AssertClosedResourceFinfoTest.php | 77 +++++++++++ .../Polyfills/AssertClosedResourceGdTest.php | 77 +++++++++++ .../AssertClosedResourceNotResourceTest.php | 79 +++++++++++ .../AssertClosedResourceProcessTest.php | 71 ++++++++++ .../AssertClosedResourceShmopTest.php | 124 ++++++++++++++++++ .../AssertClosedResourceTestCase.php | 60 +++++++++ .../AssertClosedResourceWddxTest.php | 80 +++++++++++ .../Polyfills/AssertClosedResourceZipTest.php | 81 ++++++++++++ .../AssertClosedResourceZlibTest.php | 73 +++++++++++ tests/Polyfills/Fixtures/test.tar.bz2 | Bin 0 -> 119 bytes tests/Polyfills/Fixtures/test.tar.gz | Bin 0 -> 125 bytes tests/Polyfills/Fixtures/test.txt | 1 + tests/Polyfills/Fixtures/test.zip | Bin 0 -> 163 bytes tests/TestCases/TestCaseTestTrait.php | 12 ++ 28 files changed, 1293 insertions(+) create mode 100644 src/Helpers/ResourceHelper.php create mode 100644 src/Polyfills/AssertClosedResource.php create mode 100644 src/Polyfills/AssertClosedResource_Empty.php create mode 100644 tests/Polyfills/AssertClosedResourceBzip2Test.php create mode 100644 tests/Polyfills/AssertClosedResourceCurlTest.php create mode 100644 tests/Polyfills/AssertClosedResourceDirTest.php create mode 100644 tests/Polyfills/AssertClosedResourceEnchantTest.php create mode 100644 tests/Polyfills/AssertClosedResourceFileTest.php create mode 100644 tests/Polyfills/AssertClosedResourceFinfoTest.php create mode 100644 tests/Polyfills/AssertClosedResourceGdTest.php create mode 100644 tests/Polyfills/AssertClosedResourceNotResourceTest.php create mode 100644 tests/Polyfills/AssertClosedResourceProcessTest.php create mode 100644 tests/Polyfills/AssertClosedResourceShmopTest.php create mode 100644 tests/Polyfills/AssertClosedResourceTestCase.php create mode 100644 tests/Polyfills/AssertClosedResourceWddxTest.php create mode 100644 tests/Polyfills/AssertClosedResourceZipTest.php create mode 100644 tests/Polyfills/AssertClosedResourceZlibTest.php create mode 100644 tests/Polyfills/Fixtures/test.tar.bz2 create mode 100644 tests/Polyfills/Fixtures/test.tar.gz create mode 100644 tests/Polyfills/Fixtures/test.txt create mode 100644 tests/Polyfills/Fixtures/test.zip diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 93bc4ab..dd6e745 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -45,6 +45,8 @@ + + @@ -79,6 +81,18 @@ ############################################################################# --> + + + /src/Helpers/ResourceHelper\.php$ + /src/Polyfills/AssertClosedResource*\.php$ + + + + + /src/Helpers/ResourceHelper\.php$ + /src/Polyfills/AssertClosedResource*\.php$ + + diff --git a/README.md b/README.md index 1fc7322..b0bab08 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,15 @@ Polyfills the following renamed methods: These methods were introduced in PHPUnit 9.1.0. The original methods these new methods replace were hard deprecated in PHPUnit 9.1.0 and (will be) removed in PHPUnit 10.0.0. +#### PHPUnit < 9.3.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource` + +Polyfills the following methods: +| | | +|------------------------------------|---------------------------------------| +| `Assert::assertIsClosedResource()` | `Assert::assertIsNotClosedResource()` | + +These methods were introduced in PHPUnit 9.3.0. + ### Helper traits diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 524aa95..2dd8213 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -69,6 +69,10 @@ public static function load( $className ) { self::loadAssertionRenames(); return true; + case 'Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource': + self::loadAssertClosedResource(); + return true; + case 'Yoast\PHPUnitPolyfills\TestCases\TestCase': self::loadTestCase(); return true; @@ -80,6 +84,7 @@ public static function load( $className ) { /* * Handles: * - Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper + * - Yoast\PHPUnitPolyfills\Helpers\ResourceHelper * - Yoast\PHPUnitPolyfills\TestCases\XTestCase * - Yoast\PHPUnitPolyfills\TestListeners\TestListenerSnakeCaseMethods */ @@ -336,6 +341,23 @@ public static function loadAssertionRenames() { require_once __DIR__ . '/src/Polyfills/AssertionRenames_Empty.php'; } + /** + * Load the AssertClosedResource polyfill or an empty trait with the same name + * if a PHPUnit version is used which already contains this functionality. + * + * @return void + */ + public static function loadAssertClosedResource() { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertIsClosedResource' ) === false ) { + // PHPUnit < 9.3.0. + require_once __DIR__ . '/src/Polyfills/AssertClosedResource.php'; + return; + } + + // PHPUnit >= 9.3.0. + require_once __DIR__ . '/src/Polyfills/AssertClosedResource_Empty.php'; + } + /** * Load the appropriate TestCase class based on the PHPUnit version being used. * diff --git a/src/Helpers/ResourceHelper.php b/src/Helpers/ResourceHelper.php new file mode 100644 index 0000000..c8bee8f --- /dev/null +++ b/src/Helpers/ResourceHelper.php @@ -0,0 +1,79 @@ += 9.3.0 in which the polyfill is not needed. + */ +trait AssertClosedResource {} diff --git a/src/TestCases/TestCasePHPUnitGte8.php b/src/TestCases/TestCasePHPUnitGte8.php index c66e526..cd5b120 100644 --- a/src/TestCases/TestCasePHPUnitGte8.php +++ b/src/TestCases/TestCasePHPUnitGte8.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase as PHPUnit_TestCase; use Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper; +use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches; @@ -21,6 +22,7 @@ abstract class TestCase extends PHPUnit_TestCase { use AssertAttributeHelper; + use AssertClosedResource; use AssertFileEqualsSpecializations; use AssertionRenames; use ExpectExceptionMessageMatches; diff --git a/src/TestCases/TestCasePHPUnitLte7.php b/src/TestCases/TestCasePHPUnitLte7.php index 5a143f4..6dc9596 100644 --- a/src/TestCases/TestCasePHPUnitLte7.php +++ b/src/TestCases/TestCasePHPUnitLte7.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase as PHPUnit_TestCase; use Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper; +use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource; use Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileDirectory; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations; @@ -28,6 +29,7 @@ abstract class TestCase extends PHPUnit_TestCase { use AssertAttributeHelper; + use AssertClosedResource; use AssertEqualsSpecializations; use AssertFileDirectory; use AssertFileEqualsSpecializations; diff --git a/src/TestCases/XTestCase.php b/src/TestCases/XTestCase.php index 18c1857..a5ff6ac 100644 --- a/src/TestCases/XTestCase.php +++ b/src/TestCases/XTestCase.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase as PHPUnit_TestCase; use Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper; +use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource; use Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileDirectory; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations; @@ -30,6 +31,7 @@ abstract class XTestCase extends PHPUnit_TestCase { use AssertAttributeHelper; + use AssertClosedResource; use AssertEqualsSpecializations; use AssertFileDirectory; use AssertFileEqualsSpecializations; diff --git a/tests/Polyfills/AssertClosedResourceBzip2Test.php b/tests/Polyfills/AssertClosedResourceBzip2Test.php new file mode 100644 index 0000000..4d27459 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceBzip2Test.php @@ -0,0 +1,73 @@ +assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \bzopen( __DIR__ . '/Fixtures/test.tar.bz2', 'r' ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \bzclose( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \bzopen( __DIR__ . '/Fixtures/test.tar.bz2', 'r' ); + + self::assertIsNotClosedResource( $resource ); + + \bzclose( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \bzopen( __DIR__ . '/Fixtures/test.tar.bz2', 'r' ); + \bzclose( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceCurlTest.php b/tests/Polyfills/AssertClosedResourceCurlTest.php new file mode 100644 index 0000000..8c7a62c --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceCurlTest.php @@ -0,0 +1,77 @@ += 8.0. + * + * @covers \Yoast\PHPUnitPolyfills\Helpers\ResourceHelper + * @covers \Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource + * + * @requires extension curl + * @requires PHP < 8.0 + */ +class AssertClosedResourceCurlTest extends AssertClosedResourceTestCase { + + use AssertClosedResource; + + /** + * Verify availability of the assertIsClosedResource() method. + * + * @return void + */ + public function testAssertIsClosedResourceWithClosedResource() { + $resource = \curl_init( 'http://httpbin.org/anything' ); + \curl_close( $resource ); + + $this->assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \curl_init( 'http://httpbin.org/anything' ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \curl_close( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \curl_init( 'http://httpbin.org/anything' ); + + self::assertIsNotClosedResource( $resource ); + + \curl_close( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \curl_init( 'http://httpbin.org/anything' ); + \curl_close( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceDirTest.php b/tests/Polyfills/AssertClosedResourceDirTest.php new file mode 100644 index 0000000..372a026 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceDirTest.php @@ -0,0 +1,71 @@ +assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \opendir( __DIR__ . '/Fixtures/' ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \closedir( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \opendir( __DIR__ . '/Fixtures/' ); + + self::assertIsNotClosedResource( $resource ); + + \closedir( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \opendir( __DIR__ . '/Fixtures/' ); + \closedir( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceEnchantTest.php b/tests/Polyfills/AssertClosedResourceEnchantTest.php new file mode 100644 index 0000000..4a92a2e --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceEnchantTest.php @@ -0,0 +1,80 @@ += 8.0. + * + * @covers \Yoast\PHPUnitPolyfills\Helpers\ResourceHelper + * @covers \Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource + * + * @requires extension enchant + * @requires PHP < 8.0 + * + * @phpcs:disable Generic.PHP.DeprecatedFunctions.Deprecated + * @phpcs:disable PHPCompatibility.FunctionUse.RemovedFunctions.enchant_broker_freeDeprecated + */ +class AssertClosedResourceEnchantTest extends AssertClosedResourceTestCase { + + use AssertClosedResource; + + /** + * Verify availability of the assertIsClosedResource() method. + * + * @return void + */ + public function testAssertIsClosedResourceWithClosedResource() { + $resource = \enchant_broker_init(); + \enchant_broker_free( $resource ); + + $this->assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \enchant_broker_init(); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \enchant_broker_free( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \enchant_broker_init(); + + self::assertIsNotClosedResource( $resource ); + + \enchant_broker_free( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \enchant_broker_init(); + \enchant_broker_free( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceFileTest.php b/tests/Polyfills/AssertClosedResourceFileTest.php new file mode 100644 index 0000000..51ee9c0 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceFileTest.php @@ -0,0 +1,71 @@ +assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \fopen( __DIR__ . '/Fixtures/test.txt', 'r' ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \fclose( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \fopen( __DIR__ . '/Fixtures/test.txt', 'r' ); + + self::assertIsNotClosedResource( $resource ); + + \fclose( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \fopen( __DIR__ . '/Fixtures/test.txt', 'r' ); + \fclose( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceFinfoTest.php b/tests/Polyfills/AssertClosedResourceFinfoTest.php new file mode 100644 index 0000000..d8bc724 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceFinfoTest.php @@ -0,0 +1,77 @@ += 8.1. + * + * @covers \Yoast\PHPUnitPolyfills\Helpers\ResourceHelper + * @covers \Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource + * + * @requires extension finfo + * @requires PHP < 8.1 + */ +class AssertClosedResourceFinfoTest extends AssertClosedResourceTestCase { + + use AssertClosedResource; + + /** + * Verify availability of the assertIsClosedResource() method. + * + * @return void + */ + public function testAssertIsClosedResourceWithClosedResource() { + $resource = \finfo_open(); + \finfo_close( $resource ); + + $this->assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \finfo_open(); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \finfo_close( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \finfo_open(); + + self::assertIsNotClosedResource( $resource ); + + \finfo_close( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \finfo_open(); + \finfo_close( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceGdTest.php b/tests/Polyfills/AssertClosedResourceGdTest.php new file mode 100644 index 0000000..a0a21a0 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceGdTest.php @@ -0,0 +1,77 @@ += 8.0. + * + * @covers \Yoast\PHPUnitPolyfills\Helpers\ResourceHelper + * @covers \Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource + * + * @requires extension gd + * @requires PHP < 8.0 + */ +class AssertClosedResourceGdTest extends AssertClosedResourceTestCase { + + use AssertClosedResource; + + /** + * Verify availability of the assertIsClosedResource() method. + * + * @return void + */ + public function testAssertIsClosedResourceWithClosedResource() { + $resource = \imagecreate( 1, 1 ); + \imagedestroy( $resource ); + + $this->assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \imagecreate( 1, 1 ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \imagedestroy( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \imagecreate( 1, 1 ); + + self::assertIsNotClosedResource( $resource ); + + \imagedestroy( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \imagecreate( 1, 1 ); + \imagedestroy( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceNotResourceTest.php b/tests/Polyfills/AssertClosedResourceNotResourceTest.php new file mode 100644 index 0000000..ce3fe19 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceNotResourceTest.php @@ -0,0 +1,79 @@ +expectException( $exception ); + $this->expectExceptionMessageMatches( $pattern ); + + $this->assertIsClosedResource( $value ); + } + + /** + * Verify that the assertIsNotClosedResource() method passes the test when the variable + * passed is not a resource. + * + * @dataProvider dataNotResource + * + * @param mixed $value The value to test. + * + * @return void + */ + public function testAssertIsNotClosedResource( $value ) { + self::assertIsNotClosedResource( $value ); + } + + /** + * Data provider + * + * @return array + */ + public function dataNotResource() { + return [ + 'null' => [ null ], + 'false' => [ false ], + 'true' => [ true ], + 'int' => [ 1024 ], + 'float' => [ -78.72836 ], + 'string' => [ 'text string' ], + 'array-empty' => [ [] ], + 'array-not-empty' => [ [ 'key' => 'value' ] ], + 'object' => [ new stdClass() ], + ]; + } +} diff --git a/tests/Polyfills/AssertClosedResourceProcessTest.php b/tests/Polyfills/AssertClosedResourceProcessTest.php new file mode 100644 index 0000000..4163b27 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceProcessTest.php @@ -0,0 +1,71 @@ +&1', 'r' ); + \pclose( $resource ); + + $this->assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \popen( '/path/to/executable 2>&1', 'r' ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \pclose( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \popen( '/path/to/executable 2>&1', 'r' ); + + self::assertIsNotClosedResource( $resource ); + + \pclose( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \popen( '/path/to/executable 2>&1', 'r' ); + \pclose( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceShmopTest.php b/tests/Polyfills/AssertClosedResourceShmopTest.php new file mode 100644 index 0000000..feffafc --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceShmopTest.php @@ -0,0 +1,124 @@ += 8.0. + * + * @covers \Yoast\PHPUnitPolyfills\Helpers\ResourceHelper + * @covers \Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource + * + * @requires extension shmop + * + * @phpcs:disable Generic.PHP.DeprecatedFunctions.Deprecated + * @phpcs:disable PHPCompatibility.FunctionUse.RemovedFunctions.shmop_closeDeprecated + */ +class AssertClosedResourceShmopTest extends AssertClosedResourceTestCase { + + use AssertClosedResource; + + /** + * Skip these tests on incompatible PHP versions. + * + * {@internal The PHPUnit `@requires` tag can only handle one `PHP` requirement, + * while we need two.} + * + * @before + */ + protected function skipOnIncompatiblePHP() { + if ( \PHP_VERSION_ID < 70000 || \PHP_VERSION_ID >= 80000 ) { + $this->markTestSkipped( 'This test requires PHP 7.x.' ); + } + } + + /** + * Verify availability of the assertIsClosedResource() method. + * + * @return void + */ + public function testAssertIsClosedResourceWithClosedResource() { + $key = $this->getKey( __FILE__, 't' ); + $resource = \shmop_open( $key, 'c', 0644, 100 ); + \shmop_close( $resource ); + + $this->assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $key = $this->getKey( __FILE__, 't' ); + $resource = \shmop_open( $key, 'c', 0644, 100 ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \shmop_close( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $key = $this->getKey( __FILE__, 't' ); + $resource = \shmop_open( $key, 'c', 0644, 100 ); + + self::assertIsNotClosedResource( $resource ); + + \shmop_close( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $key = $this->getKey( __FILE__, 't' ); + $resource = \shmop_open( $key, 'c', 0644, 100 ); + \shmop_close( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } + + /** + * Helper function: work round ftok() not always being available (on Windows). + * + * @link https://www.php.net/manual/en/function.ftok.php#43309 + * + * @param string $pathname Path to file. + * @param string $proj_id Project identifier. + * + * @return string + */ + private function getKey( $pathname, $proj_id ) { + if ( \function_exists( 'ftok' ) ) { + return \ftok( $pathname, $proj_id ); + } + + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + $st = @\stat( $pathname ); + if ( ! $st ) { + return -1; + } + + return \sprintf( '%u', ( ( $st['ino'] & 0xffff ) | ( ( $st['dev'] & 0xff ) << 16 ) | ( ( $proj_id & 0xff ) << 24 ) ) ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceTestCase.php b/tests/Polyfills/AssertClosedResourceTestCase.php new file mode 100644 index 0000000..00e4755 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceTestCase.php @@ -0,0 +1,60 @@ +expectException( $exception ); + $this->expectExceptionMessageMatches( $pattern ); + + $this->assertIsClosedResource( $actual ); + } + + /** + * Helper method: Verify that an exception is thrown when `assertIsNotClosedResource()` is passed a closed resource. + * + * @param resource $actual The resource under test. + * + * @return void + */ + public function isNotClosedResourceExpectExceptionOnClosedResource( $actual ) { + $msg = 'Failed asserting that NULL is not of type "resource (closed)"'; + $exception = 'PHPUnit\Framework\AssertionFailedError'; + if ( \class_exists( 'PHPUnit_Framework_AssertionFailedError' ) ) { + // PHPUnit < 6. + $exception = 'PHPUnit_Framework_AssertionFailedError'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + self::assertIsNotClosedResource( $actual ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceWddxTest.php b/tests/Polyfills/AssertClosedResourceWddxTest.php new file mode 100644 index 0000000..be01fed --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceWddxTest.php @@ -0,0 +1,80 @@ +assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \wddx_packet_start(); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \wddx_packet_end( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \wddx_packet_start(); + + self::assertIsNotClosedResource( $resource ); + + \wddx_packet_end( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \wddx_packet_start(); + \wddx_packet_end( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceZipTest.php b/tests/Polyfills/AssertClosedResourceZipTest.php new file mode 100644 index 0000000..0e7bf4a --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceZipTest.php @@ -0,0 +1,81 @@ +assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = @\zip_open( __DIR__ . '/Fixtures/test.zip' ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + @\zip_close( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = @\zip_open( __DIR__ . '/Fixtures/test.zip' ); + + self::assertIsNotClosedResource( $resource ); + + @\zip_close( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = @\zip_open( __DIR__ . '/Fixtures/test.zip' ); + @\zip_close( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceZlibTest.php b/tests/Polyfills/AssertClosedResourceZlibTest.php new file mode 100644 index 0000000..5816432 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceZlibTest.php @@ -0,0 +1,73 @@ +assertIsClosedResource( $resource ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \gzopen( __DIR__ . '/Fixtures/test.tar.gz', 'r' ); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \gzclose( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \gzopen( __DIR__ . '/Fixtures/test.tar.gz', 'r' ); + + self::assertIsNotClosedResource( $resource ); + + \gzclose( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \gzopen( __DIR__ . '/Fixtures/test.tar.gz', 'r' ); + \gzclose( $resource ); + + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + } +} diff --git a/tests/Polyfills/Fixtures/test.tar.bz2 b/tests/Polyfills/Fixtures/test.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..e7d36abadc107ebee7872c5260908bed6fe65811 GIT binary patch literal 119 zcmV--0EquWT4*^jL0KkKSsi&GqW}OvTaeNKKmmV*06=1)9zX;D0SF)fbSpGdL6FEc zqfbytDW{~+XaS%ejQ=9%1Yt)A2#O?P#3#kK*(O9-LukuXSk)?PL*Z6o01;iFc~D5T ZCN{Hrcf1|P&!ot|7ji{7P>>yYAfq-FE2IDb literal 0 HcmV?d00001 diff --git a/tests/Polyfills/Fixtures/test.tar.gz b/tests/Polyfills/Fixtures/test.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f5cdb18c8a87f88f16da6eb880a9ea684a41b2ed GIT binary patch literal 125 zcmV-@0D}J?iwFS6g}q<|1MSd33c^4P1<)L&Cy<|+n&@#d<0x)i*a#lq0VzE|p%j|8 z3Hh53A0k>6?P7(CnACeUv2x2-Z7o$f`PXSBFrcc0zva$26OTA+M$(@5e@vMM&;~w} f+ncaS*A97`{P}te+y@UKgm4@`uXqW#00;m8I4(8B literal 0 HcmV?d00001 diff --git a/tests/Polyfills/Fixtures/test.txt b/tests/Polyfills/Fixtures/test.txt new file mode 100644 index 0000000..33289e6 --- /dev/null +++ b/tests/Polyfills/Fixtures/test.txt @@ -0,0 +1 @@ +testing 123 \ No newline at end of file diff --git a/tests/Polyfills/Fixtures/test.zip b/tests/Polyfills/Fixtures/test.zip new file mode 100644 index 0000000000000000000000000000000000000000..3d93575df51775d7b8b421fca1571b0a3fe04e91 GIT binary patch literal 163 zcmWIWW@Zs#U|`^2c&&UqDCb$dGB1$F4HjWwC`m0Y(JQGa(e~8UJ9)-C+`z<$jUm9B zkx8BbmnIdURuE8708?-yxELS;j0_SCleDT#T%)fuesd@}nGK|8pMN3^;sassertDirectoryExists( $path ); } + + /** + * Verify availability of trait polyfilled PHPUnit methods [12]. + * + * @return void + */ + public function testAvailabilityAssertClosedResource() { + $resource = \fopen( __FILE__, 'r' ); + \fclose( $resource ); + + $this->assertIsClosedResource( $resource ); + } } From 1de3116f40b2fa93092ddd51015d53114fa11f61 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Jun 2021 01:42:00 +0200 Subject: [PATCH 21/39] AssertClosedResource: add `shouldClosedResourceAssertionBeSkipped()` helper function While creating tests for this polyfill, I discovered that for resources created via the `libxml` extension, the PHP native functions showed some curious behaviour. This behaviour basically makes the "is closed resource" determination unreliable for a select group of PHP versions in combination with resources created via the `libxml` extension. To prevent tests failing on this, calls to the `assertIs[Not]ClosedResource()` method should - selectively - be wrapped in a condition checking the output of the `AssertClosedResource::shouldClosedResourceAssertionBeSkipped()` method, like so: ```php if ( $this->shouldClosedResourceAssertionBeSkipped( $actual ) === false ) { $this->assertIsClosedResource( $actual ); } ``` As the PHPUnit native implementation of the assertions is also afflicted by the underlying bugs in PHP itself, this helper method is also available when the "empty" version of the `AssertClosedResource` trait is loaded and can therefore safely be used PHPUnit cross-version. As things stand, the `libxml` inconsistency is the only one I have discovered so far. However, there are quite a few extensions producing resources which are currently not covered in the tests as that would require databases or ssh/ftp/mail credentials to be added to the tests. Users are encouraged to report other inconsistencies found and the `ResourceHelper::isResourceStateReliable()` method can be expanded with additional inconsistencies when found in the future. Includes tests for this trait in combination with the `libxml` extension. Also adds calls to a few of the other tests to ensure that the `shouldClosedResourceAssertionBeSkipped()` correctly returns `false` in all non-affected situations. --- README.md | 25 ++++++ phpunit.xml.dist | 3 + src/Helpers/ResourceHelper.php | 66 ++++++++++++++ src/Polyfills/AssertClosedResource.php | 20 +++++ src/Polyfills/AssertClosedResource_Empty.php | 29 +++++- .../Polyfills/AssertClosedResourceDirTest.php | 2 + .../Polyfills/AssertClosedResourceGdTest.php | 2 + .../AssertClosedResourceXmlParserTest.php | 90 +++++++++++++++++++ 8 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 tests/Polyfills/AssertClosedResourceXmlParserTest.php diff --git a/README.md b/README.md index b0bab08..7ee688b 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,31 @@ Polyfills the following methods: These methods were introduced in PHPUnit 9.3.0. +Additionally, this trait contains a helper method `shouldClosedResourceAssertionBeSkipped()`. + +Due to some bugs in PHP itself, the "is closed resource" determination cannot always be done reliably, most notably for the `libxml` extension. + +This helper function can determine whether or not the current "value under test" in combination with the PHP version on which the test is being run is affected by these bugs. +:warning: The PHPUnit native implementation of these assertions is also affected by these bugs! +The `shouldClosedResourceAssertionBeSkipped()` helper method is therefore available cross-version. + +Usage examples: +```php +// Example: skipping the test completely. +if ( $this->shouldClosedResourceAssertionBeSkipped( $actual ) === true ) { + $this->markTestSkipped('assertIs[Not]ClosedResource() cannot determine whether this resource is' + . ' open or closed due to bugs in PHP (PHP ' . \PHP_VERSION . ').'); +} + +// Example: selectively skipping the assertion. +if ( self::shouldClosedResourceAssertionBeSkipped( $actual ) === false ) { + $this->assertIsClosedResource( $actual ); +} +``` + +> :point_right: While this polyfill is tested extensively, testing for these kind of bugs exhaustively is _hard_. +> Please [report any bugs](https://github.com/Yoast/PHPUnit-Polyfills/issues/new/choose) found and include a clear code sample to reproduce the issue. + ### Helper traits diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ce26cd9..db22336 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -17,6 +17,9 @@ ./src/ + + src/Polyfills/AssertClosedResource_Empty.php + diff --git a/src/Helpers/ResourceHelper.php b/src/Helpers/ResourceHelper.php index c8bee8f..7bdf591 100644 --- a/src/Helpers/ResourceHelper.php +++ b/src/Helpers/ResourceHelper.php @@ -76,4 +76,70 @@ public static function isClosedResource( $actual ) { return false; } + + /** + * Helper function to determine whether the open/closed state of a resource is reliable. + * + * Due to some bugs in PHP itself, the "is closed resource" determination + * cannot always be done reliably. + * + * This function can determine whether or not the current value in combination with + * the current PHP version on which the test is being run is affected by this. + * + * @param mixed $actual The variable to test. + * + * @return bool + */ + public static function isResourceStateReliable( $actual ) { + try { + $type = @\get_resource_type( $actual ); + + if ( $type === 'xml' && self::isIncompatiblePHPForLibXMLResources() === true ) { + return false; + } + } catch ( Exception $e ) { + // Ignore. Not a resource. + } catch ( TypeError $e ) { + // Ignore. Not a resource. + } + + return true; + } + + /** + * Check if the PHP version is one of a known set of PHP versions + * containing a libxml version which does not report on closed resources + * correctly. + * + * Version ranges based on {@link https://3v4l.org/tc4fE}. + * 7.0.8 - 7.0.33, 7.1.0 - 7.1.33, 7.2.0 - 7.2.34, 7.3.0 - 7.3.21, 7.4.0 - 7.4.9 + * + * {@internal IMPORTANT: Any changes made to this function should also be made + * to the function in the "empty" version of this trait.} + * + * @return bool + */ + public static function isIncompatiblePHPForLibXMLResources() { + if ( \PHP_VERSION_ID >= 70008 && \PHP_VERSION_ID < 70034 ) { + return true; + } + + if ( \PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70134 ) { + return true; + } + + if ( \PHP_VERSION_ID >= 70200 && \PHP_VERSION_ID < 70235 ) { + return true; + } + + if ( \PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70322 ) { + return true; + } + + if ( \PHP_VERSION_ID >= 70400 && \PHP_VERSION_ID < 70410 ) { + return true; + } + + return false; + } } diff --git a/src/Polyfills/AssertClosedResource.php b/src/Polyfills/AssertClosedResource.php index ff13380..371226e 100644 --- a/src/Polyfills/AssertClosedResource.php +++ b/src/Polyfills/AssertClosedResource.php @@ -45,4 +45,24 @@ public static function assertIsNotClosedResource( $actual, $message = '' ) { static::assertFalse( ResourceHelper::isClosedResource( $actual ), $message ); } + + /** + * Helper function to determine whether an assertion regarding a resource's state should be skipped. + * + * Due to some bugs in PHP itself, the "is closed resource" determination + * cannot always be done reliably. + * + * This method can determine whether or not the current value in combination with + * the current PHP version on which the test is being run is affected by this. + * + * Use this function to skip running a test using `assertIs[Not]ClosedResource()` or + * to skip running just that particular assertion. + * + * @param mixed $actual The variable to test. + * + * @return bool + */ + public static function shouldClosedResourceAssertionBeSkipped( $actual ) { + return ( ResourceHelper::isResourceStateReliable( $actual ) === false ); + } } diff --git a/src/Polyfills/AssertClosedResource_Empty.php b/src/Polyfills/AssertClosedResource_Empty.php index 0f091bc..8cb4c55 100644 --- a/src/Polyfills/AssertClosedResource_Empty.php +++ b/src/Polyfills/AssertClosedResource_Empty.php @@ -2,7 +2,34 @@ namespace Yoast\PHPUnitPolyfills\Polyfills; +use Yoast\PHPUnitPolyfills\Helpers\ResourceHelper; + /** * Empty trait for use with PHPUnit >= 9.3.0 in which the polyfill is not needed. + * + * For consistency, the "should this test be skipped" method is included + * as the PHPUnit native versions of the `assertIs[Not]ClosedResource()` + * assertions are affected by the same bugs. */ -trait AssertClosedResource {} +trait AssertClosedResource { + + /** + * Helper function to determine whether an assertion regarding a resource's state should be skipped. + * + * Due to some bugs in PHP itself, the "is closed resource" determination + * cannot always be done reliably. + * + * This method can determine whether or not the current value in combination with + * the current PHP version on which the test is being run is affected by this. + * + * Use this function to skip running a test using `assertIs[Not]ClosedResource()` or + * to skip running just that particular assertion. + * + * @param mixed $actual The variable to test. + * + * @return bool + */ + public static function shouldClosedResourceAssertionBeSkipped( $actual ) { + return ( ResourceHelper::isResourceStateReliable( $actual ) === false ); + } +} diff --git a/tests/Polyfills/AssertClosedResourceDirTest.php b/tests/Polyfills/AssertClosedResourceDirTest.php index 372a026..8389175 100644 --- a/tests/Polyfills/AssertClosedResourceDirTest.php +++ b/tests/Polyfills/AssertClosedResourceDirTest.php @@ -26,6 +26,7 @@ public function testAssertIsClosedResourceWithClosedResource() { $resource = \opendir( __DIR__ . '/Fixtures/' ); \closedir( $resource ); + $this->assertFalse( static::shouldClosedResourceAssertionBeSkipped( $resource ) ); $this->assertIsClosedResource( $resource ); } @@ -51,6 +52,7 @@ public function testAssertIsClosedResourceWithOpenResource() { public function testAssertIsNotClosedResourceWithOpenResource() { $resource = \opendir( __DIR__ . '/Fixtures/' ); + $this->assertFalse( static::shouldClosedResourceAssertionBeSkipped( $resource ) ); self::assertIsNotClosedResource( $resource ); \closedir( $resource ); diff --git a/tests/Polyfills/AssertClosedResourceGdTest.php b/tests/Polyfills/AssertClosedResourceGdTest.php index a0a21a0..f68aa61 100644 --- a/tests/Polyfills/AssertClosedResourceGdTest.php +++ b/tests/Polyfills/AssertClosedResourceGdTest.php @@ -44,6 +44,7 @@ public function testAssertIsClosedResourceWithClosedResource() { public function testAssertIsClosedResourceWithOpenResource() { $resource = \imagecreate( 1, 1 ); + $this->assertFalse( static::shouldClosedResourceAssertionBeSkipped( $resource ) ); $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); \imagedestroy( $resource ); @@ -72,6 +73,7 @@ public function testAssertIsNotClosedResourceWithClosedResource() { $resource = \imagecreate( 1, 1 ); \imagedestroy( $resource ); + $this->assertFalse( static::shouldClosedResourceAssertionBeSkipped( $resource ) ); $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); } } diff --git a/tests/Polyfills/AssertClosedResourceXmlParserTest.php b/tests/Polyfills/AssertClosedResourceXmlParserTest.php new file mode 100644 index 0000000..9eb97c1 --- /dev/null +++ b/tests/Polyfills/AssertClosedResourceXmlParserTest.php @@ -0,0 +1,90 @@ += 8.0. + * + * @covers \Yoast\PHPUnitPolyfills\Helpers\ResourceHelper + * @covers \Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource + * + * @requires extension libxml + * @requires PHP < 8.0 + */ +class AssertClosedResourceXmlParserTest extends AssertClosedResourceTestCase { + + use AssertClosedResource; + + /** + * Verify availability of the assertIsClosedResource() method. + * + * @return void + */ + public function testAssertIsClosedResourceWithClosedResource() { + $resource = \xml_parser_create(); + \xml_parser_free( $resource ); + + if ( ResourceHelper::isIncompatiblePHPForLibXMLResources() === false ) { + $this->assertIsClosedResource( $resource ); + return; + } + + // Incompatible PHP version. Verify that assertion skipping is advised. + $this->assertTrue( static::shouldClosedResourceAssertionBeSkipped( $resource ) ); + } + + /** + * Verify that the assertIsClosedResource() method fails the test when the variable + * passed is not a *closed* resource. + * + * @return void + */ + public function testAssertIsClosedResourceWithOpenResource() { + $resource = \xml_parser_create(); + + $this->isClosedResourceExpectExceptionOnOpenResource( $resource ); + + \xml_parser_free( $resource ); + } + + /** + * Verify availability of the assertIsNotClosedResource() method. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithOpenResource() { + $resource = \xml_parser_create(); + + self::assertIsNotClosedResource( $resource ); + + \xml_parser_free( $resource ); + } + + /** + * Verify that the assertIsNotClosedResource() method fails the test when the variable + * passed is a *closed* resource. + * + * @return void + */ + public function testAssertIsNotClosedResourceWithClosedResource() { + $resource = \xml_parser_create(); + \xml_parser_free( $resource ); + + if ( ResourceHelper::isIncompatiblePHPForLibXMLResources() === false ) { + $this->isNotClosedResourceExpectExceptionOnClosedResource( $resource ); + return; + } + + // Incompatible PHP version. Verify that assertion skipping is advised. + $this->assertTrue( static::shouldClosedResourceAssertionBeSkipped( $resource ) ); + } +} From 3c8dc4d5b991989b72af076c8fcc88d15477f886 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 11 Jun 2021 03:46:49 +0200 Subject: [PATCH 22/39] Documentation: minor fixes --- README.md | 8 ++++---- src/Helpers/AssertAttributeHelper.php | 4 ++-- src/Polyfills/AssertionRenames_Empty.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7ee688b..033c672 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,7 @@ if ( self::shouldClosedResourceAssertionBeSkipped( $actual ) === false ) { Helper to work around the removal of the `assertAttribute*()` methods. -The `assertAttribute*()` methods were deprecated in PHPUnit 8.x and removed in PHPUnit 9.0. +The `assertAttribute*()` methods were deprecated in PHPUnit 8.0.0 and removed in PHPUnit 9.0.0. Public properties can still be tested by accessing them directly: ```php @@ -383,7 +383,7 @@ class MyTest extends XTestCase { The method signatures in the PHPUnit `TestListener` interface have changed a number of times across versions. Additionally, the use of the TestListener principle has been deprecated in PHPUnit 7 in favour of using the [TestRunner hook interfaces](https://phpunit.readthedocs.io/en/9.3/extending-phpunit.html#extending-the-testrunner). -Note: while deprecated in PHPUnit 7, the TestListener interface has not yet been removed and is still supported in PHPUnit 9. +Note: while deprecated in PHPUnit 7, the TestListener interface has not yet been removed and is still supported in PHPUnit 9.x. If your test suite does not need to support PHPUnit < 7, it is strongly recommended to use the TestRunner hook interfaces extensions instead. @@ -541,8 +541,8 @@ For frequently used, removed PHPUnit functionality, "helpers" may be provided. T | PHPUnit | Removed | Issue | Remarks | |---------|-----------------------|-----------|------------------------| -| 9.0 | `assertArraySubset()` | [#1](https://github.com/Yoast/PHPUnit-Polyfills/issues/1) | The [`dms/phpunit-arraysubset-asserts`](https://packagist.org/packages/dms/phpunit-arraysubset-asserts) package polyfills this functionality.
As of [version 0.3.0](https://github.com/rdohms/phpunit-arraysubset-asserts/releases/tag/v0.3.0) this package can be installed in combination with PHP 5.4 - current and PHPUnit 4.8.36/5.7.21 - current.
Alternatively, tests can be refactored using the patterns outlined in [issue #1](https://github.com/Yoast/PHPUnit-Polyfills/issues/1). -| 9.0 | `assertAttribute*()` | [#2](https://github.com/Yoast/PHPUnit-Polyfills/issues/2) | Refactor the tests to not directly test private/protected properties.
As an interim solution, the [`Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper`](#yoastphpunitpolyfillshelpersassertattributehelper) trait is available. +| 9.0.0 | `assertArraySubset()` | [#1](https://github.com/Yoast/PHPUnit-Polyfills/issues/1) | The [`dms/phpunit-arraysubset-asserts`](https://packagist.org/packages/dms/phpunit-arraysubset-asserts) package polyfills this functionality.
As of [version 0.3.0](https://github.com/rdohms/phpunit-arraysubset-asserts/releases/tag/v0.3.0) this package can be installed in combination with PHP 5.4 - current and PHPUnit 4.8.36/5.7.21 - current.
Alternatively, tests can be refactored using the patterns outlined in [issue #1](https://github.com/Yoast/PHPUnit-Polyfills/issues/1). +| 9.0.0 | `assertAttribute*()` | [#2](https://github.com/Yoast/PHPUnit-Polyfills/issues/2) | Refactor the tests to not directly test private/protected properties.
As an interim solution, the [`Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper`](#yoastphpunitpolyfillshelpersassertattributehelper) trait is available. Contributing diff --git a/src/Helpers/AssertAttributeHelper.php b/src/Helpers/AssertAttributeHelper.php index 0645d9f..4ae704c 100644 --- a/src/Helpers/AssertAttributeHelper.php +++ b/src/Helpers/AssertAttributeHelper.php @@ -9,8 +9,8 @@ /** * Helper to work-around the removal of the `assertAttribute*()` methods. * - * The `assertAttribute*()` methods were deprecated in PHPUnit 8.x and - * removed in PHPUnit 9.0. + * The `assertAttribute*()` methods were deprecated in PHPUnit 8.0.0 and + * removed in PHPUnit 9.0.0. * * Public properties can still be tested by accessing them directly: * ```php diff --git a/src/Polyfills/AssertionRenames_Empty.php b/src/Polyfills/AssertionRenames_Empty.php index c6b21af..4de7858 100644 --- a/src/Polyfills/AssertionRenames_Empty.php +++ b/src/Polyfills/AssertionRenames_Empty.php @@ -3,6 +3,6 @@ namespace Yoast\PHPUnitPolyfills\Polyfills; /** - * Empty trait for use with PHPUnit < 9.1.0 in which this polyfill is not needed. + * Empty trait for use with PHPUnit >= 9.1.0 in which this polyfill is not needed. */ trait AssertionRenames {} From 2f082fcdabc8a0e74b69c2409847b2fee1e674a5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 10 Jun 2021 22:00:07 +0200 Subject: [PATCH 23/39] README: re-order the sections * Move the "Using this library" section up. * Use the introduction text previously used for "Features" in a new section "Why use the PHPUnit Polyfills ?" * Move the "Supported ways of calling the assertions" section to the "Using this library" section. --- README.md | 189 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 97 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 033c672..9b97be5 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,13 @@ Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit * [Requirements](#requirements) * [Installation](#installation) +* [Why use the PHPUnit Polyfills?](#why-use-the-phpunit-polyfills) +* [Using this library](#using-this-library) * [Features](#features) - [Polyfill traits](#polyfill-traits) - [Helper traits](#helper-traits) - [TestCases](#testcases) - [TestListener](#testlistener) -* [Using this library](#using-this-library) * [Frequently Asked Questions](#frequently-asked-questions) * [Contributing](#contributing) * [License](#license) @@ -48,7 +49,7 @@ In that case, make sure that the `phpunitpolyfills-autoload.php` file is explici (Not necessary when the Composer `vendor/autoload.php` file is used as, or `require`d in, the test bootstrap.) -Features +Why use the PHPUnit Polyfills? ------------------------------------------- This library is set up to allow for creating PHPUnit cross-version compatible tests. @@ -62,6 +63,57 @@ The polyfills have been setup to allow tests to be _forward_-compatible. What th This puts the burden of upgrading to use the syntax of newer PHPUnit versions at the point when you want to start running your tests on a newer version. By doing so, dropping support for an older PHPUnit version becomes as simple as removing it from the version constraint in your `composer.json` file. + +Using this library +------- + +Each of the polyfills and helpers has been setup as a trait and can be imported and `use`d in any test file which extends the PHPUnit native `TestCase` class. + +If the polyfill is not needed for the particular PHPUnit version on which the tests are being run, the autoloader +will automatically load an empty trait with that same name, so you can safely use these traits in tests which +need to be PHPUnit cross-version compatible. + +```php +assertIsBool( $maybeBool ); + self::assertIsNotIterable( $maybeIterable ); + } +} +``` + +Alternatively, you can use one of the [`TestCase` classes](#testcases) provided by this library instead of using the PHPUnit native `TestCase` class. + +In that case, all polyfills and helpers will be available whenever needed. + +```php +assertIsBool( $maybeBool ); + self::assertMatchesRegularExpression( $pattern, $string, $message ); + } +} +``` + ### Supported ways of calling the assertions By default, PHPUnit supports four ways of calling assertions: @@ -74,6 +126,49 @@ The polyfills in this library support the first two ways of calling the assertio For the polyfills to work, a test class is **required** to be a (grand-)child of the PHPUnit native `TestCase` class. +### Use with PHPUnit < 5.7.0 + +If your library still needs to support PHP < 5.6 and therefore needs PHPUnit 4 for testing, there are a few caveats when using the traits stand-alone as we then enter "double-polyfill" territory. + +To prevent "conflicting method names" errors when a trait is `use`d multiple times in a class, the traits offered here do not attempt to solve this. + +You will need to make sure to `use` any additional traits needed for the polyfills to work. + +| PHPUnit | When `use`-ing this trait | You also need to `use` this trait | +|-----------|---------------------------------|-----------------------------------| +| 4.8 < 5.2 | `ExpectExceptionObject` | `ExpectException` | +| 4.8 < 5.2 | `ExpectPHPException` | `ExpectException` | +| 4.8 < 5.2 | `ExpectExceptionMessageMatches` | `ExpectException` | +| 4.8 < 5.6 | `AssertionRenames` | `AssertFileDirectory` | + +_**Note: this only applies to the stand-alone use of the traits. The [`TestCase` classes](#testcases) provided by this library already take care of this automatically.**_ + +Code example testing for a PHP native warning in a test which needs to be able to run on PHPUnit 4.8: +```php +expectWarningMessage( 'A non-numeric value encountered' ); + } +} +``` + + +Features +------------------------------------------- + ### Polyfill traits #### PHPUnit < 5.0.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertNumericType` @@ -438,96 +533,6 @@ class MyTestListener implements TestListener { ``` -Using this library -------- - -Each of the polyfills and helpers has been setup as a trait and can be imported and `use`d in any test file which extends the PHPUnit native `TestCase` class. - -If the polyfill is not needed for the particular PHPUnit version on which the tests are being run, the autoloader -will automatically load an empty trait with that same name, so you can safely use these traits in tests which -need to be PHPUnit cross-version compatible. - -```php -assertIsBool( $maybeBool ); - self::assertIsNotIterable( $maybeIterable ); - } -} -``` - -Alternatively, you can use one of the [`TestCase` classes](#testcases) provided by this library instead of using the PHPUnit native `TestCase` class. - -In that case, all polyfills and helpers will be available whenever needed. - -```php -assertIsBool( $maybeBool ); - self::assertMatchesRegularExpression( $pattern, $string, $message ); - } -} -``` - -### Use with PHPUnit < 5.7.0 - -If your library still needs to support PHP < 5.6 and therefore needs PHPUnit 4 for testing, there are a few caveats when using the traits stand-alone as we then enter "double-polyfill" territory. - -To prevent "conflicting method names" errors when a trait is `use`d multiple times in a class, the traits offered here do not attempt to solve this. - -You will need to make sure to `use` any additional traits needed for the polyfills to work. - -| PHPUnit | When `use`-ing this trait | You also need to `use` this trait | -|-----------|---------------------------------|-----------------------------------| -| 4.8 < 5.2 | `ExpectExceptionObject` | `ExpectException` | -| 4.8 < 5.2 | `ExpectPHPException` | `ExpectException` | -| 4.8 < 5.2 | `ExpectExceptionMessageMatches` | `ExpectException` | -| 4.8 < 5.6 | `AssertionRenames` | `AssertFileDirectory` | - -_**Note: this only applies to the stand-alone use of the traits. The [`TestCase` classes](#testcases) provided by this library already take care of this automatically.**_ - -Code example testing for a PHP native warning in a test which needs to be able to run on PHPUnit 4.8: -```php -expectWarningMessage( 'A non-numeric value encountered' ); - } -} -``` - - Frequently Asked Questions ------- From 767b12fb2cb4523ae1e989ef6e7433a9a54bca10 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 10 Jun 2021 22:25:22 +0200 Subject: [PATCH 24/39] README: move the phar info to the FAQ This information will only be relevant to a small group of users and is not directly related to the installation section in which it was previously placed. --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9b97be5..9bec8c4 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,6 @@ To update this package, run: composer update --dev yoast/phpunit-polyfills --with-dependencies ``` -This package can also be used when running tests via a PHPUnit Phar file. -In that case, make sure that the `phpunitpolyfills-autoload.php` file is explicitly `require`d in the test bootstrap file. -(Not necessary when the Composer `vendor/autoload.php` file is used as, or `require`d in, the test bootstrap.) - Why use the PHPUnit Polyfills? ------------------------------------------- @@ -550,6 +546,14 @@ For frequently used, removed PHPUnit functionality, "helpers" may be provided. T | 9.0.0 | `assertAttribute*()` | [#2](https://github.com/Yoast/PHPUnit-Polyfills/issues/2) | Refactor the tests to not directly test private/protected properties.
As an interim solution, the [`Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper`](#yoastphpunitpolyfillshelpersassertattributehelper) trait is available. +### Q: Can this library be used when the tests are being run via a PHPUnit Phar file ? + +Yes, this package can also be used when running tests via a PHPUnit Phar file. + +In that case, make sure that the `phpunitpolyfills-autoload.php` file is explicitly `require`d in the test bootstrap file. +(Not necessary when the Composer `vendor/autoload.php` file is used as, or `require`d in, the test bootstrap.) + + Contributing ------- Contributions to this project are welcome. Clone the repo, branch off from `develop`, make your changes, commit them and send in a pull request. From 035594f4790212aa5811cdc3f64539bdf18e471b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 10 Jun 2021 22:02:24 +0200 Subject: [PATCH 25/39] README: minor language improvements --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9bec8c4..b81b9be 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,14 @@ composer update --dev yoast/phpunit-polyfills --with-dependencies Why use the PHPUnit Polyfills? ------------------------------------------- -This library is set up to allow for creating PHPUnit cross-version compatible tests. - -This library offers a number of polyfills for functionality which was introduced, split up or renamed in PHPUnit. +This library is set up to allow for creating PHPUnit cross-version compatible tests by offering a number of polyfills for functionality which was introduced, split up or renamed in PHPUnit. ### Write your tests for PHPUnit 9.x and run them on PHPUnit 4.8 - 9.x The polyfills have been setup to allow tests to be _forward_-compatible. What that means is, that your tests can use the assertions supported by the _latest_ PHPUnit version, even when running on older PHPUnit versions. -This puts the burden of upgrading to use the syntax of newer PHPUnit versions at the point when you want to start running your tests on a newer version. -By doing so, dropping support for an older PHPUnit version becomes as simple as removing it from the version constraint in your `composer.json` file. +This puts the burden of upgrading to use the syntax of newer PHPUnit versions at the point when you want to _start_ running your tests on a newer version. +By doing so, dropping support for an older PHPUnit version becomes as straight-forward as removing it from the version constraint in your `composer.json` file. Using this library @@ -113,8 +111,8 @@ class FooTest extends TestCase ### Supported ways of calling the assertions By default, PHPUnit supports four ways of calling assertions: -1. As a method in the `TestCase` class - `$this->assertSomething()`. -2. Statically as a method in the `TestCase` class - `self/static/parent::assertSomething()`. +1. **As a method in the `TestCase` class - `$this->assertSomething()`.** +2. **Statically as a method in the `TestCase` class - `self/static/parent::assertSomething()`.** 3. Statically as a method of the `Assert` class - `Assert::assertSomething()`. 4. As a global function - `assertSomething()`. From 0dc9199707eb290f2d9145854be6fb16e695ea3c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 10 Jun 2021 22:03:31 +0200 Subject: [PATCH 26/39] README: minor formatting consistency fixes --- README.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b81b9be..118ef6b 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,14 @@ Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit Requirements -------------------------------------------- +------------ * PHP 5.4 or higher. * PHPUnit 4.8 - 9.x (automatically required via Composer). Installation -------------------------------------------- +------------ To install this package, run: ```bash @@ -46,7 +46,7 @@ composer update --dev yoast/phpunit-polyfills --with-dependencies Why use the PHPUnit Polyfills? -------------------------------------------- +------------------------------ This library is set up to allow for creating PHPUnit cross-version compatible tests by offering a number of polyfills for functionality which was introduced, split up or renamed in PHPUnit. @@ -59,7 +59,7 @@ By doing so, dropping support for an older PHPUnit version becomes as straight-f Using this library -------- +------------------ Each of the polyfills and helpers has been setup as a trait and can be imported and `use`d in any test file which extends the PHPUnit native `TestCase` class. @@ -161,7 +161,7 @@ class FooTest extends TestCase Features -------------------------------------------- +-------- ### Polyfill traits @@ -275,7 +275,7 @@ Polyfills the following methods: These methods were introduced in PHPUnit 8.5.0 as alternatives to using `Assert::assertFileEquals()` and `Assert::assertFileNotEquals()` with these optional parameters. Passing the respective optional parameters to these methods was hard deprecated in PHPUnit 8.5.0 and removed in PHPUnit 9.0.0. -### PHPUnit < 9.1.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames` +#### PHPUnit < 9.1.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames` Polyfills the following renamed methods: * `Assert::assertIsNotReadable()`, introduced as alternative for `Assert::assertNotIsReadable()`. @@ -306,7 +306,8 @@ Additionally, this trait contains a helper method `shouldClosedResourceAssertion Due to some bugs in PHP itself, the "is closed resource" determination cannot always be done reliably, most notably for the `libxml` extension. This helper function can determine whether or not the current "value under test" in combination with the PHP version on which the test is being run is affected by these bugs. -:warning: The PHPUnit native implementation of these assertions is also affected by these bugs! + +> :warning: The PHPUnit native implementation of these assertions is also affected by these bugs! The `shouldClosedResourceAssertionBeSkipped()` helper method is therefore available cross-version. Usage examples: @@ -472,7 +473,8 @@ class MyTest extends XTestCase { The method signatures in the PHPUnit `TestListener` interface have changed a number of times across versions. Additionally, the use of the TestListener principle has been deprecated in PHPUnit 7 in favour of using the [TestRunner hook interfaces](https://phpunit.readthedocs.io/en/9.3/extending-phpunit.html#extending-the-testrunner). -Note: while deprecated in PHPUnit 7, the TestListener interface has not yet been removed and is still supported in PHPUnit 9.x. + +> Note: while deprecated in PHPUnit 7, the TestListener interface has not yet been removed and is still supported in PHPUnit 9.x. If your test suite does not need to support PHPUnit < 7, it is strongly recommended to use the TestRunner hook interfaces extensions instead. @@ -528,11 +530,11 @@ class MyTestListener implements TestListener { Frequently Asked Questions -------- +-------------------------- ### Q: Will this package polyfill functionality which was removed from PHPUnit ? -As a rule of thumb, removed functionality will not be polyfilled in this package. +As a rule of thumb, removed functionality will **not** be polyfilled in this package. For frequently used, removed PHPUnit functionality, "helpers" may be provided. These _helpers_ are only intended as an interim solution to allow users of this package more time to refactor their tests away from the removed functionality. @@ -553,7 +555,7 @@ In that case, make sure that the `phpunitpolyfills-autoload.php` file is explici Contributing -------- +------------ Contributions to this project are welcome. Clone the repo, branch off from `develop`, make your changes, commit them and send in a pull request. If you are unsure whether the changes you are proposing would be welcome, please open an issue first to discuss your proposal. From c2ff106e0a5686e7b3db464b1116d17cd7c65130 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 11 Jun 2021 05:03:15 +0200 Subject: [PATCH 27/39] README: add links to the PHPUnit documentation Add version agnostic links to the PHPUnit native documentation for the assertions and exception methods polyfilled as well as miscellanous other links to the documentation. --- README.md | 220 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 163 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 118ef6b..5a8c40e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,9 @@ Requirements ------------ * PHP 5.4 or higher. -* PHPUnit 4.8 - 9.x (automatically required via Composer). +* [PHPUnit] 4.8 - 9.x (automatically required via Composer). + +[PHPUnit]: https://packagist.org/packages/phpunit/phpunit Installation @@ -110,7 +112,7 @@ class FooTest extends TestCase ### Supported ways of calling the assertions -By default, PHPUnit supports four ways of calling assertions: +By default, PHPUnit supports [four ways of calling assertions]: 1. **As a method in the `TestCase` class - `$this->assertSomething()`.** 2. **Statically as a method in the `TestCase` class - `self/static/parent::assertSomething()`.** 3. Statically as a method of the `Assert` class - `Assert::assertSomething()`. @@ -120,6 +122,8 @@ The polyfills in this library support the first two ways of calling the assertio For the polyfills to work, a test class is **required** to be a (grand-)child of the PHPUnit native `TestCase` class. +[four ways of calling assertions]: https://phpunit.readthedocs.io/en/stable/assertions.html#static-vs-non-static-usage-of-assertion-methods + ### Use with PHPUnit < 5.7.0 If your library still needs to support PHP < 5.6 and therefore needs PHPUnit 4 for testing, there are a few caveats when using the traits stand-alone as we then enter "double-polyfill" territory. @@ -168,101 +172,177 @@ Features #### PHPUnit < 5.0.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertNumericType` Polyfills the following methods: -| | | | -|--------------------------|----------------------------|-----------------------| -| `Assert::assertFinite()` | `Assert::assertInfinite()` | `Assert::assertNan()` | +| | | | +|----------------------------|------------------------------|-------------------------| +| [`Assert::assertFinite()`] | [`Assert::assertInfinite()`] | [`Assert::assertNan()`] | These methods were introduced in PHPUnit 5.0.0. +[`Assert::assertFinite()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertinfinite +[`Assert::assertInfinite()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertinfinite +[`Assert::assertNan()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertnan + #### PHPUnit < 5.2.0: `Yoast\PHPUnitPolyfills\Polyfills\ExpectException` Polyfills the following methods: -| | | -|-----------------------------------|--------------------------------------------| -| `TestCase::expectException()` | `TestCase::expectExceptionMessage()` | -| `TestCase::expectExceptionCode()` | `TestCase::expectExceptionMessageRegExp()` | +| | | +|-------------------------------------|----------------------------------------------| +| [`TestCase::expectException()`] | [`TestCase::expectExceptionMessage()`] | +| [`TestCase::expectExceptionCode()`] | [`TestCase::expectExceptionMessageRegExp()`] | These methods were introduced in PHPUnit 5.2.0 as alternatives to the `Testcase::setExpectedException()` method which was deprecated in PHPUnit 5.2.0 and the `Testcase::setExpectedExceptionRegExp()` method which was deprecated in 5.6.0. Both these methods were removed in PHPUnit 6.0.0. +[`TestCase::expectException()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-exceptions +[`TestCase::expectExceptionMessage()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-exceptions +[`TestCase::expectExceptionCode()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-exceptions +[`TestCase::expectExceptionMessageRegExp()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-exceptions + #### PHPUnit < 5.6.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertFileDirectory` Polyfills the following methods: -| | | -|---------------------------------------|------------------------------------------| -| `Assert::assertIsReadable()` | `Assert::assertNotIsReadable()` | -| `Assert::assertIsWritable()` | `Assert::assertNotIsWritable()` | -| `Assert::assertDirectoryExists()` | `Assert::assertDirectoryNotExists()` | -| `Assert::assertDirectoryIsReadable()` | `Assert::assertDirectoryNotIsReadable()` | -| `Assert::assertDirectoryIsWritable()` | `Assert::assertDirectoryNotIsWritable()` | -| `Assert::assertFileIsReadable()` | `Assert::assertFileNotIsReadable()` | -| `Assert::assertFileIsWritable()` | `Assert::assertFileNotIsWritable()` | +| | | +|-----------------------------------------|--------------------------------------------| +| [`Assert::assertIsReadable()`] | [`Assert::assertNotIsReadable()`] | +| [`Assert::assertIsWritable()`] | [`Assert::assertNotIsWritable()`] | +| [`Assert::assertDirectoryExists()`] | [`Assert::assertDirectoryNotExists()`] | +| [`Assert::assertDirectoryIsReadable()`] | [`Assert::assertDirectoryNotIsReadable()`] | +| [`Assert::assertDirectoryIsWritable()`] | [`Assert::assertDirectoryNotIsWritable()`] | +| [`Assert::assertFileIsReadable()`] | [`Assert::assertFileNotIsReadable()`] | +| [`Assert::assertFileIsWritable()`] | [`Assert::assertFileNotIsWritable()`] | These methods were introduced in PHPUnit 5.6.0. +[`Assert::assertIsReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisreadable +[`Assert::assertNotIsReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisreadable +[`Assert::assertIsWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertiswritable +[`Assert::assertNotIsWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertiswritable +[`Assert::assertDirectoryExists()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryexists +[`Assert::assertDirectoryNotExists()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryexists +[`Assert::assertDirectoryIsReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryisreadable +[`Assert::assertDirectoryNotIsReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryisreadable +[`Assert::assertDirectoryIsWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryiswritable +[`Assert::assertDirectoryNotIsWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryiswritable +[`Assert::assertFileIsReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertfileisreadable +[`Assert::assertFileNotIsReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertfileisreadable +[`Assert::assertFileIsWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertfileiswritable +[`Assert::assertFileNotIsWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertfileiswritable + #### PHPUnit < 6.4.0: `Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionObject` -Polyfills the `TestCase::expectExceptionObject()` method to test all aspects of an `Exception` by passing an object to the method. +Polyfills the [`TestCase::expectExceptionObject()`] method to test all aspects of an `Exception` by passing an object to the method. This method was introduced in PHPUnit 6.4.0. +[`TestCase::expectExceptionObject()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-exceptions + #### PHPUnit < 7.5.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertIsType` Polyfills the following methods: -| | | | -|---------------------------------|---------------------------------|-------------------------------| -| `Assert::assertIsArray()` | `Assert::assertIsBool()` | `Assert::assertIsFloat()` | -| `Assert::assertIsInt()` | `Assert::assertIsNumeric()` | `Assert::assertIsObject()` | -| `Assert::assertIsResource()` | `Assert::assertIsString()` | `Assert::assertIsScalar()` | -| `Assert::assertIsCallable()` | `Assert::assertIsIterable()` | | -| `Assert::assertIsNotArray()` | `Assert::assertIsNotBool()` | `Assert::assertIsNotFloat()` | -| `Assert::assertIsNotInt()` | `Assert::assertIsNotNumeric()` | `Assert::assertIsNotObject()` | -| `Assert::assertIsNotResource()` | `Assert::assertIsNotString()` | `Assert::assertIsNotScalar()` | -| `Assert::assertIsNotCallable()` | `Assert::assertIsNotIterable()` | | +| | | | +|-----------------------------------|-----------------------------------|---------------------------------| +| [`Assert::assertIsArray()`] | [`Assert::assertIsBool()`] | [`Assert::assertIsFloat()`] | +| [`Assert::assertIsInt()`] | [`Assert::assertIsNumeric()`] | [`Assert::assertIsObject()`] | +| [`Assert::assertIsResource()`] | [`Assert::assertIsString()`] | [`Assert::assertIsScalar()`] | +| [`Assert::assertIsCallable()`] | [`Assert::assertIsIterable()`] | | +| [`Assert::assertIsNotArray()`] | [`Assert::assertIsNotBool()`] | [`Assert::assertIsNotFloat()`] | +| [`Assert::assertIsNotInt()`] | [`Assert::assertIsNotNumeric()`] | [`Assert::assertIsNotObject()`] | +| [`Assert::assertIsNotResource()`] | [`Assert::assertIsNotString()`] | [`Assert::assertIsNotScalar()`] | +| [`Assert::assertIsNotCallable()`] | [`Assert::assertIsNotIterable()`] | | These methods were introduced in PHPUnit 7.5.0 as alternatives to the `Assert::assertInternalType()` and `Assert::assertNotInternalType()` methods, which were soft deprecated in PHPUnit 7.5.0, hard deprecated (warning) in PHPUnit 8.0.0 and removed in PHPUnit 9.0.0. +[`Assert::assertIsArray()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisarray +[`Assert::assertIsNotArray()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisarray +[`Assert::assertIsBool()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisbool +[`Assert::assertIsNotBool()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisbool +[`Assert::assertIsFloat()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisfloat +[`Assert::assertIsNotFloat()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisfloat +[`Assert::assertIsInt()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisint +[`Assert::assertIsNotInt()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisint +[`Assert::assertIsNumeric()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisnumeric +[`Assert::assertIsNotNumeric()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisnumeric +[`Assert::assertIsObject()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisobject +[`Assert::assertIsNotObject()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisobject +[`Assert::assertIsResource()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisresource +[`Assert::assertIsNotResource()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisresource +[`Assert::assertIsString()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisstring +[`Assert::assertIsNotString()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisstring +[`Assert::assertIsScalar()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisscalar +[`Assert::assertIsNotScalar()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisscalar +[`Assert::assertIsCallable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertiscallable +[`Assert::assertIsNotCallable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertiscallable +[`Assert::assertIsIterable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisiterable +[`Assert::assertIsNotIterable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisiterable + #### PHPUnit < 7.5.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains` Polyfills the following methods: -| | | -|----------------------------------------------------|-------------------------------------------------------| -| `Assert::assertStringContainsString()` | `Assert::assertStringNotContainsString()` | -| `Assert::assertStringContainsStringIgnoringCase()` | `Assert::assertStringNotContainsStringIgnoringCase()` | +| | | +|------------------------------------------------------|---------------------------------------------------------| +| [`Assert::assertStringContainsString()`] | [`Assert::assertStringNotContainsString()`] | +| [`Assert::assertStringContainsStringIgnoringCase()`] | [`Assert::assertStringNotContainsStringIgnoringCase()`] | These methods were introduced in PHPUnit 7.5.0 as alternatives to using `Assert::assertContains()` and `Assert::assertNotContains()` with string haystacks. Passing string haystacks to these methods was soft deprecated in PHPUnit 7.5.0, hard deprecated (warning) in PHPUnit 8.0.0 and removed in PHPUnit 9.0.0. +[`Assert::assertStringContainsString()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertstringcontainsstring +[`Assert::assertStringNotContainsString()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertstringcontainsstring +[`Assert::assertStringContainsStringIgnoringCase()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertstringcontainsstringignoringcase +[`Assert::assertStringNotContainsStringIgnoringCase()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertstringcontainsstringignoringcase + #### PHPUnit < 7.5.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsSpecializations` Polyfills the following methods: -| | | -|----------------------------------------|-------------------------------------------| -| `Assert::assertEqualsCanonicalizing()` | `Assert::assertNotEqualsCanonicalizing()` | -| `Assert::assertEqualsIgnoringCase()` | `Assert::assertNotEqualsIgnoringCase()` | -| `Assert::assertEqualsWithDelta()` | `Assert::assertNotEqualsWithDelta()` | +| | | +|------------------------------------------|---------------------------------------------| +| [`Assert::assertEqualsCanonicalizing()`] | [`Assert::assertNotEqualsCanonicalizing()`] | +| [`Assert::assertEqualsIgnoringCase()`] | [`Assert::assertNotEqualsIgnoringCase()`] | +| [`Assert::assertEqualsWithDelta()`] | [`Assert::assertNotEqualsWithDelta()`] | These methods were introduced in PHPUnit 7.5.0 as alternatives to using `Assert::assertEquals()` and `Assert::assertNotEquals()` with these optional parameters. Passing the respective optional parameters to these methods was soft deprecated in PHPUnit 7.5.0, hard deprecated (warning) in PHPUnit 8.0.0 and removed in PHPUnit 9.0.0. +[`Assert::assertEqualsCanonicalizing()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertequalscanonicalizing +[`Assert::assertNotEqualsCanonicalizing()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertequalscanonicalizing +[`Assert::assertEqualsIgnoringCase()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertequalsignoringcase +[`Assert::assertNotEqualsIgnoringCase()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertequalsignoringcase +[`Assert::assertEqualsWithDelta()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertequalswithdelta +[`Assert::assertNotEqualsWithDelta()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertequalswithdelta + #### PHPUnit < 8.4.0: `Yoast\PHPUnitPolyfills\Polyfills\ExpectPHPException` Polyfills the following methods: -| | | | -|-----------------------|------------------------------|-------------------------------------| -| `expectError()` | `expectErrorMessage()` | `expectErrorMessageMatches()` | -| `expectWarning()` | `expectWarningMessage()` | `expectWarningMessageMatches()` | -| `expectNotice()` | `expectNoticeMessage()` | `expectNoticeMessageMatches()` | -| `expectDeprecation()` | `expectDeprecationMessage()` | `expectDeprecationMessageMatches()` | +| | | | +|-------------------------------------|--------------------------------|---------------------------------------| +| `TestCase::`[`expectError()`] | [`expectErrorMessage()`] | [`expectErrorMessageMatches()`] | +| `TestCase::`[`expectWarning()`] | [`expectWarningMessage()`] | [`expectWarningMessageMatches()`] | +| `TestCase::`[`expectNotice()`] | [`expectNoticeMessage()`] | [`expectNoticeMessageMatches()`] | +| `TestCase::`[`expectDeprecation()`] | [`expectDeprecationMessage()`] | [`expectDeprecationMessageMatches()`] | These method were introduced in PHPUnit 8.4.0 as alternatives to using `TestCase::expectException()` et al for expecting PHP native errors, warnings and notices. Using `TestCase::expectException*()` for testing PHP native notices was soft deprecated in PHPUnit 8.4.0, hard deprecated (warning) in PHPUnit 9.0.0 and (will be) removed in PHPUnit 10.0.0. +[`expectError()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectErrorMessage()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectErrorMessageMatches()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectWarning()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectWarningMessage()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectWarningMessageMatches()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectNotice()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectNoticeMessage()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectNoticeMessageMatches()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectDeprecation()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectDeprecationMessage()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices +[`expectDeprecationMessageMatches()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-php-errors-warnings-and-notices + #### PHPUnit < 8.4.0: `Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches` -Polyfills the `TestCase::expectExceptionMessageMatches()` method. +Polyfills the [`TestCase::expectExceptionMessageMatches()`] method. This method was introduced in PHPUnit 8.4.0 to improve the name of the `TestCase::expectExceptionMessageRegExp()` method. The `TestCase::expectExceptionMessageRegExp()` method was soft deprecated in PHPUnit 8.4.0, hard deprecated (warning) in PHPUnit 8.5.3 and removed in PHPUnit 9.0.0. +[`TestCase::expectExceptionMessageMatches()`]: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#testing-exceptions + #### PHPUnit < 8.5.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations` Polyfills the following methods: @@ -275,23 +355,38 @@ Polyfills the following methods: These methods were introduced in PHPUnit 8.5.0 as alternatives to using `Assert::assertFileEquals()` and `Assert::assertFileNotEquals()` with these optional parameters. Passing the respective optional parameters to these methods was hard deprecated in PHPUnit 8.5.0 and removed in PHPUnit 9.0.0. + + #### PHPUnit < 9.1.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames` Polyfills the following renamed methods: -* `Assert::assertIsNotReadable()`, introduced as alternative for `Assert::assertNotIsReadable()`. -* `Assert::assertIsNotWritable()`, introduced as alternative for `Assert::assertNotIsWritable()`. -* `Assert::assertDirectoryDoesNotExist()`, introduced as alternative for `Assert::assertDirectoryNotExists()`. -* `Assert::assertDirectoryIsNotReadable()`, introduced as alternative for `Assert::assertDirectoryNotIsReadable()`. -* `Assert::assertDirectoryIsNotWritable()`, introduced as alternative for `Assert::assertDirectoryNotIsWritable()`. -* `Assert::assertFileDoesNotExist()`, introduced as alternative for `Assert::assertFileNotExists()`. -* `Assert::assertFileIsNotReadable()`, introduced as alternative for `Assert::assertFileNotIsReadable()`. -* `Assert::assertFileIsNotWritable()`, introduced as alternative for `Assert::assertFileNotIsWritable()`. -* `Assert::assertMatchesRegularExpression()`, introduced as alternative for `Assert::assertRegExp()`. -* `Assert::assertDoesNotMatchRegularExpression()`, introduced as alternative for `Assert::assertNotRegExp()`. +* [`Assert::assertIsNotReadable()`], introduced as alternative for `Assert::assertNotIsReadable()`. +* [`Assert::assertIsNotWritable()`], introduced as alternative for `Assert::assertNotIsWritable()`. +* [`Assert::assertDirectoryDoesNotExist()`], introduced as alternative for `Assert::assertDirectoryNotExists()`. +* [`Assert::assertDirectoryIsNotReadable()`], introduced as alternative for `Assert::assertDirectoryNotIsReadable()`. +* [`Assert::assertDirectoryIsNotWritable()`], introduced as alternative for `Assert::assertDirectoryNotIsWritable()`. +* [`Assert::assertFileDoesNotExist()`], introduced as alternative for `Assert::assertFileNotExists()`. +* [`Assert::assertFileIsNotReadable()`], introduced as alternative for `Assert::assertFileNotIsReadable()`. +* [`Assert::assertFileIsNotWritable()`], introduced as alternative for `Assert::assertFileNotIsWritable()`. +* [`Assert::assertMatchesRegularExpression()`], introduced as alternative for `Assert::assertRegExp()`. +* [`Assert::assertDoesNotMatchRegularExpression()`], introduced as alternative for `Assert::assertNotRegExp()`. These methods were introduced in PHPUnit 9.1.0. The original methods these new methods replace were hard deprecated in PHPUnit 9.1.0 and (will be) removed in PHPUnit 10.0.0. +[`Assert::assertIsNotReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertisreadable +[`Assert::assertIsNotWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertiswritable +[`Assert::assertDirectoryDoesNotExist()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryexists +[`Assert::assertDirectoryIsNotReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryisreadable +[`Assert::assertDirectoryIsNotWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertdirectoryiswritable +[`Assert::assertFileDoesNotExist()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertfileexists +[`Assert::assertFileIsNotReadable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertfileisreadable +[`Assert::assertFileIsNotWritable()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertfileiswritable +[`Assert::assertMatchesRegularExpression()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertmatchesregularexpression +[`Assert::assertDoesNotMatchRegularExpression()`]: https://phpunit.readthedocs.io/en/stable/assertions.html#assertmatchesregularexpression + #### PHPUnit < 9.3.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource` Polyfills the following methods: @@ -301,6 +396,10 @@ Polyfills the following methods: These methods were introduced in PHPUnit 9.3.0. + + Additionally, this trait contains a helper method `shouldClosedResourceAssertionBeSkipped()`. Due to some bugs in PHP itself, the "is closed resource" determination cannot always be done reliably, most notably for the `libxml` extension. @@ -362,9 +461,11 @@ self::assertSame( $propertyName, self::getProperty( $objInstance, $propertyName ### TestCases -PHPUnit 8.0.0 introduced a `void` return type declaration to the "fixture" methods - `setUpBeforeClass()`, `setUp()`, `tearDown()` and `tearDownAfterClass()`. +PHPUnit 8.0.0 introduced a `void` return type declaration to the ["fixture" methods] - `setUpBeforeClass()`, `setUp()`, `tearDown()` and `tearDownAfterClass()`. As the `void` return type was not introduced until PHP 7.1, this makes it more difficult to create cross-version compatible tests when using fixtures, due to signature mismatches. +["fixture" methods]: https://phpunit.readthedocs.io/en/stable/fixtures.html + This library contains two basic `TestCase` options to overcome this issue. #### Option 1: `Yoast\PHPUnitPolyfills\TestCases\TestCase` @@ -424,9 +525,14 @@ class MyTest extends TestCase { This `TestCase` overcomes the signature mismatch by using the PHPUnit `@before[Class]` and `@after[Class]` annotations in combination with different methods names, i.e. `setUpFixturesBeforeClass()`, `setUpFixtures()`, `tearDownFixtures()` and `tearDownFixturesAfterClass()`. -When using this TestCase, overloaded fixture methods need to use the `@beforeClass`, `@before`, `@after` and `@afterClass` annotations. +When using this TestCase, overloaded fixture methods need to use the [`@beforeClass`], [`@before`], [`@after`] and [`@afterClass`] annotations. The naming of the overloaded methods is open as long as the method names don't conflict with the PHPUnit native method names. +[`@beforeClass`]: https://phpunit.readthedocs.io/en/stable/annotations.html#beforeclass +[`@before`]: https://phpunit.readthedocs.io/en/stable/annotations.html#before +[`@after`]: https://phpunit.readthedocs.io/en/stable/annotations.html#after +[`@afterClass`]: https://phpunit.readthedocs.io/en/stable/annotations.html#afterclass + ```php use Yoast\PHPUnitPolyfills\TestCases\XTestCase; From 6482b50223c8b94127302cb05665ba7702614e09 Mon Sep 17 00:00:00 2001 From: Marc Siegrist Date: Mon, 14 Jun 2021 23:07:18 +0200 Subject: [PATCH 28/39] Polyfill for equalTo constraint specializations (#28) PHPUnit 9.0.0 introduced the new `Assert::equalToCanonicalizing()`, `Assert::equalToIgnoringCase()` and `Assert::equalToWithDelta()` methods. These methods are typically used to verify parameters passed to mocked objects. This commit: * Adds two traits with the same name. One to polyfill the methods when not available in PHPUnit. The other to allow for `use`-ing the trait in PHPUnit versions in which the methods are already natively available. * Logic to the custom autoloader which will load the correct trait depending on the PHPUnit version used. * An availability tests for the functionality polyfilled. Note: the methods use `static::` to call the PHPUnit native functionality. This allows for existing method overloads in a child class of the PHPUnit native `TestCase` to be respected. Includes: * Adding information on the new polyfill to the README. * Adding the new polyfill to the existing `TestCases` classes. --- README.md | 10 +++ phpunitpolyfills-autoload.php | 21 ++++++ src/Polyfills/EqualToSpecializations.php | 53 ++++++++++++++ .../EqualToSpecializations_Empty.php | 8 +++ src/TestCases/TestCasePHPUnitGte8.php | 2 + src/TestCases/TestCasePHPUnitLte7.php | 2 + src/TestCases/XTestCase.php | 2 + .../Polyfills/EqualToSpecializationsTest.php | 70 +++++++++++++++++++ tests/TestCases/TestCaseTestTrait.php | 9 +++ 9 files changed, 177 insertions(+) create mode 100644 src/Polyfills/EqualToSpecializations.php create mode 100644 src/Polyfills/EqualToSpecializations_Empty.php create mode 100644 tests/Polyfills/EqualToSpecializationsTest.php diff --git a/README.md b/README.md index 5a8c40e..5d870f8 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,16 @@ These methods were introduced in PHPUnit 8.5.0 as alternatives to using `Assert: COMMENT: No documentation available (yet) for these assertions on the PHPUnit site. --> +#### PHPUnit < 9.0.0: `Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations` + +Polyfills the following methods: +| | | +|-----------------------------------|---------------------------------| +| `Assert::equalToCanonicalizing()` | `Assert::equalToIgnoringCase()` | +| `Assert::equalToWithDelta()` | | + +These methods, which are typically used to verify parameters passed to Mock Objects, were introduced in PHPUnit 9.0.0 as alternatives to using `Assert::EqualTo()` with these optional parameters. Support for passing the respective optional parameters to `Assert::EqualTo()` was removed in PHPUnit 9.0.0. + #### PHPUnit < 9.1.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames` Polyfills the following renamed methods: diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 2dd8213..851188c 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -65,6 +65,10 @@ public static function load( $className ) { self::loadAssertFileEqualsSpecializations(); return true; + case 'Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations': + self::loadEqualToSpecializations(); + return true; + case 'Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames': self::loadAssertionRenames(); return true; @@ -324,6 +328,23 @@ public static function loadAssertFileEqualsSpecializations() { require_once __DIR__ . '/src/Polyfills/AssertFileEqualsSpecializations_Empty.php'; } + /** + * Load the EqualToSpecializations polyfill or an empty trait with the same name + * if a PHPUnit version is used which already contains this functionality. + * + * @return void + */ + public static function loadEqualToSpecializations() { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'equalToWithDelta' ) === false ) { + // PHPUnit < 9.0.0. + require_once __DIR__ . '/src/Polyfills/EqualToSpecializations.php'; + return; + } + + // PHPUnit >= 9.0.0. + require_once __DIR__ . '/src/Polyfills/EqualToSpecializations_Empty.php'; + } + /** * Load the AssertionRenames polyfill or an empty trait with the same name * if a PHPUnit version is used which already contains this functionality. diff --git a/src/Polyfills/EqualToSpecializations.php b/src/Polyfills/EqualToSpecializations.php new file mode 100644 index 0000000..2bce1ff --- /dev/null +++ b/src/Polyfills/EqualToSpecializations.php @@ -0,0 +1,53 @@ += 9.0.0 in which this polyfill is not needed. + */ +trait EqualToSpecializations {} diff --git a/src/TestCases/TestCasePHPUnitGte8.php b/src/TestCases/TestCasePHPUnitGte8.php index cd5b120..f819be9 100644 --- a/src/TestCases/TestCasePHPUnitGte8.php +++ b/src/TestCases/TestCasePHPUnitGte8.php @@ -7,6 +7,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; +use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches; use Yoast\PHPUnitPolyfills\Polyfills\ExpectPHPException; @@ -25,6 +26,7 @@ abstract class TestCase extends PHPUnit_TestCase { use AssertClosedResource; use AssertFileEqualsSpecializations; use AssertionRenames; + use EqualToSpecializations; use ExpectExceptionMessageMatches; use ExpectPHPException; diff --git a/src/TestCases/TestCasePHPUnitLte7.php b/src/TestCases/TestCasePHPUnitLte7.php index 6dc9596..e906972 100644 --- a/src/TestCases/TestCasePHPUnitLte7.php +++ b/src/TestCases/TestCasePHPUnitLte7.php @@ -12,6 +12,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertIsType; use Yoast\PHPUnitPolyfills\Polyfills\AssertNumericType; use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; +use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionObject; @@ -37,6 +38,7 @@ abstract class TestCase extends PHPUnit_TestCase { use AssertIsType; use AssertNumericType; use AssertStringContains; + use EqualToSpecializations; use ExpectException; use ExpectExceptionMessageMatches; use ExpectExceptionObject; diff --git a/src/TestCases/XTestCase.php b/src/TestCases/XTestCase.php index a5ff6ac..3d4dbd9 100644 --- a/src/TestCases/XTestCase.php +++ b/src/TestCases/XTestCase.php @@ -12,6 +12,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertIsType; use Yoast\PHPUnitPolyfills\Polyfills\AssertNumericType; use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; +use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionObject; @@ -39,6 +40,7 @@ abstract class XTestCase extends PHPUnit_TestCase { use AssertIsType; use AssertNumericType; use AssertStringContains; + use EqualToSpecializations; use ExpectException; use ExpectExceptionMessageMatches; use ExpectExceptionObject; diff --git a/tests/Polyfills/EqualToSpecializationsTest.php b/tests/Polyfills/EqualToSpecializationsTest.php new file mode 100644 index 0000000..97871c7 --- /dev/null +++ b/tests/Polyfills/EqualToSpecializationsTest.php @@ -0,0 +1,70 @@ +assertThat( 2.5, $this->equalToWithDelta( 2.3, 0.5 ) ); + } + + /** + * Verify availability of the equalToCanonicalizing() method. + * + * @return void + */ + public function testEqualToCanonicalizing() { + self::assertThat( [ 2, 3, 1 ], static::equalToCanonicalizing( [ 3, 2, 1 ] ) ); + } + + /** + * Verify availability of the equalIgnoringCase() method. + * + * @return void + */ + public function testEqualToIgnoringCase() { + self::assertThat( 'A', self::equalToIgnoringCase( 'a' ) ); + } + + /** + * Verify ability of the equalToWithDelta() method to correctly fail a comparison. + * + * @return void + */ + public function testEqualToWithDeltaNegative() { + self::assertThat( 3.5, $this->logicalNot( $this->equalToWithDelta( 2.3, 0.5 ) ) ); + } + + /** + * Verify ability of the equalToCanonicalizing() method to correctly fail a comparison. + * + * @return void + */ + public function testEqualToCanonicalizingNegative() { + static::assertThat( [ 2, 3, 1 ], $this->logicalNot( static::equalToCanonicalizing( [ 4, 2, 1 ] ) ) ); + } + + /** + * Verify ability of the equalToIgnoringCaseNegative() method to correctly fail a comparison. + * + * @return void + */ + public function testEqualToIgnoringCaseNegative() { + self::assertThat( 'A', $this->logicalNot( $this->equalToIgnoringCase( 'b' ) ) ); + } +} diff --git a/tests/TestCases/TestCaseTestTrait.php b/tests/TestCases/TestCaseTestTrait.php index d7b4c84..54601f5 100644 --- a/tests/TestCases/TestCaseTestTrait.php +++ b/tests/TestCases/TestCaseTestTrait.php @@ -154,4 +154,13 @@ public function testAvailabilityAssertClosedResource() { $this->assertIsClosedResource( $resource ); } + + /** + * Verify availability of trait polyfilled PHPUnit methods [13]. + * + * @return void + */ + public function testAvailabilityEqualToSpecializations() { + self::assertThat( [ 2, 3, 1 ], $this->equalToCanonicalizing( [ 3, 2, 1 ] ) ); + } } From a4260a37986d69670dfe62fd511d5bace840ee66 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 16 Jun 2021 02:47:35 +0200 Subject: [PATCH 29/39] GH Actions: expand the test matrix ... to test against the highest and a "low" PHPUnit version for each supported PHP version. --- .github/workflows/test.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c67e15..fc8ff49 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,38 @@ jobs: experimental: [false] include: + # Test against a version on the low-end of the PHPUnit versions supported for each PHP version. + # On PHP 5.4 and 5.5, only PHPUnit 4.x is supported and the lowest and the + # highest supported version would be the same. + # Using the Composer `--prefer-lowest` option is, unfortunately, not viable, as + # PHPUnit 4.8.36 doesn't have proper PHP restrictions, which means that it + # would always be installed as "low", which would break the builds for PHP 7.2+. + - php: '5.6' + phpunit: '5.7.21' + experimental: false + - php: '7.0' + phpunit: '5.7.27' + experimental: false + - php: '7.1' + phpunit: '5.7.21' + experimental: false + - php: '7.2' + phpunit: '6.3.1' + experimental: false + - php: '7.3' + phpunit: '7.2.7' + experimental: false + - php: '7.4' + phpunit: '8.1.6' + experimental: false + - php: '8.0' + phpunit: '8.5.16' + experimental: false + - php: '8.0' + phpunit: '9.3.0' + experimental: false + + # Experimental builds. - php: '8.1' phpunit: 'auto' experimental: true From 967f09aeec4e2b899fd2f7cdc9515ee437919940 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 15 Jun 2021 06:53:29 +0200 Subject: [PATCH 30/39] ResourceHelper: minor PHP cross-version compatibility fix For optimal compatibility with cross-version PHP, `TypeError`s should be caught before `Exception`s. --- src/Helpers/ResourceHelper.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Helpers/ResourceHelper.php b/src/Helpers/ResourceHelper.php index 7bdf591..f3d73e0 100644 --- a/src/Helpers/ResourceHelper.php +++ b/src/Helpers/ResourceHelper.php @@ -67,10 +67,10 @@ public static function isClosedResource( $actual ) { if ( $resourceType === 'Unknown' ) { return true; } - } catch ( Exception $e ) { - // Ignore. Not a resource. } catch ( TypeError $e ) { // Ignore. Not a resource. + } catch ( Exception $e ) { + // Ignore. Not a resource. } } @@ -97,10 +97,10 @@ public static function isResourceStateReliable( $actual ) { if ( $type === 'xml' && self::isIncompatiblePHPForLibXMLResources() === true ) { return false; } - } catch ( Exception $e ) { - // Ignore. Not a resource. } catch ( TypeError $e ) { // Ignore. Not a resource. + } catch ( Exception $e ) { + // Ignore. Not a resource. } return true; From e9bd5d646322f66a7ccc769d81c3b47ab2a769df Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 16 Jun 2021 04:29:27 +0200 Subject: [PATCH 31/39] AssertClosedResource/ResourceHelper: add extra test Add an extra test to document the behaviour of the `shouldClosedResourceAssertionBeSkipped()` method when a non-resource is passed. --- .../AssertClosedResourceNotResourceTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Polyfills/AssertClosedResourceNotResourceTest.php b/tests/Polyfills/AssertClosedResourceNotResourceTest.php index ce3fe19..1aef61f 100644 --- a/tests/Polyfills/AssertClosedResourceNotResourceTest.php +++ b/tests/Polyfills/AssertClosedResourceNotResourceTest.php @@ -58,6 +58,19 @@ public function testAssertIsNotClosedResource( $value ) { self::assertIsNotClosedResource( $value ); } + /** + * Verify that the shouldClosedResourceAssertionBeSkipped() method returns true for non-resources. + * + * @dataProvider dataNotResource + * + * @param mixed $value The value to test. + * + * @return void + */ + public function testShouldClosedResourceAssertionBeSkipped( $value ) { + $this->assertFalse( self::shouldClosedResourceAssertionBeSkipped( $value ) ); + } + /** * Data provider * From c2a313eabbbb54bed3f1b1dc7bfda2a6ba2e75b1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 16 Jun 2021 05:41:15 +0200 Subject: [PATCH 32/39] GH Actions: no need to lint twice With the matrix now expanded, linting would now be done multiple times per PHP version, which isn't really necessary. --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc8ff49..86261de 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -91,11 +91,11 @@ jobs: composer-options: --ignore-platform-reqs - name: "Lint PHP files against parse errors - PHP < 7.0" - if: ${{ matrix.php < 7.0 }} + if: ${{ matrix.phpunit == 'auto' && matrix.php < 7.0 }} run: composer lint-lt71 - name: "Lint PHP files against parse errors - PHP >= 7.0" - if: ${{ matrix.php >= 7.0 }} + if: ${{ matrix.phpunit == 'auto' && matrix.php >= 7.0 }} run: composer lint - name: Run the unit tests From 64141ffee6f7a00d117d7de1f93a81a881615213 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 16 Jun 2021 15:17:02 +0200 Subject: [PATCH 33/39] Documentation: minor improvement to inline docs --- src/Polyfills/AssertIsType.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Polyfills/AssertIsType.php b/src/Polyfills/AssertIsType.php index 9e94a40..8ddcbf7 100644 --- a/src/Polyfills/AssertIsType.php +++ b/src/Polyfills/AssertIsType.php @@ -141,8 +141,8 @@ public static function assertIsCallable( $actual, $message = '' ) { * Asserts that a variable is of type iterable. * * {@internal Support for `iterable` was only added to the `Assert::assertInternalType()` method - * in PHPUnit 7.1.0, so this polyfill can't use that functionality until the minimum supported - * PHPUnit version of this library would be PHPUnit 7.1.0.} + * in PHPUnit 7.1.0, so this polyfill can't use a direct fall-through to that functionality + * until the minimum supported PHPUnit version of this library would be PHPUnit 7.1.0.} * * @link https://github.com/sebastianbergmann/phpunit/pull/3035 PR which added support for `is_iterable` * to `Assert::assertInternalType()`. @@ -288,8 +288,8 @@ public static function assertIsNotCallable( $actual, $message = '' ) { * Asserts that a variable is not of type iterable. * * {@internal Support for `iterable` was only added to the `Assert::assertNotInternalType()` method - * in PHPUnit 7.1.0, so this polyfill can't use that functionality until the minimum supported - * PHPUnit version of this library would be PHPUnit 7.1.0.} + * in PHPUnit 7.1.0, so this polyfill can't use a direct fall-through to that functionality + * until the minimum supported PHPUnit version of this library would be PHPUnit 7.1.0.} * * @link https://github.com/sebastianbergmann/phpunit/pull/3035 PR which added support for `is_iterable` * to `Assert::assertNotInternalType()`. From 86abfb9fe5798cf79a8876f04291558438d22154 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 15 Jun 2021 07:20:32 +0200 Subject: [PATCH 34/39] Add polyfills for the PHP 7.0 `TypeError` and `Error` classes Some of the existing code already references these classes and while not (currently) done in a PHP cross-version problematic way, it is still better to make sure these classes exist for optimal PHP cross-version compatibility. While not strictly correct/PSR4-compliant, I've elected to put these classes in the `Exceptions` subfolder, even though these are non-namespaced classes and not strictly part of the _PHPUnit_ polyfills. Note: the autoloader will only be called if these classes do not exist in PHP itself (PHP < 7.0), so the way it is set up now, these files will never get loaded on PHP >= 7.0 (which would otherwise cause a "Class already declared" error). Includes: * Explicitly excluding the PHPCompatibility notices about these classes in the PHPCS ruleset. _Note: due to the use of the PHPCompatibilityWP ruleset in YoastCS, these notices weren't showing anyway as WP backfills these classes. All the same, making it explicit in the ruleset documents the polyfills as now included in this repo._ * Adjustments to the Composer scripts to run the PHP linting and the GH Actions workflow which runs it. On PHP 7.x, these polyfills would cause a `Cannot declare class Error, because the name is already in use in ...` error otherwise. --- .github/workflows/test.yml | 12 ++++++++---- .phpcs.xml.dist | 11 +++++++++++ composer.json | 9 ++++++--- phpunitpolyfills-autoload.php | 21 +++++++++++++++++++++ src/Exceptions/Error.php | 8 ++++++++ src/Exceptions/TypeError.php | 8 ++++++++ 6 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 src/Exceptions/Error.php create mode 100644 src/Exceptions/TypeError.php diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86261de..4666c00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,11 +92,15 @@ jobs: - name: "Lint PHP files against parse errors - PHP < 7.0" if: ${{ matrix.phpunit == 'auto' && matrix.php < 7.0 }} - run: composer lint-lt71 + run: composer lint-lt70 - - name: "Lint PHP files against parse errors - PHP >= 7.0" - if: ${{ matrix.phpunit == 'auto' && matrix.php >= 7.0 }} - run: composer lint + - name: "Lint PHP files against parse errors - PHP 7.x" + if: ${{ matrix.phpunit == 'auto' && startsWith( matrix.php, '7' ) }} + run: composer lint7 + + - name: "Lint PHP files against parse errors - PHP >= 8.0" + if: ${{ matrix.phpunit == 'auto' && matrix.php >= 8.0 }} + run: composer lint-gte80 - name: Run the unit tests run: composer test diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index dd6e745..8223ff4 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -50,6 +50,10 @@ + + + +
@@ -81,6 +85,13 @@ ############################################################################# --> + + + /src/Exceptions/*Error\.php$ + + /src/Helpers/ResourceHelper\.php$ diff --git a/composer.json b/composer.json index e339d9f..9d0e049 100644 --- a/composer.json +++ b/composer.json @@ -46,12 +46,15 @@ } }, "scripts": { - "lint": [ - "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git" + "lint7": [ + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git --exclude src/Exceptions/Error.php --exclude src/Exceptions/TypeError.php" ], - "lint-lt71": [ + "lint-lt70": [ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git --exclude src/TestCases/TestCasePHPUnitGte8.php --exclude src/TestListeners/TestListenerDefaultImplementationPHPUnitGte7.php" ], + "lint-gte80": [ + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git" + ], "check-cs": [ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --runtime-set testVersion 5.4-" ], diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 851188c..5376f7a 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -19,6 +19,27 @@ class Autoload { * @return bool */ public static function load( $className ) { + /* + * Polyfill two PHP 7.0 classes. + * The autoloader will only be called for these if these classes don't already + * exist in PHP natively. + */ + if ( $className === 'Error' || $className === 'TypeError' ) { + $file = \realpath( + __DIR__ . \DIRECTORY_SEPARATOR + . 'src' . \DIRECTORY_SEPARATOR + . 'Exceptions' . \DIRECTORY_SEPARATOR + . $className . '.php' + ); + + if ( \file_exists( $file ) === true ) { + require_once $file; + return true; + } + + return false; + } + // Only load classes belonging to this library. if ( \stripos( $className, 'Yoast\PHPUnitPolyfills' ) !== 0 ) { return false; diff --git a/src/Exceptions/Error.php b/src/Exceptions/Error.php new file mode 100644 index 0000000..6a44e67 --- /dev/null +++ b/src/Exceptions/Error.php @@ -0,0 +1,8 @@ + Date: Wed, 16 Jun 2021 15:46:43 +0200 Subject: [PATCH 35/39] PHPCS: use PHPCompatibility proper, not PHPCompatibilityWP When adding the `TypeError`/`Error` polyfill (#36), I suddenly realized that the use of the `PHPCompatibilityWP` ruleset by the `Yoast` standard was causing certain issues not to show up, while - for this repo - they **_should_** be shown. By setting the `severity` of all the sniffs in the `PHPCompatibilility` ruleset back to `5`, the `exclude`s from the `PHPCompatibilityWP` ruleset are effectively "undone" and we are back to using `PHPCompatibility` proper. --- .phpcs.xml.dist | 8 ++++++++ src/Polyfills/AssertIsType.php | 2 ++ 2 files changed, 10 insertions(+) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 8223ff4..710fb4c 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -50,6 +50,14 @@ + + + + + 5 diff --git a/src/Polyfills/AssertIsType.php b/src/Polyfills/AssertIsType.php index 8ddcbf7..2d42f01 100644 --- a/src/Polyfills/AssertIsType.php +++ b/src/Polyfills/AssertIsType.php @@ -155,6 +155,7 @@ public static function assertIsCallable( $actual, $message = '' ) { public static function assertIsIterable( $actual, $message = '' ) { if ( \function_exists( 'is_iterable' ) === true ) { // PHP >= 7.1. + // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.is_iterableFound static::assertTrue( \is_iterable( $actual ), $message ); } else { @@ -302,6 +303,7 @@ public static function assertIsNotCallable( $actual, $message = '' ) { public static function assertIsNotIterable( $actual, $message = '' ) { if ( \function_exists( 'is_iterable' ) === true ) { // PHP >= 7.1. + // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.is_iterableFound static::assertFalse( \is_iterable( $actual ), $message ); } else { From cb1b31d382c2b5f4be70d626e26b1a6f463e3f9d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 16 Jun 2021 18:38:24 +0200 Subject: [PATCH 36/39] AssertObjectEquals trait: polyfill the Assert::assertObjectEquals() method PHPUnit 9.4.0 introduced the new `Assert::assertObjectEquals()` method. This commit: * Adds two traits with the same name. One to polyfill the method when not available in PHPUnit. The other to allow for `use`-ing the trait in PHPUnit versions in which the method is already natively available. * Adds an `InvalidComparisonMethodException` exception class. _PHPUnit natively throws a range of different exceptions._ _The polyfill included in this library throws one exception type - the `InvalidComparisonMethodException` - with a range of different messages._ * Logic to the custom autoloader which will load the correct trait depending on the PHPUnit version used. * Adds tests. As the polyfill contains logic to match the PHPUnit native implementation as closely as possible, while still being PHP and PHPUnit cross-version compatible, extensive unit tests have been added to ensure the behaviour of the polyfill matches that of the original function, with the exception of the _return type verification_. As return types were not available in PHP prior to PHP 7.0, the return type verification as done in the PHPUnit native implementation, has been replaced by a verification that the _returned value_ is of the required type. This provides the same safeguard as the PHPUnit native implementation, but in a PHP cross-version compatible manner. Note: the method uses `static::` to call the PHPUnit native functionality. This allows for existing method overloads in a child class of the PHPUnit native `TestCase` to be respected. Includes: * Adding information on the new polyfill to the README. * Adding the new polyfill to the existing `TestCases` classes. * Adding a few select exceptions to the PHPCS ruleset for the fixtures used in the tests. Refs: * https://github.com/sebastianbergmann/phpunit/issues/4467 * https://github.com/sebastianbergmann/phpunit/issues/4707 * https://github.com/sebastianbergmann/phpunit/commit/1dba8c3a4b2dd04a3ff1869f75daaeb6757a14ee * https://github.com/sebastianbergmann/phpunit/commit/6099c5eefccfda860c889f575d58b5fe6cc10c83 --- .phpcs.xml.dist | 11 + README.md | 18 + composer.json | 4 +- phpunitpolyfills-autoload.php | 22 + .../InvalidComparisonMethodException.php | 23 ++ src/Polyfills/AssertObjectEquals.php | 234 +++++++++++ src/Polyfills/AssertObjectEquals_Empty.php | 8 + src/TestCases/TestCasePHPUnitGte8.php | 2 + src/TestCases/TestCasePHPUnitLte7.php | 2 + src/TestCases/XTestCase.php | 2 + .../AssertObjectEqualsPHPUnitLt940Test.php | 314 ++++++++++++++ tests/Polyfills/AssertObjectEqualsTest.php | 391 ++++++++++++++++++ tests/Polyfills/Fixtures/ChildValueObject.php | 36 ++ tests/Polyfills/Fixtures/ValueObject.php | 105 +++++ .../Fixtures/ValueObjectNoReturnType.php | 105 +++++ tests/Polyfills/Fixtures/ValueObjectUnion.php | 36 ++ .../Fixtures/ValueObjectUnionNoReturnType.php | 36 ++ tests/TestCases/TestCaseTestTrait.php | 14 + 18 files changed, 1361 insertions(+), 2 deletions(-) create mode 100644 src/Exceptions/InvalidComparisonMethodException.php create mode 100644 src/Polyfills/AssertObjectEquals.php create mode 100644 src/Polyfills/AssertObjectEquals_Empty.php create mode 100644 tests/Polyfills/AssertObjectEqualsPHPUnitLt940Test.php create mode 100644 tests/Polyfills/AssertObjectEqualsTest.php create mode 100644 tests/Polyfills/Fixtures/ChildValueObject.php create mode 100644 tests/Polyfills/Fixtures/ValueObject.php create mode 100644 tests/Polyfills/Fixtures/ValueObjectNoReturnType.php create mode 100644 tests/Polyfills/Fixtures/ValueObjectUnion.php create mode 100644 tests/Polyfills/Fixtures/ValueObjectUnionNoReturnType.php diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 710fb4c..67c1ea3 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -145,4 +145,15 @@ /tests/TestCases/TestCaseTestTrait\.php$ + + + /tests/Polyfills/Fixtures/ChildValueObject\.php$ + /tests/Polyfills/Fixtures/ValueObject\.php$ + /tests/Polyfills/Fixtures/ValueObjectUnion\.php$ + + + /tests/Polyfills/Fixtures/ValueObjectUnion\.php$ + /tests/Polyfills/Fixtures/ValueObjectUnionNoReturnType\.php$ + + diff --git a/README.md b/README.md index 5d870f8..f78a1f6 100644 --- a/README.md +++ b/README.md @@ -436,6 +436,24 @@ if ( self::shouldClosedResourceAssertionBeSkipped( $actual ) === false ) { > :point_right: While this polyfill is tested extensively, testing for these kind of bugs exhaustively is _hard_. > Please [report any bugs](https://github.com/Yoast/PHPUnit-Polyfills/issues/new/choose) found and include a clear code sample to reproduce the issue. +#### PHPUnit < 9.4.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals` + +Polyfills the [`Assert::assertObjectEquals()`] method to verify two (value) objects are considered equal. +This assertion expects an object to contain a comparator method in the object itself. This comparator method is subsequently called to verify the "equalness" of the objects. + +The `assertObjectEquals() assertion was introduced in PHPUnit 9.4.0. + +> :info: Due to [limitations in how this assertion is implemented in PHPUnit] itself, it is currently not possible to create a single comparator method which will be compatible with both PHP < 7.0 and PHP 7.0 or higher. +> +> In effect two declarations of the same object would be needed to be compatible with PHP < 7.0 and PHP 7.0 and higher and still allow for testing the object using the `assertObjectEquals()` assertion. +> +> Due to this limitation, it is recommended to only use this assertion if the minimum supported PHP version of a project is PHP 7.0 or higher; or if the project does not run its tests on PHPUnit >= 9.4.0. + +[limitations in how this assertion is implemented in PHPUnit]: https://github.com/sebastianbergmann/phpunit/issues/4707 + + ### Helper traits diff --git a/composer.json b/composer.json index 9d0e049..5cf9d22 100644 --- a/composer.json +++ b/composer.json @@ -47,10 +47,10 @@ }, "scripts": { "lint7": [ - "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git --exclude src/Exceptions/Error.php --exclude src/Exceptions/TypeError.php" + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git --exclude src/Exceptions/Error.php --exclude src/Exceptions/TypeError.php --exclude tests/Polyfills/Fixtures/ValueObjectUnion.php --exclude tests/Polyfills/Fixtures/ValueObjectUnionNoReturnType.php" ], "lint-lt70": [ - "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git --exclude src/TestCases/TestCasePHPUnitGte8.php --exclude src/TestListeners/TestListenerDefaultImplementationPHPUnitGte7.php" + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git --exclude src/TestCases/TestCasePHPUnitGte8.php --exclude src/TestListeners/TestListenerDefaultImplementationPHPUnitGte7.php --exclude tests/Polyfills/Fixtures/ChildValueObject.php --exclude tests/Polyfills/Fixtures/ValueObject.php --exclude tests/Polyfills/Fixtures/ValueObjectUnion.php --exclude tests/Polyfills/Fixtures/ValueObjectUnionNoReturnType.php" ], "lint-gte80": [ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git" diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 5376f7a..318ab6a 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -98,6 +98,10 @@ public static function load( $className ) { self::loadAssertClosedResource(); return true; + case 'Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals': + self::loadAssertObjectEquals(); + return true; + case 'Yoast\PHPUnitPolyfills\TestCases\TestCase': self::loadTestCase(); return true; @@ -108,6 +112,7 @@ public static function load( $className ) { /* * Handles: + * - Yoast\PHPUnitPolyfills\Exceptions\InvalidComparisonMethodException * - Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper * - Yoast\PHPUnitPolyfills\Helpers\ResourceHelper * - Yoast\PHPUnitPolyfills\TestCases\XTestCase @@ -400,6 +405,23 @@ public static function loadAssertClosedResource() { require_once __DIR__ . '/src/Polyfills/AssertClosedResource_Empty.php'; } + /** + * Load the AssertObjectEquals polyfill or an empty trait with the same name + * if a PHPUnit version is used which already contains this functionality. + * + * @return void + */ + public static function loadAssertObjectEquals() { + if ( \method_exists( '\PHPUnit\Framework\Assert', 'assertObjectEquals' ) === false ) { + // PHPUnit < 9.4.0. + require_once __DIR__ . '/src/Polyfills/AssertObjectEquals.php'; + return; + } + + // PHPUnit >= 9.4.0. + require_once __DIR__ . '/src/Polyfills/AssertObjectEquals_Empty.php'; + } + /** * Load the appropriate TestCase class based on the PHPUnit version being used. * diff --git a/src/Exceptions/InvalidComparisonMethodException.php b/src/Exceptions/InvalidComparisonMethodException.php new file mode 100644 index 0000000..db05e34 --- /dev/null +++ b/src/Exceptions/InvalidComparisonMethodException.php @@ -0,0 +1,23 @@ +getMessage() . \PHP_EOL; + } +} diff --git a/src/Polyfills/AssertObjectEquals.php b/src/Polyfills/AssertObjectEquals.php new file mode 100644 index 0000000..222c9ce --- /dev/null +++ b/src/Polyfills/AssertObjectEquals.php @@ -0,0 +1,234 @@ +$method($expected)` returns boolean true. + * + * @param object $expected Expected value. + * @param object $actual The value to test. + * @param string $method The name of the comparator method within the object. + * @param string $message Optional failure message to display. + * + * @return void + * + * @throws TypeError When any of the passed arguments do not meet the required type. + * @throws InvalidComparisonMethodException When the comparator method does not comply with the requirements. + */ + public static function assertObjectEquals( $expected, $actual, $method = 'equals', $message = '' ) { + /* + * Parameter input validation. + * In PHPUnit this is done via PHP native type declarations. Emulating this for the polyfill. + */ + if ( \is_object( $expected ) === false ) { + throw new TypeError( + \sprintf( + 'Argument 1 passed to assertObjectEquals() must be an object, %s given', + \gettype( $expected ) + ) + ); + } + + if ( \is_object( $actual ) === false ) { + throw new TypeError( + \sprintf( + 'Argument 2 passed to assertObjectEquals() must be an object, %s given', + \gettype( $actual ) + ) + ); + } + + if ( \is_scalar( $method ) === false ) { + throw new TypeError( + \sprintf( + 'Argument 3 passed to assertObjectEquals() must be of the type string, %s given', + \gettype( $method ) + ) + ); + } + else { + $method = (string) $method; + } + + /* + * Comparator method validation. + */ + $reflObject = new ReflectionObject( $actual ); + + if ( $reflObject->hasMethod( $method ) === false ) { + throw new InvalidComparisonMethodException( + \sprintf( + 'Comparison method %s::%s() does not exist.', + \get_class( $actual ), + $method + ) + ); + } + + $reflMethod = $reflObject->getMethod( $method ); + + /* + * As the next step, PHPUnit natively would validate the return type, + * but as return type declarations is a PHP 7.0+ feature, the polyfill + * skips this check in favour of checking the type of the actual + * returned value. + * + * Also see the upstream discussion about this: + * {@link https://github.com/sebastianbergmann/phpunit/issues/4707} + */ + + /* + * Comparator method parameter requirements validation. + */ + if ( $reflMethod->getNumberOfParameters() !== 1 + || $reflMethod->getNumberOfRequiredParameters() !== 1 + ) { + throw new InvalidComparisonMethodException( + \sprintf( + 'Comparison method %s::%s() does not declare exactly one parameter.', + \get_class( $actual ), + $method + ) + ); + } + + $noDeclaredTypeError = \sprintf( + 'Parameter of comparison method %s::%s() does not have a declared type.', + \get_class( $actual ), + $method + ); + + $notAcceptableTypeError = \sprintf( + '%s is not an accepted argument type for comparison method %s::%s().', + \get_class( $expected ), + \get_class( $actual ), + $method + ); + + $reflParameter = $reflMethod->getParameters()[0]; + + if ( \method_exists( $reflParameter, 'hasType' ) ) { + // PHP >= 7.0. + $hasType = $reflParameter->hasType(); + if ( $hasType === false ) { + throw new InvalidComparisonMethodException( $noDeclaredTypeError ); + } + + $type = $reflParameter->getType(); + if ( \class_exists( 'ReflectionNamedType' ) ) { + // PHP >= 7.1. + if ( ( $type instanceof ReflectionNamedType ) === false ) { + throw new InvalidComparisonMethodException( $noDeclaredTypeError ); + } + + $typeName = $type->getName(); + } + else { + /* + * PHP 7.0. + * Checking for `ReflectionType` will not throw an error on union types, + * but then again union types are not supported on PHP 7.0. + */ + if ( ( $type instanceof ReflectionType ) === false ) { + throw new InvalidComparisonMethodException( $noDeclaredTypeError ); + } + + $typeName = (string) $type; + } + } + else { + // PHP < 7.0. + try { + /* + * Using `ReflectionParameter::getClass()` will trigger an autoload of the class, + * but that's okay as for a valid class type that would be triggered on the + * function call to the $method (at the end of this assertion) anyway. + */ + $hasType = $reflParameter->getClass(); + } catch ( ReflectionException $e ) { + // Class with a type declaration for a non-existent class. + throw new InvalidComparisonMethodException( $notAcceptableTypeError ); + } + + if ( ( $hasType instanceof ReflectionClass ) === false ) { + // Array or callable type. + throw new InvalidComparisonMethodException( $noDeclaredTypeError ); + } + + $typeName = $hasType->name; + } + + /* + * Validate that the $expected object complies with the declared parameter type. + */ + if ( $typeName === 'self' ) { + $typeName = \get_class( $actual ); + } + + if ( ( $expected instanceof $typeName ) === false ) { + throw new InvalidComparisonMethodException( $notAcceptableTypeError ); + } + + /* + * Execute the comparator method. + */ + $result = $actual->{$method}( $expected ); + + if ( \is_bool( $result ) === false ) { + throw new InvalidComparisonMethodException( + \sprintf( + '%s::%s() does not return a boolean value.', + \get_class( $actual ), + $method + ) + ); + } + + $msg = \sprintf( + 'Failed asserting that two objects are equal. The objects are not equal according to %s::%s()', + \get_class( $actual ), + $method + ); + + if ( $message !== '' ) { + $msg = $message . \PHP_EOL . $msg; + } + + static::assertTrue( $result, $msg ); + } +} diff --git a/src/Polyfills/AssertObjectEquals_Empty.php b/src/Polyfills/AssertObjectEquals_Empty.php new file mode 100644 index 0000000..4390a97 --- /dev/null +++ b/src/Polyfills/AssertObjectEquals_Empty.php @@ -0,0 +1,8 @@ += 9.4.0 in which this polyfill is not needed. + */ +trait AssertObjectEquals {} diff --git a/src/TestCases/TestCasePHPUnitGte8.php b/src/TestCases/TestCasePHPUnitGte8.php index f819be9..cfe397c 100644 --- a/src/TestCases/TestCasePHPUnitGte8.php +++ b/src/TestCases/TestCasePHPUnitGte8.php @@ -7,6 +7,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; +use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals; use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches; use Yoast\PHPUnitPolyfills\Polyfills\ExpectPHPException; @@ -26,6 +27,7 @@ abstract class TestCase extends PHPUnit_TestCase { use AssertClosedResource; use AssertFileEqualsSpecializations; use AssertionRenames; + use AssertObjectEquals; use EqualToSpecializations; use ExpectExceptionMessageMatches; use ExpectPHPException; diff --git a/src/TestCases/TestCasePHPUnitLte7.php b/src/TestCases/TestCasePHPUnitLte7.php index e906972..34cf29b 100644 --- a/src/TestCases/TestCasePHPUnitLte7.php +++ b/src/TestCases/TestCasePHPUnitLte7.php @@ -11,6 +11,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; use Yoast\PHPUnitPolyfills\Polyfills\AssertIsType; use Yoast\PHPUnitPolyfills\Polyfills\AssertNumericType; +use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals; use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; @@ -37,6 +38,7 @@ abstract class TestCase extends PHPUnit_TestCase { use AssertionRenames; use AssertIsType; use AssertNumericType; + use AssertObjectEquals; use AssertStringContains; use EqualToSpecializations; use ExpectException; diff --git a/src/TestCases/XTestCase.php b/src/TestCases/XTestCase.php index 3d4dbd9..4db9148 100644 --- a/src/TestCases/XTestCase.php +++ b/src/TestCases/XTestCase.php @@ -11,6 +11,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; use Yoast\PHPUnitPolyfills\Polyfills\AssertIsType; use Yoast\PHPUnitPolyfills\Polyfills\AssertNumericType; +use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals; use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\ExpectException; @@ -39,6 +40,7 @@ abstract class XTestCase extends PHPUnit_TestCase { use AssertionRenames; use AssertIsType; use AssertNumericType; + use AssertObjectEquals; use AssertStringContains; use EqualToSpecializations; use ExpectException; diff --git a/tests/Polyfills/AssertObjectEqualsPHPUnitLt940Test.php b/tests/Polyfills/AssertObjectEqualsPHPUnitLt940Test.php new file mode 100644 index 0000000..90a0e33 --- /dev/null +++ b/tests/Polyfills/AssertObjectEqualsPHPUnitLt940Test.php @@ -0,0 +1,314 @@ +=' ) ) { + $this->markTestSkipped( 'This test can not be run with the PHPUnit native implementation of assertObjectEquals()' ); + } + } + + /** + * Verify availability of the assertObjectEquals() method. + * + * @return void + */ + public function testAssertObjectEquals() { + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual ); + } + + /** + * Verify behaviour when passing the $method parameter. + * + * @return void + */ + public function testAssertObjectEqualsCustomMethodName() { + $expected = new ValueObjectNoReturnType( 'different name' ); + $actual = new ValueObjectNoReturnType( 'different name' ); + $this->assertObjectEquals( $expected, $actual, 'nonDefaultName' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $expected parameter is not an object. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnExpectedNotObject() { + $pattern = '`^Argument 1 passed to [^\s]*assertObjectEquals\(\) must be an object, string given`'; + + $this->expectException( 'TypeError' ); + $this->expectExceptionMessageMatches( $pattern ); + + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( 'className', $actual ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $actual parameter is not an object. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnActualNotObject() { + $pattern = '`^Argument 2 passed to [^\s]*assertObjectEquals\(\) must be an object, string given`'; + + $this->expectException( 'TypeError' ); + $this->expectExceptionMessageMatches( $pattern ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, 'className' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter is not + * juggleable to a string. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodNotJuggleableToString() { + $pattern = '`^Argument 3 passed to [^\s]*assertObjectEquals\(\) must be of the type string, array given`'; + + $this->expectException( 'TypeError' ); + $this->expectExceptionMessageMatches( $pattern ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual, [] ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $actual object + * does not contain a method called $method. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodNotDeclared() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::doesNotExist() does not exist.'; + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'doesNotExist' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method accepts more than one parameter. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodAllowsForMoreParams() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::equalsTwoParams() does not declare exactly one parameter.'; + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsTwoParams' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method is not a required parameter. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamNotRequired() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::equalsParamNotRequired() does not declare exactly one parameter.'; + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamNotRequired' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter + * does not have a type declaration. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamMissingTypeDeclaration() { + $msg = 'Parameter of comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::equalsParamNoType() does not have a declared type.'; + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamNoType' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter + * has a PHP 8.0+ union type declaration. + * + * @requires PHP 8.0 + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamHasUnionTypeDeclaration() { + $msg = 'Parameter of comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectUnionNoReturnType::equalsParamUnionType() does not have a declared type.'; + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectUnionNoReturnType( 'test' ); + $actual = new ValueObjectUnionNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamUnionType' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter + * does not have a class-based type declaration. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamNonClassTypeDeclaration() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::equalsParamNonClassType().'; + if ( \PHP_VERSION_ID < 70000 ) { + $msg = 'Parameter of comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::equalsParamNonClassType() does not have a declared type.'; + } + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamNonClassType' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter + * has a class-based type declaration, but for a class which doesn't exist. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamNonExistentClassTypeDeclaration() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::equalsParamNonExistentClassType().'; + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamNonExistentClassType' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when $expected is not + * an instance of the type declared for the $method parameter. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamTypeMismatch() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::equals().'; + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $actual = new ValueObjectNoReturnType( 'test' ); + $this->assertObjectEquals( new stdClass(), $actual ); + } + + /** + * Verify that the assertObjectEquals() method fails a test when a call to method + * determines that the objects are not equal. + * + * @return void + */ + public function testAssertObjectEqualsFailsAsNotEqual() { + $msg = 'Failed asserting that two objects are equal.'; + + $this->expectException( $this->getAssertionFailedExceptionName() ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'testing... 1..2..3' ); + $this->assertObjectEquals( $expected, $actual ); + } + + /** + * Verify that the assertObjectEquals() method fails a test with a custom failure message, when a call + * to the method determines that the objects are not equal and the custom $message parameter has been passed. + * + * @return void + */ + public function testAssertObjectEqualsFailsAsNotEqualWithCustomMessage() { + $pattern = '`^This assertion failed for reason XYZ\s+Failed asserting that two objects are equal\.`'; + + $this->expectException( $this->getAssertionFailedExceptionName() ); + $this->expectExceptionMessageMatches( $pattern ); + + $expected = new ValueObjectNoReturnType( 'test' ); + $actual = new ValueObjectNoReturnType( 'testing... 1..2..3' ); + $this->assertObjectEquals( $expected, $actual, 'equals', 'This assertion failed for reason XYZ' ); + } + + /** + * Helper function: retrieve the name of the "assertion failed" exception to expect (PHPUnit cross-version). + * + * @return string + */ + public function getAssertionFailedExceptionName() { + $exception = 'PHPUnit\Framework\AssertionFailedError'; + if ( \class_exists( 'PHPUnit_Framework_AssertionFailedError' ) ) { + // PHPUnit < 6. + $exception = 'PHPUnit_Framework_AssertionFailedError'; + } + + return $exception; + } +} diff --git a/tests/Polyfills/AssertObjectEqualsTest.php b/tests/Polyfills/AssertObjectEqualsTest.php new file mode 100644 index 0000000..384a62a --- /dev/null +++ b/tests/Polyfills/AssertObjectEqualsTest.php @@ -0,0 +1,391 @@ +assertObjectEquals( $expected, $actual ); + } + + /** + * Verify behaviour when passing the $method parameter. + * + * @return void + */ + public function testAssertObjectEqualsCustomMethodName() { + $expected = new ValueObject( 'different name' ); + $actual = new ValueObject( 'different name' ); + $this->assertObjectEquals( $expected, $actual, 'nonDefaultName' ); + } + + /** + * Verify behaviour when $expected is a child of $actual. + * + * @return void + */ + public function testAssertObjectEqualsExpectedChildOfActual() { + $expected = new ChildValueObject( 'inheritance' ); + $actual = new ValueObject( 'inheritance' ); + $this->assertObjectEquals( $expected, $actual ); + } + + /** + * Verify behaviour when $actual is a child of $expected. + * + * @return void + */ + public function testAssertObjectEqualsActualChildOfExpected() { + $expected = new ValueObject( 'inheritance' ); + $actual = new ChildValueObject( 'inheritance' ); + $this->assertObjectEquals( $expected, $actual ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $expected parameter is not an object. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnExpectedNotObject() { + $this->expectException( 'TypeError' ); + + if ( \PHP_VERSION_ID >= 80000 + && \version_compare( PHPUnit_Version::id(), '9.4.0', '>=' ) + ) { + $msg = 'assertObjectEquals(): Argument #1 ($expected) must be of type object, string given'; + $this->expectExceptionMessage( $msg ); + } + else { + // PHP 5/7 or PHP 8 with the polyfill. + $pattern = '`^Argument 1 passed to [^\s]*assertObjectEquals\(\) must be an object, string given`'; + $this->expectExceptionMessageMatches( $pattern ); + } + + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( 'className', $actual ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $actual parameter is not an object. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnActualNotObject() { + $this->expectException( 'TypeError' ); + + if ( \PHP_VERSION_ID >= 80000 + && \version_compare( PHPUnit_Version::id(), '9.4.0', '>=' ) + ) { + $msg = 'assertObjectEquals(): Argument #2 ($actual) must be of type object, string given'; + $this->expectExceptionMessage( $msg ); + } + else { + // PHP 5/7. + $pattern = '`^Argument 2 passed to [^\s]*assertObjectEquals\(\) must be an object, string given`'; + $this->expectExceptionMessageMatches( $pattern ); + } + + $expected = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, 'className' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter is not + * juggleable to a string. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodNotJuggleableToString() { + $this->expectException( 'TypeError' ); + + if ( \PHP_VERSION_ID >= 80000 + && \version_compare( PHPUnit_Version::id(), '9.4.0', '>=' ) + ) { + $msg = 'assertObjectEquals(): Argument #3 ($method) must be of type string, array given'; + $this->expectExceptionMessage( $msg ); + } + else { + // PHP 5/7. + $pattern = '`^Argument 3 passed to [^\s]*assertObjectEquals\(\) must be of the type string, array given`'; + $this->expectExceptionMessageMatches( $pattern ); + } + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, $actual, [] ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $actual object + * does not contain a method called $method. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodNotDeclared() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::doesNotExist() does not exist.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotExistException' ) ) { + // PHPUnit > 9.4.0. + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotExistException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'doesNotExist' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method accepts more than one parameter. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodAllowsForMoreParams() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsTwoParams() does not declare exactly one parameter.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException' ) ) { + // PHPUnit > 9.4.0. + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsTwoParams' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method is not a required parameter. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamNotRequired() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsParamNotRequired() does not declare exactly one parameter.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException' ) ) { + // PHPUnit > 9.4.0. + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamNotRequired' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter + * does not have a type declaration. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamMissingTypeDeclaration() { + $msg = 'Parameter of comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsParamNoType() does not have a declared type.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException' ) ) { + // PHPUnit > 9.4.0. + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamNoType' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter + * has a PHP 8.0+ union type declaration. + * + * @requires PHP 8.0 + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamHasUnionTypeDeclaration() { + $msg = 'Parameter of comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectUnion::equalsParamUnionType() does not have a declared type.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException' ) ) { + // PHPUnit > 9.4.0. + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectUnion( 'test' ); + $actual = new ValueObjectUnion( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamUnionType' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter + * does not have a class-based type declaration. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamNonClassTypeDeclaration() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsParamNonClassType().'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException' ) ) { + // PHPUnit > 9.4.0. + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamNonClassType' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when the $method parameter + * has a class-based type declaration, but for a class which doesn't exist. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamNonExistentClassTypeDeclaration() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsParamNonExistentClassType().'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException' ) ) { + // PHPUnit > 9.4.0. + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, $actual, 'equalsParamNonExistentClassType' ); + } + + /** + * Verify that the assertObjectEquals() method throws an error when $expected is not + * an instance of the type declared for the $method parameter. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnMethodParamTypeMismatch() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equals().'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException' ) ) { + // PHPUnit > 9.4.0. + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( new stdClass(), $actual ); + } + + /** + * Verify that the assertObjectEquals() method fails a test when a call to method + * determines that the objects are not equal. + * + * @return void + */ + public function testAssertObjectEqualsFailsAsNotEqual() { + $msg = 'Failed asserting that two objects are equal.'; + + $this->expectException( $this->getAssertionFailedExceptionName() ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'testing... 1..2..3' ); + $this->assertObjectEquals( $expected, $actual ); + } + + /** + * Verify that the assertObjectEquals() method fails a test with a custom failure message, when a call + * to the method determines that the objects are not equal and the custom $message parameter has been passed. + * + * @return void + */ + public function testAssertObjectEqualsFailsAsNotEqualWithCustomMessage() { + $pattern = '`^This assertion failed for reason XYZ\s+Failed asserting that two objects are equal\.`'; + + $this->expectException( $this->getAssertionFailedExceptionName() ); + $this->expectExceptionMessageMatches( $pattern ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'testing... 1..2..3' ); + $this->assertObjectEquals( $expected, $actual, 'equals', 'This assertion failed for reason XYZ' ); + } + + /** + * Helper function: retrieve the name of the "assertion failed" exception to expect (PHPUnit cross-version). + * + * @return string + */ + public function getAssertionFailedExceptionName() { + $exception = 'PHPUnit\Framework\AssertionFailedError'; + if ( \class_exists( 'PHPUnit_Framework_AssertionFailedError' ) ) { + // PHPUnit < 6. + $exception = 'PHPUnit_Framework_AssertionFailedError'; + } + + return $exception; + } +} diff --git a/tests/Polyfills/Fixtures/ChildValueObject.php b/tests/Polyfills/Fixtures/ChildValueObject.php new file mode 100644 index 0000000..6eaafed --- /dev/null +++ b/tests/Polyfills/Fixtures/ChildValueObject.php @@ -0,0 +1,36 @@ +value = $value; + } + + /** + * Comparator method: correctly declared. + * + * @param ValueObject $other Object to compare. + * + * @return bool + */ + public function equals( ValueObject $other ): bool { + return ( $this->value === $other->value ); + } +} diff --git a/tests/Polyfills/Fixtures/ValueObject.php b/tests/Polyfills/Fixtures/ValueObject.php new file mode 100644 index 0000000..e2305dd --- /dev/null +++ b/tests/Polyfills/Fixtures/ValueObject.php @@ -0,0 +1,105 @@ +value = $value; + } + + /** + * Comparator method: correctly declared. + * + * @param self $other Object to compare. + * + * @return bool + */ + public function equals( self $other ): bool { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: correctly declared and with the class name as type instead of `self`. + * + * @param ValueObject $other Object to compare. + * + * @return bool + */ + public function nonDefaultName( ValueObject $other ): bool { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - more than one parameter. + * + * @param ValueObject $other Object to compare. + * @param mixed $param Just testing. + * + * @return bool + */ + public function equalsTwoParams( $other, $param ): bool { + return ( $param && $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - parameter is not required. + * + * @param self|null $other Object to compare. + * + * @return bool + */ + public function equalsParamNotRequired( self $other = null ): bool { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - parameter is not typed. + * + * @param ValueObject $other Object to compare. + * + * @return bool + */ + public function equalsParamNoType( $other ): bool { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - parameter has a non-classname type. + * + * @param array $other Object to compare. + * + * @return bool + */ + public function equalsParamNonClassType( array $other ): bool { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - parameter has a non-existent classname type. + * + * @param ClassWhichDoesntExist $other Object to compare. + * + * @return bool + */ + public function equalsParamNonExistentClassType( ClassWhichDoesntExist $other ): bool { + return ( $this->value === $other->value ); + } +} diff --git a/tests/Polyfills/Fixtures/ValueObjectNoReturnType.php b/tests/Polyfills/Fixtures/ValueObjectNoReturnType.php new file mode 100644 index 0000000..52775a0 --- /dev/null +++ b/tests/Polyfills/Fixtures/ValueObjectNoReturnType.php @@ -0,0 +1,105 @@ +value = $value; + } + + /** + * Comparator method: correctly declared. + * + * @param ValueObjectNoReturnType $other Object to compare. + * + * @return bool + */ + public function equals( ValueObjectNoReturnType $other ) { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: correctly declared and with self as type instead of the class name. + * + * @param self $other Object to compare. + * + * @return bool + */ + public function nonDefaultName( self $other ) { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - more than one parameter. + * + * @param ValueObjectNoReturnType $other Object to compare. + * @param mixed $param Just testing. + * + * @return bool + */ + public function equalsTwoParams( $other, $param ) { + return ( $param && $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - parameter is not required. + * + * @param self|null $other Object to compare. + * + * @return bool + */ + public function equalsParamNotRequired( self $other = null ) { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - parameter is not typed. + * + * @param ValueObjectNoReturnType $other Object to compare. + * + * @return bool + */ + public function equalsParamNoType( $other ) { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - parameter has a non-classname type. + * + * @param array $other Object to compare. + * + * @return bool + */ + public function equalsParamNonClassType( array $other ) { + return ( $this->value === $other->value ); + } + + /** + * Comparator method: incorrectly declared - parameter has a non-existent classname type. + * + * @param ClassWhichDoesntExist $other Object to compare. + * + * @return bool + */ + public function equalsParamNonExistentClassType( ClassWhichDoesntExist $other ) { + return ( $this->value === $other->value ); + } +} diff --git a/tests/Polyfills/Fixtures/ValueObjectUnion.php b/tests/Polyfills/Fixtures/ValueObjectUnion.php new file mode 100644 index 0000000..0291fc7 --- /dev/null +++ b/tests/Polyfills/Fixtures/ValueObjectUnion.php @@ -0,0 +1,36 @@ +value = $value; + } + + /** + * Comparator method: incorrectly declared - parameter has a union type. + * + * @param self|OtherClass|array $other Object to compare. + * + * @return bool + */ + public function equalsParamUnionType( self|OtherClass|array $other ): bool { + return ( $this->value === $other->value ); + } +} diff --git a/tests/Polyfills/Fixtures/ValueObjectUnionNoReturnType.php b/tests/Polyfills/Fixtures/ValueObjectUnionNoReturnType.php new file mode 100644 index 0000000..efa2387 --- /dev/null +++ b/tests/Polyfills/Fixtures/ValueObjectUnionNoReturnType.php @@ -0,0 +1,36 @@ +value = $value; + } + + /** + * Comparator method: incorrectly declared - parameter has a union type. + * + * @param self|OtherClass|array $other Object to compare. + * + * @return bool + */ + public function equalsParamUnionType( self|OtherClass|array $other ) { + return ( $this->value === $other->value ); + } +} diff --git a/tests/TestCases/TestCaseTestTrait.php b/tests/TestCases/TestCaseTestTrait.php index 54601f5..a4a80f0 100644 --- a/tests/TestCases/TestCaseTestTrait.php +++ b/tests/TestCases/TestCaseTestTrait.php @@ -4,6 +4,7 @@ use Exception; use Yoast\PHPUnitPolyfills\Tests\Polyfills\AssertFileEqualsSpecializationsTest; +use Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject; /** * Tests for the TestCase setups. @@ -163,4 +164,17 @@ public function testAvailabilityAssertClosedResource() { public function testAvailabilityEqualToSpecializations() { self::assertThat( [ 2, 3, 1 ], $this->equalToCanonicalizing( [ 3, 2, 1 ] ) ); } + + /** + * Verify availability of trait polyfilled PHPUnit methods [14]. + * + * @requires PHP 7.0 + * + * @return void + */ + public function testAvailabilityAssertObjectEquals() { + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectEquals( $expected, $actual ); + } } From a70ce16ede0fd68236920a0f87d1a42c39553496 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 17 Jun 2021 10:36:31 +0200 Subject: [PATCH 37/39] AssertObjectEquals: improve "not boolean return value" error message ... and add tests covering this exception. --- .phpcs.xml.dist | 5 +++- src/Polyfills/AssertObjectEquals.php | 2 +- .../AssertObjectEqualsPHPUnitLt940Test.php | 17 +++++++++++++ tests/Polyfills/AssertObjectEqualsTest.php | 24 +++++++++++++++++++ tests/Polyfills/Fixtures/ValueObject.php | 11 +++++++++ .../Fixtures/ValueObjectNoReturnType.php | 19 +++++++++++++++ 6 files changed, 76 insertions(+), 2 deletions(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 67c1ea3..f1537c2 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -146,7 +146,7 @@
- + /tests/Polyfills/Fixtures/ChildValueObject\.php$ /tests/Polyfills/Fixtures/ValueObject\.php$ /tests/Polyfills/Fixtures/ValueObjectUnion\.php$ @@ -155,5 +155,8 @@ /tests/Polyfills/Fixtures/ValueObjectUnion\.php$ /tests/Polyfills/Fixtures/ValueObjectUnionNoReturnType\.php$ + + /tests/Polyfills/Fixtures/ValueObject\.php$ + diff --git a/src/Polyfills/AssertObjectEquals.php b/src/Polyfills/AssertObjectEquals.php index 222c9ce..f282a7f 100644 --- a/src/Polyfills/AssertObjectEquals.php +++ b/src/Polyfills/AssertObjectEquals.php @@ -212,7 +212,7 @@ public static function assertObjectEquals( $expected, $actual, $method = 'equals if ( \is_bool( $result ) === false ) { throw new InvalidComparisonMethodException( \sprintf( - '%s::%s() does not return a boolean value.', + 'Comparison method %s::%s() does not return a boolean value.', \get_class( $actual ), $method ) diff --git a/tests/Polyfills/AssertObjectEqualsPHPUnitLt940Test.php b/tests/Polyfills/AssertObjectEqualsPHPUnitLt940Test.php index 90a0e33..345ee2c 100644 --- a/tests/Polyfills/AssertObjectEqualsPHPUnitLt940Test.php +++ b/tests/Polyfills/AssertObjectEqualsPHPUnitLt940Test.php @@ -263,6 +263,23 @@ public function testAssertObjectEqualsFailsOnMethodParamTypeMismatch() { $this->assertObjectEquals( new stdClass(), $actual ); } + /** + * Verify that the assertObjectEquals() method throws an error when the declared return type/ + * the return value is not boolean. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnNonBooleanReturnValue() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNoReturnType::equalsNonBooleanReturnType() does not return a boolean value.'; + + $this->expectException( self::COMPARATOR_EXCEPTION ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNoReturnType( 100 ); + $actual = new ValueObjectNoReturnType( 100 ); + $this->assertObjectEquals( $expected, $actual, 'equalsNonBooleanReturnType' ); + } + /** * Verify that the assertObjectEquals() method fails a test when a call to method * determines that the objects are not equal. diff --git a/tests/Polyfills/AssertObjectEqualsTest.php b/tests/Polyfills/AssertObjectEqualsTest.php index 384a62a..6f939f7 100644 --- a/tests/Polyfills/AssertObjectEqualsTest.php +++ b/tests/Polyfills/AssertObjectEqualsTest.php @@ -340,6 +340,30 @@ public function testAssertObjectEqualsFailsOnMethodParamTypeMismatch() { $this->assertObjectEquals( new stdClass(), $actual ); } + /** + * Verify that the assertObjectEquals() method throws an error when the declared return type/ + * the return value is not boolean. + * + * @return void + */ + public function testAssertObjectEqualsFailsOnNonBooleanReturnValue() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsNonBooleanReturnType() does not return a boolean value.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareBoolReturnTypeException' ) ) { + // PHPUnit > 9.4.0. + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsNonBooleanReturnType() does not declare bool return type.'; + $exception = 'PHPUnit\Framework\ComparisonMethodDoesNotDeclareBoolReturnTypeException'; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 100 ); + $actual = new ValueObject( 100 ); + $this->assertObjectEquals( $expected, $actual, 'equalsNonBooleanReturnType' ); + } + /** * Verify that the assertObjectEquals() method fails a test when a call to method * determines that the objects are not equal. diff --git a/tests/Polyfills/Fixtures/ValueObject.php b/tests/Polyfills/Fixtures/ValueObject.php index e2305dd..323b27f 100644 --- a/tests/Polyfills/Fixtures/ValueObject.php +++ b/tests/Polyfills/Fixtures/ValueObject.php @@ -102,4 +102,15 @@ public function equalsParamNonClassType( array $other ): bool { public function equalsParamNonExistentClassType( ClassWhichDoesntExist $other ): bool { return ( $this->value === $other->value ); } + + /** + * Comparator method: incorrectly declared - non-boolean return type/value. + * + * @param self $other Object to compare. + * + * @return bool + */ + public function equalsNonBooleanReturnType( self $other ): int { + return ( $this->value <=> $other->value ); + } } diff --git a/tests/Polyfills/Fixtures/ValueObjectNoReturnType.php b/tests/Polyfills/Fixtures/ValueObjectNoReturnType.php index 52775a0..913611d 100644 --- a/tests/Polyfills/Fixtures/ValueObjectNoReturnType.php +++ b/tests/Polyfills/Fixtures/ValueObjectNoReturnType.php @@ -102,4 +102,23 @@ public function equalsParamNonClassType( array $other ) { public function equalsParamNonExistentClassType( ClassWhichDoesntExist $other ) { return ( $this->value === $other->value ); } + + /** + * Comparator method: incorrectly declared - non-boolean return type/value. + * + * @param self $other Object to compare. + * + * @return int + */ + public function equalsNonBooleanReturnType( self $other ) { + if ( $this->value === $other->value ) { + return 0; + } + + if ( $this->value > $other->value ) { + return 1; + } + + return -1; + } } From 2dfaea9bc7aaf21ef55ea7996a33f73b2f8625a4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 19 Jun 2021 15:40:02 +0200 Subject: [PATCH 38/39] Autoloader: minor simplification The path building doesn't really need to use `DIRECTORY_SEPARATOR` as Windows will handle paths with forward slashes correctly anyhow. --- phpunitpolyfills-autoload.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 318ab6a..1a66453 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -25,12 +25,7 @@ public static function load( $className ) { * exist in PHP natively. */ if ( $className === 'Error' || $className === 'TypeError' ) { - $file = \realpath( - __DIR__ . \DIRECTORY_SEPARATOR - . 'src' . \DIRECTORY_SEPARATOR - . 'Exceptions' . \DIRECTORY_SEPARATOR - . $className . '.php' - ); + $file = \realpath( __DIR__ . '/src/Exceptions/' . $className . '.php' ); if ( \file_exists( $file ) === true ) { require_once $file; @@ -119,11 +114,7 @@ public static function load( $className ) { * - Yoast\PHPUnitPolyfills\TestListeners\TestListenerSnakeCaseMethods */ default: - $file = \realpath( - __DIR__ . \DIRECTORY_SEPARATOR - . 'src' . \DIRECTORY_SEPARATOR - . \strtr( \substr( $className, 23 ), '\\', \DIRECTORY_SEPARATOR ) . '.php' - ); + $file = \realpath( __DIR__ . '/src/' . \strtr( \substr( $className, 23 ), '\\', '/' ) . '.php' ); if ( \file_exists( $file ) === true ) { require_once $file; From 8982a8928750b24e40d7f9700d514928edd306d5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 4 Dec 2020 09:52:26 +0100 Subject: [PATCH 39/39] Changelog for the release of version 1.0.0 --- CHANGELOG.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcdd0f7..dc7e44c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,38 @@ This projects adheres to [Keep a CHANGELOG](http://keepachangelog.com/) and uses _Nothing yet._ +## [1.0.0] - 2021-06-21 + +### Added +* `Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource` trait to polyfill the `Assert::assertIsClosedResource()` and `Assert::assertIsNotClosedResource()` methods as introduced in PHPUnit 9.3.0. PR [#27]. +* `Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals` trait to polyfill the `Assert::assertObjectEquals()` method as introduced in PHPUnit 9.4.0. PR [#38]. + The behaviour of the polyfill closely matches the PHPUnit native implementation, but is not 100% the same. + Most notably, the polyfill will check the type of the returned value from the comparator method instead of the enforcing a return type declaration of the comparator method. +* `Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations` trait to polyfill the `Assert::equalToCanonicalizing()`, `Assert::equalToIgnoringCase()` and `Assert::equalToWithDelta()` methods as introduced in PHPUnit 9.0.0. PR [#28], props [Marc Siegrist]. +* Polyfills for the PHP native `Error` and `TypeError` classes as introduced in PHP 7.0. PR [#36]. +* README: FAQ section covering functionality removed from PHPUnit and usage with a Phar. + +### Changed +* The minimum supported PHP version has been lowered to PHP 5.4 (was 5.5). PR [#19]. +* `XTestCase`: the visibility of the `setUpFixtures()` and the `tearDownFixtures()` methods has been changed to `protected` (was `public`). Issue [#10], PR [#20], props [Mark Baker] for reporting. +* README: re-ordered the sections and various other improvements. +* Initial preparation for PHPUnit 10.0 compatibility. +* General housekeeping. + +### Fixed +* Issue [#17] via PR [#18] - `AssertStringContainString`: PHPUnit < 6.4.2 would throw a _"mb_strpos(): empty delimiter"_ PHP warning when the `$needle` passed was an empty string. Props [Gary Jones]. + +[#10]: https://github.com/Yoast/PHPUnit-Polyfills/issues/10 +[#17]: https://github.com/Yoast/PHPUnit-Polyfills/issues/17 +[#18]: https://github.com/Yoast/PHPUnit-Polyfills/pull/18 +[#19]: https://github.com/Yoast/PHPUnit-Polyfills/pull/19 +[#20]: https://github.com/Yoast/PHPUnit-Polyfills/pull/20 +[#27]: https://github.com/Yoast/PHPUnit-Polyfills/pull/27 +[#28]: https://github.com/Yoast/PHPUnit-Polyfills/pull/28 +[#36]: https://github.com/Yoast/PHPUnit-Polyfills/pull/36 +[#38]: https://github.com/Yoast/PHPUnit-Polyfills/pull/38 + + ## [0.2.0] - 2020-11-25 ### Added @@ -16,7 +48,7 @@ _Nothing yet._ * `Yoast\PHPUnitPolyfills\Helpers\AssertAttributeHelper` trait containing a `getProperty()` and a `getPropertyValue()` method. This is a stop-gap solution for the removal of the PHPUnit `assertAttribute*()` methods in PHPUnit 9. It is strongly recommended to refactor your tests/classes in a way that protected and private properties no longer be tested directly as they should be considered an implementation detail. - However, if for some reason the value of protected or private properties still needs to be tested, this helper can be used to get access to their value. + However, if for some reason the value of protected or private properties still needs to be tested, this helper can be used to get access to their value. * `Yoast\PHPUnitPolyfills\Polyfills\AssertNumericType` trait to polyfill the `Assert::assertFinite()`, `Assert::assertInfinite()` and `Assert::assertNan()` methods as introduced in PHPUnit 5.0.0. * `Yoast\PHPUnitPolyfills\Polyfills\ExpectException` trait to polyfill the `TestCase::expectException()`, `TestCase::expectExceptionMessage()`, `TestCase::expectExceptionCode()` and `TestCase::expectExceptionMessageRegExp()` methods, as introduced in PHPUnit 5.2.0 to replace the `Testcase::setExpectedException()` and the `Testcase::setExpectedExceptionRegExp()` method. * `Yoast\PHPUnitPolyfills\Polyfills\AssertFileDirectory` trait to polyfill the `Assert::assertIsReadable()`, `Assert::assertIsWritable()` methods and their file/directory based variations, as introduced in PHPUnit 5.6.0. @@ -35,5 +67,10 @@ Initial release. [Unreleased]: https://github.com/Yoast/PHPUnit-Polyfills/compare/main...HEAD +[1.0.0]: https://github.com/Yoast/PHPUnit-Polyfills/compare/0.2.0...1.0.0 [0.2.0]: https://github.com/Yoast/PHPUnit-Polyfills/compare/0.1.0...0.2.0 [0.1.0]: https://github.com/Yoast/PHPUnit-Polyfills/compare/e8f8b7a73737aa9a5974bd9c73d2bd8d09f69873...0.1.0 + +[Gary Jones]: https://github.com/GaryJones +[Marc Siegrist]: https://github.com/mergeMarc +[Mark Baker]: https://github.com/MarkBaker