ChangeLog-7.0.md 0000666 00000006076 13436756106 0007247 0 ustar 00 # Changes in PHPUnit 7.0
All notable changes of the PHPUnit 7.0 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [7.0.3] - 2018-03-26
* Fixed [#3028](https://github.com/sebastianbergmann/phpunit/pull/3028): TestDox name prettifier does not handle test case classes correctly that are in a `Tests\*` namespace
## [7.0.2] - 2018-02-26
### Fixed
* Fixed [#2974](https://github.com/sebastianbergmann/phpunit/issues/2974): JUnit XML logfile contains invalid characters when test output contains binary data
* Fixed [#3014](https://github.com/sebastianbergmann/phpunit/issues/3014): `TypeError` in `PHPUnit\Framework\TestCase::getActualOutput()` when callback registered using `setOutputCallback()` does not return a string
* Removed more superfluous `@throws \Exception` annotations
## [7.0.1] - 2018-02-13
### Fixed
* Fixed [#3000](https://github.com/sebastianbergmann/phpunit/issues/3000): Directories are not created recursively
* Removed superfluous `@throws \Exception` annotations from assertion methods
## [7.0.0] - 2018-02-02
### Added
* Implemented [#2967](https://github.com/sebastianbergmann/phpunit/pull/2967): Added support for PHP configuration settings to `@requires` annotation
### Changed
* Implemented [#2566](https://github.com/sebastianbergmann/phpunit/issues/2566): Use `Throwable` instead of `Exception` in `PHPUnit\Framework\TestListener` method signatures
* Implemented [#2920](https://github.com/sebastianbergmann/phpunit/pull/2920): Replace CLI TestDox printer with `rpkamp/fancy-testdox-printer`
* Scalar Type Declarations and Return Type Declarations are now used where possible (as a result, the API of `PHPUnit\Framework\TestListener`, for instance, has changed)
* Some classes are now `final`
* The visibility of some methods has been changed from `protected` to `private`
### Removed
* Implemented [#2473](https://github.com/sebastianbergmann/phpunit/issues/2473): Drop support for PHP 7.0
* `@scenario` is no longer an alias for `@test`
* The `PHPUnit\Framework\BaseTestListener` class has been removed (deprecated in PHPUnit 6.4)
* The `PHPUnit\Framework\TestCase::prepareTemplate` template method has been removed
### Fixed
* Fixed [#2169](https://github.com/sebastianbergmann/phpunit/issues/2169): `assertSame()` does not show differences when used on two arrays that are not identical
* Fixed [#2902](https://github.com/sebastianbergmann/phpunit/issues/2902): `@test` annotation gets accepted no matter what
* Fixed [#2907](https://github.com/sebastianbergmann/phpunit/issues/2907): `StringMatchesFormatDescription` constraint does not handle escaped `%` correctly
* Fixed [#2919](https://github.com/sebastianbergmann/phpunit/issues/2919): `assertJsonStringEqualsJsonString()` matches empty object as empty array
[7.0.3]: https://github.com/sebastianbergmann/phpunit/compare/7.0.2...7.0.3
[7.0.2]: https://github.com/sebastianbergmann/phpunit/compare/7.0.1...7.0.2
[7.0.1]: https://github.com/sebastianbergmann/phpunit/compare/7.0.0...7.0.1
[7.0.0]: https://github.com/sebastianbergmann/phpunit/compare/6.5...7.0.0
phpunit.xsd 0000666 00000033770 13436756106 0007004 0 ustar 00
This Schema file defines the rules by which the XML configuration file of PHPUnit 7.2 may be structured.
Root Element
The main type specifying the document structure
LICENSE 0000666 00000003006 13436756106 0005567 0 ustar 00 PHPUnit
Copyright (c) 2001-2018, Sebastian Bergmann .
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Sebastian Bergmann nor the names of his
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
README.md 0000666 00000004617 13436756106 0006052 0 ustar 00 # PHPUnit
PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks.
[![Latest Stable Version](https://img.shields.io/packagist/v/phpunit/phpunit.svg?style=flat-square)](https://packagist.org/packages/phpunit/phpunit)
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg?style=flat-square)](https://php.net/)
[![Build Status](https://img.shields.io/travis/sebastianbergmann/phpunit/master.svg?style=flat-square)](https://phpunit.de/build-status.html)
## Installation
We distribute a [PHP Archive (PHAR)](https://php.net/phar) that has all required (as well as some optional) dependencies of PHPUnit 7.2 bundled in a single file:
```bash
$ wget https://phar.phpunit.de/phpunit-nightly.phar
$ php phpunit-nightly.phar --version
```
Alternatively, you may use [Composer](https://getcomposer.org/) to download and install PHPUnit as well as its dependencies. Please refer to the "[Getting Started](https://phpunit.de/getting-started-with-phpunit.html)" guide for details on how to install PHPUnit.
## Contribute
Please refer to [CONTRIBUTING.md](https://github.com/sebastianbergmann/phpunit/blob/master/.github/CONTRIBUTING.md) for information on how to contribute to PHPUnit and its related projects.
## List of Contributors
Thanks to everyone who has contributed to PHPUnit! You can find a detailed list of contributors on every PHPUnit related package on GitHub. This list shows only the major components:
* [PHPUnit](https://github.com/sebastianbergmann/phpunit/graphs/contributors)
* [php-code-coverage](https://github.com/sebastianbergmann/php-code-coverage/graphs/contributors)
A very special thanks to everyone who has contributed to the documentation and helps maintain the translations:
* [English](https://github.com/sebastianbergmann/phpunit-documentation-english/graphs/contributors)
* [Spanish](https://github.com/sebastianbergmann/phpunit-documentation-spanish/graphs/contributors)
* [French](https://github.com/sebastianbergmann/phpunit-documentation-french/graphs/contributors)
* [Japanese](https://github.com/sebastianbergmann/phpunit-documentation-japanese/graphs/contributors)
* [Brazilian Portuguese](https://github.com/sebastianbergmann/phpunit-documentation-brazilian-portuguese/graphs/contributors)
* [Simplified Chinese](https://github.com/sebastianbergmann/phpunit-documentation-chinese/graphs/contributors)
phpunit 0000666 00000002375 13436756106 0006204 0 ustar 00 #!/usr/bin/env php
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (version_compare('7.1.0', PHP_VERSION, '>')) {
fwrite(
STDERR,
sprintf(
'This version of PHPUnit is supported on PHP 7.1 and PHP 7.2.' . PHP_EOL .
'You are using PHP %s (%s).' . PHP_EOL,
PHP_VERSION,
PHP_BINARY
)
);
die(1);
}
if (!ini_get('date.timezone')) {
ini_set('date.timezone', 'UTC');
}
foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/../vendor/autoload.php', __DIR__ . '/vendor/autoload.php') as $file) {
if (file_exists($file)) {
define('PHPUNIT_COMPOSER_INSTALL', $file);
break;
}
}
unset($file);
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
fwrite(
STDERR,
'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL .
' composer install' . PHP_EOL . PHP_EOL .
'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL
);
die(1);
}
require PHPUNIT_COMPOSER_INSTALL;
PHPUnit\TextUI\Command::main();
ChangeLog-7.1.md 0000666 00000005362 13436756106 0007245 0 ustar 00 # Changes in PHPUnit 7.1
All notable changes of the PHPUnit 7.1 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [7.1.6] - 2018-MM-DD
### Fixed
* Fixed [#3107](https://github.com/sebastianbergmann/phpunit/issues/3107): `CliTestDoxPrinter::addError()` cannot handle errors in `setUpBeforeClass()`
* Fixed [#3142](https://github.com/sebastianbergmann/phpunit/issues/3142): Method-level annotations (`@backupGlobals`, `@backupStaticAttributes`, `@errorHandler`, `@preserveGlobalState`) do not override class-level annotations
## [7.1.5] - 2018-04-29
### Fixed
* Fixed [#3105](https://github.com/sebastianbergmann/phpunit/pull/3105): Name is prettified inconsistently when snake_case notation is used
## [7.1.4] - 2018-04-18
### Fixed
* Fixed [#3034](https://github.com/sebastianbergmann/phpunit/pull/3034): `$this->getStatus()` returns `STATUS_PASSED` in `tearDown()` after unexpected exception
## [7.1.3] - 2018-04-13
### Fixed
* Fixed [#3094](https://github.com/sebastianbergmann/phpunit/issues/3094): Faulty dependency constraint affecting `getObjectForTrait()` (failure using `--prefer-lowest`)
## [7.1.2] - 2018-04-10
### Fixed
* Fixed [#2830](https://github.com/sebastianbergmann/phpunit/issues/2830): `@runClassInSeparateProcess` does not work for tests that use `@dataProvider`
* Fixed [#3059](https://github.com/sebastianbergmann/phpunit/pull/3059): `StringMatchesFormatDescription` constraint fails when matching multiline with `\r\n`
* Fixed [#3087](https://github.com/sebastianbergmann/phpunit/pull/3087): `TestCase::getTestResultObject()` can return `null`
## [7.1.1] - 2018-04-06
### Fixed
* `CliTestDoxPrinter::writeProgress()` and `TeamCity::writeProgress()` are not compatible with `ResultPrinter::writeProgress()` (on PHP 7.1)
## [7.1.0] - 2018-04-06
### Added
* Implemented [#3002](https://github.com/sebastianbergmann/phpunit/issues/3002): Support for test runner extensions
* Implemented [#3035](https://github.com/sebastianbergmann/phpunit/pull/3035): Add support for `iterable` in `assertInternalType()`
### Changed
* `PHPUnit\Framework\Assert` is no longer searched for test methods
* `ReflectionMethod::invokeArgs()` is no longer used to invoke test methods
[7.1.6]: https://github.com/sebastianbergmann/phpunit/compare/7.1.5...7.1.6
[7.1.5]: https://github.com/sebastianbergmann/phpunit/compare/7.1.4...7.1.5
[7.1.4]: https://github.com/sebastianbergmann/phpunit/compare/7.1.3...7.1.4
[7.1.3]: https://github.com/sebastianbergmann/phpunit/compare/7.1.2...7.1.3
[7.1.2]: https://github.com/sebastianbergmann/phpunit/compare/7.1.1...7.1.2
[7.1.1]: https://github.com/sebastianbergmann/phpunit/compare/7.1.0...7.1.1
[7.1.0]: https://github.com/sebastianbergmann/phpunit/compare/7.0...7.1.0
.github/ISSUE_TEMPLATE.md 0000666 00000001145 13436756106 0010631 0 ustar 00 | Q | A
| --------------------| ---------------
| PHPUnit version | x.y.z
| PHP version | x.y.z
| Installation Method | Composer / PHAR
.github/stale.yml 0000666 00000003074 13436756106 0007762 0 ustar 00 # Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- blocked
- enhancement
- backward-compatibility-break
- feature-removal
- php-support-removal
- process
- rfc
- refactoring
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had activity within the last 60 days. It will be closed after 7 days if no further activity occurs. Thank you for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been automatically closed because it has not had activity since it was marked as stale. Thank you for your contributions.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues
.github/CODE_OF_CONDUCT.md 0000666 00000004532 13436756106 0010726 0 ustar 00 # Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer at sebastian@phpunit.de. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/3/0/
.github/CONTRIBUTING.md 0000666 00000004725 13436756106 0010364 0 ustar 00 # Contributing to PHPUnit
## Contributor Code of Conduct
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
## Workflow
* Fork the project.
* Make your bug fix or feature addition.
* Add tests for it. This is important so we don't break it in a future version unintentionally.
* Send a pull request. Bonus points for topic branches.
Please make sure that you have [set up your user name and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) for use with Git. Strings such as `silly nick name ` look really stupid in the commit history of a project.
Pull requests for bug fixes must be based on the current stable branch whereas pull requests for new features must be based on the `master` branch.
We are trying to keep backwards compatibility breaks in PHPUnit to an absolute minimum. Please take this into account when proposing changes.
Due to time constraints, we are not always able to respond as quickly as we would like. Please do not take delays personal and feel free to remind us if you feel that we forgot to respond.
## Coding Guidelines
This project comes with a configuration file and an executable for [php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (`.php_cs`) that you can use to (re)format your sourcecode for compliance with this project's coding guidelines:
```bash
$ ./build/tools/php-cs-fixer fix
```
## Using PHPUnit from a Git checkout
The following commands can be used to perform the initial checkout of PHPUnit:
```bash
$ git clone git://github.com/sebastianbergmann/phpunit.git
$ cd phpunit
```
Retrieve PHPUnit's dependencies using [Composer](https://getcomposer.org/):
```bash
$ composer install
```
The `phpunit` script can be used to invoke the PHPUnit test runner:
```bash
$ ./phpunit --version
```
## Running PHPUnit's own test suite
After following the steps shown above, PHPUnit's own test suite is run like this:
```bash
$ ./phpunit
```
## Reporting issues
Please use the most specific issue tracker to search for existing tickets and to open new tickets:
* [General problems](https://github.com/sebastianbergmann/phpunit/issues)
* [Code Coverage](https://github.com/sebastianbergmann/php-code-coverage/issues)
* [Documentation](https://github.com/sebastianbergmann/phpunit-documentation-english/issues)
* [Website](https://github.com/sebastianbergmann/phpunit-website/issues)
ChangeLog-6.5.md 0000666 00000007201 13436756106 0007242 0 ustar 00 # Changes in PHPUnit 6.5
All notable changes of the PHPUnit 6.5 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [6.5.9] - 2018-MM-DD
### Fixed
* Fixed [#3142](https://github.com/sebastianbergmann/phpunit/issues/3142): Method-level annotations (`@backupGlobals`, `@backupStaticAttributes`, `@errorHandler`, `@preserveGlobalState`) do not override class-level annotations
## [6.5.8] - 2018-04-10
### Fixed
* Fixed [#2830](https://github.com/sebastianbergmann/phpunit/issues/2830): `@runClassInSeparateProcess` does not work for tests that use `@dataProvider`
## [6.5.7] - 2018-02-26
### Fixed
* Fixed [#2974](https://github.com/sebastianbergmann/phpunit/issues/2974): JUnit XML logfile contains invalid characters when test output contains binary data
## [6.5.6] - 2018-02-01
### Fixed
* Fixed [#2236](https://github.com/sebastianbergmann/phpunit/issues/2236): Exceptions in `tearDown()` do not affect `getStatus()`
* Fixed [#2950](https://github.com/sebastianbergmann/phpunit/issues/2950): Class extending `PHPUnit\Framework\TestSuite` does not extend `PHPUnit\FrameworkTestCase`
* Fixed [#2972](https://github.com/sebastianbergmann/phpunit/issues/2972): PHPUnit crashes when test suite contains both `.phpt` files and unconventionally named tests
## [6.5.5] - 2017-12-17
### Fixed
* Fixed [#2922](https://github.com/sebastianbergmann/phpunit/issues/2922): Test class is not discovered when there is a test class with `@group` and provider throwing exception in it, tests are run with `--exclude-group` for that group, there is another class called later (after the class from above), and the name of that another class does not match its filename
## [6.5.4] - 2017-12-10
### Changed
* Require version 5.0.5 of `phpunit/phpunit-mock-objects` for [phpunit-mock-objects#394](https://github.com/sebastianbergmann/phpunit-mock-objects/issues/394)
## [6.5.3] - 2017-12-06
### Fixed
* Fixed an issue with PHPT tests when `forceCoversAnnotation="true"` is configured
## [6.5.2] - 2017-12-02
### Changed
* Require version 5.0.4 of `phpunit/phpunit-mock-objects` for [phpunit-mock-objects#388](https://github.com/sebastianbergmann/phpunit-mock-objects/issues/388)
## [6.5.1] - 2017-12-01
* Fixed [#2886](https://github.com/sebastianbergmann/phpunit/pull/2886): Forced environment variables do not affect `getenv()`
## [6.5.0] - 2017-12-01
### Added
* Implemented [#2286](https://github.com/sebastianbergmann/phpunit/issues/2286): Optional `$exit` parameter for `PHPUnit\TextUI\TestRunner::run()`
* Implemented [#2496](https://github.com/sebastianbergmann/phpunit/issues/2496): Allow shallow copy of dependencies
### Fixed
* Fixed [#2654](https://github.com/sebastianbergmann/phpunit/issues/2654): Problems with `assertJsonStringEqualsJsonString()`
* Fixed [#2810](https://github.com/sebastianbergmann/phpunit/pull/2810): Code Coverage for PHPT tests does not work
[6.5.9]: https://github.com/sebastianbergmann/phpunit/compare/6.5.8...6.5.9
[6.5.8]: https://github.com/sebastianbergmann/phpunit/compare/6.5.7...6.5.8
[6.5.7]: https://github.com/sebastianbergmann/phpunit/compare/6.5.6...6.5.7
[6.5.6]: https://github.com/sebastianbergmann/phpunit/compare/6.5.5...6.5.6
[6.5.5]: https://github.com/sebastianbergmann/phpunit/compare/6.5.4...6.5.5
[6.5.4]: https://github.com/sebastianbergmann/phpunit/compare/6.5.3...6.5.4
[6.5.3]: https://github.com/sebastianbergmann/phpunit/compare/6.5.2...6.5.3
[6.5.2]: https://github.com/sebastianbergmann/phpunit/compare/6.5.1...6.5.2
[6.5.1]: https://github.com/sebastianbergmann/phpunit/compare/6.5.0...6.5.1
[6.5.0]: https://github.com/sebastianbergmann/phpunit/compare/6.4...6.5.0
phpunit.xml 0000666 00000002171 13436756106 0006775 0 ustar 00
tests/Framework
tests/Runner
tests/Util
tests/Framework/MockObject/Generator
tests/TextUI
tests/Regression
src
src/Framework/Assert/Functions.php
src/Util/PHP/eval-stdin.php
ChangeLog-7.2.md 0000666 00000003207 13436756106 0007242 0 ustar 00 # Changes in PHPUnit 7.2
All notable changes of the PHPUnit 7.2 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [7.2.0] - 2018-06-01
### Added
* Implemented [#3042](https://github.com/sebastianbergmann/phpunit/pull/3042): Add `TestCase::expectNotToPerformAssertions()` method as alternative to `@doesNotPerformAssertions` annotation
* Implemented [#3064](https://github.com/sebastianbergmann/phpunit/issues/3064): Mark tests as risky when they claim not to perform assertions but do
* Implemented [#3066](https://github.com/sebastianbergmann/phpunit/issues/3066): Validate XML configuration against XSD
* Implemented [#3076](https://github.com/sebastianbergmann/phpunit/issues/3076): Extensions can be configured via PHPUnit's XML configuration
* Implemented [#3080](https://github.com/sebastianbergmann/phpunit/issues/3080): The XML configuration arguments can have boolean elements
* Implemented [#3092](https://github.com/sebastianbergmann/phpunit/pull/3092): Ability to run tests in random order, reverse order, ordered using dependency resolution
### Changed
* Implemented [#3103](https://github.com/sebastianbergmann/phpunit/issues/3103): Merge `phpunit-mock-objects` back into PHPUnit's Git repository
* Implemented [#3115](https://github.com/sebastianbergmann/phpunit/pull/3115): Method-level `@covers` annotation overrides class-level `@coversNothing` annotation
### Removed
* Fixed [#3069](https://github.com/sebastianbergmann/phpunit/issues/3069): Method `ResultPrinter::printWaitPrompt()` seems to be unused
[7.2.0]: https://github.com/sebastianbergmann/phpunit/compare/7.1...7.2.0
src/Exception.php 0000666 00000000505 13436756106 0010021 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit;
/**
* Marker interface for PHPUnit exceptions.
*/
interface Exception
{
}
src/Util/PHP/DefaultPhpProcess.php 0000666 00000012377 13436756106 0013034 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\PHP;
use PHPUnit\Framework\Exception;
/**
* Default utility for PHP sub-processes.
*/
class DefaultPhpProcess extends AbstractPhpProcess
{
/**
* @var string
*/
protected $tempFile;
/**
* Runs a single job (PHP code) using a separate PHP process.
*
* @throws Exception
*/
public function runJob(string $job, array $settings = []): array
{
if ($this->useTemporaryFile() || $this->stdin) {
if (!($this->tempFile = \tempnam(\sys_get_temp_dir(), 'PHPUnit')) ||
\file_put_contents($this->tempFile, $job) === false) {
throw new Exception(
'Unable to write temporary file'
);
}
$job = $this->stdin;
}
return $this->runProcess($job, $settings);
}
/**
* Returns an array of file handles to be used in place of pipes
*/
protected function getHandles(): array
{
return [];
}
/**
* Handles creating the child process and returning the STDOUT and STDERR
*
* @throws Exception
*/
protected function runProcess(string $job, array $settings): array
{
$handles = $this->getHandles();
$env = null;
if ($this->env) {
$env = $_SERVER ?? [];
unset($env['argv'], $env['argc']);
$env = \array_merge($env, $this->env);
foreach ($env as $envKey => $envVar) {
if (\is_array($envVar)) {
unset($env[$envKey]);
}
}
}
$pipeSpec = [
0 => $handles[0] ?? ['pipe', 'r'],
1 => $handles[1] ?? ['pipe', 'w'],
2 => $handles[2] ?? ['pipe', 'w'],
];
$process = \proc_open(
$this->getCommand($settings, $this->tempFile),
$pipeSpec,
$pipes,
null,
$env
);
if (!\is_resource($process)) {
throw new Exception(
'Unable to spawn worker process'
);
}
if ($job) {
$this->process($pipes[0], $job);
}
\fclose($pipes[0]);
$stderr = $stdout = '';
if ($this->timeout) {
unset($pipes[0]);
while (true) {
$r = $pipes;
$w = null;
$e = null;
$n = @\stream_select($r, $w, $e, $this->timeout);
if ($n === false) {
break;
}
if ($n === 0) {
\proc_terminate($process, 9);
throw new Exception(
\sprintf(
'Job execution aborted after %d seconds',
$this->timeout
)
);
}
if ($n > 0) {
foreach ($r as $pipe) {
$pipeOffset = 0;
foreach ($pipes as $i => $origPipe) {
if ($pipe === $origPipe) {
$pipeOffset = $i;
break;
}
}
if (!$pipeOffset) {
break;
}
$line = \fread($pipe, 8192);
if ($line === '') {
\fclose($pipes[$pipeOffset]);
unset($pipes[$pipeOffset]);
} else {
if ($pipeOffset === 1) {
$stdout .= $line;
} else {
$stderr .= $line;
}
}
}
if (empty($pipes)) {
break;
}
}
}
} else {
if (isset($pipes[1])) {
$stdout = \stream_get_contents($pipes[1]);
\fclose($pipes[1]);
}
if (isset($pipes[2])) {
$stderr = \stream_get_contents($pipes[2]);
\fclose($pipes[2]);
}
}
if (isset($handles[1])) {
\rewind($handles[1]);
$stdout = \stream_get_contents($handles[1]);
\fclose($handles[1]);
}
if (isset($handles[2])) {
\rewind($handles[2]);
$stderr = \stream_get_contents($handles[2]);
\fclose($handles[2]);
}
\proc_close($process);
$this->cleanup();
return ['stdout' => $stdout, 'stderr' => $stderr];
}
protected function process($pipe, string $job): void
{
\fwrite($pipe, $job);
}
protected function cleanup(): void
{
if ($this->tempFile) {
\unlink($this->tempFile);
}
}
protected function useTemporaryFile(): bool
{
return false;
}
}
src/Util/PHP/AbstractPhpProcess.php 0000666 00000023125 13436756106 0013204 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\PHP;
use __PHP_Incomplete_Class;
use ErrorException;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\SyntheticError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestFailure;
use PHPUnit\Framework\TestResult;
use SebastianBergmann\Environment\Runtime;
/**
* Utility methods for PHP sub-processes.
*/
abstract class AbstractPhpProcess
{
/**
* @var Runtime
*/
protected $runtime;
/**
* @var bool
*/
protected $stderrRedirection = false;
/**
* @var string
*/
protected $stdin = '';
/**
* @var string
*/
protected $args = '';
/**
* @var array
*/
protected $env = [];
/**
* @var int
*/
protected $timeout = 0;
public static function factory(): self
{
if (DIRECTORY_SEPARATOR === '\\') {
return new WindowsPhpProcess;
}
return new DefaultPhpProcess;
}
public function __construct()
{
$this->runtime = new Runtime;
}
/**
* Defines if should use STDERR redirection or not.
*
* Then $stderrRedirection is TRUE, STDERR is redirected to STDOUT.
*/
public function setUseStderrRedirection(bool $stderrRedirection): void
{
$this->stderrRedirection = $stderrRedirection;
}
/**
* Returns TRUE if uses STDERR redirection or FALSE if not.
*/
public function useStderrRedirection(): bool
{
return $this->stderrRedirection;
}
/**
* Sets the input string to be sent via STDIN
*/
public function setStdin(string $stdin): void
{
$this->stdin = $stdin;
}
/**
* Returns the input string to be sent via STDIN
*/
public function getStdin(): string
{
return $this->stdin;
}
/**
* Sets the string of arguments to pass to the php job
*/
public function setArgs(string $args): void
{
$this->args = $args;
}
/**
* Returns the string of arguments to pass to the php job
*/
public function getArgs(): string
{
return $this->args;
}
/**
* Sets the array of environment variables to start the child process with
*
* @param array $env
*/
public function setEnv(array $env): void
{
$this->env = $env;
}
/**
* Returns the array of environment variables to start the child process with
*/
public function getEnv(): array
{
return $this->env;
}
/**
* Sets the amount of seconds to wait before timing out
*/
public function setTimeout(int $timeout): void
{
$this->timeout = $timeout;
}
/**
* Returns the amount of seconds to wait before timing out
*/
public function getTimeout(): int
{
return $this->timeout;
}
/**
* Runs a single test in a separate PHP process.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function runTestJob(string $job, Test $test, TestResult $result): void
{
$result->startTest($test);
$_result = $this->runJob($job);
$this->processChildResult(
$test,
$result,
$_result['stdout'],
$_result['stderr']
);
}
/**
* Returns the command based into the configurations.
*/
public function getCommand(array $settings, string $file = null): string
{
$command = $this->runtime->getBinary();
$command .= $this->settingsToParameters($settings);
if (PHP_SAPI === 'phpdbg') {
$command .= ' -qrr ';
if ($file) {
$command .= '-e ' . \escapeshellarg($file);
} else {
$command .= \escapeshellarg(__DIR__ . '/eval-stdin.php');
}
} elseif ($file) {
$command .= ' -f ' . \escapeshellarg($file);
}
if ($this->args) {
$command .= ' -- ' . $this->args;
}
if ($this->stderrRedirection === true) {
$command .= ' 2>&1';
}
return $command;
}
/**
* Runs a single job (PHP code) using a separate PHP process.
*/
abstract public function runJob(string $job, array $settings = []): array;
protected function settingsToParameters(array $settings): string
{
$buffer = '';
foreach ($settings as $setting) {
$buffer .= ' -d ' . \escapeshellarg($setting);
}
return $buffer;
}
/**
* Processes the TestResult object from an isolated process.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
private function processChildResult(Test $test, TestResult $result, string $stdout, string $stderr): void
{
$time = 0;
if (!empty($stderr)) {
$result->addError(
$test,
new Exception(\trim($stderr)),
$time
);
} else {
\set_error_handler(function ($errno, $errstr, $errfile, $errline): void {
throw new ErrorException($errstr, $errno, $errno, $errfile, $errline);
});
try {
if (\strpos($stdout, "#!/usr/bin/env php\n") === 0) {
$stdout = \substr($stdout, 19);
}
$childResult = \unserialize(\str_replace("#!/usr/bin/env php\n", '', $stdout));
\restore_error_handler();
} catch (ErrorException $e) {
\restore_error_handler();
$childResult = false;
$result->addError(
$test,
new Exception(\trim($stdout), 0, $e),
$time
);
}
if ($childResult !== false) {
if (!empty($childResult['output'])) {
$output = $childResult['output'];
}
/* @var TestCase $test */
$test->setResult($childResult['testResult']);
$test->addToAssertionCount($childResult['numAssertions']);
/** @var TestResult $childResult */
$childResult = $childResult['result'];
if ($result->getCollectCodeCoverageInformation()) {
$result->getCodeCoverage()->merge(
$childResult->getCodeCoverage()
);
}
$time = $childResult->time();
$notImplemented = $childResult->notImplemented();
$risky = $childResult->risky();
$skipped = $childResult->skipped();
$errors = $childResult->errors();
$warnings = $childResult->warnings();
$failures = $childResult->failures();
if (!empty($notImplemented)) {
$result->addError(
$test,
$this->getException($notImplemented[0]),
$time
);
} elseif (!empty($risky)) {
$result->addError(
$test,
$this->getException($risky[0]),
$time
);
} elseif (!empty($skipped)) {
$result->addError(
$test,
$this->getException($skipped[0]),
$time
);
} elseif (!empty($errors)) {
$result->addError(
$test,
$this->getException($errors[0]),
$time
);
} elseif (!empty($warnings)) {
$result->addWarning(
$test,
$this->getException($warnings[0]),
$time
);
} elseif (!empty($failures)) {
$result->addFailure(
$test,
$this->getException($failures[0]),
$time
);
}
}
}
$result->endTest($test, $time);
if (!empty($output)) {
print $output;
}
}
/**
* Gets the thrown exception from a PHPUnit\Framework\TestFailure.
*
* @see https://github.com/sebastianbergmann/phpunit/issues/74
*/
private function getException(TestFailure $error): Exception
{
$exception = $error->thrownException();
if ($exception instanceof __PHP_Incomplete_Class) {
$exceptionArray = [];
foreach ((array) $exception as $key => $value) {
$key = \substr($key, \strrpos($key, "\0") + 1);
$exceptionArray[$key] = $value;
}
$exception = new SyntheticError(
\sprintf(
'%s: %s',
$exceptionArray['_PHP_Incomplete_Class_Name'],
$exceptionArray['message']
),
$exceptionArray['code'],
$exceptionArray['file'],
$exceptionArray['line'],
$exceptionArray['trace']
);
}
return $exception;
}
}
src/Util/PHP/eval-stdin.php 0000666 00000000424 13436756106 0011475 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
eval('?>' . \file_get_contents('php://stdin'));
src/Util/PHP/WindowsPhpProcess.php 0000666 00000002130 13436756106 0013064 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\PHP;
use PHPUnit\Framework\Exception;
/**
* Windows utility for PHP sub-processes.
*
* Reading from STDOUT or STDERR hangs forever on Windows if the output is
* too large.
*
* @see https://bugs.php.net/bug.php?id=51800
*/
class WindowsPhpProcess extends DefaultPhpProcess
{
public function getCommand(array $settings, string $file = null): string
{
return '"' . parent::getCommand($settings, $file) . '"';
}
protected function getHandles(): array
{
if (false === $stdout_handle = \tmpfile()) {
throw new Exception(
'A temporary file could not be created; verify that your TEMP environment variable is writable'
);
}
return [
1 => $stdout_handle
];
}
protected function useTemporaryFile(): bool
{
return true;
}
}
src/Util/PHP/Template/TestCaseClass.tpl.dist 0000666 00000005767 13436756106 0014674 0 ustar 00 setCodeCoverage(
new CodeCoverage(
null,
unserialize('{codeCoverageFilter}')
)
);
}
$result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything});
$result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests});
$result->enforceTimeLimit({enforcesTimeLimit});
$result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests});
$result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests});
$test = new {className}('{name}', unserialize('{data}'), '{dataName}');
$test->setDependencyInput(unserialize('{dependencyInput}'));
$test->setInIsolation(TRUE);
ob_end_clean();
$test->run($result);
$output = '';
if (!$test->hasExpectationOnOutput()) {
$output = $test->getActualOutput();
}
@rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */
if ($stdout = stream_get_contents(STDOUT)) {
$output = $stdout . $output;
$streamMetaData = stream_get_meta_data(STDOUT);
if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) {
@ftruncate(STDOUT, 0);
@rewind(STDOUT);
}
}
print serialize(
array(
'testResult' => $test->getResult(),
'numAssertions' => $test->getNumAssertions(),
'result' => $result,
'output' => $output
)
);
}
$configurationFilePath = '{configurationFilePath}';
if ('' !== $configurationFilePath) {
$configuration = PHPUnit\Util\Configuration::getInstance($configurationFilePath);
$configuration->handlePHPConfiguration();
unset($configuration);
}
function __phpunit_error_handler($errno, $errstr, $errfile, $errline, $errcontext)
{
return true;
}
set_error_handler('__phpunit_error_handler');
{constants}
{included_files}
{globals}
restore_error_handler();
if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
require_once $GLOBALS['__PHPUNIT_BOOTSTRAP'];
unset($GLOBALS['__PHPUNIT_BOOTSTRAP']);
}
__phpunit_run_isolated_test();
src/Util/PHP/Template/PhptTestCase.tpl.dist 0000666 00000002106 13436756106 0014522 0 ustar 00 start(__FILE__);
}
register_shutdown_function(function() use ($coverage, $autoPrependFile) {
$output = null;
if ($coverage) {
$output = $coverage->stop();
}
file_put_contents('{coverageFile}', serialize($output));
});
ob_end_clean();
if ($autoPrependFile) {
require $autoPrependFile;
$includes = get_included_files();
$GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'][] = array_pop($includes);
unset($includes);
}
src/Util/PHP/Template/TestCaseMethod.tpl.dist 0000666 00000006074 13436756106 0015037 0 ustar 00 setCodeCoverage(
new CodeCoverage(
null,
unserialize('{codeCoverageFilter}')
)
);
}
$result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything});
$result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests});
$result->enforceTimeLimit({enforcesTimeLimit});
$result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests});
$result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests});
/** @var TestCase $test */
$test = new {className}('{methodName}', unserialize('{data}'), '{dataName}');
$test->setDependencyInput(unserialize('{dependencyInput}'));
$test->setInIsolation(TRUE);
ob_end_clean();
$test->run($result);
$output = '';
if (!$test->hasExpectationOnOutput()) {
$output = $test->getActualOutput();
}
@rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */
if ($stdout = stream_get_contents(STDOUT)) {
$output = $stdout . $output;
$streamMetaData = stream_get_meta_data(STDOUT);
if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) {
@ftruncate(STDOUT, 0);
@rewind(STDOUT);
}
}
print serialize(
array(
'testResult' => $test->getResult(),
'numAssertions' => $test->getNumAssertions(),
'result' => $result,
'output' => $output
)
);
}
$configurationFilePath = '{configurationFilePath}';
if ('' !== $configurationFilePath) {
$configuration = PHPUnit\Util\Configuration::getInstance($configurationFilePath);
$configuration->handlePHPConfiguration();
unset($configuration);
}
function __phpunit_error_handler($errno, $errstr, $errfile, $errline, $errcontext)
{
return true;
}
set_error_handler('__phpunit_error_handler');
{constants}
{included_files}
{globals}
restore_error_handler();
if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
require_once $GLOBALS['__PHPUNIT_BOOTSTRAP'];
unset($GLOBALS['__PHPUNIT_BOOTSTRAP']);
}
__phpunit_run_isolated_test();
src/Util/Blacklist.php 0000666 00000007230 13436756106 0010712 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use Composer\Autoload\ClassLoader;
use DeepCopy\DeepCopy;
use Doctrine\Instantiator\Instantiator;
use PHP_Token;
use phpDocumentor\Reflection\DocBlock;
use PHPUnit\Framework\MockObject\Generator;
use PHPUnit\Framework\TestCase;
use Prophecy\Prophet;
use ReflectionClass;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Diff\Diff;
use SebastianBergmann\Environment\Runtime;
use SebastianBergmann\Exporter\Exporter;
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
use SebastianBergmann\GlobalState\Snapshot;
use SebastianBergmann\Invoker\Invoker;
use SebastianBergmann\RecursionContext\Context;
use SebastianBergmann\Timer\Timer;
use SebastianBergmann\Version;
use Text_Template;
/**
* Utility class for blacklisting PHPUnit's own source code files.
*/
final class Blacklist
{
/**
* @var array
*/
public static $blacklistedClassNames = [
FileIteratorFacade::class => 1,
Timer::class => 1,
PHP_Token::class => 1,
TestCase::class => 2,
'PHPUnit\DbUnit\TestCase' => 2,
Generator::class => 1,
Text_Template::class => 1,
'Symfony\Component\Yaml\Yaml' => 1,
CodeCoverage::class => 1,
Diff::class => 1,
Runtime::class => 1,
Comparator::class => 1,
Exporter::class => 1,
Snapshot::class => 1,
Invoker::class => 1,
Context::class => 1,
Version::class => 1,
ClassLoader::class => 1,
Instantiator::class => 1,
DocBlock::class => 1,
Prophet::class => 1,
DeepCopy::class => 1
];
/**
* @var string[]
*/
private static $directories;
/**
* @return string[]
*/
public function getBlacklistedDirectories(): array
{
$this->initialize();
return self::$directories;
}
public function isBlacklisted(string $file): bool
{
if (\defined('PHPUNIT_TESTSUITE')) {
return false;
}
$this->initialize();
foreach (self::$directories as $directory) {
if (\strpos($file, $directory) === 0) {
return true;
}
}
return false;
}
private function initialize(): void
{
if (self::$directories === null) {
self::$directories = [];
foreach (self::$blacklistedClassNames as $className => $parent) {
if (!\class_exists($className)) {
continue;
}
$reflector = new ReflectionClass($className);
$directory = $reflector->getFileName();
for ($i = 0; $i < $parent; $i++) {
$directory = \dirname($directory);
}
self::$directories[] = $directory;
}
// Hide process isolation workaround on Windows.
if (DIRECTORY_SEPARATOR === '\\') {
// tempnam() prefix is limited to first 3 chars.
// @see https://php.net/manual/en/function.tempnam.php
self::$directories[] = \sys_get_temp_dir() . '\\PHP';
}
}
}
}
src/Util/Xml.php 0000666 00000017665 13436756106 0007557 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use DOMCharacterData;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMText;
use PHPUnit\Framework\Exception;
use ReflectionClass;
final class Xml
{
/**
* Load an $actual document into a DOMDocument. This is called
* from the selector assertions.
*
* If $actual is already a DOMDocument, it is returned with
* no changes. Otherwise, $actual is loaded into a new DOMDocument
* as either HTML or XML, depending on the value of $isHtml. If $isHtml is
* false and $xinclude is true, xinclude is performed on the loaded
* DOMDocument.
*
* Note: prior to PHPUnit 3.3.0, this method loaded a file and
* not a string as it currently does. To load a file into a
* DOMDocument, use loadFile() instead.
*
* @param DOMDocument|string $actual
* @param bool $isHtml
* @param string $filename
* @param bool $xinclude
* @param bool $strict
*
* @throws Exception
*/
public static function load($actual, bool $isHtml = false, string $filename = '', bool $xinclude = false, bool $strict = false): DOMDocument
{
if ($actual instanceof DOMDocument) {
return $actual;
}
if (!\is_string($actual)) {
throw new Exception('Could not load XML from ' . \gettype($actual));
}
if ($actual === '') {
throw new Exception('Could not load XML from empty string');
}
// Required for XInclude on Windows.
if ($xinclude) {
$cwd = \getcwd();
@\chdir(\dirname($filename));
}
$document = new DOMDocument;
$document->preserveWhiteSpace = false;
$internal = \libxml_use_internal_errors(true);
$message = '';
$reporting = \error_reporting(0);
if ($filename !== '') {
// Required for XInclude
$document->documentURI = $filename;
}
if ($isHtml) {
$loaded = $document->loadHTML($actual);
} else {
$loaded = $document->loadXML($actual);
}
if (!$isHtml && $xinclude) {
$document->xinclude();
}
foreach (\libxml_get_errors() as $error) {
$message .= PHP_EOL . $error->message;
}
\libxml_use_internal_errors($internal);
\error_reporting($reporting);
if (isset($cwd)) {
@\chdir($cwd);
}
if ($loaded === false || ($strict && $message !== '')) {
if ($filename !== '') {
throw new Exception(
\sprintf(
'Could not load "%s".%s',
$filename,
$message !== '' ? PHP_EOL . $message : ''
)
);
}
if ($message === '') {
$message = 'Could not load XML for unknown reason';
}
throw new Exception($message);
}
return $document;
}
/**
* Loads an XML (or HTML) file into a DOMDocument object.
*
* @throws Exception
*/
public static function loadFile(string $filename, bool $isHtml = false, bool $xinclude = false, bool $strict = false): DOMDocument
{
$reporting = \error_reporting(0);
$contents = \file_get_contents($filename);
\error_reporting($reporting);
if ($contents === false) {
throw new Exception(
\sprintf(
'Could not read "%s".',
$filename
)
);
}
return self::load($contents, $isHtml, $filename, $xinclude, $strict);
}
public static function removeCharacterDataNodes(DOMNode $node): void
{
if ($node->hasChildNodes()) {
for ($i = $node->childNodes->length - 1; $i >= 0; $i--) {
if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) {
$node->removeChild($child);
}
}
}
}
/**
* Escapes a string for the use in XML documents
*
* Any Unicode character is allowed, excluding the surrogate blocks, FFFE,
* and FFFF (not even as character reference).
*
* @see https://www.w3.org/TR/xml/#charsets
*/
public static function prepareString(string $string): string
{
return \preg_replace(
'/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/',
'',
\htmlspecialchars(
self::convertToUtf8($string),
ENT_QUOTES
)
);
}
/**
* "Convert" a DOMElement object into a PHP variable.
*
* @return mixed
*/
public static function xmlToVariable(DOMElement $element)
{
$variable = null;
switch ($element->tagName) {
case 'array':
$variable = [];
foreach ($element->childNodes as $entry) {
if (!$entry instanceof DOMElement || $entry->tagName !== 'element') {
continue;
}
$item = $entry->childNodes->item(0);
if ($item instanceof DOMText) {
$item = $entry->childNodes->item(1);
}
$value = self::xmlToVariable($item);
if ($entry->hasAttribute('key')) {
$variable[(string) $entry->getAttribute('key')] = $value;
} else {
$variable[] = $value;
}
}
break;
case 'object':
$className = $element->getAttribute('class');
if ($element->hasChildNodes()) {
$arguments = $element->childNodes->item(0)->childNodes;
$constructorArgs = [];
foreach ($arguments as $argument) {
if ($argument instanceof DOMElement) {
$constructorArgs[] = self::xmlToVariable($argument);
}
}
$class = new ReflectionClass($className);
$variable = $class->newInstanceArgs($constructorArgs);
} else {
$variable = new $className;
}
break;
case 'boolean':
$variable = $element->textContent === 'true';
break;
case 'integer':
case 'double':
case 'string':
$variable = $element->textContent;
\settype($variable, $element->tagName);
break;
}
return $variable;
}
private static function convertToUtf8(string $string): string
{
if (!self::isUtf8($string)) {
$string = \mb_convert_encoding($string, 'UTF-8');
}
return $string;
}
private static function isUtf8(string $string): bool
{
$length = \strlen($string);
for ($i = 0; $i < $length; $i++) {
if (\ord($string[$i]) < 0x80) {
$n = 0;
} elseif ((\ord($string[$i]) & 0xE0) === 0xC0) {
$n = 1;
} elseif ((\ord($string[$i]) & 0xF0) === 0xE0) {
$n = 2;
} elseif ((\ord($string[$i]) & 0xF0) === 0xF0) {
$n = 3;
} else {
return false;
}
for ($j = 0; $j < $n; $j++) {
if ((++$i === $length) || ((\ord($string[$i]) & 0xC0) !== 0x80)) {
return false;
}
}
}
return true;
}
}
src/Util/ConfigurationGenerator.php 0000666 00000003300 13436756106 0013452 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
final class ConfigurationGenerator
{
/**
* @var string
*/
private const TEMPLATE = <<
{tests_directory}
{src_directory}
EOT;
public function generateDefaultConfiguration(string $phpunitVersion, string $bootstrapScript, string $testsDirectory, string $srcDirectory): string
{
return \str_replace(
[
'{phpunit_version}',
'{bootstrap_script}',
'{tests_directory}',
'{src_directory}'
],
[
$phpunitVersion,
$bootstrapScript,
$testsDirectory,
$srcDirectory
],
self::TEMPLATE
);
}
}
src/Util/Json.php 0000666 00000004627 13436756106 0007722 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\Exception;
final class Json
{
/**
* Prettify json string
*
* @throws \PHPUnit\Framework\Exception
*/
public static function prettify(string $json): string
{
$decodedJson = \json_decode($json, true);
if (\json_last_error()) {
throw new Exception(
'Cannot prettify invalid json'
);
}
return \json_encode($decodedJson, JSON_PRETTY_PRINT);
}
/*
* To allow comparison of JSON strings, first process them into a consistent
* format so that they can be compared as strings.
* @return array ($error, $canonicalized_json) The $error parameter is used
* to indicate an error decoding the json. This is used to avoid ambiguity
* with JSON strings consisting entirely of 'null' or 'false'.
*/
public static function canonicalize(string $json): array
{
$decodedJson = \json_decode($json);
if (\json_last_error()) {
return [true, null];
}
self::recursiveSort($decodedJson);
$reencodedJson = \json_encode($decodedJson);
return [false, $reencodedJson];
}
/*
* JSON object keys are unordered while PHP array keys are ordered.
* Sort all array keys to ensure both the expected and actual values have
* their keys in the same order.
*/
private static function recursiveSort(&$json): void
{
if (\is_array($json) === false) {
// If the object is not empty, change it to an associative array
// so we can sort the keys (and we will still re-encode it
// correctly, since PHP encodes associative arrays as JSON objects.)
// But EMPTY objects MUST remain empty objects. (Otherwise we will
// re-encode it as a JSON array rather than a JSON object.)
// See #2919.
if (\is_object($json) && \count((array) $json) > 0) {
$json = (array) $json;
} else {
return;
}
}
\ksort($json);
foreach ($json as $key => &$value) {
self::recursiveSort($value);
}
}
}
src/Util/RegularExpression.php 0000666 00000001305 13436756106 0012460 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
final class RegularExpression
{
/**
* @throws \Exception
*
* @return false|int
*/
public static function safeMatch(string $pattern, string $subject, ?array $matches = null, int $flags = 0, int $offset = 0)
{
$handler_terminator = ErrorHandler::handleErrorOnce();
$match = \preg_match($pattern, $subject, $matches, $flags, $offset);
$handler_terminator();
return $match;
}
}
src/Util/TestDox/TextResultPrinter.php 0000666 00000002136 13436756106 0014063 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\TestDox;
/**
* Prints TestDox documentation in text format to files.
* For the CLI testdox printer please refer to \PHPUnit\TextUI\TextDoxPrinter.
*/
class TextResultPrinter extends ResultPrinter
{
/**
* Handler for 'start class' event.
*/
protected function startClass(string $name): void
{
$this->write($this->currentTestClassPrettified . PHP_EOL);
}
/**
* Handler for 'on test' event.
*
* @param mixed $name
*/
protected function onTest($name, bool $success = true): void
{
if ($success) {
$this->write(' [x] ');
} else {
$this->write(' [ ] ');
}
$this->write($name . PHP_EOL);
}
/**
* Handler for 'end class' event.
*/
protected function endClass(string $name): void
{
$this->write(PHP_EOL);
}
}
src/Util/TestDox/HtmlResultPrinter.php 0000666 00000005233 13436756106 0014044 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\TestDox;
/**
* Prints TestDox documentation in HTML format.
*/
final class HtmlResultPrinter extends ResultPrinter
{
/**
* @var string
*/
private const PAGE_HEADER = <<
Test Documentation
EOT;
/**
* @var string
*/
private const CLASS_HEADER = <<%s
EOT;
/**
* @var string
*/
private const CLASS_FOOTER = <<
EOT;
/**
* @var string
*/
private const PAGE_FOOTER = <<
EOT;
/**
* Handler for 'start run' event.
*/
protected function startRun(): void
{
$this->write(self::PAGE_HEADER);
}
/**
* Handler for 'start class' event.
*/
protected function startClass(string $name): void
{
$this->write(
\sprintf(
self::CLASS_HEADER,
$name,
$this->currentTestClassPrettified
)
);
}
/**
* Handler for 'on test' event.
*
* @param mixed $name
*/
protected function onTest($name, bool $success = true): void
{
$this->write(
\sprintf(
" %s %s \n",
$success ? '#555753' : '#ef2929',
$success ? '✓' : 'âŒ',
$name
)
);
}
/**
* Handler for 'end class' event.
*/
protected function endClass(string $name): void
{
$this->write(self::CLASS_FOOTER);
}
/**
* Handler for 'end run' event.
*/
protected function endRun(): void
{
$this->write(self::PAGE_FOOTER);
}
}
src/Util/TestDox/CliTestDoxPrinter.php 0000666 00000014147 13436756106 0013767 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\TestDox;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Runner\PhptTestCase;
use PHPUnit\TextUI\ResultPrinter;
use PHPUnit\Util\TestDox\TestResult as TestDoxTestResult;
use SebastianBergmann\Timer\Timer;
/**
* This printer is for CLI output only. For the classes that output to file, html and xml,
* please refer to the PHPUnit\Util\TestDox namespace
*/
class CliTestDoxPrinter extends ResultPrinter
{
/**
* @var TestDoxTestResult
*/
private $currentTestResult;
/**
* @var TestDoxTestResult
*/
private $previousTestResult;
/**
* @var TestDoxTestResult[]
*/
private $nonSuccessfulTestResults = [];
/**
* @var NamePrettifier
*/
private $prettifier;
public function __construct($out = null, bool $verbose = false, $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false)
{
parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse);
$this->prettifier = new NamePrettifier;
}
public function startTest(Test $test): void
{
if (!$test instanceof TestCase
&& !$test instanceof PhptTestCase
&& !$test instanceof TestSuite
) {
return;
}
$class = \get_class($test);
if ($test instanceof TestCase) {
$annotations = $test->getAnnotations();
if (isset($annotations['class']['testdox'][0])) {
$className = $annotations['class']['testdox'][0];
} else {
$className = $this->prettifier->prettifyTestClass($class);
}
if (isset($annotations['method']['testdox'][0])) {
$testMethod = $annotations['method']['testdox'][0];
} else {
$testMethod = $this->prettifier->prettifyTestMethod($test->getName(false));
}
$testMethod .= \substr($test->getDataSetAsString(false), 5);
} elseif ($test instanceof TestSuite) {
$className = $test->getName();
$testMethod = \sprintf(
'Error bootstapping suite (most likely in %s::setUpBeforeClass)',
$test->getName()
);
} elseif ($test instanceof PhptTestCase) {
$className = $class;
$testMethod = $test->getName();
}
$this->currentTestResult = new TestDoxTestResult(
function (string $color, string $buffer) {
return $this->formatWithColor($color, $buffer);
},
$className,
$testMethod
);
parent::startTest($test);
}
public function endTest(Test $test, float $time): void
{
if (!$test instanceof TestCase
&& !$test instanceof PhptTestCase
&& !$test instanceof TestSuite
) {
return;
}
parent::endTest($test, $time);
$this->currentTestResult->setRuntime($time);
$this->write($this->currentTestResult->toString($this->previousTestResult, $this->verbose));
$this->previousTestResult = $this->currentTestResult;
if (!$this->currentTestResult->isTestSuccessful()) {
$this->nonSuccessfulTestResults[] = $this->currentTestResult;
}
}
public function addError(Test $test, \Throwable $t, float $time): void
{
$this->currentTestResult->fail(
$this->formatWithColor('fg-yellow', '✘'),
(string) $t
);
}
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->currentTestResult->fail(
$this->formatWithColor('fg-yellow', '✘'),
(string) $e
);
}
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->currentTestResult->fail(
$this->formatWithColor('fg-red', '✘'),
(string) $e
);
}
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
$this->currentTestResult->fail(
$this->formatWithColor('fg-yellow', '∅'),
(string) $t,
true
);
}
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
$this->currentTestResult->fail(
$this->formatWithColor('fg-yellow', '☢'),
(string) $t,
true
);
}
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
$this->currentTestResult->fail(
$this->formatWithColor('fg-yellow', '→'),
(string) $t,
true
);
}
public function writeProgress(string $progress): void
{
}
public function flush(): void
{
}
public function printResult(TestResult $result): void
{
$this->printHeader();
$this->printNonSuccessfulTestsSummary($result->count());
$this->printFooter($result);
}
protected function printHeader(): void
{
$this->write(PHP_EOL . Timer::resourceUsage() . "\n\n");
}
private function printNonSuccessfulTestsSummary(int $numberOfExecutedTests): void
{
$numberOfNonSuccessfulTests = \count($this->nonSuccessfulTestResults);
if ($numberOfNonSuccessfulTests === 0) {
return;
}
if (($numberOfNonSuccessfulTests / $numberOfExecutedTests) >= 0.7) {
return;
}
$this->write("Summary of non-successful tests:\n\n");
$previousTestResult = null;
foreach ($this->nonSuccessfulTestResults as $testResult) {
$this->write($testResult->toString($previousTestResult, $this->verbose));
$previousTestResult = $testResult;
}
}
}
src/Util/TestDox/TestResult.php 0000666 00000007624 13436756106 0012521 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\TestDox;
final class TestResult
{
/**
* @var callable
*/
private $colorize;
/**
* @var string
*/
private $testClass;
/**
* @var string
*/
private $testMethod;
/**
* @var bool
*/
private $testSuccesful;
/**
* @var string
*/
private $symbol;
/**
* @var string
*/
private $additionalInformation;
/**
* @var bool
*/
private $additionalInformationVerbose;
/**
* @var float
*/
private $runtime;
public function __construct(callable $colorize, string $testClass, string $testMethod)
{
$this->colorize = $colorize;
$this->testClass = $testClass;
$this->testMethod = $testMethod;
$this->testSuccesful = true;
$this->symbol = ($this->colorize)('fg-green', '✔');
$this->additionalInformation = '';
}
public function isTestSuccessful(): bool
{
return $this->testSuccesful;
}
public function fail(string $symbol, string $additionalInformation, bool $additionalInformationVerbose = false): void
{
$this->testSuccesful = false;
$this->symbol = $symbol;
$this->additionalInformation = $additionalInformation;
$this->additionalInformationVerbose = $additionalInformationVerbose;
}
public function setRuntime(float $runtime): void
{
$this->runtime = $runtime;
}
public function toString(?self $previousTestResult, $verbose = false): string
{
return \sprintf(
"%s%s %s %s%s\n%s",
$previousTestResult && $previousTestResult->additionalInformationPrintable($verbose) ? PHP_EOL : '',
$this->getClassNameHeader($previousTestResult ? $previousTestResult->testClass : null),
$this->symbol,
$this->testMethod,
$verbose ? ' ' . $this->getFormattedRuntime() : '',
$this->getFormattedAdditionalInformation($verbose)
);
}
private function getClassNameHeader(?string $previousTestClass): string
{
$className = '';
if ($this->testClass !== $previousTestClass) {
if (null !== $previousTestClass) {
$className = PHP_EOL;
}
$className .= \sprintf("%s\n", $this->testClass);
}
return $className;
}
private function getFormattedRuntime(): string
{
if ($this->runtime > 5) {
return ($this->colorize)('fg-red', \sprintf('[%.2f ms]', $this->runtime * 1000));
}
if ($this->runtime > 1) {
return ($this->colorize)('fg-yellow', \sprintf('[%.2f ms]', $this->runtime * 1000));
}
return \sprintf('[%.2f ms]', $this->runtime * 1000);
}
private function getFormattedAdditionalInformation($verbose): string
{
if (!$this->additionalInformationPrintable($verbose)) {
return '';
}
return \sprintf(
" │\n%s\n",
\implode(
PHP_EOL,
\array_map(
function (string $text) {
return \sprintf(' │ %s', $text);
},
\explode(PHP_EOL, $this->additionalInformation)
)
)
);
}
private function additionalInformationPrintable(bool $verbose): bool
{
if ($this->additionalInformation === '') {
return false;
}
if ($this->additionalInformationVerbose && !$verbose) {
return false;
}
return true;
}
}
src/Util/TestDox/NamePrettifier.php 0000666 00000005052 13436756106 0013312 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\TestDox;
/**
* Prettifies class and method names for use in TestDox documentation.
*/
final class NamePrettifier
{
/**
* @var array
*/
private $strings = [];
/**
* Prettifies the name of a test class.
*/
public function prettifyTestClass(string $name): string
{
$title = $name;
if (\substr($name, -1 * \strlen('Test')) === 'Test') {
$title = \substr($title, 0, \strripos($title, 'Test'));
}
if (\strpos($name, 'Tests') === 0) {
$title = \substr($title, \strlen('Tests'));
} elseif (\strpos($name, 'Test') === 0) {
$title = \substr($title, \strlen('Test'));
}
if ($title[0] === '\\') {
$title = \substr($title, 1);
}
return $title;
}
/**
* Prettifies the name of a test method.
*/
public function prettifyTestMethod(string $name): string
{
$buffer = '';
if (!\is_string($name) || $name === '') {
return $buffer;
}
$string = \preg_replace('#\d+$#', '', $name, -1, $count);
if (\in_array($string, $this->strings)) {
$name = $string;
} elseif ($count === 0) {
$this->strings[] = $string;
}
if (\strpos($name, 'test_') === 0) {
$name = \substr($name, 5);
} elseif (\strpos($name, 'test') === 0) {
$name = \substr($name, 4);
}
if ($name === '') {
return $buffer;
}
$name[0] = \strtoupper($name[0]);
if (\strpos($name, '_') !== false) {
return \trim(\str_replace('_', ' ', $name));
}
$max = \strlen($name);
$wasNumeric = false;
for ($i = 0; $i < $max; $i++) {
if ($i > 0 && \ord($name[$i]) >= 65 && \ord($name[$i]) <= 90) {
$buffer .= ' ' . \strtolower($name[$i]);
} else {
$isNumeric = \is_numeric($name[$i]);
if (!$wasNumeric && $isNumeric) {
$buffer .= ' ';
$wasNumeric = true;
}
if ($wasNumeric && !$isNumeric) {
$wasNumeric = false;
}
$buffer .= $name[$i];
}
}
return $buffer;
}
}
src/Util/TestDox/XmlResultPrinter.php 0000666 00000012374 13436756106 0013704 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\TestDox;
use DOMDocument;
use DOMElement;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Printer;
use ReflectionClass;
class XmlResultPrinter extends Printer implements TestListener
{
/**
* @var DOMDocument
*/
private $document;
/**
* @var DOMElement
*/
private $root;
/**
* @var NamePrettifier
*/
private $prettifier;
/**
* @var null|\Throwable
*/
private $exception;
/**
* @param resource|string $out
*
* @throws Exception
*/
public function __construct($out = null)
{
$this->document = new DOMDocument('1.0', 'UTF-8');
$this->document->formatOutput = true;
$this->root = $this->document->createElement('tests');
$this->document->appendChild($this->root);
$this->prettifier = new NamePrettifier;
parent::__construct($out);
}
/**
* Flush buffer and close output.
*/
public function flush(): void
{
$this->write($this->document->saveXML());
parent::flush();
}
/**
* An error occurred.
*/
public function addError(Test $test, \Throwable $t, float $time): void
{
$this->exception = $t;
}
/**
* A warning occurred.
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
}
/**
* A failure occurred.
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->exception = $e;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
}
/**
* Risky test.
*/
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
}
/**
* A test suite started.
*/
public function startTestSuite(TestSuite $suite): void
{
}
/**
* A test suite ended.
*/
public function endTestSuite(TestSuite $suite): void
{
}
/**
* A test started.
*/
public function startTest(Test $test): void
{
$this->exception = null;
}
/**
* A test ended.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function endTest(Test $test, float $time): void
{
if (!$test instanceof TestCase) {
return;
}
/* @var TestCase $test */
$groups = \array_filter(
$test->getGroups(),
function ($group) {
return !($group === 'small' || $group === 'medium' || $group === 'large');
}
);
$node = $this->document->createElement('test');
$node->setAttribute('className', \get_class($test));
$node->setAttribute('methodName', $test->getName());
$node->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(\get_class($test)));
$node->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestMethod($test->getName()));
$node->setAttribute('status', $test->getStatus());
$node->setAttribute('time', $time);
$node->setAttribute('size', $test->getSize());
$node->setAttribute('groups', \implode(',', $groups));
$inlineAnnotations = \PHPUnit\Util\Test::getInlineAnnotations(\get_class($test), $test->getName());
if (isset($inlineAnnotations['given']) && isset($inlineAnnotations['when']) && isset($inlineAnnotations['then'])) {
$node->setAttribute('given', $inlineAnnotations['given']['value']);
$node->setAttribute('givenStartLine', $inlineAnnotations['given']['line']);
$node->setAttribute('when', $inlineAnnotations['when']['value']);
$node->setAttribute('whenStartLine', $inlineAnnotations['when']['line']);
$node->setAttribute('then', $inlineAnnotations['then']['value']);
$node->setAttribute('thenStartLine', $inlineAnnotations['then']['line']);
}
if ($this->exception !== null) {
if ($this->exception instanceof Exception) {
$steps = $this->exception->getSerializableTrace();
} else {
$steps = $this->exception->getTrace();
}
$class = new ReflectionClass($test);
$file = $class->getFileName();
foreach ($steps as $step) {
if (isset($step['file']) && $step['file'] === $file) {
$node->setAttribute('exceptionLine', $step['line']);
break;
}
}
$node->setAttribute('exceptionMessage', $this->exception->getMessage());
}
$this->root->appendChild($node);
}
}
src/Util/TestDox/ResultPrinter.php 0000666 00000017143 13436756106 0013222 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\TestDox;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Framework\WarningTestCase;
use PHPUnit\Runner\BaseTestRunner;
use PHPUnit\Util\Printer;
/**
* Base class for printers of TestDox documentation.
*/
abstract class ResultPrinter extends Printer implements TestListener
{
/**
* @var NamePrettifier
*/
protected $prettifier;
/**
* @var string
*/
protected $testClass = '';
/**
* @var int
*/
protected $testStatus;
/**
* @var array
*/
protected $tests = [];
/**
* @var int
*/
protected $successful = 0;
/**
* @var int
*/
protected $warned = 0;
/**
* @var int
*/
protected $failed = 0;
/**
* @var int
*/
protected $risky = 0;
/**
* @var int
*/
protected $skipped = 0;
/**
* @var int
*/
protected $incomplete = 0;
/**
* @var null|string
*/
protected $currentTestClassPrettified;
/**
* @var null|string
*/
protected $currentTestMethodPrettified;
/**
* @var array
*/
private $groups;
/**
* @var array
*/
private $excludeGroups;
/**
* @param resource $out
* @param array $groups
* @param array $excludeGroups
*
* @throws \PHPUnit\Framework\Exception
*/
public function __construct($out = null, array $groups = [], array $excludeGroups = [])
{
parent::__construct($out);
$this->groups = $groups;
$this->excludeGroups = $excludeGroups;
$this->prettifier = new NamePrettifier;
$this->startRun();
}
/**
* Flush buffer and close output.
*/
public function flush(): void
{
$this->doEndClass();
$this->endRun();
parent::flush();
}
/**
* An error occurred.
*/
public function addError(Test $test, \Throwable $t, float $time): void
{
if (!$this->isOfInterest($test)) {
return;
}
$this->testStatus = BaseTestRunner::STATUS_ERROR;
$this->failed++;
}
/**
* A warning occurred.
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
if (!$this->isOfInterest($test)) {
return;
}
$this->testStatus = BaseTestRunner::STATUS_WARNING;
$this->warned++;
}
/**
* A failure occurred.
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
if (!$this->isOfInterest($test)) {
return;
}
$this->testStatus = BaseTestRunner::STATUS_FAILURE;
$this->failed++;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
if (!$this->isOfInterest($test)) {
return;
}
$this->testStatus = BaseTestRunner::STATUS_INCOMPLETE;
$this->incomplete++;
}
/**
* Risky test.
*/
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
if (!$this->isOfInterest($test)) {
return;
}
$this->testStatus = BaseTestRunner::STATUS_RISKY;
$this->risky++;
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
if (!$this->isOfInterest($test)) {
return;
}
$this->testStatus = BaseTestRunner::STATUS_SKIPPED;
$this->skipped++;
}
/**
* A testsuite started.
*/
public function startTestSuite(TestSuite $suite): void
{
}
/**
* A testsuite ended.
*/
public function endTestSuite(TestSuite $suite): void
{
}
/**
* A test started.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function startTest(Test $test): void
{
if (!$this->isOfInterest($test)) {
return;
}
$class = \get_class($test);
if ($this->testClass !== $class) {
if ($this->testClass !== '') {
$this->doEndClass();
}
$classAnnotations = \PHPUnit\Util\Test::parseTestMethodAnnotations($class);
if (isset($classAnnotations['class']['testdox'][0])) {
$this->currentTestClassPrettified = $classAnnotations['class']['testdox'][0];
} else {
$this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class);
}
$this->startClass($class);
$this->testClass = $class;
$this->tests = [];
}
if ($test instanceof TestCase) {
$annotations = $test->getAnnotations();
if (isset($annotations['method']['testdox'][0])) {
$this->currentTestMethodPrettified = $annotations['method']['testdox'][0];
} else {
$this->currentTestMethodPrettified = $this->prettifier->prettifyTestMethod($test->getName(false));
}
if ($test->usesDataProvider()) {
$this->currentTestMethodPrettified .= ' ' . $test->dataDescription();
}
}
$this->testStatus = BaseTestRunner::STATUS_PASSED;
}
/**
* A test ended.
*/
public function endTest(Test $test, float $time): void
{
if (!$this->isOfInterest($test)) {
return;
}
$this->tests[] = [$this->currentTestMethodPrettified, $this->testStatus];
$this->currentTestClassPrettified = null;
$this->currentTestMethodPrettified = null;
}
protected function doEndClass(): void
{
foreach ($this->tests as $test) {
$this->onTest($test[0], $test[1] === BaseTestRunner::STATUS_PASSED);
}
$this->endClass($this->testClass);
}
/**
* Handler for 'start run' event.
*/
protected function startRun(): void
{
}
/**
* Handler for 'start class' event.
*/
protected function startClass(string $name): void
{
}
/**
* Handler for 'on test' event.
*
* @param mixed $name
*/
protected function onTest($name, bool $success = true): void
{
}
/**
* Handler for 'end class' event.
*/
protected function endClass(string $name): void
{
}
/**
* Handler for 'end run' event.
*/
protected function endRun(): void
{
}
private function isOfInterest(Test $test): bool
{
if (!$test instanceof TestCase) {
return false;
}
if ($test instanceof WarningTestCase) {
return false;
}
if (!empty($this->groups)) {
foreach ($test->getGroups() as $group) {
if (\in_array($group, $this->groups)) {
return true;
}
}
return false;
}
if (!empty($this->excludeGroups)) {
foreach ($test->getGroups() as $group) {
if (\in_array($group, $this->excludeGroups)) {
return false;
}
}
return true;
}
return true;
}
}
src/Util/Test.php 0000666 00000110006 13436756106 0007715 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PharIo\Version\VersionConstraintParser;
use PHPUnit\Framework\CodeCoverageException;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\InvalidCoversTargetException;
use PHPUnit\Framework\SelfDescribing;
use PHPUnit\Framework\SkippedTestError;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Warning;
use PHPUnit\Runner\Version;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
use SebastianBergmann\Environment\OperatingSystem;
use Traversable;
final class Test
{
/**
* @var int
*/
public const UNKNOWN = -1;
/**
* @var int
*/
public const SMALL = 0;
/**
* @var int
*/
public const MEDIUM = 1;
/**
* @var int
*/
public const LARGE = 2;
/**
* @var string
*
* @todo This constant should be private (it's public because of TestTest::testGetProvidedDataRegEx)
*/
public const REGEX_DATA_PROVIDER = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/';
/**
* @var string
*/
private const REGEX_TEST_WITH = '/@testWith\s+/';
/**
* @var string
*/
private const REGEX_EXPECTED_EXCEPTION = '(@expectedException\s+([:.\w\\\\x7f-\xff]+)(?:[\t ]+(\S*))?(?:[\t ]+(\S*))?\s*$)m';
/**
* @var string
*/
private const REGEX_REQUIRES_VERSION = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[<>=!]{0,2})\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m';
/**
* @var string
*/
private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[\d\t -.|~^]+)[ \t]*\r?$/m';
/**
* @var string
*/
private const REGEX_REQUIRES_OS = '/@requires\s+(?POS(?:FAMILY)?)\s+(?P.+?)[ \t]*\r?$/m';
/**
* @var string
*/
private const REGEX_REQUIRES_SETTING = '/@requires\s+(?Psetting)\s+(?P([^ ]+?))\s*(?P[\w\.-]+[\w\.]?)?[ \t]*\r?$/m';
/**
* @var string
*/
private const REGEX_REQUIRES = '/@requires\s+(?Pfunction|extension)\s+(?P([^ ]+?))\s*(?P[<>=!]{0,2})\s*(?P[\d\.-]+[\d\.]?)?[ \t]*\r?$/m';
/**
* @var array
*/
private static $annotationCache = [];
/**
* @var array
*/
private static $hookMethods = [];
public static function describe(\PHPUnit\Framework\Test $test): array
{
if ($test instanceof TestCase) {
return [\get_class($test), $test->getName()];
}
if ($test instanceof SelfDescribing) {
return ['', $test->toString()];
}
return ['', \get_class($test)];
}
public static function describeAsString(\PHPUnit\Framework\Test $test): string
{
if ($test instanceof SelfDescribing) {
return $test->toString();
}
return \get_class($test);
}
/**
* @throws CodeCoverageException
*
* @return array|bool
*/
public static function getLinesToBeCovered(string $className, string $methodName)
{
$annotations = self::parseTestMethodAnnotations(
$className,
$methodName
);
if (self::shouldCoversAnnotationBeUsed($annotations) === false) {
return false;
}
return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers');
}
/**
* Returns lines of code specified with the @uses annotation.
*
* @throws CodeCoverageException
*/
public static function getLinesToBeUsed(string $className, string $methodName): array
{
return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses');
}
/**
* Returns the requirements for a test.
*
* @throws Warning
*/
public static function getRequirements(string $className, string $methodName): array
{
$reflector = new ReflectionClass($className);
$docComment = $reflector->getDocComment();
$reflector = new ReflectionMethod($className, $methodName);
$docComment .= PHP_EOL . $reflector->getDocComment();
$requires = [];
if ($count = \preg_match_all(self::REGEX_REQUIRES_OS, $docComment, $matches)) {
foreach (\range(0, $count - 1) as $i) {
$requires[$matches['name'][$i]] = $matches['value'][$i];
}
}
if ($count = \preg_match_all(self::REGEX_REQUIRES_VERSION, $docComment, $matches)) {
foreach (\range(0, $count - 1) as $i) {
$requires[$matches['name'][$i]] = [
'version' => $matches['version'][$i],
'operator' => $matches['operator'][$i]
];
}
}
if ($count = \preg_match_all(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $docComment, $matches)) {
foreach (\range(0, $count - 1) as $i) {
if (!empty($requires[$matches['name'][$i]])) {
continue;
}
try {
$versionConstraintParser = new VersionConstraintParser;
$requires[$matches['name'][$i] . '_constraint'] = [
'constraint' => $versionConstraintParser->parse(\trim($matches['constraint'][$i]))
];
} catch (\PharIo\Version\Exception $e) {
throw new Warning($e->getMessage(), $e->getCode(), $e);
}
}
}
if ($count = \preg_match_all(self::REGEX_REQUIRES_SETTING, $docComment, $matches)) {
$requires['setting'] = [];
foreach (\range(0, $count - 1) as $i) {
$requires['setting'][$matches['setting'][$i]] = $matches['value'][$i];
}
}
if ($count = \preg_match_all(self::REGEX_REQUIRES, $docComment, $matches)) {
foreach (\range(0, $count - 1) as $i) {
$name = $matches['name'][$i] . 's';
if (!isset($requires[$name])) {
$requires[$name] = [];
}
$requires[$name][] = $matches['value'][$i];
if ($name !== 'extensions' || empty($matches['version'][$i])) {
continue;
}
$requires['extension_versions'][$matches['value'][$i]] = [
'version' => $matches['version'][$i],
'operator' => $matches['operator'][$i]
];
}
}
return $requires;
}
/**
* Returns the missing requirements for a test.
*
* @throws Warning
*
* @return string[]
*/
public static function getMissingRequirements(string $className, string $methodName): array
{
$required = static::getRequirements($className, $methodName);
$missing = [];
if (!empty($required['PHP'])) {
$operator = empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator'];
if (!\version_compare(PHP_VERSION, $required['PHP']['version'], $operator)) {
$missing[] = \sprintf('PHP %s %s is required.', $operator, $required['PHP']['version']);
}
} elseif (!empty($required['PHP_constraint'])) {
$version = new \PharIo\Version\Version(self::sanitizeVersionNumber(PHP_VERSION));
if (!$required['PHP_constraint']['constraint']->complies($version)) {
$missing[] = \sprintf(
'PHP version does not match the required constraint %s.',
$required['PHP_constraint']['constraint']->asString()
);
}
}
if (!empty($required['PHPUnit'])) {
$phpunitVersion = Version::id();
$operator = empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator'];
if (!\version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator)) {
$missing[] = \sprintf('PHPUnit %s %s is required.', $operator, $required['PHPUnit']['version']);
}
} elseif (!empty($required['PHPUnit_constraint'])) {
$phpunitVersion = new \PharIo\Version\Version(self::sanitizeVersionNumber(Version::id()));
if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) {
$missing[] = \sprintf(
'PHPUnit version does not match the required constraint %s.',
$required['PHPUnit_constraint']['constraint']->asString()
);
}
}
if (!empty($required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem())->getFamily()) {
$missing[] = \sprintf('Operating system %s is required.', $required['OSFAMILY']);
}
if (!empty($required['OS'])) {
$requiredOsPattern = \sprintf('/%s/i', \addcslashes($required['OS'], '/'));
if (!\preg_match($requiredOsPattern, PHP_OS)) {
$missing[] = \sprintf('Operating system matching %s is required.', $requiredOsPattern);
}
}
if (!empty($required['functions'])) {
foreach ($required['functions'] as $function) {
$pieces = \explode('::', $function);
if (\count($pieces) === 2 && \method_exists($pieces[0], $pieces[1])) {
continue;
}
if (\function_exists($function)) {
continue;
}
$missing[] = \sprintf('Function %s is required.', $function);
}
}
if (!empty($required['setting'])) {
foreach ($required['setting'] as $setting => $value) {
if (\ini_get($setting) != $value) {
$missing[] = \sprintf('Setting "%s" must be "%s".', $setting, $value);
}
}
}
if (!empty($required['extensions'])) {
foreach ($required['extensions'] as $extension) {
if (isset($required['extension_versions'][$extension])) {
continue;
}
if (!\extension_loaded($extension)) {
$missing[] = \sprintf('Extension %s is required.', $extension);
}
}
}
if (!empty($required['extension_versions'])) {
foreach ($required['extension_versions'] as $extension => $required) {
$actualVersion = \phpversion($extension);
$operator = empty($required['operator']) ? '>=' : $required['operator'];
if ($actualVersion === false || !\version_compare($actualVersion, $required['version'], $operator)) {
$missing[] = \sprintf('Extension %s %s %s is required.', $extension, $operator, $required['version']);
}
}
}
return $missing;
}
/**
* Returns the expected exception for a test.
*
* @return array|false
*/
public static function getExpectedException(string $className, ?string $methodName)
{
$reflector = new ReflectionMethod($className, $methodName);
$docComment = $reflector->getDocComment();
$docComment = \substr($docComment, 3, -2);
if (\preg_match(self::REGEX_EXPECTED_EXCEPTION, $docComment, $matches)) {
$annotations = self::parseTestMethodAnnotations(
$className,
$methodName
);
$class = $matches[1];
$code = null;
$message = '';
$messageRegExp = '';
if (isset($matches[2])) {
$message = \trim($matches[2]);
} elseif (isset($annotations['method']['expectedExceptionMessage'])) {
$message = self::parseAnnotationContent(
$annotations['method']['expectedExceptionMessage'][0]
);
}
if (isset($annotations['method']['expectedExceptionMessageRegExp'])) {
$messageRegExp = self::parseAnnotationContent(
$annotations['method']['expectedExceptionMessageRegExp'][0]
);
}
if (isset($matches[3])) {
$code = $matches[3];
} elseif (isset($annotations['method']['expectedExceptionCode'])) {
$code = self::parseAnnotationContent(
$annotations['method']['expectedExceptionCode'][0]
);
}
if (\is_numeric($code)) {
$code = (int) $code;
} elseif (\is_string($code) && \defined($code)) {
$code = (int) \constant($code);
}
return [
'class' => $class, 'code' => $code, 'message' => $message, 'message_regex' => $messageRegExp
];
}
return false;
}
/**
* Returns the provided data for a method.
*
* @throws Exception
*/
public static function getProvidedData(string $className, string $methodName): ?array
{
$reflector = new ReflectionMethod($className, $methodName);
$docComment = $reflector->getDocComment();
$data = self::getDataFromDataProviderAnnotation($docComment, $className, $methodName);
if ($data === null) {
$data = self::getDataFromTestWithAnnotation($docComment);
}
if (\is_array($data) && empty($data)) {
throw new SkippedTestError;
}
if ($data !== null) {
foreach ($data as $key => $value) {
if (!\is_array($value)) {
throw new Exception(
\sprintf(
'Data set %s is invalid.',
\is_int($key) ? '#' . $key : '"' . $key . '"'
)
);
}
}
}
return $data;
}
/**
* @throws Exception
*/
public static function getDataFromTestWithAnnotation(string $docComment): ?array
{
$docComment = self::cleanUpMultiLineAnnotation($docComment);
if (\preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) {
$offset = \strlen($matches[0][0]) + $matches[0][1];
$annotationContent = \substr($docComment, $offset);
$data = [];
foreach (\explode(PHP_EOL, $annotationContent) as $candidateRow) {
$candidateRow = \trim($candidateRow);
if ($candidateRow[0] !== '[') {
break;
}
$dataSet = \json_decode($candidateRow, true);
if (\json_last_error() !== JSON_ERROR_NONE) {
throw new Exception(
'The data set for the @testWith annotation cannot be parsed: ' . \json_last_error_msg()
);
}
$data[] = $dataSet;
}
if (!$data) {
throw new Exception('The data set for the @testWith annotation cannot be parsed.');
}
return $data;
}
return null;
}
public static function parseTestMethodAnnotations(string $className, ?string $methodName = ''): array
{
if (!isset(self::$annotationCache[$className])) {
$class = new ReflectionClass($className);
$traits = $class->getTraits();
$annotations = [];
foreach ($traits as $trait) {
$annotations = \array_merge(
$annotations,
self::parseAnnotations($trait->getDocComment())
);
}
self::$annotationCache[$className] = \array_merge(
$annotations,
self::parseAnnotations($class->getDocComment())
);
}
$cacheKey = $className . '::' . $methodName;
if ($methodName !== null && !isset(self::$annotationCache[$cacheKey])) {
try {
$method = new ReflectionMethod($className, $methodName);
$annotations = self::parseAnnotations($method->getDocComment());
} catch (ReflectionException $e) {
$annotations = [];
}
self::$annotationCache[$cacheKey] = $annotations;
}
return [
'class' => self::$annotationCache[$className],
'method' => $methodName !== null ? self::$annotationCache[$cacheKey] : []
];
}
public static function getInlineAnnotations(string $className, string $methodName): array
{
$method = new ReflectionMethod($className, $methodName);
$code = \file($method->getFileName());
$lineNumber = $method->getStartLine();
$startLine = $method->getStartLine() - 1;
$endLine = $method->getEndLine() - 1;
$methodLines = \array_slice($code, $startLine, $endLine - $startLine + 1);
$annotations = [];
foreach ($methodLines as $line) {
if (\preg_match('#/\*\*?\s*@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?\*/$#m', $line, $matches)) {
$annotations[\strtolower($matches['name'])] = [
'line' => $lineNumber,
'value' => $matches['value']
];
}
$lineNumber++;
}
return $annotations;
}
public static function parseAnnotations(string $docBlock): array
{
$annotations = [];
// Strip away the docblock header and footer to ease parsing of one line annotations
$docBlock = \substr($docBlock, 3, -2);
if (\preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docBlock, $matches)) {
$numMatches = \count($matches[0]);
for ($i = 0; $i < $numMatches; ++$i) {
$annotations[$matches['name'][$i]][] = (string) $matches['value'][$i];
}
}
return $annotations;
}
public static function getBackupSettings(string $className, string $methodName): array
{
return [
'backupGlobals' => self::getBooleanAnnotationSetting(
$className,
$methodName,
'backupGlobals'
),
'backupStaticAttributes' => self::getBooleanAnnotationSetting(
$className,
$methodName,
'backupStaticAttributes'
)
];
}
public static function getDependencies(string $className, string $methodName): array
{
$annotations = self::parseTestMethodAnnotations(
$className,
$methodName
);
$dependencies = [];
if (isset($annotations['class']['depends'])) {
$dependencies = $annotations['class']['depends'];
}
if (isset($annotations['method']['depends'])) {
$dependencies = \array_merge(
$dependencies,
$annotations['method']['depends']
);
}
return \array_unique($dependencies);
}
public static function getErrorHandlerSettings(string $className, ?string $methodName): ?bool
{
return self::getBooleanAnnotationSetting(
$className,
$methodName,
'errorHandler'
);
}
public static function getGroups(string $className, ?string $methodName = ''): array
{
$annotations = self::parseTestMethodAnnotations(
$className,
$methodName
);
$groups = [];
if (isset($annotations['method']['author'])) {
$groups = $annotations['method']['author'];
} elseif (isset($annotations['class']['author'])) {
$groups = $annotations['class']['author'];
}
if (isset($annotations['class']['group'])) {
$groups = \array_merge($groups, $annotations['class']['group']);
}
if (isset($annotations['method']['group'])) {
$groups = \array_merge($groups, $annotations['method']['group']);
}
if (isset($annotations['class']['ticket'])) {
$groups = \array_merge($groups, $annotations['class']['ticket']);
}
if (isset($annotations['method']['ticket'])) {
$groups = \array_merge($groups, $annotations['method']['ticket']);
}
foreach (['method', 'class'] as $element) {
foreach (['small', 'medium', 'large'] as $size) {
if (isset($annotations[$element][$size])) {
$groups[] = $size;
break 2;
}
}
}
return \array_unique($groups);
}
public static function getSize(string $className, ?string $methodName): int
{
$groups = \array_flip(self::getGroups($className, $methodName));
if (isset($groups['large'])) {
return self::LARGE;
}
if (isset($groups['medium'])) {
return self::MEDIUM;
}
if (isset($groups['small'])) {
return self::SMALL;
}
return self::UNKNOWN;
}
public static function getProcessIsolationSettings(string $className, string $methodName): bool
{
$annotations = self::parseTestMethodAnnotations(
$className,
$methodName
);
return isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']);
}
public static function getClassProcessIsolationSettings(string $className, string $methodName): bool
{
$annotations = self::parseTestMethodAnnotations(
$className,
$methodName
);
return isset($annotations['class']['runClassInSeparateProcess']);
}
public static function getPreserveGlobalStateSettings(string $className, string $methodName): ?bool
{
return self::getBooleanAnnotationSetting(
$className,
$methodName,
'preserveGlobalState'
);
}
public static function getHookMethods(string $className): array
{
if (!\class_exists($className, false)) {
return self::emptyHookMethodsArray();
}
if (!isset(self::$hookMethods[$className])) {
self::$hookMethods[$className] = self::emptyHookMethodsArray();
try {
$class = new ReflectionClass($className);
foreach ($class->getMethods() as $method) {
if (self::isBeforeClassMethod($method)) {
\array_unshift(
self::$hookMethods[$className]['beforeClass'],
$method->getName()
);
}
if (self::isBeforeMethod($method)) {
\array_unshift(
self::$hookMethods[$className]['before'],
$method->getName()
);
}
if (self::isAfterMethod($method)) {
self::$hookMethods[$className]['after'][] = $method->getName();
}
if (self::isAfterClassMethod($method)) {
self::$hookMethods[$className]['afterClass'][] = $method->getName();
}
}
} catch (ReflectionException $e) {
}
}
return self::$hookMethods[$className];
}
/**
* @throws CodeCoverageException
*/
private static function getLinesToBeCoveredOrUsed(string $className, string $methodName, string $mode): array
{
$annotations = self::parseTestMethodAnnotations(
$className,
$methodName
);
$classShortcut = null;
if (!empty($annotations['class'][$mode . 'DefaultClass'])) {
if (\count($annotations['class'][$mode . 'DefaultClass']) > 1) {
throw new CodeCoverageException(
\sprintf(
'More than one @%sClass annotation in class or interface "%s".',
$mode,
$className
)
);
}
$classShortcut = $annotations['class'][$mode . 'DefaultClass'][0];
}
$list = [];
if (isset($annotations['class'][$mode])) {
$list = $annotations['class'][$mode];
}
if (isset($annotations['method'][$mode])) {
$list = \array_merge($list, $annotations['method'][$mode]);
}
$codeList = [];
foreach (\array_unique($list) as $element) {
if ($classShortcut && \strncmp($element, '::', 2) === 0) {
$element = $classShortcut . $element;
}
$element = \preg_replace('/[\s()]+$/', '', $element);
$element = \explode(' ', $element);
$element = $element[0];
$codeList = \array_merge(
$codeList,
self::resolveElementToReflectionObjects($element)
);
}
return self::resolveReflectionObjectsToLines($codeList);
}
/**
* Parse annotation content to use constant/class constant values
*
* Constants are specified using a starting '@'. For example: @ClassName::CONST_NAME
*
* If the constant is not found the string is used as is to ensure maximum BC.
*/
private static function parseAnnotationContent(string $message): string
{
if (\defined($message) && (\strpos($message, '::') !== false && \substr_count($message, '::') + 1 === 2)) {
$message = \constant($message);
}
return $message;
}
/**
* Returns the provided data for a method.
*/
private static function getDataFromDataProviderAnnotation(string $docComment, string $className, string $methodName): ?iterable
{
if (\preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) {
$result = [];
foreach ($matches[1] as $match) {
$dataProviderMethodNameNamespace = \explode('\\', $match);
$leaf = \explode('::', \array_pop($dataProviderMethodNameNamespace));
$dataProviderMethodName = \array_pop($leaf);
if (empty($dataProviderMethodNameNamespace)) {
$dataProviderMethodNameNamespace = '';
} else {
$dataProviderMethodNameNamespace = \implode('\\', $dataProviderMethodNameNamespace) . '\\';
}
if (empty($leaf)) {
$dataProviderClassName = $className;
} else {
$dataProviderClassName = $dataProviderMethodNameNamespace . \array_pop($leaf);
}
$dataProviderClass = new ReflectionClass($dataProviderClassName);
$dataProviderMethod = $dataProviderClass->getMethod(
$dataProviderMethodName
);
if ($dataProviderMethod->isStatic()) {
$object = null;
} else {
$object = $dataProviderClass->newInstance();
}
if ($dataProviderMethod->getNumberOfParameters() === 0) {
$data = $dataProviderMethod->invoke($object);
} else {
$data = $dataProviderMethod->invoke($object, $methodName);
}
if ($data instanceof Traversable) {
$origData = $data;
$data = [];
foreach ($origData as $key => $value) {
if (\is_int($key)) {
$data[] = $value;
} else {
$data[$key] = $value;
}
}
}
if (\is_array($data)) {
$result = \array_merge($result, $data);
}
}
return $result;
}
return null;
}
private static function cleanUpMultiLineAnnotation(string $docComment): string
{
//removing initial ' * ' for docComment
$docComment = \str_replace("\r\n", PHP_EOL, $docComment);
$docComment = \preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', PHP_EOL, $docComment);
$docComment = \substr($docComment, 0, -1);
$docComment = \rtrim($docComment, PHP_EOL);
return $docComment;
}
private static function emptyHookMethodsArray(): array
{
return [
'beforeClass' => ['setUpBeforeClass'],
'before' => ['setUp'],
'after' => ['tearDown'],
'afterClass' => ['tearDownAfterClass']
];
}
private static function getBooleanAnnotationSetting(string $className, ?string $methodName, string $settingName): ?bool
{
$annotations = self::parseTestMethodAnnotations(
$className,
$methodName
);
if (isset($annotations['method'][$settingName])) {
if ($annotations['method'][$settingName][0] === 'enabled') {
return true;
}
if ($annotations['method'][$settingName][0] === 'disabled') {
return false;
}
}
if (isset($annotations['class'][$settingName])) {
if ($annotations['class'][$settingName][0] === 'enabled') {
return true;
}
if ($annotations['class'][$settingName][0] === 'disabled') {
return false;
}
}
return null;
}
/**
* @throws InvalidCoversTargetException
*/
private static function resolveElementToReflectionObjects(string $element): array
{
$codeToCoverList = [];
if (\strpos($element, '\\') !== false && \function_exists($element)) {
$codeToCoverList[] = new ReflectionFunction($element);
} elseif (\strpos($element, '::') !== false) {
[$className, $methodName] = \explode('::', $element);
if (isset($methodName[0]) && $methodName[0] === '<') {
$classes = [$className];
foreach ($classes as $className) {
if (!\class_exists($className) &&
!\interface_exists($className) &&
!\trait_exists($className)) {
throw new InvalidCoversTargetException(
\sprintf(
'Trying to @cover or @use not existing class or ' .
'interface "%s".',
$className
)
);
}
$class = new ReflectionClass($className);
$methods = $class->getMethods();
$inverse = isset($methodName[1]) && $methodName[1] === '!';
$visibility = 'isPublic';
if (\strpos($methodName, 'protected')) {
$visibility = 'isProtected';
} elseif (\strpos($methodName, 'private')) {
$visibility = 'isPrivate';
}
foreach ($methods as $method) {
if ($inverse && !$method->$visibility()) {
$codeToCoverList[] = $method;
} elseif (!$inverse && $method->$visibility()) {
$codeToCoverList[] = $method;
}
}
}
} else {
$classes = [$className];
foreach ($classes as $className) {
if ($className === '' && \function_exists($methodName)) {
$codeToCoverList[] = new ReflectionFunction(
$methodName
);
} else {
if (!((\class_exists($className) || \interface_exists($className) || \trait_exists($className)) &&
\method_exists($className, $methodName))) {
throw new InvalidCoversTargetException(
\sprintf(
'Trying to @cover or @use not existing method "%s::%s".',
$className,
$methodName
)
);
}
$codeToCoverList[] = new ReflectionMethod(
$className,
$methodName
);
}
}
}
} else {
$extended = false;
if (\strpos($element, '') !== false) {
$element = \str_replace('', '', $element);
$extended = true;
}
$classes = [$element];
if ($extended) {
$classes = \array_merge(
$classes,
\class_implements($element),
\class_parents($element)
);
}
foreach ($classes as $className) {
if (!\class_exists($className) &&
!\interface_exists($className) &&
!\trait_exists($className)) {
throw new InvalidCoversTargetException(
\sprintf(
'Trying to @cover or @use not existing class or ' .
'interface "%s".',
$className
)
);
}
$codeToCoverList[] = new ReflectionClass($className);
}
}
return $codeToCoverList;
}
private static function resolveReflectionObjectsToLines(array $reflectors): array
{
$result = [];
foreach ($reflectors as $reflector) {
$filename = $reflector->getFileName();
if (!isset($result[$filename])) {
$result[$filename] = [];
}
$result[$filename] = \array_merge(
$result[$filename],
\range($reflector->getStartLine(), $reflector->getEndLine())
);
}
foreach ($result as $filename => $lineNumbers) {
$result[$filename] = \array_keys(\array_flip($lineNumbers));
}
return $result;
}
private static function isBeforeClassMethod(ReflectionMethod $method): bool
{
return $method->isStatic() && \strpos($method->getDocComment(), '@beforeClass') !== false;
}
private static function isBeforeMethod(ReflectionMethod $method): bool
{
return \preg_match('/@before\b/', $method->getDocComment()) > 0;
}
private static function isAfterClassMethod(ReflectionMethod $method): bool
{
return $method->isStatic() && \strpos($method->getDocComment(), '@afterClass') !== false;
}
private static function isAfterMethod(ReflectionMethod $method): bool
{
return \preg_match('/@after\b/', $method->getDocComment()) > 0;
}
/**
* Trims any extensions from version string that follows after
* the .[.] format
*
* @return mixed
*/
private static function sanitizeVersionNumber(string $version)
{
return \preg_replace(
'/^(\d+\.\d+(?:.\d+)?).*$/',
'$1',
$version
);
}
private static function shouldCoversAnnotationBeUsed(array $annotations): bool
{
if (isset($annotations['method']['coversNothing'])) {
return false;
}
if (isset($annotations['method']['covers'])) {
return true;
}
if (isset($annotations['class']['coversNothing'])) {
return false;
}
return true;
}
}
src/Util/Log/JUnit.php 0000666 00000026304 13436756106 0010557 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\Log;
use DOMDocument;
use DOMElement;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExceptionWrapper;
use PHPUnit\Framework\SelfDescribing;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestFailure;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Filter;
use PHPUnit\Util\Printer;
use PHPUnit\Util\Xml;
use ReflectionClass;
use ReflectionException;
/**
* A TestListener that generates a logfile of the test execution in XML markup.
*
* The XML markup used is the same as the one that is used by the JUnit Ant task.
*/
class JUnit extends Printer implements TestListener
{
/**
* @var DOMDocument
*/
protected $document;
/**
* @var DOMElement
*/
protected $root;
/**
* @var bool
*/
protected $reportUselessTests = false;
/**
* @var bool
*/
protected $writeDocument = true;
/**
* @var DOMElement[]
*/
protected $testSuites = [];
/**
* @var int[]
*/
protected $testSuiteTests = [0];
/**
* @var int[]
*/
protected $testSuiteAssertions = [0];
/**
* @var int[]
*/
protected $testSuiteErrors = [0];
/**
* @var int[]
*/
protected $testSuiteFailures = [0];
/**
* @var int[]
*/
protected $testSuiteSkipped = [0];
/**
* @var int[]
*/
protected $testSuiteTimes = [0];
/**
* @var int
*/
protected $testSuiteLevel = 0;
/**
* @var DOMElement
*/
protected $currentTestCase;
/**
* Constructor.
*
* @param mixed $out
* @param bool $reportUselessTests
*
* @throws \PHPUnit\Framework\Exception
*/
public function __construct($out = null, bool $reportUselessTests = false)
{
$this->document = new DOMDocument('1.0', 'UTF-8');
$this->document->formatOutput = true;
$this->root = $this->document->createElement('testsuites');
$this->document->appendChild($this->root);
parent::__construct($out);
$this->reportUselessTests = $reportUselessTests;
}
/**
* Flush buffer and close output.
*/
public function flush(): void
{
if ($this->writeDocument === true) {
$this->write($this->getXML());
}
parent::flush();
}
/**
* An error occurred.
*
* @throws \InvalidArgumentException
*/
public function addError(Test $test, \Throwable $t, float $time): void
{
$this->doAddFault($test, $t, $time, 'error');
$this->testSuiteErrors[$this->testSuiteLevel]++;
}
/**
* A warning occurred.
*
* @throws \InvalidArgumentException
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->doAddFault($test, $e, $time, 'warning');
$this->testSuiteFailures[$this->testSuiteLevel]++;
}
/**
* A failure occurred.
*
* @throws \InvalidArgumentException
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->doAddFault($test, $e, $time, 'failure');
$this->testSuiteFailures[$this->testSuiteLevel]++;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
$this->doAddSkipped($test);
}
/**
* Risky test.
*/
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
if (!$this->reportUselessTests || $this->currentTestCase === null) {
return;
}
$error = $this->document->createElement(
'error',
Xml::prepareString(
"Risky Test\n" .
Filter::getFilteredStacktrace($t)
)
);
$error->setAttribute('type', \get_class($t));
$this->currentTestCase->appendChild($error);
$this->testSuiteErrors[$this->testSuiteLevel]++;
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
$this->doAddSkipped($test);
}
/**
* A testsuite started.
*/
public function startTestSuite(TestSuite $suite): void
{
$testSuite = $this->document->createElement('testsuite');
$testSuite->setAttribute('name', $suite->getName());
if (\class_exists($suite->getName(), false)) {
try {
$class = new ReflectionClass($suite->getName());
$testSuite->setAttribute('file', $class->getFileName());
} catch (ReflectionException $e) {
}
}
if ($this->testSuiteLevel > 0) {
$this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
} else {
$this->root->appendChild($testSuite);
}
$this->testSuiteLevel++;
$this->testSuites[$this->testSuiteLevel] = $testSuite;
$this->testSuiteTests[$this->testSuiteLevel] = 0;
$this->testSuiteAssertions[$this->testSuiteLevel] = 0;
$this->testSuiteErrors[$this->testSuiteLevel] = 0;
$this->testSuiteFailures[$this->testSuiteLevel] = 0;
$this->testSuiteSkipped[$this->testSuiteLevel] = 0;
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
}
/**
* A testsuite ended.
*/
public function endTestSuite(TestSuite $suite): void
{
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'tests',
$this->testSuiteTests[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'assertions',
$this->testSuiteAssertions[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'errors',
$this->testSuiteErrors[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'failures',
$this->testSuiteFailures[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'skipped',
$this->testSuiteSkipped[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'time',
\sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
);
if ($this->testSuiteLevel > 1) {
$this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
$this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
$this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
$this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
$this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
$this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
}
$this->testSuiteLevel--;
}
/**
* A test started.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function startTest(Test $test): void
{
$testCase = $this->document->createElement('testcase');
$testCase->setAttribute('name', $test->getName());
if ($test instanceof TestCase) {
$class = new ReflectionClass($test);
$methodName = $test->getName(!$test->usesDataProvider());
if ($class->hasMethod($methodName)) {
$method = $class->getMethod($methodName);
$testCase->setAttribute('class', $class->getName());
$testCase->setAttribute('classname', \str_replace('\\', '.', $class->getName()));
$testCase->setAttribute('file', $class->getFileName());
$testCase->setAttribute('line', $method->getStartLine());
}
}
$this->currentTestCase = $testCase;
}
/**
* A test ended.
*/
public function endTest(Test $test, float $time): void
{
if ($test instanceof TestCase) {
$numAssertions = $test->getNumAssertions();
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
$this->currentTestCase->setAttribute(
'assertions',
$numAssertions
);
}
$this->currentTestCase->setAttribute(
'time',
\sprintf('%F', $time)
);
$this->testSuites[$this->testSuiteLevel]->appendChild(
$this->currentTestCase
);
$this->testSuiteTests[$this->testSuiteLevel]++;
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
if (\method_exists($test, 'hasOutput') && $test->hasOutput()) {
$systemOut = $this->document->createElement(
'system-out',
Xml::prepareString($test->getActualOutput())
);
$this->currentTestCase->appendChild($systemOut);
}
$this->currentTestCase = null;
}
/**
* Returns the XML as a string.
*/
public function getXML(): string
{
return $this->document->saveXML();
}
/**
* Enables or disables the writing of the document
* in flush().
*
* This is a "hack" needed for the integration of
* PHPUnit with Phing.
*
* @param mixed $flag
*/
public function setWriteDocument($flag): ?string
{
if (\is_bool($flag)) {
$this->writeDocument = $flag;
}
}
/**
* Method which generalizes addError() and addFailure()
*
* @param mixed $type
*
* @throws \InvalidArgumentException
*/
private function doAddFault(Test $test, \Throwable $t, float $time, $type): void
{
if ($this->currentTestCase === null) {
return;
}
if ($test instanceof SelfDescribing) {
$buffer = $test->toString() . PHP_EOL;
} else {
$buffer = '';
}
$buffer .= TestFailure::exceptionToString($t) . PHP_EOL .
Filter::getFilteredStacktrace($t);
$fault = $this->document->createElement(
$type,
Xml::prepareString($buffer)
);
if ($t instanceof ExceptionWrapper) {
$fault->setAttribute('type', $t->getClassName());
} else {
$fault->setAttribute('type', \get_class($t));
}
$this->currentTestCase->appendChild($fault);
}
private function doAddSkipped(Test $test): void
{
if ($this->currentTestCase === null) {
return;
}
$skipped = $this->document->createElement('skipped');
$this->currentTestCase->appendChild($skipped);
$this->testSuiteSkipped[$this->testSuiteLevel]++;
}
}
src/Util/Log/TeamCity.php 0000666 00000024265 13436756106 0011251 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\Log;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExceptionWrapper;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestFailure;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\TextUI\ResultPrinter;
use PHPUnit\Util\Filter;
use ReflectionClass;
use SebastianBergmann\Comparator\ComparisonFailure;
/**
* A TestListener that generates a logfile of the test execution using the
* TeamCity format (for use with PhpStorm, for instance).
*/
class TeamCity extends ResultPrinter
{
/**
* @var bool
*/
private $isSummaryTestCountPrinted = false;
/**
* @var string
*/
private $startedTestName;
/**
* @var false|int
*/
private $flowId;
public function printResult(TestResult $result): void
{
$this->printHeader();
$this->printFooter($result);
}
/**
* An error occurred.
*
* @throws \InvalidArgumentException
*/
public function addError(Test $test, \Throwable $t, float $time): void
{
$this->printEvent(
'testFailed',
[
'name' => $test->getName(),
'message' => self::getMessage($t),
'details' => self::getDetails($t),
]
);
}
/**
* A warning occurred.
*
* @throws \InvalidArgumentException
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->printEvent(
'testFailed',
[
'name' => $test->getName(),
'message' => self::getMessage($e),
'details' => self::getDetails($e)
]
);
}
/**
* A failure occurred.
*
* @throws \InvalidArgumentException
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$parameters = [
'name' => $test->getName(),
'message' => self::getMessage($e),
'details' => self::getDetails($e),
];
if ($e instanceof ExpectationFailedException) {
$comparisonFailure = $e->getComparisonFailure();
if ($comparisonFailure instanceof ComparisonFailure) {
$expectedString = $comparisonFailure->getExpectedAsString();
if ($expectedString === null || empty($expectedString)) {
$expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected());
}
$actualString = $comparisonFailure->getActualAsString();
if ($actualString === null || empty($actualString)) {
$actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual());
}
if ($actualString !== null && $expectedString !== null) {
$parameters['type'] = 'comparisonFailure';
$parameters['actual'] = $actualString;
$parameters['expected'] = $expectedString;
}
}
}
$this->printEvent('testFailed', $parameters);
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
$this->printIgnoredTest($test->getName(), $t);
}
/**
* Risky test.
*
* @throws \InvalidArgumentException
*/
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
$this->addError($test, $t, $time);
}
/**
* Skipped test.
*
* @throws \ReflectionException
*/
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
$testName = $test->getName();
if ($this->startedTestName !== $testName) {
$this->startTest($test);
$this->printIgnoredTest($testName, $t);
$this->endTest($test, $time);
} else {
$this->printIgnoredTest($testName, $t);
}
}
public function printIgnoredTest($testName, \Throwable $t): void
{
$this->printEvent(
'testIgnored',
[
'name' => $testName,
'message' => self::getMessage($t),
'details' => self::getDetails($t),
]
);
}
/**
* A testsuite started.
*
* @throws \ReflectionException
*/
public function startTestSuite(TestSuite $suite): void
{
if (\stripos(\ini_get('disable_functions'), 'getmypid') === false) {
$this->flowId = \getmypid();
} else {
$this->flowId = false;
}
if (!$this->isSummaryTestCountPrinted) {
$this->isSummaryTestCountPrinted = true;
$this->printEvent(
'testCount',
['count' => \count($suite)]
);
}
$suiteName = $suite->getName();
if (empty($suiteName)) {
return;
}
$parameters = ['name' => $suiteName];
if (\class_exists($suiteName, false)) {
$fileName = self::getFileName($suiteName);
$parameters['locationHint'] = "php_qn://$fileName::\\$suiteName";
} else {
$split = \explode('::', $suiteName);
if (\count($split) === 2 && \method_exists($split[0], $split[1])) {
$fileName = self::getFileName($split[0]);
$parameters['locationHint'] = "php_qn://$fileName::\\$suiteName";
$parameters['name'] = $split[1];
}
}
$this->printEvent('testSuiteStarted', $parameters);
}
/**
* A testsuite ended.
*/
public function endTestSuite(TestSuite $suite): void
{
$suiteName = $suite->getName();
if (empty($suiteName)) {
return;
}
$parameters = ['name' => $suiteName];
if (!\class_exists($suiteName, false)) {
$split = \explode('::', $suiteName);
if (\count($split) === 2 && \method_exists($split[0], $split[1])) {
$parameters['name'] = $split[1];
}
}
$this->printEvent('testSuiteFinished', $parameters);
}
/**
* A test started.
*
* @throws \ReflectionException
*/
public function startTest(Test $test): void
{
$testName = $test->getName();
$this->startedTestName = $testName;
$params = ['name' => $testName];
if ($test instanceof TestCase) {
$className = \get_class($test);
$fileName = self::getFileName($className);
$params['locationHint'] = "php_qn://$fileName::\\$className::$testName";
}
$this->printEvent('testStarted', $params);
}
/**
* A test ended.
*
* @param Test $test
* @param float $time
*/
public function endTest(Test $test, float $time): void
{
parent::endTest($test, $time);
$this->printEvent(
'testFinished',
[
'name' => $test->getName(),
'duration' => (int) (\round($time, 2) * 1000)
]
);
}
protected function writeProgress(string $progress): void
{
}
/**
* @param string $eventName
* @param array $params
*/
private function printEvent($eventName, $params = []): void
{
$this->write("\n##teamcity[$eventName");
if ($this->flowId) {
$params['flowId'] = $this->flowId;
}
foreach ($params as $key => $value) {
$escapedValue = self::escapeValue($value);
$this->write(" $key='$escapedValue'");
}
$this->write("]\n");
}
/**
* @param \Throwable $t
*/
private static function getMessage(\Throwable $t): string
{
$message = '';
if ($t instanceof ExceptionWrapper) {
if ($t->getClassName() !== '') {
$message .= $t->getClassName();
}
if ($message !== '' && $t->getMessage() !== '') {
$message .= ' : ';
}
}
return $message . $t->getMessage();
}
/**
* @param \Throwable $t
*
* @throws \InvalidArgumentException
*/
private static function getDetails(\Throwable $t): string
{
$stackTrace = Filter::getFilteredStacktrace($t);
$previous = $t instanceof ExceptionWrapper ? $t->getPreviousWrapped() : $t->getPrevious();
while ($previous) {
$stackTrace .= "\nCaused by\n" .
TestFailure::exceptionToString($previous) . PHP_EOL .
Filter::getFilteredStacktrace($previous);
$previous = $previous instanceof ExceptionWrapper ?
$previous->getPreviousWrapped() : $previous->getPrevious();
}
return ' ' . \str_replace(PHP_EOL, "\n ", $stackTrace);
}
/**
* @param mixed $value
*/
private static function getPrimitiveValueAsString($value): ?string
{
if ($value === null) {
return 'null';
}
if (\is_bool($value)) {
return $value === true ? 'true' : 'false';
}
if (\is_scalar($value)) {
return \print_r($value, true);
}
return null;
}
/**
* @param string $text
*/
private static function escapeValue(string $text): string
{
return \str_replace(
['|', "'", PHP_EOL, "\r", ']', '['],
['||', "|'", '|n', '|r', '|]', '|['],
$text
);
}
/**
* @param string $className
*
* @throws \ReflectionException
*/
private static function getFileName($className): string
{
$reflectionClass = new ReflectionClass($className);
return $reflectionClass->getFileName();
}
}
src/Util/InvalidArgumentHelper.php 0000666 00000001677 13436756106 0013244 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\Exception;
/**
* Factory for PHPUnit\Framework\Exception objects that are used to describe
* invalid arguments passed to a function or method.
*/
final class InvalidArgumentHelper
{
public static function factory(int $argument, string $type, $value = null): Exception
{
$stack = \debug_backtrace();
return new Exception(
\sprintf(
'Argument #%d%sof %s::%s() must be a %s',
$argument,
$value !== null ? ' (' . \gettype($value) . '#' . $value . ')' : ' (No Value) ',
$stack[1]['class'],
$stack[1]['function'],
$type
)
);
}
}
src/Util/Filter.php 0000666 00000004466 13436756106 0010237 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\SyntheticError;
final class Filter
{
public static function getFilteredStacktrace(\Throwable $t): string
{
$prefix = false;
$script = \realpath($GLOBALS['_SERVER']['SCRIPT_NAME']);
if (\defined('__PHPUNIT_PHAR_ROOT__')) {
$prefix = __PHPUNIT_PHAR_ROOT__;
}
$filteredStacktrace = '';
if ($t instanceof SyntheticError) {
$eTrace = $t->getSyntheticTrace();
$eFile = $t->getSyntheticFile();
$eLine = $t->getSyntheticLine();
} elseif ($t instanceof Exception) {
$eTrace = $t->getSerializableTrace();
$eFile = $t->getFile();
$eLine = $t->getLine();
} else {
if ($t->getPrevious()) {
$t = $t->getPrevious();
}
$eTrace = $t->getTrace();
$eFile = $t->getFile();
$eLine = $t->getLine();
}
if (!self::frameExists($eTrace, $eFile, $eLine)) {
\array_unshift(
$eTrace,
['file' => $eFile, 'line' => $eLine]
);
}
$blacklist = new Blacklist;
foreach ($eTrace as $frame) {
if (isset($frame['file']) && \is_file($frame['file']) &&
!$blacklist->isBlacklisted($frame['file']) &&
($prefix === false || \strpos($frame['file'], $prefix) !== 0) &&
$frame['file'] !== $script) {
$filteredStacktrace .= \sprintf(
"%s:%s\n",
$frame['file'],
$frame['line'] ?? '?'
);
}
}
return $filteredStacktrace;
}
private static function frameExists(array $trace, string $file, int $line): bool
{
foreach ($trace as $frame) {
if (isset($frame['file']) && $frame['file'] === $file &&
isset($frame['line']) && $frame['line'] === $line) {
return true;
}
}
return false;
}
}
src/Util/FileLoader.php 0000666 00000004115 13436756106 0011007 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\Exception;
/**
* Utility methods to load PHP sourcefiles.
*/
final class FileLoader
{
/**
* Checks if a PHP sourcecode file is readable. The sourcecode file is loaded through the load() method.
*
* As a fallback, PHP looks in the directory of the file executing the stream_resolve_include_path function.
* We do not want to load the Test.php file here, so skip it if it found that.
* PHP prioritizes the include_path setting, so if the current directory is in there, it will first look in the
* current working directory.
*
* @throws Exception
*/
public static function checkAndLoad(string $filename): string
{
$includePathFilename = \stream_resolve_include_path($filename);
$localFile = __DIR__ . DIRECTORY_SEPARATOR . $filename;
/**
* @see https://github.com/sebastianbergmann/phpunit/pull/2751
*/
$isReadable = @\fopen($includePathFilename, 'r') !== false;
if (!$includePathFilename || !$isReadable || $includePathFilename === $localFile) {
throw new Exception(
\sprintf('Cannot open file "%s".' . PHP_EOL, $filename)
);
}
self::load($includePathFilename);
return $includePathFilename;
}
/**
* Loads a PHP sourcefile.
*/
public static function load(string $filename): void
{
$oldVariableNames = \array_keys(\get_defined_vars());
include_once $filename;
$newVariables = \get_defined_vars();
$newVariableNames = \array_diff(\array_keys($newVariables), $oldVariableNames);
foreach ($newVariableNames as $variableName) {
if ($variableName !== 'oldVariableNames') {
$GLOBALS[$variableName] = $newVariables[$variableName];
}
}
}
}
src/Util/TextTestListRenderer.php 0000666 00000002202 13436756106 0013103 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\PhptTestCase;
final class TextTestListRenderer
{
public function render(TestSuite $suite): string
{
$buffer = 'Available test(s):' . PHP_EOL;
foreach (new \RecursiveIteratorIterator($suite->getIterator()) as $test) {
if ($test instanceof TestCase) {
$name = \sprintf(
'%s::%s',
\get_class($test),
\str_replace(' with data set ', '', $test->getName())
);
} elseif ($test instanceof PhptTestCase) {
$name = $test->getName();
} else {
continue;
}
$buffer .= \sprintf(
' - %s' . PHP_EOL,
$name
);
}
return $buffer;
}
}
src/Util/XmlTestListRenderer.php 0000666 00000004630 13436756106 0012726 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\PhptTestCase;
final class XmlTestListRenderer
{
public function render(TestSuite $suite): string
{
$writer = new \XMLWriter;
$writer->openMemory();
$writer->setIndent(true);
$writer->startDocument();
$writer->startElement('tests');
$currentTestCase = null;
foreach (new \RecursiveIteratorIterator($suite->getIterator()) as $test) {
if ($test instanceof TestCase) {
if (\get_class($test) !== $currentTestCase) {
if ($currentTestCase !== null) {
$writer->endElement();
}
$writer->startElement('testCaseClass');
$writer->writeAttribute('name', \get_class($test));
$currentTestCase = \get_class($test);
}
$writer->startElement('testCaseMethod');
$writer->writeAttribute('name', $test->getName(false));
$writer->writeAttribute('groups', \implode(',', $test->getGroups()));
if (!empty($test->getDataSetAsString(false))) {
$writer->writeAttribute(
'dataSet',
\str_replace(
' with data set ',
'',
$test->getDataSetAsString(false)
)
);
}
$writer->endElement();
} elseif ($test instanceof PhptTestCase) {
if ($currentTestCase !== null) {
$writer->endElement();
$currentTestCase = null;
}
$writer->startElement('phptFile');
$writer->writeAttribute('path', $test->getName());
$writer->endElement();
} else {
continue;
}
}
if ($currentTestCase !== null) {
$writer->endElement();
}
$writer->endElement();
return $writer->outputMemory();
}
}
src/Util/Filesystem.php 0000666 00000001270 13436756106 0011124 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
/**
* Filesystem helpers.
*/
final class Filesystem
{
/**
* Maps class names to source file names:
* - PEAR CS: Foo_Bar_Baz -> Foo/Bar/Baz.php
* - Namespace: Foo\Bar\Baz -> Foo/Bar/Baz.php
*/
public static function classNameToFilename(string $className): string
{
return \str_replace(
['_', '\\'],
DIRECTORY_SEPARATOR,
$className
) . '.php';
}
}
src/Util/Getopt.php 0000666 00000011700 13436756106 0010241 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\Exception;
/**
* Command-line options parsing class.
*/
final class Getopt
{
/**
* @throws Exception
*/
public static function getopt(array $args, string $short_options, array $long_options = null): array
{
if (empty($args)) {
return [[], []];
}
$opts = [];
$non_opts = [];
if ($long_options) {
\sort($long_options);
}
if (isset($args[0][0]) && $args[0][0] !== '-') {
\array_shift($args);
}
\reset($args);
$args = \array_map('trim', $args);
/* @noinspection ComparisonOperandsOrderInspection */
while (false !== $arg = \current($args)) {
$i = \key($args);
\next($args);
if ($arg === '') {
continue;
}
if ($arg === '--') {
$non_opts = \array_merge($non_opts, \array_slice($args, $i + 1));
break;
}
if ($arg[0] !== '-' || (\strlen($arg) > 1 && $arg[1] === '-' && !$long_options)) {
$non_opts[] = $args[$i];
continue;
}
if (\strlen($arg) > 1 && $arg[1] === '-') {
self::parseLongOption(
\substr($arg, 2),
$long_options,
$opts,
$args
);
} else {
self::parseShortOption(
\substr($arg, 1),
$short_options,
$opts,
$args
);
}
}
return [$opts, $non_opts];
}
/**
* @throws Exception
*/
private static function parseShortOption(string $arg, string $short_options, array &$opts, array &$args): void
{
$argLen = \strlen($arg);
for ($i = 0; $i < $argLen; $i++) {
$opt = $arg[$i];
$opt_arg = null;
if ($arg[$i] === ':' || ($spec = \strstr($short_options, $opt)) === false) {
throw new Exception(
"unrecognized option -- $opt"
);
}
if (\strlen($spec) > 1 && $spec[1] === ':') {
if ($i + 1 < $argLen) {
$opts[] = [$opt, \substr($arg, $i + 1)];
break;
}
if (!(\strlen($spec) > 2 && $spec[2] === ':')) {
/* @noinspection ComparisonOperandsOrderInspection */
if (false === $opt_arg = \current($args)) {
throw new Exception(
"option requires an argument -- $opt"
);
}
\next($args);
}
}
$opts[] = [$opt, $opt_arg];
}
}
/**
* @throws Exception
*/
private static function parseLongOption(string $arg, array $long_options, array &$opts, array &$args): void
{
$count = \count($long_options);
$list = \explode('=', $arg);
$opt = $list[0];
$opt_arg = null;
if (\count($list) > 1) {
$opt_arg = $list[1];
}
$opt_len = \strlen($opt);
for ($i = 0; $i < $count; $i++) {
$long_opt = $long_options[$i];
$opt_start = \substr($long_opt, 0, $opt_len);
if ($opt_start !== $opt) {
continue;
}
$opt_rest = \substr($long_opt, $opt_len);
if ($opt_rest !== '' && $i + 1 < $count && $opt[0] !== '=' &&
\strpos($long_options[$i + 1], $opt) === 0) {
throw new Exception(
"option --$opt is ambiguous"
);
}
if (\substr($long_opt, -1) === '=') {
/* @noinspection StrlenInEmptyStringCheckContextInspection */
if (\substr($long_opt, -2) !== '==' && !\strlen($opt_arg)) {
/* @noinspection ComparisonOperandsOrderInspection */
if (false === $opt_arg = \current($args)) {
throw new Exception(
"option --$opt requires an argument"
);
}
\next($args);
}
} elseif ($opt_arg) {
throw new Exception(
"option --$opt doesn't allow an argument"
);
}
$full_option = '--' . \preg_replace('/={1,2}$/', '', $long_opt);
$opts[] = [$full_option, $opt_arg];
return;
}
throw new Exception("unrecognized option --$opt");
}
}
src/Util/GlobalState.php 0000666 00000011274 13436756106 0011206 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use Closure;
final class GlobalState
{
/**
* @var string[]
*/
private const SUPER_GLOBAL_ARRAYS = [
'_ENV',
'_POST',
'_GET',
'_COOKIE',
'_SERVER',
'_FILES',
'_REQUEST'
];
public static function getIncludedFilesAsString(): string
{
return static::processIncludedFilesAsString(\get_included_files());
}
/**
* @param string[] $files
*/
public static function processIncludedFilesAsString(array $files): string
{
$blacklist = new Blacklist;
$prefix = false;
$result = '';
if (\defined('__PHPUNIT_PHAR__')) {
$prefix = 'phar://' . __PHPUNIT_PHAR__ . '/';
}
for ($i = \count($files) - 1; $i > 0; $i--) {
$file = $files[$i];
if (!empty($GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST']) &&
\in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'])) {
continue;
}
if ($prefix !== false && \strpos($file, $prefix) === 0) {
continue;
}
// Skip virtual file system protocols
if (\preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) {
continue;
}
if (!$blacklist->isBlacklisted($file) && \is_file($file)) {
$result = 'require_once \'' . $file . "';\n" . $result;
}
}
return $result;
}
public static function getIniSettingsAsString(): string
{
$result = '';
$iniSettings = \ini_get_all(null, false);
foreach ($iniSettings as $key => $value) {
$result .= \sprintf(
'@ini_set(%s, %s);' . PHP_EOL,
self::exportVariable($key),
self::exportVariable($value)
);
}
return $result;
}
public static function getConstantsAsString(): string
{
$constants = \get_defined_constants(true);
$result = '';
if (isset($constants['user'])) {
foreach ($constants['user'] as $name => $value) {
$result .= \sprintf(
'if (!defined(\'%s\')) define(\'%s\', %s);' . PHP_EOL,
$name,
$name,
self::exportVariable($value)
);
}
}
return $result;
}
public static function getGlobalsAsString(): string
{
$result = '';
foreach (self::SUPER_GLOBAL_ARRAYS as $superGlobalArray) {
if (isset($GLOBALS[$superGlobalArray]) && \is_array($GLOBALS[$superGlobalArray])) {
foreach (\array_keys($GLOBALS[$superGlobalArray]) as $key) {
if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) {
continue;
}
$result .= \sprintf(
'$GLOBALS[\'%s\'][\'%s\'] = %s;' . PHP_EOL,
$superGlobalArray,
$key,
self::exportVariable($GLOBALS[$superGlobalArray][$key])
);
}
}
}
$blacklist = self::SUPER_GLOBAL_ARRAYS;
$blacklist[] = 'GLOBALS';
foreach (\array_keys($GLOBALS) as $key) {
if (!$GLOBALS[$key] instanceof Closure && !\in_array($key, $blacklist, true)) {
$result .= \sprintf(
'$GLOBALS[\'%s\'] = %s;' . PHP_EOL,
$key,
self::exportVariable($GLOBALS[$key])
);
}
}
return $result;
}
private static function exportVariable($variable): string
{
if (\is_scalar($variable) || $variable === null ||
(\is_array($variable) && self::arrayOnlyContainsScalars($variable))) {
return \var_export($variable, true);
}
return 'unserialize(' . \var_export(\serialize($variable), true) . ')';
}
private static function arrayOnlyContainsScalars(array $array): bool
{
$result = true;
foreach ($array as $element) {
if (\is_array($element)) {
$result = self::arrayOnlyContainsScalars($element);
} elseif (!\is_scalar($element) && $element !== null) {
$result = false;
}
if ($result === false) {
break;
}
}
return $result;
}
}
src/Util/Configuration.php 0000666 00000115464 13436756106 0011622 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use DOMElement;
use DOMXPath;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\TestSuiteSorter;
use PHPUnit\TextUI\ResultPrinter;
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
/**
* Wrapper for the PHPUnit XML configuration file.
*
* Example XML configuration file:
*
*
*
*
*
*
* /path/to/files
* /path/to/MyTest.php
* /path/to/files/exclude
*
*
*
*
*
* name
*
*
* name
*
*
*
*
*
* name
*
*
* name
*
*
*
*
*
* /path/to/files
* /path/to/file
*
* /path/to/files
* /path/to/file
*
*
*
*
*
*
*
*
*
* Sebastian
*
*
* 22
* April
* 19.78
*
*
* MyRelativeFile.php
* MyRelativeDir
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* .
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
final class Configuration
{
/**
* @var self[]
*/
private static $instances = [];
/**
* @var \DOMDocument
*/
private $document;
/**
* @var DOMXPath
*/
private $xpath;
/**
* @var string
*/
private $filename;
/**
* @var \LibXMLError[]
*/
private $errors = [];
/**
* Returns a PHPUnit configuration object.
*
* @throws Exception
*/
public static function getInstance(string $filename): self
{
$realPath = \realpath($filename);
if ($realPath === false) {
throw new Exception(
\sprintf(
'Could not read "%s".',
$filename
)
);
}
/** @var string $realPath */
if (!isset(self::$instances[$realPath])) {
self::$instances[$realPath] = new self($realPath);
}
return self::$instances[$realPath];
}
/**
* Loads a PHPUnit configuration file.
*
* @throws Exception
*/
private function __construct(string $filename)
{
$this->filename = $filename;
$this->document = Xml::loadFile($filename, false, true, true);
$this->xpath = new DOMXPath($this->document);
$this->validateConfigurationAgainstSchema();
}
/**
* @codeCoverageIgnore
*/
private function __clone()
{
}
public function hasValidationErrors(): bool
{
return \count($this->errors) > 0;
}
public function getValidationErrors(): array
{
$result = [];
foreach ($this->errors as $error) {
if (!isset($result[$error->line])) {
$result[$error->line] = [];
}
$result[$error->line][] = \trim($error->message);
}
return $result;
}
/**
* Returns the real path to the configuration file.
*/
public function getFilename(): string
{
return $this->filename;
}
public function getExtensionConfiguration(): array
{
$result = [];
foreach ($this->xpath->query('extensions/extension') as $extension) {
/** @var DOMElement $extension */
$class = (string) $extension->getAttribute('class');
$file = '';
$arguments = $this->getConfigurationArguments($extension->childNodes);
if ($extension->getAttribute('file')) {
$file = $this->toAbsolutePath(
(string) $extension->getAttribute('file'),
true
);
}
$result[] = [
'class' => $class,
'file' => $file,
'arguments' => $arguments
];
}
return $result;
}
/**
* Returns the configuration for SUT filtering.
*/
public function getFilterConfiguration(): array
{
$addUncoveredFilesFromWhitelist = true;
$processUncoveredFilesFromWhitelist = false;
$includeDirectory = [];
$includeFile = [];
$excludeDirectory = [];
$excludeFile = [];
$tmp = $this->xpath->query('filter/whitelist');
if ($tmp->length === 1) {
if ($tmp->item(0)->hasAttribute('addUncoveredFilesFromWhitelist')) {
$addUncoveredFilesFromWhitelist = $this->getBoolean(
(string) $tmp->item(0)->getAttribute(
'addUncoveredFilesFromWhitelist'
),
true
);
}
if ($tmp->item(0)->hasAttribute('processUncoveredFilesFromWhitelist')) {
$processUncoveredFilesFromWhitelist = $this->getBoolean(
(string) $tmp->item(0)->getAttribute(
'processUncoveredFilesFromWhitelist'
),
false
);
}
$includeDirectory = $this->readFilterDirectories(
'filter/whitelist/directory'
);
$includeFile = $this->readFilterFiles(
'filter/whitelist/file'
);
$excludeDirectory = $this->readFilterDirectories(
'filter/whitelist/exclude/directory'
);
$excludeFile = $this->readFilterFiles(
'filter/whitelist/exclude/file'
);
}
return [
'whitelist' => [
'addUncoveredFilesFromWhitelist' => $addUncoveredFilesFromWhitelist,
'processUncoveredFilesFromWhitelist' => $processUncoveredFilesFromWhitelist,
'include' => [
'directory' => $includeDirectory,
'file' => $includeFile
],
'exclude' => [
'directory' => $excludeDirectory,
'file' => $excludeFile
]
]
];
}
/**
* Returns the configuration for groups.
*/
public function getGroupConfiguration(): array
{
return $this->parseGroupConfiguration('groups');
}
/**
* Returns the configuration for testdox groups.
*/
public function getTestdoxGroupConfiguration(): array
{
return $this->parseGroupConfiguration('testdoxGroups');
}
/**
* Returns the configuration for listeners.
*/
public function getListenerConfiguration(): array
{
$result = [];
foreach ($this->xpath->query('listeners/listener') as $listener) {
/** @var DOMElement $listener */
$class = (string) $listener->getAttribute('class');
$file = '';
$arguments = $this->getConfigurationArguments($listener->childNodes);
if ($listener->getAttribute('file')) {
$file = $this->toAbsolutePath(
(string) $listener->getAttribute('file'),
true
);
}
$result[] = [
'class' => $class,
'file' => $file,
'arguments' => $arguments
];
}
return $result;
}
/**
* Returns the logging configuration.
*/
public function getLoggingConfiguration(): array
{
$result = [];
foreach ($this->xpath->query('logging/log') as $log) {
/** @var DOMElement $log */
$type = (string) $log->getAttribute('type');
$target = (string) $log->getAttribute('target');
if (!$target) {
continue;
}
$target = $this->toAbsolutePath($target);
if ($type === 'coverage-html') {
if ($log->hasAttribute('lowUpperBound')) {
$result['lowUpperBound'] = $this->getInteger(
(string) $log->getAttribute('lowUpperBound'),
50
);
}
if ($log->hasAttribute('highLowerBound')) {
$result['highLowerBound'] = $this->getInteger(
(string) $log->getAttribute('highLowerBound'),
90
);
}
} elseif ($type === 'coverage-crap4j') {
if ($log->hasAttribute('threshold')) {
$result['crap4jThreshold'] = $this->getInteger(
(string) $log->getAttribute('threshold'),
30
);
}
} elseif ($type === 'coverage-text') {
if ($log->hasAttribute('showUncoveredFiles')) {
$result['coverageTextShowUncoveredFiles'] = $this->getBoolean(
(string) $log->getAttribute('showUncoveredFiles'),
false
);
}
if ($log->hasAttribute('showOnlySummary')) {
$result['coverageTextShowOnlySummary'] = $this->getBoolean(
(string) $log->getAttribute('showOnlySummary'),
false
);
}
}
$result[$type] = $target;
}
return $result;
}
/**
* Returns the PHP configuration.
*/
public function getPHPConfiguration(): array
{
$result = [
'include_path' => [],
'ini' => [],
'const' => [],
'var' => [],
'env' => [],
'post' => [],
'get' => [],
'cookie' => [],
'server' => [],
'files' => [],
'request' => []
];
foreach ($this->xpath->query('php/includePath') as $includePath) {
$path = (string) $includePath->textContent;
if ($path) {
$result['include_path'][] = $this->toAbsolutePath($path);
}
}
foreach ($this->xpath->query('php/ini') as $ini) {
/** @var DOMElement $ini */
$name = (string) $ini->getAttribute('name');
$value = (string) $ini->getAttribute('value');
$result['ini'][$name]['value'] = $value;
}
foreach ($this->xpath->query('php/const') as $const) {
/** @var DOMElement $const */
$name = (string) $const->getAttribute('name');
$value = (string) $const->getAttribute('value');
$result['const'][$name]['value'] = $this->getBoolean($value, $value);
}
foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
foreach ($this->xpath->query('php/' . $array) as $var) {
/** @var DOMElement $var */
$name = (string) $var->getAttribute('name');
$value = (string) $var->getAttribute('value');
$verbatim = false;
if ($var->hasAttribute('verbatim')) {
$verbatim = $this->getBoolean($var->getAttribute('verbatim'), false);
$result[$array][$name]['verbatim'] = $verbatim;
}
if ($var->hasAttribute('force')) {
$force = $this->getBoolean($var->getAttribute('force'), false);
$result[$array][$name]['force'] = $force;
}
if (!$verbatim) {
$value = $this->getBoolean($value, $value);
}
$result[$array][$name]['value'] = $value;
}
}
return $result;
}
/**
* Handles the PHP configuration.
*/
public function handlePHPConfiguration(): void
{
$configuration = $this->getPHPConfiguration();
if (!empty($configuration['include_path'])) {
\ini_set(
'include_path',
\implode(PATH_SEPARATOR, $configuration['include_path']) .
PATH_SEPARATOR .
\ini_get('include_path')
);
}
foreach ($configuration['ini'] as $name => $data) {
$value = $data['value'];
if (\defined($value)) {
$value = (string) \constant($value);
}
\ini_set($name, $value);
}
foreach ($configuration['const'] as $name => $data) {
$value = $data['value'];
if (!\defined($name)) {
\define($name, $value);
}
}
foreach (['var', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
/*
* @see https://github.com/sebastianbergmann/phpunit/issues/277
*/
switch ($array) {
case 'var':
$target = &$GLOBALS;
break;
case 'server':
$target = &$_SERVER;
break;
default:
$target = &$GLOBALS['_' . \strtoupper($array)];
break;
}
foreach ($configuration[$array] as $name => $data) {
$target[$name] = $data['value'];
}
}
foreach ($configuration['env'] as $name => $data) {
$value = $data['value'];
$force = $data['force'] ?? false;
if ($force || \getenv($name) === false) {
\putenv("{$name}={$value}");
}
if (!isset($_ENV[$name])) {
$_ENV[$name] = $value;
}
if ($force === true) {
$_ENV[$name] = $value;
}
}
}
/**
* Returns the PHPUnit configuration.
*/
public function getPHPUnitConfiguration(): array
{
$result = [];
$root = $this->document->documentElement;
if ($root->hasAttribute('cacheTokens')) {
$result['cacheTokens'] = $this->getBoolean(
(string) $root->getAttribute('cacheTokens'),
false
);
}
if ($root->hasAttribute('columns')) {
$columns = (string) $root->getAttribute('columns');
if ($columns === 'max') {
$result['columns'] = 'max';
} else {
$result['columns'] = $this->getInteger($columns, 80);
}
}
if ($root->hasAttribute('colors')) {
/* only allow boolean for compatibility with previous versions
'always' only allowed from command line */
if ($this->getBoolean($root->getAttribute('colors'), false)) {
$result['colors'] = ResultPrinter::COLOR_AUTO;
} else {
$result['colors'] = ResultPrinter::COLOR_NEVER;
}
}
/*
* @see https://github.com/sebastianbergmann/phpunit/issues/657
*/
if ($root->hasAttribute('stderr')) {
$result['stderr'] = $this->getBoolean(
(string) $root->getAttribute('stderr'),
false
);
}
if ($root->hasAttribute('backupGlobals')) {
$result['backupGlobals'] = $this->getBoolean(
(string) $root->getAttribute('backupGlobals'),
false
);
}
if ($root->hasAttribute('backupStaticAttributes')) {
$result['backupStaticAttributes'] = $this->getBoolean(
(string) $root->getAttribute('backupStaticAttributes'),
false
);
}
if ($root->getAttribute('bootstrap')) {
$result['bootstrap'] = $this->toAbsolutePath(
(string) $root->getAttribute('bootstrap')
);
}
if ($root->hasAttribute('convertDeprecationsToExceptions')) {
$result['convertDeprecationsToExceptions'] = $this->getBoolean(
(string) $root->getAttribute('convertDeprecationsToExceptions'),
true
);
}
if ($root->hasAttribute('convertErrorsToExceptions')) {
$result['convertErrorsToExceptions'] = $this->getBoolean(
(string) $root->getAttribute('convertErrorsToExceptions'),
true
);
}
if ($root->hasAttribute('convertNoticesToExceptions')) {
$result['convertNoticesToExceptions'] = $this->getBoolean(
(string) $root->getAttribute('convertNoticesToExceptions'),
true
);
}
if ($root->hasAttribute('convertWarningsToExceptions')) {
$result['convertWarningsToExceptions'] = $this->getBoolean(
(string) $root->getAttribute('convertWarningsToExceptions'),
true
);
}
if ($root->hasAttribute('forceCoversAnnotation')) {
$result['forceCoversAnnotation'] = $this->getBoolean(
(string) $root->getAttribute('forceCoversAnnotation'),
false
);
}
if ($root->hasAttribute('disableCodeCoverageIgnore')) {
$result['disableCodeCoverageIgnore'] = $this->getBoolean(
(string) $root->getAttribute('disableCodeCoverageIgnore'),
false
);
}
if ($root->hasAttribute('processIsolation')) {
$result['processIsolation'] = $this->getBoolean(
(string) $root->getAttribute('processIsolation'),
false
);
}
if ($root->hasAttribute('stopOnError')) {
$result['stopOnError'] = $this->getBoolean(
(string) $root->getAttribute('stopOnError'),
false
);
}
if ($root->hasAttribute('stopOnFailure')) {
$result['stopOnFailure'] = $this->getBoolean(
(string) $root->getAttribute('stopOnFailure'),
false
);
}
if ($root->hasAttribute('stopOnWarning')) {
$result['stopOnWarning'] = $this->getBoolean(
(string) $root->getAttribute('stopOnWarning'),
false
);
}
if ($root->hasAttribute('stopOnIncomplete')) {
$result['stopOnIncomplete'] = $this->getBoolean(
(string) $root->getAttribute('stopOnIncomplete'),
false
);
}
if ($root->hasAttribute('stopOnRisky')) {
$result['stopOnRisky'] = $this->getBoolean(
(string) $root->getAttribute('stopOnRisky'),
false
);
}
if ($root->hasAttribute('stopOnSkipped')) {
$result['stopOnSkipped'] = $this->getBoolean(
(string) $root->getAttribute('stopOnSkipped'),
false
);
}
if ($root->hasAttribute('failOnWarning')) {
$result['failOnWarning'] = $this->getBoolean(
(string) $root->getAttribute('failOnWarning'),
false
);
}
if ($root->hasAttribute('failOnRisky')) {
$result['failOnRisky'] = $this->getBoolean(
(string) $root->getAttribute('failOnRisky'),
false
);
}
if ($root->hasAttribute('testSuiteLoaderClass')) {
$result['testSuiteLoaderClass'] = (string) $root->getAttribute(
'testSuiteLoaderClass'
);
}
if ($root->hasAttribute('defaultTestSuite')) {
$result['defaultTestSuite'] = (string) $root->getAttribute(
'defaultTestSuite'
);
}
if ($root->getAttribute('testSuiteLoaderFile')) {
$result['testSuiteLoaderFile'] = $this->toAbsolutePath(
(string) $root->getAttribute('testSuiteLoaderFile')
);
}
if ($root->hasAttribute('printerClass')) {
$result['printerClass'] = (string) $root->getAttribute(
'printerClass'
);
}
if ($root->getAttribute('printerFile')) {
$result['printerFile'] = $this->toAbsolutePath(
(string) $root->getAttribute('printerFile')
);
}
if ($root->hasAttribute('beStrictAboutChangesToGlobalState')) {
$result['beStrictAboutChangesToGlobalState'] = $this->getBoolean(
(string) $root->getAttribute('beStrictAboutChangesToGlobalState'),
false
);
}
if ($root->hasAttribute('beStrictAboutOutputDuringTests')) {
$result['disallowTestOutput'] = $this->getBoolean(
(string) $root->getAttribute('beStrictAboutOutputDuringTests'),
false
);
}
if ($root->hasAttribute('beStrictAboutResourceUsageDuringSmallTests')) {
$result['beStrictAboutResourceUsageDuringSmallTests'] = $this->getBoolean(
(string) $root->getAttribute('beStrictAboutResourceUsageDuringSmallTests'),
false
);
}
if ($root->hasAttribute('beStrictAboutTestsThatDoNotTestAnything')) {
$result['reportUselessTests'] = $this->getBoolean(
(string) $root->getAttribute('beStrictAboutTestsThatDoNotTestAnything'),
true
);
}
if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) {
$result['disallowTodoAnnotatedTests'] = $this->getBoolean(
(string) $root->getAttribute('beStrictAboutTodoAnnotatedTests'),
false
);
}
if ($root->hasAttribute('beStrictAboutCoversAnnotation')) {
$result['strictCoverage'] = $this->getBoolean(
(string) $root->getAttribute('beStrictAboutCoversAnnotation'),
false
);
}
if ($root->hasAttribute('enforceTimeLimit')) {
$result['enforceTimeLimit'] = $this->getBoolean(
(string) $root->getAttribute('enforceTimeLimit'),
false
);
}
if ($root->hasAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage')) {
$result['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $this->getBoolean(
(string) $root->getAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage'),
false
);
}
if ($root->hasAttribute('timeoutForSmallTests')) {
$result['timeoutForSmallTests'] = $this->getInteger(
(string) $root->getAttribute('timeoutForSmallTests'),
1
);
}
if ($root->hasAttribute('timeoutForMediumTests')) {
$result['timeoutForMediumTests'] = $this->getInteger(
(string) $root->getAttribute('timeoutForMediumTests'),
10
);
}
if ($root->hasAttribute('timeoutForLargeTests')) {
$result['timeoutForLargeTests'] = $this->getInteger(
(string) $root->getAttribute('timeoutForLargeTests'),
60
);
}
if ($root->hasAttribute('reverseDefectList')) {
$result['reverseDefectList'] = $this->getBoolean(
(string) $root->getAttribute('reverseDefectList'),
false
);
}
if ($root->hasAttribute('verbose')) {
$result['verbose'] = $this->getBoolean(
(string) $root->getAttribute('verbose'),
false
);
}
if ($root->hasAttribute('registerMockObjectsFromTestArgumentsRecursively')) {
$result['registerMockObjectsFromTestArgumentsRecursively'] = $this->getBoolean(
(string) $root->getAttribute('registerMockObjectsFromTestArgumentsRecursively'),
false
);
}
if ($root->hasAttribute('extensionsDirectory')) {
$result['extensionsDirectory'] = $this->toAbsolutePath(
(string) $root->getAttribute(
'extensionsDirectory'
)
);
}
if ($root->hasAttribute('executionOrder')) {
switch ((string) $root->getAttribute('executionOrder')) {
case 'random':
$result['executionOrder'] = TestSuiteSorter::ORDER_RANDOMIZED;
break;
case 'reverse':
$result['executionOrder'] = TestSuiteSorter::ORDER_REVERSED;
break;
default:
$result['executionOrder'] = TestSuiteSorter::ORDER_DEFAULT;
}
}
if ($root->hasAttribute('resolveDependencies')) {
$result['resolveDependencies'] = $this->getBoolean(
(string) $root->getAttribute('resolveDependencies'),
false
);
}
return $result;
}
/**
* Returns the test suite configuration.
*
* @throws Exception
*/
public function getTestSuiteConfiguration(string $testSuiteFilter = ''): TestSuite
{
$testSuiteNodes = $this->xpath->query('testsuites/testsuite');
if ($testSuiteNodes->length === 0) {
$testSuiteNodes = $this->xpath->query('testsuite');
}
if ($testSuiteNodes->length === 1) {
return $this->getTestSuite($testSuiteNodes->item(0), $testSuiteFilter);
}
$suite = new TestSuite;
foreach ($testSuiteNodes as $testSuiteNode) {
$suite->addTestSuite(
$this->getTestSuite($testSuiteNode, $testSuiteFilter)
);
}
return $suite;
}
/**
* Returns the test suite names from the configuration.
*/
public function getTestSuiteNames(): array
{
$names = [];
foreach ($this->xpath->query('*/testsuite') as $node) {
/* @var DOMElement $node */
$names[] = $node->getAttribute('name');
}
return $names;
}
private function validateConfigurationAgainstSchema(): void
{
$original = \libxml_use_internal_errors(true);
$xsdFilename = __DIR__ . '/../../phpunit.xsd';
if (\defined('__PHPUNIT_PHAR_ROOT__')) {
$xsdFilename = __PHPUNIT_PHAR_ROOT__ . '/phpunit.xsd';
}
$this->document->schemaValidate($xsdFilename);
$this->errors = \libxml_get_errors();
\libxml_use_internal_errors($original);
}
/**
* Collects and returns the configuration arguments from the PHPUnit
* XML configuration
*/
private function getConfigurationArguments(\DOMNodeList $nodes): array
{
$arguments = [];
if ($nodes->length === 0) {
return $arguments;
}
foreach ($nodes as $node) {
if (!$node instanceof DOMElement) {
continue;
}
if ($node->tagName !== 'arguments') {
continue;
}
foreach ($node->childNodes as $argument) {
if (!$argument instanceof DOMElement) {
continue;
}
if ($argument->tagName === 'file' || $argument->tagName === 'directory') {
$arguments[] = $this->toAbsolutePath((string) $argument->textContent);
} else {
$arguments[] = Xml::xmlToVariable($argument);
}
}
}
return $arguments;
}
/**
* @throws \PHPUnit\Framework\Exception
*/
private function getTestSuite(DOMElement $testSuiteNode, string $testSuiteFilter = ''): TestSuite
{
if ($testSuiteNode->hasAttribute('name')) {
$suite = new TestSuite(
(string) $testSuiteNode->getAttribute('name')
);
} else {
$suite = new TestSuite;
}
$exclude = [];
foreach ($testSuiteNode->getElementsByTagName('exclude') as $excludeNode) {
$excludeFile = (string) $excludeNode->textContent;
if ($excludeFile) {
$exclude[] = $this->toAbsolutePath($excludeFile);
}
}
$fileIteratorFacade = new FileIteratorFacade;
$testSuiteFilter = $testSuiteFilter ? \explode(',', $testSuiteFilter) : [];
foreach ($testSuiteNode->getElementsByTagName('directory') as $directoryNode) {
/** @var DOMElement $directoryNode */
if (!empty($testSuiteFilter) && !\in_array($directoryNode->parentNode->getAttribute('name'), $testSuiteFilter)) {
continue;
}
$directory = (string) $directoryNode->textContent;
if (empty($directory)) {
continue;
}
$phpVersion = PHP_VERSION;
$phpVersionOperator = '>=';
$prefix = '';
$suffix = 'Test.php';
if ($directoryNode->hasAttribute('phpVersion')) {
$phpVersion = (string) $directoryNode->getAttribute('phpVersion');
}
if ($directoryNode->hasAttribute('phpVersionOperator')) {
$phpVersionOperator = (string) $directoryNode->getAttribute('phpVersionOperator');
}
if (!\version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) {
continue;
}
if ($directoryNode->hasAttribute('prefix')) {
$prefix = (string) $directoryNode->getAttribute('prefix');
}
if ($directoryNode->hasAttribute('suffix')) {
$suffix = (string) $directoryNode->getAttribute('suffix');
}
$files = $fileIteratorFacade->getFilesAsArray(
$this->toAbsolutePath($directory),
$suffix,
$prefix,
$exclude
);
$suite->addTestFiles($files);
}
foreach ($testSuiteNode->getElementsByTagName('file') as $fileNode) {
/** @var DOMElement $fileNode */
if (!empty($testSuiteFilter) && !\in_array($fileNode->parentNode->getAttribute('name'), $testSuiteFilter)) {
continue;
}
$file = (string) $fileNode->textContent;
if (empty($file)) {
continue;
}
$file = $fileIteratorFacade->getFilesAsArray(
$this->toAbsolutePath($file)
);
if (!isset($file[0])) {
continue;
}
$file = $file[0];
$phpVersion = PHP_VERSION;
$phpVersionOperator = '>=';
if ($fileNode->hasAttribute('phpVersion')) {
$phpVersion = (string) $fileNode->getAttribute('phpVersion');
}
if ($fileNode->hasAttribute('phpVersionOperator')) {
$phpVersionOperator = (string) $fileNode->getAttribute('phpVersionOperator');
}
if (!\version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) {
continue;
}
$suite->addTestFile($file);
}
return $suite;
}
/**
* if $value is 'false' or 'true', this returns the value that $value represents.
* Otherwise, returns $default, which may be a string in rare cases.
* See PHPUnit\Util\ConfigurationTest::testPHPConfigurationIsReadCorrectly
*
* @param string $value
* @param bool|string $default
*
* @return bool|string
*/
private function getBoolean(string $value, $default)
{
if (\strtolower($value) === 'false') {
return false;
}
if (\strtolower($value) === 'true') {
return true;
}
return $default;
}
private function getInteger(string $value, int $default): int
{
if (\is_numeric($value)) {
return (int) $value;
}
return $default;
}
private function readFilterDirectories(string $query): array
{
$directories = [];
foreach ($this->xpath->query($query) as $directoryNode) {
/** @var DOMElement $directoryNode */
$directoryPath = (string) $directoryNode->textContent;
if (!$directoryPath) {
continue;
}
$prefix = '';
$suffix = '.php';
$group = 'DEFAULT';
if ($directoryNode->hasAttribute('prefix')) {
$prefix = (string) $directoryNode->getAttribute('prefix');
}
if ($directoryNode->hasAttribute('suffix')) {
$suffix = (string) $directoryNode->getAttribute('suffix');
}
if ($directoryNode->hasAttribute('group')) {
$group = (string) $directoryNode->getAttribute('group');
}
$directories[] = [
'path' => $this->toAbsolutePath($directoryPath),
'prefix' => $prefix,
'suffix' => $suffix,
'group' => $group
];
}
return $directories;
}
/**
* @return string[]
*/
private function readFilterFiles(string $query): array
{
$files = [];
foreach ($this->xpath->query($query) as $file) {
$filePath = (string) $file->textContent;
if ($filePath) {
$files[] = $this->toAbsolutePath($filePath);
}
}
return $files;
}
private function toAbsolutePath(string $path, bool $useIncludePath = false): string
{
$path = \trim($path);
if ($path[0] === '/') {
return $path;
}
// Matches the following on Windows:
// - \\NetworkComputer\Path
// - \\.\D:
// - \\.\c:
// - C:\Windows
// - C:\windows
// - C:/windows
// - c:/windows
if (\defined('PHP_WINDOWS_VERSION_BUILD') &&
($path[0] === '\\' || (\strlen($path) >= 3 && \preg_match('#^[A-Z]\:[/\\\]#i', \substr($path, 0, 3))))) {
return $path;
}
if (\strpos($path, '://') !== false) {
return $path;
}
$file = \dirname($this->filename) . DIRECTORY_SEPARATOR . $path;
if ($useIncludePath && !\file_exists($file)) {
$includePathFile = \stream_resolve_include_path($path);
if ($includePathFile) {
$file = $includePathFile;
}
}
return $file;
}
private function parseGroupConfiguration(string $root): array
{
$groups = [
'include' => [],
'exclude' => []
];
foreach ($this->xpath->query($root . '/include/group') as $group) {
$groups['include'][] = (string) $group->textContent;
}
foreach ($this->xpath->query($root . '/exclude/group') as $group) {
$groups['exclude'][] = (string) $group->textContent;
}
return $groups;
}
}
src/Util/Type.php 0000666 00000001446 13436756106 0007726 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
final class Type
{
public static function isType(string $type): bool
{
switch ($type) {
case 'numeric':
case 'integer':
case 'int':
case 'iterable':
case 'float':
case 'string':
case 'boolean':
case 'bool':
case 'null':
case 'array':
case 'object':
case 'resource':
case 'scalar':
return true;
default:
return false;
}
}
}
src/Util/Printer.php 0000666 00000006556 13436756106 0010437 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\Exception;
/**
* Utility class that can print to STDOUT or write to a file.
*/
class Printer
{
/**
* If true, flush output after every write.
*
* @var bool
*/
protected $autoFlush = false;
/**
* @var resource
*/
protected $out;
/**
* @var string
*/
protected $outTarget;
/**
* Constructor.
*
* @param null|mixed $out
*
* @throws Exception
*/
public function __construct($out = null)
{
if ($out !== null) {
if (\is_string($out)) {
if (\strpos($out, 'socket://') === 0) {
$out = \explode(':', \str_replace('socket://', '', $out));
if (\count($out) !== 2) {
throw new Exception;
}
$this->out = \fsockopen($out[0], $out[1]);
} else {
if (\strpos($out, 'php://') === false && !$this->createDirectory(\dirname($out))) {
throw new Exception(\sprintf('Directory "%s" was not created', \dirname($out)));
}
$this->out = \fopen($out, 'wt');
}
$this->outTarget = $out;
} else {
$this->out = $out;
}
}
}
/**
* Flush buffer and close output if it's not to a PHP stream
*/
public function flush(): void
{
if ($this->out && \strncmp($this->outTarget, 'php://', 6) !== 0) {
\fclose($this->out);
}
}
/**
* Performs a safe, incremental flush.
*
* Do not confuse this function with the flush() function of this class,
* since the flush() function may close the file being written to, rendering
* the current object no longer usable.
*/
public function incrementalFlush(): void
{
if ($this->out) {
\fflush($this->out);
} else {
\flush();
}
}
public function write(string $buffer): void
{
if ($this->out) {
\fwrite($this->out, $buffer);
if ($this->autoFlush) {
$this->incrementalFlush();
}
} else {
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
$buffer = \htmlspecialchars($buffer, ENT_SUBSTITUTE);
}
print $buffer;
if ($this->autoFlush) {
$this->incrementalFlush();
}
}
}
/**
* Check auto-flush mode.
*/
public function getAutoFlush(): bool
{
return $this->autoFlush;
}
/**
* Set auto-flushing mode.
*
* If set, *incremental* flushes will be done after each write. This should
* not be confused with the different effects of this class' flush() method.
*/
public function setAutoFlush(bool $autoFlush): void
{
$this->autoFlush = $autoFlush;
}
private function createDirectory(string $directory): bool
{
return !(!\is_dir($directory) && !@\mkdir($directory, 0777, true) && !\is_dir($directory));
}
}
src/Util/ErrorHandler.php 0000666 00000005510 13436756106 0011370 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util;
use PHPUnit\Framework\Error\Deprecated;
use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\Error\Notice;
use PHPUnit\Framework\Error\Warning;
/**
* Error handler that converts PHP errors and warnings to exceptions.
*/
final class ErrorHandler
{
private static $errorStack = [];
/**
* Returns the error stack.
*/
public static function getErrorStack(): array
{
return self::$errorStack;
}
public static function handleError(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool
{
if (!($errorNumber & \error_reporting())) {
return false;
}
self::$errorStack[] = [$errorNumber, $errorString, $errorFile, $errorLine];
$trace = \debug_backtrace();
\array_shift($trace);
foreach ($trace as $frame) {
if ($frame['function'] === '__toString') {
return false;
}
}
if ($errorNumber === E_NOTICE || $errorNumber === E_USER_NOTICE || $errorNumber === E_STRICT) {
if (Notice::$enabled !== true) {
return false;
}
$exception = Notice::class;
} elseif ($errorNumber === E_WARNING || $errorNumber === E_USER_WARNING) {
if (Warning::$enabled !== true) {
return false;
}
$exception = Warning::class;
} elseif ($errorNumber === E_DEPRECATED || $errorNumber === E_USER_DEPRECATED) {
if (Deprecated::$enabled !== true) {
return false;
}
$exception = Deprecated::class;
} else {
$exception = Error::class;
}
throw new $exception($errorString, $errorNumber, $errorFile, $errorLine);
}
/**
* Registers an error handler and returns a function that will restore
* the previous handler when invoked
*
* @param int $severity PHP predefined error constant
*
* @throws \Exception if event of specified severity is emitted
*/
public static function handleErrorOnce($severity = E_WARNING): callable
{
$terminator = function () {
static $expired = false;
if (!$expired) {
$expired = true;
return \restore_error_handler();
}
};
\set_error_handler(
function ($errorNumber, $errorString) use ($severity) {
if ($errorNumber === $severity) {
return;
}
return false;
}
);
return $terminator;
}
}
src/Runner/Exception.php 0000666 00000000513 13436756106 0011271 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
class Exception extends \RuntimeException implements \PHPUnit\Exception
{
}
src/Runner/Filter/IncludeGroupFilterIterator.php 0000666 00000000701 13436756106 0016037 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner\Filter;
class IncludeGroupFilterIterator extends GroupFilterIterator
{
protected function doAccept(string $hash): bool
{
return \in_array($hash, $this->groupTests, true);
}
}
src/Runner/Filter/ExcludeGroupFilterIterator.php 0000666 00000000702 13436756106 0016046 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner\Filter;
class ExcludeGroupFilterIterator extends GroupFilterIterator
{
protected function doAccept(string $hash): bool
{
return !\in_array($hash, $this->groupTests, true);
}
}
src/Runner/Filter/Factory.php 0000666 00000002442 13436756106 0012172 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner\Filter;
use FilterIterator;
use InvalidArgumentException;
use Iterator;
use PHPUnit\Framework\TestSuite;
use ReflectionClass;
class Factory
{
/**
* @var array
*/
private $filters = [];
/**
* @param ReflectionClass $filter
* @param mixed $args
*
* @throws InvalidArgumentException
*/
public function addFilter(ReflectionClass $filter, $args): void
{
if (!$filter->isSubclassOf(\RecursiveFilterIterator::class)) {
throw new InvalidArgumentException(
\sprintf(
'Class "%s" does not extend RecursiveFilterIterator',
$filter->name
)
);
}
$this->filters[] = [$filter, $args];
}
public function factory(Iterator $iterator, TestSuite $suite): FilterIterator
{
foreach ($this->filters as $filter) {
[$class, $args] = $filter;
$iterator = $class->newInstance($iterator, $args, $suite);
}
return $iterator;
}
}
src/Runner/Filter/GroupFilterIterator.php 0000666 00000002403 13436756106 0014534 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner\Filter;
use PHPUnit\Framework\TestSuite;
use RecursiveFilterIterator;
use RecursiveIterator;
abstract class GroupFilterIterator extends RecursiveFilterIterator
{
/**
* @var string[]
*/
protected $groupTests = [];
public function __construct(RecursiveIterator $iterator, array $groups, TestSuite $suite)
{
parent::__construct($iterator);
foreach ($suite->getGroupDetails() as $group => $tests) {
if (\in_array($group, $groups, true)) {
$testHashes = \array_map(
'spl_object_hash',
$tests
);
$this->groupTests = \array_merge($this->groupTests, $testHashes);
}
}
}
public function accept(): bool
{
$test = $this->getInnerIterator()->current();
if ($test instanceof TestSuite) {
return true;
}
return $this->doAccept(\spl_object_hash($test));
}
abstract protected function doAccept(string $hash);
}
src/Runner/Filter/NameFilterIterator.php 0000666 00000006300 13436756106 0014320 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner\Filter;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\WarningTestCase;
use PHPUnit\Util\RegularExpression;
use PHPUnit\Util\Test;
use RecursiveFilterIterator;
use RecursiveIterator;
class NameFilterIterator extends RecursiveFilterIterator
{
/**
* @var string
*/
protected $filter;
/**
* @var int
*/
protected $filterMin;
/**
* @var int
*/
protected $filterMax;
/**
* @throws \Exception
*/
public function __construct(RecursiveIterator $iterator, string $filter)
{
parent::__construct($iterator);
$this->setFilter($filter);
}
public function accept(): bool
{
$test = $this->getInnerIterator()->current();
if ($test instanceof TestSuite) {
return true;
}
$tmp = Test::describe($test);
if ($test instanceof WarningTestCase) {
$name = $test->getMessage();
} else {
if ($tmp[0] != '') {
$name = \implode('::', $tmp);
} else {
$name = $tmp[1];
}
}
$accepted = @\preg_match($this->filter, $name, $matches);
if ($accepted && isset($this->filterMax)) {
$set = \end($matches);
$accepted = $set >= $this->filterMin && $set <= $this->filterMax;
}
return $accepted;
}
/**
* @throws \Exception
*/
protected function setFilter(string $filter): void
{
if (RegularExpression::safeMatch($filter, '') === false) {
// Handles:
// * testAssertEqualsSucceeds#4
// * testAssertEqualsSucceeds#4-8
if (\preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
if (isset($matches[3]) && $matches[2] < $matches[3]) {
$filter = \sprintf(
'%s.*with data set #(\d+)$',
$matches[1]
);
$this->filterMin = $matches[2];
$this->filterMax = $matches[3];
} else {
$filter = \sprintf(
'%s.*with data set #%s$',
$matches[1],
$matches[2]
);
}
} // Handles:
// * testDetermineJsonError@JSON_ERROR_NONE
// * testDetermineJsonError@JSON.*
elseif (\preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
$filter = \sprintf(
'%s.*with data set "%s"$',
$matches[1],
$matches[2]
);
}
// Escape delimiters in regular expression. Do NOT use preg_quote,
// to keep magic characters.
$filter = \sprintf('/%s/', \str_replace(
'/',
'\\/',
$filter
));
}
$this->filter = $filter;
}
}
src/Runner/BaseTestRunner.php 0000666 00000007210 13436756106 0012240 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestSuite;
use ReflectionClass;
use ReflectionException;
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
/**
* Base class for all test runners.
*/
abstract class BaseTestRunner
{
public const STATUS_PASSED = 0;
public const STATUS_SKIPPED = 1;
public const STATUS_INCOMPLETE = 2;
public const STATUS_FAILURE = 3;
public const STATUS_ERROR = 4;
public const STATUS_RISKY = 5;
public const STATUS_WARNING = 6;
public const SUITE_METHODNAME = 'suite';
/**
* Returns the loader to be used.
*/
public function getLoader(): TestSuiteLoader
{
return new StandardTestSuiteLoader;
}
/**
* Returns the Test corresponding to the given suite.
* This is a template method, subclasses override
* the runFailed() and clearStatus() methods.
*
* @param string $suiteClassName
* @param string $suiteClassFile
* @param array|string $suffixes
*
* @throws Exception
*/
public function getTest(string $suiteClassName, string $suiteClassFile = '', $suffixes = ''): ?Test
{
if (\is_dir($suiteClassName) &&
!\is_file($suiteClassName . '.php') && empty($suiteClassFile)) {
$facade = new FileIteratorFacade;
$files = $facade->getFilesAsArray(
$suiteClassName,
$suffixes
);
$suite = new TestSuite($suiteClassName);
$suite->addTestFiles($files);
return $suite;
}
try {
$testClass = $this->loadSuiteClass(
$suiteClassName,
$suiteClassFile
);
} catch (Exception $e) {
$this->runFailed($e->getMessage());
return null;
}
try {
$suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME);
if (!$suiteMethod->isStatic()) {
$this->runFailed(
'suite() method must be static.'
);
return null;
}
try {
$test = $suiteMethod->invoke(null, $testClass->getName());
} catch (ReflectionException $e) {
$this->runFailed(
\sprintf(
"Failed to invoke suite() method.\n%s",
$e->getMessage()
)
);
return null;
}
} catch (ReflectionException $e) {
try {
$test = new TestSuite($testClass);
} catch (Exception $e) {
$test = new TestSuite;
$test->setName($suiteClassName);
}
}
$this->clearStatus();
return $test;
}
/**
* Returns the loaded ReflectionClass for a suite name.
*/
protected function loadSuiteClass(string $suiteClassName, string $suiteClassFile = ''): ReflectionClass
{
$loader = $this->getLoader();
return $loader->load($suiteClassName, $suiteClassFile);
}
/**
* Clears the status message.
*/
protected function clearStatus(): void
{
}
/**
* Override to define how to handle a failed loading of
* a test suite.
*/
abstract protected function runFailed(string $message);
}
src/Runner/TestSuiteLoader.php 0000666 00000001040 13436756106 0012407 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
use ReflectionClass;
/**
* An interface to define how a test suite should be loaded.
*/
interface TestSuiteLoader
{
public function load(string $suiteClassName, string $suiteClassFile = ''): ReflectionClass;
public function reload(ReflectionClass $aClass): ReflectionClass;
}
src/Runner/Version.php 0000666 00000002751 13436756106 0010766 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
use SebastianBergmann\Version as VersionId;
/**
* This class defines the current version of PHPUnit.
*/
class Version
{
private static $pharVersion;
private static $version;
/**
* Returns the current version of PHPUnit.
*/
public static function id(): string
{
if (self::$pharVersion !== null) {
return self::$pharVersion;
}
if (self::$version === null) {
$version = new VersionId('7.2.0', \dirname(__DIR__, 2));
self::$version = $version->getVersion();
}
return self::$version;
}
public static function series(): string
{
if (\strpos(self::id(), '-')) {
$version = \explode('-', self::id())[0];
} else {
$version = self::id();
}
return \implode('.', \array_slice(\explode('.', $version), 0, 2));
}
public static function getVersionString(): string
{
return 'PHPUnit ' . self::id() . ' by Sebastian Bergmann and contributors.';
}
public static function getReleaseChannel(): string
{
if (\strpos(self::$pharVersion, '-') !== false) {
return '-nightly';
}
return '';
}
}
src/Runner/StandardTestSuiteLoader.php 0000666 00000006422 13436756106 0014101 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
use PHPUnit\Framework\TestCase;
use PHPUnit\Util\FileLoader;
use PHPUnit\Util\Filesystem;
use ReflectionClass;
/**
* The standard test suite loader.
*/
class StandardTestSuiteLoader implements TestSuiteLoader
{
/**
* @throws Exception
* @throws \PHPUnit\Framework\Exception
*/
public function load(string $suiteClassName, string $suiteClassFile = ''): ReflectionClass
{
$suiteClassName = \str_replace('.php', '', $suiteClassName);
if (empty($suiteClassFile)) {
$suiteClassFile = Filesystem::classNameToFilename(
$suiteClassName
);
}
if (!\class_exists($suiteClassName, false)) {
$loadedClasses = \get_declared_classes();
$filename = FileLoader::checkAndLoad($suiteClassFile);
$loadedClasses = \array_values(
\array_diff(\get_declared_classes(), $loadedClasses)
);
}
if (!\class_exists($suiteClassName, false) && !empty($loadedClasses)) {
$offset = 0 - \strlen($suiteClassName);
foreach ($loadedClasses as $loadedClass) {
$class = new ReflectionClass($loadedClass);
if (\substr($loadedClass, $offset) === $suiteClassName &&
$class->getFileName() == $filename) {
$suiteClassName = $loadedClass;
break;
}
}
}
if (!\class_exists($suiteClassName, false) && !empty($loadedClasses)) {
$testCaseClass = TestCase::class;
foreach ($loadedClasses as $loadedClass) {
$class = new ReflectionClass($loadedClass);
$classFile = $class->getFileName();
if ($class->isSubclassOf($testCaseClass) && !$class->isAbstract()) {
$suiteClassName = $loadedClass;
$testCaseClass = $loadedClass;
if ($classFile == \realpath($suiteClassFile)) {
break;
}
}
if ($class->hasMethod('suite')) {
$method = $class->getMethod('suite');
if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) {
$suiteClassName = $loadedClass;
if ($classFile == \realpath($suiteClassFile)) {
break;
}
}
}
}
}
if (\class_exists($suiteClassName, false)) {
$class = new ReflectionClass($suiteClassName);
if ($class->getFileName() == \realpath($suiteClassFile)) {
return $class;
}
}
throw new Exception(
\sprintf(
"Class '%s' could not be found in '%s'.",
$suiteClassName,
$suiteClassFile
)
);
}
public function reload(ReflectionClass $aClass): ReflectionClass
{
return $aClass;
}
}
src/Runner/PhptTestCase.php 0000666 00000036051 13436756106 0011710 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\IncompleteTestError;
use PHPUnit\Framework\SelfDescribing;
use PHPUnit\Framework\SkippedTestError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestResult;
use PHPUnit\Util\PHP\AbstractPhpProcess;
use SebastianBergmann\Timer\Timer;
use Text_Template;
use Throwable;
/**
* Runner for PHPT test cases.
*/
class PhptTestCase implements Test, SelfDescribing
{
/**
* @var string[]
*/
private const SETTINGS = [
'allow_url_fopen=1',
'auto_append_file=',
'auto_prepend_file=',
'disable_functions=',
'display_errors=1',
'docref_root=',
'docref_ext=.html',
'error_append_string=',
'error_prepend_string=',
'error_reporting=-1',
'html_errors=0',
'log_errors=0',
'magic_quotes_runtime=0',
'output_handler=',
'open_basedir=',
'output_buffering=Off',
'report_memleaks=0',
'report_zend_debug=0',
'safe_mode=0',
'xdebug.default_enable=0'
];
/**
* @var string
*/
private $filename;
/**
* @var AbstractPhpProcess
*/
private $phpUtil;
/**
* Constructs a test case with the given filename.
*
* @throws Exception
*/
public function __construct(string $filename, AbstractPhpProcess $phpUtil = null)
{
if (!\is_file($filename)) {
throw new Exception(
\sprintf(
'File "%s" does not exist.',
$filename
)
);
}
$this->filename = $filename;
$this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory();
}
/**
* Counts the number of test cases executed by run(TestResult result).
*/
public function count(): int
{
return 1;
}
/**
* Runs a test and collects its result in a TestResult instance.
*
* @throws Exception
* @throws \ReflectionException
* @throws \SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException
* @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
* @throws \SebastianBergmann\CodeCoverage\MissingCoversAnnotationException
* @throws \SebastianBergmann\CodeCoverage\RuntimeException
* @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function run(TestResult $result = null): TestResult
{
$sections = $this->parse();
$code = $this->render($sections['FILE']);
if ($result === null) {
$result = new TestResult;
}
$xfail = false;
$settings = $this->parseIniSection(self::SETTINGS);
$result->startTest($this);
if (isset($sections['INI'])) {
$settings = $this->parseIniSection($sections['INI'], $settings);
}
if (isset($sections['ENV'])) {
$env = $this->parseEnvSection($sections['ENV']);
$this->phpUtil->setEnv($env);
}
$this->phpUtil->setUseStderrRedirection(true);
if ($result->enforcesTimeLimit()) {
$this->phpUtil->setTimeout($result->getTimeoutForLargeTests());
}
$skip = $this->runSkip($sections, $result, $settings);
if ($skip) {
return $result;
}
if (isset($sections['XFAIL'])) {
$xfail = \trim($sections['XFAIL']);
}
if (isset($sections['STDIN'])) {
$this->phpUtil->setStdin($sections['STDIN']);
}
if (isset($sections['ARGS'])) {
$this->phpUtil->setArgs($sections['ARGS']);
}
if ($result->getCollectCodeCoverageInformation()) {
$this->renderForCoverage($settings);
}
Timer::start();
$jobResult = $this->phpUtil->runJob($code, $this->stringifyIni($settings));
$time = Timer::stop();
if ($result->getCollectCodeCoverageInformation() && ($coverage = $this->cleanupForCoverage())) {
$result->getCodeCoverage()->append($coverage, $this, true, [], [], true);
}
try {
$this->assertPhptExpectation($sections, $jobResult['stdout']);
} catch (AssertionFailedError $e) {
$failure = $e;
if ($xfail !== false) {
$failure = new IncompleteTestError($xfail, 0, $e);
}
$result->addFailure($this, $failure, $time);
} catch (Throwable $t) {
$result->addError($this, $t, $time);
}
if ($result->allCompletelyImplemented() && $xfail !== false) {
$result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time);
}
$this->runClean($sections);
$result->endTest($this, $time);
return $result;
}
/**
* Returns the name of the test case.
*/
public function getName(): string
{
return $this->toString();
}
/**
* Returns a string representation of the test case.
*/
public function toString(): string
{
return $this->filename;
}
/**
* Parse --INI-- section key value pairs and return as array.
*
* @param array|string
* @param mixed $content
* @param mixed $ini
*/
private function parseIniSection($content, $ini = []): array
{
if (\is_string($content)) {
$content = \explode(PHP_EOL, \trim($content));
}
foreach ($content as $setting) {
if (\strpos($setting, '=') === false) {
continue;
}
$setting = \explode('=', $setting, 2);
$name = \trim($setting[0]);
$value = \trim($setting[1]);
if ($name === 'extension' || $name === 'zend_extension') {
if (!isset($ini[$name])) {
$ini[$name] = [];
}
$ini[$name][] = $value;
continue;
}
$ini[$name] = $value;
}
return $ini;
}
private function parseEnvSection(string $content): array
{
$env = [];
foreach (\explode(PHP_EOL, \trim($content)) as $e) {
$e = \explode('=', \trim($e), 2);
if (!empty($e[0]) && isset($e[1])) {
$env[$e[0]] = $e[1];
}
}
return $env;
}
/**
* @throws Exception
*/
private function assertPhptExpectation(array $sections, string $output): void
{
$assertions = [
'EXPECT' => 'assertEquals',
'EXPECTF' => 'assertStringMatchesFormat',
'EXPECTREGEX' => 'assertRegExp',
];
$actual = \preg_replace('/\r\n/', PHP_EOL, \trim($output));
foreach ($assertions as $sectionName => $sectionAssertion) {
if (isset($sections[$sectionName])) {
$sectionContent = \preg_replace('/\r\n/', PHP_EOL, \trim($sections[$sectionName]));
$assertion = $sectionAssertion;
$expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent;
break;
}
}
if (!isset($assertion)) {
throw new Exception('No PHPT assertion found');
}
if (!isset($expected)) {
throw new Exception('No PHPT expectation found');
}
Assert::$assertion($expected, $actual);
}
/**
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
private function runSkip(array &$sections, TestResult $result, array $settings): bool
{
if (!isset($sections['SKIPIF'])) {
return false;
}
$skipif = $this->render($sections['SKIPIF']);
$jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings));
if (!\strncasecmp('skip', \ltrim($jobResult['stdout']), 4)) {
$message = '';
if (\preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $skipMatch)) {
$message = \substr($skipMatch[1], 2);
}
$result->addFailure($this, new SkippedTestError($message), 0);
$result->endTest($this, 0);
return true;
}
return false;
}
private function runClean(array &$sections): void
{
$this->phpUtil->setStdin('');
$this->phpUtil->setArgs('');
if (isset($sections['CLEAN'])) {
$cleanCode = $this->render($sections['CLEAN']);
$this->phpUtil->runJob($cleanCode, self::SETTINGS);
}
}
/**
* @throws Exception
*/
private function parse(): array
{
$sections = [];
$section = '';
$unsupportedSections = [
'REDIRECTTEST',
'REQUEST',
'POST',
'PUT',
'POST_RAW',
'GZIP_POST',
'DEFLATE_POST',
'GET',
'COOKIE',
'HEADERS',
'CGI',
'EXPECTHEADERS',
'EXTENSIONS',
'PHPDBG'
];
foreach (\file($this->filename) as $line) {
if (\preg_match('/^--([_A-Z]+)--/', $line, $result)) {
$section = $result[1];
$sections[$section] = '';
continue;
}
if (empty($section)) {
throw new Exception('Invalid PHPT file');
}
$sections[$section] .= $line;
}
if (isset($sections['FILEEOF'])) {
$sections['FILE'] = \rtrim($sections['FILEEOF'], "\r\n");
unset($sections['FILEEOF']);
}
$this->parseExternal($sections);
if (!$this->validate($sections)) {
throw new Exception('Invalid PHPT file');
}
foreach ($unsupportedSections as $section) {
if (isset($sections[$section])) {
throw new Exception(
'PHPUnit does not support this PHPT file'
);
}
}
return $sections;
}
/**
* @throws Exception
*/
private function parseExternal(array &$sections): void
{
$allowSections = [
'FILE',
'EXPECT',
'EXPECTF',
'EXPECTREGEX'
];
$testDirectory = \dirname($this->filename) . DIRECTORY_SEPARATOR;
foreach ($allowSections as $section) {
if (isset($sections[$section . '_EXTERNAL'])) {
$externalFilename = \trim($sections[$section . '_EXTERNAL']);
if (!\is_file($testDirectory . $externalFilename) ||
!\is_readable($testDirectory . $externalFilename)) {
throw new Exception(
\sprintf(
'Could not load --%s-- %s for PHPT file',
$section . '_EXTERNAL',
$testDirectory . $externalFilename
)
);
}
$sections[$section] = \file_get_contents($testDirectory . $externalFilename);
unset($sections[$section . '_EXTERNAL']);
}
}
}
private function validate(array &$sections): bool
{
$requiredSections = [
'FILE',
[
'EXPECT',
'EXPECTF',
'EXPECTREGEX'
]
];
foreach ($requiredSections as $section) {
if (\is_array($section)) {
$foundSection = false;
foreach ($section as $anySection) {
if (isset($sections[$anySection])) {
$foundSection = true;
break;
}
}
if (!$foundSection) {
return false;
}
continue;
}
if (!isset($sections[$section])) {
return false;
}
}
return true;
}
private function render(string $code): string
{
return \str_replace(
[
'__DIR__',
'__FILE__'
],
[
"'" . \dirname($this->filename) . "'",
"'" . $this->filename . "'"
],
$code
);
}
private function getCoverageFiles(): array
{
$baseDir = \dirname($this->filename) . DIRECTORY_SEPARATOR;
$basename = \basename($this->filename, 'phpt');
return [
'coverage' => $baseDir . $basename . 'coverage',
'job' => $baseDir . $basename . 'php'
];
}
private function renderForCoverage(array &$settings): void
{
$files = $this->getCoverageFiles();
$template = new Text_Template(
__DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl'
);
$composerAutoload = '\'\'';
if (\defined('PHPUNIT_COMPOSER_INSTALL') && !\defined('PHPUNIT_TESTSUITE')) {
$composerAutoload = \var_export(PHPUNIT_COMPOSER_INSTALL, true);
}
$phar = '\'\'';
if (\defined('__PHPUNIT_PHAR__')) {
$phar = \var_export(__PHPUNIT_PHAR__, true);
}
$globals = '';
if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
$globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . \var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n";
}
$template->setVar(
[
'composerAutoload' => $composerAutoload,
'phar' => $phar,
'globals' => $globals,
'job' => $files['job'],
'coverageFile' => $files['coverage'],
'autoPrependFile' => \var_export(
!empty($settings['auto_prepend_file']) ? $settings['auto_prepend_file'] : false,
true
)
]
);
\file_put_contents($files['job'], $template->render());
$settings['auto_prepend_file'] = $files['job'];
}
private function cleanupForCoverage(): array
{
$files = $this->getCoverageFiles();
$coverage = @\unserialize(\file_get_contents($files['coverage']));
foreach ($files as $file) {
@\unlink($file);
}
return $coverage;
}
private function stringifyIni(array $ini): array
{
$settings = [];
foreach ($ini as $key => $value) {
if (\is_array($value)) {
foreach ($value as $val) {
$settings[] = $key . '=' . $val;
}
continue;
}
$settings[] = $key . '=' . $value;
}
return $settings;
}
}
src/Runner/TestSuiteSorter.php 0000666 00000007415 13436756106 0012473 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
final class TestSuiteSorter
{
/**
* @var int
*/
public const ORDER_DEFAULT = 0;
/**
* @var int
*/
public const ORDER_RANDOMIZED = 1;
/**
* @var int
*/
public const ORDER_REVERSED = 2;
/**
* @throws Exception
*/
public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies): void
{
if ($order !== self::ORDER_DEFAULT && $order !== self::ORDER_REVERSED && $order !== self::ORDER_RANDOMIZED) {
throw new Exception(
'$order must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_REVERSED, or TestSuiteSorter::ORDER_RANDOMIZED'
);
}
if ($suite instanceof TestSuite && !empty($suite->tests())) {
foreach ($suite as $_suite) {
$this->reorderTestsInSuite($_suite, $order, $resolveDependencies);
}
$this->sort($suite, $order, $resolveDependencies);
}
}
private function sort(TestSuite $suite, int $order, bool $resolveDependencies): void
{
if (empty($suite->tests())) {
return;
}
if ($order === self::ORDER_REVERSED) {
$suite->setTests($this->reverse($suite->tests()));
} elseif ($order === self::ORDER_RANDOMIZED) {
$suite->setTests($this->randomize($suite->tests()));
}
if ($resolveDependencies && $suite->tests()[0] instanceof TestCase) {
$suite->setTests($this->resolveDependencies($suite->tests()));
}
}
private function reverse(array $tests): array
{
return \array_reverse($tests);
}
private function randomize(array $tests): array
{
\shuffle($tests);
return $tests;
}
/**
* Reorder Tests within a TestCase in such a way as to resolve as many dependencies as possible.
* The algorithm will leave the tests in original running order when it can.
* For more details see the documentation for test dependencies.
*
* Short description of algorithm:
* 1. Pick the next Test from remaining tests to be checked for dependencies.
* 2. If the test has no dependencies: mark done, start again from the top
* 3. If the test has dependencies but none left to do: mark done, start again from the top
* 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution.
*
* @param Test[] $tests
*
* @return Test[]
*/
private function resolveDependencies(array $tests): array
{
if (empty($tests)) {
return $tests;
}
$newTestOrder = [];
$i = 0;
do {
$todoNames = \array_merge(
\array_map(function (Test $t) {
return $t->getName();
}, $tests),
\array_map(function (Test $t) {
return \get_class($t) . '::' . $t->getName();
}, $tests)
);
if (!$tests[$i]->hasDependencies() || empty(\array_intersect($tests[$i]->getDependencies(), $todoNames))) {
$newTestOrder = \array_merge($newTestOrder, \array_splice($tests, $i, 1));
$i = 0;
} else {
$i++;
}
} while (!empty($tests) && ($i < \count($tests)));
return \array_merge($newTestOrder, $tests);
}
}
src/Runner/Hook/TestHook.php 0000666 00000000475 13436756106 0012002 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface TestHook extends Hook
{
}
src/Runner/Hook/AfterRiskyTestHook.php 0000666 00000000650 13436756106 0014001 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface AfterRiskyTestHook extends TestHook
{
public function executeAfterRiskyTest(string $test, string $message, float $time): void;
}
src/Runner/Hook/Hook.php 0000666 00000000454 13436756106 0011137 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface Hook
{
}
src/Runner/Hook/BeforeTestHook.php 0000666 00000000602 13436756106 0013115 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface BeforeTestHook extends TestHook
{
public function executeBeforeTest(string $test): void;
}
src/Runner/Hook/AfterSkippedTestHook.php 0000666 00000000654 13436756106 0014303 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface AfterSkippedTestHook extends TestHook
{
public function executeAfterSkippedTest(string $test, string $message, float $time): void;
}
src/Runner/Hook/AfterTestWarningHook.php 0000666 00000000654 13436756106 0014311 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface AfterTestWarningHook extends TestHook
{
public function executeAfterTestWarning(string $test, string $message, float $time): void;
}
src/Runner/Hook/AfterTestFailureHook.php 0000666 00000000654 13436756106 0014273 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface AfterTestFailureHook extends TestHook
{
public function executeAfterTestFailure(string $test, string $message, float $time): void;
}
src/Runner/Hook/AfterLastTestHook.php 0000666 00000000570 13436756106 0013604 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface AfterLastTestHook extends Hook
{
public function executeAfterLastTest(): void;
}
src/Runner/Hook/TestListenerAdapter.php 0000666 00000007376 13436756106 0014177 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Test as TestUtil;
final class TestListenerAdapter implements TestListener
{
/**
* @var TestHook[]
*/
private $hooks = [];
/**
* @var bool
*/
private $lastTestWasNotSuccessful;
public function add(TestHook $hook): void
{
$this->hooks[] = $hook;
}
public function startTest(Test $test): void
{
foreach ($this->hooks as $hook) {
if ($hook instanceof BeforeTestHook) {
$hook->executeBeforeTest(TestUtil::describeAsString($test));
}
}
$this->lastTestWasNotSuccessful = false;
}
public function addError(Test $test, \Throwable $t, float $time): void
{
foreach ($this->hooks as $hook) {
if ($hook instanceof AfterTestErrorHook) {
$hook->executeAfterTestError(TestUtil::describeAsString($test), $t->getMessage(), $time);
}
}
$this->lastTestWasNotSuccessful = true;
}
public function addWarning(Test $test, Warning $e, float $time): void
{
foreach ($this->hooks as $hook) {
if ($hook instanceof AfterTestWarningHook) {
$hook->executeAfterTestWarning(TestUtil::describeAsString($test), $e->getMessage(), $time);
}
}
$this->lastTestWasNotSuccessful = true;
}
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
foreach ($this->hooks as $hook) {
if ($hook instanceof AfterTestFailureHook) {
$hook->executeAfterTestFailure(TestUtil::describeAsString($test), $e->getMessage(), $time);
}
}
$this->lastTestWasNotSuccessful = true;
}
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
foreach ($this->hooks as $hook) {
if ($hook instanceof AfterIncompleteTestHook) {
$hook->executeAfterIncompleteTest(TestUtil::describeAsString($test), $t->getMessage(), $time);
}
}
$this->lastTestWasNotSuccessful = true;
}
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
foreach ($this->hooks as $hook) {
if ($hook instanceof AfterRiskyTestHook) {
$hook->executeAfterRiskyTest(TestUtil::describeAsString($test), $t->getMessage(), $time);
}
}
$this->lastTestWasNotSuccessful = true;
}
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
foreach ($this->hooks as $hook) {
if ($hook instanceof AfterSkippedTestHook) {
$hook->executeAfterSkippedTest(TestUtil::describeAsString($test), $t->getMessage(), $time);
}
}
$this->lastTestWasNotSuccessful = true;
}
public function endTest(Test $test, float $time): void
{
if ($this->lastTestWasNotSuccessful === true) {
return;
}
foreach ($this->hooks as $hook) {
if ($hook instanceof AfterSuccessfulTestHook) {
$hook->executeAfterSuccessfulTest(TestUtil::describeAsString($test), $time);
}
}
}
public function startTestSuite(TestSuite $suite): void
{
}
public function endTestSuite(TestSuite $suite): void
{
}
}
src/Runner/Hook/AfterSuccessfulTestHook.php 0000666 00000000641 13436756106 0015017 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface AfterSuccessfulTestHook extends TestHook
{
public function executeAfterSuccessfulTest(string $test, float $time): void;
}
src/Runner/Hook/AfterIncompleteTestHook.php 0000666 00000000662 13436756106 0015002 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface AfterIncompleteTestHook extends TestHook
{
public function executeAfterIncompleteTest(string $test, string $message, float $time): void;
}
src/Runner/Hook/BeforeFirstTestHook.php 0000666 00000000574 13436756106 0014135 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface BeforeFirstTestHook extends Hook
{
public function executeBeforeFirstTest(): void;
}
src/Runner/Hook/AfterTestErrorHook.php 0000666 00000000650 13436756106 0013771 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner;
interface AfterTestErrorHook extends TestHook
{
public function executeAfterTestError(string $test, string $message, float $time): void;
}
src/TextUI/TestRunner.php 0000666 00000136564 13436756106 0011375 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI;
use PHPUnit\Framework\Error\Deprecated;
use PHPUnit\Framework\Error\Notice;
use PHPUnit\Framework\Error\Warning;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\AfterLastTestHook;
use PHPUnit\Runner\BaseTestRunner;
use PHPUnit\Runner\BeforeFirstTestHook;
use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator;
use PHPUnit\Runner\Filter\Factory;
use PHPUnit\Runner\Filter\IncludeGroupFilterIterator;
use PHPUnit\Runner\Filter\NameFilterIterator;
use PHPUnit\Runner\Hook;
use PHPUnit\Runner\StandardTestSuiteLoader;
use PHPUnit\Runner\TestHook;
use PHPUnit\Runner\TestListenerAdapter;
use PHPUnit\Runner\TestSuiteLoader;
use PHPUnit\Runner\TestSuiteSorter;
use PHPUnit\Runner\Version;
use PHPUnit\Util\Configuration;
use PHPUnit\Util\Log\JUnit;
use PHPUnit\Util\Log\TeamCity;
use PHPUnit\Util\Printer;
use PHPUnit\Util\TestDox\HtmlResultPrinter;
use PHPUnit\Util\TestDox\TextResultPrinter;
use PHPUnit\Util\TestDox\XmlResultPrinter;
use ReflectionClass;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException;
use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter;
use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport;
use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport;
use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport;
use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport;
use SebastianBergmann\CodeCoverage\Report\Text as TextReport;
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport;
use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Environment\Runtime;
/**
* A TestRunner for the Command Line Interface (CLI)
* PHP SAPI Module.
*/
class TestRunner extends BaseTestRunner
{
public const SUCCESS_EXIT = 0;
public const FAILURE_EXIT = 1;
public const EXCEPTION_EXIT = 2;
/**
* @var bool
*/
protected static $versionStringPrinted = false;
/**
* @var CodeCoverageFilter
*/
protected $codeCoverageFilter;
/**
* @var TestSuiteLoader
*/
protected $loader;
/**
* @var ResultPrinter
*/
protected $printer;
/**
* @var Runtime
*/
private $runtime;
/**
* @var bool
*/
private $messagePrinted = false;
/**
* @var Hook[]
*/
private $extensions = [];
/**
* @param ReflectionClass|Test $test
* @param array $arguments
* @param bool $exit
*
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @throws Exception
* @throws \ReflectionException
*/
public static function run($test, array $arguments = [], $exit = true): TestResult
{
if ($test instanceof ReflectionClass) {
$test = new TestSuite($test);
}
if ($test instanceof Test) {
$aTestRunner = new self;
return $aTestRunner->doRun(
$test,
$arguments,
$exit
);
}
throw new Exception('No test case or test suite found.');
}
public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null)
{
if ($filter === null) {
$filter = new CodeCoverageFilter;
}
$this->codeCoverageFilter = $filter;
$this->loader = $loader;
$this->runtime = new Runtime;
}
/**
* @throws \PHPUnit\Runner\Exception
* @throws Exception
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \ReflectionException
*/
public function doRun(Test $suite, array $arguments = [], bool $exit = true): TestResult
{
if (isset($arguments['configuration'])) {
$GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration'];
}
$this->handleConfiguration($arguments);
$this->processSuiteFilters($suite, $arguments);
if (isset($arguments['bootstrap'])) {
$GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap'];
}
if ($arguments['backupGlobals'] === true) {
$suite->setBackupGlobals(true);
}
if ($arguments['backupStaticAttributes'] === true) {
$suite->setBackupStaticAttributes(true);
}
if ($arguments['beStrictAboutChangesToGlobalState'] === true) {
$suite->setBeStrictAboutChangesToGlobalState(true);
}
if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) {
\mt_srand($arguments['randomOrderSeed']);
}
if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) {
$sorter = new TestSuiteSorter;
$sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies']);
unset($sorter);
}
if (\is_int($arguments['repeat']) && $arguments['repeat'] > 0) {
$_suite = new TestSuite;
foreach (\range(1, $arguments['repeat']) as $step) {
$_suite->addTest($suite);
}
$suite = $_suite;
unset($_suite);
}
$result = $this->createTestResult();
$listener = new TestListenerAdapter;
$listenerNeeded = false;
foreach ($this->extensions as $extension) {
if ($extension instanceof TestHook) {
$listener->add($extension);
$listenerNeeded = true;
}
}
if ($listenerNeeded) {
$result->addListener($listener);
}
unset($listener, $listenerNeeded);
if (!$arguments['convertErrorsToExceptions']) {
$result->convertErrorsToExceptions(false);
}
if (!$arguments['convertDeprecationsToExceptions']) {
Deprecated::$enabled = false;
}
if (!$arguments['convertNoticesToExceptions']) {
Notice::$enabled = false;
}
if (!$arguments['convertWarningsToExceptions']) {
Warning::$enabled = false;
}
if ($arguments['stopOnError']) {
$result->stopOnError(true);
}
if ($arguments['stopOnFailure']) {
$result->stopOnFailure(true);
}
if ($arguments['stopOnWarning']) {
$result->stopOnWarning(true);
}
if ($arguments['stopOnIncomplete']) {
$result->stopOnIncomplete(true);
}
if ($arguments['stopOnRisky']) {
$result->stopOnRisky(true);
}
if ($arguments['stopOnSkipped']) {
$result->stopOnSkipped(true);
}
if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) {
$result->setRegisterMockObjectsFromTestArgumentsRecursively(true);
}
if ($this->printer === null) {
if (isset($arguments['printer']) &&
$arguments['printer'] instanceof Printer) {
$this->printer = $arguments['printer'];
} else {
$printerClass = ResultPrinter::class;
if (isset($arguments['printer']) && \is_string($arguments['printer']) && \class_exists($arguments['printer'], false)) {
$class = new ReflectionClass($arguments['printer']);
if ($class->isSubclassOf(ResultPrinter::class)) {
$printerClass = $arguments['printer'];
}
}
$this->printer = new $printerClass(
(isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null,
$arguments['verbose'],
$arguments['colors'],
$arguments['debug'],
$arguments['columns'],
$arguments['reverseList']
);
}
}
$this->printer->write(
Version::getVersionString() . PHP_EOL
);
self::$versionStringPrinted = true;
if ($arguments['verbose']) {
$runtime = $this->runtime->getNameWithVersion();
if ($this->runtime->hasXdebug()) {
$runtime .= \sprintf(
' with Xdebug %s',
\phpversion('xdebug')
);
}
$this->writeMessage('Runtime', $runtime);
if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) {
$this->writeMessage(
'Random seed',
$arguments['randomOrderSeed']
);
}
if (isset($arguments['configuration'])) {
$this->writeMessage(
'Configuration',
$arguments['configuration']->getFilename()
);
}
foreach ($arguments['loadedExtensions'] as $extension) {
$this->writeMessage(
'Extension',
$extension
);
}
foreach ($arguments['notLoadedExtensions'] as $extension) {
$this->writeMessage(
'Extension',
$extension
);
}
}
if ($this->runtime->discardsComments()) {
$this->writeMessage('Warning', 'opcache.save_comments=0 set; annotations will not work');
}
if (isset($arguments['configuration']) && $arguments['configuration']->hasValidationErrors()) {
$this->write(
"\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n"
);
foreach ($arguments['configuration']->getValidationErrors() as $line => $errors) {
$this->write(\sprintf("\n Line %d:\n", $line));
foreach ($errors as $msg) {
$this->write(\sprintf(" - %s\n", $msg));
}
}
$this->write("\n Test results may not be as expected.\n\n");
}
foreach ($arguments['listeners'] as $listener) {
$result->addListener($listener);
}
$result->addListener($this->printer);
$codeCoverageReports = 0;
if (!isset($arguments['noLogging'])) {
if (isset($arguments['testdoxHTMLFile'])) {
$result->addListener(
new HtmlResultPrinter(
$arguments['testdoxHTMLFile'],
$arguments['testdoxGroups'],
$arguments['testdoxExcludeGroups']
)
);
}
if (isset($arguments['testdoxTextFile'])) {
$result->addListener(
new TextResultPrinter(
$arguments['testdoxTextFile'],
$arguments['testdoxGroups'],
$arguments['testdoxExcludeGroups']
)
);
}
if (isset($arguments['testdoxXMLFile'])) {
$result->addListener(
new XmlResultPrinter(
$arguments['testdoxXMLFile']
)
);
}
if (isset($arguments['teamcityLogfile'])) {
$result->addListener(
new TeamCity($arguments['teamcityLogfile'])
);
}
if (isset($arguments['junitLogfile'])) {
$result->addListener(
new JUnit(
$arguments['junitLogfile'],
$arguments['reportUselessTests']
)
);
}
if (isset($arguments['coverageClover'])) {
$codeCoverageReports++;
}
if (isset($arguments['coverageCrap4J'])) {
$codeCoverageReports++;
}
if (isset($arguments['coverageHtml'])) {
$codeCoverageReports++;
}
if (isset($arguments['coveragePHP'])) {
$codeCoverageReports++;
}
if (isset($arguments['coverageText'])) {
$codeCoverageReports++;
}
if (isset($arguments['coverageXml'])) {
$codeCoverageReports++;
}
}
if (isset($arguments['noCoverage'])) {
$codeCoverageReports = 0;
}
if ($codeCoverageReports > 0 && !$this->runtime->canCollectCodeCoverage()) {
$this->writeMessage('Error', 'No code coverage driver is available');
$codeCoverageReports = 0;
}
if ($codeCoverageReports > 0) {
$codeCoverage = new CodeCoverage(
null,
$this->codeCoverageFilter
);
$codeCoverage->setUnintentionallyCoveredSubclassesWhitelist(
[Comparator::class]
);
$codeCoverage->setCheckForUnintentionallyCoveredCode(
$arguments['strictCoverage']
);
$codeCoverage->setCheckForMissingCoversAnnotation(
$arguments['strictCoverage']
);
if (isset($arguments['forceCoversAnnotation'])) {
$codeCoverage->setForceCoversAnnotation(
$arguments['forceCoversAnnotation']
);
}
if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) {
$codeCoverage->setIgnoreDeprecatedCode(
$arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']
);
}
if (isset($arguments['disableCodeCoverageIgnore'])) {
$codeCoverage->setDisableIgnoredLines(true);
}
$whitelistFromConfigurationFile = false;
$whitelistFromOption = false;
if (isset($arguments['whitelist'])) {
$this->codeCoverageFilter->addDirectoryToWhitelist($arguments['whitelist']);
$whitelistFromOption = true;
}
if (isset($arguments['configuration'])) {
$filterConfiguration = $arguments['configuration']->getFilterConfiguration();
if (!empty($filterConfiguration['whitelist'])) {
$whitelistFromConfigurationFile = true;
}
if (!empty($filterConfiguration['whitelist'])) {
$codeCoverage->setAddUncoveredFilesFromWhitelist(
$filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist']
);
$codeCoverage->setProcessUncoveredFilesFromWhitelist(
$filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist']
);
foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) {
$this->codeCoverageFilter->addDirectoryToWhitelist(
$dir['path'],
$dir['suffix'],
$dir['prefix']
);
}
foreach ($filterConfiguration['whitelist']['include']['file'] as $file) {
$this->codeCoverageFilter->addFileToWhitelist($file);
}
foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) {
$this->codeCoverageFilter->removeDirectoryFromWhitelist(
$dir['path'],
$dir['suffix'],
$dir['prefix']
);
}
foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) {
$this->codeCoverageFilter->removeFileFromWhitelist($file);
}
}
}
if (!$this->codeCoverageFilter->hasWhitelist()) {
if (!$whitelistFromConfigurationFile && !$whitelistFromOption) {
$this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.');
} else {
$this->writeMessage('Error', 'Incorrect whitelist config, no code coverage will be generated.');
}
$codeCoverageReports = 0;
unset($codeCoverage);
}
}
$this->printer->write(PHP_EOL);
if (isset($codeCoverage)) {
$result->setCodeCoverage($codeCoverage);
if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) {
$codeCoverage->setCacheTokens($arguments['cacheTokens']);
}
}
$result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']);
$result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']);
$result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']);
$result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']);
$result->enforceTimeLimit($arguments['enforceTimeLimit']);
$result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']);
$result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']);
$result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']);
if ($suite instanceof TestSuite) {
$suite->setRunTestInSeparateProcess($arguments['processIsolation']);
}
foreach ($this->extensions as $extension) {
if ($extension instanceof BeforeFirstTestHook) {
$extension->executeBeforeFirstTest();
}
}
$suite->run($result);
foreach ($this->extensions as $extension) {
if ($extension instanceof AfterLastTestHook) {
$extension->executeAfterLastTest();
}
}
$result->flushListeners();
if ($this->printer instanceof ResultPrinter) {
$this->printer->printResult($result);
}
if (isset($codeCoverage)) {
if (isset($arguments['coverageClover'])) {
$this->printer->write(
"\nGenerating code coverage report in Clover XML format ..."
);
try {
$writer = new CloverReport;
$writer->process($codeCoverage, $arguments['coverageClover']);
$this->printer->write(" done\n");
unset($writer);
} catch (CodeCoverageException $e) {
$this->printer->write(
" failed\n" . $e->getMessage() . PHP_EOL
);
}
}
if (isset($arguments['coverageCrap4J'])) {
$this->printer->write(
"\nGenerating Crap4J report XML file ..."
);
try {
$writer = new Crap4jReport($arguments['crap4jThreshold']);
$writer->process($codeCoverage, $arguments['coverageCrap4J']);
$this->printer->write(" done\n");
unset($writer);
} catch (CodeCoverageException $e) {
$this->printer->write(
" failed\n" . $e->getMessage() . PHP_EOL
);
}
}
if (isset($arguments['coverageHtml'])) {
$this->printer->write(
"\nGenerating code coverage report in HTML format ..."
);
try {
$writer = new HtmlReport(
$arguments['reportLowUpperBound'],
$arguments['reportHighLowerBound'],
\sprintf(
' and PHPUnit %s ',
Version::id()
)
);
$writer->process($codeCoverage, $arguments['coverageHtml']);
$this->printer->write(" done\n");
unset($writer);
} catch (CodeCoverageException $e) {
$this->printer->write(
" failed\n" . $e->getMessage() . PHP_EOL
);
}
}
if (isset($arguments['coveragePHP'])) {
$this->printer->write(
"\nGenerating code coverage report in PHP format ..."
);
try {
$writer = new PhpReport;
$writer->process($codeCoverage, $arguments['coveragePHP']);
$this->printer->write(" done\n");
unset($writer);
} catch (CodeCoverageException $e) {
$this->printer->write(
" failed\n" . $e->getMessage() . PHP_EOL
);
}
}
if (isset($arguments['coverageText'])) {
if ($arguments['coverageText'] == 'php://stdout') {
$outputStream = $this->printer;
$colors = $arguments['colors'] && $arguments['colors'] != ResultPrinter::COLOR_NEVER;
} else {
$outputStream = new Printer($arguments['coverageText']);
$colors = false;
}
$processor = new TextReport(
$arguments['reportLowUpperBound'],
$arguments['reportHighLowerBound'],
$arguments['coverageTextShowUncoveredFiles'],
$arguments['coverageTextShowOnlySummary']
);
$outputStream->write(
$processor->process($codeCoverage, $colors)
);
}
if (isset($arguments['coverageXml'])) {
$this->printer->write(
"\nGenerating code coverage report in PHPUnit XML format ..."
);
try {
$writer = new XmlReport(Version::id());
$writer->process($codeCoverage, $arguments['coverageXml']);
$this->printer->write(" done\n");
unset($writer);
} catch (CodeCoverageException $e) {
$this->printer->write(
" failed\n" . $e->getMessage() . PHP_EOL
);
}
}
}
if ($exit) {
if ($result->wasSuccessful()) {
if ($arguments['failOnRisky'] && !$result->allHarmless()) {
exit(self::FAILURE_EXIT);
}
if ($arguments['failOnWarning'] && $result->warningCount() > 0) {
exit(self::FAILURE_EXIT);
}
exit(self::SUCCESS_EXIT);
}
if ($result->errorCount() > 0) {
exit(self::EXCEPTION_EXIT);
}
if ($result->failureCount() > 0) {
exit(self::FAILURE_EXIT);
}
}
return $result;
}
public function setPrinter(ResultPrinter $resultPrinter): void
{
$this->printer = $resultPrinter;
}
/**
* Returns the loader to be used.
*/
public function getLoader(): TestSuiteLoader
{
if ($this->loader === null) {
$this->loader = new StandardTestSuiteLoader;
}
return $this->loader;
}
protected function createTestResult(): TestResult
{
return new TestResult;
}
/**
* Override to define how to handle a failed loading of
* a test suite.
*/
protected function runFailed(string $message): void
{
$this->write($message . PHP_EOL);
exit(self::FAILURE_EXIT);
}
protected function write(string $buffer): void
{
if (PHP_SAPI != 'cli' && PHP_SAPI != 'phpdbg') {
$buffer = \htmlspecialchars($buffer);
}
if ($this->printer !== null) {
$this->printer->write($buffer);
} else {
print $buffer;
}
}
/**
* @throws Exception
*/
protected function handleConfiguration(array &$arguments): void
{
if (isset($arguments['configuration']) &&
!$arguments['configuration'] instanceof Configuration) {
$arguments['configuration'] = Configuration::getInstance(
$arguments['configuration']
);
}
$arguments['debug'] = $arguments['debug'] ?? false;
$arguments['filter'] = $arguments['filter'] ?? false;
$arguments['listeners'] = $arguments['listeners'] ?? [];
if (isset($arguments['configuration'])) {
$arguments['configuration']->handlePHPConfiguration();
$phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration();
if (isset($phpunitConfiguration['backupGlobals']) && !isset($arguments['backupGlobals'])) {
$arguments['backupGlobals'] = $phpunitConfiguration['backupGlobals'];
}
if (isset($phpunitConfiguration['backupStaticAttributes']) && !isset($arguments['backupStaticAttributes'])) {
$arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes'];
}
if (isset($phpunitConfiguration['beStrictAboutChangesToGlobalState']) && !isset($arguments['beStrictAboutChangesToGlobalState'])) {
$arguments['beStrictAboutChangesToGlobalState'] = $phpunitConfiguration['beStrictAboutChangesToGlobalState'];
}
if (isset($phpunitConfiguration['bootstrap']) && !isset($arguments['bootstrap'])) {
$arguments['bootstrap'] = $phpunitConfiguration['bootstrap'];
}
if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) {
$arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens'];
}
if (isset($phpunitConfiguration['colors']) && !isset($arguments['colors'])) {
$arguments['colors'] = $phpunitConfiguration['colors'];
}
if (isset($phpunitConfiguration['convertDeprecationsToExceptions']) && !isset($arguments['convertDeprecationsToExceptions'])) {
$arguments['convertDeprecationsToExceptions'] = $phpunitConfiguration['convertDeprecationsToExceptions'];
}
if (isset($phpunitConfiguration['convertErrorsToExceptions']) && !isset($arguments['convertErrorsToExceptions'])) {
$arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions'];
}
if (isset($phpunitConfiguration['convertNoticesToExceptions']) && !isset($arguments['convertNoticesToExceptions'])) {
$arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions'];
}
if (isset($phpunitConfiguration['convertWarningsToExceptions']) && !isset($arguments['convertWarningsToExceptions'])) {
$arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions'];
}
if (isset($phpunitConfiguration['processIsolation']) && !isset($arguments['processIsolation'])) {
$arguments['processIsolation'] = $phpunitConfiguration['processIsolation'];
}
if (isset($phpunitConfiguration['stopOnError']) && !isset($arguments['stopOnError'])) {
$arguments['stopOnError'] = $phpunitConfiguration['stopOnError'];
}
if (isset($phpunitConfiguration['stopOnFailure']) && !isset($arguments['stopOnFailure'])) {
$arguments['stopOnFailure'] = $phpunitConfiguration['stopOnFailure'];
}
if (isset($phpunitConfiguration['stopOnWarning']) && !isset($arguments['stopOnWarning'])) {
$arguments['stopOnWarning'] = $phpunitConfiguration['stopOnWarning'];
}
if (isset($phpunitConfiguration['stopOnIncomplete']) && !isset($arguments['stopOnIncomplete'])) {
$arguments['stopOnIncomplete'] = $phpunitConfiguration['stopOnIncomplete'];
}
if (isset($phpunitConfiguration['stopOnRisky']) && !isset($arguments['stopOnRisky'])) {
$arguments['stopOnRisky'] = $phpunitConfiguration['stopOnRisky'];
}
if (isset($phpunitConfiguration['stopOnSkipped']) && !isset($arguments['stopOnSkipped'])) {
$arguments['stopOnSkipped'] = $phpunitConfiguration['stopOnSkipped'];
}
if (isset($phpunitConfiguration['failOnWarning']) && !isset($arguments['failOnWarning'])) {
$arguments['failOnWarning'] = $phpunitConfiguration['failOnWarning'];
}
if (isset($phpunitConfiguration['failOnRisky']) && !isset($arguments['failOnRisky'])) {
$arguments['failOnRisky'] = $phpunitConfiguration['failOnRisky'];
}
if (isset($phpunitConfiguration['timeoutForSmallTests']) && !isset($arguments['timeoutForSmallTests'])) {
$arguments['timeoutForSmallTests'] = $phpunitConfiguration['timeoutForSmallTests'];
}
if (isset($phpunitConfiguration['timeoutForMediumTests']) && !isset($arguments['timeoutForMediumTests'])) {
$arguments['timeoutForMediumTests'] = $phpunitConfiguration['timeoutForMediumTests'];
}
if (isset($phpunitConfiguration['timeoutForLargeTests']) && !isset($arguments['timeoutForLargeTests'])) {
$arguments['timeoutForLargeTests'] = $phpunitConfiguration['timeoutForLargeTests'];
}
if (isset($phpunitConfiguration['reportUselessTests']) && !isset($arguments['reportUselessTests'])) {
$arguments['reportUselessTests'] = $phpunitConfiguration['reportUselessTests'];
}
if (isset($phpunitConfiguration['strictCoverage']) && !isset($arguments['strictCoverage'])) {
$arguments['strictCoverage'] = $phpunitConfiguration['strictCoverage'];
}
if (isset($phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']) && !isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) {
$arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage'];
}
if (isset($phpunitConfiguration['disallowTestOutput']) && !isset($arguments['disallowTestOutput'])) {
$arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput'];
}
if (isset($phpunitConfiguration['enforceTimeLimit']) && !isset($arguments['enforceTimeLimit'])) {
$arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit'];
}
if (isset($phpunitConfiguration['disallowTodoAnnotatedTests']) && !isset($arguments['disallowTodoAnnotatedTests'])) {
$arguments['disallowTodoAnnotatedTests'] = $phpunitConfiguration['disallowTodoAnnotatedTests'];
}
if (isset($phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']) && !isset($arguments['beStrictAboutResourceUsageDuringSmallTests'])) {
$arguments['beStrictAboutResourceUsageDuringSmallTests'] = $phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests'];
}
if (isset($phpunitConfiguration['verbose']) && !isset($arguments['verbose'])) {
$arguments['verbose'] = $phpunitConfiguration['verbose'];
}
if (isset($phpunitConfiguration['reverseDefectList']) && !isset($arguments['reverseList'])) {
$arguments['reverseList'] = $phpunitConfiguration['reverseDefectList'];
}
if (isset($phpunitConfiguration['forceCoversAnnotation']) && !isset($arguments['forceCoversAnnotation'])) {
$arguments['forceCoversAnnotation'] = $phpunitConfiguration['forceCoversAnnotation'];
}
if (isset($phpunitConfiguration['disableCodeCoverageIgnore']) && !isset($arguments['disableCodeCoverageIgnore'])) {
$arguments['disableCodeCoverageIgnore'] = $phpunitConfiguration['disableCodeCoverageIgnore'];
}
if (isset($phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']) && !isset($arguments['registerMockObjectsFromTestArgumentsRecursively'])) {
$arguments['registerMockObjectsFromTestArgumentsRecursively'] = $phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively'];
}
if (isset($phpunitConfiguration['executionOrder']) && !isset($arguments['executionOrder'])) {
$arguments['executionOrder'] = $phpunitConfiguration['executionOrder'];
}
if (isset($phpunitConfiguration['resolveDependencies']) && !isset($arguments['resolveDependencies'])) {
$arguments['resolveDependencies'] = $phpunitConfiguration['resolveDependencies'];
}
$groupCliArgs = [];
if (!empty($arguments['groups'])) {
$groupCliArgs = $arguments['groups'];
}
$groupConfiguration = $arguments['configuration']->getGroupConfiguration();
if (!empty($groupConfiguration['include']) && !isset($arguments['groups'])) {
$arguments['groups'] = $groupConfiguration['include'];
}
if (!empty($groupConfiguration['exclude']) && !isset($arguments['excludeGroups'])) {
$arguments['excludeGroups'] = \array_diff($groupConfiguration['exclude'], $groupCliArgs);
}
foreach ($arguments['configuration']->getExtensionConfiguration() as $extension) {
if (!\class_exists($extension['class'], false) && $extension['file'] !== '') {
require_once $extension['file'];
}
if (!\class_exists($extension['class'])) {
throw new Exception(
\sprintf(
'Class "%s" does not exist',
$extension['class']
)
);
}
$extensionClass = new ReflectionClass($extension['class']);
if (!$extensionClass->implementsInterface(Hook::class)) {
throw new Exception(
\sprintf(
'Class "%s" does not implement a PHPUnit\Runner\Hook interface',
$extension['class']
)
);
}
if (\count($extension['arguments']) == 0) {
$this->extensions[] = $extensionClass->newInstance();
} else {
$this->extensions[] = $extensionClass->newInstanceArgs(
$extension['arguments']
);
}
}
foreach ($arguments['configuration']->getListenerConfiguration() as $listener) {
if (!\class_exists($listener['class'], false) &&
$listener['file'] !== '') {
require_once $listener['file'];
}
if (!\class_exists($listener['class'])) {
throw new Exception(
\sprintf(
'Class "%s" does not exist',
$listener['class']
)
);
}
$listenerClass = new ReflectionClass($listener['class']);
if (!$listenerClass->implementsInterface(TestListener::class)) {
throw new Exception(
\sprintf(
'Class "%s" does not implement the PHPUnit\Framework\TestListener interface',
$listener['class']
)
);
}
if (\count($listener['arguments']) == 0) {
$listener = new $listener['class'];
} else {
$listener = $listenerClass->newInstanceArgs(
$listener['arguments']
);
}
$arguments['listeners'][] = $listener;
}
$loggingConfiguration = $arguments['configuration']->getLoggingConfiguration();
if (isset($loggingConfiguration['coverage-clover']) && !isset($arguments['coverageClover'])) {
$arguments['coverageClover'] = $loggingConfiguration['coverage-clover'];
}
if (isset($loggingConfiguration['coverage-crap4j']) && !isset($arguments['coverageCrap4J'])) {
$arguments['coverageCrap4J'] = $loggingConfiguration['coverage-crap4j'];
if (isset($loggingConfiguration['crap4jThreshold']) && !isset($arguments['crap4jThreshold'])) {
$arguments['crap4jThreshold'] = $loggingConfiguration['crap4jThreshold'];
}
}
if (isset($loggingConfiguration['coverage-html']) && !isset($arguments['coverageHtml'])) {
if (isset($loggingConfiguration['lowUpperBound']) && !isset($arguments['reportLowUpperBound'])) {
$arguments['reportLowUpperBound'] = $loggingConfiguration['lowUpperBound'];
}
if (isset($loggingConfiguration['highLowerBound']) && !isset($arguments['reportHighLowerBound'])) {
$arguments['reportHighLowerBound'] = $loggingConfiguration['highLowerBound'];
}
$arguments['coverageHtml'] = $loggingConfiguration['coverage-html'];
}
if (isset($loggingConfiguration['coverage-php']) && !isset($arguments['coveragePHP'])) {
$arguments['coveragePHP'] = $loggingConfiguration['coverage-php'];
}
if (isset($loggingConfiguration['coverage-text']) && !isset($arguments['coverageText'])) {
$arguments['coverageText'] = $loggingConfiguration['coverage-text'];
if (isset($loggingConfiguration['coverageTextShowUncoveredFiles'])) {
$arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration['coverageTextShowUncoveredFiles'];
} else {
$arguments['coverageTextShowUncoveredFiles'] = false;
}
if (isset($loggingConfiguration['coverageTextShowOnlySummary'])) {
$arguments['coverageTextShowOnlySummary'] = $loggingConfiguration['coverageTextShowOnlySummary'];
} else {
$arguments['coverageTextShowOnlySummary'] = false;
}
}
if (isset($loggingConfiguration['coverage-xml']) && !isset($arguments['coverageXml'])) {
$arguments['coverageXml'] = $loggingConfiguration['coverage-xml'];
}
if (isset($loggingConfiguration['plain'])) {
$arguments['listeners'][] = new ResultPrinter(
$loggingConfiguration['plain'],
true
);
}
if (isset($loggingConfiguration['teamcity']) && !isset($arguments['teamcityLogfile'])) {
$arguments['teamcityLogfile'] = $loggingConfiguration['teamcity'];
}
if (isset($loggingConfiguration['junit']) && !isset($arguments['junitLogfile'])) {
$arguments['junitLogfile'] = $loggingConfiguration['junit'];
}
if (isset($loggingConfiguration['testdox-html']) && !isset($arguments['testdoxHTMLFile'])) {
$arguments['testdoxHTMLFile'] = $loggingConfiguration['testdox-html'];
}
if (isset($loggingConfiguration['testdox-text']) && !isset($arguments['testdoxTextFile'])) {
$arguments['testdoxTextFile'] = $loggingConfiguration['testdox-text'];
}
if (isset($loggingConfiguration['testdox-xml']) && !isset($arguments['testdoxXMLFile'])) {
$arguments['testdoxXMLFile'] = $loggingConfiguration['testdox-xml'];
}
$testdoxGroupConfiguration = $arguments['configuration']->getTestdoxGroupConfiguration();
if (isset($testdoxGroupConfiguration['include']) &&
!isset($arguments['testdoxGroups'])) {
$arguments['testdoxGroups'] = $testdoxGroupConfiguration['include'];
}
if (isset($testdoxGroupConfiguration['exclude']) &&
!isset($arguments['testdoxExcludeGroups'])) {
$arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration['exclude'];
}
}
$arguments['addUncoveredFilesFromWhitelist'] = $arguments['addUncoveredFilesFromWhitelist'] ?? true;
$arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null;
$arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null;
$arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null;
$arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false;
$arguments['cacheTokens'] = $arguments['cacheTokens'] ?? false;
$arguments['colors'] = $arguments['colors'] ?? ResultPrinter::COLOR_DEFAULT;
$arguments['columns'] = $arguments['columns'] ?? 80;
$arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? true;
$arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true;
$arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true;
$arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true;
$arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30;
$arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false;
$arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false;
$arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false;
$arguments['excludeGroups'] = $arguments['excludeGroups'] ?? [];
$arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false;
$arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false;
$arguments['groups'] = $arguments['groups'] ?? [];
$arguments['processIsolation'] = $arguments['processIsolation'] ?? false;
$arguments['processUncoveredFilesFromWhitelist'] = $arguments['processUncoveredFilesFromWhitelist'] ?? false;
$arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? \time();
$arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false;
$arguments['repeat'] = $arguments['repeat'] ?? false;
$arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90;
$arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50;
$arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true;
$arguments['reverseList'] = $arguments['reverseList'] ?? false;
$arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT;
$arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? false;
$arguments['stopOnError'] = $arguments['stopOnError'] ?? false;
$arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false;
$arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false;
$arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false;
$arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false;
$arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false;
$arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false;
$arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? [];
$arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? [];
$arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60;
$arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10;
$arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1;
$arguments['verbose'] = $arguments['verbose'] ?? false;
}
/**
* @throws \ReflectionException
* @throws \InvalidArgumentException
*/
private function processSuiteFilters(TestSuite $suite, array $arguments): void
{
if (!$arguments['filter'] &&
empty($arguments['groups']) &&
empty($arguments['excludeGroups'])) {
return;
}
$filterFactory = new Factory;
if (!empty($arguments['excludeGroups'])) {
$filterFactory->addFilter(
new ReflectionClass(ExcludeGroupFilterIterator::class),
$arguments['excludeGroups']
);
}
if (!empty($arguments['groups'])) {
$filterFactory->addFilter(
new ReflectionClass(IncludeGroupFilterIterator::class),
$arguments['groups']
);
}
if ($arguments['filter']) {
$filterFactory->addFilter(
new ReflectionClass(NameFilterIterator::class),
$arguments['filter']
);
}
$suite->injectFilter($filterFactory);
}
private function writeMessage(string $type, string $message): void
{
if (!$this->messagePrinted) {
$this->write(PHP_EOL);
}
$this->write(
\sprintf(
"%-15s%s\n",
$type . ':',
$message
)
);
$this->messagePrinted = true;
}
}
src/TextUI/ResultPrinter.php 0000666 00000036666 13436756106 0012110 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestFailure;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Runner\PhptTestCase;
use PHPUnit\Util\InvalidArgumentHelper;
use PHPUnit\Util\Printer;
use SebastianBergmann\Environment\Console;
use SebastianBergmann\Timer\Timer;
/**
* Prints the result of a TextUI TestRunner run.
*/
class ResultPrinter extends Printer implements TestListener
{
public const EVENT_TEST_START = 0;
public const EVENT_TEST_END = 1;
public const EVENT_TESTSUITE_START = 2;
public const EVENT_TESTSUITE_END = 3;
public const COLOR_NEVER = 'never';
public const COLOR_AUTO = 'auto';
public const COLOR_ALWAYS = 'always';
public const COLOR_DEFAULT = self::COLOR_NEVER;
private const AVAILABLE_COLORS = [self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS];
/**
* @var array
*/
private static $ansiCodes = [
'bold' => 1,
'fg-black' => 30,
'fg-red' => 31,
'fg-green' => 32,
'fg-yellow' => 33,
'fg-blue' => 34,
'fg-magenta' => 35,
'fg-cyan' => 36,
'fg-white' => 37,
'bg-black' => 40,
'bg-red' => 41,
'bg-green' => 42,
'bg-yellow' => 43,
'bg-blue' => 44,
'bg-magenta' => 45,
'bg-cyan' => 46,
'bg-white' => 47
];
/**
* @var int
*/
protected $column = 0;
/**
* @var int
*/
protected $maxColumn;
/**
* @var bool
*/
protected $lastTestFailed = false;
/**
* @var int
*/
protected $numAssertions = 0;
/**
* @var int
*/
protected $numTests = -1;
/**
* @var int
*/
protected $numTestsRun = 0;
/**
* @var int
*/
protected $numTestsWidth;
/**
* @var bool
*/
protected $colors = false;
/**
* @var bool
*/
protected $debug = false;
/**
* @var bool
*/
protected $verbose = false;
/**
* @var int
*/
private $numberOfColumns;
/**
* @var bool
*/
private $reverse;
/**
* @var bool
*/
private $defectListPrinted = false;
/**
* Constructor.
*
* @param mixed $out
* @param bool $verbose
* @param string $colors
* @param bool $debug
* @param int|string $numberOfColumns
* @param bool $reverse
*
* @throws Exception
*/
public function __construct($out = null, bool $verbose = false, $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false)
{
parent::__construct($out);
if (!\in_array($colors, self::AVAILABLE_COLORS, true)) {
throw InvalidArgumentHelper::factory(
3,
\vsprintf('value from "%s", "%s" or "%s"', self::AVAILABLE_COLORS)
);
}
if (!\is_int($numberOfColumns) && $numberOfColumns !== 'max') {
throw InvalidArgumentHelper::factory(5, 'integer or "max"');
}
$console = new Console;
$maxNumberOfColumns = $console->getNumberOfColumns();
if ($numberOfColumns === 'max' || ($numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns)) {
$numberOfColumns = $maxNumberOfColumns;
}
$this->numberOfColumns = $numberOfColumns;
$this->verbose = $verbose;
$this->debug = $debug;
$this->reverse = $reverse;
if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) {
$this->colors = true;
} else {
$this->colors = (self::COLOR_ALWAYS === $colors);
}
}
public function printResult(TestResult $result): void
{
$this->printHeader();
$this->printErrors($result);
$this->printWarnings($result);
$this->printFailures($result);
$this->printRisky($result);
if ($this->verbose) {
$this->printIncompletes($result);
$this->printSkipped($result);
}
$this->printFooter($result);
}
/**
* An error occurred.
*/
public function addError(Test $test, \Throwable $t, float $time): void
{
$this->writeProgressWithColor('fg-red, bold', 'E');
$this->lastTestFailed = true;
}
/**
* A failure occurred.
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->writeProgressWithColor('bg-red, fg-white', 'F');
$this->lastTestFailed = true;
}
/**
* A warning occurred.
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->writeProgressWithColor('fg-yellow, bold', 'W');
$this->lastTestFailed = true;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
$this->writeProgressWithColor('fg-yellow, bold', 'I');
$this->lastTestFailed = true;
}
/**
* Risky test.
*/
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
$this->writeProgressWithColor('fg-yellow, bold', 'R');
$this->lastTestFailed = true;
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
$this->writeProgressWithColor('fg-cyan, bold', 'S');
$this->lastTestFailed = true;
}
/**
* A testsuite started.
*/
public function startTestSuite(TestSuite $suite): void
{
if ($this->numTests == -1) {
$this->numTests = \count($suite);
$this->numTestsWidth = \strlen((string) $this->numTests);
$this->maxColumn = $this->numberOfColumns - \strlen(' / (XXX%)') - (2 * $this->numTestsWidth);
}
}
/**
* A testsuite ended.
*/
public function endTestSuite(TestSuite $suite): void
{
}
/**
* A test started.
*/
public function startTest(Test $test): void
{
if ($this->debug) {
$this->write(
\sprintf(
"Test '%s' started\n",
\PHPUnit\Util\Test::describeAsString($test)
)
);
}
}
/**
* A test ended.
*/
public function endTest(Test $test, float $time): void
{
if ($this->debug) {
$this->write(
\sprintf(
"Test '%s' ended\n",
\PHPUnit\Util\Test::describeAsString($test)
)
);
}
if (!$this->lastTestFailed) {
$this->writeProgress('.');
}
if ($test instanceof TestCase) {
$this->numAssertions += $test->getNumAssertions();
} elseif ($test instanceof PhptTestCase) {
$this->numAssertions++;
}
$this->lastTestFailed = false;
if ($test instanceof TestCase && !$test->hasExpectationOnOutput()) {
$this->write($test->getActualOutput());
}
}
protected function printDefects(array $defects, string $type): void
{
$count = \count($defects);
if ($count == 0) {
return;
}
if ($this->defectListPrinted) {
$this->write("\n--\n\n");
}
$this->write(
\sprintf(
"There %s %d %s%s:\n",
($count == 1) ? 'was' : 'were',
$count,
$type,
($count == 1) ? '' : 's'
)
);
$i = 1;
if ($this->reverse) {
$defects = \array_reverse($defects);
}
foreach ($defects as $defect) {
$this->printDefect($defect, $i++);
}
$this->defectListPrinted = true;
}
protected function printDefect(TestFailure $defect, int $count): void
{
$this->printDefectHeader($defect, $count);
$this->printDefectTrace($defect);
}
protected function printDefectHeader(TestFailure $defect, int $count): void
{
$this->write(
\sprintf(
"\n%d) %s\n",
$count,
$defect->getTestName()
)
);
}
protected function printDefectTrace(TestFailure $defect): void
{
$e = $defect->thrownException();
$this->write((string) $e);
while ($e = $e->getPrevious()) {
$this->write("\nCaused by\n" . $e);
}
}
protected function printErrors(TestResult $result): void
{
$this->printDefects($result->errors(), 'error');
}
protected function printFailures(TestResult $result): void
{
$this->printDefects($result->failures(), 'failure');
}
protected function printWarnings(TestResult $result): void
{
$this->printDefects($result->warnings(), 'warning');
}
protected function printIncompletes(TestResult $result): void
{
$this->printDefects($result->notImplemented(), 'incomplete test');
}
protected function printRisky(TestResult $result): void
{
$this->printDefects($result->risky(), 'risky test');
}
protected function printSkipped(TestResult $result): void
{
$this->printDefects($result->skipped(), 'skipped test');
}
protected function printHeader(): void
{
$this->write("\n\n" . Timer::resourceUsage() . "\n\n");
}
protected function printFooter(TestResult $result): void
{
if (\count($result) === 0) {
$this->writeWithColor(
'fg-black, bg-yellow',
'No tests executed!'
);
return;
}
if ($result->wasSuccessful() &&
$result->allHarmless() &&
$result->allCompletelyImplemented() &&
$result->noneSkipped()) {
$this->writeWithColor(
'fg-black, bg-green',
\sprintf(
'OK (%d test%s, %d assertion%s)',
\count($result),
(\count($result) == 1) ? '' : 's',
$this->numAssertions,
($this->numAssertions == 1) ? '' : 's'
)
);
} else {
if ($result->wasSuccessful()) {
$color = 'fg-black, bg-yellow';
if ($this->verbose || !$result->allHarmless()) {
$this->write(PHP_EOL);
}
$this->writeWithColor(
$color,
'OK, but incomplete, skipped, or risky tests!'
);
} else {
$this->write(PHP_EOL);
if ($result->errorCount()) {
$color = 'fg-white, bg-red';
$this->writeWithColor(
$color,
'ERRORS!'
);
} elseif ($result->failureCount()) {
$color = 'fg-white, bg-red';
$this->writeWithColor(
$color,
'FAILURES!'
);
} elseif ($result->warningCount()) {
$color = 'fg-black, bg-yellow';
$this->writeWithColor(
$color,
'WARNINGS!'
);
}
}
$this->writeCountString(\count($result), 'Tests', $color, true);
$this->writeCountString($this->numAssertions, 'Assertions', $color, true);
$this->writeCountString($result->errorCount(), 'Errors', $color);
$this->writeCountString($result->failureCount(), 'Failures', $color);
$this->writeCountString($result->warningCount(), 'Warnings', $color);
$this->writeCountString($result->skippedCount(), 'Skipped', $color);
$this->writeCountString($result->notImplementedCount(), 'Incomplete', $color);
$this->writeCountString($result->riskyCount(), 'Risky', $color);
$this->writeWithColor($color, '.');
}
}
protected function writeProgress(string $progress): void
{
if ($this->debug) {
return;
}
$this->write($progress);
$this->column++;
$this->numTestsRun++;
if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) {
if ($this->numTestsRun == $this->numTests) {
$this->write(\str_repeat(' ', $this->maxColumn - $this->column));
}
$this->write(
\sprintf(
' %' . $this->numTestsWidth . 'd / %' .
$this->numTestsWidth . 'd (%3s%%)',
$this->numTestsRun,
$this->numTests,
\floor(($this->numTestsRun / $this->numTests) * 100)
)
);
if ($this->column == $this->maxColumn) {
$this->writeNewLine();
}
}
}
protected function writeNewLine(): void
{
$this->column = 0;
$this->write(PHP_EOL);
}
/**
* Formats a buffer with a specified ANSI color sequence if colors are
* enabled.
*/
protected function formatWithColor(string $color, string $buffer): string
{
if (!$this->colors) {
return $buffer;
}
$codes = \array_map('\trim', \explode(',', $color));
$lines = \explode(PHP_EOL, $buffer);
$padding = \max(\array_map('\strlen', $lines));
$styles = [];
foreach ($codes as $code) {
$styles[] = self::$ansiCodes[$code];
}
$style = \sprintf("\x1b[%sm", \implode(';', $styles));
$styledLines = [];
foreach ($lines as $line) {
$styledLines[] = $style . \str_pad($line, $padding) . "\x1b[0m";
}
return \implode(PHP_EOL, $styledLines);
}
/**
* Writes a buffer out with a color sequence if colors are enabled.
*/
protected function writeWithColor(string $color, string $buffer, bool $lf = true): void
{
$this->write($this->formatWithColor($color, $buffer));
if ($lf) {
$this->write(PHP_EOL);
}
}
/**
* Writes progress with a color sequence if colors are enabled.
*/
protected function writeProgressWithColor(string $color, string $buffer): void
{
$buffer = $this->formatWithColor($color, $buffer);
$this->writeProgress($buffer);
}
private function writeCountString(int $count, string $name, string $color, bool $always = false): void
{
static $first = true;
if ($always || $count > 0) {
$this->writeWithColor(
$color,
\sprintf(
'%s%s: %d',
!$first ? ', ' : '',
$name,
$count
),
false
);
$first = false;
}
}
}
src/TextUI/Command.php 0000666 00000122177 13436756106 0010635 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI;
use PharIo\Manifest\ApplicationName;
use PharIo\Manifest\Exception as ManifestException;
use PharIo\Manifest\ManifestLoader;
use PharIo\Version\Version as PharIoVersion;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\PhptTestCase;
use PHPUnit\Runner\StandardTestSuiteLoader;
use PHPUnit\Runner\TestSuiteLoader;
use PHPUnit\Runner\TestSuiteSorter;
use PHPUnit\Runner\Version;
use PHPUnit\Util\Configuration;
use PHPUnit\Util\ConfigurationGenerator;
use PHPUnit\Util\FileLoader;
use PHPUnit\Util\Filesystem;
use PHPUnit\Util\Getopt;
use PHPUnit\Util\Log\TeamCity;
use PHPUnit\Util\Printer;
use PHPUnit\Util\TestDox\CliTestDoxPrinter;
use PHPUnit\Util\TextTestListRenderer;
use PHPUnit\Util\XmlTestListRenderer;
use ReflectionClass;
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
use Throwable;
/**
* A TestRunner for the Command Line Interface (CLI)
* PHP SAPI Module.
*/
class Command
{
/**
* @var array
*/
protected $arguments = [
'listGroups' => false,
'listSuites' => false,
'listTests' => false,
'listTestsXml' => false,
'loader' => null,
'useDefaultConfiguration' => true,
'loadedExtensions' => [],
'notLoadedExtensions' => []
];
/**
* @var array
*/
protected $options = [];
/**
* @var array
*/
protected $longOptions = [
'atleast-version=' => null,
'bootstrap=' => null,
'check-version' => null,
'colors==' => null,
'columns=' => null,
'configuration=' => null,
'coverage-clover=' => null,
'coverage-crap4j=' => null,
'coverage-html=' => null,
'coverage-php=' => null,
'coverage-text==' => null,
'coverage-xml=' => null,
'debug' => null,
'disallow-test-output' => null,
'disallow-resource-usage' => null,
'disallow-todo-tests' => null,
'enforce-time-limit' => null,
'exclude-group=' => null,
'filter=' => null,
'generate-configuration' => null,
'globals-backup' => null,
'group=' => null,
'help' => null,
'resolve-dependencies' => null,
'ignore-dependencies' => null,
'include-path=' => null,
'list-groups' => null,
'list-suites' => null,
'list-tests' => null,
'list-tests-xml=' => null,
'loader=' => null,
'log-junit=' => null,
'log-teamcity=' => null,
'no-configuration' => null,
'no-coverage' => null,
'no-logging' => null,
'no-extensions' => null,
'printer=' => null,
'process-isolation' => null,
'repeat=' => null,
'dont-report-useless-tests' => null,
'random-order' => null,
'random-order-seed=' => null,
'reverse-order' => null,
'reverse-list' => null,
'static-backup' => null,
'stderr' => null,
'stop-on-error' => null,
'stop-on-failure' => null,
'stop-on-warning' => null,
'stop-on-incomplete' => null,
'stop-on-risky' => null,
'stop-on-skipped' => null,
'fail-on-warning' => null,
'fail-on-risky' => null,
'strict-coverage' => null,
'disable-coverage-ignore' => null,
'strict-global-state' => null,
'teamcity' => null,
'testdox' => null,
'testdox-group=' => null,
'testdox-exclude-group=' => null,
'testdox-html=' => null,
'testdox-text=' => null,
'testdox-xml=' => null,
'test-suffix=' => null,
'testsuite=' => null,
'verbose' => null,
'version' => null,
'whitelist=' => null
];
/**
* @var bool
*/
private $versionStringPrinted = false;
/**
* @throws \RuntimeException
* @throws \PHPUnit\Framework\Exception
* @throws \InvalidArgumentException
*/
public static function main(bool $exit = true): int
{
$command = new static;
return $command->run($_SERVER['argv'], $exit);
}
/**
* @throws \RuntimeException
* @throws \ReflectionException
* @throws \InvalidArgumentException
* @throws Exception
*/
public function run(array $argv, bool $exit = true): int
{
$this->handleArguments($argv);
$runner = $this->createRunner();
if ($this->arguments['test'] instanceof Test) {
$suite = $this->arguments['test'];
} else {
$suite = $runner->getTest(
$this->arguments['test'],
$this->arguments['testFile'],
$this->arguments['testSuffixes']
);
}
if ($this->arguments['listGroups']) {
return $this->handleListGroups($suite, $exit);
}
if ($this->arguments['listSuites']) {
return $this->handleListSuites($exit);
}
if ($this->arguments['listTests']) {
return $this->handleListTests($suite, $exit);
}
if ($this->arguments['listTestsXml']) {
return $this->handleListTestsXml($suite, $this->arguments['listTestsXml'], $exit);
}
unset(
$this->arguments['test'],
$this->arguments['testFile']
);
try {
$result = $runner->doRun($suite, $this->arguments, $exit);
} catch (Exception $e) {
print $e->getMessage() . PHP_EOL;
}
$return = TestRunner::FAILURE_EXIT;
if (isset($result) && $result->wasSuccessful()) {
$return = TestRunner::SUCCESS_EXIT;
} elseif (!isset($result) || $result->errorCount() > 0) {
$return = TestRunner::EXCEPTION_EXIT;
}
if ($exit) {
exit($return);
}
return $return;
}
/**
* Create a TestRunner, override in subclasses.
*/
protected function createRunner(): TestRunner
{
return new TestRunner($this->arguments['loader']);
}
/**
* Handles the command-line arguments.
*
* A child class of PHPUnit\TextUI\Command can hook into the argument
* parsing by adding the switch(es) to the $longOptions array and point to a
* callback method that handles the switch(es) in the child class like this
*
*
* longOptions['my-switch'] = 'myHandler';
* // my-secondswitch will accept a value - note the equals sign
* $this->longOptions['my-secondswitch='] = 'myOtherHandler';
* }
*
* // --my-switch -> myHandler()
* protected function myHandler()
* {
* }
*
* // --my-secondswitch foo -> myOtherHandler('foo')
* protected function myOtherHandler ($value)
* {
* }
*
* // You will also need this - the static keyword in the
* // PHPUnit\TextUI\Command will mean that it'll be
* // PHPUnit\TextUI\Command that gets instantiated,
* // not MyCommand
* public static function main($exit = true)
* {
* $command = new static;
*
* return $command->run($_SERVER['argv'], $exit);
* }
*
* }
*
*
* @throws Exception
*/
protected function handleArguments(array $argv): void
{
try {
$this->options = Getopt::getopt(
$argv,
'd:c:hv',
\array_keys($this->longOptions)
);
} catch (Exception $t) {
$this->exitWithErrorMessage($t->getMessage());
}
foreach ($this->options[0] as $option) {
switch ($option[0]) {
case '--colors':
$this->arguments['colors'] = $option[1] ?: ResultPrinter::COLOR_AUTO;
break;
case '--bootstrap':
$this->arguments['bootstrap'] = $option[1];
break;
case '--columns':
if (\is_numeric($option[1])) {
$this->arguments['columns'] = (int) $option[1];
} elseif ($option[1] === 'max') {
$this->arguments['columns'] = 'max';
}
break;
case 'c':
case '--configuration':
$this->arguments['configuration'] = $option[1];
break;
case '--coverage-clover':
$this->arguments['coverageClover'] = $option[1];
break;
case '--coverage-crap4j':
$this->arguments['coverageCrap4J'] = $option[1];
break;
case '--coverage-html':
$this->arguments['coverageHtml'] = $option[1];
break;
case '--coverage-php':
$this->arguments['coveragePHP'] = $option[1];
break;
case '--coverage-text':
if ($option[1] === null) {
$option[1] = 'php://stdout';
}
$this->arguments['coverageText'] = $option[1];
$this->arguments['coverageTextShowUncoveredFiles'] = false;
$this->arguments['coverageTextShowOnlySummary'] = false;
break;
case '--coverage-xml':
$this->arguments['coverageXml'] = $option[1];
break;
case 'd':
$ini = \explode('=', $option[1]);
if (isset($ini[0])) {
if (isset($ini[1])) {
\ini_set($ini[0], $ini[1]);
} else {
\ini_set($ini[0], true);
}
}
break;
case '--debug':
$this->arguments['debug'] = true;
break;
case 'h':
case '--help':
$this->showHelp();
exit(TestRunner::SUCCESS_EXIT);
break;
case '--filter':
$this->arguments['filter'] = $option[1];
break;
case '--testsuite':
$this->arguments['testsuite'] = $option[1];
break;
case '--generate-configuration':
$this->printVersionString();
print 'Generating phpunit.xml in ' . \getcwd() . PHP_EOL . PHP_EOL;
print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): ';
$bootstrapScript = \trim(\fgets(STDIN));
print 'Tests directory (relative to path shown above; default: tests): ';
$testsDirectory = \trim(\fgets(STDIN));
print 'Source directory (relative to path shown above; default: src): ';
$src = \trim(\fgets(STDIN));
if ($bootstrapScript === '') {
$bootstrapScript = 'vendor/autoload.php';
}
if ($testsDirectory === '') {
$testsDirectory = 'tests';
}
if ($src === '') {
$src = 'src';
}
$generator = new ConfigurationGenerator;
\file_put_contents(
'phpunit.xml',
$generator->generateDefaultConfiguration(
Version::series(),
$bootstrapScript,
$testsDirectory,
$src
)
);
print PHP_EOL . 'Generated phpunit.xml in ' . \getcwd() . PHP_EOL;
exit(TestRunner::SUCCESS_EXIT);
break;
case '--group':
$this->arguments['groups'] = \explode(',', $option[1]);
break;
case '--exclude-group':
$this->arguments['excludeGroups'] = \explode(
',',
$option[1]
);
break;
case '--test-suffix':
$this->arguments['testSuffixes'] = \explode(
',',
$option[1]
);
break;
case '--include-path':
$includePath = $option[1];
break;
case '--list-groups':
$this->arguments['listGroups'] = true;
break;
case '--list-suites':
$this->arguments['listSuites'] = true;
break;
case '--list-tests':
$this->arguments['listTests'] = true;
break;
case '--list-tests-xml':
$this->arguments['listTestsXml'] = $option[1];
break;
case '--printer':
$this->arguments['printer'] = $option[1];
break;
case '--loader':
$this->arguments['loader'] = $option[1];
break;
case '--log-junit':
$this->arguments['junitLogfile'] = $option[1];
break;
case '--log-teamcity':
$this->arguments['teamcityLogfile'] = $option[1];
break;
case '--process-isolation':
$this->arguments['processIsolation'] = true;
break;
case '--repeat':
$this->arguments['repeat'] = (int) $option[1];
break;
case '--stderr':
$this->arguments['stderr'] = true;
break;
case '--stop-on-error':
$this->arguments['stopOnError'] = true;
break;
case '--stop-on-failure':
$this->arguments['stopOnFailure'] = true;
break;
case '--stop-on-warning':
$this->arguments['stopOnWarning'] = true;
break;
case '--stop-on-incomplete':
$this->arguments['stopOnIncomplete'] = true;
break;
case '--stop-on-risky':
$this->arguments['stopOnRisky'] = true;
break;
case '--stop-on-skipped':
$this->arguments['stopOnSkipped'] = true;
break;
case '--fail-on-warning':
$this->arguments['failOnWarning'] = true;
break;
case '--fail-on-risky':
$this->arguments['failOnRisky'] = true;
break;
case '--teamcity':
$this->arguments['printer'] = TeamCity::class;
break;
case '--testdox':
$this->arguments['printer'] = CliTestDoxPrinter::class;
break;
case '--testdox-group':
$this->arguments['testdoxGroups'] = \explode(
',',
$option[1]
);
break;
case '--testdox-exclude-group':
$this->arguments['testdoxExcludeGroups'] = \explode(
',',
$option[1]
);
break;
case '--testdox-html':
$this->arguments['testdoxHTMLFile'] = $option[1];
break;
case '--testdox-text':
$this->arguments['testdoxTextFile'] = $option[1];
break;
case '--testdox-xml':
$this->arguments['testdoxXMLFile'] = $option[1];
break;
case '--no-configuration':
$this->arguments['useDefaultConfiguration'] = false;
break;
case '--no-extensions':
$this->arguments['noExtensions'] = true;
break;
case '--no-coverage':
$this->arguments['noCoverage'] = true;
break;
case '--no-logging':
$this->arguments['noLogging'] = true;
break;
case '--globals-backup':
$this->arguments['backupGlobals'] = true;
break;
case '--static-backup':
$this->arguments['backupStaticAttributes'] = true;
break;
case 'v':
case '--verbose':
$this->arguments['verbose'] = true;
break;
case '--atleast-version':
if (\version_compare(Version::id(), $option[1], '>=')) {
exit(TestRunner::SUCCESS_EXIT);
}
exit(TestRunner::FAILURE_EXIT);
break;
case '--version':
$this->printVersionString();
exit(TestRunner::SUCCESS_EXIT);
break;
case '--dont-report-useless-tests':
$this->arguments['reportUselessTests'] = false;
break;
case '--strict-coverage':
$this->arguments['strictCoverage'] = true;
break;
case '--disable-coverage-ignore':
$this->arguments['disableCodeCoverageIgnore'] = true;
break;
case '--strict-global-state':
$this->arguments['beStrictAboutChangesToGlobalState'] = true;
break;
case '--disallow-test-output':
$this->arguments['disallowTestOutput'] = true;
break;
case '--disallow-resource-usage':
$this->arguments['beStrictAboutResourceUsageDuringSmallTests'] = true;
break;
case '--enforce-time-limit':
$this->arguments['enforceTimeLimit'] = true;
break;
case '--disallow-todo-tests':
$this->arguments['disallowTodoAnnotatedTests'] = true;
break;
case '--reverse-list':
$this->arguments['reverseList'] = true;
break;
case '--check-version':
$this->handleVersionCheck();
break;
case '--whitelist':
$this->arguments['whitelist'] = $option[1];
break;
case '--random-order':
$this->arguments['executionOrder'] = TestSuiteSorter::ORDER_RANDOMIZED;
break;
case '--random-order-seed':
$this->arguments['randomOrderSeed'] = (int) $option[1];
break;
case '--resolve-dependencies':
$this->arguments['resolveDependencies'] = true;
break;
case '--ignore-dependencies':
$this->arguments['resolveDependencies'] = false;
break;
case '--reverse-order':
$this->arguments['executionOrder'] = TestSuiteSorter::ORDER_REVERSED;
break;
default:
$optionName = \str_replace('--', '', $option[0]);
$handler = null;
if (isset($this->longOptions[$optionName])) {
$handler = $this->longOptions[$optionName];
} elseif (isset($this->longOptions[$optionName . '='])) {
$handler = $this->longOptions[$optionName . '='];
}
if (isset($handler) && \is_callable([$this, $handler])) {
$this->$handler($option[1]);
}
}
}
$this->handleCustomTestSuite();
if (!isset($this->arguments['test'])) {
if (isset($this->options[1][0])) {
$this->arguments['test'] = $this->options[1][0];
}
if (isset($this->options[1][1])) {
$this->arguments['testFile'] = \realpath($this->options[1][1]);
} else {
$this->arguments['testFile'] = '';
}
if (isset($this->arguments['test']) &&
\is_file($this->arguments['test']) &&
\substr($this->arguments['test'], -5, 5) != '.phpt') {
$this->arguments['testFile'] = \realpath($this->arguments['test']);
$this->arguments['test'] = \substr($this->arguments['test'], 0, \strrpos($this->arguments['test'], '.'));
}
}
if (!isset($this->arguments['testSuffixes'])) {
$this->arguments['testSuffixes'] = ['Test.php', '.phpt'];
}
if (isset($includePath)) {
\ini_set(
'include_path',
$includePath . PATH_SEPARATOR . \ini_get('include_path')
);
}
if ($this->arguments['loader'] !== null) {
$this->arguments['loader'] = $this->handleLoader($this->arguments['loader']);
}
if (isset($this->arguments['configuration']) &&
\is_dir($this->arguments['configuration'])) {
$configurationFile = $this->arguments['configuration'] . '/phpunit.xml';
if (\file_exists($configurationFile)) {
$this->arguments['configuration'] = \realpath(
$configurationFile
);
} elseif (\file_exists($configurationFile . '.dist')) {
$this->arguments['configuration'] = \realpath(
$configurationFile . '.dist'
);
}
} elseif (!isset($this->arguments['configuration']) &&
$this->arguments['useDefaultConfiguration']) {
if (\file_exists('phpunit.xml')) {
$this->arguments['configuration'] = \realpath('phpunit.xml');
} elseif (\file_exists('phpunit.xml.dist')) {
$this->arguments['configuration'] = \realpath(
'phpunit.xml.dist'
);
}
}
if (isset($this->arguments['configuration'])) {
try {
$configuration = Configuration::getInstance(
$this->arguments['configuration']
);
} catch (Throwable $t) {
print $t->getMessage() . PHP_EOL;
exit(TestRunner::FAILURE_EXIT);
}
$phpunitConfiguration = $configuration->getPHPUnitConfiguration();
$configuration->handlePHPConfiguration();
/*
* Issue #1216
*/
if (isset($this->arguments['bootstrap'])) {
$this->handleBootstrap($this->arguments['bootstrap']);
} elseif (isset($phpunitConfiguration['bootstrap'])) {
$this->handleBootstrap($phpunitConfiguration['bootstrap']);
}
/*
* Issue #657
*/
if (isset($phpunitConfiguration['stderr']) && !isset($this->arguments['stderr'])) {
$this->arguments['stderr'] = $phpunitConfiguration['stderr'];
}
if (isset($phpunitConfiguration['extensionsDirectory']) && !isset($this->arguments['noExtensions']) && \extension_loaded('phar')) {
$this->handleExtensions($phpunitConfiguration['extensionsDirectory']);
}
if (isset($phpunitConfiguration['columns']) && !isset($this->arguments['columns'])) {
$this->arguments['columns'] = $phpunitConfiguration['columns'];
}
if (!isset($this->arguments['printer']) && isset($phpunitConfiguration['printerClass'])) {
if (isset($phpunitConfiguration['printerFile'])) {
$file = $phpunitConfiguration['printerFile'];
} else {
$file = '';
}
$this->arguments['printer'] = $this->handlePrinter(
$phpunitConfiguration['printerClass'],
$file
);
}
if (isset($phpunitConfiguration['testSuiteLoaderClass'])) {
if (isset($phpunitConfiguration['testSuiteLoaderFile'])) {
$file = $phpunitConfiguration['testSuiteLoaderFile'];
} else {
$file = '';
}
$this->arguments['loader'] = $this->handleLoader(
$phpunitConfiguration['testSuiteLoaderClass'],
$file
);
}
if (!isset($this->arguments['testsuite']) && isset($phpunitConfiguration['defaultTestSuite'])) {
$this->arguments['testsuite'] = $phpunitConfiguration['defaultTestSuite'];
}
if (!isset($this->arguments['test'])) {
$testSuite = $configuration->getTestSuiteConfiguration($this->arguments['testsuite'] ?? '');
if ($testSuite !== null) {
$this->arguments['test'] = $testSuite;
}
}
} elseif (isset($this->arguments['bootstrap'])) {
$this->handleBootstrap($this->arguments['bootstrap']);
}
if (isset($this->arguments['printer']) &&
\is_string($this->arguments['printer'])) {
$this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']);
}
if (isset($this->arguments['test']) && \is_string($this->arguments['test']) && \substr($this->arguments['test'], -5, 5) == '.phpt') {
$test = new PhptTestCase($this->arguments['test']);
$this->arguments['test'] = new TestSuite;
$this->arguments['test']->addTest($test);
}
if (!isset($this->arguments['test'])) {
$this->showHelp();
exit(TestRunner::EXCEPTION_EXIT);
}
}
/**
* Handles the loading of the PHPUnit\Runner\TestSuiteLoader implementation.
*/
protected function handleLoader(string $loaderClass, string $loaderFile = ''): ?TestSuiteLoader
{
if (!\class_exists($loaderClass, false)) {
if ($loaderFile == '') {
$loaderFile = Filesystem::classNameToFilename(
$loaderClass
);
}
$loaderFile = \stream_resolve_include_path($loaderFile);
if ($loaderFile) {
require $loaderFile;
}
}
if (\class_exists($loaderClass, false)) {
$class = new ReflectionClass($loaderClass);
if ($class->implementsInterface(TestSuiteLoader::class) &&
$class->isInstantiable()) {
return $class->newInstance();
}
}
if ($loaderClass == StandardTestSuiteLoader::class) {
return null;
}
$this->exitWithErrorMessage(
\sprintf(
'Could not use "%s" as loader.',
$loaderClass
)
);
return null;
}
/**
* Handles the loading of the PHPUnit\Util\Printer implementation.
*
* @return null|Printer|string
*/
protected function handlePrinter(string $printerClass, string $printerFile = '')
{
if (!\class_exists($printerClass, false)) {
if ($printerFile == '') {
$printerFile = Filesystem::classNameToFilename(
$printerClass
);
}
$printerFile = \stream_resolve_include_path($printerFile);
if ($printerFile) {
require $printerFile;
}
}
if (!\class_exists($printerClass)) {
$this->exitWithErrorMessage(
\sprintf(
'Could not use "%s" as printer: class does not exist',
$printerClass
)
);
}
$class = new ReflectionClass($printerClass);
if (!$class->implementsInterface(TestListener::class)) {
$this->exitWithErrorMessage(
\sprintf(
'Could not use "%s" as printer: class does not implement %s',
$printerClass,
TestListener::class
)
);
}
if (!$class->isSubclassOf(Printer::class)) {
$this->exitWithErrorMessage(
\sprintf(
'Could not use "%s" as printer: class does not extend %s',
$printerClass,
Printer::class
)
);
}
if (!$class->isInstantiable()) {
$this->exitWithErrorMessage(
\sprintf(
'Could not use "%s" as printer: class cannot be instantiated',
$printerClass
)
);
}
if ($class->isSubclassOf(ResultPrinter::class)) {
return $printerClass;
}
$outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null;
return $class->newInstance($outputStream);
}
/**
* Loads a bootstrap file.
*/
protected function handleBootstrap(string $filename): void
{
try {
FileLoader::checkAndLoad($filename);
} catch (Exception $e) {
$this->exitWithErrorMessage($e->getMessage());
}
}
protected function handleVersionCheck(): void
{
$this->printVersionString();
$latestVersion = \file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit');
$isOutdated = \version_compare($latestVersion, Version::id(), '>');
if ($isOutdated) {
\printf(
'You are not using the latest version of PHPUnit.' . PHP_EOL .
'The latest version is PHPUnit %s.' . PHP_EOL,
$latestVersion
);
} else {
print 'You are using the latest version of PHPUnit.' . PHP_EOL;
}
exit(TestRunner::SUCCESS_EXIT);
}
/**
* Show the help message.
*/
protected function showHelp(): void
{
$this->printVersionString();
print <<
Code Coverage Options:
--coverage-clover Generate code coverage report in Clover XML format
--coverage-crap4j Generate code coverage report in Crap4J XML format
--coverage-html Generate code coverage report in HTML format
--coverage-php Export PHP_CodeCoverage object to file
--coverage-text= Generate code coverage report in text format
Default: Standard output
--coverage-xml Generate code coverage report in PHPUnit XML format
--whitelist Whitelist for code coverage analysis
--disable-coverage-ignore Disable annotations for ignoring code coverage
Logging Options:
--log-junit Log test execution in JUnit XML format to file
--log-teamcity Log test execution in TeamCity format to file
--testdox-html Write agile documentation in HTML format to file
--testdox-text Write agile documentation in Text format to file
--testdox-xml Write agile documentation in XML format to file
--reverse-list Print defects in reverse order
Test Selection Options:
--filter Filter which tests to run
--testsuite Filter which testsuite to run
--group ... Only runs tests from the specified group(s)
--exclude-group ... Exclude tests from the specified group(s)
--list-groups List available test groups
--list-suites List available test suites
--list-tests List available tests
--list-tests-xml List available tests in XML format
--test-suffix ... Only search for test in files with specified
suffix(es). Default: Test.php,.phpt
Test Execution Options:
--dont-report-useless-tests Do not report tests that do not test anything
--strict-coverage Be strict about @covers annotation usage
--strict-global-state Be strict about changes to global state
--disallow-test-output Be strict about output during tests
--disallow-resource-usage Be strict about resource usage during small tests
--enforce-time-limit Enforce time limit based on test size
--disallow-todo-tests Disallow @todo-annotated tests
--process-isolation Run each test in a separate PHP process
--globals-backup Backup and restore \$GLOBALS for each test
--static-backup Backup and restore static attributes for each test
--colors= Use colors in output ("never", "auto" or "always")
--columns Number of columns to use for progress output
--columns max Use maximum number of columns for progress output
--stderr Write to STDERR instead of STDOUT
--stop-on-error Stop execution upon first error
--stop-on-failure Stop execution upon first error or failure
--stop-on-warning Stop execution upon first warning
--stop-on-risky Stop execution upon first risky test
--stop-on-skipped Stop execution upon first skipped test
--stop-on-incomplete Stop execution upon first incomplete test
--fail-on-warning Treat tests with warnings as failures
--fail-on-risky Treat risky tests as failures
-v|--verbose Output more verbose information
--debug Display debugging information
--loader TestSuiteLoader implementation to use
--repeat Runs the test(s) repeatedly
--teamcity Report test execution progress in TeamCity format
--testdox Report test execution progress in TestDox format
--testdox-group Only include tests from the specified group(s)
--testdox-exclude-group Exclude tests from the specified group(s)
--printer TestListener implementation to use
--resolve-dependencies Resolve dependencies between tests
--random-order Run tests in random order
--random-order-seed= Use a specific random seed for random order
--reverse-order Run tests last-to-first
Configuration Options:
--bootstrap A "bootstrap" PHP file that is run before the tests
-c|--configuration Read configuration from XML file
--no-configuration Ignore default configuration file (phpunit.xml)
--no-coverage Ignore code coverage configuration
--no-logging Ignore logging configuration
--no-extensions Do not load PHPUnit extensions
--include-path Prepend PHP's include_path with given path(s)
-d key[=value] Sets a php.ini value
--generate-configuration Generate configuration file with suggested settings
Miscellaneous Options:
-h|--help Prints this usage information
--version Prints the version and exits
--atleast-version Checks that version is greater than min and exits
--check-version Check whether PHPUnit is the latest version
EOT;
}
/**
* Custom callback for test suite discovery.
*/
protected function handleCustomTestSuite(): void
{
}
private function printVersionString(): void
{
if ($this->versionStringPrinted) {
return;
}
print Version::getVersionString() . PHP_EOL . PHP_EOL;
$this->versionStringPrinted = true;
}
private function exitWithErrorMessage(string $message): void
{
$this->printVersionString();
print $message . PHP_EOL;
exit(TestRunner::FAILURE_EXIT);
}
private function handleExtensions(string $directory): void
{
$facade = new FileIteratorFacade;
foreach ($facade->getFilesAsArray($directory, '.phar') as $file) {
if (!\file_exists('phar://' . $file . '/manifest.xml')) {
$this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit';
continue;
}
try {
$applicationName = new ApplicationName('phpunit/phpunit');
$version = new PharIoVersion(Version::series());
$manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml');
if (!$manifest->isExtensionFor($applicationName)) {
$this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit';
continue;
}
if (!$manifest->isExtensionFor($applicationName, $version)) {
$this->arguments['notLoadedExtensions'][] = $file . ' is not compatible with this version of PHPUnit';
continue;
}
} catch (ManifestException $e) {
$this->arguments['notLoadedExtensions'][] = $file . ': ' . $e->getMessage();
continue;
}
require $file;
$this->arguments['loadedExtensions'][] = $manifest->getName() . ' ' . $manifest->getVersion()->getVersionString();
}
}
private function handleListGroups(TestSuite $suite, bool $exit): int
{
$this->printVersionString();
print 'Available test group(s):' . PHP_EOL;
$groups = $suite->getGroups();
\sort($groups);
foreach ($groups as $group) {
\printf(
' - %s' . PHP_EOL,
$group
);
}
if ($exit) {
exit(TestRunner::SUCCESS_EXIT);
}
return TestRunner::SUCCESS_EXIT;
}
private function handleListSuites(bool $exit): int
{
$this->printVersionString();
print 'Available test suite(s):' . PHP_EOL;
$configuration = Configuration::getInstance(
$this->arguments['configuration']
);
$suiteNames = $configuration->getTestSuiteNames();
foreach ($suiteNames as $suiteName) {
\printf(
' - %s' . PHP_EOL,
$suiteName
);
}
if ($exit) {
exit(TestRunner::SUCCESS_EXIT);
}
return TestRunner::SUCCESS_EXIT;
}
private function handleListTests(TestSuite $suite, bool $exit): int
{
$this->printVersionString();
$renderer = new TextTestListRenderer;
print $renderer->render($suite);
if ($exit) {
exit(TestRunner::SUCCESS_EXIT);
}
return TestRunner::SUCCESS_EXIT;
}
private function handleListTestsXml(TestSuite $suite, string $target, bool $exit): int
{
$this->printVersionString();
$renderer = new XmlTestListRenderer;
\file_put_contents($target, $renderer->render($suite));
\printf(
'Wrote list of tests that would have been run to %s' . \PHP_EOL,
$target
);
if ($exit) {
exit(TestRunner::SUCCESS_EXIT);
}
return TestRunner::SUCCESS_EXIT;
}
}
src/Framework/SkippedTestCase.php 0000666 00000002656 13436756106 0013064 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
/**
* A skipped test case
*/
class SkippedTestCase extends TestCase
{
/**
* @var string
*/
protected $message = '';
/**
* @var bool
*/
protected $backupGlobals = false;
/**
* @var bool
*/
protected $backupStaticAttributes = false;
/**
* @var bool
*/
protected $runTestInSeparateProcess = false;
/**
* @var bool
*/
protected $useErrorHandler = false;
/**
* @var bool
*/
protected $useOutputBuffering = false;
public function __construct(string $className, string $methodName, string $message = '')
{
parent::__construct($className . '::' . $methodName);
$this->message = $message;
}
public function getMessage(): string
{
return $this->message;
}
/**
* Returns a string representation of the test case.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function toString(): string
{
return $this->getName();
}
/**
* @throws Exception
*/
protected function runTest(): void
{
$this->markTestSkipped($this->message);
}
}
src/Framework/CoveredCodeNotExecutedException.php 0000666 00000000504 13436756106 0016230 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
class CoveredCodeNotExecutedException extends RiskyTestError
{
}
src/Framework/Assert.php 0000666 00000255236 13436756106 0011276 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
use ArrayAccess;
use Countable;
use DOMDocument;
use DOMElement;
use PHPUnit\Framework\Constraint\ArrayHasKey;
use PHPUnit\Framework\Constraint\ArraySubset;
use PHPUnit\Framework\Constraint\Attribute;
use PHPUnit\Framework\Constraint\Callback;
use PHPUnit\Framework\Constraint\ClassHasAttribute;
use PHPUnit\Framework\Constraint\ClassHasStaticAttribute;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\Constraint\Count;
use PHPUnit\Framework\Constraint\DirectoryExists;
use PHPUnit\Framework\Constraint\FileExists;
use PHPUnit\Framework\Constraint\GreaterThan;
use PHPUnit\Framework\Constraint\IsAnything;
use PHPUnit\Framework\Constraint\IsEmpty;
use PHPUnit\Framework\Constraint\IsEqual;
use PHPUnit\Framework\Constraint\IsFalse;
use PHPUnit\Framework\Constraint\IsFinite;
use PHPUnit\Framework\Constraint\IsIdentical;
use PHPUnit\Framework\Constraint\IsInfinite;
use PHPUnit\Framework\Constraint\IsInstanceOf;
use PHPUnit\Framework\Constraint\IsJson;
use PHPUnit\Framework\Constraint\IsNan;
use PHPUnit\Framework\Constraint\IsNull;
use PHPUnit\Framework\Constraint\IsReadable;
use PHPUnit\Framework\Constraint\IsTrue;
use PHPUnit\Framework\Constraint\IsType;
use PHPUnit\Framework\Constraint\IsWritable;
use PHPUnit\Framework\Constraint\JsonMatches;
use PHPUnit\Framework\Constraint\LessThan;
use PHPUnit\Framework\Constraint\LogicalAnd;
use PHPUnit\Framework\Constraint\LogicalNot;
use PHPUnit\Framework\Constraint\LogicalOr;
use PHPUnit\Framework\Constraint\LogicalXor;
use PHPUnit\Framework\Constraint\ObjectHasAttribute;
use PHPUnit\Framework\Constraint\RegularExpression;
use PHPUnit\Framework\Constraint\SameSize;
use PHPUnit\Framework\Constraint\StringContains;
use PHPUnit\Framework\Constraint\StringEndsWith;
use PHPUnit\Framework\Constraint\StringMatchesFormatDescription;
use PHPUnit\Framework\Constraint\StringStartsWith;
use PHPUnit\Framework\Constraint\TraversableContains;
use PHPUnit\Framework\Constraint\TraversableContainsOnly;
use PHPUnit\Util\InvalidArgumentHelper;
use PHPUnit\Util\Type;
use PHPUnit\Util\Xml;
use ReflectionClass;
use ReflectionException;
use ReflectionObject;
use ReflectionProperty;
use Traversable;
/**
* A set of assertion methods.
*/
abstract class Assert
{
/**
* @var int
*/
private static $count = 0;
/**
* Asserts that an array has a specified key.
*
* @param int|string $key
* @param array|ArrayAccess $array
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertArrayHasKey($key, $array, string $message = ''): void
{
if (!(\is_int($key) || \is_string($key))) {
throw InvalidArgumentHelper::factory(
1,
'integer or string'
);
}
if (!(\is_array($array) || $array instanceof ArrayAccess)) {
throw InvalidArgumentHelper::factory(
2,
'array or ArrayAccess'
);
}
$constraint = new ArrayHasKey($key);
static::assertThat($array, $constraint, $message);
}
/**
* Asserts that an array has a specified subset.
*
* @param array|ArrayAccess $subset
* @param array|ArrayAccess $array
* @param bool $strict Check for object identity
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertArraySubset($subset, $array, bool $strict = false, string $message = ''): void
{
if (!(\is_array($subset) || $subset instanceof ArrayAccess)) {
throw InvalidArgumentHelper::factory(
1,
'array or ArrayAccess'
);
}
if (!(\is_array($array) || $array instanceof ArrayAccess)) {
throw InvalidArgumentHelper::factory(
2,
'array or ArrayAccess'
);
}
$constraint = new ArraySubset($subset, $strict);
static::assertThat($array, $constraint, $message);
}
/**
* Asserts that an array does not have a specified key.
*
* @param int|string $key
* @param array|ArrayAccess $array
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertArrayNotHasKey($key, $array, string $message = ''): void
{
if (!(\is_int($key) || \is_string($key))) {
throw InvalidArgumentHelper::factory(
1,
'integer or string'
);
}
if (!(\is_array($array) || $array instanceof ArrayAccess)) {
throw InvalidArgumentHelper::factory(
2,
'array or ArrayAccess'
);
}
$constraint = new LogicalNot(
new ArrayHasKey($key)
);
static::assertThat($array, $constraint, $message);
}
/**
* Asserts that a haystack contains a needle.
*
* @param mixed $needle
* @param mixed $haystack
* @param string $message
* @param bool $ignoreCase
* @param bool $checkForObjectIdentity
* @param bool $checkForNonObjectIdentity
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertContains($needle, $haystack, string $message = '', bool $ignoreCase = false, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): void
{
if (\is_array($haystack) ||
(\is_object($haystack) && $haystack instanceof Traversable)) {
$constraint = new TraversableContains(
$needle,
$checkForObjectIdentity,
$checkForNonObjectIdentity
);
} elseif (\is_string($haystack)) {
if (!\is_string($needle)) {
throw InvalidArgumentHelper::factory(
1,
'string'
);
}
$constraint = new StringContains(
$needle,
$ignoreCase
);
} else {
throw InvalidArgumentHelper::factory(
2,
'array, traversable or string'
);
}
static::assertThat($haystack, $constraint, $message);
}
/**
* Asserts that a haystack that is stored in a static attribute of a class
* or an attribute of an object contains a needle.
*
* @param mixed $needle
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
* @param bool $ignoreCase
* @param bool $checkForObjectIdentity
* @param bool $checkForNonObjectIdentity
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeContains($needle, string $haystackAttributeName, $haystackClassOrObject, string $message = '', bool $ignoreCase = false, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): void
{
static::assertContains(
$needle,
static::readAttribute($haystackClassOrObject, $haystackAttributeName),
$message,
$ignoreCase,
$checkForObjectIdentity,
$checkForNonObjectIdentity
);
}
/**
* Asserts that a haystack does not contain a needle.
*
* @param mixed $needle
* @param mixed $haystack
* @param string $message
* @param bool $ignoreCase
* @param bool $checkForObjectIdentity
* @param bool $checkForNonObjectIdentity
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotContains($needle, $haystack, string $message = '', bool $ignoreCase = false, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): void
{
if (\is_array($haystack) ||
(\is_object($haystack) && $haystack instanceof Traversable)) {
$constraint = new LogicalNot(
new TraversableContains(
$needle,
$checkForObjectIdentity,
$checkForNonObjectIdentity
)
);
} elseif (\is_string($haystack)) {
if (!\is_string($needle)) {
throw InvalidArgumentHelper::factory(
1,
'string'
);
}
$constraint = new LogicalNot(
new StringContains(
$needle,
$ignoreCase
)
);
} else {
throw InvalidArgumentHelper::factory(
2,
'array, traversable or string'
);
}
static::assertThat($haystack, $constraint, $message);
}
/**
* Asserts that a haystack that is stored in a static attribute of a class
* or an attribute of an object does not contain a needle.
*
* @param mixed $needle
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
* @param bool $ignoreCase
* @param bool $checkForObjectIdentity
* @param bool $checkForNonObjectIdentity
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeNotContains($needle, string $haystackAttributeName, $haystackClassOrObject, string $message = '', bool $ignoreCase = false, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): void
{
static::assertNotContains(
$needle,
static::readAttribute($haystackClassOrObject, $haystackAttributeName),
$message,
$ignoreCase,
$checkForObjectIdentity,
$checkForNonObjectIdentity
);
}
/**
* Asserts that a haystack contains only values of a given type.
*
* @param string $type
* @param iterable $haystack
* @param null|bool $isNativeType
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void
{
if ($isNativeType === null) {
$isNativeType = Type::isType($type);
}
static::assertThat(
$haystack,
new TraversableContainsOnly(
$type,
$isNativeType
),
$message
);
}
/**
* Asserts that a haystack contains only instances of a given class name.
*
* @param string $className
* @param iterable $haystack
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void
{
static::assertThat(
$haystack,
new TraversableContainsOnly(
$className,
false
),
$message
);
}
/**
* Asserts that a haystack that is stored in a static attribute of a class
* or an attribute of an object contains only values of a given type.
*
* @param string $type
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param bool $isNativeType
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeContainsOnly(string $type, string $haystackAttributeName, $haystackClassOrObject, ?bool $isNativeType = null, string $message = ''): void
{
static::assertContainsOnly(
$type,
static::readAttribute($haystackClassOrObject, $haystackAttributeName),
$isNativeType,
$message
);
}
/**
* Asserts that a haystack does not contain only values of a given type.
*
* @param string $type
* @param iterable $haystack
* @param null|bool $isNativeType
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void
{
if ($isNativeType === null) {
$isNativeType = Type::isType($type);
}
static::assertThat(
$haystack,
new LogicalNot(
new TraversableContainsOnly(
$type,
$isNativeType
)
),
$message
);
}
/**
* Asserts that a haystack that is stored in a static attribute of a class
* or an attribute of an object does not contain only values of a given
* type.
*
* @param string $type
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param bool $isNativeType
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeNotContainsOnly(string $type, string $haystackAttributeName, $haystackClassOrObject, ?bool $isNativeType = null, string $message = ''): void
{
static::assertNotContainsOnly(
$type,
static::readAttribute($haystackClassOrObject, $haystackAttributeName),
$isNativeType,
$message
);
}
/**
* Asserts the number of elements of an array, Countable or Traversable.
*
* @param int $expectedCount
* @param Countable|iterable $haystack
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertCount(int $expectedCount, $haystack, string $message = ''): void
{
if (!$haystack instanceof Countable && !\is_iterable($haystack)) {
throw InvalidArgumentHelper::factory(2, 'countable or iterable');
}
static::assertThat(
$haystack,
new Count($expectedCount),
$message
);
}
/**
* Asserts the number of elements of an array, Countable or Traversable
* that is stored in an attribute.
*
* @param int $expectedCount
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeCount(int $expectedCount, string $haystackAttributeName, $haystackClassOrObject, string $message = ''): void
{
static::assertCount(
$expectedCount,
static::readAttribute($haystackClassOrObject, $haystackAttributeName),
$message
);
}
/**
* Asserts the number of elements of an array, Countable or Traversable.
*
* @param int $expectedCount
* @param Countable|iterable $haystack
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotCount(int $expectedCount, $haystack, string $message = ''): void
{
if (!$haystack instanceof Countable && !\is_iterable($haystack)) {
throw InvalidArgumentHelper::factory(2, 'countable or iterable');
}
$constraint = new LogicalNot(
new Count($expectedCount)
);
static::assertThat($haystack, $constraint, $message);
}
/**
* Asserts the number of elements of an array, Countable or Traversable
* that is stored in an attribute.
*
* @param int $expectedCount
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeNotCount(int $expectedCount, string $haystackAttributeName, $haystackClassOrObject, string $message = ''): void
{
static::assertNotCount(
$expectedCount,
static::readAttribute($haystackClassOrObject, $haystackAttributeName),
$message
);
}
/**
* Asserts that two variables are equal.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
* @param float $delta
* @param int $maxDepth
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertEquals($expected, $actual, string $message = '', float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void
{
$constraint = new IsEqual(
$expected,
$delta,
$maxDepth,
$canonicalize,
$ignoreCase
);
static::assertThat($actual, $constraint, $message);
}
/**
* Asserts that a variable is equal to an attribute of an object.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
* @param float $delta
* @param int $maxDepth
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeEquals($expected, string $actualAttributeName, $actualClassOrObject, string $message = '', float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void
{
static::assertEquals(
$expected,
static::readAttribute($actualClassOrObject, $actualAttributeName),
$message,
$delta,
$maxDepth,
$canonicalize,
$ignoreCase
);
}
/**
* Asserts that two variables are not equal.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
* @param float $delta
* @param int $maxDepth
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotEquals($expected, $actual, string $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false): void
{
$constraint = new LogicalNot(
new IsEqual(
$expected,
$delta,
$maxDepth,
$canonicalize,
$ignoreCase
)
);
static::assertThat($actual, $constraint, $message);
}
/**
* Asserts that a variable is not equal to an attribute of an object.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
* @param float $delta
* @param int $maxDepth
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeNotEquals($expected, string $actualAttributeName, $actualClassOrObject, string $message = '', float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void
{
static::assertNotEquals(
$expected,
static::readAttribute($actualClassOrObject, $actualAttributeName),
$message,
$delta,
$maxDepth,
$canonicalize,
$ignoreCase
);
}
/**
* Asserts that a variable is empty.
*
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertEmpty($actual, string $message = ''): void
{
static::assertThat($actual, static::isEmpty(), $message);
}
/**
* Asserts that a static attribute of a class or an attribute of an object
* is empty.
*
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeEmpty(string $haystackAttributeName, $haystackClassOrObject, string $message = ''): void
{
static::assertEmpty(
static::readAttribute($haystackClassOrObject, $haystackAttributeName),
$message
);
}
/**
* Asserts that a variable is not empty.
*
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotEmpty($actual, string $message = ''): void
{
static::assertThat($actual, static::logicalNot(static::isEmpty()), $message);
}
/**
* Asserts that a static attribute of a class or an attribute of an object
* is not empty.
*
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeNotEmpty(string $haystackAttributeName, $haystackClassOrObject, string $message = ''): void
{
static::assertNotEmpty(
static::readAttribute($haystackClassOrObject, $haystackAttributeName),
$message
);
}
/**
* Asserts that a value is greater than another value.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertGreaterThan($expected, $actual, string $message = ''): void
{
static::assertThat($actual, static::greaterThan($expected), $message);
}
/**
* Asserts that an attribute is greater than another value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeGreaterThan($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
static::assertGreaterThan(
$expected,
static::readAttribute($actualClassOrObject, $actualAttributeName),
$message
);
}
/**
* Asserts that a value is greater than or equal to another value.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void
{
static::assertThat(
$actual,
static::greaterThanOrEqual($expected),
$message
);
}
/**
* Asserts that an attribute is greater than or equal to another value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeGreaterThanOrEqual($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
static::assertGreaterThanOrEqual(
$expected,
static::readAttribute($actualClassOrObject, $actualAttributeName),
$message
);
}
/**
* Asserts that a value is smaller than another value.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertLessThan($expected, $actual, string $message = ''): void
{
static::assertThat($actual, static::lessThan($expected), $message);
}
/**
* Asserts that an attribute is smaller than another value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeLessThan($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
static::assertLessThan(
$expected,
static::readAttribute($actualClassOrObject, $actualAttributeName),
$message
);
}
/**
* Asserts that a value is smaller than or equal to another value.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertLessThanOrEqual($expected, $actual, string $message = ''): void
{
static::assertThat($actual, static::lessThanOrEqual($expected), $message);
}
/**
* Asserts that an attribute is smaller than or equal to another value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeLessThanOrEqual($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
static::assertLessThanOrEqual(
$expected,
static::readAttribute($actualClassOrObject, $actualAttributeName),
$message
);
}
/**
* Asserts that the contents of one file is equal to the contents of another
* file.
*
* @param string $expected
* @param string $actual
* @param string $message
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFileEquals(string $expected, string $actual, string $message = '', bool $canonicalize = false, bool $ignoreCase = false): void
{
static::assertFileExists($expected, $message);
static::assertFileExists($actual, $message);
static::assertEquals(
\file_get_contents($expected),
\file_get_contents($actual),
$message,
0,
10,
$canonicalize,
$ignoreCase
);
}
/**
* Asserts that the contents of one file is not equal to the contents of
* another file.
*
* @param string $expected
* @param string $actual
* @param string $message
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFileNotEquals(string $expected, string $actual, string $message = '', bool $canonicalize = false, bool $ignoreCase = false): void
{
static::assertFileExists($expected, $message);
static::assertFileExists($actual, $message);
static::assertNotEquals(
\file_get_contents($expected),
\file_get_contents($actual),
$message,
0,
10,
$canonicalize,
$ignoreCase
);
}
/**
* Asserts that the contents of a string is equal
* to the contents of a file.
*
* @param string $expectedFile
* @param string $actualString
* @param string $message
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = '', bool $canonicalize = false, bool $ignoreCase = false): void
{
static::assertFileExists($expectedFile, $message);
/** @noinspection PhpUnitTestsInspection */
static::assertEquals(
\file_get_contents($expectedFile),
$actualString,
$message,
0,
10,
$canonicalize,
$ignoreCase
);
}
/**
* Asserts that the contents of a string is not equal
* to the contents of a file.
*
* @param string $expectedFile
* @param string $actualString
* @param string $message
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = '', bool $canonicalize = false, bool $ignoreCase = false): void
{
static::assertFileExists($expectedFile, $message);
static::assertNotEquals(
\file_get_contents($expectedFile),
$actualString,
$message,
0,
10,
$canonicalize,
$ignoreCase
);
}
/**
* Asserts that a file/dir is readable.
*
* @param string $filename
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertIsReadable(string $filename, string $message = ''): void
{
static::assertThat($filename, new IsReadable, $message);
}
/**
* Asserts that a file/dir exists and is not readable.
*
* @param string $filename
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotIsReadable(string $filename, string $message = ''): void
{
static::assertThat($filename, new LogicalNot(new IsReadable), $message);
}
/**
* Asserts that a file/dir exists and is writable.
*
* @param string $filename
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertIsWritable(string $filename, string $message = ''): void
{
static::assertThat($filename, new IsWritable, $message);
}
/**
* Asserts that a file/dir exists and is not writable.
*
* @param string $filename
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotIsWritable(string $filename, string $message = ''): void
{
static::assertThat($filename, new LogicalNot(new IsWritable), $message);
}
/**
* Asserts that a directory exists.
*
* @param string $directory
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertDirectoryExists(string $directory, string $message = ''): void
{
static::assertThat($directory, new DirectoryExists, $message);
}
/**
* Asserts that a directory does not exist.
*
* @param string $directory
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertDirectoryNotExists(string $directory, string $message = ''): void
{
static::assertThat($directory, new LogicalNot(new DirectoryExists), $message);
}
/**
* Asserts that a directory exists and is readable.
*
* @param string $directory
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertDirectoryIsReadable(string $directory, string $message = ''): void
{
self::assertDirectoryExists($directory, $message);
self::assertIsReadable($directory, $message);
}
/**
* Asserts that a directory exists and is not readable.
*
* @param string $directory
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertDirectoryNotIsReadable(string $directory, string $message = ''): void
{
self::assertDirectoryExists($directory, $message);
self::assertNotIsReadable($directory, $message);
}
/**
* Asserts that a directory exists and is writable.
*
* @param string $directory
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertDirectoryIsWritable(string $directory, string $message = ''): void
{
self::assertDirectoryExists($directory, $message);
self::assertIsWritable($directory, $message);
}
/**
* Asserts that a directory exists and is not writable.
*
* @param string $directory
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertDirectoryNotIsWritable(string $directory, string $message = ''): void
{
self::assertDirectoryExists($directory, $message);
self::assertNotIsWritable($directory, $message);
}
/**
* Asserts that a file exists.
*
* @param string $filename
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFileExists(string $filename, string $message = ''): void
{
static::assertThat($filename, new FileExists, $message);
}
/**
* Asserts that a file does not exist.
*
* @param string $filename
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFileNotExists(string $filename, string $message = ''): void
{
static::assertThat($filename, new LogicalNot(new FileExists), $message);
}
/**
* Asserts that a file exists and is readable.
*
* @param string $file
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFileIsReadable(string $file, string $message = ''): void
{
self::assertFileExists($file, $message);
self::assertIsReadable($file, $message);
}
/**
* Asserts that a file exists and is not readable.
*
* @param string $file
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFileNotIsReadable(string $file, string $message = ''): void
{
self::assertFileExists($file, $message);
self::assertNotIsReadable($file, $message);
}
/**
* Asserts that a file exists and is writable.
*
* @param string $file
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFileIsWritable(string $file, string $message = ''): void
{
self::assertFileExists($file, $message);
self::assertIsWritable($file, $message);
}
/**
* Asserts that a file exists and is not writable.
*
* @param string $file
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFileNotIsWritable(string $file, string $message = ''): void
{
self::assertFileExists($file, $message);
self::assertNotIsWritable($file, $message);
}
/**
* Asserts that a condition is true.
*
* @param mixed $condition
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertTrue($condition, string $message = ''): void
{
static::assertThat($condition, static::isTrue(), $message);
}
/**
* Asserts that a condition is not true.
*
* @param mixed $condition
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotTrue($condition, string $message = ''): void
{
static::assertThat($condition, static::logicalNot(static::isTrue()), $message);
}
/**
* Asserts that a condition is false.
*
* @param mixed $condition
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFalse($condition, string $message = ''): void
{
static::assertThat($condition, static::isFalse(), $message);
}
/**
* Asserts that a condition is not false.
*
* @param mixed $condition
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotFalse($condition, string $message = ''): void
{
static::assertThat($condition, static::logicalNot(static::isFalse()), $message);
}
/**
* Asserts that a variable is null.
*
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNull($actual, string $message = ''): void
{
static::assertThat($actual, static::isNull(), $message);
}
/**
* Asserts that a variable is not null.
*
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotNull($actual, string $message = ''): void
{
static::assertThat($actual, static::logicalNot(static::isNull()), $message);
}
/**
* Asserts that a variable is finite.
*
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertFinite($actual, string $message = ''): void
{
static::assertThat($actual, static::isFinite(), $message);
}
/**
* Asserts that a variable is infinite.
*
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertInfinite($actual, string $message = ''): void
{
static::assertThat($actual, static::isInfinite(), $message);
}
/**
* Asserts that a variable is nan.
*
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNan($actual, string $message = ''): void
{
static::assertThat($actual, static::isNan(), $message);
}
/**
* Asserts that a class has a specified attribute.
*
* @param string $attributeName
* @param string $className
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void
{
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(1, 'valid attribute name');
}
if (!\class_exists($className)) {
throw InvalidArgumentHelper::factory(2, 'class name', $className);
}
static::assertThat($className, new ClassHasAttribute($attributeName), $message);
}
/**
* Asserts that a class does not have a specified attribute.
*
* @param string $attributeName
* @param string $className
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void
{
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(1, 'valid attribute name');
}
if (!\class_exists($className)) {
throw InvalidArgumentHelper::factory(2, 'class name', $className);
}
static::assertThat(
$className,
new LogicalNot(
new ClassHasAttribute($attributeName)
),
$message
);
}
/**
* Asserts that a class has a specified static attribute.
*
* @param string $attributeName
* @param string $className
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void
{
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(1, 'valid attribute name');
}
if (!\class_exists($className)) {
throw InvalidArgumentHelper::factory(2, 'class name', $className);
}
static::assertThat(
$className,
new ClassHasStaticAttribute($attributeName),
$message
);
}
/**
* Asserts that a class does not have a specified static attribute.
*
* @param string $attributeName
* @param string $className
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void
{
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(1, 'valid attribute name');
}
if (!\class_exists($className)) {
throw InvalidArgumentHelper::factory(2, 'class name', $className);
}
static::assertThat(
$className,
new LogicalNot(
new ClassHasStaticAttribute($attributeName)
),
$message
);
}
/**
* Asserts that an object has a specified attribute.
*
* @param string $attributeName
* @param object $object
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void
{
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(1, 'valid attribute name');
}
if (!\is_object($object)) {
throw InvalidArgumentHelper::factory(2, 'object');
}
static::assertThat(
$object,
new ObjectHasAttribute($attributeName),
$message
);
}
/**
* Asserts that an object does not have a specified attribute.
*
* @param string $attributeName
* @param object $object
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void
{
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(1, 'valid attribute name');
}
if (!\is_object($object)) {
throw InvalidArgumentHelper::factory(2, 'object');
}
static::assertThat(
$object,
new LogicalNot(
new ObjectHasAttribute($attributeName)
),
$message
);
}
/**
* Asserts that two variables have the same type and value.
* Used on objects, it asserts that two variables reference
* the same object.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertSame($expected, $actual, string $message = ''): void
{
if (\is_bool($expected) && \is_bool($actual)) {
static::assertEquals($expected, $actual, $message);
}
static::assertThat(
$actual,
new IsIdentical($expected),
$message
);
}
/**
* Asserts that a variable and an attribute of an object have the same type
* and value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeSame($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
static::assertSame(
$expected,
static::readAttribute($actualClassOrObject, $actualAttributeName),
$message
);
}
/**
* Asserts that two variables do not have the same type and value.
* Used on objects, it asserts that two variables do not reference
* the same object.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotSame($expected, $actual, string $message = ''): void
{
if (\is_bool($expected) && \is_bool($actual)) {
static::assertNotEquals($expected, $actual, $message);
}
static::assertThat(
$actual,
new LogicalNot(
new IsIdentical($expected)
),
$message
);
}
/**
* Asserts that a variable and an attribute of an object do not have the
* same type and value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeNotSame($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
static::assertNotSame(
$expected,
static::readAttribute($actualClassOrObject, $actualAttributeName),
$message
);
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertInstanceOf(string $expected, $actual, string $message = ''): void
{
if (!\class_exists($expected) && !\interface_exists($expected)) {
throw InvalidArgumentHelper::factory(1, 'class or interface name');
}
static::assertThat(
$actual,
new IsInstanceOf($expected),
$message
);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param object|string $classOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeInstanceOf(string $expected, string $attributeName, $classOrObject, string $message = ''): void
{
static::assertInstanceOf(
$expected,
static::readAttribute($classOrObject, $attributeName),
$message
);
}
/**
* Asserts that a variable is not of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotInstanceOf(string $expected, $actual, string $message = ''): void
{
if (!\class_exists($expected) && !\interface_exists($expected)) {
throw InvalidArgumentHelper::factory(1, 'class or interface name');
}
static::assertThat(
$actual,
new LogicalNot(
new IsInstanceOf($expected)
),
$message
);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param object|string $classOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeNotInstanceOf(string $expected, string $attributeName, $classOrObject, string $message = ''): void
{
static::assertNotInstanceOf(
$expected,
static::readAttribute($classOrObject, $attributeName),
$message
);
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertInternalType(string $expected, $actual, string $message = ''): void
{
static::assertThat(
$actual,
new IsType($expected),
$message
);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param object|string $classOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeInternalType(string $expected, string $attributeName, $classOrObject, string $message = ''): void
{
static::assertInternalType(
$expected,
static::readAttribute($classOrObject, $attributeName),
$message
);
}
/**
* Asserts that a variable is not of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotInternalType(string $expected, $actual, string $message = ''): void
{
static::assertThat(
$actual,
new LogicalNot(
new IsType($expected)
),
$message
);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param object|string $classOrObject
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertAttributeNotInternalType(string $expected, string $attributeName, $classOrObject, string $message = ''): void
{
static::assertNotInternalType(
$expected,
static::readAttribute($classOrObject, $attributeName),
$message
);
}
/**
* Asserts that a string matches a given regular expression.
*
* @param string $pattern
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertRegExp(string $pattern, string $string, string $message = ''): void
{
static::assertThat($string, new RegularExpression($pattern), $message);
}
/**
* Asserts that a string does not match a given regular expression.
*
* @param string $pattern
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotRegExp(string $pattern, string $string, string $message = ''): void
{
static::assertThat(
$string,
new LogicalNot(
new RegularExpression($pattern)
),
$message
);
}
/**
* Assert that the size of two arrays (or `Countable` or `Traversable` objects)
* is the same.
*
* @param Countable|iterable $expected
* @param Countable|iterable $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertSameSize($expected, $actual, string $message = ''): void
{
if (!$expected instanceof Countable && !\is_iterable($expected)) {
throw InvalidArgumentHelper::factory(1, 'countable or iterable');
}
if (!$actual instanceof Countable && !\is_iterable($actual)) {
throw InvalidArgumentHelper::factory(2, 'countable or iterable');
}
static::assertThat(
$actual,
new SameSize($expected),
$message
);
}
/**
* Assert that the size of two arrays (or `Countable` or `Traversable` objects)
* is not the same.
*
* @param Countable|iterable $expected
* @param Countable|iterable $actual
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertNotSameSize($expected, $actual, string $message = ''): void
{
if (!$expected instanceof Countable && !\is_iterable($expected)) {
throw InvalidArgumentHelper::factory(1, 'countable or iterable');
}
if (!$actual instanceof Countable && !\is_iterable($actual)) {
throw InvalidArgumentHelper::factory(2, 'countable or iterable');
}
static::assertThat(
$actual,
new LogicalNot(
new SameSize($expected)
),
$message
);
}
/**
* Asserts that a string matches a given format string.
*
* @param string $format
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringMatchesFormat(string $format, string $string, string $message = ''): void
{
static::assertThat($string, new StringMatchesFormatDescription($format), $message);
}
/**
* Asserts that a string does not match a given format string.
*
* @param string $format
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void
{
static::assertThat(
$string,
new LogicalNot(
new StringMatchesFormatDescription($format)
),
$message
);
}
/**
* Asserts that a string matches a given format file.
*
* @param string $formatFile
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void
{
static::assertFileExists($formatFile, $message);
static::assertThat(
$string,
new StringMatchesFormatDescription(
\file_get_contents($formatFile)
),
$message
);
}
/**
* Asserts that a string does not match a given format string.
*
* @param string $formatFile
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void
{
static::assertFileExists($formatFile, $message);
static::assertThat(
$string,
new LogicalNot(
new StringMatchesFormatDescription(
\file_get_contents($formatFile)
)
),
$message
);
}
/**
* Asserts that a string starts with a given prefix.
*
* @param string $prefix
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringStartsWith(string $prefix, string $string, string $message = ''): void
{
static::assertThat($string, new StringStartsWith($prefix), $message);
}
/**
* Asserts that a string starts not with a given prefix.
*
* @param string $prefix
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringStartsNotWith($prefix, $string, string $message = ''): void
{
static::assertThat(
$string,
new LogicalNot(
new StringStartsWith($prefix)
),
$message
);
}
/**
* Asserts that a string ends with a given suffix.
*
* @param string $suffix
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringEndsWith(string $suffix, string $string, string $message = ''): void
{
static::assertThat($string, new StringEndsWith($suffix), $message);
}
/**
* Asserts that a string ends not with a given suffix.
*
* @param string $suffix
* @param string $string
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void
{
static::assertThat(
$string,
new LogicalNot(
new StringEndsWith($suffix)
),
$message
);
}
/**
* Asserts that two XML files are equal.
*
* @param string $expectedFile
* @param string $actualFile
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void
{
$expected = Xml::loadFile($expectedFile);
$actual = Xml::loadFile($actualFile);
static::assertEquals($expected, $actual, $message);
}
/**
* Asserts that two XML files are not equal.
*
* @param string $expectedFile
* @param string $actualFile
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void
{
$expected = Xml::loadFile($expectedFile);
$actual = Xml::loadFile($actualFile);
static::assertNotEquals($expected, $actual, $message);
}
/**
* Asserts that two XML documents are equal.
*
* @param string $expectedFile
* @param DOMDocument|string $actualXml
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void
{
$expected = Xml::loadFile($expectedFile);
$actual = Xml::load($actualXml);
static::assertEquals($expected, $actual, $message);
}
/**
* Asserts that two XML documents are not equal.
*
* @param string $expectedFile
* @param DOMDocument|string $actualXml
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void
{
$expected = Xml::loadFile($expectedFile);
$actual = Xml::load($actualXml);
static::assertNotEquals($expected, $actual, $message);
}
/**
* Asserts that two XML documents are equal.
*
* @param DOMDocument|string $expectedXml
* @param DOMDocument|string $actualXml
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void
{
$expected = Xml::load($expectedXml);
$actual = Xml::load($actualXml);
static::assertEquals($expected, $actual, $message);
}
/**
* Asserts that two XML documents are not equal.
*
* @param DOMDocument|string $expectedXml
* @param DOMDocument|string $actualXml
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void
{
$expected = Xml::load($expectedXml);
$actual = Xml::load($actualXml);
static::assertNotEquals($expected, $actual, $message);
}
/**
* Asserts that a hierarchy of DOMElements matches.
*
* @param DOMElement $expectedElement
* @param DOMElement $actualElement
* @param bool $checkAttributes
* @param string $message
*
* @throws AssertionFailedError
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void
{
$tmp = new DOMDocument;
$expectedElement = $tmp->importNode($expectedElement, true);
$tmp = new DOMDocument;
$actualElement = $tmp->importNode($actualElement, true);
unset($tmp);
static::assertEquals(
$expectedElement->tagName,
$actualElement->tagName,
$message
);
if ($checkAttributes) {
static::assertEquals(
$expectedElement->attributes->length,
$actualElement->attributes->length,
\sprintf(
'%s%sNumber of attributes on node "%s" does not match',
$message,
!empty($message) ? PHP_EOL : '',
$expectedElement->tagName
)
);
for ($i = 0; $i < $expectedElement->attributes->length; $i++) {
$expectedAttribute = $expectedElement->attributes->item($i);
$actualAttribute = $actualElement->attributes->getNamedItem(
$expectedAttribute->name
);
if (!$actualAttribute) {
static::fail(
\sprintf(
'%s%sCould not find attribute "%s" on node "%s"',
$message,
!empty($message) ? PHP_EOL : '',
$expectedAttribute->name,
$expectedElement->tagName
)
);
}
}
}
Xml::removeCharacterDataNodes($expectedElement);
Xml::removeCharacterDataNodes($actualElement);
static::assertEquals(
$expectedElement->childNodes->length,
$actualElement->childNodes->length,
\sprintf(
'%s%sNumber of child nodes of "%s" differs',
$message,
!empty($message) ? PHP_EOL : '',
$expectedElement->tagName
)
);
for ($i = 0; $i < $expectedElement->childNodes->length; $i++) {
static::assertEqualXMLStructure(
$expectedElement->childNodes->item($i),
$actualElement->childNodes->item($i),
$checkAttributes,
$message
);
}
}
/**
* Evaluates a PHPUnit\Framework\Constraint matcher object.
*
* @param mixed $value
* @param Constraint $constraint
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertThat($value, Constraint $constraint, string $message = ''): void
{
self::$count += \count($constraint);
$constraint->evaluate($value, $message);
}
/**
* Asserts that a string is a valid JSON string.
*
* @param string $actualJson
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertJson(string $actualJson, string $message = ''): void
{
static::assertThat($actualJson, static::isJson(), $message);
}
/**
* Asserts that two given JSON encoded objects or arrays are equal.
*
* @param string $expectedJson
* @param string $actualJson
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void
{
static::assertJson($expectedJson, $message);
static::assertJson($actualJson, $message);
static::assertThat($actualJson, new JsonMatches($expectedJson), $message);
}
/**
* Asserts that two given JSON encoded objects or arrays are not equal.
*
* @param string $expectedJson
* @param string $actualJson
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void
{
static::assertJson($expectedJson, $message);
static::assertJson($actualJson, $message);
static::assertThat(
$actualJson,
new LogicalNot(
new JsonMatches($expectedJson)
),
$message
);
}
/**
* Asserts that the generated JSON encoded object and the content of the given file are equal.
*
* @param string $expectedFile
* @param string $actualJson
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void
{
static::assertFileExists($expectedFile, $message);
$expectedJson = \file_get_contents($expectedFile);
static::assertJson($expectedJson, $message);
static::assertJson($actualJson, $message);
static::assertThat($actualJson, new JsonMatches($expectedJson), $message);
}
/**
* Asserts that the generated JSON encoded object and the content of the given file are not equal.
*
* @param string $expectedFile
* @param string $actualJson
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void
{
static::assertFileExists($expectedFile, $message);
$expectedJson = \file_get_contents($expectedFile);
static::assertJson($expectedJson, $message);
static::assertJson($actualJson, $message);
static::assertThat(
$actualJson,
new LogicalNot(
new JsonMatches($expectedJson)
),
$message
);
}
/**
* Asserts that two JSON files are equal.
*
* @param string $expectedFile
* @param string $actualFile
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void
{
static::assertFileExists($expectedFile, $message);
static::assertFileExists($actualFile, $message);
$actualJson = \file_get_contents($actualFile);
$expectedJson = \file_get_contents($expectedFile);
static::assertJson($expectedJson, $message);
static::assertJson($actualJson, $message);
$constraintExpected = new JsonMatches(
$expectedJson
);
$constraintActual = new JsonMatches($actualJson);
static::assertThat($expectedJson, $constraintActual, $message);
static::assertThat($actualJson, $constraintExpected, $message);
}
/**
* Asserts that two JSON files are not equal.
*
* @param string $expectedFile
* @param string $actualFile
* @param string $message
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void
{
static::assertFileExists($expectedFile, $message);
static::assertFileExists($actualFile, $message);
$actualJson = \file_get_contents($actualFile);
$expectedJson = \file_get_contents($expectedFile);
static::assertJson($expectedJson, $message);
static::assertJson($actualJson, $message);
$constraintExpected = new JsonMatches(
$expectedJson
);
$constraintActual = new JsonMatches($actualJson);
static::assertThat($expectedJson, new LogicalNot($constraintActual), $message);
static::assertThat($actualJson, new LogicalNot($constraintExpected), $message);
}
public static function logicalAnd(): LogicalAnd
{
$constraints = \func_get_args();
$constraint = new LogicalAnd;
$constraint->setConstraints($constraints);
return $constraint;
}
public static function logicalOr(): LogicalOr
{
$constraints = \func_get_args();
$constraint = new LogicalOr;
$constraint->setConstraints($constraints);
return $constraint;
}
public static function logicalNot(Constraint $constraint): LogicalNot
{
return new LogicalNot($constraint);
}
public static function logicalXor(): LogicalXor
{
$constraints = \func_get_args();
$constraint = new LogicalXor;
$constraint->setConstraints($constraints);
return $constraint;
}
public static function anything(): IsAnything
{
return new IsAnything;
}
public static function isTrue(): IsTrue
{
return new IsTrue;
}
public static function callback(callable $callback): Callback
{
return new Callback($callback);
}
public static function isFalse(): IsFalse
{
return new IsFalse;
}
public static function isJson(): IsJson
{
return new IsJson;
}
public static function isNull(): IsNull
{
return new IsNull;
}
public static function isFinite(): IsFinite
{
return new IsFinite;
}
public static function isInfinite(): IsInfinite
{
return new IsInfinite;
}
public static function isNan(): IsNan
{
return new IsNan;
}
public static function attribute(Constraint $constraint, string $attributeName): Attribute
{
return new Attribute($constraint, $attributeName);
}
public static function contains($value, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): TraversableContains
{
return new TraversableContains($value, $checkForObjectIdentity, $checkForNonObjectIdentity);
}
public static function containsOnly(string $type): TraversableContainsOnly
{
return new TraversableContainsOnly($type);
}
public static function containsOnlyInstancesOf(string $className): TraversableContainsOnly
{
return new TraversableContainsOnly($className, false);
}
/**
* @param int|string $key
*/
public static function arrayHasKey($key): ArrayHasKey
{
return new ArrayHasKey($key);
}
public static function equalTo($value, float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): IsEqual
{
return new IsEqual($value, $delta, $maxDepth, $canonicalize, $ignoreCase);
}
public static function attributeEqualTo(string $attributeName, $value, float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): Attribute
{
return static::attribute(
static::equalTo(
$value,
$delta,
$maxDepth,
$canonicalize,
$ignoreCase
),
$attributeName
);
}
public static function isEmpty(): IsEmpty
{
return new IsEmpty;
}
public static function isWritable(): IsWritable
{
return new IsWritable;
}
public static function isReadable(): IsReadable
{
return new IsReadable;
}
public static function directoryExists(): DirectoryExists
{
return new DirectoryExists;
}
public static function fileExists(): FileExists
{
return new FileExists;
}
public static function greaterThan($value): GreaterThan
{
return new GreaterThan($value);
}
public static function greaterThanOrEqual($value): LogicalOr
{
return static::logicalOr(
new IsEqual($value),
new GreaterThan($value)
);
}
public static function classHasAttribute(string $attributeName): ClassHasAttribute
{
return new ClassHasAttribute($attributeName);
}
public static function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute
{
return new ClassHasStaticAttribute($attributeName);
}
public static function objectHasAttribute($attributeName): ObjectHasAttribute
{
return new ObjectHasAttribute($attributeName);
}
public static function identicalTo($value): IsIdentical
{
return new IsIdentical($value);
}
public static function isInstanceOf(string $className): IsInstanceOf
{
return new IsInstanceOf($className);
}
public static function isType(string $type): IsType
{
return new IsType($type);
}
public static function lessThan($value): LessThan
{
return new LessThan($value);
}
public static function lessThanOrEqual($value): LogicalOr
{
return static::logicalOr(
new IsEqual($value),
new LessThan($value)
);
}
public static function matchesRegularExpression(string $pattern): RegularExpression
{
return new RegularExpression($pattern);
}
public static function matches(string $string): StringMatchesFormatDescription
{
return new StringMatchesFormatDescription($string);
}
public static function stringStartsWith($prefix): StringStartsWith
{
return new StringStartsWith($prefix);
}
public static function stringContains(string $string, bool $case = true): StringContains
{
return new StringContains($string, $case);
}
public static function stringEndsWith(string $suffix): StringEndsWith
{
return new StringEndsWith($suffix);
}
public static function countOf(int $count): Count
{
return new Count($count);
}
/**
* Fails a test with the given message.
*
* @param string $message
*
* @throws AssertionFailedError
*/
public static function fail(string $message = ''): void
{
self::$count++;
throw new AssertionFailedError($message);
}
/**
* Returns the value of an attribute of a class or an object.
* This also works for attributes that are declared protected or private.
*
* @param object|string $classOrObject
* @param string $attributeName
*
* @throws Exception
*
* @return mixed
*/
public static function readAttribute($classOrObject, string $attributeName)
{
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(2, 'valid attribute name');
}
if (\is_string($classOrObject)) {
if (!\class_exists($classOrObject)) {
throw InvalidArgumentHelper::factory(
1,
'class name'
);
}
return static::getStaticAttribute(
$classOrObject,
$attributeName
);
}
if (\is_object($classOrObject)) {
return static::getObjectAttribute(
$classOrObject,
$attributeName
);
}
throw InvalidArgumentHelper::factory(
1,
'class name or object'
);
}
/**
* Returns the value of a static attribute.
* This also works for attributes that are declared protected or private.
*
* @param string $className
* @param string $attributeName
*
* @throws Exception
* @throws ReflectionException
*
* @return mixed
*/
public static function getStaticAttribute(string $className, string $attributeName)
{
if (!\class_exists($className)) {
throw InvalidArgumentHelper::factory(1, 'class name');
}
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(2, 'valid attribute name');
}
$class = new ReflectionClass($className);
while ($class) {
$attributes = $class->getStaticProperties();
if (\array_key_exists($attributeName, $attributes)) {
return $attributes[$attributeName];
}
$class = $class->getParentClass();
}
throw new Exception(
\sprintf(
'Attribute "%s" not found in class.',
$attributeName
)
);
}
/**
* Returns the value of an object's attribute.
* This also works for attributes that are declared protected or private.
*
* @param object $object
* @param string $attributeName
*
* @throws Exception
*
* @return mixed
*/
public static function getObjectAttribute($object, string $attributeName)
{
if (!\is_object($object)) {
throw InvalidArgumentHelper::factory(1, 'object');
}
if (!self::isValidAttributeName($attributeName)) {
throw InvalidArgumentHelper::factory(2, 'valid attribute name');
}
try {
$attribute = new ReflectionProperty($object, $attributeName);
} catch (ReflectionException $e) {
$reflector = new ReflectionObject($object);
while ($reflector = $reflector->getParentClass()) {
try {
$attribute = $reflector->getProperty($attributeName);
break;
} catch (ReflectionException $e) {
}
}
}
if (isset($attribute)) {
if (!$attribute || $attribute->isPublic()) {
return $object->$attributeName;
}
$attribute->setAccessible(true);
$value = $attribute->getValue($object);
$attribute->setAccessible(false);
return $value;
}
throw new Exception(
\sprintf(
'Attribute "%s" not found in object.',
$attributeName
)
);
}
/**
* Mark the test as incomplete.
*
* @param string $message
*
* @throws IncompleteTestError
*/
public static function markTestIncomplete(string $message = ''): void
{
throw new IncompleteTestError($message);
}
/**
* Mark the test as skipped.
*
* @param string $message
*
* @throws SkippedTestError
*/
public static function markTestSkipped(string $message = ''): void
{
throw new SkippedTestError($message);
}
/**
* Return the current assertion count.
*/
public static function getCount(): int
{
return self::$count;
}
/**
* Reset the assertion counter.
*/
public static function resetCount(): void
{
self::$count = 0;
}
private static function isValidAttributeName(string $attributeName): bool
{
return \preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName);
}
}
src/Framework/Warning.php 0000666 00000001016 13436756106 0011423 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
/**
* Thrown when there is a warning.
*/
class Warning extends Exception implements SelfDescribing
{
/**
* Wrapper for getMessage() which is declared as final.
*/
public function toString(): string
{
return $this->getMessage();
}
}
src/Framework/Exception.php 0000666 00000004413 13436756106 0011760 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
use PHPUnit\Util\Filter;
/**
* Base class for all PHPUnit Framework exceptions.
*
* Ensures that exceptions thrown during a test run do not leave stray
* references behind.
*
* Every Exception contains a stack trace. Each stack frame contains the 'args'
* of the called function. The function arguments can contain references to
* instantiated objects. The references prevent the objects from being
* destructed (until test results are eventually printed), so memory cannot be
* freed up.
*
* With enabled process isolation, test results are serialized in the child
* process and unserialized in the parent process. The stack trace of Exceptions
* may contain objects that cannot be serialized or unserialized (e.g., PDO
* connections). Unserializing user-space objects from the child process into
* the parent would break the intended encapsulation of process isolation.
*
* @see http://fabien.potencier.org/article/9/php-serialization-stack-traces-and-exceptions
*/
class Exception extends \RuntimeException implements \PHPUnit\Exception
{
/**
* @var array
*/
protected $serializableTrace;
public function __construct($message = '', $code = 0, \Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->serializableTrace = $this->getTrace();
foreach ($this->serializableTrace as $i => $call) {
unset($this->serializableTrace[$i]['args']);
}
}
/**
* @throws \InvalidArgumentException
*/
public function __toString(): string
{
$string = TestFailure::exceptionToString($this);
if ($trace = Filter::getFilteredStacktrace($this)) {
$string .= PHP_EOL . $trace;
}
return $string;
}
public function __sleep(): array
{
return \array_keys(\get_object_vars($this));
}
/**
* Returns the serializable trace (without 'args').
*/
public function getSerializableTrace(): array
{
return $this->serializableTrace;
}
}
src/Framework/MissingCoversAnnotationException.php 0000666 00000000505 13436756106 0016525 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
class MissingCoversAnnotationException extends RiskyTestError
{
}
src/Framework/RiskyTestError.php 0000666 00000000515 13436756106 0012774 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
class RiskyTestError extends AssertionFailedError implements RiskyTest
{
}
src/Framework/AssertionFailedError.php 0000666 00000001035 13436756106 0014105 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
/**
* Thrown when an assertion failed.
*/
class AssertionFailedError extends Exception implements SelfDescribing
{
/**
* Wrapper for getMessage() which is declared as final.
*/
public function toString(): string
{
return $this->getMessage();
}
}
src/Framework/TestSuiteIterator.php 0000666 00000003361 13436756106 0013466 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
use RecursiveIterator;
/**
* Iterator for test suites.
*/
final class TestSuiteIterator implements RecursiveIterator
{
/**
* @var int
*/
private $position;
/**
* @var Test[]
*/
private $tests;
public function __construct(TestSuite $testSuite)
{
$this->tests = $testSuite->tests();
}
/**
* Rewinds the Iterator to the first element.
*/
public function rewind(): void
{
$this->position = 0;
}
/**
* Checks if there is a current element after calls to rewind() or next().
*/
public function valid(): bool
{
return $this->position < \count($this->tests);
}
/**
* Returns the key of the current element.
*/
public function key(): int
{
return $this->position;
}
/**
* Returns the current element.
*/
public function current(): Test
{
return $this->valid() ? $this->tests[$this->position] : null;
}
/**
* Moves forward to next element.
*/
public function next(): void
{
$this->position++;
}
/**
* Returns the sub iterator for the current element.
*/
public function getChildren(): self
{
return new self(
$this->tests[$this->position]
);
}
/**
* Checks whether the current element has children.
*/
public function hasChildren(): bool
{
return $this->tests[$this->position] instanceof TestSuite;
}
}
src/Framework/ExpectationFailedException.php 0000666 00000002052 13436756106 0015266 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
use SebastianBergmann\Comparator\ComparisonFailure;
/**
* Exception for expectations which failed their check.
*
* The exception contains the error message and optionally a
* SebastianBergmann\Comparator\ComparisonFailure which is used to
* generate diff output of the failed expectations.
*/
class ExpectationFailedException extends AssertionFailedError
{
/**
* @var ComparisonFailure
*/
protected $comparisonFailure;
public function __construct(string $message, ComparisonFailure $comparisonFailure = null, \Exception $previous = null)
{
$this->comparisonFailure = $comparisonFailure;
parent::__construct($message, 0, $previous);
}
public function getComparisonFailure(): ?ComparisonFailure
{
return $this->comparisonFailure;
}
}
src/Framework/IncompleteTest.php 0000666 00000000666 13436756106 0012767 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
/**
* A marker interface for marking any exception/error as result of an unit
* test as incomplete implementation or currently not implemented.
*/
interface IncompleteTest
{
}
src/Framework/CodeCoverageException.php 0000666 00000000465 13436756106 0014232 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
class CodeCoverageException extends Exception
{
}
src/Framework/ExceptionWrapper.php 0000666 00000006214 13436756106 0013322 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
use PHPUnit\Util\Filter;
use Throwable;
/**
* Wraps Exceptions thrown by code under test.
*
* Re-instantiates Exceptions thrown by user-space code to retain their original
* class names, properties, and stack traces (but without arguments).
*
* Unlike PHPUnit\Framework_\Exception, the complete stack of previous Exceptions
* is processed.
*/
class ExceptionWrapper extends Exception
{
/**
* @var string
*/
protected $className;
/**
* @var null|ExceptionWrapper
*/
protected $previous;
/**
* @param Throwable $t
*/
public function __construct(Throwable $t)
{
// PDOException::getCode() is a string.
// @see https://php.net/manual/en/class.pdoexception.php#95812
parent::__construct($t->getMessage(), (int) $t->getCode());
$this->setOriginalException($t);
}
/**
* @throws \InvalidArgumentException
*/
public function __toString(): string
{
$string = TestFailure::exceptionToString($this);
if ($trace = Filter::getFilteredStacktrace($this)) {
$string .= PHP_EOL . $trace;
}
if ($this->previous) {
$string .= "\nCaused by\n" . $this->previous;
}
return $string;
}
public function getClassName(): string
{
return $this->className;
}
public function getPreviousWrapped(): ?self
{
return $this->previous;
}
/**
* @param string $className
*/
public function setClassName(string $className): void
{
$this->className = $className;
}
public function setOriginalException(\Throwable $t): void
{
$this->originalException($t);
$this->className = \get_class($t);
$this->file = $t->getFile();
$this->line = $t->getLine();
$this->serializableTrace = $t->getTrace();
foreach ($this->serializableTrace as $i => $call) {
unset($this->serializableTrace[$i]['args']);
}
if ($t->getPrevious()) {
$this->previous = new self($t->getPrevious());
}
}
public function getOriginalException(): ?Throwable
{
return $this->originalException();
}
/**
* Method to contain static originalException to exclude it from stacktrace to prevent the stacktrace contents,
* which can be quite big, from being garbage-collected, thus blocking memory until shutdown.
* Approach works both for var_dump() and var_export() and print_r()
*
* @param null|Throwable $exceptionToStore
*/
private function originalException(Throwable $exceptionToStore = null): ?Throwable
{
static $originalExceptions;
$instanceId = \spl_object_hash($this);
if ($exceptionToStore) {
$originalExceptions[$instanceId] = $exceptionToStore;
}
return $originalExceptions[$instanceId] ?? null;
}
}
src/Framework/TestListenerDefaultImplementation.php 0000666 00000002156 13436756106 0016664 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
trait TestListenerDefaultImplementation
{
public function addError(Test $test, \Throwable $t, float $time): void
{
}
public function addWarning(Test $test, Warning $e, float $time): void
{
}
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
}
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
}
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
}
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
}
public function startTestSuite(TestSuite $suite): void
{
}
public function endTestSuite(TestSuite $suite): void
{
}
public function startTest(Test $test): void
{
}
public function endTest(Test $test, float $time): void
{
}
}
src/Framework/WarningTestCase.php 0000666 00000002342 13436756106 0013062 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
/**
* A warning.
*/
class WarningTestCase extends TestCase
{
/**
* @var string
*/
protected $message = '';
/**
* @var bool
*/
protected $backupGlobals = false;
/**
* @var bool
*/
protected $backupStaticAttributes = false;
/**
* @var bool
*/
protected $runTestInSeparateProcess = false;
/**
* @var bool
*/
protected $useErrorHandler = false;
/**
* @param string $message
*/
public function __construct($message = '')
{
$this->message = $message;
parent::__construct('Warning');
}
public function getMessage(): string
{
return $this->message;
}
/**
* Returns a string representation of the test case.
*/
public function toString(): string
{
return 'Warning';
}
/**
* @throws Exception
*/
protected function runTest(): void
{
throw new Warning($this->message);
}
}
src/Framework/SyntheticError.php 0000666 00000002336 13436756106 0013010 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
/**
* Creates a synthetic failed assertion.
*/
class SyntheticError extends AssertionFailedError
{
/**
* The synthetic file.
*
* @var string
*/
protected $syntheticFile = '';
/**
* The synthetic line number.
*
* @var int
*/
protected $syntheticLine = 0;
/**
* The synthetic trace.
*
* @var array
*/
protected $syntheticTrace = [];
public function __construct(string $message, int $code, string $file, int $line, array $trace)
{
parent::__construct($message, $code);
$this->syntheticFile = $file;
$this->syntheticLine = $line;
$this->syntheticTrace = $trace;
}
public function getSyntheticFile(): string
{
return $this->syntheticFile;
}
public function getSyntheticLine(): int
{
return $this->syntheticLine;
}
public function getSyntheticTrace(): array
{
return $this->syntheticTrace;
}
}
src/Framework/SelfDescribing.php 0000666 00000000730 13436756106 0012703 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
/**
* Interface for classes that can return a description of itself.
*/
interface SelfDescribing
{
/**
* Returns a string representation of the object.
*/
public function toString(): string;
}
src/Framework/RiskyTest.php 0000666 00000000433 13436756106 0011761 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
interface RiskyTest
{
}
src/Framework/SkippedTest.php 0000666 00000000435 13436756106 0012261 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
interface SkippedTest
{
}
src/Framework/Assert/Functions.php 0000666 00000155012 13436756106 0013235 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Constraint\ArrayHasKey;
use PHPUnit\Framework\Constraint\Attribute;
use PHPUnit\Framework\Constraint\Callback;
use PHPUnit\Framework\Constraint\ClassHasAttribute;
use PHPUnit\Framework\Constraint\ClassHasStaticAttribute;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\Constraint\Count;
use PHPUnit\Framework\Constraint\DirectoryExists;
use PHPUnit\Framework\Constraint\FileExists;
use PHPUnit\Framework\Constraint\GreaterThan;
use PHPUnit\Framework\Constraint\IsAnything;
use PHPUnit\Framework\Constraint\IsEmpty;
use PHPUnit\Framework\Constraint\IsEqual;
use PHPUnit\Framework\Constraint\IsFalse;
use PHPUnit\Framework\Constraint\IsFinite;
use PHPUnit\Framework\Constraint\IsIdentical;
use PHPUnit\Framework\Constraint\IsInfinite;
use PHPUnit\Framework\Constraint\IsInstanceOf;
use PHPUnit\Framework\Constraint\IsJson;
use PHPUnit\Framework\Constraint\IsNan;
use PHPUnit\Framework\Constraint\IsNull;
use PHPUnit\Framework\Constraint\IsReadable;
use PHPUnit\Framework\Constraint\IsTrue;
use PHPUnit\Framework\Constraint\IsType;
use PHPUnit\Framework\Constraint\IsWritable;
use PHPUnit\Framework\Constraint\LessThan;
use PHPUnit\Framework\Constraint\LogicalAnd;
use PHPUnit\Framework\Constraint\LogicalNot;
use PHPUnit\Framework\Constraint\LogicalOr;
use PHPUnit\Framework\Constraint\LogicalXor;
use PHPUnit\Framework\Constraint\ObjectHasAttribute;
use PHPUnit\Framework\Constraint\RegularExpression;
use PHPUnit\Framework\Constraint\StringContains;
use PHPUnit\Framework\Constraint\StringEndsWith;
use PHPUnit\Framework\Constraint\StringMatchesFormatDescription;
use PHPUnit\Framework\Constraint\StringStartsWith;
use PHPUnit\Framework\Constraint\TraversableContains;
use PHPUnit\Framework\Constraint\TraversableContainsOnly;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\MockObject\Matcher\AnyInvokedCount as AnyInvokedCountMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedAtIndex as InvokedAtIndexMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedAtLeastCount as InvokedAtLeastCountMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedAtMostCount as InvokedAtMostCountMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedCount as InvokedCountMatcher;
use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub;
use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub;
use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub;
use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub;
use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub;
use PHPUnit\Framework\MockObject\Stub\ReturnStub;
use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub;
/**
* Asserts that an array has a specified key.
*
* @param int|string $key
* @param array|ArrayAccess $array
* @param string $message
*
* @throws Exception
*/
function assertArrayHasKey($key, $array, string $message = ''): void
{
Assert::assertArrayHasKey(...\func_get_args());
}
/**
* Asserts that an array has a specified subset.
*
* @param array|ArrayAccess $subset
* @param array|ArrayAccess $array
* @param bool $strict Check for object identity
* @param string $message
*
* @throws Exception
*/
function assertArraySubset($subset, $array, bool $strict = false, string $message = ''): void
{
Assert::assertArraySubset(...\func_get_args());
}
/**
* Asserts that an array does not have a specified key.
*
* @param int|string $key
* @param array|ArrayAccess $array
* @param string $message
*
* @throws Exception
*/
function assertArrayNotHasKey($key, $array, string $message = ''): void
{
Assert::assertArrayNotHasKey(...\func_get_args());
}
/**
* Asserts that a haystack contains a needle.
*
* @param mixed $needle
* @param mixed $haystack
* @param string $message
* @param bool $ignoreCase
* @param bool $checkForObjectIdentity
* @param bool $checkForNonObjectIdentity
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \PHPUnit\Framework\Exception
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertContains($needle, $haystack, string $message = '', bool $ignoreCase = false, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): void
{
Assert::assertContains(...\func_get_args());
}
/**
* Asserts that a haystack that is stored in a static attribute of a class
* or an attribute of an object contains a needle.
*
* @param mixed $needle
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
* @param bool $ignoreCase
* @param bool $checkForObjectIdentity
* @param bool $checkForNonObjectIdentity
*
* @throws Exception
*/
function assertAttributeContains($needle, string $haystackAttributeName, $haystackClassOrObject, string $message = '', bool $ignoreCase = false, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): void
{
Assert::assertAttributeContains(...\func_get_args());
}
/**
* Asserts that a haystack does not contain a needle.
*
* @param mixed $needle
* @param mixed $haystack
* @param string $message
* @param bool $ignoreCase
* @param bool $checkForObjectIdentity
* @param bool $checkForNonObjectIdentity
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \PHPUnit\Framework\Exception
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotContains($needle, $haystack, string $message = '', bool $ignoreCase = false, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): void
{
Assert::assertNotContains(...\func_get_args());
}
/**
* Asserts that a haystack that is stored in a static attribute of a class
* or an attribute of an object does not contain a needle.
*
* @param mixed $needle
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
* @param bool $ignoreCase
* @param bool $checkForObjectIdentity
* @param bool $checkForNonObjectIdentity
*
* @throws Exception
*/
function assertAttributeNotContains($needle, string $haystackAttributeName, $haystackClassOrObject, string $message = '', bool $ignoreCase = false, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): void
{
Assert::assertAttributeNotContains(...\func_get_args());
}
/**
* Asserts that a haystack contains only values of a given type.
*
* @param string $type
* @param iterable $haystack
* @param null|bool $isNativeType
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void
{
Assert::assertContainsOnly(...\func_get_args());
}
/**
* Asserts that a haystack contains only instances of a given class name.
*
* @param string $className
* @param iterable $haystack
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void
{
Assert::assertContainsOnlyInstancesOf(...\func_get_args());
}
/**
* Asserts that a haystack that is stored in a static attribute of a class
* or an attribute of an object contains only values of a given type.
*
* @param string $type
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param bool $isNativeType
* @param string $message
*
* @throws Exception
*/
function assertAttributeContainsOnly(string $type, string $haystackAttributeName, $haystackClassOrObject, ?bool $isNativeType = null, string $message = ''): void
{
Assert::assertAttributeContainsOnly(...\func_get_args());
}
/**
* Asserts that a haystack does not contain only values of a given type.
*
* @param string $type
* @param iterable $haystack
* @param null|bool $isNativeType
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void
{
Assert::assertNotContainsOnly(...\func_get_args());
}
/**
* Asserts that a haystack that is stored in a static attribute of a class
* or an attribute of an object does not contain only values of a given
* type.
*
* @param string $type
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param bool $isNativeType
* @param string $message
*
* @throws Exception
*/
function assertAttributeNotContainsOnly(string $type, string $haystackAttributeName, $haystackClassOrObject, ?bool $isNativeType = null, string $message = ''): void
{
Assert::assertAttributeNotContainsOnly(...\func_get_args());
}
/**
* Asserts the number of elements of an array, Countable or Traversable.
*
* @param int $expectedCount
* @param Countable|iterable $haystack
* @param string $message
*
* @throws Exception
*/
function assertCount(int $expectedCount, $haystack, string $message = ''): void
{
Assert::assertCount(...\func_get_args());
}
/**
* Asserts the number of elements of an array, Countable or Traversable
* that is stored in an attribute.
*
* @param int $expectedCount
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeCount(int $expectedCount, string $haystackAttributeName, $haystackClassOrObject, string $message = ''): void
{
Assert::assertAttributeCount(...\func_get_args());
}
/**
* Asserts the number of elements of an array, Countable or Traversable.
*
* @param int $expectedCount
* @param Countable|iterable $haystack
* @param string $message
*
* @throws Exception
*/
function assertNotCount(int $expectedCount, $haystack, string $message = ''): void
{
Assert::assertNotCount(...\func_get_args());
}
/**
* Asserts the number of elements of an array, Countable or Traversable
* that is stored in an attribute.
*
* @param int $expectedCount
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeNotCount(int $expectedCount, string $haystackAttributeName, $haystackClassOrObject, string $message = ''): void
{
Assert::assertAttributeNotCount(...\func_get_args());
}
/**
* Asserts that two variables are equal.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
* @param float $delta
* @param int $maxDepth
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertEquals($expected, $actual, string $message = '', float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void
{
Assert::assertEquals(...\func_get_args());
}
/**
* Asserts that a variable is equal to an attribute of an object.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
* @param float $delta
* @param int $maxDepth
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws Exception
*/
function assertAttributeEquals($expected, string $actualAttributeName, $actualClassOrObject, string $message = '', float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void
{
Assert::assertAttributeEquals(...\func_get_args());
}
/**
* Asserts that two variables are not equal.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
* @param float $delta
* @param int $maxDepth
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotEquals($expected, $actual, string $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false): void
{
Assert::assertNotEquals(...\func_get_args());
}
/**
* Asserts that a variable is not equal to an attribute of an object.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
* @param float $delta
* @param int $maxDepth
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws Exception
*/
function assertAttributeNotEquals($expected, string $actualAttributeName, $actualClassOrObject, string $message = '', float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void
{
Assert::assertAttributeNotEquals(...\func_get_args());
}
/**
* Asserts that a variable is empty.
*
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertEmpty($actual, string $message = ''): void
{
Assert::assertEmpty(...\func_get_args());
}
/**
* Asserts that a static attribute of a class or an attribute of an object
* is empty.
*
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeEmpty(string $haystackAttributeName, $haystackClassOrObject, string $message = ''): void
{
Assert::assertAttributeEmpty(...\func_get_args());
}
/**
* Asserts that a variable is not empty.
*
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotEmpty($actual, string $message = ''): void
{
Assert::assertNotEmpty(...\func_get_args());
}
/**
* Asserts that a static attribute of a class or an attribute of an object
* is not empty.
*
* @param string $haystackAttributeName
* @param object|string $haystackClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeNotEmpty(string $haystackAttributeName, $haystackClassOrObject, string $message = ''): void
{
Assert::assertAttributeNotEmpty(...\func_get_args());
}
/**
* Asserts that a value is greater than another value.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertGreaterThan($expected, $actual, string $message = ''): void
{
Assert::assertGreaterThan(...\func_get_args());
}
/**
* Asserts that an attribute is greater than another value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeGreaterThan($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
Assert::assertAttributeGreaterThan(...\func_get_args());
}
/**
* Asserts that a value is greater than or equal to another value.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void
{
Assert::assertGreaterThanOrEqual(...\func_get_args());
}
/**
* Asserts that an attribute is greater than or equal to another value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeGreaterThanOrEqual($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
Assert::assertAttributeGreaterThanOrEqual(...\func_get_args());
}
/**
* Asserts that a value is smaller than another value.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertLessThan($expected, $actual, string $message = ''): void
{
Assert::assertLessThan(...\func_get_args());
}
/**
* Asserts that an attribute is smaller than another value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeLessThan($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
Assert::assertAttributeLessThan(...\func_get_args());
}
/**
* Asserts that a value is smaller than or equal to another value.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertLessThanOrEqual($expected, $actual, string $message = ''): void
{
Assert::assertLessThanOrEqual(...\func_get_args());
}
/**
* Asserts that an attribute is smaller than or equal to another value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeLessThanOrEqual($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
Assert::assertAttributeLessThanOrEqual(...\func_get_args());
}
/**
* Asserts that the contents of one file is equal to the contents of another
* file.
*
* @param string $expected
* @param string $actual
* @param string $message
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws Exception
*/
function assertFileEquals(string $expected, string $actual, string $message = '', bool $canonicalize = false, bool $ignoreCase = false): void
{
Assert::assertFileEquals(...\func_get_args());
}
/**
* Asserts that the contents of one file is not equal to the contents of
* another file.
*
* @param string $expected
* @param string $actual
* @param string $message
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws Exception
*/
function assertFileNotEquals(string $expected, string $actual, string $message = '', bool $canonicalize = false, bool $ignoreCase = false): void
{
Assert::assertFileNotEquals(...\func_get_args());
}
/**
* Asserts that the contents of a string is equal
* to the contents of a file.
*
* @param string $expectedFile
* @param string $actualString
* @param string $message
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws Exception
*/
function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = '', bool $canonicalize = false, bool $ignoreCase = false): void
{
Assert::assertStringEqualsFile(...\func_get_args());
}
/**
* Asserts that the contents of a string is not equal
* to the contents of a file.
*
* @param string $expectedFile
* @param string $actualString
* @param string $message
* @param bool $canonicalize
* @param bool $ignoreCase
*
* @throws Exception
*/
function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = '', bool $canonicalize = false, bool $ignoreCase = false): void
{
Assert::assertStringNotEqualsFile(...\func_get_args());
}
/**
* Asserts that a file/dir is readable.
*
* @param string $filename
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertIsReadable(string $filename, string $message = ''): void
{
Assert::assertIsReadable(...\func_get_args());
}
/**
* Asserts that a file/dir exists and is not readable.
*
* @param string $filename
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotIsReadable(string $filename, string $message = ''): void
{
Assert::assertNotIsReadable(...\func_get_args());
}
/**
* Asserts that a file/dir exists and is writable.
*
* @param string $filename
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertIsWritable(string $filename, string $message = ''): void
{
Assert::assertIsWritable(...\func_get_args());
}
/**
* Asserts that a file/dir exists and is not writable.
*
* @param string $filename
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotIsWritable(string $filename, string $message = ''): void
{
Assert::assertNotIsWritable(...\func_get_args());
}
/**
* Asserts that a directory exists.
*
* @param string $directory
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertDirectoryExists(string $directory, string $message = ''): void
{
Assert::assertDirectoryExists(...\func_get_args());
}
/**
* Asserts that a directory does not exist.
*
* @param string $directory
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertDirectoryNotExists(string $directory, string $message = ''): void
{
Assert::assertDirectoryNotExists(...\func_get_args());
}
/**
* Asserts that a directory exists and is readable.
*
* @param string $directory
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertDirectoryIsReadable(string $directory, string $message = ''): void
{
Assert::assertDirectoryIsReadable(...\func_get_args());
}
/**
* Asserts that a directory exists and is not readable.
*
* @param string $directory
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertDirectoryNotIsReadable(string $directory, string $message = ''): void
{
Assert::assertDirectoryNotIsReadable(...\func_get_args());
}
/**
* Asserts that a directory exists and is writable.
*
* @param string $directory
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertDirectoryIsWritable(string $directory, string $message = ''): void
{
Assert::assertDirectoryIsWritable(...\func_get_args());
}
/**
* Asserts that a directory exists and is not writable.
*
* @param string $directory
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertDirectoryNotIsWritable(string $directory, string $message = ''): void
{
Assert::assertDirectoryNotIsWritable(...\func_get_args());
}
/**
* Asserts that a file exists.
*
* @param string $filename
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertFileExists(string $filename, string $message = ''): void
{
Assert::assertFileExists(...\func_get_args());
}
/**
* Asserts that a file does not exist.
*
* @param string $filename
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertFileNotExists(string $filename, string $message = ''): void
{
Assert::assertFileNotExists(...\func_get_args());
}
/**
* Asserts that a file exists and is readable.
*
* @param string $file
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertFileIsReadable(string $file, string $message = ''): void
{
Assert::assertFileIsReadable(...\func_get_args());
}
/**
* Asserts that a file exists and is not readable.
*
* @param string $file
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertFileNotIsReadable(string $file, string $message = ''): void
{
Assert::assertFileNotIsReadable(...\func_get_args());
}
/**
* Asserts that a file exists and is writable.
*
* @param string $file
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertFileIsWritable(string $file, string $message = ''): void
{
Assert::assertFileIsWritable(...\func_get_args());
}
/**
* Asserts that a file exists and is not writable.
*
* @param string $file
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertFileNotIsWritable(string $file, string $message = ''): void
{
Assert::assertFileNotIsWritable(...\func_get_args());
}
/**
* Asserts that a condition is true.
*
* @param mixed $condition
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertTrue($condition, string $message = ''): void
{
Assert::assertTrue(...\func_get_args());
}
/**
* Asserts that a condition is not true.
*
* @param mixed $condition
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotTrue($condition, string $message = ''): void
{
Assert::assertNotTrue(...\func_get_args());
}
/**
* Asserts that a condition is false.
*
* @param mixed $condition
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertFalse($condition, string $message = ''): void
{
Assert::assertFalse(...\func_get_args());
}
/**
* Asserts that a condition is not false.
*
* @param mixed $condition
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotFalse($condition, string $message = ''): void
{
Assert::assertNotFalse(...\func_get_args());
}
/**
* Asserts that a variable is null.
*
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNull($actual, string $message = ''): void
{
Assert::assertNull(...\func_get_args());
}
/**
* Asserts that a variable is not null.
*
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotNull($actual, string $message = ''): void
{
Assert::assertNotNull(...\func_get_args());
}
/**
* Asserts that a variable is finite.
*
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertFinite($actual, string $message = ''): void
{
Assert::assertFinite(...\func_get_args());
}
/**
* Asserts that a variable is infinite.
*
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertInfinite($actual, string $message = ''): void
{
Assert::assertInfinite(...\func_get_args());
}
/**
* Asserts that a variable is nan.
*
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNan($actual, string $message = ''): void
{
Assert::assertNan(...\func_get_args());
}
/**
* Asserts that a class has a specified attribute.
*
* @param string $attributeName
* @param string $className
* @param string $message
*
* @throws Exception
*/
function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void
{
Assert::assertClassHasAttribute(...\func_get_args());
}
/**
* Asserts that a class does not have a specified attribute.
*
* @param string $attributeName
* @param string $className
* @param string $message
*
* @throws Exception
*/
function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void
{
Assert::assertClassNotHasAttribute(...\func_get_args());
}
/**
* Asserts that a class has a specified static attribute.
*
* @param string $attributeName
* @param string $className
* @param string $message
*
* @throws Exception
*/
function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void
{
Assert::assertClassHasStaticAttribute(...\func_get_args());
}
/**
* Asserts that a class does not have a specified static attribute.
*
* @param string $attributeName
* @param string $className
* @param string $message
*
* @throws Exception
*/
function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void
{
Assert::assertClassNotHasStaticAttribute(...\func_get_args());
}
/**
* Asserts that an object has a specified attribute.
*
* @param string $attributeName
* @param object $object
* @param string $message
*
* @throws Exception
*/
function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void
{
Assert::assertObjectHasAttribute(...\func_get_args());
}
/**
* Asserts that an object does not have a specified attribute.
*
* @param string $attributeName
* @param object $object
* @param string $message
*
* @throws Exception
*/
function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void
{
Assert::assertObjectNotHasAttribute(...\func_get_args());
}
/**
* Asserts that two variables have the same type and value.
* Used on objects, it asserts that two variables reference
* the same object.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertSame($expected, $actual, string $message = ''): void
{
Assert::assertSame(...\func_get_args());
}
/**
* Asserts that a variable and an attribute of an object have the same type
* and value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeSame($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
Assert::assertAttributeSame(...\func_get_args());
}
/**
* Asserts that two variables do not have the same type and value.
* Used on objects, it asserts that two variables do not reference
* the same object.
*
* @param mixed $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotSame($expected, $actual, string $message = ''): void
{
Assert::assertNotSame(...\func_get_args());
}
/**
* Asserts that a variable and an attribute of an object do not have the
* same type and value.
*
* @param mixed $expected
* @param string $actualAttributeName
* @param object|string $actualClassOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeNotSame($expected, string $actualAttributeName, $actualClassOrObject, string $message = ''): void
{
Assert::assertAttributeNotSame(...\func_get_args());
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
*/
function assertInstanceOf(string $expected, $actual, string $message = ''): void
{
Assert::assertInstanceOf(...\func_get_args());
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param object|string $classOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeInstanceOf(string $expected, string $attributeName, $classOrObject, string $message = ''): void
{
Assert::assertAttributeInstanceOf(...\func_get_args());
}
/**
* Asserts that a variable is not of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
*/
function assertNotInstanceOf(string $expected, $actual, string $message = ''): void
{
Assert::assertNotInstanceOf(...\func_get_args());
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param object|string $classOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeNotInstanceOf(string $expected, string $attributeName, $classOrObject, string $message = ''): void
{
Assert::assertAttributeNotInstanceOf(...\func_get_args());
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertInternalType(string $expected, $actual, string $message = ''): void
{
Assert::assertInternalType(...\func_get_args());
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param object|string $classOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeInternalType(string $expected, string $attributeName, $classOrObject, string $message = ''): void
{
Assert::assertAttributeInternalType(...\func_get_args());
}
/**
* Asserts that a variable is not of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotInternalType(string $expected, $actual, string $message = ''): void
{
Assert::assertNotInternalType(...\func_get_args());
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param object|string $classOrObject
* @param string $message
*
* @throws Exception
*/
function assertAttributeNotInternalType(string $expected, string $attributeName, $classOrObject, string $message = ''): void
{
Assert::assertAttributeNotInternalType(...\func_get_args());
}
/**
* Asserts that a string matches a given regular expression.
*
* @param string $pattern
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertRegExp(string $pattern, string $string, string $message = ''): void
{
Assert::assertRegExp(...\func_get_args());
}
/**
* Asserts that a string does not match a given regular expression.
*
* @param string $pattern
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertNotRegExp(string $pattern, string $string, string $message = ''): void
{
Assert::assertNotRegExp(...\func_get_args());
}
/**
* Assert that the size of two arrays (or `Countable` or `Traversable` objects)
* is the same.
*
* @param Countable|iterable $expected
* @param Countable|iterable $actual
* @param string $message
*
* @throws Exception
*/
function assertSameSize($expected, $actual, string $message = ''): void
{
Assert::assertSameSize(...\func_get_args());
}
/**
* Assert that the size of two arrays (or `Countable` or `Traversable` objects)
* is not the same.
*
* @param Countable|iterable $expected
* @param Countable|iterable $actual
* @param string $message
*
* @throws Exception
*/
function assertNotSameSize($expected, $actual, string $message = ''): void
{
Assert::assertNotSameSize(...\func_get_args());
}
/**
* Asserts that a string matches a given format string.
*
* @param string $format
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertStringMatchesFormat(string $format, string $string, string $message = ''): void
{
Assert::assertStringMatchesFormat(...\func_get_args());
}
/**
* Asserts that a string does not match a given format string.
*
* @param string $format
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void
{
Assert::assertStringNotMatchesFormat(...\func_get_args());
}
/**
* Asserts that a string matches a given format file.
*
* @param string $formatFile
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void
{
Assert::assertStringMatchesFormatFile(...\func_get_args());
}
/**
* Asserts that a string does not match a given format string.
*
* @param string $formatFile
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void
{
Assert::assertStringNotMatchesFormatFile(...\func_get_args());
}
/**
* Asserts that a string starts with a given prefix.
*
* @param string $prefix
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertStringStartsWith(string $prefix, string $string, string $message = ''): void
{
Assert::assertStringStartsWith(...\func_get_args());
}
/**
* Asserts that a string starts not with a given prefix.
*
* @param string $prefix
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertStringStartsNotWith($prefix, $string, string $message = ''): void
{
Assert::assertStringStartsNotWith(...\func_get_args());
}
/**
* Asserts that a string ends with a given suffix.
*
* @param string $suffix
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertStringEndsWith(string $suffix, string $string, string $message = ''): void
{
Assert::assertStringEndsWith(...\func_get_args());
}
/**
* Asserts that a string ends not with a given suffix.
*
* @param string $suffix
* @param string $string
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void
{
Assert::assertStringEndsNotWith(...\func_get_args());
}
/**
* Asserts that two XML files are equal.
*
* @param string $expectedFile
* @param string $actualFile
* @param string $message
*
* @throws Exception
*/
function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void
{
Assert::assertXmlFileEqualsXmlFile(...\func_get_args());
}
/**
* Asserts that two XML files are not equal.
*
* @param string $expectedFile
* @param string $actualFile
* @param string $message
*
* @throws Exception
*/
function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void
{
Assert::assertXmlFileNotEqualsXmlFile(...\func_get_args());
}
/**
* Asserts that two XML documents are equal.
*
* @param string $expectedFile
* @param DOMDocument|string $actualXml
* @param string $message
*
* @throws Exception
*/
function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void
{
Assert::assertXmlStringEqualsXmlFile(...\func_get_args());
}
/**
* Asserts that two XML documents are not equal.
*
* @param string $expectedFile
* @param DOMDocument|string $actualXml
* @param string $message
*
* @throws Exception
*/
function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void
{
Assert::assertXmlStringNotEqualsXmlFile(...\func_get_args());
}
/**
* Asserts that two XML documents are equal.
*
* @param DOMDocument|string $expectedXml
* @param DOMDocument|string $actualXml
* @param string $message
*
* @throws Exception
*/
function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void
{
Assert::assertXmlStringEqualsXmlString(...\func_get_args());
}
/**
* Asserts that two XML documents are not equal.
*
* @param DOMDocument|string $expectedXml
* @param DOMDocument|string $actualXml
* @param string $message
*
* @throws Exception
*/
function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void
{
Assert::assertXmlStringNotEqualsXmlString(...\func_get_args());
}
/**
* Asserts that a hierarchy of DOMElements matches.
*
* @param DOMElement $expectedElement
* @param DOMElement $actualElement
* @param bool $checkAttributes
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \PHPUnit\Framework\AssertionFailedError
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void
{
Assert::assertEqualXMLStructure(...\func_get_args());
}
/**
* Evaluates a PHPUnit\Framework\Constraint matcher object.
*
* @param mixed $value
* @param Constraint $constraint
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertThat($value, Constraint $constraint, string $message = ''): void
{
Assert::assertThat(...\func_get_args());
}
/**
* Asserts that a string is a valid JSON string.
*
* @param string $actualJson
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertJson(string $actualJson, string $message = ''): void
{
Assert::assertJson(...\func_get_args());
}
/**
* Asserts that two given JSON encoded objects or arrays are equal.
*
* @param string $expectedJson
* @param string $actualJson
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void
{
Assert::assertJsonStringEqualsJsonString(...\func_get_args());
}
/**
* Asserts that two given JSON encoded objects or arrays are not equal.
*
* @param string $expectedJson
* @param string $actualJson
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void
{
Assert::assertJsonStringNotEqualsJsonString(...\func_get_args());
}
/**
* Asserts that the generated JSON encoded object and the content of the given file are equal.
*
* @param string $expectedFile
* @param string $actualJson
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void
{
Assert::assertJsonStringEqualsJsonFile(...\func_get_args());
}
/**
* Asserts that the generated JSON encoded object and the content of the given file are not equal.
*
* @param string $expectedFile
* @param string $actualJson
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void
{
Assert::assertJsonStringNotEqualsJsonFile(...\func_get_args());
}
/**
* Asserts that two JSON files are equal.
*
* @param string $expectedFile
* @param string $actualFile
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void
{
Assert::assertJsonFileEqualsJsonFile(...\func_get_args());
}
/**
* Asserts that two JSON files are not equal.
*
* @param string $expectedFile
* @param string $actualFile
* @param string $message
*
* @throws Exception
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void
{
Assert::assertJsonFileNotEqualsJsonFile(...\func_get_args());
}
function logicalAnd(): LogicalAnd
{
return Assert::logicalAnd(...\func_get_args());
}
function logicalOr(): LogicalOr
{
return Assert::logicalOr(...\func_get_args());
}
function logicalNot(Constraint $constraint): LogicalNot
{
return Assert::logicalNot(...\func_get_args());
}
function logicalXor(): LogicalXor
{
return Assert::logicalXor(...\func_get_args());
}
function anything(): IsAnything
{
return Assert::anything();
}
function isTrue(): IsTrue
{
return Assert::isTrue();
}
function callback(callable $callback): Callback
{
return Assert::callback(...\func_get_args());
}
function isFalse(): IsFalse
{
return Assert::isFalse();
}
function isJson(): IsJson
{
return Assert::isJson();
}
function isNull(): IsNull
{
return Assert::isNull();
}
function isFinite(): IsFinite
{
return Assert::isFinite();
}
function isInfinite(): IsInfinite
{
return Assert::isInfinite();
}
function isNan(): IsNan
{
return Assert::isNan();
}
function attribute(Constraint $constraint, string $attributeName): Attribute
{
return Assert::attribute(...\func_get_args());
}
function contains($value, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): TraversableContains
{
return Assert::contains(...\func_get_args());
}
function containsOnly(string $type): TraversableContainsOnly
{
return Assert::containsOnly(...\func_get_args());
}
function containsOnlyInstancesOf(string $className): TraversableContainsOnly
{
return Assert::containsOnlyInstancesOf(...\func_get_args());
}
function arrayHasKey($key): ArrayHasKey
{
return Assert::arrayHasKey(...\func_get_args());
}
function equalTo($value, float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): IsEqual
{
return Assert::equalTo(...\func_get_args());
}
function attributeEqualTo(string $attributeName, $value, float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): Attribute
{
return Assert::attributeEqualTo(...\func_get_args());
}
function isEmpty(): IsEmpty
{
return Assert::isEmpty();
}
function isWritable(): IsWritable
{
return Assert::isWritable();
}
function isReadable(): IsReadable
{
return Assert::isReadable();
}
function directoryExists(): DirectoryExists
{
return Assert::directoryExists();
}
function fileExists(): FileExists
{
return Assert::fileExists();
}
function greaterThan($value): GreaterThan
{
return Assert::greaterThan(...\func_get_args());
}
function greaterThanOrEqual($value): LogicalOr
{
return Assert::greaterThanOrEqual(...\func_get_args());
}
function classHasAttribute(string $attributeName): ClassHasAttribute
{
return Assert::classHasAttribute(...\func_get_args());
}
function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute
{
return Assert::classHasStaticAttribute(...\func_get_args());
}
function objectHasAttribute($attributeName): ObjectHasAttribute
{
return Assert::objectHasAttribute(...\func_get_args());
}
function identicalTo($value): IsIdentical
{
return Assert::identicalTo(...\func_get_args());
}
function isInstanceOf(string $className): IsInstanceOf
{
return Assert::isInstanceOf(...\func_get_args());
}
function isType(string $type): IsType
{
return Assert::isType(...\func_get_args());
}
function lessThan($value): LessThan
{
return Assert::lessThan(...\func_get_args());
}
function lessThanOrEqual($value): LogicalOr
{
return Assert::lessThanOrEqual(...\func_get_args());
}
function matchesRegularExpression(string $pattern): RegularExpression
{
return Assert::matchesRegularExpression(...\func_get_args());
}
function matches(string $string): StringMatchesFormatDescription
{
return Assert::matches(...\func_get_args());
}
function stringStartsWith($prefix): StringStartsWith
{
return Assert::stringStartsWith(...\func_get_args());
}
function stringContains(string $string, bool $case = true): StringContains
{
return Assert::stringContains(...\func_get_args());
}
function stringEndsWith(string $suffix): StringEndsWith
{
return Assert::stringEndsWith(...\func_get_args());
}
function countOf(int $count): Count
{
return Assert::countOf(...\func_get_args());
}
/**
* Returns a matcher that matches when the method is executed
* zero or more times.
*/
function any(): AnyInvokedCountMatcher
{
return new AnyInvokedCountMatcher;
}
/**
* Returns a matcher that matches when the method is never executed.
*/
function never(): InvokedCountMatcher
{
return new InvokedCountMatcher(0);
}
/**
* Returns a matcher that matches when the method is executed
* at least N times.
*
* @param int $requiredInvocations
*/
function atLeast($requiredInvocations): InvokedAtLeastCountMatcher
{
return new InvokedAtLeastCountMatcher(
$requiredInvocations
);
}
/**
* Returns a matcher that matches when the method is executed at least once.
*/
function atLeastOnce(): InvokedAtLeastOnceMatcher
{
return new InvokedAtLeastOnceMatcher;
}
/**
* Returns a matcher that matches when the method is executed exactly once.
*/
function once(): InvokedCountMatcher
{
return new InvokedCountMatcher(1);
}
/**
* Returns a matcher that matches when the method is executed
* exactly $count times.
*
* @param int $count
*/
function exactly($count): InvokedCountMatcher
{
return new InvokedCountMatcher($count);
}
/**
* Returns a matcher that matches when the method is executed
* at most N times.
*
* @param int $allowedInvocations
*/
function atMost($allowedInvocations): InvokedAtMostCountMatcher
{
return new InvokedAtMostCountMatcher($allowedInvocations);
}
/**
* Returns a matcher that matches when the method is executed
* at the given index.
*
* @param int $index
*/
function at($index): InvokedAtIndexMatcher
{
return new InvokedAtIndexMatcher($index);
}
/**
* @param mixed $value
*/
function returnValue($value): ReturnStub
{
return new ReturnStub($value);
}
/**
* @param array $valueMap
*/
function returnValueMap(array $valueMap): ReturnValueMapStub
{
return new ReturnValueMapStub($valueMap);
}
/**
* @param int $argumentIndex
*/
function returnArgument($argumentIndex): ReturnArgumentStub
{
return new ReturnArgumentStub($argumentIndex);
}
/**
* @param mixed $callback
*/
function returnCallback($callback): ReturnCallbackStub
{
return new ReturnCallbackStub($callback);
}
/**
* Returns the current object.
*
* This method is useful when mocking a fluent interface.
*/
function returnSelf(): ReturnSelfStub
{
return new ReturnSelfStub;
}
/**
* @param Throwable $exception
*/
function throwException(Throwable $exception): ExceptionStub
{
return new ExceptionStub($exception);
}
/**
* @param mixed $value , ...
*/
function onConsecutiveCalls(): ConsecutiveCallsStub
{
$args = \func_get_args();
return new ConsecutiveCallsStub($args);
}
src/Framework/Test.php 0000666 00000001003 13436756106 0010731 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
use Countable;
/**
* A Test can be run and collect its results.
*/
interface Test extends Countable
{
/**
* Runs a test and collects its result in a TestResult instance.
*/
public function run(TestResult $result = null): TestResult;
}
src/Framework/IncompleteTestError.php 0000666 00000000527 13436756106 0013775 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
class IncompleteTestError extends AssertionFailedError implements IncompleteTest
{
}
src/Framework/OutputError.php 0000666 00000000465 13436756106 0012337 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
class OutputError extends AssertionFailedError
{
}
src/Framework/TestSuite.php 0000666 00000066041 13436756106 0011760 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
use Iterator;
use IteratorAggregate;
use PHPUnit\Runner\BaseTestRunner;
use PHPUnit\Runner\Filter\Factory;
use PHPUnit\Runner\PhptTestCase;
use PHPUnit\Util\FileLoader;
use PHPUnit\Util\InvalidArgumentHelper;
use ReflectionClass;
use ReflectionMethod;
use Throwable;
/**
* A TestSuite is a composite of Tests. It runs a collection of test cases.
*/
class TestSuite implements Test, SelfDescribing, IteratorAggregate
{
/**
* Enable or disable the backup and restoration of the $GLOBALS array.
*
* @var bool
*/
protected $backupGlobals;
/**
* Enable or disable the backup and restoration of static attributes.
*
* @var bool
*/
protected $backupStaticAttributes;
/**
* @var bool
*/
protected $runTestInSeparateProcess = false;
/**
* The name of the test suite.
*
* @var string
*/
protected $name = '';
/**
* The test groups of the test suite.
*
* @var array
*/
protected $groups = [];
/**
* The tests in the test suite.
*
* @var TestCase[]
*/
protected $tests = [];
/**
* The number of tests in the test suite.
*
* @var int
*/
protected $numTests = -1;
/**
* @var bool
*/
protected $testCase = false;
/**
* @var array
*/
protected $foundClasses = [];
/**
* Last count of tests in this suite.
*
* @var null|int
*/
private $cachedNumTests;
/**
* @var bool
*/
private $beStrictAboutChangesToGlobalState;
/**
* @var Factory
*/
private $iteratorFilter;
/**
* @var string[]
*/
private $declaredClasses;
/**
* @param ReflectionClass $theClass
* @param string $name
*
* @throws Exception
*/
public static function createTest(ReflectionClass $theClass, $name): Test
{
$className = $theClass->getName();
if (!$theClass->isInstantiable()) {
return self::warning(
\sprintf('Cannot instantiate class "%s".', $className)
);
}
$backupSettings = \PHPUnit\Util\Test::getBackupSettings(
$className,
$name
);
$preserveGlobalState = \PHPUnit\Util\Test::getPreserveGlobalStateSettings(
$className,
$name
);
$runTestInSeparateProcess = \PHPUnit\Util\Test::getProcessIsolationSettings(
$className,
$name
);
$runClassInSeparateProcess = \PHPUnit\Util\Test::getClassProcessIsolationSettings(
$className,
$name
);
$constructor = $theClass->getConstructor();
if ($constructor !== null) {
$parameters = $constructor->getParameters();
// TestCase() or TestCase($name)
if (\count($parameters) < 2) {
$test = new $className;
} // TestCase($name, $data)
else {
try {
$data = \PHPUnit\Util\Test::getProvidedData(
$className,
$name
);
} catch (IncompleteTestError $e) {
$message = \sprintf(
'Test for %s::%s marked incomplete by data provider',
$className,
$name
);
$_message = $e->getMessage();
if (!empty($_message)) {
$message .= PHP_EOL . $_message;
}
$data = self::incompleteTest($className, $name, $message);
} catch (SkippedTestError $e) {
$message = \sprintf(
'Test for %s::%s skipped by data provider',
$className,
$name
);
$_message = $e->getMessage();
if (!empty($_message)) {
$message .= PHP_EOL . $_message;
}
$data = self::skipTest($className, $name, $message);
} catch (Throwable $_t) {
$t = $_t;
} catch (Exception $_t) {
$t = $_t;
}
if (isset($t)) {
$message = \sprintf(
'The data provider specified for %s::%s is invalid.',
$className,
$name
);
$_message = $t->getMessage();
if (!empty($_message)) {
$message .= PHP_EOL . $_message;
}
$data = self::warning($message);
}
// Test method with @dataProvider.
if (isset($data)) {
$test = new DataProviderTestSuite(
$className . '::' . $name
);
if (empty($data)) {
$data = self::warning(
\sprintf(
'No tests found in suite "%s".',
$test->getName()
)
);
}
$groups = \PHPUnit\Util\Test::getGroups($className, $name);
if ($data instanceof WarningTestCase ||
$data instanceof SkippedTestCase ||
$data instanceof IncompleteTestCase) {
$test->addTest($data, $groups);
} else {
foreach ($data as $_dataName => $_data) {
$_test = new $className($name, $_data, $_dataName);
/* @var TestCase $_test */
if ($runTestInSeparateProcess) {
$_test->setRunTestInSeparateProcess(true);
if ($preserveGlobalState !== null) {
$_test->setPreserveGlobalState($preserveGlobalState);
}
}
if ($runClassInSeparateProcess) {
$_test->setRunClassInSeparateProcess(true);
if ($preserveGlobalState !== null) {
$_test->setPreserveGlobalState($preserveGlobalState);
}
}
if ($backupSettings['backupGlobals'] !== null) {
$_test->setBackupGlobals(
$backupSettings['backupGlobals']
);
}
if ($backupSettings['backupStaticAttributes'] !== null) {
$_test->setBackupStaticAttributes(
$backupSettings['backupStaticAttributes']
);
}
$test->addTest($_test, $groups);
}
}
} else {
$test = new $className;
}
}
}
if (!isset($test)) {
throw new Exception('No valid test provided.');
}
if ($test instanceof TestCase) {
$test->setName($name);
if ($runTestInSeparateProcess) {
$test->setRunTestInSeparateProcess(true);
if ($preserveGlobalState !== null) {
$test->setPreserveGlobalState($preserveGlobalState);
}
}
if ($runClassInSeparateProcess) {
$test->setRunClassInSeparateProcess(true);
if ($preserveGlobalState !== null) {
$test->setPreserveGlobalState($preserveGlobalState);
}
}
if ($backupSettings['backupGlobals'] !== null) {
$test->setBackupGlobals($backupSettings['backupGlobals']);
}
if ($backupSettings['backupStaticAttributes'] !== null) {
$test->setBackupStaticAttributes(
$backupSettings['backupStaticAttributes']
);
}
}
return $test;
}
/**
* @param ReflectionMethod $method
*/
public static function isTestMethod(ReflectionMethod $method): bool
{
if (\strpos($method->name, 'test') === 0) {
return true;
}
$annotations = \PHPUnit\Util\Test::parseAnnotations($method->getDocComment());
return isset($annotations['test']);
}
/**
* Constructs a new TestSuite:
*
* - PHPUnit\Framework\TestSuite() constructs an empty TestSuite.
*
* - PHPUnit\Framework\TestSuite(ReflectionClass) constructs a
* TestSuite from the given class.
*
* - PHPUnit\Framework\TestSuite(ReflectionClass, String)
* constructs a TestSuite from the given class with the given
* name.
*
* - PHPUnit\Framework\TestSuite(String) either constructs a
* TestSuite from the given class (if the passed string is the
* name of an existing class) or constructs an empty TestSuite
* with the given name.
*
* @param mixed $theClass
* @param string $name
*
* @throws Exception
*/
public function __construct($theClass = '', $name = '')
{
$this->declaredClasses = \get_declared_classes();
$argumentsValid = false;
if (\is_object($theClass) &&
$theClass instanceof ReflectionClass) {
$argumentsValid = true;
} elseif (\is_string($theClass) &&
$theClass !== '' &&
\class_exists($theClass, false)) {
$argumentsValid = true;
if ($name == '') {
$name = $theClass;
}
$theClass = new ReflectionClass($theClass);
} elseif (\is_string($theClass)) {
$this->setName($theClass);
return;
}
if (!$argumentsValid) {
throw new Exception;
}
if (!$theClass->isSubclassOf(TestCase::class)) {
throw new Exception(
'Class "' . $theClass->name . '" does not extend PHPUnit\Framework\TestCase.'
);
}
if ($name != '') {
$this->setName($name);
} else {
$this->setName($theClass->getName());
}
$constructor = $theClass->getConstructor();
if ($constructor !== null &&
!$constructor->isPublic()) {
$this->addTest(
self::warning(
\sprintf(
'Class "%s" has no public constructor.',
$theClass->getName()
)
)
);
return;
}
foreach ($theClass->getMethods() as $method) {
if ($method->getDeclaringClass()->getName() === Assert::class) {
continue;
}
if ($method->getDeclaringClass()->getName() === TestCase::class) {
continue;
}
$this->addTestMethod($theClass, $method);
}
if (empty($this->tests)) {
$this->addTest(
self::warning(
\sprintf(
'No tests found in class "%s".',
$theClass->getName()
)
)
);
}
$this->testCase = true;
}
/**
* Template Method that is called before the tests
* of this test suite are run.
*/
protected function setUp(): void
{
}
/**
* Template Method that is called after the tests
* of this test suite have finished running.
*/
protected function tearDown(): void
{
}
/**
* Returns a string representation of the test suite.
*/
public function toString(): string
{
return $this->getName();
}
/**
* Adds a test to the suite.
*
* @param Test $test
* @param array $groups
*/
public function addTest(Test $test, $groups = []): void
{
$class = new ReflectionClass($test);
if (!$class->isAbstract()) {
$this->tests[] = $test;
$this->numTests = -1;
if ($test instanceof self && empty($groups)) {
$groups = $test->getGroups();
}
if (empty($groups)) {
$groups = ['default'];
}
foreach ($groups as $group) {
if (!isset($this->groups[$group])) {
$this->groups[$group] = [$test];
} else {
$this->groups[$group][] = $test;
}
}
if ($test instanceof TestCase) {
$test->setGroups($groups);
}
}
}
/**
* Adds the tests from the given class to the suite.
*
* @param mixed $testClass
*
* @throws Exception
*/
public function addTestSuite($testClass): void
{
if (\is_string($testClass) && \class_exists($testClass)) {
$testClass = new ReflectionClass($testClass);
}
if (!\is_object($testClass)) {
throw InvalidArgumentHelper::factory(
1,
'class name or object'
);
}
if ($testClass instanceof self) {
$this->addTest($testClass);
} elseif ($testClass instanceof ReflectionClass) {
$suiteMethod = false;
if (!$testClass->isAbstract() && $testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) {
$method = $testClass->getMethod(
BaseTestRunner::SUITE_METHODNAME
);
if ($method->isStatic()) {
$this->addTest(
$method->invoke(null, $testClass->getName())
);
$suiteMethod = true;
}
}
if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(TestCase::class)) {
$this->addTest(new self($testClass));
}
} else {
throw new Exception;
}
}
/**
* Wraps both addTest()
and addTestSuite
* as well as the separate import statements for the user's convenience.
*
* If the named file cannot be read or there are no new tests that can be
* added, a PHPUnit\Framework\WarningTestCase
will be created instead,
* leaving the current test run untouched.
*
* @param string $filename
*
* @throws Exception
*/
public function addTestFile(string $filename): void
{
if (\file_exists($filename) && \substr($filename, -5) == '.phpt') {
$this->addTest(
new PhptTestCase($filename)
);
return;
}
// The given file may contain further stub classes in addition to the
// test class itself. Figure out the actual test class.
$filename = FileLoader::checkAndLoad($filename);
$newClasses = \array_diff(\get_declared_classes(), $this->declaredClasses);
// The diff is empty in case a parent class (with test methods) is added
// AFTER a child class that inherited from it. To account for that case,
// accumulate all discovered classes, so the parent class may be found in
// a later invocation.
if (!empty($newClasses)) {
// On the assumption that test classes are defined first in files,
// process discovered classes in approximate LIFO order, so as to
// avoid unnecessary reflection.
$this->foundClasses = \array_merge($newClasses, $this->foundClasses);
$this->declaredClasses = \get_declared_classes();
}
// The test class's name must match the filename, either in full, or as
// a PEAR/PSR-0 prefixed short name ('NameSpace_ShortName'), or as a
// PSR-1 local short name ('NameSpace\ShortName'). The comparison must be
// anchored to prevent false-positive matches (e.g., 'OtherShortName').
$shortName = \basename($filename, '.php');
$shortNameRegEx = '/(?:^|_|\\\\)' . \preg_quote($shortName, '/') . '$/';
foreach ($this->foundClasses as $i => $className) {
if (\preg_match($shortNameRegEx, $className)) {
$class = new ReflectionClass($className);
if ($class->getFileName() == $filename) {
$newClasses = [$className];
unset($this->foundClasses[$i]);
break;
}
}
}
foreach ($newClasses as $className) {
$class = new ReflectionClass($className);
if (\dirname($class->getFileName()) === __DIR__) {
continue;
}
if (!$class->isAbstract()) {
if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) {
$method = $class->getMethod(
BaseTestRunner::SUITE_METHODNAME
);
if ($method->isStatic()) {
$this->addTest($method->invoke(null, $className));
}
} elseif ($class->implementsInterface(Test::class)) {
$this->addTestSuite($class);
}
}
}
$this->numTests = -1;
}
/**
* Wrapper for addTestFile() that adds multiple test files.
*
* @param array|Iterator $fileNames
*
* @throws Exception
*/
public function addTestFiles($fileNames): void
{
if (!(\is_array($fileNames) ||
(\is_object($fileNames) && $fileNames instanceof Iterator))) {
throw InvalidArgumentHelper::factory(
1,
'array or iterator'
);
}
foreach ($fileNames as $filename) {
$this->addTestFile((string) $filename);
}
}
/**
* Counts the number of test cases that will be run by this test.
*
* @param bool $preferCache indicates if cache is preferred
*/
public function count($preferCache = false): int
{
if ($preferCache && $this->cachedNumTests !== null) {
return $this->cachedNumTests;
}
$numTests = 0;
foreach ($this as $test) {
$numTests += \count($test);
}
$this->cachedNumTests = $numTests;
return $numTests;
}
/**
* Returns the name of the suite.
*/
public function getName(): string
{
return $this->name;
}
/**
* Returns the test groups of the suite.
*/
public function getGroups(): array
{
return \array_keys($this->groups);
}
public function getGroupDetails()
{
return $this->groups;
}
/**
* Set tests groups of the test case
*
* @param array $groups
*/
public function setGroupDetails(array $groups): void
{
$this->groups = $groups;
}
/**
* Runs the tests and collects their result in a TestResult.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function run(TestResult $result = null): TestResult
{
if ($result === null) {
$result = $this->createResult();
}
if (\count($this) == 0) {
return $result;
}
$hookMethods = \PHPUnit\Util\Test::getHookMethods($this->name);
$result->startTestSuite($this);
try {
$this->setUp();
foreach ($hookMethods['beforeClass'] as $beforeClassMethod) {
if ($this->testCase === true &&
\class_exists($this->name, false) &&
\method_exists($this->name, $beforeClassMethod)) {
if ($missingRequirements = \PHPUnit\Util\Test::getMissingRequirements($this->name, $beforeClassMethod)) {
$this->markTestSuiteSkipped(\implode(PHP_EOL, $missingRequirements));
}
\call_user_func([$this->name, $beforeClassMethod]);
}
}
} catch (SkippedTestSuiteError $e) {
$numTests = \count($this);
for ($i = 0; $i < $numTests; $i++) {
$result->startTest($this);
$result->addFailure($this, $e, 0);
$result->endTest($this, 0);
}
$this->tearDown();
$result->endTestSuite($this);
return $result;
} catch (Throwable $_t) {
$t = $_t;
} catch (Exception $_t) {
$t = $_t;
}
if (isset($t)) {
$numTests = \count($this);
for ($i = 0; $i < $numTests; $i++) {
if ($result->shouldStop()) {
break;
}
$result->startTest($this);
$result->addError($this, $t, 0);
$result->endTest($this, 0);
}
$this->tearDown();
$result->endTestSuite($this);
return $result;
}
foreach ($this as $test) {
if ($result->shouldStop()) {
break;
}
if ($test instanceof TestCase || $test instanceof self) {
$test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState);
$test->setBackupGlobals($this->backupGlobals);
$test->setBackupStaticAttributes($this->backupStaticAttributes);
$test->setRunTestInSeparateProcess($this->runTestInSeparateProcess);
}
$test->run($result);
}
foreach ($hookMethods['afterClass'] as $afterClassMethod) {
if ($this->testCase === true && \class_exists($this->name, false) && \method_exists($this->name, $afterClassMethod)) {
\call_user_func([$this->name, $afterClassMethod]);
}
}
$this->tearDown();
$result->endTestSuite($this);
return $result;
}
public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void
{
$this->runTestInSeparateProcess = $runTestInSeparateProcess;
}
public function setName(string $name): void
{
$this->name = $name;
}
/**
* Returns the test at the given index.
*
* @return false|Test
*/
public function testAt(int $index)
{
if (isset($this->tests[$index])) {
return $this->tests[$index];
}
return false;
}
/**
* Returns the tests as an enumeration.
*/
public function tests(): array
{
return $this->tests;
}
/**
* Set tests of the test suite
*
* @param array $tests
*/
public function setTests(array $tests): void
{
$this->tests = $tests;
}
/**
* Mark the test suite as skipped.
*
* @param string $message
*
* @throws SkippedTestSuiteError
*/
public function markTestSuiteSkipped($message = ''): void
{
throw new SkippedTestSuiteError($message);
}
/**
* @param bool $beStrictAboutChangesToGlobalState
*/
public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState): void
{
if (null === $this->beStrictAboutChangesToGlobalState && \is_bool($beStrictAboutChangesToGlobalState)) {
$this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState;
}
}
/**
* @param bool $backupGlobals
*/
public function setBackupGlobals($backupGlobals): void
{
if (null === $this->backupGlobals && \is_bool($backupGlobals)) {
$this->backupGlobals = $backupGlobals;
}
}
/**
* @param bool $backupStaticAttributes
*/
public function setBackupStaticAttributes($backupStaticAttributes): void
{
if (null === $this->backupStaticAttributes && \is_bool($backupStaticAttributes)) {
$this->backupStaticAttributes = $backupStaticAttributes;
}
}
/**
* Returns an iterator for this test suite.
*/
public function getIterator(): Iterator
{
$iterator = new TestSuiteIterator($this);
if ($this->iteratorFilter !== null) {
$iterator = $this->iteratorFilter->factory($iterator, $this);
}
return $iterator;
}
public function injectFilter(Factory $filter): void
{
$this->iteratorFilter = $filter;
foreach ($this as $test) {
if ($test instanceof self) {
$test->injectFilter($filter);
}
}
}
/**
* Creates a default TestResult object.
*/
protected function createResult(): TestResult
{
return new TestResult;
}
/**
* @param ReflectionClass $class
* @param ReflectionMethod $method
*
* @throws Exception
*/
protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void
{
if (!$this->isTestMethod($method)) {
return;
}
$name = $method->getName();
if (!$method->isPublic()) {
$this->addTest(
self::warning(
\sprintf(
'Test method "%s" in test class "%s" is not public.',
$name,
$class->getName()
)
)
);
return;
}
$test = self::createTest($class, $name);
if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) {
$test->setDependencies(
\PHPUnit\Util\Test::getDependencies($class->getName(), $name)
);
}
$this->addTest(
$test,
\PHPUnit\Util\Test::getGroups($class->getName(), $name)
);
}
/**
* @param string $message
*/
protected static function warning($message): WarningTestCase
{
return new WarningTestCase($message);
}
/**
* @param string $class
* @param string $methodName
* @param string $message
*/
protected static function skipTest($class, $methodName, $message): SkippedTestCase
{
return new SkippedTestCase($class, $methodName, $message);
}
/**
* @param string $class
* @param string $methodName
* @param string $message
*/
protected static function incompleteTest($class, $methodName, $message): IncompleteTestCase
{
return new IncompleteTestCase($class, $methodName, $message);
}
}
src/Framework/IncompleteTestCase.php 0000666 00000002670 13436756106 0013560 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
/**
* An incomplete test case
*/
class IncompleteTestCase extends TestCase
{
/**
* @var string
*/
protected $message = '';
/**
* @var bool
*/
protected $backupGlobals = false;
/**
* @var bool
*/
protected $backupStaticAttributes = false;
/**
* @var bool
*/
protected $runTestInSeparateProcess = false;
/**
* @var bool
*/
protected $useErrorHandler = false;
/**
* @var bool
*/
protected $useOutputBuffering = false;
public function __construct(string $className, string $methodName, string $message = '')
{
parent::__construct($className . '::' . $methodName);
$this->message = $message;
}
public function getMessage(): string
{
return $this->message;
}
/**
* Returns a string representation of the test case.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function toString(): string
{
return $this->getName();
}
/**
* @throws Exception
*/
protected function runTest(): void
{
$this->markTestIncomplete($this->message);
}
}
src/Framework/InvalidCoversTargetException.php 0000666 00000000507 13436756106 0015620 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;
class InvalidCoversTargetException extends CodeCoverageException
{
}
src/Framework/Constraint/IsFalse.php 0000666 00000001422 13436756106 0013471 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that accepts false.
*/
class IsFalse extends Constraint
{
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return 'is false';
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return $other === false;
}
}
src/Framework/Constraint/ClassHasStaticAttribute.php 0000666 00000002344 13436756106 0016704 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use ReflectionClass;
/**
* Constraint that asserts that the class it is evaluated for has a given
* static attribute.
*
* The attribute name is passed in the constructor.
*/
class ClassHasStaticAttribute extends ClassHasAttribute
{
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return \sprintf(
'has static attribute "%s"',
$this->attributeName()
);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
$class = new ReflectionClass($other);
if ($class->hasProperty($this->attributeName())) {
$attribute = $class->getProperty($this->attributeName());
return $attribute->isStatic();
}
return false;
}
}
src/Framework/Constraint/ExceptionMessage.php 0000666 00000003456 13436756106 0015417 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
class ExceptionMessage extends Constraint
{
/**
* @var string
*/
private $expectedMessage;
public function __construct(string $expected)
{
parent::__construct();
$this->expectedMessage = $expected;
}
public function toString(): string
{
if ($this->expectedMessage === '') {
return 'exception message is empty';
}
return 'exception message contains ';
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param \Throwable $other
*/
protected function matches($other): bool
{
if ($this->expectedMessage === '') {
return $other->getMessage() === '';
}
return \strpos($other->getMessage(), $this->expectedMessage) !== false;
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
if ($this->expectedMessage === '') {
return \sprintf(
"exception message is empty but is '%s'",
$other->getMessage()
);
}
return \sprintf(
"exception message '%s' contains '%s'",
$other->getMessage(),
$this->expectedMessage
);
}
}
src/Framework/Constraint/LogicalXor.php 0000666 00000006024 13436756106 0014211 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Framework\ExpectationFailedException;
/**
* Logical XOR.
*/
class LogicalXor extends Constraint
{
/**
* @var Constraint[]
*/
private $constraints = [];
public static function fromConstraints(Constraint ...$constraints): self
{
$constraint = new self;
$constraint->constraints = \array_values($constraints);
return $constraint;
}
/**
* @param Constraint[] $constraints
*/
public function setConstraints(array $constraints): void
{
$this->constraints = [];
foreach ($constraints as $constraint) {
if (!($constraint instanceof Constraint)) {
$constraint = new IsEqual(
$constraint
);
}
$this->constraints[] = $constraint;
}
}
/**
* Evaluates the constraint for parameter $other
*
* If $returnResult is set to false (the default), an exception is thrown
* in case of a failure. null is returned otherwise.
*
* If $returnResult is true, the result of the evaluation is returned as
* a boolean value instead: true in case of success, false in case of a
* failure.
*
* @param mixed $other value or object to evaluate
* @param string $description Additional information about the test
* @param bool $returnResult Whether to return a result or throw an exception
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*
* @return mixed
*/
public function evaluate($other, $description = '', $returnResult = false)
{
$success = true;
$lastResult = null;
foreach ($this->constraints as $constraint) {
$result = $constraint->evaluate($other, $description, true);
if ($result === $lastResult) {
$success = false;
break;
}
$lastResult = $result;
}
if ($returnResult) {
return $success;
}
if (!$success) {
$this->fail($other, $description);
}
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
$text = '';
foreach ($this->constraints as $key => $constraint) {
if ($key > 0) {
$text .= ' xor ';
}
$text .= $constraint->toString();
}
return $text;
}
/**
* Counts the number of constraint elements.
*/
public function count(): int
{
$count = 0;
foreach ($this->constraints as $constraint) {
$count += \count($constraint);
}
return $count;
}
}
src/Framework/Constraint/IsEqual.php 0000666 00000007727 13436756106 0013524 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Framework\ExpectationFailedException;
use SebastianBergmann\Comparator\ComparisonFailure;
use SebastianBergmann\Comparator\Factory as ComparatorFactory;
/**
* Constraint that checks if one value is equal to another.
*
* Equality is checked with PHP's == operator, the operator is explained in
* detail at {@url https://php.net/manual/en/types.comparisons.php}.
* Two values are equal if they have the same value disregarding type.
*
* The expected value is passed in the constructor.
*/
class IsEqual extends Constraint
{
/**
* @var mixed
*/
private $value;
/**
* @var float
*/
private $delta;
/**
* @var int
*/
private $maxDepth;
/**
* @var bool
*/
private $canonicalize;
/**
* @var bool
*/
private $ignoreCase;
public function __construct($value, float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false)
{
parent::__construct();
$this->value = $value;
$this->delta = $delta;
$this->maxDepth = $maxDepth;
$this->canonicalize = $canonicalize;
$this->ignoreCase = $ignoreCase;
}
/**
* Evaluates the constraint for parameter $other
*
* If $returnResult is set to false (the default), an exception is thrown
* in case of a failure. null is returned otherwise.
*
* If $returnResult is true, the result of the evaluation is returned as
* a boolean value instead: true in case of success, false in case of a
* failure.
*
* @param mixed $other value or object to evaluate
* @param string $description Additional information about the test
* @param bool $returnResult Whether to return a result or throw an exception
*
* @throws ExpectationFailedException
*
* @return mixed
*/
public function evaluate($other, $description = '', $returnResult = false)
{
// If $this->value and $other are identical, they are also equal.
// This is the most common path and will allow us to skip
// initialization of all the comparators.
if ($this->value === $other) {
return true;
}
$comparatorFactory = ComparatorFactory::getInstance();
try {
$comparator = $comparatorFactory->getComparatorFor(
$this->value,
$other
);
$comparator->assertEquals(
$this->value,
$other,
$this->delta,
$this->canonicalize,
$this->ignoreCase
);
} catch (ComparisonFailure $f) {
if ($returnResult) {
return false;
}
throw new ExpectationFailedException(
\trim($description . PHP_EOL . $f->getMessage()),
$f
);
}
return true;
}
/**
* Returns a string representation of the constraint.
*
* @throws SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function toString(): string
{
$delta = '';
if (\is_string($this->value)) {
if (\strpos($this->value, PHP_EOL) !== false) {
return 'is equal to ';
}
return \sprintf(
'is equal to "%s"',
$this->value
);
}
if ($this->delta != 0) {
$delta = \sprintf(
' with delta <%F>',
$this->delta
);
}
return \sprintf(
'is equal to %s%s',
$this->exporter->export($this->value),
$delta
);
}
}
src/Framework/Constraint/Exception.php 0000666 00000003763 13436756106 0014113 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Util\Filter;
use Throwable;
class Exception extends Constraint
{
/**
* @var string
*/
private $className;
public function __construct(string $className)
{
parent::__construct();
$this->className = $className;
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return \sprintf(
'exception of type "%s"',
$this->className
);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return $other instanceof $this->className;
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
if ($other !== null) {
$message = '';
if ($other instanceof Throwable) {
$message = '. Message was: "' . $other->getMessage() . '" at'
. PHP_EOL . Filter::getFilteredStacktrace($other);
}
return \sprintf(
'exception of type "%s" matches expected exception "%s"%s',
\get_class($other),
$this->className,
$message
);
}
return \sprintf(
'exception of type "%s" is thrown',
$this->className
);
}
}
src/Framework/Constraint/JsonMatches.php 0000666 00000005555 13436756106 0014374 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Util\Json;
use SebastianBergmann\Comparator\ComparisonFailure;
/**
* Asserts whether or not two JSON objects are equal.
*/
class JsonMatches extends Constraint
{
/**
* @var string
*/
private $value;
public function __construct(string $value)
{
parent::__construct();
$this->value = $value;
}
/**
* Returns a string representation of the object.
*/
public function toString(): string
{
return \sprintf(
'matches JSON string "%s"',
$this->value
);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* This method can be overridden to implement the evaluation algorithm.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
[$error, $recodedOther] = Json::canonicalize($other);
if ($error) {
return false;
}
[$error, $recodedValue] = Json::canonicalize($this->value);
if ($error) {
return false;
}
return $recodedOther == $recodedValue;
}
/**
* Throws an exception for the given compared value and test description
*
* @param mixed $other evaluated value or object
* @param string $description Additional information about the test
* @param ComparisonFailure $comparisonFailure
*
* @throws ExpectationFailedException
* @throws \PHPUnit\Framework\Exception
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void
{
if ($comparisonFailure === null) {
[$error] = Json::canonicalize($other);
if ($error) {
parent::fail($other, $description);
return;
}
[$error] = Json::canonicalize($this->value);
if ($error) {
parent::fail($other, $description);
return;
}
$comparisonFailure = new ComparisonFailure(
\json_decode($this->value),
\json_decode($other),
Json::prettify($this->value),
Json::prettify($other),
false,
'Failed asserting that two json values are equal.'
);
}
parent::fail($other, $description, $comparisonFailure);
}
}
src/Framework/Constraint/IsType.php 0000666 00000006752 13436756106 0013373 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that asserts that the value it is evaluated for is of a
* specified type.
*
* The expected value is passed in the constructor.
*/
class IsType extends Constraint
{
public const TYPE_ARRAY = 'array';
public const TYPE_BOOL = 'bool';
public const TYPE_FLOAT = 'float';
public const TYPE_INT = 'int';
public const TYPE_NULL = 'null';
public const TYPE_NUMERIC = 'numeric';
public const TYPE_OBJECT = 'object';
public const TYPE_RESOURCE = 'resource';
public const TYPE_STRING = 'string';
public const TYPE_SCALAR = 'scalar';
public const TYPE_CALLABLE = 'callable';
public const TYPE_ITERABLE = 'iterable';
/**
* @var array
*/
private const KNOWN_TYPES = [
'array' => true,
'boolean' => true,
'bool' => true,
'double' => true,
'float' => true,
'integer' => true,
'int' => true,
'null' => true,
'numeric' => true,
'object' => true,
'real' => true,
'resource' => true,
'string' => true,
'scalar' => true,
'callable' => true,
'iterable' => true
];
/**
* @var string
*/
private $type;
/**
* @throws \PHPUnit\Framework\Exception
*/
public function __construct(string $type)
{
parent::__construct();
if (!isset(self::KNOWN_TYPES[$type])) {
throw new \PHPUnit\Framework\Exception(
\sprintf(
'Type specified for PHPUnit\Framework\Constraint\IsType <%s> ' .
'is not a valid type.',
$type
)
);
}
$this->type = $type;
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return \sprintf(
'is of type "%s"',
$this->type
);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
switch ($this->type) {
case 'numeric':
return \is_numeric($other);
case 'integer':
case 'int':
return \is_int($other);
case 'double':
case 'float':
case 'real':
return \is_float($other);
case 'string':
return \is_string($other);
case 'boolean':
case 'bool':
return \is_bool($other);
case 'null':
return null === $other;
case 'array':
return \is_array($other);
case 'object':
return \is_object($other);
case 'resource':
return \is_resource($other) || \is_string(@\get_resource_type($other));
case 'scalar':
return \is_scalar($other);
case 'callable':
return \is_callable($other);
case 'iterable':
return \is_iterable($other);
}
}
}
src/Framework/Constraint/SameSize.php 0000666 00000000651 13436756106 0013666 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
class SameSize extends Count
{
public function __construct(iterable $expected)
{
parent::__construct($this->getCountOf($expected));
}
}
src/Framework/Constraint/Constraint.php 0000666 00000010533 13436756106 0014272 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use Countable;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\SelfDescribing;
use SebastianBergmann\Comparator\ComparisonFailure;
use SebastianBergmann\Exporter\Exporter;
/**
* Abstract base class for constraints which can be applied to any value.
*/
abstract class Constraint implements Countable, SelfDescribing
{
protected $exporter;
public function __construct()
{
$this->exporter = new Exporter;
}
/**
* Evaluates the constraint for parameter $other
*
* If $returnResult is set to false (the default), an exception is thrown
* in case of a failure. null is returned otherwise.
*
* If $returnResult is true, the result of the evaluation is returned as
* a boolean value instead: true in case of success, false in case of a
* failure.
*
* @param mixed $other value or object to evaluate
* @param string $description Additional information about the test
* @param bool $returnResult Whether to return a result or throw an exception
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*
* @return mixed
*/
public function evaluate($other, $description = '', $returnResult = false)
{
$success = false;
if ($this->matches($other)) {
$success = true;
}
if ($returnResult) {
return $success;
}
if (!$success) {
$this->fail($other, $description);
}
}
/**
* Counts the number of constraint elements.
*/
public function count(): int
{
return 1;
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* This method can be overridden to implement the evaluation algorithm.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return false;
}
/**
* Throws an exception for the given compared value and test description
*
* @param mixed $other evaluated value or object
* @param string $description Additional information about the test
* @param ComparisonFailure $comparisonFailure
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void
{
$failureDescription = \sprintf(
'Failed asserting that %s.',
$this->failureDescription($other)
);
$additionalFailureDescription = $this->additionalFailureDescription($other);
if ($additionalFailureDescription) {
$failureDescription .= PHP_EOL . $additionalFailureDescription;
}
if (!empty($description)) {
$failureDescription = $description . PHP_EOL . $failureDescription;
}
throw new ExpectationFailedException(
$failureDescription,
$comparisonFailure
);
}
/**
* Return additional failure description where needed
*
* The function can be overridden to provide additional failure
* information like a diff
*
* @param mixed $other evaluated value or object
*/
protected function additionalFailureDescription($other): string
{
return '';
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* To provide additional failure information additionalFailureDescription
* can be used.
*
* @param mixed $other evaluated value or object
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
protected function failureDescription($other): string
{
return $this->exporter->export($other) . ' ' . $this->toString();
}
}
src/Framework/Constraint/Callback.php 0000666 00000002037 13436756106 0013642 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that evaluates against a specified closure.
*/
class Callback extends Constraint
{
/**
* @var callable
*/
private $callback;
public function __construct(callable $callback)
{
parent::__construct();
$this->callback = $callback;
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return 'is accepted by specified callback';
}
/**
* Evaluates the constraint for parameter $value. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return \call_user_func($this->callback, $other);
}
}
src/Framework/Constraint/ExceptionMessageRegularExpression.php 0000666 00000003536 13436756106 0021020 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Util\RegularExpression as RegularExpressionUtil;
class ExceptionMessageRegularExpression extends Constraint
{
/**
* @var string
*/
private $expectedMessageRegExp;
public function __construct(string $expected)
{
parent::__construct();
$this->expectedMessageRegExp = $expected;
}
public function toString(): string
{
return 'exception message matches ';
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param \PHPUnit\Framework\Exception $other
*
* @throws \Exception
* @throws \PHPUnit\Framework\Exception
*/
protected function matches($other): bool
{
$match = RegularExpressionUtil::safeMatch($this->expectedMessageRegExp, $other->getMessage());
if ($match === false) {
throw new \PHPUnit\Framework\Exception(
"Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'"
);
}
return $match === 1;
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
return \sprintf(
"exception message '%s' matches '%s'",
$other->getMessage(),
$this->expectedMessageRegExp
);
}
}
src/Framework/Constraint/TraversableContainsOnly.php 0000666 00000004567 13436756106 0016773 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Framework\ExpectationFailedException;
/**
* Constraint that asserts that the Traversable it is applied to contains
* only values of a given type.
*/
class TraversableContainsOnly extends Constraint
{
/**
* @var Constraint
*/
private $constraint;
/**
* @var string
*/
private $type;
/**
* @throws \PHPUnit\Framework\Exception
*/
public function __construct(string $type, bool $isNativeType = true)
{
parent::__construct();
if ($isNativeType) {
$this->constraint = new IsType($type);
} else {
$this->constraint = new IsInstanceOf(
$type
);
}
$this->type = $type;
}
/**
* Evaluates the constraint for parameter $other
*
* If $returnResult is set to false (the default), an exception is thrown
* in case of a failure. null is returned otherwise.
*
* If $returnResult is true, the result of the evaluation is returned as
* a boolean value instead: true in case of success, false in case of a
* failure.
*
* @param mixed $other value or object to evaluate
* @param string $description Additional information about the test
* @param bool $returnResult Whether to return a result or throw an exception
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*
* @return mixed
*/
public function evaluate($other, $description = '', $returnResult = false)
{
$success = true;
foreach ($other as $item) {
if (!$this->constraint->evaluate($item, '', true)) {
$success = false;
break;
}
}
if ($returnResult) {
return $success;
}
if (!$success) {
$this->fail($other, $description);
}
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return 'contains only values of type "' . $this->type . '"';
}
}
src/Framework/Constraint/RegularExpression.php 0000666 00000002474 13436756106 0015634 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that asserts that the string it is evaluated for matches
* a regular expression.
*
* Checks a given value using the Perl Compatible Regular Expression extension
* in PHP. The pattern is matched by executing preg_match().
*
* The pattern string passed in the constructor.
*/
class RegularExpression extends Constraint
{
/**
* @var string
*/
private $pattern;
public function __construct(string $pattern)
{
parent::__construct();
$this->pattern = $pattern;
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return \sprintf(
'matches PCRE pattern "%s"',
$this->pattern
);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return \preg_match($this->pattern, $other) > 0;
}
}
src/Framework/Constraint/IsReadable.php 0000666 00000002505 13436756106 0014141 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that checks if the file/dir(name) that it is evaluated for is readable.
*
* The file path to check is passed as $other in evaluate().
*/
class IsReadable extends Constraint
{
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return 'is readable';
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return \is_readable($other);
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
return \sprintf(
'"%s" is readable',
$other
);
}
}
src/Framework/Constraint/IsWritable.php 0000666 00000002505 13436756106 0014213 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that checks if the file/dir(name) that it is evaluated for is writable.
*
* The file path to check is passed as $other in evaluate().
*/
class IsWritable extends Constraint
{
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return 'is writable';
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return \is_writable($other);
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
return \sprintf(
'"%s" is writable',
$other
);
}
}
src/Framework/Constraint/LessThan.php 0000666 00000002261 13436756106 0013666 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that asserts that the value it is evaluated for is less than
* a given value.
*/
class LessThan extends Constraint
{
/**
* @var float|int
*/
private $value;
/**
* @param float|int $value
*/
public function __construct($value)
{
parent::__construct();
$this->value = $value;
}
/**
* Returns a string representation of the constraint.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function toString(): string
{
return 'is less than ' . $this->exporter->export($this->value);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return $this->value > $other;
}
}
src/Framework/Constraint/Attribute.php 0000666 00000004460 13436756106 0014113 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\ExpectationFailedException;
class Attribute extends Composite
{
/**
* @var string
*/
private $attributeName;
public function __construct(Constraint $constraint, string $attributeName)
{
parent::__construct($constraint);
$this->attributeName = $attributeName;
}
/**
* Evaluates the constraint for parameter $other
*
* If $returnResult is set to false (the default), an exception is thrown
* in case of a failure. null is returned otherwise.
*
* If $returnResult is true, the result of the evaluation is returned as
* a boolean value instead: true in case of success, false in case of a
* failure.
*
* @param mixed $other value or object to evaluate
* @param string $description Additional information about the test
* @param bool $returnResult Whether to return a result or throw an exception
*
* @throws ExpectationFailedException
* @throws \PHPUnit\Framework\Exception
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*
* @return mixed
*/
public function evaluate($other, $description = '', $returnResult = false)
{
return parent::evaluate(
Assert::readAttribute(
$other,
$this->attributeName
),
$description,
$returnResult
);
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return 'attribute "' . $this->attributeName . '" ' . $this->innerConstraint()->toString();
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
return $this->toString();
}
}
src/Framework/Constraint/StringContains.php 0000666 00000003315 13436756106 0015113 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that asserts that the string it is evaluated for contains
* a given string.
*
* Uses mb_strpos() to find the position of the string in the input, if not
* found the evaluation fails.
*
* The sub-string is passed in the constructor.
*/
class StringContains extends Constraint
{
/**
* @var string
*/
private $string;
/**
* @var bool
*/
private $ignoreCase;
public function __construct(string $string, bool $ignoreCase = false)
{
parent::__construct();
$this->string = $string;
$this->ignoreCase = $ignoreCase;
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
if ($this->ignoreCase) {
$string = \mb_strtolower($this->string);
} else {
$string = $this->string;
}
return \sprintf(
'contains "%s"',
$string
);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
if ('' === $this->string) {
return true;
}
if ($this->ignoreCase) {
return \mb_stripos($other, $this->string) !== false;
}
return \mb_strpos($other, $this->string) !== false;
}
}
src/Framework/Constraint/IsInstanceOf.php 0000666 00000004246 13436756106 0014477 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use ReflectionClass;
use ReflectionException;
/**
* Constraint that asserts that the object it is evaluated for is an instance
* of a given class.
*
* The expected class name is passed in the constructor.
*/
class IsInstanceOf extends Constraint
{
/**
* @var string
*/
private $className;
public function __construct(string $className)
{
parent::__construct();
$this->className = $className;
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return \sprintf(
'is instance of %s "%s"',
$this->getType(),
$this->className
);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return $other instanceof $this->className;
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
protected function failureDescription($other): string
{
return \sprintf(
'%s is an instance of %s "%s"',
$this->exporter->shortenedExport($other),
$this->getType(),
$this->className
);
}
private function getType(): string
{
try {
$reflection = new ReflectionClass($this->className);
if ($reflection->isInterface()) {
return 'interface';
}
} catch (ReflectionException $e) {
}
return 'class';
}
}
src/Framework/Constraint/ObjectHasAttribute.php 0000666 00000001535 13436756106 0015676 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use ReflectionObject;
/**
* Constraint that asserts that the object it is evaluated for has a given
* attribute.
*
* The attribute name is passed in the constructor.
*/
class ObjectHasAttribute extends ClassHasAttribute
{
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
$object = new ReflectionObject($other);
return $object->hasProperty($this->attributeName());
}
}
src/Framework/Constraint/ExceptionCode.php 0000666 00000003070 13436756106 0014675 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
class ExceptionCode extends Constraint
{
/**
* @var int|string
*/
private $expectedCode;
/**
* @param int|string $expected
*/
public function __construct($expected)
{
parent::__construct();
$this->expectedCode = $expected;
}
public function toString(): string
{
return 'exception code is ';
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param \Throwable $other
*/
protected function matches($other): bool
{
return (string) $other->getCode() === (string) $this->expectedCode;
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
protected function failureDescription($other): string
{
return \sprintf(
'%s is equal to expected exception code %s',
$this->exporter->export($other->getCode()),
$this->exporter->export($this->expectedCode)
);
}
}
src/Framework/Constraint/DirectoryExists.php 0000666 00000002513 13436756106 0015311 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that checks if the directory(name) that it is evaluated for exists.
*
* The file path to check is passed as $other in evaluate().
*/
class DirectoryExists extends Constraint
{
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return 'directory exists';
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
return \is_dir($other);
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
return \sprintf(
'directory "%s" exists',
$other
);
}
}
src/Framework/Constraint/Composite.php 0000666 00000003741 13436756106 0014113 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Framework\ExpectationFailedException;
abstract class Composite extends Constraint
{
/**
* @var Constraint
*/
private $innerConstraint;
public function __construct(Constraint $innerConstraint)
{
parent::__construct();
$this->innerConstraint = $innerConstraint;
}
/**
* Evaluates the constraint for parameter $other
*
* If $returnResult is set to false (the default), an exception is thrown
* in case of a failure. null is returned otherwise.
*
* If $returnResult is true, the result of the evaluation is returned as
* a boolean value instead: true in case of success, false in case of a
* failure.
*
* @param mixed $other value or object to evaluate
* @param string $description Additional information about the test
* @param bool $returnResult Whether to return a result or throw an exception
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*
* @return mixed
*/
public function evaluate($other, $description = '', $returnResult = false)
{
try {
return $this->innerConstraint->evaluate(
$other,
$description,
$returnResult
);
} catch (ExpectationFailedException $e) {
$this->fail($other, $description, $e->getComparisonFailure());
}
}
/**
* Counts the number of constraint elements.
*/
public function count(): int
{
return \count($this->innerConstraint);
}
protected function innerConstraint(): Constraint
{
return $this->innerConstraint;
}
}
src/Framework/Constraint/TraversableContains.php 0000666 00000006204 13436756106 0016117 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use SplObjectStorage;
/**
* Constraint that asserts that the Traversable it is applied to contains
* a given value.
*/
class TraversableContains extends Constraint
{
/**
* @var bool
*/
private $checkForObjectIdentity;
/**
* @var bool
*/
private $checkForNonObjectIdentity;
/**
* @var mixed
*/
private $value;
/**
* @param mixed $value
*
* @throws \PHPUnit\Framework\Exception
*/
public function __construct($value, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false)
{
parent::__construct();
$this->checkForObjectIdentity = $checkForObjectIdentity;
$this->checkForNonObjectIdentity = $checkForNonObjectIdentity;
$this->value = $value;
}
/**
* Returns a string representation of the constraint.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function toString(): string
{
if (\is_string($this->value) && \strpos($this->value, PHP_EOL) !== false) {
return 'contains "' . $this->value . '"';
}
return 'contains ' . $this->exporter->export($this->value);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
if ($other instanceof SplObjectStorage) {
return $other->contains($this->value);
}
if (\is_object($this->value)) {
foreach ($other as $element) {
if ($this->checkForObjectIdentity && $element === $this->value) {
return true;
}
if (!$this->checkForObjectIdentity && $element == $this->value) {
return true;
}
}
} else {
foreach ($other as $element) {
if ($this->checkForNonObjectIdentity && $element === $this->value) {
return true;
}
if (!$this->checkForNonObjectIdentity && $element == $this->value) {
return true;
}
}
}
return false;
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
protected function failureDescription($other): string
{
return \sprintf(
'%s %s',
\is_array($other) ? 'an array' : 'a traversable',
$this->toString()
);
}
}
src/Framework/Constraint/ClassHasAttribute.php 0000666 00000003630 13436756106 0015533 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use ReflectionClass;
/**
* Constraint that asserts that the class it is evaluated for has a given
* attribute.
*
* The attribute name is passed in the constructor.
*/
class ClassHasAttribute extends Constraint
{
/**
* @var string
*/
private $attributeName;
public function __construct(string $attributeName)
{
parent::__construct();
$this->attributeName = $attributeName;
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return \sprintf(
'has attribute "%s"',
$this->attributeName
);
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
$class = new ReflectionClass($other);
return $class->hasProperty($this->attributeName);
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
return \sprintf(
'%sclass "%s" %s',
\is_object($other) ? 'object of ' : '',
\is_object($other) ? \get_class($other) : $other,
$this->toString()
);
}
protected function attributeName(): string
{
return $this->attributeName;
}
}
src/Framework/Constraint/IsJson.php 0000666 00000003366 13436756106 0013361 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
/**
* Constraint that asserts that a string is valid JSON.
*/
class IsJson extends Constraint
{
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return 'is valid JSON';
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
if ($other === '') {
return false;
}
\json_decode($other);
if (\json_last_error()) {
return false;
}
return true;
}
/**
* Returns the description of the failure
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
protected function failureDescription($other): string
{
if ($other === '') {
return 'an empty string is valid JSON';
}
\json_decode($other);
$error = JsonMatchesErrorMessageProvider::determineJsonError(
\json_last_error()
);
return \sprintf(
'%s is valid JSON (%s)',
$this->exporter->shortenedExport($other),
$error
);
}
}
src/Framework/Constraint/LogicalAnd.php 0000666 00000006055 13436756106 0014147 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use PHPUnit\Framework\ExpectationFailedException;
/**
* Logical AND.
*/
class LogicalAnd extends Constraint
{
/**
* @var Constraint[]
*/
private $constraints = [];
public static function fromConstraints(Constraint ...$constraints): self
{
$constraint = new self;
$constraint->constraints = \array_values($constraints);
return $constraint;
}
/**
* @param Constraint[] $constraints
*
* @throws \PHPUnit\Framework\Exception
*/
public function setConstraints(array $constraints): void
{
$this->constraints = [];
foreach ($constraints as $constraint) {
if (!($constraint instanceof Constraint)) {
throw new \PHPUnit\Framework\Exception(
'All parameters to ' . __CLASS__ .
' must be a constraint object.'
);
}
$this->constraints[] = $constraint;
}
}
/**
* Evaluates the constraint for parameter $other
*
* If $returnResult is set to false (the default), an exception is thrown
* in case of a failure. null is returned otherwise.
*
* If $returnResult is true, the result of the evaluation is returned as
* a boolean value instead: true in case of success, false in case of a
* failure.
*
* @param mixed $other value or object to evaluate
* @param string $description Additional information about the test
* @param bool $returnResult Whether to return a result or throw an exception
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*
* @return mixed
*/
public function evaluate($other, $description = '', $returnResult = false)
{
$success = true;
foreach ($this->constraints as $constraint) {
if (!$constraint->evaluate($other, $description, true)) {
$success = false;
break;
}
}
if ($returnResult) {
return $success;
}
if (!$success) {
$this->fail($other, $description);
}
}
/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
$text = '';
foreach ($this->constraints as $key => $constraint) {
if ($key > 0) {
$text .= ' and ';
}
$text .= $constraint->toString();
}
return $text;
}
/**
* Counts the number of constraint elements.
*/
public function count(): int
{
$count = 0;
foreach ($this->constraints as $constraint) {
$count += \count($constraint);
}
return $count;
}
}
src/Framework/Constraint/StringMatchesFormatDescription.php 0000666 00000005722 13436756106 0020302 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;
use SebastianBergmann\Diff\Differ;
/**
* ...
*/
class StringMatchesFormatDescription extends RegularExpression
{
/**
* @var string
*/
private $string;
public function __construct(string $string)
{
parent::__construct(
$this->createPatternFromFormat(
$this->convertNewlines($string)
)
);
$this->string = $string;
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*
* @return bool
*/
protected function matches($other): bool
{
return parent::matches(
$this->convertNewlines($other)
);
}
protected function failureDescription($other): string
{
return 'string matches format description';
}
protected function additionalFailureDescription($other): string
{
$from = \explode(PHP_EOL, $this->string);
$to = \explode(PHP_EOL, $this->convertNewlines($other));
foreach ($from as $index => $line) {
if (isset($to[$index]) && $line !== $to[$index]) {
$line = $this->createPatternFromFormat($line);
if (\preg_match($line, $to[$index]) > 0) {
$from[$index] = $to[$index];
}
}
}
$this->string = \implode(PHP_EOL, $from);
$other = \implode(PHP_EOL, $to);
$differ = new Differ("--- Expected\n+++ Actual\n");
return $differ->diff($this->string, $other);
}
private function createPatternFromFormat(string $string): string
{
$string = \preg_replace(
[
'/(?