diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 33392505b..0fcb5a786 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1 +1 @@ -Please refer to [https://github.com/sebastianbergmann/phpunit/blob/master/CONTRIBUTING.md](https://github.com/sebastianbergmann/phpunit/blob/master/.github/CONTRIBUTING.md) for details on how to contribute to this project. +Please refer to [https://github.com/sebastianbergmann/phpunit/blob/main/CONTRIBUTING.md](https://github.com/sebastianbergmann/phpunit/blob/main/.github/CONTRIBUTING.md) for details on how to contribute to this project. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 81e8f8b4a..5ca75c07d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,7 @@ | --------------------------| --------------- | php-code-coverage version | x.y.z | PHP version | x.y.z -| Driver | PCOV / Xdebug / PHPDBG +| Driver | PCOV / Xdebug | PCOV version (if used) | x.y.z | Xdebug version (if used) | x.y.z | Installation Method | Composer / PHPUnit PHAR diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b07a445e5..23815f27b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: name: CI env: - COMPOSER_ROOT_VERSION: "9.2-dev" + COMPOSER_ROOT_VERSION: "10.1-dev" jobs: coding-guidelines: @@ -23,7 +23,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: 8.2 - extensions: :apcu, :imagick + extensions: none, iconv, json, phar, tokenizer coverage: none tools: none @@ -43,7 +43,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: 8.2 - extensions: :apcu, :imagick + extensions: none, dom, iconv, mbstring, opcache, simplexml, tokenizer, xml, xmlwriter coverage: none tools: none @@ -59,7 +59,7 @@ jobs: runs-on: ${{ matrix.os }} env: - PHP_EXTENSIONS: dom, json, libxml, mbstring, pdo_sqlite, soap, xml, xmlwriter, :apcu, :imagick + PHP_EXTENSIONS: none, dom, json, libxml, mbstring, openssl, pdo_sqlite, soap, tokenizer, xml, xmlwriter PHP_INI_VALUES: memory_limit=-1, assert.exception=1, zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On strategy: @@ -70,9 +70,6 @@ jobs: - windows-latest php-version: - - "7.3" - - "7.4" - - "8.0" - "8.1" - "8.2" - "8.3" @@ -80,22 +77,8 @@ jobs: coverage-driver: - "pcov" - - "xdebug" - "xdebug3" - exclude: - - php-version: "8.0" - coverage-driver: "xdebug" - - - php-version: "8.1" - coverage-driver: "xdebug" - - - php-version: "8.2" - coverage-driver: "xdebug" - - - php-version: "8.3" - coverage-driver: "xdebug" - steps: - name: Configure Git to avoid issues with line endings if: matrix.os == 'windows-latest' diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5156c5caa..0c7e1dbd9 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -95,6 +95,7 @@ 'function_declaration' => true, 'function_to_constant' => true, 'function_typehint_space' => true, + 'get_class_to_class_keyword' => true, 'global_namespace_import' => [ 'import_classes' => true, 'import_constants' => true, diff --git a/.psalm/baseline.xml b/.psalm/baseline.xml index 787f31557..983e1f149 100644 --- a/.psalm/baseline.xml +++ b/.psalm/baseline.xml @@ -1,31 +1,37 @@ - + - $unit[0] + $tmp[0] - - - - - Test::LARGE - Test::MEDIUM - Test::SMALL - - - getName - getName - getSize - isLarge - isMedium - isSubclassOf($parentClass)]]> is_array($linesToBeCovered) - - include_once $uncoveredFile - + + + + $functionData + $functionData + + + lineCoverage[$file][$line]]]> + + + functionCoverage]]> + functionCoverage]]> + functionCoverage]]> + functionCoverage]]> + functionCoverage]]> + functionCoverage]]> + functionCoverage]]> + functionCoverage]]> + functionCoverage]]> + functionCoverage]]> + lineCoverage]]> + lineCoverage]]> + lineCoverage]]> + @@ -39,14 +45,7 @@ waiting() - - - XDEBUG_CC_BRANCH_CHECK - XDEBUG_CC_DEAD_CODE - XDEBUG_CC_UNUSED - - - + XDEBUG_CC_BRANCH_CHECK XDEBUG_CC_DEAD_CODE @@ -56,46 +55,26 @@ + + $directory + $directory + + + excludeFile + $files - - - id === null]]> - parent === null]]> - parent === null]]> - pathAsArray === null]]> - pathAsString === null]]> - - - $parent - - - $id - $pathAsArray - $pathAsString - - - - classes === null]]> - functions === null]]> - traits === null]]> - IteratorAggregate - - $classes - $functions - $traits - directories]]> files]]> @@ -105,27 +84,6 @@ files[] = &$this->children[count($this->children) - 1]]]> - - - numClasses === null]]> - numMethods === null]]> - numTestedFunctions === null]]> - numTestedMethods === null]]> - numTraits === null]]> - - - codeUnitsByLine]]> - codeUnitsByLine]]> - codeUnitsByLine]]> - - - $numClasses - $numMethods - $numTestedFunctions - $numTestedMethods - $numTraits - - nodes[$this->position]]]> @@ -136,21 +94,25 @@ $position - - - - functionCoverage[$file][$functionName]['branches'][$branchId]['hit']]]> - - - functionCoverage[$file][$functionName]['branches']]]> - functionCoverage[$file][$functionName]['branches'][$branchId]]]> - functionCoverage[$file][$functionName]['branches'][$branchId]['hit']]]> - - - functionCoverage[$file][$functionName]['branches']]]> - + + children()]]> + + + $node->id(), + 'full_path' => $node->pathAsString(), + 'path_to_root' => $this->pathToRoot($node), + 'breadcrumbs' => $this->breadcrumbs($node), + 'date' => $this->date, + 'version' => $this->version, + 'runtime' => $this->runtimeString(), + 'generator' => $this->generator, + 'low_upper_bound' => $this->thresholds->lowUpperBound(), + 'high_lower_bound' => $this->thresholds->highLowerBound(), + ]]]> + @@ -169,15 +131,14 @@ - - BaseTestRunner::STATUS_ERROR - BaseTestRunner::STATUS_FAILURE - BaseTestRunner::STATUS_INCOMPLETE - BaseTestRunner::STATUS_PASSED - BaseTestRunner::STATUS_RISKY - BaseTestRunner::STATUS_SKIPPED - BaseTestRunner::STATUS_WARNING - + + $lineNumber, + 'lineContent' => $lineContent, + 'class' => $class, + 'popover' => $popover, + ]]]> + $tokens[$j - 1] @@ -237,9 +198,6 @@ - - $codeMap - createElementNS @@ -295,9 +253,6 @@ toString - $node instanceof Function_ - $node instanceof Function_ - $type instanceof ComplexType namespacedName instanceof Name)]]> namespacedName instanceof Name)]]> namespacedName instanceof Name)]]> @@ -310,9 +265,4 @@ traits]]> - - - self::$version === null - - diff --git a/ChangeLog-10.1.md b/ChangeLog-10.1.md new file mode 100644 index 000000000..fdee9eb79 --- /dev/null +++ b/ChangeLog-10.1.md @@ -0,0 +1,48 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [10.1.4] - 2023-08-31 + +### Fixed + +* Exceptions of type `SebastianBergmann\Template\Exception` are now properly handled + +## [10.1.3] - 2023-07-26 + +### Changed + +* The result of `CodeCoverage::getReport()` is now cached + +### Fixed + +* Static analysis cache keys do not include configuration settings that affect source code parsing +* The Clover, Cobertura, Crap4j, and PHP report writers no longer create a `php:` directory when they should write to `php://stdout`, for instance + +## [10.1.2] - 2023-05-22 + +### Fixed + +* [#998](https://github.com/sebastianbergmann/php-code-coverage/pull/998): Group Use Declarations are not handled properly + +## [10.1.1] - 2023-04-17 + +### Fixed + +* [#994](https://github.com/sebastianbergmann/php-code-coverage/issues/994): Argument `$linesToBeIgnored` of `CodeCoverage::stop()` has no effect for files that are not executed at all + +## [10.1.0] - 2023-04-13 + +### Added + +* [#982](https://github.com/sebastianbergmann/php-code-coverage/issues/982): Add option to ignore lines from code coverage + +### Deprecated + +* The `SebastianBergmann\CodeCoverage\Filter::includeDirectory()`, `SebastianBergmann\CodeCoverage\Filter::excludeDirectory()`, and `SebastianBergmann\CodeCoverage\Filter::excludeFile()` methods are now deprecated + +[10.1.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/10.1.3...10.1.4 +[10.1.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/10.1.2...10.1.3 +[10.1.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/10.1.1...10.1.2 +[10.1.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/10.1.0...10.1.1 +[10.1.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/10.0.2...10.1.0 diff --git a/ChangeLog-9.2.md b/ChangeLog-9.2.md deleted file mode 100644 index 880be3be8..000000000 --- a/ChangeLog-9.2.md +++ /dev/null @@ -1,549 +0,0 @@ -# ChangeLog - -All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. - -## [9.2.27] - 2023-07-26 - -### Changed - -* The result of `CodeCoverage::getReport()` is now cached - -### Fixed - -* Static analysis cache keys do not include configuration settings that affect source code parsing -* The Clover, Cobertura, Crap4j, and PHP report writers no longer create a `php:` directory when they should write to `php://stdout`, for instance - -## [9.2.26] - 2023-03-06 - -### Changed - -* Improved the legend on the file pages of the HTML code coverage report - -## [9.2.25] - 2023-02-25 - -### Fixed - -* [#981](https://github.com/sebastianbergmann/php-code-coverage/issues/981): `CodeUnitFindingVisitor` does not support DNF types - -## [9.2.24] - 2023-01-26 - -### Changed - -* [#970](https://github.com/sebastianbergmann/php-code-coverage/issues/970): CSS and JavaScript assets are now referenced using `?v=%s` URLs in the HTML report to avoid cache issues - -## [9.2.23] - 2022-12-28 - -### Fixed - -* [#971](https://github.com/sebastianbergmann/php-code-coverage/issues/971): PHP report does not handle serialized code coverage data larger than 2 GB -* [#974](https://github.com/sebastianbergmann/php-code-coverage/issues/974): Executable line analysis fails for declarations with enumerations and unions - -## [9.2.22] - 2022-12-18 - -### Fixed - -* [#969](https://github.com/sebastianbergmann/php-code-coverage/pull/969): Fixed identifying line with `throw` as executable - -## [9.2.21] - 2022-12-14 - -### Changed - -* [#964](https://github.com/sebastianbergmann/php-code-coverage/pull/964): Changed how executable lines are identified - -## [9.2.20] - 2022-12-13 - -### Fixed - -* [#960](https://github.com/sebastianbergmann/php-code-coverage/issues/960): New body font-size is way too big - -## [9.2.19] - 2022-11-18 - -### Fixed - -* [#949](https://github.com/sebastianbergmann/php-code-coverage/pull/949): Various issues related to identifying executable lines - -### Changed - -* Tweaked CSS for HTML report -* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.6.2 and jQuery 3.6.1 - -## [9.2.18] - 2022-10-27 - -### Fixed - -* [#935](https://github.com/sebastianbergmann/php-code-coverage/pull/935): Cobertura package name attribute is always empty -* [#946](https://github.com/sebastianbergmann/php-code-coverage/issues/946): `return` with multiline constant expression must only contain the last line - -## [9.2.17] - 2022-08-30 - -### Changed - -* [#928](https://github.com/sebastianbergmann/php-code-coverage/pull/928): Avoid unnecessary `is_file()` calls -* [#931](https://github.com/sebastianbergmann/php-code-coverage/pull/931): Use MD5 instead of CRC32 for static analysis cache file identifier - -### Fixed - -* [#926](https://github.com/sebastianbergmann/php-code-coverage/pull/926): Static Analysis cache does not work with `open_basedir` - -## [9.2.16] - 2022-08-20 - -### Fixed - -* [#926](https://github.com/sebastianbergmann/php-code-coverage/issues/926): File view has wrong colouring for the first column - -## [9.2.15] - 2022-03-07 - -### Fixed - -* [#885](https://github.com/sebastianbergmann/php-code-coverage/issues/885): Files that have only `\r` (CR, 0x0d) EOL characters are not handled correctly -* [#907](https://github.com/sebastianbergmann/php-code-coverage/issues/907): Line with only `return [` is not recognized as executable - -## [9.2.14] - 2022-02-28 - -### Fixed - -* [#904](https://github.com/sebastianbergmann/php-code-coverage/issues/904): Lines of code containing the `match` keyword were not recognized as executable correctly -* [#905](https://github.com/sebastianbergmann/php-code-coverage/issues/905): Lines of code in constructors were not recognized as executable correctly when constructor property promotion is used - -## [9.2.13] - 2022-02-23 - -### Changed - -* The contents of the static analysis sourcecode files is now used to generate the static analysis cache version identifier - -### Fixed - -* Reverted rename of `SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData` to `SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData` (this class is marked as `@internal` and not covered by the backward compatibility promise, but it is (still) used directly by PHPUnit) -* Reverted rename of `SebastianBergmann\CodeCoverage\RawCodeCoverageData` to `SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData` (this class is marked as `@internal` and not covered by the backward compatibility promise, but it is (still) used directly by PHPUnit) -* The `ArrayDim`, `Cast`, and `MethodCall` nodes are now considered when determining whether a line of code is executable or not - -## [9.2.12] - 2022-02-23 [YANKED] - -### Changed - -* [#898](https://github.com/sebastianbergmann/php-code-coverage/pull/898): Use content hash instead of `filemtime()` to determine cache hit/miss - -### Fixed - -* [#736](https://github.com/sebastianbergmann/php-code-coverage/issues/736): HTML report generator allows invalid values for low upper bound and high lower bound -* [#854](https://github.com/sebastianbergmann/php-code-coverage/issues/854): "Class Coverage Distribution" and "Class Complexity" graphs are not displayed at full width -* [#897](https://github.com/sebastianbergmann/php-code-coverage/issues/897): `declare(strict_types=1)` marked as uncovered - -## [9.2.11] - 2022-02-18 - -### Changed - -* `CoveredFileAnalyser` and `UncoveredFileAnalyser` have been combined to `FileAnalyser` -* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.6.1, jQuery 3.6.0, and popper.js 1.16.1 - -### Fixed - -* [#889](https://github.com/sebastianbergmann/php-code-coverage/issues/889): Code Coverage depends on autoload order - -## [9.2.10] - 2021-12-05 - -### Fixed - -* [#887](https://github.com/sebastianbergmann/php-code-coverage/issues/887): Document return type of `CodeUnitFindingVisitor::enterNode()` so that Symfony's DebugClassLoader does not trigger a deprecation warning - -## [9.2.9] - 2021-11-19 - -### Fixed - -* [#882](https://github.com/sebastianbergmann/php-code-coverage/issues/882): PHPUnit 9.2.8 has wrong version number - -## [9.2.8] - 2021-10-30 - -### Fixed - -* [#866](https://github.com/sebastianbergmann/php-code-coverage/issues/866): `CodeUnitFindingVisitor` does not handle `enum` type introduced in PHP 8.1 -* [#868](https://github.com/sebastianbergmann/php-code-coverage/pull/868): Uncovered files should be ignored unless requested -* [#876](https://github.com/sebastianbergmann/php-code-coverage/issues/876): PCOV driver causes 2x slowdown after upgrade to PHPUnit 9.5 - -## [9.2.7] - 2021-09-17 - -### Fixed - -* [#860](https://github.com/sebastianbergmann/php-code-coverage/pull/860): Empty value for `XDEBUG_MODE` environment variable is not handled correctly - -## [9.2.6] - 2021-03-28 - -### Fixed - -* [#846](https://github.com/sebastianbergmann/php-code-coverage/issues/846): Method name should not appear in the method signature attribute of Cobertura XML - -## [9.2.5] - 2020-11-28 - -### Fixed - -* [#831](https://github.com/sebastianbergmann/php-code-coverage/issues/831): Files that do not contain a newline are not handled correctly - -## [9.2.4] - 2020-11-27 - -### Added - -* [#834](https://github.com/sebastianbergmann/php-code-coverage/issues/834): Support `XDEBUG_MODE` environment variable - -## [9.2.3] - 2020-10-30 - -### Changed - -* Bumped required version of `nikic/php-parser` - -## [9.2.2] - 2020-10-28 - -### Fixed - -* [#820](https://github.com/sebastianbergmann/php-code-coverage/issues/820): Hidden dependency on PHPUnit - -## [9.2.1] - 2020-10-26 - -### Fixed - -* `SebastianBergmann\CodeCoverage\Exception` now correctly extends `\Throwable` - -## [9.2.0] - 2020-10-02 - -### Added - -* [#812](https://github.com/sebastianbergmann/php-code-coverage/pull/812): Support for Cobertura XML report format - -### Changed - -* Reduced the number of I/O operations performed by the static analysis cache - -## [9.1.11] - 2020-09-19 - -### Fixed - -* [#811](https://github.com/sebastianbergmann/php-code-coverage/issues/811): `T_FN` constant is used on PHP 7.3 where it is not available - -## [9.1.10] - 2020-09-18 - -### Added - -* `SebastianBergmann\CodeCoverage\Driver\Selector::forLineCoverage()` and `SebastianBergmann\CodeCoverage\Driver\Selector::forLineAndPathCoverage()` have been added - -### Fixed - -* [#810](https://github.com/sebastianbergmann/php-code-coverage/issues/810): `SebastianBergmann\CodeCoverage\Driver\Driver::forLineCoverage()` and `SebastianBergmann\CodeCoverage\Driver\Driver::forLineAndPathCoverage()` are marked as internal - -### Removed - -* `SebastianBergmann\CodeCoverage\Driver\Driver::forLineCoverage()` and `SebastianBergmann\CodeCoverage\Driver\Driver::forLineAndPathCoverage()` are now deprecated - -## [9.1.9] - 2020-09-15 - -### Fixed - -* [#808](https://github.com/sebastianbergmann/php-code-coverage/issues/808): `PHP Warning: Use of undefined constant T_MATCH` - -## [9.1.8] - 2020-09-07 - -### Changed - -* [#800](https://github.com/sebastianbergmann/php-code-coverage/pull/800): All files on the inclusion list are no longer loaded when `SebastianBergmann\CodeCoverage::start()` is called for the first time and `processUncoveredFiles` is set to `true` - -### Fixed - -* [#799](https://github.com/sebastianbergmann/php-code-coverage/issues/799): Uncovered new line at end of file - -## [9.1.7] - 2020-09-03 - -### Fixed - -* Fixed regressions introduced in versions 9.1.5 and 9.1.6 - -## [9.1.6] - 2020-08-31 - -### Fixed - -* [#799](https://github.com/sebastianbergmann/php-code-coverage/issues/799): Uncovered new line at end of file -* [#803](https://github.com/sebastianbergmann/php-code-coverage/issues/803): HTML report does not sort directories and files anymore - -## [9.1.5] - 2020-08-27 - -### Changed - -* [#800](https://github.com/sebastianbergmann/php-code-coverage/pull/800): All files on the inclusion list are no longer loaded when `SebastianBergmann\CodeCoverage::start()` is called for the first time and `processUncoveredFiles` is set to `true` - -### Fixed - -* [#797](https://github.com/sebastianbergmann/php-code-coverage/pull/797): Class name is wrongly removed from namespace name - -## [9.1.4] - 2020-08-13 - -### Fixed - -* [#793](https://github.com/sebastianbergmann/php-code-coverage/issues/793): Lines with `::class` constant are not covered - -## [9.1.3] - 2020-08-10 - -### Changed - -* Changed PHP-Parser usage to parse sourcecode according to the PHP version we are currently running on instead of using emulative lexing - -## [9.1.2] - 2020-08-10 - -### Fixed - -* [#791](https://github.com/sebastianbergmann/php-code-coverage/pull/791): Cache Warmer does not warm all caches - -## [9.1.1] - 2020-08-10 - -### Added - -* Added `SebastianBergmann\CodeCoverage::cacheDirectory()` method for querying where the cache writes its files - -## [9.1.0] - 2020-08-10 - -### Added - -* Implemented a persistent cache for information gathered using PHP-Parser based static analysis (hereinafter referred to as "cache") -* Added `SebastianBergmann\CodeCoverage::cacheStaticAnalysis(string $cacheDirectory)` method for enabling the cache; it will write its files to `$directory` -* Added `SebastianBergmann\CodeCoverage::doNotCacheStaticAnalysis` method for disabling the cache -* Added `SebastianBergmann\CodeCoverage::cachesStaticAnalysis()` method for querying whether the cache is enabled -* Added `SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer::warmCache()` method for warming the cache - -## [9.0.0] - 2020-08-07 - -### Added - -* [#761](https://github.com/sebastianbergmann/php-code-coverage/pull/761): Support for Branch Coverage and Path Coverage -* Added `SebastianBergmann\CodeCoverage\Driver\Driver::forLineCoverage()` for selecting the best available driver for line coverage -* Added `SebastianBergmann\CodeCoverage\Driver\Driver::forLineAndPathCoverage()` for selecting the best available driver for path coverage -* This component is now supported on PHP 8 -* This component now supports Xdebug 3 - -### Changed - -* [#746](https://github.com/sebastianbergmann/php-code-coverage/pull/746): Remove some ancient workarounds for very old Xdebug versions -* [#747](https://github.com/sebastianbergmann/php-code-coverage/pull/747): Use native filtering in PCOV and Xdebug drivers -* [#748](https://github.com/sebastianbergmann/php-code-coverage/pull/748): Store raw code coverage in value objects instead of arrays -* [#749](https://github.com/sebastianbergmann/php-code-coverage/pull/749): Store processed code coverage in value objects instead of arrays -* [#752](https://github.com/sebastianbergmann/php-code-coverage/pull/752): Rework how code coverage settings are propagated to the driver -* [#754](https://github.com/sebastianbergmann/php-code-coverage/pull/754): Implement collection of raw branch and path coverage -* [#755](https://github.com/sebastianbergmann/php-code-coverage/pull/755): Implement processing of raw branch and path coverage -* [#756](https://github.com/sebastianbergmann/php-code-coverage/pull/756): Improve handling of uncovered files -* `SebastianBergmann\CodeCoverage\Filter::addDirectoryToWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::includeDirectory()` -* `SebastianBergmann\CodeCoverage\Filter::addFilesToWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::includeFiles()` -* `SebastianBergmann\CodeCoverage\Filter::addFileToWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::includeFile()` -* `SebastianBergmann\CodeCoverage\Filter::removeDirectoryFromWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::excludeDirectory()` -* `SebastianBergmann\CodeCoverage\Filter::removeFileFromWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::excludeFile()` -* `SebastianBergmann\CodeCoverage\Filter::isFiltered()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::isExcluded()` -* `SebastianBergmann\CodeCoverage\Filter::getWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::files()` -* The arguments for `CodeCoverage::__construct()` are no longer optional - -### Fixed - -* [#700](https://github.com/sebastianbergmann/php-code-coverage/pull/700): Throw an exception if code coverage fails to write to disk - -### Removed - -* `SebastianBergmann\CodeCoverage\CodeCoverage::setCacheTokens()` and `SebastianBergmann\CodeCoverage\CodeCoverage::getCacheTokens()` have been removed -* `SebastianBergmann\CodeCoverage\CodeCoverage::setCheckForUnintentionallyCoveredCode()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::enableCheckForUnintentionallyCoveredCode()` or `SebastianBergmann\CodeCoverage\CodeCoverage::disableCheckForUnintentionallyCoveredCode()` instead -* `SebastianBergmann\CodeCoverage\CodeCoverage::setSubclassesExcludedFromUnintentionallyCoveredCodeCheck()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck()` instead -* `SebastianBergmann\CodeCoverage\CodeCoverage::setAddUncoveredFilesFromWhitelist()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::includeUncoveredFiles()` or `SebastianBergmann\CodeCoverage\CodeCoverage::excludeUncoveredFiles()` instead -* `SebastianBergmann\CodeCoverage\CodeCoverage::setProcessUncoveredFiles()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::processUncoveredFiles()` or `SebastianBergmann\CodeCoverage\CodeCoverage::doNotProcessUncoveredFiles()` instead -* `SebastianBergmann\CodeCoverage\CodeCoverage::setIgnoreDeprecatedCode()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::ignoreDeprecatedCode()` or `SebastianBergmann\CodeCoverage\CodeCoverage::doNotIgnoreDeprecatedCode()` instead -* `SebastianBergmann\CodeCoverage\CodeCoverage::setDisableIgnoredLines()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::enableAnnotationsForIgnoringCode()` or `SebastianBergmann\CodeCoverage\CodeCoverage::disableAnnotationsForIgnoringCode()` instead -* `SebastianBergmann\CodeCoverage\CodeCoverage::setCheckForMissingCoversAnnotation()` has been removed -* `SebastianBergmann\CodeCoverage\CodeCoverage::setCheckForUnexecutedCoveredCode()` has been removed -* `SebastianBergmann\CodeCoverage\CodeCoverage::setForceCoversAnnotation()` has been removed -* `SebastianBergmann\CodeCoverage\Filter::hasWhitelist()` has been removed, please use `SebastianBergmann\CodeCoverage\Filter::isEmpty()` instead -* `SebastianBergmann\CodeCoverage\Filter::getWhitelistedFiles()` has been removed -* `SebastianBergmann\CodeCoverage\Filter::setWhitelistedFiles()` has been removed - -## [8.0.2] - 2020-05-23 - -### Fixed - -* [#750](https://github.com/sebastianbergmann/php-code-coverage/pull/750): Inconsistent handling of namespaces -* [#751](https://github.com/sebastianbergmann/php-code-coverage/pull/751): Dead code is not highlighted correctly -* [#753](https://github.com/sebastianbergmann/php-code-coverage/issues/753): Do not use `$_SERVER['REQUEST_TIME']` because the test(ed) code might unset it - -## [8.0.1] - 2020-02-19 - -### Fixed - -* [#731](https://github.com/sebastianbergmann/php-code-coverage/pull/731): Confusing footer in the HTML report - -## [8.0.0] - 2020-02-07 - -### Fixed - -* [#721](https://github.com/sebastianbergmann/php-code-coverage/pull/721): Workaround for PHP bug [#79191](https://bugs.php.net/bug.php?id=79191) - -### Removed - -* This component is no longer supported on PHP 7.2 - -## [7.0.15] - 2021-07-26 - -### Changed - -* Bumped required version of php-token-stream - -## [7.0.14] - 2020-12-02 - -### Changed - -* [#837](https://github.com/sebastianbergmann/php-code-coverage/issues/837): Allow version 4 of php-token-stream - -## [7.0.13] - 2020-11-30 - -### Changed - -* Changed PHP version constraint in `composer.json` from `^7.2` to `>=7.2` to allow installation of this version of this library on PHP 8. However, this version of this library does not work on PHP 8. PHPUnit 8.5, which uses this version of this library, does not call into this library and instead shows a message that code coverage functionality is not available for PHPUnit 8.5 on PHP 8. - -## [7.0.12] - 2020-11-27 - -### Added - -* [#834](https://github.com/sebastianbergmann/php-code-coverage/issues/834): Support `XDEBUG_MODE` environment variable - -## [7.0.11] - 2020-11-27 - -### Added - -* Support for Xdebug 3 - -## [7.0.10] - 2019-11-20 - -### Fixed - -* [#710](https://github.com/sebastianbergmann/php-code-coverage/pull/710): Code Coverage does not work in PhpStorm - -## [7.0.9] - 2019-11-20 - -### Changed - -* [#709](https://github.com/sebastianbergmann/php-code-coverage/pull/709): Prioritize PCOV over Xdebug - -## [7.0.8] - 2019-09-17 - -### Changed - -* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.3.1, jQuery 3.4.1, and popper.js 1.15.0 - -## [7.0.7] - 2019-07-25 - -### Changed - -* Bumped required version of php-token-stream - -## [7.0.6] - 2019-07-08 - -### Changed - -* Bumped required version of php-token-stream - -## [7.0.5] - 2019-06-06 - -### Fixed - -* [#681](https://github.com/sebastianbergmann/php-code-coverage/pull/681): `use function` statements are not ignored - -## [7.0.4] - 2019-05-29 - -### Fixed - -* [#682](https://github.com/sebastianbergmann/php-code-coverage/pull/682): Code that is not executed is reported as being executed when using PCOV - -## [7.0.3] - 2019-02-26 - -### Fixed - -* [#671](https://github.com/sebastianbergmann/php-code-coverage/issues/671): `TypeError` when directory name is a number - -## [7.0.2] - 2019-02-15 - -### Changed - -* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.3.0 - -### Fixed - -* [#667](https://github.com/sebastianbergmann/php-code-coverage/pull/667): `TypeError` in PHP reporter - -## [7.0.1] - 2019-02-01 - -### Fixed - -* [#664](https://github.com/sebastianbergmann/php-code-coverage/issues/664): `TypeError` when whitelisted file does not exist - -## [7.0.0] - 2019-02-01 - -### Added - -* [#663](https://github.com/sebastianbergmann/php-code-coverage/pull/663): Support for PCOV - -### Fixed - -* [#654](https://github.com/sebastianbergmann/php-code-coverage/issues/654): HTML report fails to load assets -* [#655](https://github.com/sebastianbergmann/php-code-coverage/issues/655): Popin pops in outside of screen - -### Removed - -* This component is no longer supported on PHP 7.1 - -[9.2.27]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.26...9.2.27 -[9.2.26]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.25...9.2.26 -[9.2.25]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.24...9.2.25 -[9.2.24]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.23...9.2.24 -[9.2.23]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.22...9.2.23 -[9.2.22]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.21...9.2.22 -[9.2.21]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.20...9.2.21 -[9.2.20]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.19...9.2.20 -[9.2.19]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.18...9.2.19 -[9.2.18]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.17...9.2.18 -[9.2.17]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.16...9.2.17 -[9.2.16]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.15...9.2.16 -[9.2.15]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.14...9.2.15 -[9.2.14]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.13...9.2.14 -[9.2.13]: https://github.com/sebastianbergmann/php-code-coverage/compare/c011a0b6aaa4acd2f39b7f51fb4ad4442b6ec631...9.2.13 -[9.2.12]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.11...c011a0b6aaa4acd2f39b7f51fb4ad4442b6ec631 -[9.2.11]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.10...9.2.11 -[9.2.10]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.9...9.2.10 -[9.2.9]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.8...9.2.9 -[9.2.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.7...9.2.8 -[9.2.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.6...9.2.7 -[9.2.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.5...9.2.6 -[9.2.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.4...9.2.5 -[9.2.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.3...9.2.4 -[9.2.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.2...9.2.3 -[9.2.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.1...9.2.2 -[9.2.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.0...9.2.1 -[9.2.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.11...9.2.0 -[9.1.11]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.10...9.1.11 -[9.1.10]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.9...9.1.10 -[9.1.9]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.8...9.1.9 -[9.1.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.7...9.1.8 -[9.1.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.6...9.1.7 -[9.1.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.5...9.1.6 -[9.1.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.4...9.1.5 -[9.1.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.3...9.1.4 -[9.1.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.2...9.1.3 -[9.1.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.1...9.1.2 -[9.1.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.0...9.1.1 -[9.1.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.0.0...9.1.0 -[9.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/8.0...9.0.0 -[8.0.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/8.0.1...8.0.2 -[8.0.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/8.0.0...8.0.1 -[8.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.10...8.0.0 -[7.0.15]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.14...7.0.15 -[7.0.14]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.13...7.0.14 -[7.0.13]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.12...7.0.13 -[7.0.12]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.11...7.0.12 -[7.0.11]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.10...7.0.11 -[7.0.10]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.9...7.0.10 -[7.0.9]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.8...7.0.9 -[7.0.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.7...7.0.8 -[7.0.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.6...7.0.7 -[7.0.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.5...7.0.6 -[7.0.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.4...7.0.5 -[7.0.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.3...7.0.4 -[7.0.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.2...7.0.3 -[7.0.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.1...7.0.2 -[7.0.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.0...7.0.1 -[7.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/6.1.4...7.0.0 diff --git a/README.md b/README.md index 53ce9b338..26807a8d9 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Latest Stable Version](https://poser.pugx.org/phpunit/php-code-coverage/v/stable.png)](https://packagist.org/packages/phpunit/php-code-coverage) [![CI Status](https://github.com/sebastianbergmann/php-code-coverage/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/php-code-coverage/actions) [![Type Coverage](https://shepherd.dev/github/sebastianbergmann/php-code-coverage/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/php-code-coverage) +[![codecov](https://codecov.io/gh/sebastianbergmann/php-code-coverage/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/php-code-coverage) Provides collection, processing, and rendering functionality for PHP code coverage information. @@ -30,7 +31,13 @@ use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; $filter = new Filter; -$filter->includeDirectory('/path/to/directory'); + +$filter->includeFiles( + [ + '/path/to/file.php', + '/path/to/another_file.php', + ] +); $coverage = new CodeCoverage( (new Selector)->forLineCoverage($filter), diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/composer.json b/composer.json index 0f18174cd..92e9d5e2e 100644 --- a/composer.json +++ b/composer.json @@ -22,29 +22,29 @@ }, "config": { "platform": { - "php": "7.3.0" + "php": "8.1.0" }, "optimize-autoloader": true, "sort-packages": true }, "prefer-stable": true, "require": { - "php": ">=7.3", + "php": ">=8.1", "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", "nikic/php-parser": "^4.15", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-text-template": "^3.0", + "sebastian/code-unit-reverse-lookup": "^3.0", + "sebastian/complexity": "^3.0", + "sebastian/environment": "^6.0", + "sebastian/lines-of-code": "^2.0", + "sebastian/version": "^4.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -63,7 +63,7 @@ }, "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "10.1-dev" } } } diff --git a/phpunit.xml b/phpunit.xml index 3a11b9d57..85d8f4207 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,24 +2,21 @@ + colors="true"> tests/tests - + - src + src - + diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index bdf9c64c3..f22b5f76c 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -14,110 +14,65 @@ use function array_flip; use function array_keys; use function array_merge; +use function array_merge_recursive; use function array_unique; -use function array_values; use function count; use function explode; -use function get_class; use function is_array; +use function is_file; use function sort; -use PHPUnit\Framework\TestCase; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Test; use ReflectionClass; +use SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; use SebastianBergmann\CodeCoverage\Driver\Driver; use SebastianBergmann\CodeCoverage\Node\Builder; use SebastianBergmann\CodeCoverage\Node\Directory; use SebastianBergmann\CodeCoverage\StaticAnalysis\CachingFileAnalyser; use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; use SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser; +use SebastianBergmann\CodeCoverage\Test\TestSize\TestSize; +use SebastianBergmann\CodeCoverage\Test\TestStatus\TestStatus; use SebastianBergmann\CodeUnitReverseLookup\Wizard; /** * Provides collection functionality for PHP code coverage information. + * + * @psalm-type TestType = array{ + * size: string, + * status: string, + * } */ final class CodeCoverage { private const UNCOVERED_FILES = 'UNCOVERED_FILES'; + private readonly Driver $driver; + private readonly Filter $filter; + private readonly Wizard $wizard; + private bool $checkForUnintentionallyCoveredCode = false; + private bool $includeUncoveredFiles = true; + private bool $ignoreDeprecatedCode = false; + private ?string $currentId = null; + private ?TestSize $currentSize = null; + private ProcessedCodeCoverageData $data; + private bool $useAnnotationsForIgnoringCode = true; /** - * @var Driver - */ - private $driver; - - /** - * @var Filter - */ - private $filter; - - /** - * @var Wizard - */ - private $wizard; - - /** - * @var bool - */ - private $checkForUnintentionallyCoveredCode = false; - - /** - * @var bool - */ - private $includeUncoveredFiles = true; - - /** - * @var bool - */ - private $processUncoveredFiles = false; - - /** - * @var bool - */ - private $ignoreDeprecatedCode = false; - - /** - * @var null|PhptTestCase|string|TestCase - */ - private $currentId; - - /** - * Code coverage data. - * - * @var ProcessedCodeCoverageData - */ - private $data; - - /** - * @var bool + * @psalm-var array> */ - private $useAnnotationsForIgnoringCode = true; + private array $linesToBeIgnored = []; /** - * Test data. - * - * @var array + * @psalm-var array */ - private $tests = []; + private array $tests = []; /** * @psalm-var list */ - private $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = []; - - /** - * @var ?FileAnalyser - */ - private $analyser; - - /** - * @var ?string - */ - private $cacheDirectory; - - /** - * @var ?Directory - */ - private $cachedReport; + private array $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = []; + private ?FileAnalyser $analyser = null; + private ?string $cacheDirectory = null; + private ?Directory $cachedReport = null; public function __construct(Driver $driver, Filter $filter) { @@ -145,11 +100,20 @@ public function getReport(): Directory public function clear(): void { $this->currentId = null; + $this->currentSize = null; $this->data = new ProcessedCodeCoverageData; $this->tests = []; $this->cachedReport = null; } + /** + * @internal + */ + public function clearCache(): void + { + $this->cachedReport = null; + } + /** * Returns the filter object used. */ @@ -164,9 +128,7 @@ public function filter(): Filter public function getData(bool $raw = false): ProcessedCodeCoverageData { if (!$raw) { - if ($this->processUncoveredFiles) { - $this->processUncoveredFilesFromFilter(); - } elseif ($this->includeUncoveredFiles) { + if ($this->includeUncoveredFiles) { $this->addUncoveredFilesFromFilter(); } } @@ -183,7 +145,7 @@ public function setData(ProcessedCodeCoverageData $data): void } /** - * Returns the test data. + * @psalm-return array */ public function getTests(): array { @@ -191,25 +153,21 @@ public function getTests(): array } /** - * Sets the test data. + * @psalm-param array $tests */ public function setTests(array $tests): void { $this->tests = $tests; } - /** - * Start collection of code coverage information. - * - * @param PhptTestCase|string|TestCase $id - */ - public function start($id, bool $clear = false): void + public function start(string $id, TestSize $size = null, bool $clear = false): void { if ($clear) { $this->clear(); } - $this->currentId = $id; + $this->currentId = $id; + $this->currentSize = $size; $this->driver->start(); @@ -217,38 +175,34 @@ public function start($id, bool $clear = false): void } /** - * Stop collection of code coverage information. - * - * @param array|false $linesToBeCovered + * @psalm-param array> $linesToBeIgnored */ - public function stop(bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = []): RawCodeCoverageData + public function stop(bool $append = true, TestStatus $status = null, array|false $linesToBeCovered = [], array $linesToBeUsed = [], array $linesToBeIgnored = []): RawCodeCoverageData { - if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) { - throw new InvalidArgumentException( - '$linesToBeCovered must be an array or false' - ); - } - $data = $this->driver->stop(); - $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed); + + $this->linesToBeIgnored = array_merge_recursive( + $this->linesToBeIgnored, + $linesToBeIgnored + ); + + $this->append($data, null, $append, $status, $linesToBeCovered, $linesToBeUsed, $linesToBeIgnored); $this->currentId = null; + $this->currentSize = null; $this->cachedReport = null; return $data; } /** - * Appends code coverage data. - * - * @param PhptTestCase|string|TestCase $id - * @param array|false $linesToBeCovered + * @psalm-param array> $linesToBeIgnored * * @throws ReflectionException * @throws TestIdMissingException * @throws UnintentionallyCoveredCodeException */ - public function append(RawCodeCoverageData $rawData, $id = null, bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = []): void + public function append(RawCodeCoverageData $rawData, string $id = null, bool $append = true, TestStatus $status = null, array|false $linesToBeCovered = [], array $linesToBeUsed = [], array $linesToBeIgnored = []): void { if ($id === null) { $id = $this->currentId; @@ -260,12 +214,22 @@ public function append(RawCodeCoverageData $rawData, $id = null, bool $append = $this->cachedReport = null; + if ($status === null) { + $status = TestStatus::unknown(); + } + + $size = $this->currentSize; + + if ($size === null) { + $size = TestSize::unknown(); + } + $this->applyFilter($rawData); $this->applyExecutableLinesFilter($rawData); if ($this->useAnnotationsForIgnoringCode) { - $this->applyIgnoredLinesFilter($rawData); + $this->applyIgnoredLinesFilter($rawData, $linesToBeIgnored); } $this->data->initializeUnseenData($rawData); @@ -274,45 +238,27 @@ public function append(RawCodeCoverageData $rawData, $id = null, bool $append = return; } - if ($id !== self::UNCOVERED_FILES) { - $this->applyCoversAnnotationFilter( - $rawData, - $linesToBeCovered, - $linesToBeUsed - ); - - if (empty($rawData->lineCoverage())) { - return; - } - - $size = 'unknown'; - $status = -1; - $fromTestcase = false; - - if ($id instanceof TestCase) { - $fromTestcase = true; - $_size = $id->getSize(); + if ($id === self::UNCOVERED_FILES) { + return; + } - if ($_size === Test::SMALL) { - $size = 'small'; - } elseif ($_size === Test::MEDIUM) { - $size = 'medium'; - } elseif ($_size === Test::LARGE) { - $size = 'large'; - } + $this->applyCoversAndUsesFilter( + $rawData, + $linesToBeCovered, + $linesToBeUsed, + $size + ); - $status = $id->getStatus(); - $id = get_class($id) . '::' . $id->getName(); - } elseif ($id instanceof PhptTestCase) { - $fromTestcase = true; - $size = 'large'; - $id = $id->getName(); - } + if (empty($rawData->lineCoverage())) { + return; + } - $this->tests[$id] = ['size' => $size, 'status' => $status, 'fromTestcase' => $fromTestcase]; + $this->tests[$id] = [ + 'size' => $size->asString(), + 'status' => $status->asString(), + ]; - $this->data->markCodeAsExecutedByTestCase($id, $rawData); - } + $this->data->markCodeAsExecutedByTestCase($id, $rawData); } /** @@ -351,16 +297,6 @@ public function excludeUncoveredFiles(): void $this->includeUncoveredFiles = false; } - public function processUncoveredFiles(): void - { - $this->processUncoveredFiles = true; - } - - public function doNotProcessUncoveredFiles(): void - { - $this->processUncoveredFiles = false; - } - public function enableAnnotationsForIgnoringCode(): void { $this->useAnnotationsForIgnoringCode = true; @@ -442,14 +378,10 @@ public function detectsDeadCode(): bool } /** - * Applies the @covers annotation filtering. - * - * @param array|false $linesToBeCovered - * * @throws ReflectionException * @throws UnintentionallyCoveredCodeException */ - private function applyCoversAnnotationFilter(RawCodeCoverageData $rawData, $linesToBeCovered, array $linesToBeUsed): void + private function applyCoversAndUsesFilter(RawCodeCoverageData $rawData, array|false $linesToBeCovered, array $linesToBeUsed, TestSize $size): void { if ($linesToBeCovered === false) { $rawData->clear(); @@ -461,9 +393,7 @@ private function applyCoversAnnotationFilter(RawCodeCoverageData $rawData, $line return; } - if ($this->checkForUnintentionallyCoveredCode && - (!$this->currentId instanceof TestCase || - (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) { + if ($this->checkForUnintentionallyCoveredCode && !$size->isMedium() && !$size->isLarge()) { $this->performUnintentionallyCoveredCodeCheck($rawData, $linesToBeCovered, $linesToBeUsed); } @@ -516,13 +446,23 @@ private function applyExecutableLinesFilter(RawCodeCoverageData $data): void } } - private function applyIgnoredLinesFilter(RawCodeCoverageData $data): void + /** + * @psalm-param array> $linesToBeIgnored + */ + private function applyIgnoredLinesFilter(RawCodeCoverageData $data, array $linesToBeIgnored): void { foreach (array_keys($data->lineCoverage()) as $filename) { if (!$this->filter->isFile($filename)) { continue; } + if (isset($linesToBeIgnored[$filename])) { + $data->removeCoverageDataForLines( + $filename, + $linesToBeIgnored[$filename] + ); + } + $data->removeCoverageDataForLines( $filename, $this->analyser()->ignoredLinesFor($filename) @@ -541,39 +481,19 @@ private function addUncoveredFilesFromFilter(): void ); foreach ($uncoveredFiles as $uncoveredFile) { - if ($this->filter->isFile($uncoveredFile)) { + if (is_file($uncoveredFile)) { $this->append( RawCodeCoverageData::fromUncoveredFile( $uncoveredFile, $this->analyser() ), - self::UNCOVERED_FILES + self::UNCOVERED_FILES, + linesToBeIgnored: $this->linesToBeIgnored ); } } } - /** - * @throws UnintentionallyCoveredCodeException - */ - private function processUncoveredFilesFromFilter(): void - { - $uncoveredFiles = array_diff( - $this->filter->files(), - $this->data->coveredFiles() - ); - - $this->driver->start(); - - foreach ($uncoveredFiles as $uncoveredFile) { - if ($this->filter->isFile($uncoveredFile)) { - include_once $uncoveredFile; - } - } - - $this->append($this->driver->stop(), self::UNCOVERED_FILES); - } - /** * @throws ReflectionException * @throws UnintentionallyCoveredCodeException @@ -640,28 +560,32 @@ private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed): } /** + * @param list $unintentionallyCoveredUnits + * * @throws ReflectionException + * + * @return list */ private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits): array { $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits); - sort($unintentionallyCoveredUnits); + $processed = []; - foreach (array_keys($unintentionallyCoveredUnits) as $k => $v) { - $unit = explode('::', $unintentionallyCoveredUnits[$k]); + foreach ($unintentionallyCoveredUnits as $unintentionallyCoveredUnit) { + $tmp = explode('::', $unintentionallyCoveredUnit); + + if (count($tmp) !== 2) { + $processed[] = $unintentionallyCoveredUnit; - if (count($unit) !== 2) { continue; } try { - $class = new ReflectionClass($unit[0]); + $class = new ReflectionClass($tmp[0]); foreach ($this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck as $parentClass) { if ($class->isSubclassOf($parentClass)) { - unset($unintentionallyCoveredUnits[$k]); - - break; + continue 2; } } } catch (\ReflectionException $e) { @@ -671,9 +595,15 @@ private function processUnintentionallyCoveredUnits(array $unintentionallyCovere $e ); } + + $processed[] = $tmp[0]; } - return array_values($unintentionallyCoveredUnits); + $processed = array_unique($processed); + + sort($processed); + + return $processed; } private function analyser(): FileAnalyser diff --git a/src/ProcessedCodeCoverageData.php b/src/Data/ProcessedCodeCoverageData.php similarity index 90% rename from src/ProcessedCodeCoverageData.php rename to src/Data/ProcessedCodeCoverageData.php index 1ed29ad52..550c037a2 100644 --- a/src/ProcessedCodeCoverageData.php +++ b/src/Data/ProcessedCodeCoverageData.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace SebastianBergmann\CodeCoverage; +namespace SebastianBergmann\CodeCoverage\Data; use function array_key_exists; use function array_keys; @@ -20,6 +20,10 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type XdebugFunctionCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver + * + * @psalm-type TestIdType = string */ final class ProcessedCodeCoverageData { @@ -27,18 +31,33 @@ final class ProcessedCodeCoverageData * Line coverage data. * An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids. * - * @var array + * @psalm-var array>> */ - private $lineCoverage = []; + private array $lineCoverage = []; /** * Function coverage data. * Maintains base format of raw data (@see https://xdebug.org/docs/code_coverage), but each 'hit' entry is an array * of testcase ids. * - * @var array + * @psalm-var array, + * out: array, + * out_hit: array, + * }>, + * paths: array, + * hit: list, + * }>, + * hit: list + * }>> */ - private $functionCoverage = []; + private array $functionCoverage = []; public function initializeUnseenData(RawCodeCoverageData $rawData): void { @@ -217,6 +236,8 @@ private function priorityForLine(array $data, int $line): int /** * For a function we have never seen before, copy all data over and simply init the 'hit' array. + * + * @psalm-param XdebugFunctionCoverageType $functionData */ private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData): void { @@ -235,6 +256,8 @@ private function initPreviouslyUnseenFunction(string $file, string $functionName * For a function we have seen before, only copy over and init the 'hit' array for any unseen branches and paths. * Techniques such as mocking and where the contents of a file are different vary during tests (e.g. compiling * containers) mean that the functions inside a file cannot be relied upon to be static. + * + * @psalm-param XdebugFunctionCoverageType $functionData */ private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData): void { diff --git a/src/RawCodeCoverageData.php b/src/Data/RawCodeCoverageData.php similarity index 86% rename from src/RawCodeCoverageData.php rename to src/Data/RawCodeCoverageData.php index 9cb20e731..0d91745f8 100644 --- a/src/RawCodeCoverageData.php +++ b/src/Data/RawCodeCoverageData.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace SebastianBergmann\CodeCoverage; +namespace SebastianBergmann\CodeCoverage\Data; use function array_diff; use function array_diff_key; @@ -26,33 +26,39 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type XdebugFunctionsCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver + * @psalm-import-type XdebugCodeCoverageWithoutPathCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver + * @psalm-import-type XdebugCodeCoverageWithPathCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver */ final class RawCodeCoverageData { /** * @var array> */ - private static $emptyLineCache = []; + private static array $emptyLineCache = []; /** - * @var array - * - * @see https://xdebug.org/docs/code_coverage for format + * @psalm-var XdebugCodeCoverageWithoutPathCoverageType */ - private $lineCoverage; + private array $lineCoverage; /** - * @var array - * - * @see https://xdebug.org/docs/code_coverage for format + * @psalm-var array */ - private $functionCoverage; + private array $functionCoverage; + /** + * @psalm-param XdebugCodeCoverageWithoutPathCoverageType $rawCoverage + */ public static function fromXdebugWithoutPathCoverage(array $rawCoverage): self { return new self($rawCoverage, []); } + /** + * @psalm-param XdebugCodeCoverageWithPathCoverageType $rawCoverage + */ public static function fromXdebugWithPathCoverage(array $rawCoverage): self { $lineCoverage = []; @@ -66,27 +72,6 @@ public static function fromXdebugWithPathCoverage(array $rawCoverage): self return new self($lineCoverage, $functionCoverage); } - public static function fromXdebugWithMixedCoverage(array $rawCoverage): self - { - $lineCoverage = []; - $functionCoverage = []; - - foreach ($rawCoverage as $file => $fileCoverageData) { - if (!isset($fileCoverageData['functions'])) { - // Current file does not have functions, so line coverage - // is stored in $fileCoverageData, not in $fileCoverageData['lines'] - $lineCoverage[$file] = $fileCoverageData; - - continue; - } - - $lineCoverage[$file] = $fileCoverageData['lines']; - $functionCoverage[$file] = $fileCoverageData['functions']; - } - - return new self($lineCoverage, $functionCoverage); - } - public static function fromUncoveredFile(string $filename, FileAnalyser $analyser): self { $lineCoverage = []; @@ -98,6 +83,10 @@ public static function fromUncoveredFile(string $filename, FileAnalyser $analyse return new self([$filename => $lineCoverage], []); } + /** + * @psalm-param XdebugCodeCoverageWithoutPathCoverageType $lineCoverage + * @psalm-param array $functionCoverage + */ private function __construct(array $lineCoverage, array $functionCoverage) { $this->lineCoverage = $lineCoverage; @@ -111,11 +100,17 @@ public function clear(): void $this->lineCoverage = $this->functionCoverage = []; } + /** + * @psalm-return XdebugCodeCoverageWithoutPathCoverageType + */ public function lineCoverage(): array { return $this->lineCoverage; } + /** + * @psalm-return array + */ public function functionCoverage(): array { return $this->functionCoverage; diff --git a/src/Driver/Driver.php b/src/Driver/Driver.php index dc2de68f4..31c4e644c 100644 --- a/src/Driver/Driver.php +++ b/src/Driver/Driver.php @@ -11,11 +11,8 @@ use function sprintf; use SebastianBergmann\CodeCoverage\BranchAndPathCoverageNotSupportedException; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; use SebastianBergmann\CodeCoverage\DeadCodeDetectionNotSupportedException; -use SebastianBergmann\CodeCoverage\Filter; -use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; -use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage @@ -55,45 +52,9 @@ abstract class Driver * * @see http://xdebug.org/docs/code_coverage */ - public const BRANCH_HIT = 1; - - /** - * @var bool - */ - private $collectBranchAndPathCoverage = false; - - /** - * @var bool - */ - private $detectDeadCode = false; - - /** - * @throws NoCodeCoverageDriverAvailableException - * @throws PcovNotAvailableException - * @throws PhpdbgNotAvailableException - * @throws Xdebug2NotEnabledException - * @throws Xdebug3NotEnabledException - * @throws XdebugNotAvailableException - * - * @deprecated Use DriverSelector::forLineCoverage() instead - */ - public static function forLineCoverage(Filter $filter): self - { - return (new Selector)->forLineCoverage($filter); - } - - /** - * @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException - * @throws Xdebug2NotEnabledException - * @throws Xdebug3NotEnabledException - * @throws XdebugNotAvailableException - * - * @deprecated Use DriverSelector::forLineAndPathCoverage() instead - */ - public static function forLineAndPathCoverage(Filter $filter): self - { - return (new Selector)->forLineAndPathCoverage($filter); - } + public const BRANCH_HIT = 1; + private bool $collectBranchAndPathCoverage = false; + private bool $detectDeadCode = false; public function canCollectBranchAndPathCoverage(): bool { diff --git a/src/Driver/PcovDriver.php b/src/Driver/PcovDriver.php index c30b30c44..cdb01e65a 100644 --- a/src/Driver/PcovDriver.php +++ b/src/Driver/PcovDriver.php @@ -18,27 +18,22 @@ use function pcov\stop; use function pcov\waiting; use function phpversion; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; use SebastianBergmann\CodeCoverage\Filter; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class PcovDriver extends Driver { - /** - * @var Filter - */ - private $filter; + private readonly Filter $filter; /** * @throws PcovNotAvailableException */ public function __construct(Filter $filter) { - if (!extension_loaded('pcov')) { - throw new PcovNotAvailableException; - } + $this->ensurePcovIsAvailable(); $this->filter = $filter; } @@ -72,4 +67,14 @@ public function nameAndVersion(): string { return 'PCOV ' . phpversion('pcov'); } + + /** + * @throws PcovNotAvailableException + */ + private function ensurePcovIsAvailable(): void + { + if (!extension_loaded('pcov')) { + throw new PcovNotAvailableException; + } + } } diff --git a/src/Driver/PhpdbgDriver.php b/src/Driver/PhpdbgDriver.php deleted file mode 100644 index 7ee13b00f..000000000 --- a/src/Driver/PhpdbgDriver.php +++ /dev/null @@ -1,93 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\Driver; - -use const PHP_SAPI; -use const PHP_VERSION; -use function array_diff; -use function array_keys; -use function array_merge; -use function get_included_files; -use function phpdbg_end_oplog; -use function phpdbg_get_executable; -use function phpdbg_start_oplog; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; - -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class PhpdbgDriver extends Driver -{ - /** - * @throws PhpdbgNotAvailableException - */ - public function __construct() - { - if (PHP_SAPI !== 'phpdbg') { - throw new PhpdbgNotAvailableException; - } - } - - public function start(): void - { - phpdbg_start_oplog(); - } - - public function stop(): RawCodeCoverageData - { - static $fetchedLines = []; - - $dbgData = phpdbg_end_oplog(); - - if ($fetchedLines === []) { - $sourceLines = phpdbg_get_executable(); - } else { - $newFiles = array_diff(get_included_files(), array_keys($fetchedLines)); - - $sourceLines = []; - - if ($newFiles) { - $sourceLines = phpdbg_get_executable(['files' => $newFiles]); - } - } - - foreach ($sourceLines as $file => $lines) { - foreach ($lines as $lineNo => $numExecuted) { - $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; - } - } - - $fetchedLines = array_merge($fetchedLines, $sourceLines); - - return RawCodeCoverageData::fromXdebugWithoutPathCoverage( - $this->detectExecutedLines($fetchedLines, $dbgData) - ); - } - - public function nameAndVersion(): string - { - return 'PHPDBG ' . PHP_VERSION; - } - - private function detectExecutedLines(array $sourceLines, array $dbgData): array - { - foreach ($dbgData as $file => $coveredLines) { - foreach ($coveredLines as $lineNo => $numExecuted) { - // phpdbg also reports $lineNo=0 when e.g. exceptions get thrown. - // make sure we only mark lines executed which are actually executable. - if (isset($sourceLines[$file][$lineNo])) { - $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; - } - } - } - - return $sourceLines; - } -} diff --git a/src/Driver/Selector.php b/src/Driver/Selector.php index 936ee8981..f1ff32b58 100644 --- a/src/Driver/Selector.php +++ b/src/Driver/Selector.php @@ -9,8 +9,6 @@ */ namespace SebastianBergmann\CodeCoverage\Driver; -use function phpversion; -use function version_compare; use SebastianBergmann\CodeCoverage\Filter; use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; @@ -21,29 +19,19 @@ final class Selector /** * @throws NoCodeCoverageDriverAvailableException * @throws PcovNotAvailableException - * @throws PhpdbgNotAvailableException - * @throws Xdebug2NotEnabledException - * @throws Xdebug3NotEnabledException * @throws XdebugNotAvailableException + * @throws XdebugNotEnabledException */ public function forLineCoverage(Filter $filter): Driver { $runtime = new Runtime; - if ($runtime->hasPHPDBGCodeCoverage()) { - return new PhpdbgDriver; - } - if ($runtime->hasPCOV()) { return new PcovDriver($filter); } if ($runtime->hasXdebug()) { - if (version_compare(phpversion('xdebug'), '3', '>=')) { - $driver = new Xdebug3Driver($filter); - } else { - $driver = new Xdebug2Driver($filter); - } + $driver = new XdebugDriver($filter); $driver->enableDeadCodeDetection(); @@ -55,18 +43,13 @@ public function forLineCoverage(Filter $filter): Driver /** * @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException - * @throws Xdebug2NotEnabledException - * @throws Xdebug3NotEnabledException * @throws XdebugNotAvailableException + * @throws XdebugNotEnabledException */ public function forLineAndPathCoverage(Filter $filter): Driver { if ((new Runtime)->hasXdebug()) { - if (version_compare(phpversion('xdebug'), '3', '>=')) { - $driver = new Xdebug3Driver($filter); - } else { - $driver = new Xdebug2Driver($filter); - } + $driver = new XdebugDriver($filter); $driver->enableDeadCodeDetection(); $driver->enableBranchAndPathCoverage(); diff --git a/src/Driver/Xdebug2Driver.php b/src/Driver/Xdebug2Driver.php deleted file mode 100644 index 74cbbfbcd..000000000 --- a/src/Driver/Xdebug2Driver.php +++ /dev/null @@ -1,128 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\Driver; - -use const XDEBUG_CC_BRANCH_CHECK; -use const XDEBUG_CC_DEAD_CODE; -use const XDEBUG_CC_UNUSED; -use const XDEBUG_FILTER_CODE_COVERAGE; -use const XDEBUG_PATH_INCLUDE; -use const XDEBUG_PATH_WHITELIST; -use function defined; -use function extension_loaded; -use function ini_get; -use function phpversion; -use function sprintf; -use function version_compare; -use function xdebug_get_code_coverage; -use function xdebug_set_filter; -use function xdebug_start_code_coverage; -use function xdebug_stop_code_coverage; -use SebastianBergmann\CodeCoverage\Filter; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; - -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Xdebug2Driver extends Driver -{ - /** - * @var bool - */ - private $pathCoverageIsMixedCoverage; - - /** - * @throws WrongXdebugVersionException - * @throws Xdebug2NotEnabledException - * @throws XdebugNotAvailableException - */ - public function __construct(Filter $filter) - { - if (!extension_loaded('xdebug')) { - throw new XdebugNotAvailableException; - } - - if (version_compare(phpversion('xdebug'), '3', '>=')) { - throw new WrongXdebugVersionException( - sprintf( - 'This driver requires Xdebug 2 but version %s is loaded', - phpversion('xdebug') - ) - ); - } - - if (!ini_get('xdebug.coverage_enable')) { - throw new Xdebug2NotEnabledException; - } - - if (!$filter->isEmpty()) { - if (defined('XDEBUG_PATH_WHITELIST')) { - $listType = XDEBUG_PATH_WHITELIST; - } else { - $listType = XDEBUG_PATH_INCLUDE; - } - - xdebug_set_filter( - XDEBUG_FILTER_CODE_COVERAGE, - $listType, - $filter->files() - ); - } - - $this->pathCoverageIsMixedCoverage = version_compare(phpversion('xdebug'), '2.9.6', '<'); - } - - public function canCollectBranchAndPathCoverage(): bool - { - return true; - } - - public function canDetectDeadCode(): bool - { - return true; - } - - public function start(): void - { - $flags = XDEBUG_CC_UNUSED; - - if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) { - $flags |= XDEBUG_CC_DEAD_CODE; - } - - if ($this->collectsBranchAndPathCoverage()) { - $flags |= XDEBUG_CC_BRANCH_CHECK; - } - - xdebug_start_code_coverage($flags); - } - - public function stop(): RawCodeCoverageData - { - $data = xdebug_get_code_coverage(); - - xdebug_stop_code_coverage(); - - if ($this->collectsBranchAndPathCoverage()) { - if ($this->pathCoverageIsMixedCoverage) { - return RawCodeCoverageData::fromXdebugWithMixedCoverage($data); - } - - return RawCodeCoverageData::fromXdebugWithPathCoverage($data); - } - - return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); - } - - public function nameAndVersion(): string - { - return 'Xdebug ' . phpversion('xdebug'); - } -} diff --git a/src/Driver/Xdebug3Driver.php b/src/Driver/XdebugDriver.php similarity index 57% rename from src/Driver/Xdebug3Driver.php rename to src/Driver/XdebugDriver.php index b85db4034..3123de6eb 100644 --- a/src/Driver/Xdebug3Driver.php +++ b/src/Driver/XdebugDriver.php @@ -20,50 +20,56 @@ use function in_array; use function ini_get; use function phpversion; -use function sprintf; use function version_compare; use function xdebug_get_code_coverage; +use function xdebug_info; use function xdebug_set_filter; use function xdebug_start_code_coverage; use function xdebug_stop_code_coverage; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; use SebastianBergmann\CodeCoverage\Filter; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @see https://xdebug.org/docs/code_coverage#xdebug_get_code_coverage + * + * @psalm-type XdebugLinesCoverageType = array + * @psalm-type XdebugBranchCoverageType = array{ + * op_start: int, + * op_end: int, + * line_start: int, + * line_end: int, + * hit: int, + * out: array, + * out_hit: array, + * } + * @psalm-type XdebugPathCoverageType = array{ + * path: array, + * hit: int, + * } + * @psalm-type XdebugFunctionCoverageType = array{ + * branches: array, + * paths: array, + * } + * @psalm-type XdebugFunctionsCoverageType = array + * @psalm-type XdebugPathAndBranchesCoverageType = array{ + * lines: XdebugLinesCoverageType, + * functions: XdebugFunctionsCoverageType, + * } + * @psalm-type XdebugCodeCoverageWithoutPathCoverageType = array + * @psalm-type XdebugCodeCoverageWithPathCoverageType = array */ -final class Xdebug3Driver extends Driver +final class XdebugDriver extends Driver { /** - * @throws WrongXdebugVersionException - * @throws Xdebug3NotEnabledException * @throws XdebugNotAvailableException + * @throws XdebugNotEnabledException */ public function __construct(Filter $filter) { - if (!extension_loaded('xdebug')) { - throw new XdebugNotAvailableException; - } - - if (version_compare(phpversion('xdebug'), '3', '<')) { - throw new WrongXdebugVersionException( - sprintf( - 'This driver requires Xdebug 3 but version %s is loaded', - phpversion('xdebug') - ) - ); - } - - $mode = getenv('XDEBUG_MODE'); - - if ($mode === false || $mode === '') { - $mode = ini_get('xdebug.mode'); - } - - if ($mode === false || - !in_array('coverage', explode(',', $mode), true)) { - throw new Xdebug3NotEnabledException; - } + $this->ensureXdebugIsAvailable(); + $this->ensureXdebugCodeCoverageFeatureIsEnabled(); if (!$filter->isEmpty()) { xdebug_set_filter( @@ -106,9 +112,11 @@ public function stop(): RawCodeCoverageData xdebug_stop_code_coverage(); if ($this->collectsBranchAndPathCoverage()) { + /* @var XdebugCodeCoverageWithPathCoverageType $data */ return RawCodeCoverageData::fromXdebugWithPathCoverage($data); } + /* @var XdebugCodeCoverageWithoutPathCoverageType $data */ return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); } @@ -116,4 +124,39 @@ public function nameAndVersion(): string { return 'Xdebug ' . phpversion('xdebug'); } + + /** + * @throws XdebugNotAvailableException + */ + private function ensureXdebugIsAvailable(): void + { + if (!extension_loaded('xdebug')) { + throw new XdebugNotAvailableException; + } + } + + /** + * @throws XdebugNotEnabledException + */ + private function ensureXdebugCodeCoverageFeatureIsEnabled(): void + { + if (version_compare(phpversion('xdebug'), '3.1', '>=')) { + if (!in_array('coverage', xdebug_info('mode'), true)) { + throw new XdebugNotEnabledException; + } + + return; + } + + $mode = getenv('XDEBUG_MODE'); + + if ($mode === false || $mode === '') { + $mode = ini_get('xdebug.mode'); + } + + if ($mode === false || + !in_array('coverage', explode(',', $mode), true)) { + throw new XdebugNotEnabledException; + } + } } diff --git a/src/Exception/WrongXdebugVersionException.php b/src/Exception/FileCouldNotBeWrittenException.php similarity index 62% rename from src/Exception/WrongXdebugVersionException.php rename to src/Exception/FileCouldNotBeWrittenException.php index 6e8f10a92..db9cdac34 100644 --- a/src/Exception/WrongXdebugVersionException.php +++ b/src/Exception/FileCouldNotBeWrittenException.php @@ -7,11 +7,10 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace SebastianBergmann\CodeCoverage\Driver; +namespace SebastianBergmann\CodeCoverage; use RuntimeException; -use SebastianBergmann\CodeCoverage\Exception; -final class WrongXdebugVersionException extends RuntimeException implements Exception +final class FileCouldNotBeWrittenException extends RuntimeException implements Exception { } diff --git a/src/Exception/PhpdbgNotAvailableException.php b/src/Exception/PhpdbgNotAvailableException.php deleted file mode 100644 index bfb183d5c..000000000 --- a/src/Exception/PhpdbgNotAvailableException.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\Driver; - -use RuntimeException; -use SebastianBergmann\CodeCoverage\Exception; - -final class PhpdbgNotAvailableException extends RuntimeException implements Exception -{ - public function __construct() - { - parent::__construct('The PHPDBG SAPI is not available'); - } -} diff --git a/src/Exception/UnintentionallyCoveredCodeException.php b/src/Exception/UnintentionallyCoveredCodeException.php index cb7a975f7..bb7d88c97 100644 --- a/src/Exception/UnintentionallyCoveredCodeException.php +++ b/src/Exception/UnintentionallyCoveredCodeException.php @@ -14,10 +14,13 @@ final class UnintentionallyCoveredCodeException extends RuntimeException implements Exception { /** - * @var array + * @var list */ - private $unintentionallyCoveredUnits; + private readonly array $unintentionallyCoveredUnits; + /** + * @param list $unintentionallyCoveredUnits + */ public function __construct(array $unintentionallyCoveredUnits) { $this->unintentionallyCoveredUnits = $unintentionallyCoveredUnits; @@ -25,6 +28,9 @@ public function __construct(array $unintentionallyCoveredUnits) parent::__construct($this->toString()); } + /** + * @return list + */ public function getUnintentionallyCoveredUnits(): array { return $this->unintentionallyCoveredUnits; diff --git a/src/Exception/Xdebug2NotEnabledException.php b/src/Exception/Xdebug2NotEnabledException.php deleted file mode 100644 index 3039e77c0..000000000 --- a/src/Exception/Xdebug2NotEnabledException.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\Driver; - -use RuntimeException; -use SebastianBergmann\CodeCoverage\Exception; - -final class Xdebug2NotEnabledException extends RuntimeException implements Exception -{ - public function __construct() - { - parent::__construct('xdebug.coverage_enable=On has to be set'); - } -} diff --git a/src/Exception/Xdebug3NotEnabledException.php b/src/Exception/XdebugNotEnabledException.php similarity index 86% rename from src/Exception/Xdebug3NotEnabledException.php rename to src/Exception/XdebugNotEnabledException.php index 5d3b106ce..b070ed5a6 100644 --- a/src/Exception/Xdebug3NotEnabledException.php +++ b/src/Exception/XdebugNotEnabledException.php @@ -12,7 +12,7 @@ use RuntimeException; use SebastianBergmann\CodeCoverage\Exception; -final class Xdebug3NotEnabledException extends RuntimeException implements Exception +final class XdebugNotEnabledException extends RuntimeException implements Exception { public function __construct() { diff --git a/src/Filter.php b/src/Filter.php index 5a0a142a8..f51eb3e1f 100644 --- a/src/Filter.php +++ b/src/Filter.php @@ -12,7 +12,8 @@ use function array_keys; use function is_file; use function realpath; -use function strpos; +use function str_contains; +use function str_starts_with; use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; final class Filter @@ -20,13 +21,16 @@ final class Filter /** * @psalm-var array */ - private $files = []; + private array $files = []; /** * @psalm-var array */ - private $isFileCache = []; + private array $isFileCache = []; + /** + * @deprecated + */ public function includeDirectory(string $directory, string $suffix = '.php', string $prefix = ''): void { foreach ((new FileIteratorFacade)->getFilesAsArray($directory, $suffix, $prefix) as $file) { @@ -55,6 +59,9 @@ public function includeFile(string $filename): void $this->files[$filename] = true; } + /** + * @deprecated + */ public function excludeDirectory(string $directory, string $suffix = '.php', string $prefix = ''): void { foreach ((new FileIteratorFacade)->getFilesAsArray($directory, $suffix, $prefix) as $file) { @@ -62,6 +69,9 @@ public function excludeDirectory(string $directory, string $suffix = '.php', str } } + /** + * @deprecated + */ public function excludeFile(string $filename): void { $filename = realpath($filename); @@ -80,14 +90,14 @@ public function isFile(string $filename): bool } if ($filename === '-' || - strpos($filename, 'vfs://') === 0 || - strpos($filename, 'xdebug://debug-eval') !== false || - strpos($filename, 'eval()\'d code') !== false || - strpos($filename, 'runtime-created function') !== false || - strpos($filename, 'runkit created function') !== false || - strpos($filename, 'assert code') !== false || - strpos($filename, 'regexp code') !== false || - strpos($filename, 'Standard input code') !== false) { + str_starts_with($filename, 'vfs://') || + str_contains($filename, 'xdebug://debug-eval') || + str_contains($filename, 'eval()\'d code') || + str_contains($filename, 'runtime-created function') || + str_contains($filename, 'runkit created function') || + str_contains($filename, 'assert code') || + str_contains($filename, 'regexp code') || + str_contains($filename, 'Standard input code')) { $isFile = false; } else { $isFile = is_file($filename); diff --git a/src/Node/AbstractNode.php b/src/Node/AbstractNode.php index 60d6391d3..214258e78 100644 --- a/src/Node/AbstractNode.php +++ b/src/Node/AbstractNode.php @@ -11,6 +11,7 @@ use const DIRECTORY_SEPARATOR; use function array_merge; +use function str_ends_with; use function str_replace; use function substr; use Countable; @@ -18,42 +19,31 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser + * @psalm-import-type ProcessedFunctionType from \SebastianBergmann\CodeCoverage\Node\File + * @psalm-import-type ProcessedClassType from \SebastianBergmann\CodeCoverage\Node\File + * @psalm-import-type ProcessedTraitType from \SebastianBergmann\CodeCoverage\Node\File */ abstract class AbstractNode implements Countable { - /** - * @var string - */ - private $name; - - /** - * @var string - */ - private $pathAsString; - - /** - * @var array - */ - private $pathAsArray; - - /** - * @var AbstractNode - */ - private $parent; - - /** - * @var string - */ - private $id; + private readonly string $name; + private string $pathAsString; + private array $pathAsArray; + private readonly ?AbstractNode $parent; + private string $id; public function __construct(string $name, self $parent = null) { - if (substr($name, -1) === DIRECTORY_SEPARATOR) { + if (str_ends_with($name, DIRECTORY_SEPARATOR)) { $name = substr($name, 0, -1); } $this->name = $name; $this->parent = $parent; + + $this->processId(); + $this->processPath(); } public function name(): string @@ -63,50 +53,16 @@ public function name(): string public function id(): string { - if ($this->id === null) { - $parent = $this->parent(); - - if ($parent === null) { - $this->id = 'index'; - } else { - $parentId = $parent->id(); - - if ($parentId === 'index') { - $this->id = str_replace(':', '_', $this->name); - } else { - $this->id = $parentId . '/' . $this->name; - } - } - } - return $this->id; } public function pathAsString(): string { - if ($this->pathAsString === null) { - if ($this->parent === null) { - $this->pathAsString = $this->name; - } else { - $this->pathAsString = $this->parent->pathAsString() . DIRECTORY_SEPARATOR . $this->name; - } - } - return $this->pathAsString; } public function pathAsArray(): array { - if ($this->pathAsArray === null) { - if ($this->parent === null) { - $this->pathAsArray = []; - } else { - $this->pathAsArray = $this->parent->pathAsArray(); - } - - $this->pathAsArray[] = $this; - } - return $this->pathAsArray; } @@ -212,14 +168,23 @@ public function numberOfTestedFunctionsAndMethods(): int return $this->numberOfTestedFunctions() + $this->numberOfTestedMethods(); } + /** + * @psalm-return array + */ abstract public function classes(): array; + /** + * @psalm-return array + */ abstract public function traits(): array; + /** + * @psalm-return array + */ abstract public function functions(): array; /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + * @psalm-return LinesOfCodeType */ abstract public function linesOfCode(): array; @@ -250,4 +215,36 @@ abstract public function numberOfTestedMethods(): int; abstract public function numberOfFunctions(): int; abstract public function numberOfTestedFunctions(): int; + + private function processId(): void + { + if ($this->parent === null) { + $this->id = 'index'; + + return; + } + + $parentId = $this->parent->id(); + + if ($parentId === 'index') { + $this->id = str_replace(':', '_', $this->name); + } else { + $this->id = $parentId . '/' . $this->name; + } + } + + private function processPath(): void + { + if ($this->parent === null) { + $this->pathAsArray = [$this]; + $this->pathAsString = $this->name; + + return; + } + + $this->pathAsArray = $this->parent->pathAsArray(); + $this->pathAsString = $this->parent->pathAsString() . DIRECTORY_SEPARATOR . $this->name; + + $this->pathAsArray[] = $this; + } } diff --git a/src/Node/Builder.php b/src/Node/Builder.php index 6d11c7798..a2885752b 100644 --- a/src/Node/Builder.php +++ b/src/Node/Builder.php @@ -17,22 +17,22 @@ use function explode; use function implode; use function is_file; +use function str_ends_with; use function str_replace; -use function strpos; +use function str_starts_with; use function substr; use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData; use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type TestType from \SebastianBergmann\CodeCoverage\CodeCoverage */ final class Builder { - /** - * @var FileAnalyser - */ - private $analyser; + private readonly FileAnalyser $analyser; public function __construct(FileAnalyser $analyser) { @@ -57,12 +57,15 @@ public function build(CodeCoverage $coverage): Directory return $root; } + /** + * @psalm-param array $tests + */ private function addItems(Directory $root, array $items, array $tests): void { foreach ($items as $key => $value) { $key = (string) $key; - if (substr($key, -2) === '/f') { + if (str_ends_with($key, '/f')) { $key = substr($key, 0, -2); $filename = $root->pathAsString() . DIRECTORY_SEPARATOR . $key; @@ -128,6 +131,8 @@ private function addItems(Directory $root, array $items, array $tests): void * ) * ) * + * + * @psalm-return array, functionCoverage: array>}>> */ private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array { @@ -214,7 +219,7 @@ private function reducePaths(ProcessedCodeCoverageData $coverage): string for ($i = 0; $i < $max; $i++) { // strip phar:// prefixes - if (strpos($paths[$i], 'phar://') === 0) { + if (str_starts_with($paths[$i], 'phar://')) { $paths[$i] = substr($paths[$i], 7); $paths[$i] = str_replace('/', DIRECTORY_SEPARATOR, $paths[$i]); } diff --git a/src/Node/CrapIndex.php b/src/Node/CrapIndex.php index 30b86b7d2..8992346cd 100644 --- a/src/Node/CrapIndex.php +++ b/src/Node/CrapIndex.php @@ -16,15 +16,8 @@ */ final class CrapIndex { - /** - * @var int - */ - private $cyclomaticComplexity; - - /** - * @var float - */ - private $codeCoverage; + private readonly int $cyclomaticComplexity; + private readonly float $codeCoverage; public function __construct(int $cyclomaticComplexity, float $codeCoverage) { diff --git a/src/Node/Directory.php b/src/Node/Directory.php index d6ee07e4d..8e71f6460 100644 --- a/src/Node/Directory.php +++ b/src/Node/Directory.php @@ -16,118 +16,48 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser */ final class Directory extends AbstractNode implements IteratorAggregate { /** - * @var AbstractNode[] - */ - private $children = []; - - /** - * @var Directory[] - */ - private $directories = []; - - /** - * @var File[] - */ - private $files = []; - - /** - * @var array - */ - private $classes; - - /** - * @var array - */ - private $traits; - - /** - * @var array - */ - private $functions; - - /** - * @psalm-var null|array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} - */ - private $linesOfCode; - - /** - * @var int - */ - private $numFiles = -1; - - /** - * @var int - */ - private $numExecutableLines = -1; - - /** - * @var int - */ - private $numExecutedLines = -1; - - /** - * @var int - */ - private $numExecutableBranches = -1; - - /** - * @var int - */ - private $numExecutedBranches = -1; - - /** - * @var int - */ - private $numExecutablePaths = -1; - - /** - * @var int - */ - private $numExecutedPaths = -1; - - /** - * @var int - */ - private $numClasses = -1; - - /** - * @var int - */ - private $numTestedClasses = -1; - - /** - * @var int - */ - private $numTraits = -1; - - /** - * @var int - */ - private $numTestedTraits = -1; - - /** - * @var int + * @var list */ - private $numMethods = -1; + private array $children = []; /** - * @var int + * @var list */ - private $numTestedMethods = -1; + private array $directories = []; /** - * @var int + * @var list */ - private $numFunctions = -1; + private array $files = []; + private ?array $classes = null; + private ?array $traits = null; + private ?array $functions = null; /** - * @var int + * @psalm-var null|LinesOfCodeType */ - private $numTestedFunctions = -1; + private ?array $linesOfCode = null; + private int $numFiles = -1; + private int $numExecutableLines = -1; + private int $numExecutedLines = -1; + private int $numExecutableBranches = -1; + private int $numExecutedBranches = -1; + private int $numExecutablePaths = -1; + private int $numExecutedPaths = -1; + private int $numClasses = -1; + private int $numTestedClasses = -1; + private int $numTraits = -1; + private int $numTestedTraits = -1; + private int $numMethods = -1; + private int $numTestedMethods = -1; + private int $numFunctions = -1; + private int $numTestedFunctions = -1; public function count(): int { @@ -233,7 +163,7 @@ public function functions(): array } /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + * @psalm-return LinesOfCodeType */ public function linesOfCode(): array { diff --git a/src/Node/File.php b/src/Node/File.php index af3764e41..120ebd27a 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -15,116 +15,134 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser + * @psalm-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser + * + * @psalm-type ProcessedFunctionType = array{ + * functionName: string, + * namespace: string, + * signature: string, + * startLine: int, + * endLine: int, + * executableLines: int, + * executedLines: int, + * executableBranches: int, + * executedBranches: int, + * executablePaths: int, + * executedPaths: int, + * ccn: int, + * coverage: int|float, + * crap: int|string, + * link: string + * } + * @psalm-type ProcessedMethodType = array{ + * methodName: string, + * visibility: string, + * signature: string, + * startLine: int, + * endLine: int, + * executableLines: int, + * executedLines: int, + * executableBranches: int, + * executedBranches: int, + * executablePaths: int, + * executedPaths: int, + * ccn: int, + * coverage: float|int, + * crap: int|string, + * link: string + * } + * @psalm-type ProcessedClassType = array{ + * className: string, + * namespace: string, + * methods: array, + * startLine: int, + * executableLines: int, + * executedLines: int, + * executableBranches: int, + * executedBranches: int, + * executablePaths: int, + * executedPaths: int, + * ccn: int, + * coverage: int|float, + * crap: int|string, + * link: string + * } + * @psalm-type ProcessedTraitType = array{ + * traitName: string, + * namespace: string, + * methods: array, + * startLine: int, + * executableLines: int, + * executedLines: int, + * executableBranches: int, + * executedBranches: int, + * executablePaths: int, + * executedPaths: int, + * ccn: int, + * coverage: float|int, + * crap: int|string, + * link: string + * } */ final class File extends AbstractNode { /** - * @var array - */ - private $lineCoverageData; - - /** - * @var array - */ - private $functionCoverageData; - - /** - * @var array + * @psalm-var array> */ - private $testData; + private array $lineCoverageData; + private array $functionCoverageData; + private readonly array $testData; + private int $numExecutableLines = 0; + private int $numExecutedLines = 0; + private int $numExecutableBranches = 0; + private int $numExecutedBranches = 0; + private int $numExecutablePaths = 0; + private int $numExecutedPaths = 0; /** - * @var int + * @psalm-var array */ - private $numExecutableLines = 0; + private array $classes = []; /** - * @var int + * @psalm-var array */ - private $numExecutedLines = 0; + private array $traits = []; /** - * @var int + * @psalm-var array */ - private $numExecutableBranches = 0; + private array $functions = []; /** - * @var int + * @psalm-var LinesOfCodeType */ - private $numExecutedBranches = 0; + private readonly array $linesOfCode; + private ?int $numClasses = null; + private int $numTestedClasses = 0; + private ?int $numTraits = null; + private int $numTestedTraits = 0; + private ?int $numMethods = null; + private ?int $numTestedMethods = null; + private ?int $numTestedFunctions = null; /** - * @var int + * @var array */ - private $numExecutablePaths = 0; + private array $codeUnitsByLine = []; /** - * @var int - */ - private $numExecutedPaths = 0; - - /** - * @var array - */ - private $classes = []; - - /** - * @var array - */ - private $traits = []; - - /** - * @var array - */ - private $functions = []; - - /** - * @psalm-var array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} - */ - private $linesOfCode; - - /** - * @var int - */ - private $numClasses; - - /** - * @var int - */ - private $numTestedClasses = 0; - - /** - * @var int - */ - private $numTraits; - - /** - * @var int - */ - private $numTestedTraits = 0; - - /** - * @var int - */ - private $numMethods; - - /** - * @var int - */ - private $numTestedMethods; - - /** - * @var int - */ - private $numTestedFunctions; - - /** - * @var array - */ - private $codeUnitsByLine = []; - - /** - * @psalm-param array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} $linesOfCode + * @psalm-param array> $lineCoverageData + * @psalm-param LinesOfCodeType $linesOfCode + * @psalm-param array $classes + * @psalm-param array $traits + * @psalm-param array $functions */ public function __construct(string $name, AbstractNode $parent, array $lineCoverageData, array $functionCoverageData, array $testData, array $classes, array $traits, array $functions, array $linesOfCode) { @@ -143,6 +161,9 @@ public function count(): int return 1; } + /** + * @psalm-return array> + */ public function lineCoverageData(): array { return $this->lineCoverageData; @@ -173,9 +194,6 @@ public function functions(): array return $this->functions; } - /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} - */ public function linesOfCode(): array { return $this->linesOfCode; @@ -332,6 +350,11 @@ public function numberOfTestedFunctions(): int return $this->numTestedFunctions; } + /** + * @psalm-param array $classes + * @psalm-param array $traits + * @psalm-param array $functions + */ private function calculateStatistics(array $classes, array $traits, array $functions): void { foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) { @@ -434,6 +457,9 @@ private function calculateStatistics(array $classes, array $traits, array $funct } } + /** + * @psalm-param array $classes + */ private function processClasses(array $classes): void { $link = $this->id() . '.html#'; @@ -480,6 +506,9 @@ private function processClasses(array $classes): void } } + /** + * @psalm-param array $traits + */ private function processTraits(array $traits): void { $link = $this->id() . '.html#'; @@ -526,6 +555,9 @@ private function processTraits(array $traits): void } } + /** + * @psalm-param array $functions + */ private function processFunctions(array $functions): void { $link = $this->id() . '.html#'; @@ -592,6 +624,11 @@ static function (array $path) } } + /** + * @psalm-param CodeUnitMethodType $method + * + * @psalm-return ProcessedMethodType + */ private function newMethod(string $className, string $methodName, array $method, string $link): array { $methodData = [ diff --git a/src/Node/Iterator.php b/src/Node/Iterator.php index 0d1c73545..b110d4679 100644 --- a/src/Node/Iterator.php +++ b/src/Node/Iterator.php @@ -17,15 +17,12 @@ */ final class Iterator implements RecursiveIterator { - /** - * @var int - */ - private $position; + private int $position; /** - * @var AbstractNode[] + * @var list */ - private $nodes; + private readonly array $nodes; public function __construct(Directory $node) { diff --git a/src/Report/Clover.php b/src/Report/Clover.php index d80ab4dee..08ba148b5 100644 --- a/src/Report/Clover.php +++ b/src/Report/Clover.php @@ -89,7 +89,7 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $methodCount = 0; foreach (range($method['startLine'], $method['endLine']) as $line) { - if (isset($coverageData[$line]) && ($coverageData[$line] !== null)) { + if (isset($coverageData[$line])) { $methodCount = max($methodCount, count($coverageData[$line])); } } diff --git a/src/Report/Cobertura.php b/src/Report/Cobertura.php index ead31b466..542bba005 100644 --- a/src/Report/Cobertura.php +++ b/src/Report/Cobertura.php @@ -175,7 +175,7 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $methodElement->appendChild($methodLinesElement); foreach (range($method['startLine'], $method['endLine']) as $line) { - if (!isset($coverageData[$line]) || $coverageData[$line] === null) { + if (!isset($coverageData[$line])) { continue; } $methodLineElement = $document->createElement('line'); @@ -256,7 +256,7 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $methodElement->appendChild($methodLinesElement); foreach (range($function['startLine'], $function['endLine']) as $line) { - if (!isset($coverageData[$line]) || $coverageData[$line] === null) { + if (!isset($coverageData[$line])) { continue; } $methodLineElement = $document->createElement('line'); diff --git a/src/Report/Crap4j.php b/src/Report/Crap4j.php index 2d91567a0..305e7e122 100644 --- a/src/Report/Crap4j.php +++ b/src/Report/Crap4j.php @@ -24,10 +24,7 @@ final class Crap4j { - /** - * @var int - */ - private $threshold; + private readonly int $threshold; public function __construct(int $threshold = 30) { diff --git a/src/Report/Html/Colors.php b/src/Report/Html/Colors.php new file mode 100644 index 000000000..25ca9034b --- /dev/null +++ b/src/Report/Html/Colors.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +/** + * @psalm-immutable + */ +final class Colors +{ + private readonly string $successLow; + private readonly string $successMedium; + private readonly string $successHigh; + private readonly string $warning; + private readonly string $danger; + + public static function default(): self + { + return new self('#dff0d8', '#c3e3b5', '#99cb84', '#fcf8e3', '#f2dede'); + } + + public static function from(string $successLow, string $successMedium, string $successHigh, string $warning, string $danger): self + { + return new self($successLow, $successMedium, $successHigh, $warning, $danger); + } + + private function __construct(string $successLow, string $successMedium, string $successHigh, string $warning, string $danger) + { + $this->successLow = $successLow; + $this->successMedium = $successMedium; + $this->successHigh = $successHigh; + $this->warning = $warning; + $this->danger = $danger; + } + + public function successLow(): string + { + return $this->successLow; + } + + public function successMedium(): string + { + return $this->successMedium; + } + + public function successHigh(): string + { + return $this->successHigh; + } + + public function warning(): string + { + return $this->warning; + } + + public function danger(): string + { + return $this->danger; + } +} diff --git a/src/Report/Html/CustomCssFile.php b/src/Report/Html/CustomCssFile.php new file mode 100644 index 000000000..2e0a4fef2 --- /dev/null +++ b/src/Report/Html/CustomCssFile.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use function is_file; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; + +/** + * @psalm-immutable + */ +final class CustomCssFile +{ + private readonly string $path; + + public static function default(): self + { + return new self(__DIR__ . '/Renderer/Template/css/custom.css'); + } + + /** + * @throws InvalidArgumentException + */ + public static function from(string $path): self + { + if (!is_file($path)) { + throw new InvalidArgumentException( + '$path does not exist' + ); + } + + return new self($path); + } + + private function __construct(string $path) + { + $this->path = $path; + } + + public function path(): string + { + return $this->path; + } +} diff --git a/src/Report/Html/Facade.php b/src/Report/Html/Facade.php index 69935d734..ee8f1c315 100644 --- a/src/Report/Html/Facade.php +++ b/src/Report/Html/Facade.php @@ -13,46 +13,30 @@ use function copy; use function date; use function dirname; -use function substr; +use function str_ends_with; use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\InvalidArgumentException; +use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\CodeCoverage\Report\Thresholds; use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\Template\Exception; +use SebastianBergmann\Template\Template; final class Facade { - /** - * @var string - */ - private $templatePath; - - /** - * @var string - */ - private $generator; - - /** - * @var int - */ - private $lowUpperBound; - - /** - * @var int - */ - private $highLowerBound; - - public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, string $generator = '') - { - if ($lowUpperBound > $highLowerBound) { - throw new InvalidArgumentException( - '$lowUpperBound must not be larger than $highLowerBound' - ); - } + private readonly string $templatePath; + private readonly string $generator; + private readonly Colors $colors; + private readonly Thresholds $thresholds; + private readonly CustomCssFile $customCssFile; - $this->generator = $generator; - $this->highLowerBound = $highLowerBound; - $this->lowUpperBound = $lowUpperBound; - $this->templatePath = __DIR__ . '/Renderer/Template/'; + public function __construct(string $generator = '', ?Colors $colors = null, ?Thresholds $thresholds = null, ?CustomCssFile $customCssFile = null) + { + $this->generator = $generator; + $this->colors = $colors ?? Colors::default(); + $this->thresholds = $thresholds ?? Thresholds::default(); + $this->customCssFile = $customCssFile ?? CustomCssFile::default(); + $this->templatePath = __DIR__ . '/Renderer/Template/'; } public function process(CodeCoverage $coverage, string $target): void @@ -65,8 +49,7 @@ public function process(CodeCoverage $coverage, string $target): void $this->templatePath, $this->generator, $date, - $this->lowUpperBound, - $this->highLowerBound, + $this->thresholds, $coverage->collectsBranchAndPathCoverage() ); @@ -74,8 +57,7 @@ public function process(CodeCoverage $coverage, string $target): void $this->templatePath, $this->generator, $date, - $this->lowUpperBound, - $this->highLowerBound, + $this->thresholds, $coverage->collectsBranchAndPathCoverage() ); @@ -83,8 +65,7 @@ public function process(CodeCoverage $coverage, string $target): void $this->templatePath, $this->generator, $date, - $this->lowUpperBound, - $this->highLowerBound, + $this->thresholds, $coverage->collectsBranchAndPathCoverage() ); @@ -109,6 +90,7 @@ public function process(CodeCoverage $coverage, string $target): void } $this->copyFiles($target); + $this->renderCss($target); } private function copyFiles(string $target): void @@ -117,8 +99,7 @@ private function copyFiles(string $target): void copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css'); copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css'); - copy($this->templatePath . 'css/style.css', $dir . 'style.css'); - copy($this->templatePath . 'css/custom.css', $dir . 'custom.css'); + copy($this->customCssFile->path(), $dir . 'custom.css'); copy($this->templatePath . 'css/octicons.css', $dir . 'octicons.css'); $dir = $this->directory($target . '_icons'); @@ -134,9 +115,34 @@ private function copyFiles(string $target): void copy($this->templatePath . 'js/file.js', $dir . 'file.js'); } + private function renderCss(string $target): void + { + $template = new Template($this->templatePath . 'css/style.css', '{{', '}}'); + + $template->setVar( + [ + 'success-low' => $this->colors->successLow(), + 'success-medium' => $this->colors->successMedium(), + 'success-high' => $this->colors->successHigh(), + 'warning' => $this->colors->warning(), + 'danger' => $this->colors->danger(), + ] + ); + + try { + $template->renderTo($this->directory($target . '_css') . 'style.css'); + } catch (Exception $e) { + throw new FileCouldNotBeWrittenException( + $e->getMessage(), + $e->getCode(), + $e + ); + } + } + private function directory(string $directory): string { - if (substr($directory, -1, 1) != DIRECTORY_SEPARATOR) { + if (!str_ends_with($directory, DIRECTORY_SEPARATOR)) { $directory .= DIRECTORY_SEPARATOR; } diff --git a/src/Report/Html/Renderer.php b/src/Report/Html/Renderer.php index fe285b186..cfc403e8c 100644 --- a/src/Report/Html/Renderer.php +++ b/src/Report/Html/Renderer.php @@ -17,6 +17,7 @@ use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; use SebastianBergmann\CodeCoverage\Node\File as FileNode; +use SebastianBergmann\CodeCoverage\Report\Thresholds; use SebastianBergmann\CodeCoverage\Version; use SebastianBergmann\Environment\Runtime; use SebastianBergmann\Template\Template; @@ -26,48 +27,19 @@ */ abstract class Renderer { - /** - * @var string - */ - protected $templatePath; - - /** - * @var string - */ - protected $generator; - - /** - * @var string - */ - protected $date; - - /** - * @var int - */ - protected $lowUpperBound; - - /** - * @var int - */ - protected $highLowerBound; - - /** - * @var bool - */ - protected $hasBranchCoverage; - - /** - * @var string - */ - protected $version; - - public function __construct(string $templatePath, string $generator, string $date, int $lowUpperBound, int $highLowerBound, bool $hasBranchCoverage) + protected string $templatePath; + protected string $generator; + protected string $date; + protected Thresholds $thresholds; + protected bool $hasBranchCoverage; + protected string $version; + + public function __construct(string $templatePath, string $generator, string $date, Thresholds $thresholds, bool $hasBranchCoverage) { $this->templatePath = $templatePath; $this->generator = $generator; $this->date = $date; - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; + $this->thresholds = $thresholds; $this->version = Version::id(); $this->hasBranchCoverage = $hasBranchCoverage; } @@ -199,8 +171,8 @@ protected function setCommonTemplateVariables(Template $template, AbstractNode $ 'version' => $this->version, 'runtime' => $this->runtimeString(), 'generator' => $this->generator, - 'low_upper_bound' => $this->lowUpperBound, - 'high_lower_bound' => $this->highLowerBound, + 'low_upper_bound' => $this->thresholds->lowUpperBound(), + 'high_lower_bound' => $this->thresholds->highLowerBound(), ] ); } @@ -288,12 +260,12 @@ protected function coverageBar(float $percent): string protected function colorLevel(float $percent): string { - if ($percent <= $this->lowUpperBound) { + if ($percent <= $this->thresholds->lowUpperBound()) { return 'danger'; } - if ($percent > $this->lowUpperBound && - $percent < $this->highLowerBound) { + if ($percent > $this->thresholds->lowUpperBound() && + $percent < $this->thresholds->highLowerBound()) { return 'warning'; } diff --git a/src/Report/Html/Renderer/Dashboard.php b/src/Report/Html/Renderer/Dashboard.php index b44870b53..4cb220e8f 100644 --- a/src/Report/Html/Renderer/Dashboard.php +++ b/src/Report/Html/Renderer/Dashboard.php @@ -18,8 +18,10 @@ use function json_encode; use function sprintf; use function str_replace; +use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\Template\Exception; use SebastianBergmann\Template\Template; /** @@ -58,7 +60,15 @@ public function render(DirectoryNode $node, string $file): void ] ); - $template->renderTo($file); + try { + $template->renderTo($file); + } catch (Exception $e) { + throw new FileCouldNotBeWrittenException( + $e->getMessage(), + $e->getCode(), + $e + ); + } } protected function activeBreadcrumb(AbstractNode $node): string @@ -188,7 +198,7 @@ private function insufficientCoverage(array $classes, string $baseLink): array foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] < $this->highLowerBound) { + if ($method['coverage'] < $this->thresholds->highLowerBound()) { $key = $methodName; if ($className !== '*') { @@ -199,7 +209,7 @@ private function insufficientCoverage(array $classes, string $baseLink): array } } - if ($class['coverage'] < $this->highLowerBound) { + if ($class['coverage'] < $this->thresholds->highLowerBound()) { $leastTestedClasses[$className] = $class['coverage']; } } @@ -242,7 +252,7 @@ private function projectRisks(array $classes, string $baseLink): array foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] < $this->highLowerBound && $method['ccn'] > 1) { + if ($method['coverage'] < $this->thresholds->highLowerBound() && $method['ccn'] > 1) { $key = $methodName; if ($className !== '*') { @@ -253,7 +263,7 @@ private function projectRisks(array $classes, string $baseLink): array } } - if ($class['coverage'] < $this->highLowerBound && + if ($class['coverage'] < $this->thresholds->highLowerBound() && $class['ccn'] > count($class['methods'])) { $classRisks[$className] = $class['crap']; } diff --git a/src/Report/Html/Renderer/Directory.php b/src/Report/Html/Renderer/Directory.php index faacbc31d..c6f885f23 100644 --- a/src/Report/Html/Renderer/Directory.php +++ b/src/Report/Html/Renderer/Directory.php @@ -12,8 +12,10 @@ use function count; use function sprintf; use function str_repeat; +use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\AbstractNode as Node; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\Template\Exception; use SebastianBergmann\Template\Template; /** @@ -45,7 +47,15 @@ public function render(DirectoryNode $node, string $file): void ] ); - $template->renderTo($file); + try { + $template->renderTo($file); + } catch (Exception $e) { + throw new FileCouldNotBeWrittenException( + $e->getMessage(), + $e->getCode(), + $e + ); + } } private function renderItem(Node $node, bool $total = false): string diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index b59dc89d3..eb98623d6 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -84,9 +84,7 @@ use function array_merge; use function array_pop; use function array_unique; -use function constant; use function count; -use function defined; use function explode; use function file_get_contents; use function htmlspecialchars; @@ -95,13 +93,14 @@ use function range; use function sort; use function sprintf; +use function str_ends_with; use function str_replace; -use function substr; use function token_get_all; use function trim; -use PHPUnit\Runner\BaseTestRunner; +use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\Util\Percentage; +use SebastianBergmann\Template\Exception; use SebastianBergmann\Template\Template; /** @@ -112,17 +111,78 @@ final class File extends Renderer /** * @psalm-var array */ - private static $keywordTokens = []; - - /** - * @var array - */ - private static $formattedSourceCache = []; - - /** - * @var int - */ - private $htmlSpecialCharsFlags = ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE; + private const KEYWORD_TOKENS = [ + T_ABSTRACT => true, + T_ARRAY => true, + T_AS => true, + T_BREAK => true, + T_CALLABLE => true, + T_CASE => true, + T_CATCH => true, + T_CLASS => true, + T_CLONE => true, + T_CONST => true, + T_CONTINUE => true, + T_DECLARE => true, + T_DEFAULT => true, + T_DO => true, + T_ECHO => true, + T_ELSE => true, + T_ELSEIF => true, + T_EMPTY => true, + T_ENDDECLARE => true, + T_ENDFOR => true, + T_ENDFOREACH => true, + T_ENDIF => true, + T_ENDSWITCH => true, + T_ENDWHILE => true, + T_ENUM => true, + T_EVAL => true, + T_EXIT => true, + T_EXTENDS => true, + T_FINAL => true, + T_FINALLY => true, + T_FN => true, + T_FOR => true, + T_FOREACH => true, + T_FUNCTION => true, + T_GLOBAL => true, + T_GOTO => true, + T_HALT_COMPILER => true, + T_IF => true, + T_IMPLEMENTS => true, + T_INCLUDE => true, + T_INCLUDE_ONCE => true, + T_INSTANCEOF => true, + T_INSTEADOF => true, + T_INTERFACE => true, + T_ISSET => true, + T_LIST => true, + T_MATCH => true, + T_NAMESPACE => true, + T_NEW => true, + T_PRINT => true, + T_PRIVATE => true, + T_PROTECTED => true, + T_PUBLIC => true, + T_READONLY => true, + T_REQUIRE => true, + T_REQUIRE_ONCE => true, + T_RETURN => true, + T_STATIC => true, + T_SWITCH => true, + T_THROW => true, + T_TRAIT => true, + T_TRY => true, + T_UNSET => true, + T_USE => true, + T_VAR => true, + T_WHILE => true, + T_YIELD => true, + T_YIELD_FROM => true, + ]; + private static array $formattedSourceCache = []; + private int $htmlSpecialCharsFlags = ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE; public function render(FileNode $node, string $file): void { @@ -139,7 +199,15 @@ public function render(FileNode $node, string $file): void ] ); - $template->renderTo($file . '.html'); + try { + $template->renderTo($file . '.html'); + } catch (Exception $e) { + throw new FileCouldNotBeWrittenException( + $e->getMessage(), + $e->getCode(), + $e + ); + } if ($this->hasBranchCoverage) { $template->setVar( @@ -151,7 +219,15 @@ public function render(FileNode $node, string $file): void ] ); - $template->renderTo($file . '_branch.html'); + try { + $template->renderTo($file . '_branch.html'); + } catch (Exception $e) { + throw new FileCouldNotBeWrittenException( + $e->getMessage(), + $e->getCode(), + $e + ); + } $template->setVar( [ @@ -162,7 +238,15 @@ public function render(FileNode $node, string $file): void ] ); - $template->renderTo($file . '_path.html'); + try { + $template->renderTo($file . '_path.html'); + } catch (Exception $e) { + throw new FileCouldNotBeWrittenException( + $e->getMessage(), + $e->getCode(), + $e + ); + } } } @@ -898,7 +982,7 @@ private function loadFile(string $file): array $result = ['']; $i = 0; $stringFlag = false; - $fileEndsWithNewLine = substr($buffer, -1) === "\n"; + $fileEndsWithNewLine = str_ends_with($buffer, "\n"); unset($buffer); @@ -1005,42 +1089,21 @@ private function createPopoverContentForTest(string $test, array $testData): str { $testCSS = ''; - if ($testData['fromTestcase']) { - switch ($testData['status']) { - case BaseTestRunner::STATUS_PASSED: - switch ($testData['size']) { - case 'small': - $testCSS = ' class="covered-by-small-tests"'; - - break; - - case 'medium': - $testCSS = ' class="covered-by-medium-tests"'; - - break; - - default: - $testCSS = ' class="covered-by-large-tests"'; - - break; - } - - break; - - case BaseTestRunner::STATUS_SKIPPED: - case BaseTestRunner::STATUS_INCOMPLETE: - case BaseTestRunner::STATUS_RISKY: - case BaseTestRunner::STATUS_WARNING: - $testCSS = ' class="warning"'; + switch ($testData['status']) { + case 'success': + $testCSS = match ($testData['size']) { + 'small' => ' class="covered-by-small-tests"', + 'medium' => ' class="covered-by-medium-tests"', + // no break + default => ' class="covered-by-large-tests"', + }; - break; + break; - case BaseTestRunner::STATUS_FAILURE: - case BaseTestRunner::STATUS_ERROR: - $testCSS = ' class="danger"'; + case 'failure': + $testCSS = ' class="danger"'; - break; - } + break; } return sprintf( @@ -1062,101 +1125,6 @@ private function isInlineHtml(int $token): bool private function isKeyword(int $token): bool { - return isset(self::keywordTokens()[$token]); - } - - /** - * @psalm-return array - */ - private static function keywordTokens(): array - { - if (self::$keywordTokens !== []) { - return self::$keywordTokens; - } - - self::$keywordTokens = [ - T_ABSTRACT => true, - T_ARRAY => true, - T_AS => true, - T_BREAK => true, - T_CALLABLE => true, - T_CASE => true, - T_CATCH => true, - T_CLASS => true, - T_CLONE => true, - T_CONST => true, - T_CONTINUE => true, - T_DECLARE => true, - T_DEFAULT => true, - T_DO => true, - T_ECHO => true, - T_ELSE => true, - T_ELSEIF => true, - T_EMPTY => true, - T_ENDDECLARE => true, - T_ENDFOR => true, - T_ENDFOREACH => true, - T_ENDIF => true, - T_ENDSWITCH => true, - T_ENDWHILE => true, - T_EVAL => true, - T_EXIT => true, - T_EXTENDS => true, - T_FINAL => true, - T_FINALLY => true, - T_FOR => true, - T_FOREACH => true, - T_FUNCTION => true, - T_GLOBAL => true, - T_GOTO => true, - T_HALT_COMPILER => true, - T_IF => true, - T_IMPLEMENTS => true, - T_INCLUDE => true, - T_INCLUDE_ONCE => true, - T_INSTANCEOF => true, - T_INSTEADOF => true, - T_INTERFACE => true, - T_ISSET => true, - T_LIST => true, - T_NAMESPACE => true, - T_NEW => true, - T_PRINT => true, - T_PRIVATE => true, - T_PROTECTED => true, - T_PUBLIC => true, - T_REQUIRE => true, - T_REQUIRE_ONCE => true, - T_RETURN => true, - T_STATIC => true, - T_SWITCH => true, - T_THROW => true, - T_TRAIT => true, - T_TRY => true, - T_UNSET => true, - T_USE => true, - T_VAR => true, - T_WHILE => true, - T_YIELD => true, - T_YIELD_FROM => true, - ]; - - if (defined('T_FN')) { - self::$keywordTokens[constant('T_FN')] = true; - } - - if (defined('T_MATCH')) { - self::$keywordTokens[constant('T_MATCH')] = true; - } - - if (defined('T_ENUM')) { - self::$keywordTokens[constant('T_ENUM')] = true; - } - - if (defined('T_READONLY')) { - self::$keywordTokens[constant('T_READONLY')] = true; - } - - return self::$keywordTokens; + return isset(self::KEYWORD_TOKENS[$token]); } } diff --git a/src/Report/Html/Renderer/Template/css/style.css b/src/Report/Html/Renderer/Template/css/style.css index 526cac0de..2e3ece54a 100644 --- a/src/Report/Html/Renderer/Template/css/style.css +++ b/src/Report/Html/Renderer/Template/css/style.css @@ -40,23 +40,23 @@ body { } .table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success { - background-color: #dff0d8; + background-color: {{success-low}}; } .table tbody tr.covered-by-medium-tests, li.covered-by-medium-tests { - background-color: #c3e3b5; + background-color: {{success-medium}}; } .table tbody tr.covered-by-small-tests, li.covered-by-small-tests { - background-color: #99cb84; + background-color: {{success-high}}; } -.table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { - background-color: #f2dede; +.table tbody tr.warning, .table tbody td.warning, li.warning, span.warning { + background-color: {{warning}}; } -.table tbody tr.warning, .table tbody td.warning, li.warning, span.warning { - background-color: #fcf8e3; +.table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { + background-color: {{danger}}; } .table tbody td.info { @@ -138,21 +138,21 @@ table + .structure-heading { } .covered-by-small-tests { - background-color: #99cb84; + background-color: {{success-high}}; } .covered-by-medium-tests { - background-color: #c3e3b5; + background-color: {{success-medium}}; } .covered-by-large-tests { - background-color: #dff0d8; + background-color: {{success-low}}; } .not-covered { - background-color: #f2dede; + background-color: {{danger}}; } .not-coverable { - background-color: #fcf8e3; + background-color: {{warning}}; } diff --git a/src/Report/PHP.php b/src/Report/PHP.php index 2058fb39a..1f8186d04 100644 --- a/src/Report/PHP.php +++ b/src/Report/PHP.php @@ -21,6 +21,8 @@ final class PHP { public function process(CodeCoverage $coverage, ?string $target = null): string { + $coverage->clearCache(); + $buffer = "lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; + $this->thresholds = $thresholds; $this->showUncoveredFiles = $showUncoveredFiles; $this->showOnlySummary = $showOnlySummary; } @@ -96,7 +73,6 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin 'branches' => '', 'paths' => '', 'reset' => '', - 'eol' => '', ]; if ($showColors) { @@ -127,7 +103,6 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $colors['reset'] = self::COLOR_RESET; $colors['header'] = self::COLOR_HEADER; - $colors['eol'] = self::COLOR_EOL; } $classes = sprintf( @@ -306,11 +281,11 @@ private function coverageColor(int $numberOfCoveredElements, int $totalNumberOfE $totalNumberOfElements ); - if ($coverage->asFloat() >= $this->highLowerBound) { + if ($coverage->asFloat() >= $this->thresholds->highLowerBound()) { return self::COLOR_GREEN; } - if ($coverage->asFloat() > $this->lowUpperBound) { + if ($coverage->asFloat() > $this->thresholds->lowUpperBound()) { return self::COLOR_YELLOW; } @@ -329,10 +304,7 @@ private function printCoverageCounts(int $numberOfCoveredElements, int $totalNum sprintf($format, $totalNumberOfElements) . ')'; } - /** - * @param false|string $string - */ - private function format(string $color, int $padding, $string): string + private function format(string $color, int $padding, string|false $string): string { $reset = $color ? self::COLOR_RESET : ''; diff --git a/src/Report/Thresholds.php b/src/Report/Thresholds.php new file mode 100644 index 000000000..a1fa6e9f1 --- /dev/null +++ b/src/Report/Thresholds.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report; + +use SebastianBergmann\CodeCoverage\InvalidArgumentException; + +/** + * @psalm-immutable + */ +final class Thresholds +{ + private readonly int $lowUpperBound; + private readonly int $highLowerBound; + + public static function default(): self + { + return new self(50, 90); + } + + /** + * @throws InvalidArgumentException + */ + public static function from(int $lowUpperBound, int $highLowerBound): self + { + if ($lowUpperBound > $highLowerBound) { + throw new InvalidArgumentException( + '$lowUpperBound must not be larger than $highLowerBound' + ); + } + + return new self($lowUpperBound, $highLowerBound); + } + + private function __construct(int $lowUpperBound, int $highLowerBound) + { + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + } + + public function lowUpperBound(): int + { + return $this->lowUpperBound; + } + + public function highLowerBound(): int + { + return $this->highLowerBound; + } +} diff --git a/src/Report/Xml/BuildInformation.php b/src/Report/Xml/BuildInformation.php index ebdbae612..fac06ff4f 100644 --- a/src/Report/Xml/BuildInformation.php +++ b/src/Report/Xml/BuildInformation.php @@ -9,7 +9,6 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function constant; use function phpversion; use DateTimeImmutable; use DOMElement; @@ -20,10 +19,7 @@ */ final class BuildInformation { - /** - * @var DOMElement - */ - private $contextNode; + private readonly DOMElement $contextNode; public function __construct(DOMElement $contextNode) { @@ -40,11 +36,6 @@ public function setRuntimeInformation(Runtime $runtime): void $driverNode = $this->nodeByName('driver'); - if ($runtime->hasPHPDBGCodeCoverage()) { - $driverNode->setAttribute('name', 'phpdbg'); - $driverNode->setAttribute('version', constant('PHPDBG_VERSION')); - } - if ($runtime->hasXdebug()) { $driverNode->setAttribute('name', 'xdebug'); $driverNode->setAttribute('version', phpversion('xdebug')); diff --git a/src/Report/Xml/Coverage.php b/src/Report/Xml/Coverage.php index b556d8205..9c853ec25 100644 --- a/src/Report/Xml/Coverage.php +++ b/src/Report/Xml/Coverage.php @@ -18,20 +18,9 @@ */ final class Coverage { - /** - * @var XMLWriter - */ - private $writer; - - /** - * @var DOMElement - */ - private $contextNode; - - /** - * @var bool - */ - private $finalized = false; + private readonly XMLWriter $writer; + private readonly DOMElement $contextNode; + private bool $finalized = false; public function __construct(DOMElement $context, string $line) { diff --git a/src/Report/Xml/Facade.php b/src/Report/Xml/Facade.php index 3ecc7506f..8788200c4 100644 --- a/src/Report/Xml/Facade.php +++ b/src/Report/Xml/Facade.php @@ -40,20 +40,9 @@ final class Facade { - /** - * @var string - */ - private $target; - - /** - * @var Project - */ - private $project; - - /** - * @var string - */ - private $phpUnitVersion; + private string $target; + private Project $project; + private readonly string $phpUnitVersion; public function __construct(string $version) { diff --git a/src/Report/Xml/File.php b/src/Report/Xml/File.php index 245c5cee6..a1da30a7d 100644 --- a/src/Report/Xml/File.php +++ b/src/Report/Xml/File.php @@ -17,15 +17,8 @@ */ class File { - /** - * @var DOMDocument - */ - private $dom; - - /** - * @var DOMElement - */ - private $contextNode; + private readonly DOMDocument $dom; + private readonly DOMElement $contextNode; public function __construct(DOMElement $context) { diff --git a/src/Report/Xml/Method.php b/src/Report/Xml/Method.php index 7e3009997..b1ab9ae53 100644 --- a/src/Report/Xml/Method.php +++ b/src/Report/Xml/Method.php @@ -16,10 +16,7 @@ */ final class Method { - /** - * @var DOMElement - */ - private $contextNode; + private readonly DOMElement $contextNode; public function __construct(DOMElement $context, string $name) { diff --git a/src/Report/Xml/Node.php b/src/Report/Xml/Node.php index 159923093..bd14c8d64 100644 --- a/src/Report/Xml/Node.php +++ b/src/Report/Xml/Node.php @@ -17,15 +17,8 @@ */ abstract class Node { - /** - * @var DOMDocument - */ - private $dom; - - /** - * @var DOMElement - */ - private $contextNode; + private DOMDocument $dom; + private DOMElement $contextNode; public function __construct(DOMElement $context) { diff --git a/src/Report/Xml/Source.php b/src/Report/Xml/Source.php index 2b67ce1da..f6171f8ea 100644 --- a/src/Report/Xml/Source.php +++ b/src/Report/Xml/Source.php @@ -19,8 +19,7 @@ */ final class Source { - /** @var DOMElement */ - private $context; + private readonly DOMElement $context; public function __construct(DOMElement $context) { diff --git a/src/Report/Xml/Tests.php b/src/Report/Xml/Tests.php index c6da4145b..e56df70e6 100644 --- a/src/Report/Xml/Tests.php +++ b/src/Report/Xml/Tests.php @@ -13,26 +13,21 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type TestType from \SebastianBergmann\CodeCoverage\CodeCoverage */ final class Tests { - private $contextNode; - private $codeMap = [ - -1 => 'UNKNOWN', // PHPUnit_Runner_BaseTestRunner::STATUS_UNKNOWN - 0 => 'PASSED', // PHPUnit_Runner_BaseTestRunner::STATUS_PASSED - 1 => 'SKIPPED', // PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED - 2 => 'INCOMPLETE', // PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE - 3 => 'FAILURE', // PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE - 4 => 'ERROR', // PHPUnit_Runner_BaseTestRunner::STATUS_ERROR - 5 => 'RISKY', // PHPUnit_Runner_BaseTestRunner::STATUS_RISKY - 6 => 'WARNING', // PHPUnit_Runner_BaseTestRunner::STATUS_WARNING - ]; + private readonly DOMElement $contextNode; public function __construct(DOMElement $context) { $this->contextNode = $context; } + /** + * @param TestType $result + */ public function addTest(string $test, array $result): void { $node = $this->contextNode->appendChild( @@ -44,7 +39,6 @@ public function addTest(string $test, array $result): void $node->setAttribute('name', $test); $node->setAttribute('size', $result['size']); - $node->setAttribute('result', (string) $result['status']); - $node->setAttribute('status', $this->codeMap[(int) $result['status']]); + $node->setAttribute('status', $result['status']); } } diff --git a/src/Report/Xml/Totals.php b/src/Report/Xml/Totals.php index 370813188..d4f8f795a 100644 --- a/src/Report/Xml/Totals.php +++ b/src/Report/Xml/Totals.php @@ -19,35 +19,12 @@ */ final class Totals { - /** - * @var DOMNode - */ - private $container; - - /** - * @var DOMElement - */ - private $linesNode; - - /** - * @var DOMElement - */ - private $methodsNode; - - /** - * @var DOMElement - */ - private $functionsNode; - - /** - * @var DOMElement - */ - private $classesNode; - - /** - * @var DOMElement - */ - private $traitsNode; + private readonly DOMNode $container; + private readonly DOMElement $linesNode; + private readonly DOMElement $methodsNode; + private readonly DOMElement $functionsNode; + private readonly DOMElement $classesNode; + private readonly DOMElement $traitsNode; public function __construct(DOMElement $container) { diff --git a/src/Report/Xml/Unit.php b/src/Report/Xml/Unit.php index d84dc481f..2a4feb99a 100644 --- a/src/Report/Xml/Unit.php +++ b/src/Report/Xml/Unit.php @@ -16,10 +16,7 @@ */ final class Unit { - /** - * @var DOMElement - */ - private $contextNode; + private readonly DOMElement $contextNode; public function __construct(DOMElement $context, string $name) { diff --git a/src/StaticAnalysis/CachingFileAnalyser.php b/src/StaticAnalysis/CachingFileAnalyser.php index 63e6e22ba..879f569ed 100644 --- a/src/StaticAnalysis/CachingFileAnalyser.php +++ b/src/StaticAnalysis/CachingFileAnalyser.php @@ -21,38 +21,17 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser */ final class CachingFileAnalyser implements FileAnalyser { - /** - * @var ?string - */ - private static $cacheVersion; - - /** - * @var string - */ - private $directory; - - /** - * @var FileAnalyser - */ - private $analyser; - - /** - * @var bool - */ - private $useAnnotationsForIgnoringCode; - - /** - * @var bool - */ - private $ignoreDeprecatedCode; - - /** - * @var array - */ - private $cache = []; + private static ?string $cacheVersion = null; + private readonly string $directory; + private readonly FileAnalyser $analyser; + private readonly bool $useAnnotationsForIgnoringCode; + private readonly bool $ignoreDeprecatedCode; + private array $cache = []; public function __construct(string $directory, FileAnalyser $analyser, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) { @@ -92,7 +71,7 @@ public function functionsIn(string $filename): array } /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + * @psalm-return LinesOfCodeType */ public function linesOfCodeFor(string $filename): array { @@ -143,10 +122,7 @@ public function process(string $filename): void $this->write($filename, $this->cache[$filename]); } - /** - * @return mixed - */ - private function read(string $filename) + private function read(string $filename): array|false { $cacheFile = $this->cacheFile($filename); @@ -160,10 +136,7 @@ private function read(string $filename) ); } - /** - * @param mixed $data - */ - private function write(string $filename, $data): void + private function write(string $filename, array $data): void { file_put_contents( $this->cacheFile($filename), diff --git a/src/StaticAnalysis/CodeUnitFindingVisitor.php b/src/StaticAnalysis/CodeUnitFindingVisitor.php index cb85cd61e..265c151e6 100644 --- a/src/StaticAnalysis/CodeUnitFindingVisitor.php +++ b/src/StaticAnalysis/CodeUnitFindingVisitor.php @@ -26,30 +26,63 @@ use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Trait_; use PhpParser\Node\UnionType; -use PhpParser\NodeAbstract; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-type CodeUnitFunctionType = array{ + * name: string, + * namespacedName: string, + * namespace: string, + * signature: string, + * startLine: int, + * endLine: int, + * ccn: int + * } + * @psalm-type CodeUnitMethodType = array{ + * methodName: string, + * signature: string, + * visibility: string, + * startLine: int, + * endLine: int, + * ccn: int + * } + * @psalm-type CodeUnitClassType = array{ + * name: string, + * namespacedName: string, + * namespace: string, + * startLine: int, + * endLine: int, + * methods: array + * } + * @psalm-type CodeUnitTraitType = array{ + * name: string, + * namespacedName: string, + * namespace: string, + * startLine: int, + * endLine: int, + * methods: array + * } */ final class CodeUnitFindingVisitor extends NodeVisitorAbstract { /** - * @psalm-var array}> + * @psalm-var array */ - private $classes = []; + private array $classes = []; /** - * @psalm-var array}> + * @psalm-var array */ - private $traits = []; + private array $traits = []; /** - * @psalm-var array + * @psalm-var array */ - private $functions = []; + private array $functions = []; public function enterNode(Node $node): void { @@ -85,7 +118,7 @@ public function enterNode(Node $node): void } /** - * @psalm-return array}> + * @psalm-return array */ public function classes(): array { @@ -93,7 +126,7 @@ public function classes(): array } /** - * @psalm-return array}> + * @psalm-return array */ public function traits(): array { @@ -101,20 +134,15 @@ public function traits(): array } /** - * @psalm-return array + * @psalm-return array */ public function functions(): array { return $this->functions; } - /** - * @psalm-param ClassMethod|Function_ $node - */ - private function cyclomaticComplexity(Node $node): int + private function cyclomaticComplexity(ClassMethod|Function_ $node): int { - assert($node instanceof ClassMethod || $node instanceof Function_); - $nodes = $node->getStmts(); if ($nodes === null) { @@ -133,13 +161,8 @@ private function cyclomaticComplexity(Node $node): int return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); } - /** - * @psalm-param ClassMethod|Function_ $node - */ - private function signature(Node $node): string + private function signature(ClassMethod|Function_ $node): string { - assert($node instanceof ClassMethod || $node instanceof Function_); - $signature = ($node->returnsByRef() ? '&' : '') . $node->name->toString() . '('; $parameters = []; @@ -170,13 +193,8 @@ private function signature(Node $node): string return $signature; } - /** - * @psalm-param Identifier|Name|ComplexType $type - */ - private function type(Node $type): string + private function type(Identifier|Name|ComplexType $type): string { - assert($type instanceof Identifier || $type instanceof Name || $type instanceof ComplexType); - if ($type instanceof NullableType) { return '?' . $type->type; } @@ -331,10 +349,7 @@ private function intersectionTypeAsString(IntersectionType $node): string return implode('&', $types); } - /** - * @psalm-param Identifier|Name $node $node - */ - private function typeAsString(NodeAbstract $node): string + private function typeAsString(Identifier|Name $node): string { if ($node instanceof Name) { return $node->toCodeString(); diff --git a/src/StaticAnalysis/ExecutableLinesFindingVisitor.php b/src/StaticAnalysis/ExecutableLinesFindingVisitor.php index eadff1cf0..4ce6e4366 100644 --- a/src/StaticAnalysis/ExecutableLinesFindingVisitor.php +++ b/src/StaticAnalysis/ExecutableLinesFindingVisitor.php @@ -26,33 +26,28 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser */ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract { - /** - * @var int - */ - private $nextBranch = 0; - - /** - * @var string - */ - private $source; + private int $nextBranch = 0; + private readonly string $source; /** - * @var array + * @psalm-var LinesType */ - private $executableLinesGroupedByBranch = []; + private array $executableLinesGroupedByBranch = []; /** - * @var array + * @psalm-var array */ - private $unsets = []; + private array $unsets = []; /** - * @var array + * @psalm-var array */ - private $commentsToCheckForUnset = []; + private array $commentsToCheckForUnset = []; public function __construct(string $source) { @@ -360,6 +355,9 @@ public function afterTraverse(array $nodes): void ); } + /** + * @psalm-return LinesType + */ public function executableLinesGroupedByBranch(): array { return $this->executableLinesGroupedByBranch; diff --git a/src/StaticAnalysis/FileAnalyser.php b/src/StaticAnalysis/FileAnalyser.php index 3dbcf68f6..f260341b1 100644 --- a/src/StaticAnalysis/FileAnalyser.php +++ b/src/StaticAnalysis/FileAnalyser.php @@ -11,21 +11,50 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser + * @psalm-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser + * + * @psalm-type LinesOfCodeType = array{ + * linesOfCode: int, + * commentLinesOfCode: int, + * nonCommentLinesOfCode: int + * } + * @psalm-type LinesType = array */ interface FileAnalyser { + /** + * @psalm-return array + */ public function classesIn(string $filename): array; + /** + * @psalm-return array + */ public function traitsIn(string $filename): array; + /** + * @psalm-return array + */ public function functionsIn(string $filename): array; /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + * @psalm-return LinesOfCodeType */ public function linesOfCodeFor(string $filename): array; + /** + * @psalm-return LinesType + */ public function executableLinesIn(string $filename): array; + /** + * @psalm-return LinesType + */ public function ignoredLinesFor(string $filename): array; } diff --git a/src/StaticAnalysis/IgnoredLinesFindingVisitor.php b/src/StaticAnalysis/IgnoredLinesFindingVisitor.php index 3c0b2373c..7480e6fba 100644 --- a/src/StaticAnalysis/IgnoredLinesFindingVisitor.php +++ b/src/StaticAnalysis/IgnoredLinesFindingVisitor.php @@ -9,10 +9,8 @@ */ namespace SebastianBergmann\CodeCoverage\StaticAnalysis; -use function array_merge; use function assert; -use function range; -use function strpos; +use function str_contains; use PhpParser\Node; use PhpParser\Node\Attribute; use PhpParser\Node\Stmt\Class_; @@ -28,19 +26,11 @@ final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract { /** - * @psalm-var list + * @psalm-var array */ - private $ignoredLines = []; - - /** - * @var bool - */ - private $useAnnotationsForIgnoringCode; - - /** - * @var bool - */ - private $ignoreDeprecated; + private array $ignoredLines = []; + private readonly bool $useAnnotationsForIgnoringCode; + private readonly bool $ignoreDeprecated; public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecated) { @@ -83,11 +73,23 @@ public function enterNode(Node $node): void return; } + if ($node instanceof Attribute && + $node->name->toString() === 'PHPUnit\Framework\Attributes\CodeCoverageIgnore') { + $attributeGroup = $node->getAttribute('parent'); + $attributedNode = $attributeGroup->getAttribute('parent'); + + for ($line = $attributedNode->getStartLine(); $line <= $attributedNode->getEndLine(); $line++) { + $this->ignoredLines[] = $line; + } + + return; + } + $this->processDocComment($node); } /** - * @psalm-return list + * @psalm-return array */ public function ignoredLines(): array { @@ -102,18 +104,16 @@ private function processDocComment(Node $node): void return; } - if (strpos($docComment->getText(), '@codeCoverageIgnore') !== false) { - $this->ignoredLines = array_merge( - $this->ignoredLines, - range($node->getStartLine(), $node->getEndLine()) - ); + if (str_contains($docComment->getText(), '@codeCoverageIgnore')) { + for ($line = $node->getStartLine(); $line <= $node->getEndLine(); $line++) { + $this->ignoredLines[] = $line; + } } - if ($this->ignoreDeprecated && strpos($docComment->getText(), '@deprecated') !== false) { - $this->ignoredLines = array_merge( - $this->ignoredLines, - range($node->getStartLine(), $node->getEndLine()) - ); + if ($this->ignoreDeprecated && str_contains($docComment->getText(), '@deprecated')) { + for ($line = $node->getStartLine(); $line <= $node->getEndLine(); $line++) { + $this->ignoredLines[] = $line; + } } } } diff --git a/src/StaticAnalysis/ParsingFileAnalyser.php b/src/StaticAnalysis/ParsingFileAnalyser.php index e68638219..3d1b5c886 100644 --- a/src/StaticAnalysis/ParsingFileAnalyser.php +++ b/src/StaticAnalysis/ParsingFileAnalyser.php @@ -32,48 +32,47 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @psalm-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor + * @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser + * @psalm-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser */ final class ParsingFileAnalyser implements FileAnalyser { /** - * @var array - */ - private $classes = []; - - /** - * @var array - */ - private $traits = []; - - /** - * @var array + * @psalm-var array> */ - private $functions = []; + private array $classes = []; /** - * @var array + * @psalm-var array> */ - private $linesOfCode = []; + private array $traits = []; /** - * @var array + * @psalm-var array> */ - private $ignoredLines = []; + private array $functions = []; /** - * @var array + * @var array */ - private $executableLines = []; + private array $linesOfCode = []; /** - * @var bool + * @var array */ - private $useAnnotationsForIgnoringCode; + private array $ignoredLines = []; /** - * @var bool + * @var array */ - private $ignoreDeprecatedCode; + private array $executableLines = []; + private readonly bool $useAnnotationsForIgnoringCode; + private readonly bool $ignoreDeprecatedCode; public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) { @@ -102,9 +101,6 @@ public function functionsIn(string $filename): array return $this->functions[$filename]; } - /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} - */ public function linesOfCodeFor(string $filename): array { $this->analyse($filename); @@ -142,6 +138,8 @@ private function analyse(string $filename): void $linesOfCode = 1; } + assert($linesOfCode > 0); + $parser = (new ParserFactory)->create( ParserFactory::PREFER_PHP7, new Lexer diff --git a/src/TestSize/Known.php b/src/TestSize/Known.php new file mode 100644 index 000000000..785fa8656 --- /dev/null +++ b/src/TestSize/Known.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @psalm-immutable + */ +abstract class Known extends TestSize +{ + /** + * @psalm-assert-if-true Known $this + */ + public function isKnown(): bool + { + return true; + } + + abstract public function isGreaterThan(self $other): bool; +} diff --git a/src/TestSize/Large.php b/src/TestSize/Large.php new file mode 100644 index 000000000..cdf3441df --- /dev/null +++ b/src/TestSize/Large.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @psalm-immutable + */ +final class Large extends Known +{ + /** + * @psalm-assert-if-true Large $this + */ + public function isLarge(): bool + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return !$other->isLarge(); + } + + public function asString(): string + { + return 'large'; + } +} diff --git a/src/TestSize/Medium.php b/src/TestSize/Medium.php new file mode 100644 index 000000000..309655461 --- /dev/null +++ b/src/TestSize/Medium.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @psalm-immutable + */ +final class Medium extends Known +{ + /** + * @psalm-assert-if-true Medium $this + */ + public function isMedium(): bool + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return $other->isSmall(); + } + + public function asString(): string + { + return 'medium'; + } +} diff --git a/src/TestSize/Small.php b/src/TestSize/Small.php new file mode 100644 index 000000000..6697bdef5 --- /dev/null +++ b/src/TestSize/Small.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @psalm-immutable + */ +final class Small extends Known +{ + /** + * @psalm-assert-if-true Small $this + */ + public function isSmall(): bool + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return false; + } + + public function asString(): string + { + return 'small'; + } +} diff --git a/src/TestSize/TestSize.php b/src/TestSize/TestSize.php new file mode 100644 index 000000000..0eacc59a4 --- /dev/null +++ b/src/TestSize/TestSize.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @psalm-immutable + */ +abstract class TestSize +{ + public static function unknown(): self + { + return new Unknown; + } + + public static function small(): self + { + return new Small; + } + + public static function medium(): self + { + return new Medium; + } + + public static function large(): self + { + return new Large; + } + + /** + * @psalm-assert-if-true Known $this + */ + public function isKnown(): bool + { + return false; + } + + /** + * @psalm-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @psalm-assert-if-true Small $this + */ + public function isSmall(): bool + { + return false; + } + + /** + * @psalm-assert-if-true Medium $this + */ + public function isMedium(): bool + { + return false; + } + + /** + * @psalm-assert-if-true Large $this + */ + public function isLarge(): bool + { + return false; + } + + abstract public function asString(): string; +} diff --git a/src/TestSize/Unknown.php b/src/TestSize/Unknown.php new file mode 100644 index 000000000..24116f946 --- /dev/null +++ b/src/TestSize/Unknown.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @psalm-immutable + */ +final class Unknown extends TestSize +{ + /** + * @psalm-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return true; + } + + public function asString(): string + { + return 'unknown'; + } +} diff --git a/src/TestStatus/Failure.php b/src/TestStatus/Failure.php new file mode 100644 index 000000000..1867d81d9 --- /dev/null +++ b/src/TestStatus/Failure.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @psalm-immutable + */ +final class Failure extends Known +{ + /** + * @psalm-assert-if-true Failure $this + */ + public function isFailure(): bool + { + return true; + } + + public function asString(): string + { + return 'failure'; + } +} diff --git a/src/TestStatus/Known.php b/src/TestStatus/Known.php new file mode 100644 index 000000000..9c0a52a5d --- /dev/null +++ b/src/TestStatus/Known.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @psalm-immutable + */ +abstract class Known extends TestStatus +{ + /** + * @psalm-assert-if-true Known $this + */ + public function isKnown(): bool + { + return true; + } +} diff --git a/src/TestStatus/Success.php b/src/TestStatus/Success.php new file mode 100644 index 000000000..dcb81a3ca --- /dev/null +++ b/src/TestStatus/Success.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @psalm-immutable + */ +final class Success extends Known +{ + /** + * @psalm-assert-if-true Success $this + */ + public function isSuccess(): bool + { + return true; + } + + public function asString(): string + { + return 'success'; + } +} diff --git a/src/TestStatus/TestStatus.php b/src/TestStatus/TestStatus.php new file mode 100644 index 000000000..5d532eaac --- /dev/null +++ b/src/TestStatus/TestStatus.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @psalm-immutable + */ +abstract class TestStatus +{ + public static function unknown(): self + { + return new Unknown; + } + + public static function success(): self + { + return new Success; + } + + public static function failure(): self + { + return new Failure; + } + + /** + * @psalm-assert-if-true Known $this + */ + public function isKnown(): bool + { + return false; + } + + /** + * @psalm-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @psalm-assert-if-true Success $this + */ + public function isSuccess(): bool + { + return false; + } + + /** + * @psalm-assert-if-true Failure $this + */ + public function isFailure(): bool + { + return false; + } + + abstract public function asString(): string; +} diff --git a/src/TestStatus/Unknown.php b/src/TestStatus/Unknown.php new file mode 100644 index 000000000..d9183fb33 --- /dev/null +++ b/src/TestStatus/Unknown.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @psalm-immutable + */ +final class Unknown extends TestStatus +{ + /** + * @psalm-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return true; + } + + public function asString(): string + { + return 'unknown'; + } +} diff --git a/src/Util/Percentage.php b/src/Util/Percentage.php index 0f7a3fec1..a69f2366e 100644 --- a/src/Util/Percentage.php +++ b/src/Util/Percentage.php @@ -16,15 +16,8 @@ */ final class Percentage { - /** - * @var float - */ - private $fraction; - - /** - * @var float - */ - private $total; + private readonly float $fraction; + private readonly float $total; public static function fromFractionAndTotal(float $fraction, float $total): self { diff --git a/src/Version.php b/src/Version.php index 3084a2c62..f8b9d17eb 100644 --- a/src/Version.php +++ b/src/Version.php @@ -14,15 +14,12 @@ final class Version { - /** - * @var string - */ - private static $version; + private static string $version = ''; public static function id(): string { - if (self::$version === null) { - self::$version = (new VersionId('9.2.27', dirname(__DIR__)))->getVersion(); + if (self::$version === '') { + self::$version = (new VersionId('10.1.4', dirname(__DIR__)))->asString(); } return self::$version; diff --git a/tests/TestCase.php b/tests/TestCase.php index 1ddb311f3..e6e34660c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,7 +9,7 @@ */ namespace SebastianBergmann\CodeCoverage; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; use function array_merge; use function range; use function rmdir; @@ -1021,39 +1021,44 @@ protected function getLineCoverageForBankAccount(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); $coverage->start( - new BankAccountTest('testBalanceIsInitiallyZero'), + 'BankAccountTest::testBalanceIsInitiallyZero', + null, true ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(6, 9)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative') + 'BankAccountTest::testBalanceCannotBecomeNegative' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(27, 32)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative2') + 'BankAccountTest::testBalanceCannotBecomeNegative2' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(20, 25)] ); $coverage->start( - new BankAccountTest('testDepositWithdrawMoney') + 'BankAccountTest::testDepositWithdrawMoney' ); $coverage->stop( true, + null, [ TEST_FILES_PATH . 'BankAccount.php' => array_merge( range(6, 9), @@ -1083,39 +1088,44 @@ protected function getPathCoverageForBankAccount(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); $coverage->start( - new BankAccountTest('testBalanceIsInitiallyZero'), + 'BankAccountTest::testBalanceIsInitiallyZero', + null, true ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(6, 9)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative') + 'BankAccountTest::testBalanceCannotBecomeNegative' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(27, 32)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative2') + 'BankAccountTest::testBalanceCannotBecomeNegative2' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(20, 25)] ); $coverage->start( - new BankAccountTest('testDepositWithdrawMoney') + 'BankAccountTest::testDepositWithdrawMoney' ); $coverage->stop( true, + null, [ TEST_FILES_PATH . 'BankAccount.php' => array_merge( range(6, 9), @@ -1146,6 +1156,7 @@ protected function getPathCoverageForSourceWithoutNamespace(): CodeCoverage $coverage->start( 'faketest', + null, true ); @@ -1221,39 +1232,44 @@ protected function getLineCoverageForNamespacedBankAccount(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); $coverage->start( - new BankAccountTest('testBalanceIsInitiallyZero'), + 'BankAccountTest::testBalanceIsInitiallyZero', + null, true ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'NamespacedBankAccount.php' => range(11, 14)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative') + 'BankAccountTest::testBalanceCannotBecomeNegative' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'NamespacedBankAccount.php' => range(32, 37)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative2') + 'BankAccountTest::testBalanceCannotBecomeNegative2' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'NamespacedBankAccount.php' => range(25, 30)] ); $coverage->start( - new BankAccountTest('testDepositWithdrawMoney') + 'BankAccountTest::testDepositWithdrawMoney' ); $coverage->stop( true, + null, [ TEST_FILES_PATH . 'NamespacedBankAccount.php' => array_merge( range(11, 14), @@ -1281,21 +1297,24 @@ protected function getLineCoverageForBankAccountForFirstTwoTests(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); $coverage->start( - new BankAccountTest('testBalanceIsInitiallyZero'), + 'BankAccountTest::testBalanceIsInitiallyZero', + null, true ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(6, 9)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative') + 'BankAccountTest::testBalanceCannotBecomeNegative' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(27, 32)] ); @@ -1317,20 +1336,22 @@ protected function getLineCoverageForBankAccountForLastTwoTests(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative2') + 'BankAccountTest::testBalanceCannotBecomeNegative2' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(20, 25)] ); $coverage->start( - new BankAccountTest('testDepositWithdrawMoney') + 'BankAccountTest::testDepositWithdrawMoney' ); $coverage->stop( true, + null, [ TEST_FILES_PATH . 'BankAccount.php' => array_merge( range(6, 9), @@ -1418,21 +1439,24 @@ protected function getPathCoverageForBankAccountForFirstTwoTests(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); $coverage->start( - new BankAccountTest('testBalanceIsInitiallyZero'), + 'BankAccountTest::testBalanceIsInitiallyZero', + null, true ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(6, 9)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative') + 'BankAccountTest::testBalanceCannotBecomeNegative' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(27, 32)] ); @@ -1454,20 +1478,22 @@ protected function getPathCoverageForBankAccountForLastTwoTests(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative2') + 'BankAccountTest::testBalanceCannotBecomeNegative2' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(20, 25)] ); $coverage->start( - new BankAccountTest('testDepositWithdrawMoney') + 'BankAccountTest::testDepositWithdrawMoney' ); $coverage->stop( true, + null, [ TEST_FILES_PATH . 'BankAccount.php' => array_merge( range(6, 9), @@ -1693,7 +1719,7 @@ protected function getCoverageForFileWithIgnoredLines(): CodeCoverage $filter ); - $coverage->start('FileWithIgnoredLines', true); + $coverage->start('FileWithIgnoredLines', null, true); $coverage->stop(); return $coverage; @@ -1727,7 +1753,7 @@ protected function getLineCoverageForFileWithEval(): CodeCoverage $filter ); - $coverage->start('FileWithEval', true); + $coverage->start('FileWithEval', null, true); $coverage->stop(); return $coverage; @@ -1763,7 +1789,7 @@ protected function getCoverageForClassWithAnonymousFunction(): CodeCoverage $filter ); - $coverage->start('ClassWithAnonymousFunction', true); + $coverage->start('ClassWithAnonymousFunction', null, true); $coverage->stop(); return $coverage; @@ -1803,7 +1829,7 @@ protected function getCoverageForClassWithOutsideFunction(): CodeCoverage $filter ); - $coverage->start('ClassWithOutsideFunction', true); + $coverage->start('ClassWithOutsideFunction', null, true); $coverage->stop(); return $coverage; @@ -1859,39 +1885,44 @@ protected function getCoverageForFilesWithUncoveredIncluded(): CodeCoverage $coverage->includeUncoveredFiles(); $coverage->start( - new BankAccountTest('testBalanceIsInitiallyZero'), + 'BankAccountTest::testBalanceIsInitiallyZero', + null, true ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(6, 9)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative') + 'BankAccountTest::testBalanceCannotBecomeNegative' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(27, 32)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative2') + 'BankAccountTest::testBalanceCannotBecomeNegative2' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(20, 25)] ); $coverage->start( - new BankAccountTest('testDepositWithdrawMoney') + 'BankAccountTest::testDepositWithdrawMoney' ); $coverage->stop( true, + null, [ TEST_FILES_PATH . 'BankAccount.php' => array_merge( range(6, 9), @@ -1921,39 +1952,44 @@ protected function getCoverageForFilesWithUncoveredExcluded(): CodeCoverage $coverage->excludeUncoveredFiles(); $coverage->start( - new BankAccountTest('testBalanceIsInitiallyZero'), + 'BankAccountTest::testBalanceIsInitiallyZero', + null, true ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(6, 9)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative') + 'BankAccountTest::testBalanceCannotBecomeNegative' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(27, 32)] ); $coverage->start( - new BankAccountTest('testBalanceCannotBecomeNegative2') + 'BankAccountTest::testBalanceCannotBecomeNegative2' ); $coverage->stop( true, + null, [TEST_FILES_PATH . 'BankAccount.php' => range(20, 25)] ); $coverage->start( - new BankAccountTest('testDepositWithdrawMoney') + 'BankAccountTest::testDepositWithdrawMoney' ); $coverage->stop( true, + null, [ TEST_FILES_PATH . 'BankAccount.php' => array_merge( range(6, 9), diff --git a/tests/_files/Report/HTML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/dashboard.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/dashboard.html similarity index 100% rename from tests/_files/Report/HTML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/dashboard.html rename to tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/dashboard.html diff --git a/tests/_files/Report/HTML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/index.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html similarity index 100% rename from tests/_files/Report/HTML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/index.html rename to tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html diff --git a/tests/_files/Report/HTML/PHP81AndUp/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html similarity index 100% rename from tests/_files/Report/HTML/PHP81AndUp/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html rename to tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html diff --git a/tests/_files/Report/HTML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html b/tests/_files/Report/HTML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html deleted file mode 100644 index 68eb0f7b2..000000000 --- a/tests/_files/Report/HTML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - Code Coverage for %s%esource_with_class_and_anonymous_function.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
8 / 8
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CoveredClassWithAnonymousFunctionInStaticMethod
-
- 100.00% covered (success) -
-
-
100.00%
8 / 8
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 runAnonymous
-
- 100.00% covered (success) -
-
-
100.00%
8 / 8
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3class CoveredClassWithAnonymousFunctionInStaticMethod
4{
5    public static function runAnonymous()
6    {
7        $filter = ['abc124', 'abc123', '123'];
8
9        array_walk(
10            $filter,
11            function (&$val, $key) {
12                $val = preg_replace('|[^0-9]|', '', $val);
13            }
14        );
15
16        // Should be covered
17        $extravar = true;
18    }
19}
- - -
-
-

Legend

-

Covered by small (and larger) testsCovered by medium (and large) testsCovered by large tests (and tests of unknown size)Not coveredNot coverable

-

- Generated by php-code-coverage %s using %s at %s. -

- - - -
-
- - - - - - diff --git a/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php.html b/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php.html deleted file mode 100644 index ce260f70f..000000000 --- a/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - Code Coverage for %ssource_without_namespace.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CRAP
n/a
0 / 0
foo
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
6
Foo
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - -
1<?php
2/**
3 * Represents foo.
4 */
5class Foo
6{
7}
8
9/**
10 * @param mixed $bar
11 */
12function &foo($bar)
13{
14    $baz = function () {};
15    $a   = true ? true : false;
16    $b   = "{$a}";
17    $c   = "${b}";
18}
- - -
-
-

Legend

-

Covered by small (and larger) testsCovered by medium (and large) testsCovered by large tests (and tests of unknown size)Not coveredNot coverable

-

- Generated by php-code-coverage %s using %s at %s. -

- - - -
-
- - - - - - diff --git a/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_branch.html b/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_branch.html deleted file mode 100644 index 236200a66..000000000 --- a/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_branch.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - Code Coverage for %ssource_without_namespace.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CRAP
n/a
0 / 0
foo
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
6
Foo
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - -
1<?php
2/**
3 * Represents foo.
4 */
5class Foo
6{
7}
8
9/**
10 * @param mixed $bar
11 */
12function &foo($bar)
13{
14    $baz = function () {};
15    $a   = true ? true : false;
16    $b   = "{$a}";
17    $c   = "${b}";
18}
- -
-

Branches

-

- Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not - necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. - Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement - always has an else as part of its logical flow even if you didn't write one. -

-
foo
- - - - - - - - -
12function &foo($bar)
13{
14    $baz = function () {};
15    $a   = true ? true : false;
- - - - - -
15    $a   = true ? true : false;
- - - - - -
15    $a   = true ? true : false;
- - - - - - - -
15    $a   = true ? true : false;
16    $b   = "{$a}";
17    $c   = "${b}";
-
{closure:%ssource_without_namespace.php:14-14}
- - - - - -
14    $baz = function () {};
- - - -
- - - - - - diff --git a/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_path.html b/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_path.html deleted file mode 100644 index 92b20e978..000000000 --- a/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_path.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - Code Coverage for %ssource_without_namespace.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CRAP
n/a
0 / 0
foo
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
6
Foo
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - -
1<?php
2/**
3 * Represents foo.
4 */
5class Foo
6{
7}
8
9/**
10 * @param mixed $bar
11 */
12function &foo($bar)
13{
14    $baz = function () {};
15    $a   = true ? true : false;
16    $b   = "{$a}";
17    $c   = "${b}";
18}
- -
-

Paths

-

- Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not - necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. - Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement - always has an else as part of its logical flow even if you didn't write one. -

-
foo
- - - - - - - - - - - - - - -
12function &foo($bar)
13{
14    $baz = function () {};
15    $a   = true ? true : false;
 
15    $a   = true ? true : false;
 
15    $a   = true ? true : false;
16    $b   = "{$a}";
17    $c   = "${b}";
- - - - - - - - - - - - - - -
12function &foo($bar)
13{
14    $baz = function () {};
15    $a   = true ? true : false;
 
15    $a   = true ? true : false;
 
15    $a   = true ? true : false;
16    $b   = "{$a}";
17    $c   = "${b}";
-
{closure:%ssource_without_namespace.php:14-14}
- - - - - -
14    $baz = function () {};
- - - -
- - - - - - diff --git a/tests/_files/Report/HTML/PHP81AndUp/CoverageForClassWithAnonymousFunction/dashboard.html b/tests/_files/Report/HTML/PHP81AndUp/CoverageForClassWithAnonymousFunction/dashboard.html deleted file mode 100644 index e36ce51c1..000000000 --- a/tests/_files/Report/HTML/PHP81AndUp/CoverageForClassWithAnonymousFunction/dashboard.html +++ /dev/null @@ -1,283 +0,0 @@ - - - - - Dashboard for %s - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/_files/Report/HTML/PHP81AndUp/CoverageForClassWithAnonymousFunction/index.html b/tests/_files/Report/HTML/PHP81AndUp/CoverageForClassWithAnonymousFunction/index.html deleted file mode 100644 index 20b76959d..000000000 --- a/tests/_files/Report/HTML/PHP81AndUp/CoverageForClassWithAnonymousFunction/index.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - Code Coverage for %s - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
8 / 8
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
source_with_class_and_anonymous_function.php
-
- 100.00% covered (success) -
-
-
100.00%
8 / 8
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
-
-
-

Legend

-

- Low: 0% to 50% - Medium: 50% to 90% - High: 90% to 100% -

-

- Generated by php-code-coverage %s using %s at %s. -

-
-
- - diff --git a/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/dashboard.html b/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/dashboard.html deleted file mode 100644 index 460a712e2..000000000 --- a/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/dashboard.html +++ /dev/null @@ -1,283 +0,0 @@ - - - - - Dashboard for %s - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/index.html b/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/index.html deleted file mode 100644 index 8bf0a63ff..000000000 --- a/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/index.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - Code Coverage for %s - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
n/a
0 / 0
source_without_namespace.php [line] [branch] [path]
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
n/a
0 / 0
-
-
-
-

Legend

-

- Low: 0% to 50% - Medium: 50% to 90% - High: 90% to 100% -

-

- Generated by php-code-coverage %s using %s at %s. -

-
-
- - diff --git a/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/dashboard.html b/tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/dashboard.html similarity index 100% rename from tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/dashboard.html rename to tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/dashboard.html diff --git a/tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/index.html b/tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/index.html similarity index 100% rename from tests/_files/Report/HTML/PHP80AndBelow/PathCoverageForSourceWithoutNamespace/index.html rename to tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/index.html diff --git a/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/source_without_namespace.php.html b/tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/source_without_namespace.php.html similarity index 100% rename from tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/source_without_namespace.php.html rename to tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/source_without_namespace.php.html diff --git a/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_branch.html b/tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_branch.html similarity index 100% rename from tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_branch.html rename to tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_branch.html diff --git a/tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_path.html b/tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_path.html similarity index 100% rename from tests/_files/Report/HTML/PHP81AndUp/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_path.html rename to tests/_files/Report/HTML/PathCoverageForSourceWithoutNamespace/source_without_namespace.php_path.html diff --git a/tests/_files/Report/XML/CoverageForBankAccount/index.xml b/tests/_files/Report/XML/CoverageForBankAccount/index.xml index a18d5d871..6e551a135 100644 --- a/tests/_files/Report/XML/CoverageForBankAccount/index.xml +++ b/tests/_files/Report/XML/CoverageForBankAccount/index.xml @@ -6,10 +6,10 @@ - - - - + + + + diff --git a/tests/_files/Report/XML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/index.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml similarity index 92% rename from tests/_files/Report/XML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/index.xml rename to tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml index 3b6cbade3..3bfba4dda 100644 --- a/tests/_files/Report/XML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/index.xml +++ b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml @@ -6,7 +6,7 @@ - + diff --git a/tests/_files/Report/XML/PHP81AndUp/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml similarity index 100% rename from tests/_files/Report/XML/PHP81AndUp/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml rename to tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml diff --git a/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/index.xml b/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/index.xml index a8e89ab5e..a53b12c53 100644 --- a/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/index.xml +++ b/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/index.xml @@ -6,7 +6,7 @@ - + diff --git a/tests/_files/Report/XML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml b/tests/_files/Report/XML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml deleted file mode 100644 index cef706bf5..000000000 --- a/tests/_files/Report/XML/PHP80AndBelow/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <?php - - - - class - - CoveredClassWithAnonymousFunctionInStaticMethod - - - { - - - - public - - static - - function - - runAnonymous - ( - ) - - - - { - - - - $filter - - = - - [ - 'abc124' - , - - 'abc123' - , - - '123' - ] - ; - - - - - array_walk - ( - - - - $filter - , - - - - function - - ( - & - $val - , - - $key - ) - - { - - - - $val - - = - - preg_replace - ( - '|[^0-9]|' - , - - '' - , - - $val - ) - ; - - - - } - - - - ) - ; - - - - - // Should be covered - - - - $extravar - - = - - true - ; - - - - } - - - } - - - - - diff --git a/tests/_files/Report/XML/PHP81AndUp/CoverageForClassWithAnonymousFunction/index.xml b/tests/_files/Report/XML/PHP81AndUp/CoverageForClassWithAnonymousFunction/index.xml deleted file mode 100644 index 3b6cbade3..000000000 --- a/tests/_files/Report/XML/PHP81AndUp/CoverageForClassWithAnonymousFunction/index.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/_files/source_for_branched_exec_lines_php74.php b/tests/_files/source_for_branched_exec_lines_php74.php deleted file mode 100644 index 44d95ebe0..000000000 --- a/tests/_files/source_for_branched_exec_lines_php74.php +++ /dev/null @@ -1,6 +0,0 @@ - $x + $y; // +1 -$fn1 = fn($x) => // +1 - $x + $y; // +1 diff --git a/tests/_files/source_for_branched_exec_lines_php80.php b/tests/_files/source_for_branched_exec_lines_php80.php deleted file mode 100644 index 4a977c3a6..000000000 --- a/tests/_files/source_for_branched_exec_lines_php80.php +++ /dev/null @@ -1,43 +0,0 @@ - ++$var, // 0 - 1 => ++$var, // 0 - default => ++$var, // 0 -}; // 0 -$var2 // +1 - = // 0 - match // 0 - ( // 0 - $var // 0 - ) // 0 - { // 0 - 0 // 0 - => // 0 - ++$var // 0 - , // 0 - 1, // 0 - 2 // 0 - => // 0 - ++$var // 0 - , // 0 - default // 0 - => // 0 - ++$var // 0 - , // 0 -} // 0 -; // 0 - -// Nullsafe Operator -$ymd = $date?->format('Ymd'); // +1 -++$var; // +1 - -// Union types -interface MyUnion -{ - public function getNameIdentifier(): ?string; - public function hasClaim(bool|string $type, mixed $value): bool; - public function getClaims($type1 = null): array; -} diff --git a/tests/_files/source_with_ignore_attributes.php b/tests/_files/source_with_ignore_attributes.php index abe5b77e1..ec8b9a6ea 100644 --- a/tests/_files/source_with_ignore_attributes.php +++ b/tests/_files/source_with_ignore_attributes.php @@ -4,15 +4,17 @@ #[CodeCoverageIgnore] class Foo { - public function bar(): void + public function bar(): bool { + return true; } } class Bar { #[CodeCoverageIgnore] - public function foo(): void + public function foo(): bool { + return false; } } diff --git a/tests/tests/CodeCoverageTest.php b/tests/tests/CodeCoverageTest.php index 80c8c8f92..4a4ebe2f5 100644 --- a/tests/tests/CodeCoverageTest.php +++ b/tests/tests/CodeCoverageTest.php @@ -10,19 +10,16 @@ namespace SebastianBergmann\CodeCoverage; use function array_fill; +use PHPUnit\Framework\Attributes\CoversClass; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; use SebastianBergmann\CodeCoverage\Driver\Driver; use SebastianBergmann\CodeCoverage\Driver\Selector; use SebastianBergmann\Environment\Runtime; -/** - * @covers \SebastianBergmann\CodeCoverage\CodeCoverage - */ +#[CoversClass(CodeCoverage::class)] final class CodeCoverageTest extends TestCase { - /** - * @var CodeCoverage - */ - private $coverage; + private CodeCoverage $coverage; protected function setUp(): void { @@ -40,20 +37,6 @@ protected function setUp(): void ); } - public function testCannotStopWithInvalidSecondArgument(): void - { - $this->expectException(Exception::class); - - $this->coverage->stop(true, null); - } - - public function testCannotAppendWithInvalidArgument(): void - { - $this->expectException(Exception::class); - - $this->coverage->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage([]), null); - } - public function testCollect(): void { $coverage = $this->getLineCoverageForBankAccount(); @@ -65,16 +48,16 @@ public function testCollect(): void $this->assertEquals( [ - 'BankAccountTest::testBalanceIsInitiallyZero' => ['size' => 'unknown', 'status' => -1, 'fromTestcase' => true], - 'BankAccountTest::testBalanceCannotBecomeNegative' => ['size' => 'unknown', 'status' => -1, 'fromTestcase' => true], - 'BankAccountTest::testBalanceCannotBecomeNegative2' => ['size' => 'unknown', 'status' => -1, 'fromTestcase' => true], - 'BankAccountTest::testDepositWithdrawMoney' => ['size' => 'unknown', 'status' => -1, 'fromTestcase' => true], + 'BankAccountTest::testBalanceIsInitiallyZero' => ['size' => 'unknown', 'status' => 'unknown'], + 'BankAccountTest::testBalanceCannotBecomeNegative' => ['size' => 'unknown', 'status' => 'unknown'], + 'BankAccountTest::testBalanceCannotBecomeNegative2' => ['size' => 'unknown', 'status' => 'unknown'], + 'BankAccountTest::testDepositWithdrawMoney' => ['size' => 'unknown', 'status' => 'unknown'], ], $coverage->getTests() ); } - public function testWhitelistFiltering(): void + public function testIncludeListFiltering(): void { $this->coverage->filter()->includeFile(TEST_FILES_PATH . 'BankAccount.php'); diff --git a/tests/tests/Data/ProcessedCodeCoverageDataTest.php b/tests/tests/Data/ProcessedCodeCoverageDataTest.php index 206634041..c2a9c68d6 100644 --- a/tests/tests/Data/ProcessedCodeCoverageDataTest.php +++ b/tests/tests/Data/ProcessedCodeCoverageDataTest.php @@ -9,7 +9,6 @@ */ namespace SebastianBergmann\CodeCoverage\Data; -use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; use SebastianBergmann\CodeCoverage\TestCase; final class ProcessedCodeCoverageDataTest extends TestCase diff --git a/tests/tests/Data/RawCodeCoverageDataTest.php b/tests/tests/Data/RawCodeCoverageDataTest.php index cea8fe0f0..7feead3be 100644 --- a/tests/tests/Data/RawCodeCoverageDataTest.php +++ b/tests/tests/Data/RawCodeCoverageDataTest.php @@ -9,7 +9,6 @@ */ namespace SebastianBergmann\CodeCoverage\Data; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; use SebastianBergmann\CodeCoverage\TestCase; final class RawCodeCoverageDataTest extends TestCase @@ -78,48 +77,6 @@ public function testLineDataFromPathCoverageXDebugFormat(): void $this->assertEquals($lineData, $dataObject->lineCoverage()); } - /** - * In the path-coverage XDebug format for Xdebug < 2.9.6, the line data exists inside a "lines" array key where the - * file has classes or functions. For files without them, the data is stored in the line-only format. - */ - public function testLineDataFromMixedCoverageXDebugFormat(): void - { - $rawDataFromDriver = [ - '/some/path/SomeClass.php' => [ - 'lines' => [ - 8 => 1, - 9 => -2, - 13 => -1, - ], - 'functions' => [ - - ], - ], - '/some/path/justAScript.php' => [ - 18 => 1, - 19 => -2, - 113 => -1, - ], - ]; - - $lineData = [ - '/some/path/SomeClass.php' => [ - 8 => 1, - 9 => -2, - 13 => -1, - ], - '/some/path/justAScript.php' => [ - 18 => 1, - 19 => -2, - 113 => -1, - ], - ]; - - $dataObject = RawCodeCoverageData::fromXdebugWithMixedCoverage($rawDataFromDriver); - - $this->assertEquals($lineData, $dataObject->lineCoverage()); - } - public function testClear(): void { $lineDataFromDriver = [ diff --git a/tests/tests/Driver/PhpdbgDriverTest.php b/tests/tests/Driver/PhpdbgDriverTest.php deleted file mode 100644 index 2216483f3..000000000 --- a/tests/tests/Driver/PhpdbgDriverTest.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\Driver; - -use const PHP_SAPI; -use SebastianBergmann\CodeCoverage\BranchAndPathCoverageNotSupportedException; -use SebastianBergmann\CodeCoverage\DeadCodeDetectionNotSupportedException; -use SebastianBergmann\CodeCoverage\TestCase; - -final class PhpdbgDriverTest extends TestCase -{ - protected function setUp(): void - { - if (PHP_SAPI !== 'phpdbg') { - $this->markTestSkipped('This test requires the PHPDBG commandline interpreter'); - } - } - - public function testDoesNotSupportBranchAndPathCoverage(): void - { - $this->assertFalse($this->driver()->canCollectBranchAndPathCoverage()); - } - - public function testBranchAndPathCoverageCanBeDisabled(): void - { - $driver = $this->driver(); - - $driver->disableBranchAndPathCoverage(); - - $this->assertFalse($driver->collectsBranchAndPathCoverage()); - } - - public function testBranchAndPathCoverageCannotBeEnabled(): void - { - $this->expectException(BranchAndPathCoverageNotSupportedException::class); - - $this->driver()->enableBranchAndPathCoverage(); - } - - public function testBranchAndPathCoverageIsNotCollected(): void - { - $this->assertFalse($this->driver()->collectsBranchAndPathCoverage()); - } - - public function testDoesNotSupportDeadCodeDetection(): void - { - $this->assertFalse($this->driver()->canDetectDeadCode()); - } - - public function testDeadCodeDetectionCanBeDisabled(): void - { - $driver = $this->driver(); - - $driver->disableDeadCodeDetection(); - - $this->assertFalse($driver->detectsDeadCode()); - } - - public function testDeadCodeDetectionCannotBeEnabled(): void - { - $this->expectException(DeadCodeDetectionNotSupportedException::class); - - $this->driver()->enableDeadCodeDetection(); - } - - public function testDeadCodeIsNotDetected(): void - { - $this->assertFalse($this->driver()->detectsDeadCode()); - } - - public function testHasNameAndVersion(): void - { - $this->assertStringMatchesFormat('PHPDBG %s', $this->driver()->nameAndVersion()); - } - - private function driver(): PhpdbgDriver - { - return new PhpdbgDriver; - } -} diff --git a/tests/tests/Driver/Xdebug2DriverTest.php b/tests/tests/Driver/Xdebug2DriverTest.php deleted file mode 100644 index c4985cb46..000000000 --- a/tests/tests/Driver/Xdebug2DriverTest.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\Driver; - -use const PHP_SAPI; -use function extension_loaded; -use function ini_get; -use function phpversion; -use function version_compare; -use function xdebug_code_coverage_started; -use function xdebug_get_code_coverage; -use SebastianBergmann\CodeCoverage\Filter; -use SebastianBergmann\CodeCoverage\TestCase; - -final class Xdebug2DriverTest extends TestCase -{ - protected function setUp(): void - { - if (PHP_SAPI !== 'cli') { - $this->markTestSkipped('This test requires the PHP commandline interpreter'); - } - - if (!extension_loaded('xdebug')) { - $this->markTestSkipped('This test requires the Xdebug extension to be loaded'); - } - - if (version_compare(phpversion('xdebug'), '3', '>=')) { - $this->markTestSkipped('This test requires version 2 of the Xdebug extension to be loaded'); - } - - if (!ini_get('xdebug.coverage_enable')) { - $this->markTestSkipped('This test requires the Xdebug extension\'s code coverage functionality to be enabled'); - } - - if (!xdebug_code_coverage_started()) { - $this->markTestSkipped('This test requires code coverage data collection using Xdebug to be active'); - } - } - - public function testSupportsBranchAndPathCoverage(): void - { - $this->assertTrue($this->driver()->canCollectBranchAndPathCoverage()); - } - - public function testBranchAndPathCoverageCanBeDisabled(): void - { - $driver = $this->driver(); - - $driver->disableBranchAndPathCoverage(); - - $this->assertFalse($driver->collectsBranchAndPathCoverage()); - } - - public function testBranchAndPathCoverageCanBeEnabled(): void - { - $driver = $this->driver(); - - $driver->enableBranchAndPathCoverage(); - - $this->assertTrue($driver->collectsBranchAndPathCoverage()); - } - - public function testBranchAndPathCoverageIsNotCollectedByDefault(): void - { - $this->assertFalse($this->driver()->collectsBranchAndPathCoverage()); - } - - public function testSupportsDeadCodeDetection(): void - { - $this->assertTrue($this->driver()->canDetectDeadCode()); - } - - public function testDeadCodeDetectionCanBeDisabled(): void - { - $driver = $this->driver(); - - $driver->disableDeadCodeDetection(); - - $this->assertFalse($driver->detectsDeadCode()); - } - - public function testDeadCodeDetectionCanBeEnabled(): void - { - $driver = $this->driver(); - - $driver->enableDeadCodeDetection(); - - $this->assertTrue($driver->detectsDeadCode()); - } - - public function testDeadCodeIsNotDetectedByDefault(): void - { - $this->assertFalse($this->driver()->detectsDeadCode()); - } - - public function testHasNameAndVersion(): void - { - $this->assertStringMatchesFormat('Xdebug %s', $this->driver()->nameAndVersion()); - } - - public function testFilterWorks(): void - { - $bankAccount = TEST_FILES_PATH . 'BankAccount.php'; - - require $bankAccount; - - $this->assertArrayNotHasKey($bankAccount, xdebug_get_code_coverage()); - } - - private function driver(): Xdebug2Driver - { - return new Xdebug2Driver(new Filter); - } -} diff --git a/tests/tests/Driver/Xdebug3DriverTest.php b/tests/tests/Driver/XdebugDriverTest.php similarity index 89% rename from tests/tests/Driver/Xdebug3DriverTest.php rename to tests/tests/Driver/XdebugDriverTest.php index 0349ef6a5..e88aeac94 100644 --- a/tests/tests/Driver/Xdebug3DriverTest.php +++ b/tests/tests/Driver/XdebugDriverTest.php @@ -12,14 +12,12 @@ use const PHP_SAPI; use function extension_loaded; use function ini_get; -use function phpversion; -use function version_compare; use function xdebug_code_coverage_started; use function xdebug_get_code_coverage; use SebastianBergmann\CodeCoverage\Filter; use SebastianBergmann\CodeCoverage\TestCase; -final class Xdebug3DriverTest extends TestCase +final class XdebugDriverTest extends TestCase { protected function setUp(): void { @@ -31,10 +29,6 @@ protected function setUp(): void $this->markTestSkipped('This test requires the Xdebug extension to be loaded'); } - if (version_compare(phpversion('xdebug'), '3', '<')) { - $this->markTestSkipped('This test requires version 3 of the Xdebug extension to be loaded'); - } - if (!ini_get('xdebug.mode') || ini_get('xdebug.mode') !== 'coverage') { $this->markTestSkipped('This test requires the Xdebug extension\'s code coverage functionality to be enabled'); } @@ -114,8 +108,8 @@ public function testFilterWorks(): void $this->assertArrayNotHasKey($bankAccount, xdebug_get_code_coverage()); } - private function driver(): Xdebug3Driver + private function driver(): XdebugDriver { - return new Xdebug3Driver(new Filter); + return new XdebugDriver(new Filter); } } diff --git a/tests/tests/FilterTest.php b/tests/tests/FilterTest.php index 9ffd89279..67f09f68e 100644 --- a/tests/tests/FilterTest.php +++ b/tests/tests/FilterTest.php @@ -10,17 +10,13 @@ namespace SebastianBergmann\CodeCoverage; use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -/** - * @covers \SebastianBergmann\CodeCoverage\Filter - */ +#[CoversClass(Filter::class)] final class FilterTest extends TestCase { - /** - * @var Filter - */ - private $filter; + private Filter $filter; protected function setUp(): void { diff --git a/tests/tests/Node/BuilderTest.php b/tests/tests/Node/BuilderTest.php index 681d189f3..fa5d81e06 100644 --- a/tests/tests/Node/BuilderTest.php +++ b/tests/tests/Node/BuilderTest.php @@ -11,8 +11,9 @@ use const DIRECTORY_SEPARATOR; use function rtrim; +use Generator; use ReflectionMethod; -use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData; use SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser; use SebastianBergmann\CodeCoverage\TestCase; @@ -20,6 +21,55 @@ final class BuilderTest extends TestCase { private $factory; + public static function reducePathsProvider(): Generator + { + $s = DIRECTORY_SEPARATOR; + + yield [ + [], + '.', + self::pathsToProcessedDataObjectHelper([]), + ]; + + foreach (["C:{$s}", "{$s}"] as $p) { + yield [ + [ + 'Money.php' => [], + ], + "{$p}home{$s}sb{$s}Money{$s}", + self::pathsToProcessedDataObjectHelper([ + "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], + ]), + ]; + + yield [ + [ + 'Money.php' => [], + 'MoneyBag.php' => [], + ], + "{$p}home{$s}sb{$s}Money", + self::pathsToProcessedDataObjectHelper([ + "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], + "{$p}home{$s}sb{$s}Money{$s}MoneyBag.php" => [], + ]), + ]; + + yield [ + [ + 'Money.php' => [], + 'MoneyBag.php' => [], + "Cash.phar{$s}Cash.php" => [], + ], + "{$p}home{$s}sb{$s}Money", + self::pathsToProcessedDataObjectHelper([ + "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], + "{$p}home{$s}sb{$s}Money{$s}MoneyBag.php" => [], + "phar://{$p}home{$s}sb{$s}Money{$s}Cash.phar{$s}Cash.php" => [], + ]), + ]; + } + } + protected function setUp(): void { $this->factory = new Builder(new ParsingFileAnalyser(true, true)); @@ -205,58 +255,10 @@ public function testReducePaths(array $reducedPaths, string $commonPath, Process $this->assertEquals($commonPath, $_commonPath); } - public function reducePathsProvider() - { - $s = DIRECTORY_SEPARATOR; - - yield [ - [], - '.', - $this->pathsToProcessedDataObjectHelper([]), - ]; - - foreach (["C:{$s}", "{$s}"] as $p) { - yield [ - [ - 'Money.php' => [], - ], - "{$p}home{$s}sb{$s}Money{$s}", - $this->pathsToProcessedDataObjectHelper([ - "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], - ]), - ]; - - yield [ - [ - 'Money.php' => [], - 'MoneyBag.php' => [], - ], - "{$p}home{$s}sb{$s}Money", - $this->pathsToProcessedDataObjectHelper([ - "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], - "{$p}home{$s}sb{$s}Money{$s}MoneyBag.php" => [], - ]), - ]; - - yield [ - [ - 'Money.php' => [], - 'MoneyBag.php' => [], - "Cash.phar{$s}Cash.php" => [], - ], - "{$p}home{$s}sb{$s}Money", - $this->pathsToProcessedDataObjectHelper([ - "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], - "{$p}home{$s}sb{$s}Money{$s}MoneyBag.php" => [], - "phar://{$p}home{$s}sb{$s}Money{$s}Cash.phar{$s}Cash.php" => [], - ]), - ]; - } - } - - private function pathsToProcessedDataObjectHelper(array $paths): ProcessedCodeCoverageData + private static function pathsToProcessedDataObjectHelper(array $paths): ProcessedCodeCoverageData { $coverage = new ProcessedCodeCoverageData; + $coverage->setLineCoverage($paths); return $coverage; diff --git a/tests/tests/Report/CloverTest.php b/tests/tests/Report/CloverTest.php index c667c9a2f..d492191a1 100644 --- a/tests/tests/Report/CloverTest.php +++ b/tests/tests/Report/CloverTest.php @@ -9,11 +9,10 @@ */ namespace SebastianBergmann\CodeCoverage\Report; +use PHPUnit\Framework\Attributes\CoversClass; use SebastianBergmann\CodeCoverage\TestCase; -/** - * @covers \SebastianBergmann\CodeCoverage\Report\Clover - */ +#[CoversClass(Clover::class)] final class CloverTest extends TestCase { public function testLineCoverageForBankAccountTest(): void diff --git a/tests/tests/Report/CoberturaTest.php b/tests/tests/Report/CoberturaTest.php index 05163ed2d..2f6782a8f 100644 --- a/tests/tests/Report/CoberturaTest.php +++ b/tests/tests/Report/CoberturaTest.php @@ -9,11 +9,10 @@ */ namespace SebastianBergmann\CodeCoverage\Report; +use PHPUnit\Framework\Attributes\CoversClass; use SebastianBergmann\CodeCoverage\TestCase; -/** - * @covers \SebastianBergmann\CodeCoverage\Report\Cobertura - */ +#[CoversClass(Cobertura::class)] final class CoberturaTest extends TestCase { public function testLineCoverageForBankAccountTest(): void diff --git a/tests/tests/Report/Crap4jTest.php b/tests/tests/Report/Crap4jTest.php index f3c0f85f5..ce428e0d8 100644 --- a/tests/tests/Report/Crap4jTest.php +++ b/tests/tests/Report/Crap4jTest.php @@ -9,11 +9,10 @@ */ namespace SebastianBergmann\CodeCoverage\Report; +use PHPUnit\Framework\Attributes\CoversClass; use SebastianBergmann\CodeCoverage\TestCase; -/** - * @covers \SebastianBergmann\CodeCoverage\Report\Crap4j - */ +#[CoversClass(Crap4j::class)] final class Crap4jTest extends TestCase { public function testForBankAccountTest(): void diff --git a/tests/tests/Report/Html/ColorsTest.php b/tests/tests/Report/Html/ColorsTest.php new file mode 100644 index 000000000..78f17b58f --- /dev/null +++ b/tests/tests/Report/Html/ColorsTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Colors::class)] +#[Small] +final class ColorsTest extends TestCase +{ + public function testCanBeCreatedFromDefaults(): void + { + $colors = Colors::default(); + + $this->assertSame('#dff0d8', $colors->successLow()); + $this->assertSame('#c3e3b5', $colors->successMedium()); + $this->assertSame('#99cb84', $colors->successHigh()); + $this->assertSame('#fcf8e3', $colors->warning()); + $this->assertSame('#f2dede', $colors->danger()); + } + + public function testCanBeCreatedFromCustomValues(): void + { + $colors = Colors::from('successLow', 'successMedium', 'successHigh', 'warning', 'danger'); + + $this->assertSame('successLow', $colors->successLow()); + $this->assertSame('successMedium', $colors->successMedium()); + $this->assertSame('successHigh', $colors->successHigh()); + $this->assertSame('warning', $colors->warning()); + $this->assertSame('danger', $colors->danger()); + } +} diff --git a/tests/tests/Report/Html/CustomCssFileTest.php b/tests/tests/Report/Html/CustomCssFileTest.php new file mode 100644 index 000000000..90dfe46a8 --- /dev/null +++ b/tests/tests/Report/Html/CustomCssFileTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; + +#[CoversClass(CustomCssFile::class)] +#[Small] +final class CustomCssFileTest extends TestCase +{ + public function testCanBeCreatedFromDefaults(): void + { + $file = CustomCssFile::default(); + + $this->assertSame( + realpath(__DIR__ . '/../../../../src/Report/Html/Renderer/Template/css/custom.css'), + realpath($file->path()) + ); + } + + public function testCanBeCreatedFromValidPath(): void + { + $file = CustomCssFile::from(__FILE__); + + $this->assertSame(__FILE__, $file->path()); + } + + public function testCannotBeCreatedFromInvalidPath(): void + { + $this->expectException(InvalidArgumentException::class); + + CustomCssFile::from('does-not-exist'); + } +} diff --git a/tests/tests/Report/HtmlTest.php b/tests/tests/Report/Html/EndToEndTest.php similarity index 81% rename from tests/tests/Report/HtmlTest.php rename to tests/tests/Report/Html/EndToEndTest.php index c9dae8110..27bde9615 100644 --- a/tests/tests/Report/HtmlTest.php +++ b/tests/tests/Report/Html/EndToEndTest.php @@ -18,7 +18,7 @@ use RegexIterator; use SebastianBergmann\CodeCoverage\TestCase; -final class HtmlTest extends TestCase +final class EndToEndTest extends TestCase { private static $TEST_REPORT_PATH_SOURCE; @@ -58,11 +58,7 @@ public function testPathCoverageForBankAccountTest(): void public function testPathCoverageForSourceWithoutNamespace(): void { - if (PHP_VERSION_ID >= 80100) { - $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'PHP81AndUp' . DIRECTORY_SEPARATOR . 'PathCoverageForSourceWithoutNamespace'; - } else { - $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'PHP80AndBelow' . DIRECTORY_SEPARATOR . 'PathCoverageForSourceWithoutNamespace'; - } + $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'PathCoverageForSourceWithoutNamespace'; $report = new Facade; $report->process($this->getPathCoverageForSourceWithoutNamespace(), self::$TEST_TMP_PATH); @@ -82,11 +78,7 @@ public function testForFileWithIgnoredLines(): void public function testForClassWithAnonymousFunction(): void { - if (PHP_VERSION_ID >= 80100) { - $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'PHP81AndUp' . DIRECTORY_SEPARATOR . 'CoverageForClassWithAnonymousFunction'; - } else { - $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'PHP80AndBelow' . DIRECTORY_SEPARATOR . 'CoverageForClassWithAnonymousFunction'; - } + $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'CoverageForClassWithAnonymousFunction'; $report = new Facade; $report->process($this->getCoverageForClassWithAnonymousFunction(), self::$TEST_TMP_PATH); diff --git a/tests/tests/Report/Html/ThresholdsTest.php b/tests/tests/Report/Html/ThresholdsTest.php new file mode 100644 index 000000000..2f348730d --- /dev/null +++ b/tests/tests/Report/Html/ThresholdsTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; +use SebastianBergmann\CodeCoverage\Report\Thresholds; + +#[CoversClass(Thresholds::class)] +#[Small] +final class ThresholdsTest extends TestCase +{ + public function testCanBeCreatedFromDefaults(): void + { + $thresholds = Thresholds::default(); + + $this->assertSame(50, $thresholds->lowUpperBound()); + $this->assertSame(90, $thresholds->highLowerBound()); + } + + public function testCanBeCreatedFromValidCustomValues(): void + { + $thresholds = Thresholds::from(60, 95); + + $this->assertSame(60, $thresholds->lowUpperBound()); + $this->assertSame(95, $thresholds->highLowerBound()); + } + + public function testCannotBeCreatedFromInvalidValues(): void + { + $this->expectException(InvalidArgumentException::class); + + Thresholds::from(90, 50); + } +} diff --git a/tests/tests/Report/PhpTest.php b/tests/tests/Report/PhpTest.php index 657278bcd..733e739f4 100644 --- a/tests/tests/Report/PhpTest.php +++ b/tests/tests/Report/PhpTest.php @@ -9,6 +9,7 @@ */ namespace SebastianBergmann\CodeCoverage\Report; +use ReflectionProperty; use SebastianBergmann\CodeCoverage\TestCase; final class PhpTest extends TestCase @@ -43,4 +44,21 @@ public function testPHPSerialisationProducesValidCodeWhenOutputIncludesSingleQuo $this->assertEquals($coverage, $unserialized); } + + public function testCacheDataNeverGetSaved(): void + { + $coverage = $this->getLineCoverageForBankAccount(); + + // Warm up cache + $coverage->getReport(); + + $refProperty = new ReflectionProperty($coverage, 'cachedReport'); + + $this->assertNotNull($refProperty->getValue($coverage)); + + /* @noinspection UnusedFunctionResultInspection */ + (new PHP)->process($coverage, self::$TEST_TMP_PATH . '/serialized.php'); + + $this->assertNull($refProperty->getValue($coverage)); + } } diff --git a/tests/tests/Report/TextTest.php b/tests/tests/Report/TextTest.php index 241d992c6..9194712d7 100644 --- a/tests/tests/Report/TextTest.php +++ b/tests/tests/Report/TextTest.php @@ -11,16 +11,15 @@ use const PHP_EOL; use function str_replace; +use PHPUnit\Framework\Attributes\CoversClass; use SebastianBergmann\CodeCoverage\TestCase; -/** - * @covers \SebastianBergmann\CodeCoverage\Report\Text - */ +#[CoversClass(Text::class)] final class TextTest extends TestCase { public function testLineCoverageForBankAccountTest(): void { - $text = new Text(50, 90, false, false); + $text = new Text(Thresholds::default(), false, false); $this->assertStringMatchesFormatFile( TEST_FILES_PATH . 'BankAccount-text-line.txt', @@ -30,7 +29,7 @@ public function testLineCoverageForBankAccountTest(): void public function testPathCoverageForBankAccountTest(): void { - $text = new Text(50, 90, false, false); + $text = new Text(Thresholds::default(), false, false); $this->assertStringMatchesFormatFile( TEST_FILES_PATH . 'BankAccount-text-path.txt', @@ -40,7 +39,7 @@ public function testPathCoverageForBankAccountTest(): void public function testTextOnlySummaryForBankAccountTest(): void { - $text = new Text(50, 90, false, true); + $text = new Text(Thresholds::default(), false, true); $this->assertStringMatchesFormatFile( TEST_FILES_PATH . 'BankAccount-text-summary.txt', @@ -50,7 +49,7 @@ public function testTextOnlySummaryForBankAccountTest(): void public function testTextForNamespacedBankAccountTest(): void { - $text = new Text(50, 90, true, false); + $text = new Text(Thresholds::default(), true, false); $this->assertStringMatchesFormatFile( TEST_FILES_PATH . 'NamespacedBankAccount-text.txt', @@ -60,7 +59,7 @@ public function testTextForNamespacedBankAccountTest(): void public function testTextForFileWithIgnoredLines(): void { - $text = new Text(50, 90, false, false); + $text = new Text(Thresholds::default(), false, false); $this->assertStringMatchesFormatFile( TEST_FILES_PATH . 'ignored-lines-text.txt', @@ -70,7 +69,7 @@ public function testTextForFileWithIgnoredLines(): void public function testTextForClassWithAnonymousFunction(): void { - $text = new Text(50, 90, false, false); + $text = new Text(Thresholds::default(), false, false); $this->assertStringMatchesFormatFile( TEST_FILES_PATH . 'class-with-anonymous-function-text.txt', @@ -80,7 +79,7 @@ public function testTextForClassWithAnonymousFunction(): void public function testUncoveredFilesAreIncludedWhenConfiguredTest(): void { - $text = new Text(50, 90, false, false); + $text = new Text(Thresholds::default(), false, false); $this->assertStringMatchesFormatFile( TEST_FILES_PATH . 'BankAccountWithUncovered-text-line.txt', @@ -90,7 +89,7 @@ public function testUncoveredFilesAreIncludedWhenConfiguredTest(): void public function testUncoveredFilesAreExcludedWhenConfiguredTest(): void { - $text = new Text(50, 90, false, false); + $text = new Text(Thresholds::default(), false, false); $this->assertStringMatchesFormatFile( TEST_FILES_PATH . 'BankAccountWithoutUncovered-text-line.txt', diff --git a/tests/tests/Report/XmlTest.php b/tests/tests/Report/XmlTest.php index ea36f4fa8..f7e8f9b60 100644 --- a/tests/tests/Report/XmlTest.php +++ b/tests/tests/Report/XmlTest.php @@ -18,7 +18,7 @@ final class XmlTest extends TestCase { - private static $TEST_REPORT_PATH_SOURCE; + private static string $TEST_REPORT_PATH_SOURCE; public static function setUpBeforeClass(): void { @@ -59,11 +59,7 @@ public function testForFileWithIgnoredLines(): void public function testForClassWithAnonymousFunction(): void { - if (PHP_VERSION_ID >= 80100) { - $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'PHP81AndUp' . DIRECTORY_SEPARATOR . 'CoverageForClassWithAnonymousFunction'; - } else { - $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'PHP80AndBelow' . DIRECTORY_SEPARATOR . 'CoverageForClassWithAnonymousFunction'; - } + $expectedFilesPath = self::$TEST_REPORT_PATH_SOURCE . DIRECTORY_SEPARATOR . 'CoverageForClassWithAnonymousFunction'; $xml = new Facade('1.0.0'); $xml->process($this->getCoverageForClassWithAnonymousFunction(), self::$TEST_TMP_PATH); @@ -71,11 +67,7 @@ public function testForClassWithAnonymousFunction(): void $this->assertFilesEquals($expectedFilesPath, self::$TEST_TMP_PATH); } - /** - * @param string $expectedFilesPath - * @param string $actualFilesPath - */ - private function assertFilesEquals($expectedFilesPath, $actualFilesPath): void + private function assertFilesEquals(string $expectedFilesPath, string $actualFilesPath): void { $expectedFilesIterator = new FilesystemIterator($expectedFilesPath); $actualFilesIterator = new FilesystemIterator($actualFilesPath); diff --git a/tests/tests/StaticAnalysis/CodeUnitFindingVisitorTest.php b/tests/tests/StaticAnalysis/CodeUnitFindingVisitorTest.php index e9a6a6d10..5042b222b 100644 --- a/tests/tests/StaticAnalysis/CodeUnitFindingVisitorTest.php +++ b/tests/tests/StaticAnalysis/CodeUnitFindingVisitorTest.php @@ -15,13 +15,12 @@ use PhpParser\NodeVisitor\NameResolver; use PhpParser\NodeVisitor\ParentConnectingVisitor; use PhpParser\ParserFactory; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use SebastianBergmann\CodeCoverage\TestFixture\ClassThatUsesAnonymousClass; use SebastianBergmann\CodeCoverage\TestFixture\ClassWithNameThatIsPartOfItsNamespacesName\ClassWithNameThatIsPartOfItsNamespacesName; -/** - * @covers \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor - */ +#[CoversClass(CodeUnitFindingVisitor::class)] final class CodeUnitFindingVisitorTest extends TestCase { /** @@ -99,9 +98,6 @@ public function testHandlesFunctionOrMethodWithUnionTypes(): void ); } - /** - * @requires PHP 8.1 - */ public function testHandlesFunctionOrMethodWithIntersectionTypes(): void { $codeUnitFindingVisitor = $this->findCodeUnits(__DIR__ . '/../../_files/FunctionWithIntersectionTypes.php'); diff --git a/tests/tests/StaticAnalysis/ExecutableLinesFindingVisitorTest.php b/tests/tests/StaticAnalysis/ExecutableLinesFindingVisitorTest.php index c9089a45e..041a1fe99 100644 --- a/tests/tests/StaticAnalysis/ExecutableLinesFindingVisitorTest.php +++ b/tests/tests/StaticAnalysis/ExecutableLinesFindingVisitorTest.php @@ -12,15 +12,15 @@ use function explode; use function file_get_contents; use function preg_match; -use function strpos; +use function str_contains; use PhpParser\Lexer; use PhpParser\NodeTraverser; use PhpParser\ParserFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\TestCase; -/** - * @covers \SebastianBergmann\CodeCoverage\StaticAnalysis\ExecutableLinesFindingVisitor - */ +#[CoversClass(ExecutableLinesFindingVisitor::class)] final class ExecutableLinesFindingVisitorTest extends TestCase { public function testExecutableLinesAreGroupedByBranch(): void @@ -28,33 +28,13 @@ public function testExecutableLinesAreGroupedByBranch(): void $this->doTestSelfDescribingAsset(TEST_FILES_PATH . 'source_for_branched_exec_lines.php'); } - /** - * @requires PHP 7.4 - */ - public function testExecutableLinesAreGroupedByBranchPhp74(): void - { - $this->doTestSelfDescribingAsset(TEST_FILES_PATH . 'source_for_branched_exec_lines_php74.php'); - } - - /** - * @requires PHP 8 - */ - public function testExecutableLinesAreGroupedByBranchPhp80(): void - { - $this->doTestSelfDescribingAsset(TEST_FILES_PATH . 'source_for_branched_exec_lines_php80.php'); - } - - /** - * @requires PHP 8.1 - */ + #[RequiresPhp('8.1.*')] public function testExecutableLinesAreGroupedByBranchPhp81(): void { $this->doTestSelfDescribingAsset(TEST_FILES_PATH . 'source_for_branched_exec_lines_php81.php'); } - /** - * @requires PHP 8.2 - */ + #[RequiresPhp('8.2.*')] public function testExecutableLinesAreGroupedByBranchPhp82(): void { $this->doTestSelfDescribingAsset(TEST_FILES_PATH . 'source_for_branched_exec_lines_php82.php'); @@ -82,7 +62,7 @@ private function doTestSelfDescribingAsset(string $filename): void $branch = 0; foreach ($linesFromSource as $lineNumber => $line) { - if (false !== strpos($line, 'LINE_ADDED_IN_TEST')) { + if (str_contains($line, 'LINE_ADDED_IN_TEST')) { $expected[1 + $lineNumber] = $branch; continue; diff --git a/tests/tests/StaticAnalysis/ParsingFileAnalyserTest.php b/tests/tests/StaticAnalysis/ParsingFileAnalyserTest.php index 424d34f11..5b3b03624 100644 --- a/tests/tests/StaticAnalysis/ParsingFileAnalyserTest.php +++ b/tests/tests/StaticAnalysis/ParsingFileAnalyserTest.php @@ -9,13 +9,12 @@ */ namespace SebastianBergmann\CodeCoverage\StaticAnalysis; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -/** - * @covers \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor - * @covers \SebastianBergmann\CodeCoverage\StaticAnalysis\IgnoredLinesFindingVisitor - * @covers \SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser - */ +#[CoversClass(CodeUnitFindingVisitor::class)] +#[CoversClass(IgnoredLinesFindingVisitor::class)] +#[CoversClass(ParsingFileAnalyser::class)] final class ParsingFileAnalyserTest extends TestCase { public function testGetLinesToBeIgnored(): void @@ -142,17 +141,24 @@ public function testGetLinesOfCodeForFileCrLineEndings(): void $this->assertSame(2, $result['nonCommentLinesOfCode']); } - /** - * @requires PHP 8 - */ public function testLinesCanBeIgnoredUsingAttribute(): void { $this->assertSame( [ 4, 5, - 12, - 14, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 15, + 16, + 17, + 18, + 19, ], (new ParsingFileAnalyser(true, true))->ignoredLinesFor( TEST_FILES_PATH . 'source_with_ignore_attributes.php' diff --git a/tests/tests/Util/PercentageTest.php b/tests/tests/Util/PercentageTest.php index eaf35ec46..c794ff27a 100644 --- a/tests/tests/Util/PercentageTest.php +++ b/tests/tests/Util/PercentageTest.php @@ -9,11 +9,10 @@ */ namespace SebastianBergmann\CodeCoverage\Util; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -/** - * @covers \SebastianBergmann\CodeCoverage\Util\Percentage - */ +#[CoversClass(Percentage::class)] final class PercentageTest extends TestCase { public function testCanBeRepresentedAsFloat(): void