-
-
Notifications
You must be signed in to change notification settings - Fork 379
PHP-Parser interferes with testing code that uses (back|for)ward compatibility layers for ext/tokenizer #798
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
If you use PHPUnit from a PHAR then no autoloading is used and all sourceode files of PHPUnit as well as its dependencies are loading during startup. This is to prevent problems that otherwise may occur when PHPUnit is used from a PHAR in a project that also install PHPUnit via Composer. If you install PHPUnit using Composer then regular, Composer-based autoloading is used. In theory, we could be loading PHP-Parser too early when the PHAR is used. However, since you also experience the problem when PHPUnit is installed using Composer, I do not believe this to be the case. Furthermore, the code in PHP-Parser that invokes Maybe @nikic has an idea what might be going on here. |
To clarify: by "[t]he first few times a However, I do not understand why I will leave this issue open although I do not believe there to be a problem in php-code-coverage. This looks like an issue of competing backward/forward compatibility layers, one in PHP-Parser and one in the code you're testing, Juliette, for dealing with token constants not defined in the version of PHP used. |
@sebastianbergmann Thank you for taking a look.
Correct, though I have no idea when or why
That's the whole point. How can I still test the (code coverage of the) code relating to the correct functioning of those compatibility layers in my code if the code of the test framework I use interferes ? I can understand that for visualizing the code coverage, the tokenization of a file is used to determine which lines/tokens are "covered", but I would expect that to be done after my tests have finished running, not while my tests are running, which is what is causing the interference and why I used the loaded too early in the title of the issue. |
To be honest: I do not know. I understand that this is frustrating, but your use case might be an edge case that we are not able to accomodate. I think that PHP-Parser is wrong about declaring those constants in
A sourcecode file is processed after it has been "seen" by php-code-coverage for the first time. This processing included parsing it with PHP-Parser. We need to do this to find |
For the time being, I've restricted our builds to use PHPUnit 9.2.x for code coverage, that will at least allow us to have passing builds again, so PRs can get merged. I'll keep my fingers crossed that @nikic will have additional insights once he's back from his vacation. @nikic If you do read your mail while on holiday: please enjoy your time off, this can wait until you're back. |
Hear, hear. |
@jrfnl Here is an idea you could try: configure the cache for static analysis results (either using the |
@sebastianbergmann I'd love to give that a try. Just been trying to find out more information about the options you mention in the PHPUnit documentation, but I can't seem to find anything. Are these options new for a future version ? Or have they just not been added to the documentation yet ? |
I did not get a chance yet to document these options. They were introduced in PHPUnit 9.3.4: https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.3.md#934---2020-08-10 |
I pushed (an initial version of) documentation for these options to the https://github.com/sebastianbergmann/phpunit-documentation-english. |
@sebastianbergmann Thanks for doing that! ❤️ I need to finish some other things first, but I'm hoping to be able to sit down with this again towards the end of the week. |
@jrfnl Did you get a chance to try the proposed solution/workaround? |
@sebastianbergmann Sorry, I haven't gotten round to it yet. For now, I'm using PHPUnit 9.2 with PHP 7.4 for the code coverage builds, but once PHP 8 comes out I will need to have it working with PHPUnit 9.3+, so it is definitely on my (overly full) to-do list to experiment with the caching options you suggested, just not top-of-the-list priority at this very moment. |
No feedback, closing. |
@sebastianbergmann Sorry about this. It's still on my todo list, but hasn't reached the top yet. I'm going to try and reserve time to look into this more deeply in a week or two. |
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. The problem with this is that PHP-Parser backfills new PHP native tokens, while the code in this repo contains logic which checks for the existence of tokens using `defined()`. That logic breaks with PHP-Parser in play. An external PHPCS standard may also be using PHP-Parser, but as Parser doesn't fix the tokenization of files, the tokens polyfilled by PHP-Parser would still cause interference. To work round this, I'm introducing a `TokenHelper::tokenExists()` method which can distinguish tokens defined by PHP and PHPCS from tokens defined by PHP-Parser. This should solve the issue in most cases, though in edge-cases, PHP-Parser could still prevent the PHPCS native `Tokenizer\PHP` class from providing polyfills when it should. For external standards which want to use PHPUnit 9.3+ for calculating code coverage, warming the cache is also still recommended, though it shouldn't necessarily be _needed_ anymore if the standard implements the use of the `TokenHelper::tokenExists()` method in all the appropriate places. Includes unit tests. Refs: * sebastianbergmann/php-code-coverage#798 * https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Lexer.php Note: while `Helper` would have been the more intuitive name for the class, as there is also a `BackCompat\Helper` class, this would mean use aliases would need to be used a lot, so instead, I've elected to name the class `TokenHelper`.
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. The problem with this is that PHP-Parser backfills new PHP native tokens, while the code in this repo contains logic which checks for the existence of tokens using `defined()`. That logic breaks with PHP-Parser in play. An external PHPCS standard may also be using PHP-Parser, but as Parser doesn't fix the tokenization of files, the tokens polyfilled by PHP-Parser would still cause interference. To work round this, I'm introducing a `TokenHelper::tokenExists()` method which can distinguish tokens defined by PHP and PHPCS from tokens defined by PHP-Parser. This should solve the issue in most cases, though in edge-cases, PHP-Parser could still prevent the PHPCS native `Tokenizer\PHP` class from providing polyfills when it should. For external standards which want to use PHPUnit 9.3+ for calculating code coverage, warming the cache is also still recommended, though it shouldn't necessarily be _needed_ anymore if the standard implements the use of the `TokenHelper::tokenExists()` method in all the appropriate places. Includes unit tests. Refs: * sebastianbergmann/php-code-coverage#798 * https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Lexer.php Note: while `Helper` would have been the more intuitive name for the class, as there is also a `BackCompat\Helper` class, this would mean use aliases would need to be used a lot, so instead, I've elected to name the class `TokenHelper`.
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. Until now, this prevented this repo from using PHPUnit 9.3 (see PR 304 for more details). Now the issues related to that have been fixed, the PHPUnit version requirements can be widened again. This then allows to: * Remove the tweaking of the PHPUnit version in `test` jobs in GH Actions. * Run code coverage on the latest PHP version (8.1). Just in case there would still be residual issues, I'm also implementing cache warming in the code coverage workflow as recommended by Sebastian. Notes regarding cache-warming * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. If, at some point in the future, two config files would be used (one for PHPUnit < 9.3 and one for PHPUnit >= 9.3), the cache directory could be added to the config instead of passing it on the command-line. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. Until now, this prevented this repo from using PHPUnit 9.3 (see PR 304 for more details). Now the issues related to that have been fixed, the PHPUnit version requirements can be widened again. This then allows to: * Remove the tweaking of the PHPUnit version in `test` jobs in GH Actions. * Run code coverage on the latest PHP version (8.1). Just in case there would still be residual issues, I'm also implementing cache warming in the code coverage workflow as recommended by Sebastian. Notes regarding cache-warming: * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. If, at some point in the future, two config files would be used (one for PHPUnit < 9.3 and one for PHPUnit >= 9.3), the cache directory could be added to the config instead of passing it on the command-line. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. Until now, this prevented this repo from using PHPUnit 9.3 (see PR 304 for more details). Now the issues related to that have been fixed, the PHPUnit version requirements can be widened again. This then allows to: * Remove the tweaking of the PHPUnit version in `test` jobs in GH Actions. * Run code coverage on the latest PHP version (8.1). Just in case there would still be residual issues, I'm also implementing cache warming in the code coverage workflow as recommended by Sebastian. Notes regarding cache-warming: * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. If, at some point in the future, two config files would be used (one for PHPUnit < 9.3 and one for PHPUnit >= 9.3), the cache directory could be added to the config instead of passing it on the command-line. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4
Okay, so it took a little longer still before I had time to properly work on this and run tests with various scenarios, but I finally got round to it. I can confirm that using the In case anyone else comes across this issue and is looking for a code sample on how to implement this in a repo which needs to support multiple versions of PHPUnit, here is a code sample for a working GH Actions workflow: coverage:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['5.6', '7.2', '8.1']
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
- name: Install Composer dependencies
uses: "ramsey/composer-install@v2"
- name: Grab PHPUnit version
id: phpunit_version
run: echo ::set-output name=VERSION::$(vendor/bin/phpunit --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+\.[0-9]+')
- name: "Warm the PHPUnit cache (PHPUnit 9.3+)"
if: ${{ steps.phpunit_version.outputs.VERSION >= '9.3' }}
run: vendor/bin/phpunit --coverage-cache ./build/phpunit-cache --warm-coverage-cache
- name: "Run the unit tests with code coverage (PHPUnit < 9.3)"
if: ${{ steps.phpunit_version.outputs.VERSION < '9.3' }}
run: vendor/bin/phpunit
- name: "Run the unit tests with code coverage (PHPUnit 9.3+)"
if: ${{ steps.phpunit_version.outputs.VERSION >= '9.3' }}
run: vendor/bin/phpunit --coverage-cache ./build/phpunit-cache Notes:
|
Thank you, @jrfnl. |
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. This prevented this repo from using PHPUnit 9.3 (see PR PHPCSStandards/PHPCSUtils 304 for more details) as PHP Parser also backfills tokens and that interfered with the sniffs. Now support for PHPCS < 3.7.1 has been dropped though, all tokens should exist in PHPCS. And if in the future we would need to do "does this token exist ?" checks again, PHPCSUtils will provide a `TokenHelper::tokenExists()` method (as of version 1.0.0-alpha4), which will allow us to distinguish between tokens backfilled by PHPCS and tokens backfilled by PHP-Parser. PHPUnit itself has also added a new feature in PHPUnit 9.3.4 to warm the code cache ahead of running tests recording code coverage. Using that feature should prevent the token interference completely, but implementing it was a bit of an experiment as the feature is barely documented. Either way, the combination of the above, allows us to: * Remove the tweaking of the PHPUnit version in `test` jobs in GH Actions. * Run code coverage on the latest PHP version (8.1). Notes regarding cache-warming: * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used by PHPCompatibility needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. If, at some point in the future, two config files would be used (one for PHPUnit < 9.3 and one for PHPUnit >= 9.3), the cache directory could be added to the config instead of passing it on the command-line. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. PHP Parser also polyfills tokens and that can interfere with the sniffs, but also with the code coverage runs. PHPUnit has also added a new feature in PHPUnit 9.3.4 to warm the code cache ahead of running tests recording code coverage. Using that feature should prevent the token interference. Notes regarding cache-warming: * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. PHP Parser also polyfills tokens and that can interfere with the sniffs, but also with the code coverage runs. PHPUnit has also added a new feature in PHPUnit 9.3.4 to warm the code cache ahead of running tests recording code coverage. Using that feature should prevent the token interference. Notes regarding cache-warming: * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. PHP Parser also polyfills tokens and that can interfere with the sniffs, but also with the code coverage runs. PHPUnit has also added a new feature in PHPUnit 9.3.4 to warm the code cache ahead of running tests recording code coverage. Using that feature should prevent the token interference. Notes regarding cache-warming: * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. PHP Parser also polyfills tokens and that can interfere with the sniffs, but only in code coverage runs. In PHPUnit 9.3.4 a new feature was added to warm the code cache ahead of running tests recording code coverage. Using that feature should prevent the token interference. So far, I've not seen problems with this for this codebase, but better safe than sorry, so I'm going to enable cache warming now anyway. Notes regarding cache-warming: * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4 Also note that PHP-Parser 5.0 was released a couple of days ago and when used for code coverage runs on PHP 8.0 (as used for the 4.0 branch), this causes problems. When I merge this commit up to 4.0, I will adjust the GH Actions script for the 4.0 branch to use PHP 8.1 instead of PHP 8.0 to avoid this issue. The issue has been reported upstream: sebastianbergmann/php-code-coverage#1025
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. PHP Parser also polyfills tokens and that can interfere with the sniffs, but only in code coverage runs. In PHPUnit 9.3.4 a new feature was added to warm the code cache ahead of running tests recording code coverage. Using that feature should prevent the token interference. So far, I've not seen problems with this for this codebase, but better safe than sorry, so I'm going to enable cache warming now anyway. Notes regarding cache-warming: * The `--coverage-cache` and `--warm-coverage-cache` options are available since PHPUnit 9.3.4 and if these are used on older PHPUnit versions, PHPUnit will error out with an "unrecognized CLI argument" error. * In other words: these options can only be used with PHPUnit 9.3+, which is why the PHPUnit version is checked and remembered and subsequently used in the conditions. * Also: running PHPUnit with the `--warm-coverage-cache` option _does not run the tests_. It literally only warms the cache, which is why this is implemented as a separate step in the workflow. * The cache directory can be configured in the `phpunit.xml[.dist]` file, but only when using the PHPUnit 9.3+ `coverage` XML tag. As the PHPUnit config used needs to stay cross-version compatible with older PHPUnit versions, the CLI option is used for now. Refs: * sebastianbergmann/php-code-coverage#798 (comment) * sebastianbergmann/phpunit@9.3.3...9.3.4 Also note that PHP-Parser 5.0 was released a couple of days ago and when used for code coverage runs on PHP 8.0 (as used for the 4.0 branch), this causes problems. When I merge this commit up to 4.0, I will adjust the GH Actions script for the 4.0 branch to use PHP 8.1 instead of PHP 8.0 to avoid this issue. The issue has been reported upstream: sebastianbergmann/php-code-coverage#1025
When running PHPUnit with code coverage enabled the tests for PHPCSUtils and PHPCompatibility have started to semi-randomly break due to PHPUnit's use of PHP-Parser.
PHP-Parser breaks our tests as it interferes with globally defined PHP native constants by backfilling them.
The first few times a
defined()
for a PHP native token constant is called, the response is correct and as expected, but the third time that samedefined()
is called, the response will betrue
, even if the token constant is not defined in our stack and all subsequent tests relying on thedefined()
logic working will subsequently fail.Related code in PHP-Parser:
Could it be that PHP Parser is loaded too early to prevent interference ?
For an extensive description of the problem, please see: https://gist.github.com/jrfnl/c6fad49bb7580b7b4371af3c68b122f7
Note: while this isn't a problem yet for PHP_CodeSniffer itself at this time as they don't support PHPUnit 9 yet, it will be in the near future. PHPCS itself should also be able to rely on whether or not tokens are defined in PHP to determine how to adjust a tokenized file (if necessary). So, the PHP native tokens being defined by PHP Parser will break coverage builds for PHPCS as well.
With graceful thanks to @derickr for helping me debug this as I initially though it was related to the use of the latest Xdebug version.
The text was updated successfully, but these errors were encountered: