├── .gitignore
├── src
├── ResultPrinter
│ ├── template
│ │ ├── scenario_header.html.dist
│ │ ├── suite.html.dist
│ │ ├── fail.html.dist
│ │ ├── step.html.dist
│ │ ├── substeps.html.dist
│ │ ├── scenario.html.dist
│ │ └── scenarios.html.dist
│ ├── Report.php
│ ├── UI.php
│ └── HTML.php
├── Init.php
├── ConsolePrinter.php
├── phpunit7-interfaces.php
├── shim.php
├── DispatcherWrapper.php
├── Constraint
│ ├── CrawlerNot.php
│ ├── WebDriverNot.php
│ ├── JsonType.php
│ ├── JsonContains.php
│ ├── Page.php
│ ├── Crawler.php
│ └── WebDriver.php
├── FilterTest.php
├── TestCase.php
├── Log
│ ├── JUnit.php
│ └── PhpUnit.php
├── ResultPrinter.php
├── Overrides
│ └── Filter.php
├── NonFinal
│ ├── NameFilterIterator.php
│ ├── JUnit.php
│ └── TestRunner.php
├── Listener.php
├── Runner.php
└── phpunit5-loggers.php
├── composer.json
├── README.md
├── .github
└── workflows
│ └── build.yml
└── RoboFile.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /composer.lock
3 | /vendor
4 | /tests
5 | /codecept
6 | /codeception.yml
7 |
--------------------------------------------------------------------------------
/src/ResultPrinter/template/scenario_header.html.dist:
--------------------------------------------------------------------------------
1 |
{name} {status} ({time}s)
2 |
3 |
--------------------------------------------------------------------------------
/src/ResultPrinter/template/suite.html.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | {suite} Tests
4 | |
5 |
--------------------------------------------------------------------------------
/src/ResultPrinter/template/fail.html.dist:
--------------------------------------------------------------------------------
1 |
2 | |
3 | {fail}
4 | |
5 |
--------------------------------------------------------------------------------
/src/ResultPrinter/template/step.html.dist:
--------------------------------------------------------------------------------
1 |
2 | | {action} |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/Init.php:
--------------------------------------------------------------------------------
1 |
2 | + {metaStep}
3 |
4 | |
5 |
6 |
7 | |
8 |
11 | |
12 |
13 |
--------------------------------------------------------------------------------
/src/ConsolePrinter.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {toggle}
4 | {name} {time}s
5 | |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {steps}
13 | {failure}
14 | {png}
15 | {html}
16 |
17 |
18 |
19 | |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/shim.php:
--------------------------------------------------------------------------------
1 | =7.2",
18 | "phpunit/phpunit": "^9.0"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "Codeception\\PHPUnit\\": "src/"
23 | }
24 | },
25 | "require-dev": {
26 | "vlucas/phpdotenv": "^3.0",
27 | "codeception/specify": "*",
28 | "consolidation/robo": "^3.0.0-alpha3"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHPUnit Wrapper
2 |
3 | Builds:
4 | * 9.0 
5 | * 8.0 
6 | * 7.1 
7 | * 6.5 
8 | * 6.0 
9 |
10 |
11 | Codeception heavily relies on PHPUnit for running and managing tests.
12 | Not all PHPUnit classes fit the needs of Codeception, that's why they were extended or redefined.
13 |
14 | Releases follow major PHPUnit versions.
15 |
--------------------------------------------------------------------------------
/src/DispatcherWrapper.php:
--------------------------------------------------------------------------------
1 | dispatch($eventObject, $eventType);
23 | } else {
24 | //Symfony 4.2 or lower
25 | $dispatcher->dispatch($eventType, $eventObject);
26 | }
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | tests:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | php: [7.3, 7.4, 8.0]
12 |
13 | env:
14 | CODECEPTION_VERSION: 'dev-4.2-backport-useless-test-event as 4.2.0'
15 |
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v2
19 |
20 | - name: Setup PHP
21 | uses: shivammathur/setup-php@v2
22 | with:
23 | php-version: ${{ matrix.php }}
24 | coverage: xdebug
25 |
26 | - name: Prepare dependencies
27 | run: |
28 | composer update
29 | php ./vendor/bin/robo prepare:dependencies
30 | composer update --prefer-source
31 | php ./vendor/bin/robo prepare:tests
32 | php ./vendor/bin/robo prepare:test-autoloading
33 | composer dump-autoload
34 |
35 | - name: Run test suite
36 | run: |
37 | php ./codecept run -c vendor/codeception/module-asserts/
38 | php ./codecept run unit -g core
39 | php ./codecept run cli
40 |
--------------------------------------------------------------------------------
/src/Constraint/CrawlerNot.php:
--------------------------------------------------------------------------------
1 | string) {
16 | throw new \PHPUnit\Framework\ExpectationFailedException(
17 | "Element '$selector' was found",
18 | $comparisonFailure
19 | );
20 | }
21 | /** @var $nodes DomCrawler * */
22 |
23 | $output = "There was '$selector' element";
24 | $output .= $this->uriMessage('on page');
25 | $output .= $this->nodesList($nodes, $this->string);
26 | $output .= "\ncontaining '{$this->string}'";
27 |
28 | throw new \PHPUnit\Framework\ExpectationFailedException(
29 | $output,
30 | $comparisonFailure
31 | );
32 | }
33 |
34 | public function toString() : string
35 | {
36 | if ($this->string) {
37 | return 'that contains text "' . $this->string . '"';
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Constraint/WebDriverNot.php:
--------------------------------------------------------------------------------
1 | string) {
20 | throw new \PHPUnit\Framework\ExpectationFailedException(
21 | "Element $selector was found",
22 | $comparisonFailure
23 | );
24 | }
25 |
26 | $output = "There was $selector element";
27 | $output .= $this->uriMessage("on page");
28 | $output .= $this->nodesList($nodes, $this->string);
29 | $output .= "\ncontaining '{$this->string}'";
30 |
31 | throw new \PHPUnit\Framework\ExpectationFailedException(
32 | $output,
33 | $comparisonFailure
34 | );
35 | }
36 |
37 | public function toString() : string
38 | {
39 | if ($this->string) {
40 | return 'that contains text "' . $this->string . '"';
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/FilterTest.php:
--------------------------------------------------------------------------------
1 | getInnerIterator()->current();
18 |
19 | if ($test instanceof \PHPUnit\Framework\TestSuite) {
20 | return true;
21 | }
22 |
23 | $name = Descriptor::getTestSignature($test);
24 | $index = Descriptor::getTestDataSetIndex($test);
25 |
26 | if (!is_null($index)) {
27 | $name .= " with data set #{$index}";
28 | }
29 |
30 | $accepted = preg_match($this->filter, $name, $matches);
31 |
32 | // This fix the issue when an invalid dataprovider method generate a warning
33 | // See issue https://github.com/Codeception/Codeception/issues/4888
34 | if($test instanceof \PHPUnit\Framework\WarningTestCase) {
35 | $message = $test->getMessage();
36 | $accepted = preg_match($this->filter, $message, $matches);
37 | }
38 |
39 | if ($accepted && isset($this->filterMax)) {
40 | $set = end($matches);
41 | $accepted = $set >= $this->filterMin && $set <= $this->filterMax;
42 | }
43 | return $accepted;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/TestCase.php:
--------------------------------------------------------------------------------
1 | _setUp();
13 | }
14 | }
15 |
16 | protected function tearDown(): void
17 | {
18 | if (method_exists($this, '_tearDown')) {
19 | $this->_tearDown();
20 | }
21 | }
22 |
23 | public static function setUpBeforeClass(): void
24 | {
25 | if (method_exists(get_called_class(), '_setUpBeforeClass')) {
26 | static::_setUpBeforeClass();
27 | }
28 | }
29 |
30 | public static function tearDownAfterClass(): void
31 | {
32 | if (method_exists(get_called_class(), '_tearDownAfterClass')) {
33 | static::_tearDownAfterClass();
34 | }
35 | }
36 |
37 | public function expectExceptionMessageRegExp(string $regularExpression): void
38 | {
39 | $this->expectExceptionMessageMatches($regularExpression);
40 | }
41 |
42 | public static function assertRegExp(string $pattern, string $string, string $message = ''): void
43 | {
44 | parent::assertMatchesRegularExpression($pattern, $string, $message);
45 | }
46 |
47 | public static function assertNotRegExp(string $pattern, string $string, string $message = ''): void
48 | {
49 | parent::assertDoesNotMatchRegularExpression($pattern, $string, $message);
50 | }
51 |
52 | public static function assertFileNotExists(string $filename, string $message = ''): void
53 | {
54 | parent::assertFileDoesNotExist($filename, $message);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Constraint/JsonType.php:
--------------------------------------------------------------------------------
1 | jsonType = $jsonType;
16 | $this->match = $match;
17 | }
18 |
19 | /**
20 | * Evaluates the constraint for parameter $other. Returns true if the
21 | * constraint is met, false otherwise.
22 | *
23 | * @param mixed $jsonArray Value or object to evaluate.
24 | *
25 | * @return bool
26 | */
27 | protected function matches($jsonArray) : bool
28 | {
29 | if ($jsonArray instanceof JsonArray) {
30 | $jsonArray = $jsonArray->toArray();
31 | }
32 |
33 | $matched = (new JsonTypeUtil($jsonArray))->matches($this->jsonType);
34 |
35 | if ($this->match) {
36 | if ($matched !== true) {
37 | throw new \PHPUnit\Framework\ExpectationFailedException($matched);
38 | }
39 | } else {
40 | if ($matched === true) {
41 | throw new \PHPUnit\Framework\ExpectationFailedException('Unexpectedly response matched: ' . json_encode($jsonArray));
42 | }
43 | }
44 | return true;
45 | }
46 |
47 | /**
48 | * Returns a string representation of the constraint.
49 | *
50 | * @return string
51 | */
52 | public function toString() : string
53 | {
54 | //unused
55 | return '';
56 | }
57 |
58 | protected function failureDescription($other) : string
59 | {
60 | //unused
61 | return '';
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Constraint/JsonContains.php:
--------------------------------------------------------------------------------
1 | expected = $expected;
20 | }
21 |
22 | /**
23 | * Evaluates the constraint for parameter $other. Returns true if the
24 | * constraint is met, false otherwise.
25 | *
26 | * @param mixed $other Value or object to evaluate.
27 | *
28 | * @return bool
29 | */
30 | protected function matches($other) : bool
31 | {
32 | $jsonResponseArray = new JsonArray($other);
33 | if (!is_array($jsonResponseArray->toArray())) {
34 | throw new \PHPUnit\Framework\AssertionFailedError('JSON response is not an array: ' . $other);
35 | }
36 |
37 | if ($jsonResponseArray->containsArray($this->expected)) {
38 | return true;
39 | }
40 |
41 | $comparator = new ArrayComparator();
42 | $comparator->setFactory(new Factory);
43 | try {
44 | $comparator->assertEquals($this->expected, $jsonResponseArray->toArray());
45 | } catch (ComparisonFailure $failure) {
46 | throw new \PHPUnit\Framework\ExpectationFailedException(
47 | "Response JSON does not contain the provided JSON\n",
48 | $failure
49 | );
50 | }
51 |
52 | return false;
53 | }
54 |
55 | /**
56 | * Returns a string representation of the constraint.
57 | *
58 | * @return string
59 | */
60 | public function toString() : string
61 | {
62 | //unused
63 | return '';
64 | }
65 |
66 | protected function failureDescription($other) : string
67 | {
68 | //unused
69 | return '';
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/RoboFile.php:
--------------------------------------------------------------------------------
1 | '*'];
23 |
24 | file_put_contents(__DIR__ . '/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
25 | }
26 |
27 | public function prepareTests()
28 | {
29 | $this->_copyDir(__DIR__ . '/vendor/codeception/codeception/tests', __DIR__ . '/tests');
30 | $this->_copy(__DIR__ . '/vendor/codeception/codeception/codeception.yml', __DIR__ .'/codeception.yml');
31 | $this->_symlink(__DIR__ . '/vendor/bin/codecept', __DIR__ . '/codecept');
32 | }
33 |
34 | public function prepareTestAutoloading()
35 | {
36 | $config = json_decode(file_get_contents(__DIR__ . '/composer.json'), true);
37 | $config['autoload-dev'] = [
38 | 'classmap' => [
39 | 'tests/cli/_steps',
40 | 'tests/data/DummyClass.php',
41 | 'tests/data/claypit/tests/_data'
42 | ]
43 | ];
44 | file_put_contents(__DIR__ . '/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Log/JUnit.php:
--------------------------------------------------------------------------------
1 | currentTestCase = $this->document->createElement('testcase');
21 |
22 | $isStrict = Configuration::config()['settings']['strict_xml'];
23 |
24 | foreach ($test->getReportFields() as $attr => $value) {
25 | if ($isStrict and !in_array($attr, $this->strictAttributes)) {
26 | continue;
27 | }
28 | $this->currentTestCase->setAttribute($attr, $value);
29 | }
30 | }
31 |
32 | public function endTest(\PHPUnit\Framework\Test $test, float $time):void
33 | {
34 | if ($this->currentTestCase !== null and $test instanceof Test) {
35 | $numAssertions = $test->getNumAssertions();
36 | $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
37 |
38 | $this->currentTestCase->setAttribute(
39 | 'assertions',
40 | $numAssertions
41 | );
42 | }
43 |
44 | if ($test instanceof TestCase) {
45 | parent::endTest($test, $time);
46 | return;
47 | }
48 |
49 | // since PhpUnit 7.4.0, parent::endTest ignores tests that aren't instances of TestCase
50 | // so I copied this code from PhpUnit 7.3.5
51 |
52 | $this->currentTestCase->setAttribute(
53 | 'time',
54 | \sprintf('%F', $time)
55 | );
56 | $this->testSuites[$this->testSuiteLevel]->appendChild(
57 | $this->currentTestCase
58 | );
59 | $this->testSuiteTests[$this->testSuiteLevel]++;
60 | $this->testSuiteTimes[$this->testSuiteLevel] += $time;
61 | $this->currentTestCase = null;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Constraint/Page.php:
--------------------------------------------------------------------------------
1 | string = $this->normalizeText((string)$string);
14 | $this->uri = $uri;
15 | }
16 |
17 | /**
18 | * Evaluates the constraint for parameter $other. Returns true if the
19 | * constraint is met, false otherwise.
20 | *
21 | * @param mixed $other Value or object to evaluate.
22 | *
23 | * @return bool
24 | */
25 | protected function matches($other) : bool
26 | {
27 | $other = $this->normalizeText($other);
28 | return mb_stripos($other, $this->string, 0, 'UTF-8') !== false;
29 | }
30 |
31 | /**
32 | * @param $text
33 | * @return string
34 | */
35 | private function normalizeText($text)
36 | {
37 | $text = strtr($text, "\r\n", " ");
38 | return trim(preg_replace('/\\s{2,}/', ' ', $text));
39 | }
40 |
41 | /**
42 | * Returns a string representation of the constraint.
43 | *
44 | * @return string
45 | */
46 | public function toString() : string
47 | {
48 | return sprintf(
49 | 'contains "%s"',
50 | $this->string
51 | );
52 | }
53 |
54 | protected function failureDescription($pageContent) : string
55 | {
56 | $message = $this->uriMessage('on page');
57 | $message->append("\n--> ");
58 | $message->append(mb_substr($pageContent, 0, 300, 'utf-8'));
59 | if (mb_strlen($pageContent, 'utf-8') > 300) {
60 | $debugMessage = new Message(
61 | "[Content too long to display. See complete response in '" . codecept_output_dir() . "' directory]"
62 | );
63 | $message->append("\n")->append($debugMessage);
64 | }
65 | $message->append("\n--> ");
66 | return $message->getMessage() . $this->toString();
67 | }
68 |
69 | protected function uriMessage($onPage = "")
70 | {
71 | if (!$this->uri) {
72 | return new Message('');
73 | }
74 | $message = new Message($this->uri);
75 | $message->prepend(" $onPage ");
76 | return $message;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/ResultPrinter/Report.php:
--------------------------------------------------------------------------------
1 | testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED);
19 | if ($success) {
20 | $this->successful++;
21 | }
22 |
23 | if ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE) {
24 | $status = "\033[41;37mFAIL\033[0m";
25 | } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED) {
26 | $status = 'Skipped';
27 | } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) {
28 | $status = 'Incomplete';
29 | } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY) {
30 | $status = 'Useless';
31 | } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR) {
32 | $status = 'ERROR';
33 | } else {
34 | $status = 'Ok';
35 | }
36 |
37 | if (strlen($name) > 75) {
38 | $name = substr($name, 0, 70);
39 | }
40 | $line = $name . str_repeat('.', 75 - strlen($name));
41 | $line .= $status;
42 |
43 | $this->write($line . "\n");
44 | }
45 |
46 | protected function endRun() : void
47 | {
48 | }
49 |
50 | public function printResult(\PHPUnit\Framework\TestResult $result): void
51 | {
52 | $this->write("\nCodeception Results\n");
53 | $this->write(sprintf(
54 | "Successful: %d. Failed: %d. Incomplete: %d. Skipped: %d. Useless: %d",
55 | $this->successful,
56 | $this->failed,
57 | $this->incomplete,
58 | $this->skipped,
59 | $this->risky
60 | ) . "\n");
61 | }
62 |
63 | public function write(string $buffer) : void
64 | {
65 | parent::write($buffer);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Constraint/Crawler.php:
--------------------------------------------------------------------------------
1 | count()) {
15 | return false;
16 | }
17 | if ($this->string === '') {
18 | return true;
19 | }
20 |
21 | foreach ($nodes as $node) {
22 | if (parent::matches($node->nodeValue)) {
23 | return true;
24 | }
25 | }
26 | return false;
27 | }
28 |
29 | protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null):void
30 | {
31 | /** @var $nodes DomCrawler * */
32 | if (!$nodes->count()) {
33 | throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath');
34 | }
35 |
36 | $output = "Failed asserting that any element by '$selector'";
37 | $output .= $this->uriMessage('on page');
38 | $output .= " ";
39 |
40 | if ($nodes->count() < 10) {
41 | $output .= $this->nodesList($nodes);
42 | } else {
43 | $message = new Message("[total %s elements]");
44 | $output .= $message->with($nodes->count())->getMessage();
45 | }
46 | $output .= "\ncontains text '{$this->string}'";
47 |
48 | throw new \PHPUnit\Framework\ExpectationFailedException(
49 | $output,
50 | $comparisonFailure
51 | );
52 | }
53 |
54 | protected function failureDescription($other) : string
55 | {
56 | $desc = '';
57 | foreach ($other as $o) {
58 | $desc .= parent::failureDescription($o->textContent);
59 | }
60 | return $desc;
61 | }
62 |
63 | protected function nodesList(DomCrawler $nodes, $contains = null)
64 | {
65 | $output = "";
66 | foreach ($nodes as $node) {
67 | if ($contains && strpos($node->nodeValue, $contains) === false) {
68 | continue;
69 | }
70 | $output .= "\n+ " . $node->C14N();
71 | }
72 | return $output;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Constraint/WebDriver.php:
--------------------------------------------------------------------------------
1 | string === '') {
18 | return true;
19 | }
20 |
21 | foreach ($nodes as $node) {
22 | /** @var $node \WebDriverElement * */
23 | if (!$node->isDisplayed()) {
24 | continue;
25 | }
26 | if (parent::matches(htmlspecialchars_decode($node->getText()))) {
27 | return true;
28 | }
29 | }
30 | return false;
31 | }
32 |
33 | protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null) : void
34 | {
35 | if (!count($nodes)) {
36 | throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath');
37 | }
38 |
39 | $output = "Failed asserting that any element by " . Locator::humanReadableString($selector);
40 | $output .= $this->uriMessage('on page');
41 |
42 | if (count($nodes) < 5) {
43 | $output .= "\nElements: ";
44 | $output .= $this->nodesList($nodes);
45 | } else {
46 | $message = new Message("[total %s elements]");
47 | $output .= $message->with(count($nodes));
48 | }
49 | $output .= "\ncontains text '" . $this->string . "'";
50 |
51 | throw new \PHPUnit\Framework\ExpectationFailedException(
52 | $output,
53 | $comparisonFailure
54 | );
55 | }
56 |
57 | protected function failureDescription($nodes) : string
58 | {
59 | $desc = '';
60 | foreach ($nodes as $node) {
61 | $desc .= parent::failureDescription($node->getText());
62 | }
63 | return $desc;
64 | }
65 |
66 | protected function nodesList($nodes, $contains = null)
67 | {
68 | $output = "";
69 | foreach ($nodes as $node) {
70 | if ($contains && strpos($node->getText(), $contains) === false) {
71 | continue;
72 | }
73 | /** @var $node \WebDriverElement * */
74 | $message = new Message("\n+ <%s> %s");
75 | $output .= $message->with($node->getTagName(), $node->getText());
76 | }
77 | return $output;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/ResultPrinter.php:
--------------------------------------------------------------------------------
1 | testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR;
21 | $this->failed++;
22 | }
23 |
24 | /**
25 | * A failure occurred.
26 | *
27 | * @param \PHPUnit\Framework\Test $test
28 | * @param \PHPUnit\Framework\AssertionFailedError $e
29 | * @param float $time
30 | */
31 | public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void
32 | {
33 | $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE;
34 | $this->failed++;
35 | }
36 |
37 | /**
38 | * A warning occurred.
39 | *
40 | * @param \PHPUnit\Framework\Test $test
41 | * @param \PHPUnit\Framework\Warning $e
42 | * @param float $time
43 | */
44 | public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void
45 | {
46 | $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING;
47 | $this->warned++;
48 | }
49 |
50 | /**
51 | * Incomplete test.
52 | *
53 | * @param \PHPUnit\Framework\Test $test
54 | * @param \Throwable $e
55 | * @param float $time
56 | */
57 | public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
58 | {
59 | $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE;
60 | $this->incomplete++;
61 | }
62 |
63 | /**
64 | * Risky test.
65 | *
66 | * @param \PHPUnit\Framework\Test $test
67 | * @param \Throwable $e
68 | * @param float $time
69 | *
70 | * @since Method available since Release 4.0.0
71 | */
72 | public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
73 | {
74 | $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY;
75 | $this->risky++;
76 | }
77 |
78 | /**
79 | * Skipped test.
80 | *
81 | * @param \PHPUnit\Framework\Test $test
82 | * @param \Throwable $e
83 | * @param float $time
84 | *
85 | * @since Method available since Release 3.0.0
86 | */
87 | public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
88 | {
89 | $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED;
90 | $this->skipped++;
91 | }
92 |
93 | public function startTest(\PHPUnit\Framework\Test $test) : void
94 | {
95 | $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED;
96 | }
97 |
98 | public function printResult(TestResult $result): void
99 | {
100 | // TODO: Implement printResult() method.
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Overrides/Filter.php:
--------------------------------------------------------------------------------
1 | getPrevious() ? $e->getPrevious()->getTrace() : $e->getTrace();
18 | if ($e instanceof \PHPUnit\Framework\ExceptionWrapper) {
19 | $trace = $e->getSerializableTrace();
20 | }
21 |
22 | $eFile = $e->getFile();
23 | $eLine = $e->getLine();
24 |
25 | if (!self::frameExists($trace, $eFile, $eLine)) {
26 | array_unshift(
27 | $trace,
28 | ['file' => $eFile, 'line' => $eLine]
29 | );
30 | }
31 |
32 | foreach ($trace as $step) {
33 | if (self::classIsFiltered($step) and $filter) {
34 | continue;
35 | }
36 | if (self::fileIsFiltered($step) and $filter) {
37 | continue;
38 | }
39 |
40 | if (!$asString) {
41 | $stackTrace[] = $step;
42 | continue;
43 | }
44 | if (!isset($step['file'])) {
45 | continue;
46 | }
47 |
48 | $stackTrace .= $step['file'] . ':' . $step['line'] . "\n";
49 | }
50 |
51 | return $stackTrace;
52 | }
53 |
54 | protected static function classIsFiltered($step)
55 | {
56 | if (!isset($step['class'])) {
57 | return false;
58 | }
59 | $className = $step['class'];
60 |
61 | foreach (self::$filteredClassesPattern as $filteredClassName) {
62 | if (strpos($className, $filteredClassName) === 0) {
63 | return true;
64 | }
65 | }
66 | return false;
67 | }
68 |
69 | protected static function fileIsFiltered($step)
70 | {
71 | if (!isset($step['file'])) {
72 | return false;
73 | }
74 |
75 | if (strpos($step['file'], 'codecept.phar/') !== false) {
76 | return true;
77 | }
78 |
79 | if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'phpunit') !== false) {
80 | return true;
81 | }
82 |
83 | if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'codeception') !== false) {
84 | return true;
85 | }
86 |
87 | $modulePath = 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR . 'Module';
88 | if (strpos($step['file'], $modulePath) !== false) {
89 | return false; // don`t filter modules
90 | }
91 |
92 | if (strpos($step['file'], 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR) !== false) {
93 | return true;
94 | }
95 |
96 | return false;
97 | }
98 |
99 | /**
100 | * @param array $trace
101 | * @param string $file
102 | * @param int $line
103 | *
104 | * @return bool
105 | */
106 | private static function frameExists(array $trace, $file, $line)
107 | {
108 | foreach ($trace as $frame) {
109 | if (isset($frame['file']) && $frame['file'] == $file &&
110 | isset($frame['line']) && $frame['line'] == $line) {
111 | return true;
112 | }
113 | }
114 |
115 | return false;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/ResultPrinter/UI.php:
--------------------------------------------------------------------------------
1 | OutputInterface::VERBOSITY_NORMAL, $options['colors'] ? 'always' : 'never');
23 | $this->dispatcher = $dispatcher;
24 | }
25 |
26 | protected function printDefect(\PHPUnit\Framework\TestFailure $defect, int $count): void
27 | {
28 | $this->write("\n---------\n");
29 | $this->dispatch(
30 | $this->dispatcher,
31 | Events::TEST_FAIL_PRINT,
32 | new FailEvent($defect->failedTest(), null, $defect->thrownException(), $count)
33 | );
34 | }
35 |
36 | /**
37 | * @param \PHPUnit\Framework\TestFailure $defect
38 | */
39 | protected function printDefectTrace(\PHPUnit\Framework\TestFailure $defect): void
40 | {
41 | $this->write($defect->getExceptionAsString());
42 | $this->writeNewLine();
43 |
44 | $stackTrace = \PHPUnit\Util\Filter::getFilteredStacktrace($defect->thrownException());
45 |
46 | foreach ($stackTrace as $i => $frame) {
47 | if (!isset($frame['file'])) {
48 | continue;
49 | }
50 |
51 | $this->write(
52 | sprintf(
53 | "#%d %s(%s)",
54 | $i + 1,
55 | $frame['file'],
56 | isset($frame['line']) ? $frame['line'] : '?'
57 | )
58 | );
59 |
60 | $this->writeNewLine();
61 | }
62 | }
63 |
64 | public function startTest(\PHPUnit\Framework\Test $test) : void
65 | {
66 | if ($test instanceof Unit) {
67 | parent::startTest($test);
68 | }
69 | }
70 |
71 | public function endTest(\PHPUnit\Framework\Test $test, float $time) : void
72 | {
73 | if ($test instanceof \PHPUnit\Framework\TestCase or $test instanceof \Codeception\Test\Test) {
74 | $this->numAssertions += $test->getNumAssertions();
75 | }
76 |
77 | $this->lastTestFailed = false;
78 | }
79 |
80 | public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
81 | {
82 | $this->lastTestFailed = true;
83 | }
84 |
85 | public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void
86 | {
87 | $this->lastTestFailed = true;
88 | }
89 |
90 | public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void
91 | {
92 | $this->lastTestFailed = true;
93 | }
94 |
95 | public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
96 | {
97 | $this->lastTestFailed = true;
98 | }
99 |
100 | public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
101 | {
102 | $this->lastTestFailed = true;
103 | }
104 |
105 | public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
106 | {
107 | $this->lastTestFailed = true;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/NonFinal/NameFilterIterator.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Codeception\PHPUnit\NonFinal;
11 |
12 | use PHPUnit\Framework\TestSuite;
13 | use PHPUnit\Framework\WarningTestCase;
14 | use PHPUnit\Util\RegularExpression;
15 | use RecursiveFilterIterator;
16 | use RecursiveIterator;
17 |
18 | /**
19 | * @internal This class is not covered by the backward compatibility promise for PHPUnit
20 | */
21 | class NameFilterIterator extends RecursiveFilterIterator
22 | {
23 | /**
24 | * @var string
25 | */
26 | protected $filter;
27 |
28 | /**
29 | * @var int
30 | */
31 | protected $filterMin;
32 |
33 | /**
34 | * @var int
35 | */
36 | protected $filterMax;
37 |
38 | /**
39 | * @throws \Exception
40 | */
41 | public function __construct(RecursiveIterator $iterator, string $filter)
42 | {
43 | parent::__construct($iterator);
44 |
45 | $this->setFilter($filter);
46 | }
47 |
48 | /**
49 | * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
50 | */
51 | public function accept(): bool
52 | {
53 | $test = $this->getInnerIterator()->current();
54 |
55 | if ($test instanceof TestSuite) {
56 | return true;
57 | }
58 |
59 | $tmp = \PHPUnit\Util\Test::describe($test);
60 |
61 | if ($test instanceof WarningTestCase) {
62 | $name = $test->getMessage();
63 | } else {
64 | if ($tmp[0] !== '') {
65 | $name = \implode('::', $tmp);
66 | } else {
67 | $name = $tmp[1];
68 | }
69 | }
70 |
71 | $accepted = @\preg_match($this->filter, $name, $matches);
72 |
73 | if ($accepted && isset($this->filterMax)) {
74 | $set = \end($matches);
75 | $accepted = $set >= $this->filterMin && $set <= $this->filterMax;
76 | }
77 |
78 | return (bool) $accepted;
79 | }
80 |
81 | /**
82 | * @throws \Exception
83 | */
84 | protected function setFilter(string $filter): void
85 | {
86 | if (RegularExpression::safeMatch($filter, '') === false) {
87 | // Handles:
88 | // * testAssertEqualsSucceeds#4
89 | // * testAssertEqualsSucceeds#4-8
90 | if (\preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
91 | if (isset($matches[3]) && $matches[2] < $matches[3]) {
92 | $filter = \sprintf(
93 | '%s.*with data set #(\d+)$',
94 | $matches[1]
95 | );
96 |
97 | $this->filterMin = $matches[2];
98 | $this->filterMax = $matches[3];
99 | } else {
100 | $filter = \sprintf(
101 | '%s.*with data set #%s$',
102 | $matches[1],
103 | $matches[2]
104 | );
105 | }
106 | } // Handles:
107 | // * testDetermineJsonError@JSON_ERROR_NONE
108 | // * testDetermineJsonError@JSON.*
109 | elseif (\preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
110 | $filter = \sprintf(
111 | '%s.*with data set "%s"$',
112 | $matches[1],
113 | $matches[2]
114 | );
115 | }
116 |
117 | // Escape delimiters in regular expression. Do NOT use preg_quote,
118 | // to keep magic characters.
119 | $filter = \sprintf('/%s/i', \str_replace(
120 | '/',
121 | '\\/',
122 | $filter
123 | ));
124 | }
125 |
126 | $this->filter = $filter;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Log/PhpUnit.php:
--------------------------------------------------------------------------------
1 | getFileName();
24 | } else {
25 | $reflector = new \ReflectionClass($test);
26 | $filename = $reflector->getFileName();
27 | }
28 |
29 | if ($filename !== $this->currentFile) {
30 | if ($this->currentFile !== null) {
31 | parent::endTestSuite(new TestSuite());
32 | }
33 |
34 | //initialize all values to avoid warnings
35 | $this->testSuiteAssertions[self::FILE_LEVEL] = 0;
36 | $this->testSuiteTests[self::FILE_LEVEL] = 0;
37 | $this->testSuiteTimes[self::FILE_LEVEL] = 0;
38 | $this->testSuiteErrors[self::FILE_LEVEL] = 0;
39 | $this->testSuiteFailures[self::FILE_LEVEL] = 0;
40 | $this->testSuiteSkipped[self::FILE_LEVEL] = 0;
41 |
42 | $this->testSuiteLevel = self::FILE_LEVEL;
43 |
44 | $this->currentFile = $filename;
45 | $this->currentFileSuite = $this->document->createElement('testsuite');
46 |
47 | if ($test instanceof Reported) {
48 | $reportFields = $test->getReportFields();
49 | $class = isset($reportFields['class']) ? $reportFields['class'] : $reportFields['name'];
50 | $this->currentFileSuite->setAttribute('name', $class);
51 | } else {
52 | $this->currentFileSuite->setAttribute('name', get_class($test));
53 | }
54 |
55 | $this->currentFileSuite->setAttribute('file', $filename);
56 |
57 | $this->testSuites[self::SUITE_LEVEL]->appendChild($this->currentFileSuite);
58 | $this->testSuites[self::FILE_LEVEL] = $this->currentFileSuite;
59 | }
60 |
61 | if (!$test instanceof Reported) {
62 | parent::startTest($test);
63 | return;
64 | }
65 |
66 | $this->currentTestCase = $this->document->createElement('testcase');
67 |
68 | $isStrict = Configuration::config()['settings']['strict_xml'];
69 |
70 | foreach ($test->getReportFields() as $attr => $value) {
71 | if ($isStrict and !in_array($attr, $this->strictAttributes)) {
72 | continue;
73 | }
74 | $this->currentTestCase->setAttribute($attr, $value);
75 | }
76 | }
77 |
78 | public function endTest(\PHPUnit\Framework\Test $test, float $time):void
79 | {
80 | if ($this->currentTestCase !== null && $test instanceof Test) {
81 | $numAssertions = $test->getNumAssertions();
82 | $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
83 |
84 | $this->currentTestCase->setAttribute(
85 | 'assertions',
86 | $numAssertions
87 | );
88 | }
89 |
90 | if ($test instanceof TestCase) {
91 | parent::endTest($test, $time);
92 | return;
93 | }
94 |
95 | // In PhpUnit 7.4.*, parent::endTest ignores tests that aren't instances of TestCase
96 | // so I copied this code from PhpUnit 7.3.5
97 |
98 | $this->currentTestCase->setAttribute(
99 | 'time',
100 | \sprintf('%F', $time)
101 | );
102 | $this->testSuites[$this->testSuiteLevel]->appendChild(
103 | $this->currentTestCase
104 | );
105 | $this->testSuiteTests[$this->testSuiteLevel]++;
106 | $this->testSuiteTimes[$this->testSuiteLevel] += $time;
107 | $this->currentTestCase = null;
108 | }
109 |
110 | /**
111 | * Cleans the mess caused by test suite manipulation in startTest
112 | */
113 | public function endTestSuite(TestSuite $suite): void
114 | {
115 | if ($suite->getName()) {
116 | if ($this->currentFile) {
117 | //close last file in the test suite
118 | parent::endTestSuite(new TestSuite());
119 | $this->currentFile = null;
120 | }
121 | $this->testSuiteLevel = self::SUITE_LEVEL;
122 | }
123 | parent::endTestSuite($suite);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Listener.php:
--------------------------------------------------------------------------------
1 | dispatcher = $dispatcher;
28 | }
29 |
30 | /**
31 | * Risky test.
32 | *
33 | * @param PHPUnit\Framework\Test $test
34 | * @param \Throwable $e
35 | * @param float $time
36 | * @since Method available since Release 4.0.0
37 | */
38 | public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
39 | {
40 | $this->unsuccessfulTests[] = spl_object_hash($test);
41 | $this->fire('test.useless', new FailEvent($test, $time, $e));
42 | }
43 |
44 | public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void
45 | {
46 | $this->unsuccessfulTests[] = spl_object_hash($test);
47 | $this->fire(Events::TEST_FAIL, new FailEvent($test, $time, $e));
48 | }
49 |
50 | public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
51 | {
52 | $this->unsuccessfulTests[] = spl_object_hash($test);
53 | $this->fire(Events::TEST_ERROR, new FailEvent($test, $time, $e));
54 | }
55 |
56 | // This method was added in PHPUnit 6
57 | public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void
58 | {
59 | $this->unsuccessfulTests[] = spl_object_hash($test);
60 | $this->fire(Events::TEST_WARNING, new FailEvent($test, $time, $e));
61 | }
62 |
63 | public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
64 | {
65 | if (in_array(spl_object_hash($test), $this->skippedTests)) {
66 | return;
67 | }
68 | $this->unsuccessfulTests[] = spl_object_hash($test);
69 | $this->fire(Events::TEST_INCOMPLETE, new FailEvent($test, $time, $e));
70 | $this->skippedTests[] = spl_object_hash($test);
71 | }
72 |
73 | public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
74 | {
75 | if (in_array(spl_object_hash($test), $this->skippedTests)) {
76 | return;
77 | }
78 | $this->unsuccessfulTests[] = spl_object_hash($test);
79 | $this->fire(Events::TEST_SKIPPED, new FailEvent($test, $time, $e));
80 | $this->skippedTests[] = spl_object_hash($test);
81 | }
82 |
83 | public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void
84 | {
85 | $this->dispatch($this->dispatcher, 'suite.start', new SuiteEvent($suite));
86 | }
87 |
88 | public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void
89 | {
90 | $this->dispatch($this->dispatcher, 'suite.end', new SuiteEvent($suite));
91 | }
92 |
93 | public function startTest(\PHPUnit\Framework\Test $test) : void
94 | {
95 | $this->dispatch($this->dispatcher, Events::TEST_START, new TestEvent($test));
96 | if (!$test instanceof TestInterface) {
97 | return;
98 | }
99 | if ($test->getMetadata()->isBlocked()) {
100 | return;
101 | }
102 |
103 | try {
104 | $this->startedTests[] = spl_object_hash($test);
105 | $this->fire(Events::TEST_BEFORE, new TestEvent($test));
106 | } catch (\PHPUnit\Framework\IncompleteTestError $e) {
107 | $test->getTestResultObject()->addFailure($test, $e, 0);
108 | } catch (\PHPUnit\Framework\SkippedTestError $e) {
109 | $test->getTestResultObject()->addFailure($test, $e, 0);
110 | } catch (\Throwable $e) {
111 | $test->getTestResultObject()->addError($test, $e, 0);
112 | }
113 | }
114 |
115 | public function endTest(\PHPUnit\Framework\Test $test, float $time) : void
116 | {
117 | $hash = spl_object_hash($test);
118 | if (!in_array($hash, $this->unsuccessfulTests)) {
119 | $this->fire(Events::TEST_SUCCESS, new TestEvent($test, $time));
120 | }
121 | if (in_array($hash, $this->startedTests)) {
122 | $this->fire(Events::TEST_AFTER, new TestEvent($test, $time));
123 | }
124 |
125 | $this->dispatch($this->dispatcher, Events::TEST_END, new TestEvent($test, $time));
126 | }
127 |
128 | protected function fire($event, TestEvent $eventType)
129 | {
130 | $test = $eventType->getTest();
131 | if ($test instanceof TestInterface) {
132 | foreach ($test->getMetadata()->getGroups() as $group) {
133 | $this->dispatch($this->dispatcher, $event . '.' . $group, $eventType);
134 | }
135 | }
136 | $this->dispatch($this->dispatcher, $event, $eventType);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/Runner.php:
--------------------------------------------------------------------------------
1 | false,
13 | 'phpunit-xml' => false,
14 | 'html' => false,
15 | 'tap' => false,
16 | 'json' => false,
17 | 'report' => false
18 | ];
19 |
20 | protected $config = [];
21 |
22 | protected $logDir = null;
23 |
24 | public function __construct()
25 | {
26 | $this->config = Configuration::config();
27 | $this->logDir = Configuration::outputDir(); // prepare log dir
28 | $this->phpUnitOverriders();
29 | parent::__construct();
30 | }
31 |
32 | public function phpUnitOverriders()
33 | {
34 | require_once __DIR__ . DIRECTORY_SEPARATOR . 'Overrides/Filter.php';
35 | }
36 |
37 | /**
38 | * @return null|\PHPUnit\TextUI\ResultPrinter
39 | */
40 | public function getPrinter()
41 | {
42 | return $this->printer;
43 | }
44 |
45 | public function prepareSuite(\PHPUnit\Framework\Test $suite, array &$arguments)
46 | {
47 | $this->handleConfiguration($arguments);
48 |
49 | $filterAdded = false;
50 |
51 | $filterFactory = new \PHPUnit\Runner\Filter\Factory();
52 | if ($arguments['groups']) {
53 | $filterAdded = true;
54 | $filterFactory->addFilter(
55 | new \ReflectionClass('PHPUnit\Runner\Filter\IncludeGroupFilterIterator'),
56 | $arguments['groups']
57 | );
58 | }
59 |
60 | if ($arguments['excludeGroups']) {
61 | $filterAdded = true;
62 | $filterFactory->addFilter(
63 | new \ReflectionClass('PHPUnit\Runner\Filter\ExcludeGroupFilterIterator'),
64 | $arguments['excludeGroups']
65 | );
66 | }
67 |
68 | if ($arguments['filter']) {
69 | $filterAdded = true;
70 | $filterFactory->addFilter(
71 | new \ReflectionClass('Codeception\PHPUnit\FilterTest'),
72 | $arguments['filter']
73 | );
74 | }
75 |
76 | if ($filterAdded) {
77 | $suite->injectFilter($filterFactory);
78 | }
79 | }
80 |
81 | public function doEnhancedRun(
82 | \PHPUnit\Framework\Test $suite,
83 | \PHPUnit\Framework\TestResult $result,
84 | array $arguments = []
85 | ) {
86 | unset($GLOBALS['app']); // hook for not to serialize globals
87 |
88 | $result->convertErrorsToExceptions(false);
89 |
90 | if (isset($arguments['report_useless_tests'])) {
91 | $result->beStrictAboutTestsThatDoNotTestAnything((bool)$arguments['report_useless_tests']);
92 | }
93 |
94 | if (isset($arguments['disallow_test_output'])) {
95 | $result->beStrictAboutOutputDuringTests((bool)$arguments['disallow_test_output']);
96 | }
97 |
98 | if (empty(self::$persistentListeners)) {
99 | $this->applyReporters($result, $arguments);
100 | }
101 |
102 | if (class_exists('\Symfony\Bridge\PhpUnit\SymfonyTestsListener')) {
103 | $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : [];
104 |
105 | $listener = new \Symfony\Bridge\PhpUnit\SymfonyTestsListener();
106 | $listener->globalListenerDisabled();
107 | $arguments['listeners'][] = $listener;
108 | }
109 |
110 | $arguments['listeners'][] = $this->printer;
111 |
112 | // clean up listeners between suites
113 | foreach ($arguments['listeners'] as $listener) {
114 | $result->addListener($listener);
115 | }
116 |
117 | $suite->run($result);
118 | unset($suite);
119 |
120 | foreach ($arguments['listeners'] as $listener) {
121 | $result->removeListener($listener);
122 | }
123 |
124 | return $result;
125 | }
126 |
127 | /**
128 | * @param \PHPUnit\Framework\TestResult $result
129 | * @param array $arguments
130 | *
131 | * @return array
132 | */
133 | protected function applyReporters(\PHPUnit\Framework\TestResult $result, array $arguments)
134 | {
135 | foreach ($this->defaultListeners as $listener => $value) {
136 | if (!isset($arguments[$listener])) {
137 | $arguments[$listener] = $value;
138 | }
139 | }
140 |
141 | if ($arguments['report']) {
142 | self::$persistentListeners[] = $this->instantiateReporter('report');
143 | }
144 |
145 | if ($arguments['html']) {
146 | codecept_debug('Printing HTML report into ' . $arguments['html']);
147 | self::$persistentListeners[] = $this->instantiateReporter(
148 | 'html',
149 | [$this->absolutePath($arguments['html'])]
150 | );
151 | }
152 | if ($arguments['xml']) {
153 | codecept_debug('Printing JUNIT report into ' . $arguments['xml']);
154 | self::$persistentListeners[] = $this->instantiateReporter(
155 | 'xml',
156 | [$this->absolutePath($arguments['xml']), (bool)$arguments['log_incomplete_skipped']]
157 | );
158 | }
159 | if ($arguments['phpunit-xml']) {
160 | codecept_debug('Printing PHPUNIT report into ' . $arguments['phpunit-xml']);
161 | self::$persistentListeners[] = $this->instantiateReporter(
162 | 'phpunit-xml',
163 | [$this->absolutePath($arguments['phpunit-xml']), (bool)$arguments['log_incomplete_skipped']]
164 | );
165 | }
166 | if ($arguments['tap']) {
167 | codecept_debug('Printing TAP report into ' . $arguments['tap']);
168 | self::$persistentListeners[] = $this->instantiateReporter('tap', [$this->absolutePath($arguments['tap'])]);
169 | }
170 | if ($arguments['json']) {
171 | codecept_debug('Printing JSON report into ' . $arguments['json']);
172 | self::$persistentListeners[] = $this->instantiateReporter(
173 | 'json',
174 | [$this->absolutePath($arguments['json'])]
175 | );
176 | }
177 |
178 | foreach (self::$persistentListeners as $listener) {
179 | if ($listener instanceof ConsolePrinter) {
180 | $this->printer = $listener;
181 | continue;
182 | }
183 | $result->addListener($listener);
184 | }
185 | }
186 |
187 | protected function instantiateReporter($name, $args = [])
188 | {
189 | if (!isset($this->config['reporters'][$name])) {
190 | throw new ConfigurationException("Reporter $name not defined");
191 | }
192 | return (new \ReflectionClass($this->config['reporters'][$name]))->newInstanceArgs($args);
193 | }
194 |
195 | private function absolutePath($path)
196 | {
197 | if ((strpos($path, '/') === 0) or (strpos($path, ':') === 1)) { // absolute path
198 | return $path;
199 | }
200 | return $this->logDir . $path;
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/ResultPrinter/template/scenarios.html.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | Test results
4 |
5 |
6 |
7 |
142 |
143 |
208 |
209 |
210 |
211 |
219 |
220 | {header}
221 |
222 |
223 | {scenarios}
224 |
225 |
226 | Summary
227 |
228 |
229 |
230 | | Successful scenarios: |
231 | {successfulScenarios} |
232 |
233 |
234 | | Failed scenarios: |
235 | {failedScenarios} |
236 |
237 |
238 | | Skipped scenarios: |
239 | {skippedScenarios} |
240 |
241 |
242 | | Incomplete scenarios: |
243 | {incompleteScenarios} |
244 |
245 |
246 | | Useless scenarios: |
247 | {uselessScenarios} |
248 |
249 |
250 |
251 | |
252 |
253 |
254 |
255 |
256 |
257 |
--------------------------------------------------------------------------------
/src/ResultPrinter/HTML.php:
--------------------------------------------------------------------------------
1 | templatePath = sprintf(
54 | '%s%stemplate%s',
55 | __DIR__,
56 | DIRECTORY_SEPARATOR,
57 | DIRECTORY_SEPARATOR
58 | );
59 | }
60 |
61 | /**
62 | * Handler for 'start class' event.
63 | *
64 | * @param string $name
65 | */
66 | protected function startClass(string $name):void
67 | {
68 | }
69 |
70 | public function endTest(\PHPUnit\Framework\Test $test, float $time) : void
71 | {
72 | $steps = [];
73 | $success = ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED);
74 | if ($success) {
75 | $this->successful++;
76 | }
77 |
78 | if ($test instanceof ScenarioDriven) {
79 | $steps = $test->getScenario()->getSteps();
80 | }
81 | $this->timeTaken += $time;
82 |
83 | switch ($this->testStatus) {
84 | case \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE:
85 | $scenarioStatus = 'scenarioFailed';
86 | break;
87 | case \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED:
88 | $scenarioStatus = 'scenarioSkipped';
89 | break;
90 | case \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE:
91 | $scenarioStatus = 'scenarioIncomplete';
92 | break;
93 | case \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR:
94 | $scenarioStatus = 'scenarioFailed';
95 | break;
96 | default:
97 | $scenarioStatus = 'scenarioSuccess';
98 | }
99 |
100 | $stepsBuffer = '';
101 | $subStepsRendered = [];
102 |
103 | foreach ($steps as $step) {
104 | if ($step->getMetaStep()) {
105 | $key = $step->getMetaStep()->getLine() . $step->getMetaStep()->getAction();
106 | $subStepsRendered[$key][] = $this->renderStep($step);
107 | }
108 | }
109 |
110 | foreach ($steps as $step) {
111 | if ($step->getMetaStep()) {
112 | $key = $step->getMetaStep()->getLine() . $step->getMetaStep()->getAction();
113 | if (! empty($subStepsRendered[$key])) {
114 | $subStepsBuffer = implode('', $subStepsRendered[$key]);
115 | unset($subStepsRendered[$key]);
116 | $stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer);
117 | }
118 | } else {
119 | $stepsBuffer .= $this->renderStep($step);
120 | }
121 | }
122 |
123 | $scenarioTemplate = new Template(
124 | $this->templatePath . 'scenario.html'
125 | );
126 |
127 | $failures = '';
128 | $name = Descriptor::getTestSignatureUnique($test);
129 | if (isset($this->failures[$name])) {
130 | $failTemplate = new Template(
131 | $this->templatePath . 'fail.html'
132 | );
133 | foreach ($this->failures[$name] as $failure) {
134 | $failTemplate->setVar(['fail' => nl2br($failure)]);
135 | $failures .= $failTemplate->render() . PHP_EOL;
136 | }
137 | $this->failures[$name] = [];
138 | }
139 |
140 | $png = '';
141 | $html = '';
142 | if ($test instanceof TestInterface) {
143 | $reports = $test->getMetadata()->getReports();
144 | if (isset($reports['png'])) {
145 | $localPath = PathResolver::getRelativeDir($reports['png'], codecept_output_dir());
146 | $png = " |
";
147 | }
148 | if (isset($reports['html'])) {
149 | $localPath = PathResolver::getRelativeDir($reports['html'], codecept_output_dir());
150 | $html = "| See HTML snapshot of a failed page |
";
151 | }
152 | }
153 |
154 | $toggle = $stepsBuffer ? '+' : '';
155 |
156 | $testString = htmlspecialchars(ucfirst(Descriptor::getTestAsString($test)));
157 | $testString = preg_replace('~^([\s\w\\\]+):\s~', '$1 » ', $testString);
158 |
159 | $scenarioTemplate->setVar(
160 | [
161 | 'id' => ++$this->id,
162 | 'name' => $testString,
163 | 'scenarioStatus' => $scenarioStatus,
164 | 'steps' => $stepsBuffer,
165 | 'toggle' => $toggle,
166 | 'failure' => $failures,
167 | 'png' => $png,
168 | 'html' => $html,
169 | 'time' => round($time, 2)
170 | ]
171 | );
172 |
173 | $this->scenarios .= $scenarioTemplate->render();
174 | }
175 |
176 | public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void
177 | {
178 | $suiteTemplate = new Template(
179 | $this->templatePath . 'suite.html'
180 | );
181 | if (!$suite->getName()) {
182 | return;
183 | }
184 |
185 | $suiteTemplate->setVar(['suite' => ucfirst($suite->getName())]);
186 |
187 | $this->scenarios .= $suiteTemplate->render();
188 | }
189 |
190 | /**
191 | * Handler for 'end run' event.
192 | */
193 | protected function endRun():void
194 | {
195 | $scenarioHeaderTemplate = new Template(
196 | $this->templatePath . 'scenario_header.html'
197 | );
198 |
199 | $status = !$this->failed
200 | ? 'OK'
201 | : 'FAILED';
202 |
203 |
204 | $scenarioHeaderTemplate->setVar(
205 | [
206 | 'name' => 'Codeception Results',
207 | 'status' => $status,
208 | 'time' => round($this->timeTaken, 1)
209 | ]
210 | );
211 |
212 | $header = $scenarioHeaderTemplate->render();
213 |
214 | $scenariosTemplate = new Template(
215 | $this->templatePath . 'scenarios.html'
216 | );
217 |
218 | $scenariosTemplate->setVar(
219 | [
220 | 'header' => $header,
221 | 'scenarios' => $this->scenarios,
222 | 'successfulScenarios' => $this->successful,
223 | 'failedScenarios' => $this->failed,
224 | 'skippedScenarios' => $this->skipped,
225 | 'incompleteScenarios' => $this->incomplete,
226 | 'uselessScenarios' => $this->risky,
227 | ]
228 | );
229 |
230 | $this->write($scenariosTemplate->render());
231 | }
232 |
233 | /**
234 | * An error occurred.
235 | *
236 | * @param \PHPUnit\Framework\Test $test
237 | * @param \Exception $e
238 | * @param float $time
239 | */
240 | public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
241 | {
242 | $this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e);
243 | parent::addError($test, $e, $time);
244 | }
245 |
246 | /**
247 | * A failure occurred.
248 | *
249 | * @param \PHPUnit\Framework\Test $test
250 | * @param \PHPUnit\Framework\AssertionFailedError $e
251 | * @param float $time
252 | */
253 | public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void
254 | {
255 | $this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e);
256 | parent::addFailure($test, $e, $time);
257 | }
258 |
259 | /**
260 | * Starts test
261 | *
262 | * @param \PHPUnit\Framework\Test $test
263 | */
264 | public function startTest(\PHPUnit\Framework\Test $test):void
265 | {
266 | $name = Descriptor::getTestSignatureUnique($test);
267 | if (isset($this->failures[$name])) {
268 | // test failed in before hook
269 | return;
270 | }
271 |
272 | // start test and mark initialize as passed
273 | parent::startTest($test);
274 | }
275 |
276 |
277 | /**
278 | * @param $step
279 | * @return string
280 | */
281 | protected function renderStep(Step $step)
282 | {
283 | $stepTemplate = new Template($this->templatePath . 'step.html');
284 | $stepTemplate->setVar(['action' => $step->getHtml(), 'error' => $step->hasFailed() ? 'failedStep' : '']);
285 | return $stepTemplate->render();
286 | }
287 |
288 | /**
289 | * @param $metaStep
290 | * @param $substepsBuffer
291 | * @return string
292 | */
293 | protected function renderSubsteps(Meta $metaStep, $substepsBuffer)
294 | {
295 | $metaTemplate = new Template($this->templatePath . 'substeps.html');
296 | $metaTemplate->setVar(['metaStep' => $metaStep->getHtml(), 'error' => $metaStep->hasFailed() ? 'failedStep' : '', 'steps' => $substepsBuffer, 'id' => uniqid()]);
297 | return $metaTemplate->render();
298 | }
299 |
300 | private function cleanMessage($exception)
301 | {
302 | $msg = $exception->getMessage();
303 | if ($exception instanceof \PHPUnit\Framework\ExpectationFailedException && $exception->getComparisonFailure()) {
304 | $msg .= $exception->getComparisonFailure()->getDiff();
305 | }
306 | $msg = str_replace(['','','',''], ['','','',''], $msg);
307 | return htmlentities($msg);
308 | }
309 |
310 | public function printResult(TestResult $result): void
311 | {
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/src/NonFinal/JUnit.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Codeception\PHPUnit\NonFinal;
11 |
12 | use DOMDocument;
13 | use DOMElement;
14 | use PHPUnit\Framework\AssertionFailedError;
15 | use PHPUnit\Framework\ExceptionWrapper;
16 | use PHPUnit\Framework\SelfDescribing;
17 | use PHPUnit\Framework\Test;
18 | use PHPUnit\Framework\TestFailure;
19 | use PHPUnit\Framework\TestListener;
20 | use PHPUnit\Framework\TestSuite;
21 | use PHPUnit\Framework\Warning;
22 | use PHPUnit\Util\Filter;
23 | use PHPUnit\Util\Printer;
24 | use PHPUnit\Util\Xml;
25 | use ReflectionClass;
26 | use ReflectionException;
27 |
28 | /**
29 | * @internal This class is not covered by the backward compatibility promise for PHPUnit
30 | */
31 | class JUnit extends Printer implements TestListener
32 | {
33 | /**
34 | * @var DOMDocument
35 | */
36 | protected $document;
37 |
38 | /**
39 | * @var DOMElement
40 | */
41 | protected $root;
42 |
43 | /**
44 | * @var bool
45 | */
46 | protected $reportUselessTests = false;
47 |
48 | /**
49 | * @var bool
50 | */
51 | protected $writeDocument = true;
52 |
53 | /**
54 | * @var DOMElement[]
55 | */
56 | protected $testSuites = [];
57 |
58 | /**
59 | * @var int[]
60 | */
61 | protected $testSuiteTests = [0];
62 |
63 | /**
64 | * @var int[]
65 | */
66 | protected $testSuiteAssertions = [0];
67 |
68 | /**
69 | * @var int[]
70 | */
71 | protected $testSuiteErrors = [0];
72 |
73 | /**
74 | * @var int[]
75 | */
76 | protected $testSuiteFailures = [0];
77 |
78 | /**
79 | * @var int[]
80 | */
81 | protected $testSuiteSkipped = [0];
82 |
83 | /**
84 | * @var int[]
85 | */
86 | protected $testSuiteTimes = [0];
87 |
88 | /**
89 | * @var int
90 | */
91 | protected $testSuiteLevel = 0;
92 |
93 | /**
94 | * @var DOMElement
95 | */
96 | protected $currentTestCase;
97 |
98 | /**
99 | * Constructor.
100 | *
101 | * @param null|mixed $out
102 | *
103 | * @throws \PHPUnit\Framework\Exception
104 | */
105 | public function __construct($out = null, bool $reportUselessTests = false)
106 | {
107 | $this->document = new DOMDocument('1.0', 'UTF-8');
108 | $this->document->formatOutput = true;
109 |
110 | $this->root = $this->document->createElement('testsuites');
111 | $this->document->appendChild($this->root);
112 |
113 | parent::__construct($out);
114 |
115 | $this->reportUselessTests = $reportUselessTests;
116 | }
117 |
118 | /**
119 | * Flush buffer and close output.
120 | */
121 | public function flush(): void
122 | {
123 | if ($this->writeDocument === true) {
124 | $this->write($this->getXML());
125 | }
126 |
127 | parent::flush();
128 | }
129 |
130 | /**
131 | * An error occurred.
132 | *
133 | * @throws \InvalidArgumentException
134 | * @throws ReflectionException
135 | */
136 | public function addError(Test $test, \Throwable $t, float $time): void
137 | {
138 | $this->doAddFault($test, $t, $time, 'error');
139 | $this->testSuiteErrors[$this->testSuiteLevel]++;
140 | }
141 |
142 | /**
143 | * A warning occurred.
144 | *
145 | * @throws \InvalidArgumentException
146 | * @throws ReflectionException
147 | */
148 | public function addWarning(Test $test, Warning $e, float $time): void
149 | {
150 | $this->doAddFault($test, $e, $time, 'warning');
151 | $this->testSuiteFailures[$this->testSuiteLevel]++;
152 | }
153 |
154 | /**
155 | * A failure occurred.
156 | *
157 | * @throws \InvalidArgumentException
158 | * @throws ReflectionException
159 | */
160 | public function addFailure(Test $test, AssertionFailedError $e, float $time): void
161 | {
162 | $this->doAddFault($test, $e, $time, 'failure');
163 | $this->testSuiteFailures[$this->testSuiteLevel]++;
164 | }
165 |
166 | /**
167 | * Incomplete test.
168 | */
169 | public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
170 | {
171 | $this->doAddSkipped($test);
172 | }
173 |
174 | /**
175 | * Risky test.
176 | *
177 | * @throws ReflectionException
178 | */
179 | public function addRiskyTest(Test $test, \Throwable $t, float $time): void
180 | {
181 | if (!$this->reportUselessTests || $this->currentTestCase === null) {
182 | return;
183 | }
184 |
185 | $error = $this->document->createElement(
186 | 'error',
187 | Xml::prepareString(
188 | "Risky Test\n" .
189 | Filter::getFilteredStacktrace($t)
190 | )
191 | );
192 |
193 | $error->setAttribute('type', \get_class($t));
194 |
195 | $this->currentTestCase->appendChild($error);
196 |
197 | $this->testSuiteErrors[$this->testSuiteLevel]++;
198 | }
199 |
200 | /**
201 | * Skipped test.
202 | */
203 | public function addSkippedTest(Test $test, \Throwable $t, float $time): void
204 | {
205 | $this->doAddSkipped($test);
206 | }
207 |
208 | /**
209 | * A testsuite started.
210 | */
211 | public function startTestSuite(TestSuite $suite): void
212 | {
213 | $testSuite = $this->document->createElement('testsuite');
214 | $testSuite->setAttribute('name', $suite->getName());
215 |
216 | if (\class_exists($suite->getName(), false)) {
217 | try {
218 | $class = new ReflectionClass($suite->getName());
219 |
220 | $testSuite->setAttribute('file', $class->getFileName());
221 | } catch (ReflectionException $e) {
222 | }
223 | }
224 |
225 | if ($this->testSuiteLevel > 0) {
226 | $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
227 | } else {
228 | $this->root->appendChild($testSuite);
229 | }
230 |
231 | $this->testSuiteLevel++;
232 | $this->testSuites[$this->testSuiteLevel] = $testSuite;
233 | $this->testSuiteTests[$this->testSuiteLevel] = 0;
234 | $this->testSuiteAssertions[$this->testSuiteLevel] = 0;
235 | $this->testSuiteErrors[$this->testSuiteLevel] = 0;
236 | $this->testSuiteFailures[$this->testSuiteLevel] = 0;
237 | $this->testSuiteSkipped[$this->testSuiteLevel] = 0;
238 | $this->testSuiteTimes[$this->testSuiteLevel] = 0;
239 | }
240 |
241 | /**
242 | * A testsuite ended.
243 | */
244 | public function endTestSuite(TestSuite $suite): void
245 | {
246 | $this->testSuites[$this->testSuiteLevel]->setAttribute(
247 | 'tests',
248 | (string) $this->testSuiteTests[$this->testSuiteLevel]
249 | );
250 |
251 | $this->testSuites[$this->testSuiteLevel]->setAttribute(
252 | 'assertions',
253 | (string) $this->testSuiteAssertions[$this->testSuiteLevel]
254 | );
255 |
256 | $this->testSuites[$this->testSuiteLevel]->setAttribute(
257 | 'errors',
258 | (string) $this->testSuiteErrors[$this->testSuiteLevel]
259 | );
260 |
261 | $this->testSuites[$this->testSuiteLevel]->setAttribute(
262 | 'failures',
263 | (string) $this->testSuiteFailures[$this->testSuiteLevel]
264 | );
265 |
266 | $this->testSuites[$this->testSuiteLevel]->setAttribute(
267 | 'skipped',
268 | (string) $this->testSuiteSkipped[$this->testSuiteLevel]
269 | );
270 |
271 | $this->testSuites[$this->testSuiteLevel]->setAttribute(
272 | 'time',
273 | \sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
274 | );
275 |
276 | if ($this->testSuiteLevel > 1) {
277 | $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
278 | $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
279 | $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
280 | $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
281 | $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
282 | $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
283 | }
284 |
285 | $this->testSuiteLevel--;
286 | }
287 |
288 | /**
289 | * A test started.
290 | *
291 | * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
292 | * @throws ReflectionException
293 | */
294 | public function startTest(Test $test): void
295 | {
296 | $usesDataprovider = false;
297 |
298 | if (\method_exists($test, 'usesDataProvider')) {
299 | $usesDataprovider = $test->usesDataProvider();
300 | }
301 |
302 | $testCase = $this->document->createElement('testcase');
303 | $testCase->setAttribute('name', $test->getName());
304 |
305 | $class = new ReflectionClass($test);
306 | $methodName = $test->getName(!$usesDataprovider);
307 |
308 | if ($class->hasMethod($methodName)) {
309 | $method = $class->getMethod($methodName);
310 |
311 | $testCase->setAttribute('class', $class->getName());
312 | $testCase->setAttribute('classname', \str_replace('\\', '.', $class->getName()));
313 | $testCase->setAttribute('file', $class->getFileName());
314 | $testCase->setAttribute('line', (string) $method->getStartLine());
315 | }
316 |
317 | $this->currentTestCase = $testCase;
318 | }
319 |
320 | /**
321 | * A test ended.
322 | */
323 | public function endTest(Test $test, float $time): void
324 | {
325 | $numAssertions = 0;
326 |
327 | if (\method_exists($test, 'getNumAssertions')) {
328 | $numAssertions = $test->getNumAssertions();
329 | }
330 |
331 | $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
332 |
333 | $this->currentTestCase->setAttribute(
334 | 'assertions',
335 | (string) $numAssertions
336 | );
337 |
338 | $this->currentTestCase->setAttribute(
339 | 'time',
340 | \sprintf('%F', $time)
341 | );
342 |
343 | $this->testSuites[$this->testSuiteLevel]->appendChild(
344 | $this->currentTestCase
345 | );
346 |
347 | $this->testSuiteTests[$this->testSuiteLevel]++;
348 | $this->testSuiteTimes[$this->testSuiteLevel] += $time;
349 |
350 | $testOutput = '';
351 |
352 | if (\method_exists($test, 'hasOutput') && \method_exists($test, 'getActualOutput')) {
353 | $testOutput = $test->hasOutput() ? $test->getActualOutput() : '';
354 | }
355 |
356 | if (!empty($testOutput)) {
357 | $systemOut = $this->document->createElement(
358 | 'system-out',
359 | Xml::prepareString($testOutput)
360 | );
361 |
362 | $this->currentTestCase->appendChild($systemOut);
363 | }
364 |
365 | $this->currentTestCase = null;
366 | }
367 |
368 | /**
369 | * Returns the XML as a string.
370 | */
371 | public function getXML(): string
372 | {
373 | return $this->document->saveXML();
374 | }
375 |
376 | /**
377 | * Enables or disables the writing of the document
378 | * in flush().
379 | *
380 | * This is a "hack" needed for the integration of
381 | * PHPUnit with Phing.
382 | */
383 | public function setWriteDocument(/*bool*/ $flag): void
384 | {
385 | if (\is_bool($flag)) {
386 | $this->writeDocument = $flag;
387 | }
388 | }
389 |
390 | /**
391 | * Method which generalizes addError() and addFailure()
392 | *
393 | * @throws \InvalidArgumentException
394 | * @throws ReflectionException
395 | */
396 | private function doAddFault(Test $test, \Throwable $t, float $time, $type): void
397 | {
398 | if ($this->currentTestCase === null) {
399 | return;
400 | }
401 |
402 | if ($test instanceof SelfDescribing) {
403 | $buffer = $test->toString() . "\n";
404 | } else {
405 | $buffer = '';
406 | }
407 |
408 | $buffer .= TestFailure::exceptionToString($t) . "\n" .
409 | Filter::getFilteredStacktrace($t);
410 |
411 | $fault = $this->document->createElement(
412 | $type,
413 | Xml::prepareString($buffer)
414 | );
415 |
416 | if ($t instanceof ExceptionWrapper) {
417 | $fault->setAttribute('type', $t->getClassName());
418 | } else {
419 | $fault->setAttribute('type', \get_class($t));
420 | }
421 |
422 | $this->currentTestCase->appendChild($fault);
423 | }
424 |
425 | private function doAddSkipped(Test $test): void
426 | {
427 | if ($this->currentTestCase === null) {
428 | return;
429 | }
430 |
431 | $skipped = $this->document->createElement('skipped');
432 | $this->currentTestCase->appendChild($skipped);
433 |
434 | $this->testSuiteSkipped[$this->testSuiteLevel]++;
435 | }
436 | }
437 |
--------------------------------------------------------------------------------
/src/phpunit5-loggers.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace {
13 |
14 | if (!class_exists('PHPUnit_Util_String')) {
15 |
16 | /**
17 | * String helpers.
18 | */
19 | class PHPUnit_Util_String
20 | {
21 | /**
22 | * Converts a string to UTF-8 encoding.
23 | *
24 | * @param string $string
25 | *
26 | * @return string
27 | */
28 | public static function convertToUtf8($string)
29 | {
30 | return mb_convert_encoding($string, 'UTF-8');
31 | }
32 |
33 | /**
34 | * Checks a string for UTF-8 encoding.
35 | *
36 | * @param string $string
37 | *
38 | * @return bool
39 | */
40 | protected static function isUtf8($string)
41 | {
42 | $length = strlen($string);
43 |
44 | for ($i = 0; $i < $length; $i++) {
45 | if (ord($string[$i]) < 0x80) {
46 | $n = 0;
47 | } elseif ((ord($string[$i]) & 0xE0) == 0xC0) {
48 | $n = 1;
49 | } elseif ((ord($string[$i]) & 0xF0) == 0xE0) {
50 | $n = 2;
51 | } elseif ((ord($string[$i]) & 0xF0) == 0xF0) {
52 | $n = 3;
53 | } else {
54 | return false;
55 | }
56 |
57 | for ($j = 0; $j < $n; $j++) {
58 | if ((++$i == $length) || ((ord($string[$i]) & 0xC0) != 0x80)) {
59 | return false;
60 | }
61 | }
62 | }
63 |
64 | return true;
65 | }
66 | }
67 | }
68 | }
69 |
70 |
71 | namespace PHPUnit\Util\Log {
72 |
73 | /*
74 | * This file is part of PHPUnit.
75 | *
76 | * (c) Sebastian Bergmann
77 | *
78 | * For the full copyright and license information, please view the LICENSE
79 | * file that was distributed with this source code.
80 | */
81 |
82 | use Codeception\Test\Descriptor;
83 |
84 | /**
85 | * A TestListener that generates JSON messages.
86 | */
87 | if (!class_exists('\PHPUnit\Util\Log\JSON')) {
88 | class JSON extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener
89 | {
90 | /**
91 | * @var string
92 | */
93 | protected $currentTestSuiteName = '';
94 |
95 | /**
96 | * @var string
97 | */
98 | protected $currentTestName = '';
99 |
100 | /**
101 | * @var bool
102 | */
103 | protected $currentTestPass = true;
104 |
105 | /**
106 | * @var array
107 | */
108 | protected $logEvents = [];
109 |
110 | /**
111 | * An error occurred.
112 | *
113 | * @param \PHPUnit\Framework\Test $test
114 | * @param \Throwable $e
115 | * @param float $time
116 | */
117 | public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
118 | {
119 | $this->writeCase(
120 | 'error',
121 | $time,
122 | \PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
123 | \PHPUnit\Framework\TestFailure::exceptionToString($e),
124 | $test
125 | );
126 |
127 | $this->currentTestPass = false;
128 | }
129 |
130 | /**
131 | * A warning occurred.
132 | *
133 | * @param \PHPUnit\Framework\Test $test
134 | * @param \PHPUnit\Framework\Warning $e
135 | * @param float $time
136 | */
137 | public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void
138 | {
139 | $this->writeCase(
140 | 'warning',
141 | $time,
142 | \PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
143 | \PHPUnit\Framework\TestFailure::exceptionToString($e),
144 | $test
145 | );
146 |
147 | $this->currentTestPass = false;
148 | }
149 |
150 | /**
151 | * A failure occurred.
152 | *
153 | * @param \PHPUnit\Framework\Test $test
154 | * @param \PHPUnit\Framework\AssertionFailedError $e
155 | * @param float $time
156 | */
157 | public function addFailure(
158 | \PHPUnit\Framework\Test $test,
159 | \PHPUnit\Framework\AssertionFailedError $e,
160 | float $time
161 | ): void{
162 | $this->writeCase(
163 | 'fail',
164 | $time,
165 | \PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
166 | \PHPUnit\Framework\TestFailure::exceptionToString($e),
167 | $test
168 | );
169 |
170 | $this->currentTestPass = false;
171 | }
172 |
173 | /**
174 | * Incomplete test.
175 | *
176 | * @param \PHPUnit\Framework\Test $test
177 | * @param Throwable $e
178 | * @param float $time
179 | */
180 | public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
181 | {
182 | $this->writeCase(
183 | 'error',
184 | $time,
185 | \PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
186 | 'Incomplete Test: ' . $e->getMessage(),
187 | $test
188 | );
189 |
190 | $this->currentTestPass = false;
191 | }
192 |
193 | /**
194 | * Risky test.
195 | *
196 | * @param \PHPUnit\Framework\Test $test
197 | * @param Throwable $e
198 | * @param float $time
199 | */
200 | public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
201 | {
202 | $this->writeCase(
203 | 'error',
204 | $time,
205 | \PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
206 | 'Risky Test: ' . $e->getMessage(),
207 | $test
208 | );
209 |
210 | $this->currentTestPass = false;
211 | }
212 |
213 | /**
214 | * Skipped test.
215 | *
216 | * @param \PHPUnit\Framework\Test $test
217 | * @param Throwable $e
218 | * @param float $time
219 | */
220 | public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
221 | {
222 | $this->writeCase(
223 | 'error',
224 | $time,
225 | \PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
226 | 'Skipped Test: ' . $e->getMessage(),
227 | $test
228 | );
229 |
230 | $this->currentTestPass = false;
231 | }
232 |
233 | /**
234 | * A testsuite started.
235 | *
236 | * @param \PHPUnit\Framework\TestSuite $suite
237 | */
238 | public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void
239 | {
240 | $this->currentTestSuiteName = $suite->getName();
241 | $this->currentTestName = '';
242 |
243 | $this->addLogEvent(
244 | [
245 | 'event' => 'suiteStart',
246 | 'suite' => $this->currentTestSuiteName,
247 | 'tests' => count($suite)
248 | ]
249 | );
250 | }
251 |
252 | /**
253 | * A testsuite ended.
254 | *
255 | * @param \PHPUnit\Framework\TestSuite $suite
256 | */
257 | public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void
258 | {
259 | $this->currentTestSuiteName = '';
260 | $this->currentTestName = '';
261 |
262 | $this->writeArray($this->logEvents);
263 | }
264 |
265 | /**
266 | * A test started.
267 | *
268 | * @param \PHPUnit\Framework\Test $test
269 | */
270 | public function startTest(\PHPUnit\Framework\Test $test): void
271 | {
272 | $this->currentTestName = \PHPUnit\Util\Test::describe($test);
273 | $this->currentTestPass = true;
274 |
275 | $this->addLogEvent(
276 | [
277 | 'event' => 'testStart',
278 | 'suite' => $this->currentTestSuiteName,
279 | 'test' => $this->currentTestName
280 | ]
281 | );
282 | }
283 |
284 | /**
285 | * A test ended.
286 | *
287 | * @param \PHPUnit\Framework\Test $test
288 | * @param float $time
289 | */
290 | public function endTest(\PHPUnit\Framework\Test $test, float $time): void
291 | {
292 | if ($this->currentTestPass) {
293 | $this->writeCase('pass', $time, [], '', $test);
294 | }
295 | }
296 |
297 | /**
298 | * @param string $status
299 | * @param float $time
300 | * @param array $trace
301 | * @param string $message
302 | * @param \PHPUnit\Framework\TestCase|null $test
303 | */
304 | protected function writeCase($status, float $time, array $trace = [], $message = '', $test = null): void
305 | {
306 | $output = '';
307 | // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput
308 | if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) {
309 | $output = $test->getActualOutput();
310 | }
311 | $this->addLogEvent(
312 | [
313 | 'event' => 'test',
314 | 'suite' => $this->currentTestSuiteName,
315 | 'test' => $this->currentTestName,
316 | 'status' => $status,
317 | 'time' => $time,
318 | 'trace' => $trace,
319 | 'message' => \PHPUnit_Util_String::convertToUtf8($message),
320 | 'output' => $output,
321 | ]
322 | );
323 | }
324 |
325 | /**
326 | * @param array $event_data
327 | */
328 | protected function addLogEvent($event_data = []): void
329 | {
330 | if (count($event_data)) {
331 | array_push($this->logEvents, $event_data);
332 | }
333 | }
334 |
335 | /**
336 | * @param array $buffer
337 | */
338 | public function writeArray($buffer)
339 | {
340 | array_walk_recursive(
341 | $buffer, function (&$input){
342 | if (is_string($input)) {
343 | $input = \PHPUnit_Util_String::convertToUtf8($input);
344 | }
345 | }
346 | );
347 |
348 | $this->write(json_encode($buffer, JSON_PRETTY_PRINT));
349 | }
350 | }
351 | }
352 |
353 | /*
354 | * This file is part of PHPUnit.
355 | *
356 | * (c) Sebastian Bergmann
357 | *
358 | * For the full copyright and license information, please view the LICENSE
359 | * file that was distributed with this source code.
360 | */
361 |
362 | if (!class_exists('\PHPUnit\Util\Log\TAP')) {
363 |
364 | /**
365 | * A TestListener that generates a logfile of the
366 | * test execution using the Test Anything Protocol (TAP).
367 | */
368 | class TAP extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener
369 | {
370 | /**
371 | * @var int
372 | */
373 | protected $testNumber = 0;
374 |
375 | /**
376 | * @var int
377 | */
378 | protected $testSuiteLevel = 0;
379 |
380 | /**
381 | * @var bool
382 | */
383 | protected $testSuccessful = true;
384 |
385 | /**
386 | * Constructor.
387 | *
388 | * @param mixed $out
389 | *
390 | * @throws \PHPUnit\Framework\Throwable
391 | */
392 | public function __construct($out = null)
393 | {
394 | parent::__construct($out);
395 | $this->write("TAP version 13\n");
396 | }
397 |
398 | /**
399 | * An error occurred.
400 | *
401 | * @param \PHPUnit\Framework\Test $test
402 | * @param Throwable $e
403 | * @param float $time
404 | */
405 | public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
406 | {
407 | $this->writeNotOk($test, 'Error');
408 | }
409 |
410 | /**
411 | * A warning occurred.
412 | *
413 | * @param \PHPUnit\Framework\Test $test
414 | * @param \PHPUnit\Framework\Warning $e
415 | * @param float $time
416 | */
417 | public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void
418 | {
419 | $this->writeNotOk($test, 'Warning');
420 | }
421 |
422 | /**
423 | * A failure occurred.
424 | *
425 | * @param \PHPUnit\Framework\Test $test
426 | * @param \PHPUnit\Framework\AssertionFailedError $e
427 | * @param float $time
428 | */
429 | public function addFailure(
430 | \PHPUnit\Framework\Test $test,
431 | \PHPUnit\Framework\AssertionFailedError $e,
432 | float $time
433 | ): void{
434 | $this->writeNotOk($test, 'Failure');
435 |
436 | $message = explode(
437 | "\n",
438 | \PHPUnit\Framework\TestFailure::exceptionToString($e)
439 | );
440 |
441 | $diagnostic = [
442 | 'message' => $message[0],
443 | 'severity' => 'fail'
444 | ];
445 |
446 | if ($e instanceof \PHPUnit\Framework\ExpectationFailedThrowable) {
447 | $cf = $e->getComparisonFailure();
448 |
449 | if ($cf !== null) {
450 | $diagnostic['data'] = [
451 | 'got' => $cf->getActual(),
452 | 'expected' => $cf->getExpected()
453 | ];
454 | }
455 | }
456 |
457 | $yaml = new \Symfony\Component\Yaml\Dumper;
458 |
459 | $this->write(
460 | sprintf(
461 | " ---\n%s ...\n",
462 | $yaml->dump($diagnostic, 2, 2)
463 | )
464 | );
465 | }
466 |
467 | /**
468 | * Incomplete test.
469 | *
470 | * @param \PHPUnit\Framework\Test $test
471 | * @param \Throwable $e
472 | * @param float $time
473 | */
474 | public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
475 | {
476 | $this->writeNotOk($test, '', 'TODO Incomplete Test');
477 | }
478 |
479 | /**
480 | * Risky test.
481 | *
482 | * @param \PHPUnit\Framework\Test $test
483 | * @param Throwable $e
484 | * @param float $time
485 | */
486 | public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
487 | {
488 | $this->write(
489 | sprintf(
490 | "ok %d - # RISKY%s\n",
491 | $this->testNumber,
492 | $e->getMessage() != '' ? ' ' . $e->getMessage() : ''
493 | )
494 | );
495 |
496 | $this->testSuccessful = false;
497 | }
498 |
499 | /**
500 | * Skipped test.
501 | *
502 | * @param \PHPUnit\Framework\Test $test
503 | * @param Throwable $e
504 | * @param float $time
505 | */
506 | public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
507 | {
508 | $this->write(
509 | sprintf(
510 | "ok %d - # SKIP%s\n",
511 | $this->testNumber,
512 | $e->getMessage() != '' ? ' ' . $e->getMessage() : ''
513 | )
514 | );
515 |
516 | $this->testSuccessful = false;
517 | }
518 |
519 | /**
520 | * A testsuite started.
521 | *
522 | * @param \PHPUnit\Framework\TestSuite $suite
523 | */
524 | public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void
525 | {
526 | $this->testSuiteLevel++;
527 | }
528 |
529 | /**
530 | * A testsuite ended.
531 | *
532 | * @param \PHPUnit\Framework\TestSuite $suite
533 | */
534 | public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void
535 | {
536 | $this->testSuiteLevel--;
537 |
538 | if ($this->testSuiteLevel == 0) {
539 | $this->write(sprintf("1..%d\n", $this->testNumber));
540 | }
541 | }
542 |
543 | /**
544 | * A test started.
545 | *
546 | * @param \PHPUnit\Framework\Test $test
547 | */
548 | public function startTest(\PHPUnit\Framework\Test $test): void
549 | {
550 | $this->testNumber++;
551 | $this->testSuccessful = true;
552 | }
553 |
554 | /**
555 | * A test ended.
556 | *
557 | * @param \PHPUnit\Framework\Test $test
558 | * @param float $time
559 | */
560 | public function endTest(\PHPUnit\Framework\Test $test, float $time): void
561 | {
562 | if ($this->testSuccessful === true) {
563 | $this->write(
564 | sprintf(
565 | "ok %d - %s\n",
566 | $this->testNumber,
567 | Descriptor::getTestSignature($test)
568 | )
569 | );
570 | }
571 |
572 | $this->writeDiagnostics($test);
573 | }
574 |
575 | /**
576 | * @param \PHPUnit\Framework\Test $test
577 | * @param string $prefix
578 | * @param string $directive
579 | */
580 | protected function writeNotOk(\PHPUnit\Framework\Test $test, $prefix = '', $directive = '')
581 | {
582 | $this->write(
583 | sprintf(
584 | "not ok %d - %s%s%s\n",
585 | $this->testNumber,
586 | $prefix != '' ? $prefix . ': ' : '',
587 | \PHPUnit\Util\Test::describeAsString($test),
588 | $directive != '' ? ' # ' . $directive : ''
589 | )
590 | );
591 |
592 | $this->testSuccessful = false;
593 | }
594 |
595 | /**
596 | * @param \PHPUnit\Framework\Test $test
597 | */
598 | private function writeDiagnostics(\PHPUnit\Framework\Test $test)
599 | {
600 | if (!$test instanceof \PHPUnit\Framework\TestCase) {
601 | return;
602 | }
603 |
604 | if (!$test->hasOutput()) {
605 | return;
606 | }
607 |
608 | foreach (explode("\n", trim($test->getActualOutput())) as $line) {
609 | $this->write(
610 | sprintf(
611 | "# %s\n",
612 | $line
613 | )
614 | );
615 | }
616 | }
617 | }
618 | }
619 | }
620 | // @codingStandardsIgnoreEnd
621 |
--------------------------------------------------------------------------------
/src/NonFinal/TestRunner.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Codeception\PHPUnit\NonFinal;
11 |
12 | use PHPUnit\Framework\Error\Deprecated;
13 | use PHPUnit\Framework\Error\Notice;
14 | use PHPUnit\Framework\Error\Warning;
15 | use PHPUnit\Framework\Exception;
16 | use PHPUnit\Framework\Test;
17 | use PHPUnit\Framework\TestCase;
18 | use PHPUnit\Framework\TestListener;
19 | use PHPUnit\Framework\TestResult;
20 | use PHPUnit\Framework\TestSuite;
21 | use PHPUnit\Runner\AfterLastTestHook;
22 | use PHPUnit\Runner\BaseTestRunner;
23 | use PHPUnit\Runner\BeforeFirstTestHook;
24 | use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator;
25 | use PHPUnit\Runner\Filter\Factory;
26 | use PHPUnit\Runner\Filter\IncludeGroupFilterIterator;
27 | use PHPUnit\Runner\Filter\NameFilterIterator;
28 | use PHPUnit\Runner\Hook;
29 | use PHPUnit\Runner\NullTestResultCache;
30 | use PHPUnit\Runner\ResultCacheExtension;
31 | use PHPUnit\Runner\StandardTestSuiteLoader;
32 | use PHPUnit\Runner\TestHook;
33 | use PHPUnit\Runner\TestListenerAdapter;
34 | use PHPUnit\Runner\TestResultCache;
35 | use PHPUnit\Runner\TestSuiteLoader;
36 | use PHPUnit\Runner\TestSuiteSorter;
37 | use PHPUnit\Runner\Version;
38 | use PHPUnit\TextUI\ResultPrinter;
39 | use PHPUnit\Util\Configuration;
40 | use PHPUnit\Util\Filesystem;
41 | use PHPUnit\Util\Log\JUnit;
42 | use PHPUnit\Util\Log\TeamCity;
43 | use PHPUnit\Util\Printer;
44 | use PHPUnit\Util\TestDox\CliTestDoxPrinter;
45 | use PHPUnit\Util\TestDox\HtmlResultPrinter;
46 | use PHPUnit\Util\TestDox\TextResultPrinter;
47 | use PHPUnit\Util\TestDox\XmlResultPrinter;
48 | use PHPUnit\Util\XdebugFilterScriptGenerator;
49 | use ReflectionClass;
50 | use SebastianBergmann\CodeCoverage\CodeCoverage;
51 | use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException;
52 | use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter;
53 | use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport;
54 | use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport;
55 | use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport;
56 | use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport;
57 | use SebastianBergmann\CodeCoverage\Report\Text as TextReport;
58 | use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport;
59 | use SebastianBergmann\Comparator\Comparator;
60 | use SebastianBergmann\Environment\Runtime;
61 | use SebastianBergmann\Invoker\Invoker;
62 |
63 | /**
64 | * @internal This class is not covered by the backward compatibility promise for PHPUnit
65 | */
66 | class TestRunner extends BaseTestRunner
67 | {
68 | public const SUCCESS_EXIT = 0;
69 |
70 | public const FAILURE_EXIT = 1;
71 |
72 | public const EXCEPTION_EXIT = 2;
73 |
74 | /**
75 | * @var bool
76 | */
77 | protected static $versionStringPrinted = false;
78 |
79 | /**
80 | * @var CodeCoverageFilter
81 | */
82 | protected $codeCoverageFilter;
83 |
84 | /**
85 | * @var TestSuiteLoader
86 | */
87 | protected $loader;
88 |
89 | /**
90 | * @var ResultPrinter
91 | */
92 | protected $printer;
93 |
94 | /**
95 | * @var Runtime
96 | */
97 | private $runtime;
98 |
99 | /**
100 | * @var bool
101 | */
102 | private $messagePrinted = false;
103 |
104 | /**
105 | * @var Hook[]
106 | */
107 | private $extensions = [];
108 |
109 | /**
110 | * @param ReflectionClass|Test $test
111 | * @param bool $exit
112 | *
113 | * @throws \RuntimeException
114 | * @throws \InvalidArgumentException
115 | * @throws Exception
116 | * @throws \ReflectionException
117 | */
118 | public static function run($test, array $arguments = [], $exit = true): TestResult
119 | {
120 | if ($test instanceof ReflectionClass) {
121 | $test = new TestSuite($test);
122 | }
123 |
124 | if ($test instanceof Test) {
125 | $aTestRunner = new self;
126 |
127 | return $aTestRunner->doRun(
128 | $test,
129 | $arguments,
130 | $exit
131 | );
132 | }
133 |
134 | throw new Exception('No test case or test suite found.');
135 | }
136 |
137 | public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null)
138 | {
139 | if ($filter === null) {
140 | $filter = new CodeCoverageFilter;
141 | }
142 |
143 | $this->codeCoverageFilter = $filter;
144 | $this->loader = $loader;
145 | $this->runtime = new Runtime;
146 | }
147 |
148 | /**
149 | * @throws \PHPUnit\Runner\Exception
150 | * @throws Exception
151 | * @throws \InvalidArgumentException
152 | * @throws \RuntimeException
153 | * @throws \ReflectionException
154 | */
155 | public function doRun(Test $suite, array $arguments = [], bool $exit = true): TestResult
156 | {
157 | if (isset($arguments['configuration'])) {
158 | $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration'];
159 | }
160 |
161 | $this->handleConfiguration($arguments);
162 |
163 | if (\is_int($arguments['columns']) && $arguments['columns'] < 16) {
164 | $arguments['columns'] = 16;
165 | $tooFewColumnsRequested = true;
166 | }
167 |
168 | if (isset($arguments['bootstrap'])) {
169 | $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap'];
170 | }
171 |
172 | if ($suite instanceof TestCase || $suite instanceof TestSuite) {
173 | if ($arguments['backupGlobals'] === true) {
174 | $suite->setBackupGlobals(true);
175 | }
176 |
177 | if ($arguments['backupStaticAttributes'] === true) {
178 | $suite->setBackupStaticAttributes(true);
179 | }
180 |
181 | if ($arguments['beStrictAboutChangesToGlobalState'] === true) {
182 | $suite->setBeStrictAboutChangesToGlobalState(true);
183 | }
184 | }
185 |
186 | if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) {
187 | \mt_srand($arguments['randomOrderSeed']);
188 | }
189 |
190 | if ($arguments['cacheResult']) {
191 | if (isset($arguments['cacheResultFile'])) {
192 | $cache = new TestResultCache($arguments['cacheResultFile']);
193 | } else {
194 | $cache = new TestResultCache;
195 | }
196 |
197 | $this->addExtension(new ResultCacheExtension($cache));
198 | }
199 |
200 | if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) {
201 | $cache = $cache ?? new NullTestResultCache;
202 |
203 | $cache->load();
204 |
205 | $sorter = new TestSuiteSorter($cache);
206 |
207 | $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']);
208 | $originalExecutionOrder = $sorter->getOriginalExecutionOrder();
209 |
210 | unset($sorter);
211 | }
212 |
213 | if (\is_int($arguments['repeat']) && $arguments['repeat'] > 0) {
214 | $_suite = new TestSuite;
215 |
216 | /* @noinspection PhpUnusedLocalVariableInspection */
217 | foreach (\range(1, $arguments['repeat']) as $step) {
218 | $_suite->addTest($suite);
219 | }
220 |
221 | $suite = $_suite;
222 |
223 | unset($_suite);
224 | }
225 |
226 | $result = $this->createTestResult();
227 |
228 | $listener = new TestListenerAdapter;
229 | $listenerNeeded = false;
230 |
231 | foreach ($this->extensions as $extension) {
232 | if ($extension instanceof TestHook) {
233 | $listener->add($extension);
234 |
235 | $listenerNeeded = true;
236 | }
237 | }
238 |
239 | if ($listenerNeeded) {
240 | $result->addListener($listener);
241 | }
242 |
243 | unset($listener, $listenerNeeded);
244 |
245 | if (!$arguments['convertErrorsToExceptions']) {
246 | $result->convertErrorsToExceptions(false);
247 | }
248 |
249 | if (!$arguments['convertDeprecationsToExceptions']) {
250 | Deprecated::$enabled = false;
251 | }
252 |
253 | if (!$arguments['convertNoticesToExceptions']) {
254 | Notice::$enabled = false;
255 | }
256 |
257 | if (!$arguments['convertWarningsToExceptions']) {
258 | Warning::$enabled = false;
259 | }
260 |
261 | if ($arguments['stopOnError']) {
262 | $result->stopOnError(true);
263 | }
264 |
265 | if ($arguments['stopOnFailure']) {
266 | $result->stopOnFailure(true);
267 | }
268 |
269 | if ($arguments['stopOnWarning']) {
270 | $result->stopOnWarning(true);
271 | }
272 |
273 | if ($arguments['stopOnIncomplete']) {
274 | $result->stopOnIncomplete(true);
275 | }
276 |
277 | if ($arguments['stopOnRisky']) {
278 | $result->stopOnRisky(true);
279 | }
280 |
281 | if ($arguments['stopOnSkipped']) {
282 | $result->stopOnSkipped(true);
283 | }
284 |
285 | if ($arguments['stopOnDefect']) {
286 | $result->stopOnDefect(true);
287 | }
288 |
289 | if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) {
290 | $result->setRegisterMockObjectsFromTestArgumentsRecursively(true);
291 | }
292 |
293 | if ($this->printer === null) {
294 | if (isset($arguments['printer']) &&
295 | $arguments['printer'] instanceof Printer) {
296 | $this->printer = $arguments['printer'];
297 | } else {
298 | $printerClass = ResultPrinter::class;
299 |
300 | if (isset($arguments['printer']) && \is_string($arguments['printer']) && \class_exists($arguments['printer'], false)) {
301 | $class = new ReflectionClass($arguments['printer']);
302 |
303 | if ($class->isSubclassOf(ResultPrinter::class)) {
304 | $printerClass = $arguments['printer'];
305 | }
306 | }
307 |
308 | $this->printer = new $printerClass(
309 | (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null,
310 | $arguments['verbose'],
311 | $arguments['colors'],
312 | $arguments['debug'],
313 | $arguments['columns'],
314 | $arguments['reverseList']
315 | );
316 |
317 | if (isset($originalExecutionOrder) && ($this->printer instanceof CliTestDoxPrinter)) {
318 | /* @var CliTestDoxPrinter */
319 | $this->printer->setOriginalExecutionOrder($originalExecutionOrder);
320 | }
321 | }
322 | }
323 |
324 | $this->printer->write(
325 | Version::getVersionString() . "\n"
326 | );
327 |
328 | self::$versionStringPrinted = true;
329 |
330 | if ($arguments['verbose']) {
331 | $this->writeMessage('Runtime', $this->runtime->getNameWithVersionAndCodeCoverageDriver());
332 |
333 | if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) {
334 | $this->writeMessage(
335 | 'Random seed',
336 | (string) $arguments['randomOrderSeed']
337 | );
338 | }
339 |
340 | if (isset($arguments['configuration'])) {
341 | $this->writeMessage(
342 | 'Configuration',
343 | $arguments['configuration']->getFilename()
344 | );
345 | }
346 |
347 | foreach ($arguments['loadedExtensions'] as $extension) {
348 | $this->writeMessage(
349 | 'Extension',
350 | $extension
351 | );
352 | }
353 |
354 | foreach ($arguments['notLoadedExtensions'] as $extension) {
355 | $this->writeMessage(
356 | 'Extension',
357 | $extension
358 | );
359 | }
360 | }
361 |
362 | if (isset($tooFewColumnsRequested)) {
363 | $this->writeMessage('Error', 'Less than 16 columns requested, number of columns set to 16');
364 | }
365 |
366 | if ($this->runtime->discardsComments()) {
367 | $this->writeMessage('Warning', 'opcache.save_comments=0 set; annotations will not work');
368 | }
369 |
370 | if (isset($arguments['configuration']) && $arguments['configuration']->hasValidationErrors()) {
371 | $this->write(
372 | "\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n"
373 | );
374 |
375 | foreach ($arguments['configuration']->getValidationErrors() as $line => $errors) {
376 | $this->write(\sprintf("\n Line %d:\n", $line));
377 |
378 | foreach ($errors as $msg) {
379 | $this->write(\sprintf(" - %s\n", $msg));
380 | }
381 | }
382 | $this->write("\n Test results may not be as expected.\n\n");
383 | }
384 |
385 | foreach ($arguments['listeners'] as $listener) {
386 | $result->addListener($listener);
387 | }
388 |
389 | $result->addListener($this->printer);
390 |
391 | $codeCoverageReports = 0;
392 |
393 | if (!isset($arguments['noLogging'])) {
394 | if (isset($arguments['testdoxHTMLFile'])) {
395 | $result->addListener(
396 | new HtmlResultPrinter(
397 | $arguments['testdoxHTMLFile'],
398 | $arguments['testdoxGroups'],
399 | $arguments['testdoxExcludeGroups']
400 | )
401 | );
402 | }
403 |
404 | if (isset($arguments['testdoxTextFile'])) {
405 | $result->addListener(
406 | new TextResultPrinter(
407 | $arguments['testdoxTextFile'],
408 | $arguments['testdoxGroups'],
409 | $arguments['testdoxExcludeGroups']
410 | )
411 | );
412 | }
413 |
414 | if (isset($arguments['testdoxXMLFile'])) {
415 | $result->addListener(
416 | new XmlResultPrinter(
417 | $arguments['testdoxXMLFile']
418 | )
419 | );
420 | }
421 |
422 | if (isset($arguments['teamcityLogfile'])) {
423 | $result->addListener(
424 | new TeamCity($arguments['teamcityLogfile'])
425 | );
426 | }
427 |
428 | if (isset($arguments['junitLogfile'])) {
429 | $result->addListener(
430 | new JUnit(
431 | $arguments['junitLogfile'],
432 | $arguments['reportUselessTests']
433 | )
434 | );
435 | }
436 |
437 | if (isset($arguments['coverageClover'])) {
438 | $codeCoverageReports++;
439 | }
440 |
441 | if (isset($arguments['coverageCrap4J'])) {
442 | $codeCoverageReports++;
443 | }
444 |
445 | if (isset($arguments['coverageHtml'])) {
446 | $codeCoverageReports++;
447 | }
448 |
449 | if (isset($arguments['coveragePHP'])) {
450 | $codeCoverageReports++;
451 | }
452 |
453 | if (isset($arguments['coverageText'])) {
454 | $codeCoverageReports++;
455 | }
456 |
457 | if (isset($arguments['coverageXml'])) {
458 | $codeCoverageReports++;
459 | }
460 | }
461 |
462 | if (isset($arguments['noCoverage'])) {
463 | $codeCoverageReports = 0;
464 | }
465 |
466 | if ($codeCoverageReports > 0 && !$this->runtime->canCollectCodeCoverage()) {
467 | $this->writeMessage('Error', 'No code coverage driver is available');
468 |
469 | $codeCoverageReports = 0;
470 | }
471 |
472 | if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) {
473 | $whitelistFromConfigurationFile = false;
474 | $whitelistFromOption = false;
475 |
476 | if (isset($arguments['whitelist'])) {
477 | $this->codeCoverageFilter->addDirectoryToWhitelist($arguments['whitelist']);
478 |
479 | $whitelistFromOption = true;
480 | }
481 |
482 | if (isset($arguments['configuration'])) {
483 | $filterConfiguration = $arguments['configuration']->getFilterConfiguration();
484 |
485 | if (!empty($filterConfiguration['whitelist'])) {
486 | $whitelistFromConfigurationFile = true;
487 | }
488 |
489 | if (!empty($filterConfiguration['whitelist'])) {
490 | foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) {
491 | $this->codeCoverageFilter->addDirectoryToWhitelist(
492 | $dir['path'],
493 | $dir['suffix'],
494 | $dir['prefix']
495 | );
496 | }
497 |
498 | foreach ($filterConfiguration['whitelist']['include']['file'] as $file) {
499 | $this->codeCoverageFilter->addFileToWhitelist($file);
500 | }
501 |
502 | foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) {
503 | $this->codeCoverageFilter->removeDirectoryFromWhitelist(
504 | $dir['path'],
505 | $dir['suffix'],
506 | $dir['prefix']
507 | );
508 | }
509 |
510 | foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) {
511 | $this->codeCoverageFilter->removeFileFromWhitelist($file);
512 | }
513 | }
514 | }
515 | }
516 |
517 | if ($codeCoverageReports > 0) {
518 | $codeCoverage = new CodeCoverage(
519 | null,
520 | $this->codeCoverageFilter
521 | );
522 |
523 | $codeCoverage->setUnintentionallyCoveredSubclassesWhitelist(
524 | [Comparator::class]
525 | );
526 |
527 | $codeCoverage->setCheckForUnintentionallyCoveredCode(
528 | $arguments['strictCoverage']
529 | );
530 |
531 | $codeCoverage->setCheckForMissingCoversAnnotation(
532 | $arguments['strictCoverage']
533 | );
534 |
535 | if (isset($arguments['forceCoversAnnotation'])) {
536 | $codeCoverage->setForceCoversAnnotation(
537 | $arguments['forceCoversAnnotation']
538 | );
539 | }
540 |
541 | if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) {
542 | $codeCoverage->setIgnoreDeprecatedCode(
543 | $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']
544 | );
545 | }
546 |
547 | if (isset($arguments['disableCodeCoverageIgnore'])) {
548 | $codeCoverage->setDisableIgnoredLines(true);
549 | }
550 |
551 | if (!empty($filterConfiguration['whitelist'])) {
552 | $codeCoverage->setAddUncoveredFilesFromWhitelist(
553 | $filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist']
554 | );
555 |
556 | $codeCoverage->setProcessUncoveredFilesFromWhitelist(
557 | $filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist']
558 | );
559 | }
560 |
561 | if (!$this->codeCoverageFilter->hasWhitelist()) {
562 | if (!$whitelistFromConfigurationFile && !$whitelistFromOption) {
563 | $this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.');
564 | } else {
565 | $this->writeMessage('Error', 'Incorrect whitelist config, no code coverage will be generated.');
566 | }
567 |
568 | $codeCoverageReports = 0;
569 |
570 | unset($codeCoverage);
571 | }
572 | }
573 |
574 | if (isset($arguments['xdebugFilterFile'], $filterConfiguration)) {
575 | $this->write("\n");
576 |
577 | $script = (new XdebugFilterScriptGenerator)->generate($filterConfiguration['whitelist']);
578 |
579 | if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(\dirname($arguments['xdebugFilterFile']))) {
580 | $this->write(\sprintf('Cannot write Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile']));
581 |
582 | exit(self::EXCEPTION_EXIT);
583 | }
584 |
585 | \file_put_contents($arguments['xdebugFilterFile'], $script);
586 |
587 | $this->write(\sprintf('Wrote Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile']));
588 |
589 | exit(self::SUCCESS_EXIT);
590 | }
591 |
592 | $this->printer->write("\n");
593 |
594 | if (isset($codeCoverage)) {
595 | $result->setCodeCoverage($codeCoverage);
596 |
597 | if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) {
598 | $codeCoverage->setCacheTokens($arguments['cacheTokens']);
599 | }
600 | }
601 |
602 | $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']);
603 | $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']);
604 | $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']);
605 | $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']);
606 |
607 | if ($arguments['enforceTimeLimit'] === true) {
608 | if (!\class_exists(Invoker::class)) {
609 | $this->writeMessage('Error', 'Package phpunit/php-invoker is required for enforcing time limits');
610 | }
611 |
612 | if (!\extension_loaded('pcntl') || \strpos(\ini_get('disable_functions'), 'pcntl') !== false) {
613 | $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits');
614 | }
615 | }
616 | $result->enforceTimeLimit($arguments['enforceTimeLimit']);
617 | $result->setDefaultTimeLimit($arguments['defaultTimeLimit']);
618 | $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']);
619 | $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']);
620 | $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']);
621 |
622 | if ($suite instanceof TestSuite) {
623 | $this->processSuiteFilters($suite, $arguments);
624 | $suite->setRunTestInSeparateProcess($arguments['processIsolation']);
625 | }
626 |
627 | foreach ($this->extensions as $extension) {
628 | if ($extension instanceof BeforeFirstTestHook) {
629 | $extension->executeBeforeFirstTest();
630 | }
631 | }
632 |
633 | $suite->run($result);
634 |
635 | foreach ($this->extensions as $extension) {
636 | if ($extension instanceof AfterLastTestHook) {
637 | $extension->executeAfterLastTest();
638 | }
639 | }
640 |
641 | $result->flushListeners();
642 |
643 | if ($this->printer instanceof ResultPrinter) {
644 | $this->printer->printResult($result);
645 | }
646 |
647 | if (isset($codeCoverage)) {
648 | if (isset($arguments['coverageClover'])) {
649 | $this->printer->write(
650 | "\nGenerating code coverage report in Clover XML format ..."
651 | );
652 |
653 | try {
654 | $writer = new CloverReport;
655 | $writer->process($codeCoverage, $arguments['coverageClover']);
656 |
657 | $this->printer->write(" done\n");
658 | unset($writer);
659 | } catch (CodeCoverageException $e) {
660 | $this->printer->write(
661 | " failed\n" . $e->getMessage() . "\n"
662 | );
663 | }
664 | }
665 |
666 | if (isset($arguments['coverageCrap4J'])) {
667 | $this->printer->write(
668 | "\nGenerating Crap4J report XML file ..."
669 | );
670 |
671 | try {
672 | $writer = new Crap4jReport($arguments['crap4jThreshold']);
673 | $writer->process($codeCoverage, $arguments['coverageCrap4J']);
674 |
675 | $this->printer->write(" done\n");
676 | unset($writer);
677 | } catch (CodeCoverageException $e) {
678 | $this->printer->write(
679 | " failed\n" . $e->getMessage() . "\n"
680 | );
681 | }
682 | }
683 |
684 | if (isset($arguments['coverageHtml'])) {
685 | $this->printer->write(
686 | "\nGenerating code coverage report in HTML format ..."
687 | );
688 |
689 | try {
690 | $writer = new HtmlReport(
691 | $arguments['reportLowUpperBound'],
692 | $arguments['reportHighLowerBound'],
693 | \sprintf(
694 | ' and PHPUnit %s',
695 | Version::id()
696 | )
697 | );
698 |
699 | $writer->process($codeCoverage, $arguments['coverageHtml']);
700 |
701 | $this->printer->write(" done\n");
702 | unset($writer);
703 | } catch (CodeCoverageException $e) {
704 | $this->printer->write(
705 | " failed\n" . $e->getMessage() . "\n"
706 | );
707 | }
708 | }
709 |
710 | if (isset($arguments['coveragePHP'])) {
711 | $this->printer->write(
712 | "\nGenerating code coverage report in PHP format ..."
713 | );
714 |
715 | try {
716 | $writer = new PhpReport;
717 | $writer->process($codeCoverage, $arguments['coveragePHP']);
718 |
719 | $this->printer->write(" done\n");
720 | unset($writer);
721 | } catch (CodeCoverageException $e) {
722 | $this->printer->write(
723 | " failed\n" . $e->getMessage() . "\n"
724 | );
725 | }
726 | }
727 |
728 | if (isset($arguments['coverageText'])) {
729 | if ($arguments['coverageText'] == 'php://stdout') {
730 | $outputStream = $this->printer;
731 | $colors = $arguments['colors'] && $arguments['colors'] != ResultPrinter::COLOR_NEVER;
732 | } else {
733 | $outputStream = new Printer($arguments['coverageText']);
734 | $colors = false;
735 | }
736 |
737 | $processor = new TextReport(
738 | $arguments['reportLowUpperBound'],
739 | $arguments['reportHighLowerBound'],
740 | $arguments['coverageTextShowUncoveredFiles'],
741 | $arguments['coverageTextShowOnlySummary']
742 | );
743 |
744 | $outputStream->write(
745 | $processor->process($codeCoverage, $colors)
746 | );
747 | }
748 |
749 | if (isset($arguments['coverageXml'])) {
750 | $this->printer->write(
751 | "\nGenerating code coverage report in PHPUnit XML format ..."
752 | );
753 |
754 | try {
755 | $writer = new XmlReport(Version::id());
756 | $writer->process($codeCoverage, $arguments['coverageXml']);
757 |
758 | $this->printer->write(" done\n");
759 | unset($writer);
760 | } catch (CodeCoverageException $e) {
761 | $this->printer->write(
762 | " failed\n" . $e->getMessage() . "\n"
763 | );
764 | }
765 | }
766 | }
767 |
768 | if ($exit) {
769 | if ($result->wasSuccessful()) {
770 | if ($arguments['failOnRisky'] && !$result->allHarmless()) {
771 | exit(self::FAILURE_EXIT);
772 | }
773 |
774 | if ($arguments['failOnWarning'] && $result->warningCount() > 0) {
775 | exit(self::FAILURE_EXIT);
776 | }
777 |
778 | exit(self::SUCCESS_EXIT);
779 | }
780 |
781 | if ($result->errorCount() > 0) {
782 | exit(self::EXCEPTION_EXIT);
783 | }
784 |
785 | if ($result->failureCount() > 0) {
786 | exit(self::FAILURE_EXIT);
787 | }
788 | }
789 |
790 | return $result;
791 | }
792 |
793 | public function setPrinter(ResultPrinter $resultPrinter): void
794 | {
795 | $this->printer = $resultPrinter;
796 | }
797 |
798 | /**
799 | * Returns the loader to be used.
800 | */
801 | public function getLoader(): TestSuiteLoader
802 | {
803 | if ($this->loader === null) {
804 | $this->loader = new StandardTestSuiteLoader;
805 | }
806 |
807 | return $this->loader;
808 | }
809 |
810 | public function addExtension(TestHook $extension): void
811 | {
812 | $this->extensions[] = $extension;
813 | }
814 |
815 | protected function createTestResult(): TestResult
816 | {
817 | return new TestResult;
818 | }
819 |
820 | /**
821 | * Override to define how to handle a failed loading of
822 | * a test suite.
823 | */
824 | protected function runFailed(string $message): void
825 | {
826 | $this->write($message . \PHP_EOL);
827 |
828 | exit(self::FAILURE_EXIT);
829 | }
830 |
831 | protected function write(string $buffer): void
832 | {
833 | if (\PHP_SAPI != 'cli' && \PHP_SAPI != 'phpdbg') {
834 | $buffer = \htmlspecialchars($buffer);
835 | }
836 |
837 | if ($this->printer !== null) {
838 | $this->printer->write($buffer);
839 | } else {
840 | print $buffer;
841 | }
842 | }
843 |
844 | /**
845 | * @throws Exception
846 | */
847 | protected function handleConfiguration(array &$arguments): void
848 | {
849 | if (isset($arguments['configuration']) &&
850 | !$arguments['configuration'] instanceof Configuration) {
851 | $arguments['configuration'] = Configuration::getInstance(
852 | $arguments['configuration']
853 | );
854 | }
855 |
856 | $arguments['debug'] = $arguments['debug'] ?? false;
857 | $arguments['filter'] = $arguments['filter'] ?? false;
858 | $arguments['listeners'] = $arguments['listeners'] ?? [];
859 |
860 | if (isset($arguments['configuration'])) {
861 | $arguments['configuration']->handlePHPConfiguration();
862 |
863 | $phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration();
864 |
865 | if (isset($phpunitConfiguration['backupGlobals']) && !isset($arguments['backupGlobals'])) {
866 | $arguments['backupGlobals'] = $phpunitConfiguration['backupGlobals'];
867 | }
868 |
869 | if (isset($phpunitConfiguration['backupStaticAttributes']) && !isset($arguments['backupStaticAttributes'])) {
870 | $arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes'];
871 | }
872 |
873 | if (isset($phpunitConfiguration['beStrictAboutChangesToGlobalState']) && !isset($arguments['beStrictAboutChangesToGlobalState'])) {
874 | $arguments['beStrictAboutChangesToGlobalState'] = $phpunitConfiguration['beStrictAboutChangesToGlobalState'];
875 | }
876 |
877 | if (isset($phpunitConfiguration['bootstrap']) && !isset($arguments['bootstrap'])) {
878 | $arguments['bootstrap'] = $phpunitConfiguration['bootstrap'];
879 | }
880 |
881 | if (isset($phpunitConfiguration['cacheResult']) && !isset($arguments['cacheResult'])) {
882 | $arguments['cacheResult'] = $phpunitConfiguration['cacheResult'];
883 | }
884 |
885 | if (isset($phpunitConfiguration['cacheResultFile']) && !isset($arguments['cacheResultFile'])) {
886 | $arguments['cacheResultFile'] = $phpunitConfiguration['cacheResultFile'];
887 | }
888 |
889 | if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) {
890 | $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens'];
891 | }
892 |
893 | if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) {
894 | $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens'];
895 | }
896 |
897 | if (isset($phpunitConfiguration['colors']) && !isset($arguments['colors'])) {
898 | $arguments['colors'] = $phpunitConfiguration['colors'];
899 | }
900 |
901 | if (isset($phpunitConfiguration['convertDeprecationsToExceptions']) && !isset($arguments['convertDeprecationsToExceptions'])) {
902 | $arguments['convertDeprecationsToExceptions'] = $phpunitConfiguration['convertDeprecationsToExceptions'];
903 | }
904 |
905 | if (isset($phpunitConfiguration['convertErrorsToExceptions']) && !isset($arguments['convertErrorsToExceptions'])) {
906 | $arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions'];
907 | }
908 |
909 | if (isset($phpunitConfiguration['convertNoticesToExceptions']) && !isset($arguments['convertNoticesToExceptions'])) {
910 | $arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions'];
911 | }
912 |
913 | if (isset($phpunitConfiguration['convertWarningsToExceptions']) && !isset($arguments['convertWarningsToExceptions'])) {
914 | $arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions'];
915 | }
916 |
917 | if (isset($phpunitConfiguration['processIsolation']) && !isset($arguments['processIsolation'])) {
918 | $arguments['processIsolation'] = $phpunitConfiguration['processIsolation'];
919 | }
920 |
921 | if (isset($phpunitConfiguration['stopOnDefect']) && !isset($arguments['stopOnDefect'])) {
922 | $arguments['stopOnDefect'] = $phpunitConfiguration['stopOnDefect'];
923 | }
924 |
925 | if (isset($phpunitConfiguration['stopOnError']) && !isset($arguments['stopOnError'])) {
926 | $arguments['stopOnError'] = $phpunitConfiguration['stopOnError'];
927 | }
928 |
929 | if (isset($phpunitConfiguration['stopOnFailure']) && !isset($arguments['stopOnFailure'])) {
930 | $arguments['stopOnFailure'] = $phpunitConfiguration['stopOnFailure'];
931 | }
932 |
933 | if (isset($phpunitConfiguration['stopOnWarning']) && !isset($arguments['stopOnWarning'])) {
934 | $arguments['stopOnWarning'] = $phpunitConfiguration['stopOnWarning'];
935 | }
936 |
937 | if (isset($phpunitConfiguration['stopOnIncomplete']) && !isset($arguments['stopOnIncomplete'])) {
938 | $arguments['stopOnIncomplete'] = $phpunitConfiguration['stopOnIncomplete'];
939 | }
940 |
941 | if (isset($phpunitConfiguration['stopOnRisky']) && !isset($arguments['stopOnRisky'])) {
942 | $arguments['stopOnRisky'] = $phpunitConfiguration['stopOnRisky'];
943 | }
944 |
945 | if (isset($phpunitConfiguration['stopOnSkipped']) && !isset($arguments['stopOnSkipped'])) {
946 | $arguments['stopOnSkipped'] = $phpunitConfiguration['stopOnSkipped'];
947 | }
948 |
949 | if (isset($phpunitConfiguration['failOnWarning']) && !isset($arguments['failOnWarning'])) {
950 | $arguments['failOnWarning'] = $phpunitConfiguration['failOnWarning'];
951 | }
952 |
953 | if (isset($phpunitConfiguration['failOnRisky']) && !isset($arguments['failOnRisky'])) {
954 | $arguments['failOnRisky'] = $phpunitConfiguration['failOnRisky'];
955 | }
956 |
957 | if (isset($phpunitConfiguration['timeoutForSmallTests']) && !isset($arguments['timeoutForSmallTests'])) {
958 | $arguments['timeoutForSmallTests'] = $phpunitConfiguration['timeoutForSmallTests'];
959 | }
960 |
961 | if (isset($phpunitConfiguration['timeoutForMediumTests']) && !isset($arguments['timeoutForMediumTests'])) {
962 | $arguments['timeoutForMediumTests'] = $phpunitConfiguration['timeoutForMediumTests'];
963 | }
964 |
965 | if (isset($phpunitConfiguration['timeoutForLargeTests']) && !isset($arguments['timeoutForLargeTests'])) {
966 | $arguments['timeoutForLargeTests'] = $phpunitConfiguration['timeoutForLargeTests'];
967 | }
968 |
969 | if (isset($phpunitConfiguration['reportUselessTests']) && !isset($arguments['reportUselessTests'])) {
970 | $arguments['reportUselessTests'] = $phpunitConfiguration['reportUselessTests'];
971 | }
972 |
973 | if (isset($phpunitConfiguration['strictCoverage']) && !isset($arguments['strictCoverage'])) {
974 | $arguments['strictCoverage'] = $phpunitConfiguration['strictCoverage'];
975 | }
976 |
977 | if (isset($phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']) && !isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) {
978 | $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage'];
979 | }
980 |
981 | if (isset($phpunitConfiguration['disallowTestOutput']) && !isset($arguments['disallowTestOutput'])) {
982 | $arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput'];
983 | }
984 |
985 | if (isset($phpunitConfiguration['defaultTimeLimit']) && !isset($arguments['defaultTimeLimit'])) {
986 | $arguments['defaultTimeLimit'] = $phpunitConfiguration['defaultTimeLimit'];
987 | }
988 |
989 | if (isset($phpunitConfiguration['enforceTimeLimit']) && !isset($arguments['enforceTimeLimit'])) {
990 | $arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit'];
991 | }
992 |
993 | if (isset($phpunitConfiguration['disallowTodoAnnotatedTests']) && !isset($arguments['disallowTodoAnnotatedTests'])) {
994 | $arguments['disallowTodoAnnotatedTests'] = $phpunitConfiguration['disallowTodoAnnotatedTests'];
995 | }
996 |
997 | if (isset($phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']) && !isset($arguments['beStrictAboutResourceUsageDuringSmallTests'])) {
998 | $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests'];
999 | }
1000 |
1001 | if (isset($phpunitConfiguration['verbose']) && !isset($arguments['verbose'])) {
1002 | $arguments['verbose'] = $phpunitConfiguration['verbose'];
1003 | }
1004 |
1005 | if (isset($phpunitConfiguration['reverseDefectList']) && !isset($arguments['reverseList'])) {
1006 | $arguments['reverseList'] = $phpunitConfiguration['reverseDefectList'];
1007 | }
1008 |
1009 | if (isset($phpunitConfiguration['forceCoversAnnotation']) && !isset($arguments['forceCoversAnnotation'])) {
1010 | $arguments['forceCoversAnnotation'] = $phpunitConfiguration['forceCoversAnnotation'];
1011 | }
1012 |
1013 | if (isset($phpunitConfiguration['disableCodeCoverageIgnore']) && !isset($arguments['disableCodeCoverageIgnore'])) {
1014 | $arguments['disableCodeCoverageIgnore'] = $phpunitConfiguration['disableCodeCoverageIgnore'];
1015 | }
1016 |
1017 | if (isset($phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']) && !isset($arguments['registerMockObjectsFromTestArgumentsRecursively'])) {
1018 | $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively'];
1019 | }
1020 |
1021 | if (isset($phpunitConfiguration['executionOrder']) && !isset($arguments['executionOrder'])) {
1022 | $arguments['executionOrder'] = $phpunitConfiguration['executionOrder'];
1023 | }
1024 |
1025 | if (isset($phpunitConfiguration['executionOrderDefects']) && !isset($arguments['executionOrderDefects'])) {
1026 | $arguments['executionOrderDefects'] = $phpunitConfiguration['executionOrderDefects'];
1027 | }
1028 |
1029 | if (isset($phpunitConfiguration['resolveDependencies']) && !isset($arguments['resolveDependencies'])) {
1030 | $arguments['resolveDependencies'] = $phpunitConfiguration['resolveDependencies'];
1031 | }
1032 |
1033 | $groupCliArgs = [];
1034 |
1035 | if (!empty($arguments['groups'])) {
1036 | $groupCliArgs = $arguments['groups'];
1037 | }
1038 |
1039 | $groupConfiguration = $arguments['configuration']->getGroupConfiguration();
1040 |
1041 | if (!empty($groupConfiguration['include']) && !isset($arguments['groups'])) {
1042 | $arguments['groups'] = $groupConfiguration['include'];
1043 | }
1044 |
1045 | if (!empty($groupConfiguration['exclude']) && !isset($arguments['excludeGroups'])) {
1046 | $arguments['excludeGroups'] = \array_diff($groupConfiguration['exclude'], $groupCliArgs);
1047 | }
1048 |
1049 | foreach ($arguments['configuration']->getExtensionConfiguration() as $extension) {
1050 | if (!\class_exists($extension['class'], false) && $extension['file'] !== '') {
1051 | require_once $extension['file'];
1052 | }
1053 |
1054 | if (!\class_exists($extension['class'])) {
1055 | throw new Exception(
1056 | \sprintf(
1057 | 'Class "%s" does not exist',
1058 | $extension['class']
1059 | )
1060 | );
1061 | }
1062 |
1063 | $extensionClass = new ReflectionClass($extension['class']);
1064 |
1065 | if (!$extensionClass->implementsInterface(Hook::class)) {
1066 | throw new Exception(
1067 | \sprintf(
1068 | 'Class "%s" does not implement a PHPUnit\Runner\Hook interface',
1069 | $extension['class']
1070 | )
1071 | );
1072 | }
1073 |
1074 | if (\count($extension['arguments']) == 0) {
1075 | $extensionObject = $extensionClass->newInstance();
1076 | } else {
1077 | $extensionObject = $extensionClass->newInstanceArgs(
1078 | $extension['arguments']
1079 | );
1080 | }
1081 |
1082 | \assert($extensionObject instanceof TestHook);
1083 |
1084 | $this->addExtension($extensionObject);
1085 | }
1086 |
1087 | foreach ($arguments['configuration']->getListenerConfiguration() as $listener) {
1088 | if (!\class_exists($listener['class'], false) &&
1089 | $listener['file'] !== '') {
1090 | require_once $listener['file'];
1091 | }
1092 |
1093 | if (!\class_exists($listener['class'])) {
1094 | throw new Exception(
1095 | \sprintf(
1096 | 'Class "%s" does not exist',
1097 | $listener['class']
1098 | )
1099 | );
1100 | }
1101 |
1102 | $listenerClass = new ReflectionClass($listener['class']);
1103 |
1104 | if (!$listenerClass->implementsInterface(TestListener::class)) {
1105 | throw new Exception(
1106 | \sprintf(
1107 | 'Class "%s" does not implement the PHPUnit\Framework\TestListener interface',
1108 | $listener['class']
1109 | )
1110 | );
1111 | }
1112 |
1113 | if (\count($listener['arguments']) == 0) {
1114 | $listener = new $listener['class'];
1115 | } else {
1116 | $listener = $listenerClass->newInstanceArgs(
1117 | $listener['arguments']
1118 | );
1119 | }
1120 |
1121 | $arguments['listeners'][] = $listener;
1122 | }
1123 |
1124 | $loggingConfiguration = $arguments['configuration']->getLoggingConfiguration();
1125 |
1126 | if (isset($loggingConfiguration['coverage-clover']) && !isset($arguments['coverageClover'])) {
1127 | $arguments['coverageClover'] = $loggingConfiguration['coverage-clover'];
1128 | }
1129 |
1130 | if (isset($loggingConfiguration['coverage-crap4j']) && !isset($arguments['coverageCrap4J'])) {
1131 | $arguments['coverageCrap4J'] = $loggingConfiguration['coverage-crap4j'];
1132 |
1133 | if (isset($loggingConfiguration['crap4jThreshold']) && !isset($arguments['crap4jThreshold'])) {
1134 | $arguments['crap4jThreshold'] = $loggingConfiguration['crap4jThreshold'];
1135 | }
1136 | }
1137 |
1138 | if (isset($loggingConfiguration['coverage-html']) && !isset($arguments['coverageHtml'])) {
1139 | if (isset($loggingConfiguration['lowUpperBound']) && !isset($arguments['reportLowUpperBound'])) {
1140 | $arguments['reportLowUpperBound'] = $loggingConfiguration['lowUpperBound'];
1141 | }
1142 |
1143 | if (isset($loggingConfiguration['highLowerBound']) && !isset($arguments['reportHighLowerBound'])) {
1144 | $arguments['reportHighLowerBound'] = $loggingConfiguration['highLowerBound'];
1145 | }
1146 |
1147 | $arguments['coverageHtml'] = $loggingConfiguration['coverage-html'];
1148 | }
1149 |
1150 | if (isset($loggingConfiguration['coverage-php']) && !isset($arguments['coveragePHP'])) {
1151 | $arguments['coveragePHP'] = $loggingConfiguration['coverage-php'];
1152 | }
1153 |
1154 | if (isset($loggingConfiguration['coverage-text']) && !isset($arguments['coverageText'])) {
1155 | $arguments['coverageText'] = $loggingConfiguration['coverage-text'];
1156 |
1157 | if (isset($loggingConfiguration['coverageTextShowUncoveredFiles'])) {
1158 | $arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration['coverageTextShowUncoveredFiles'];
1159 | } else {
1160 | $arguments['coverageTextShowUncoveredFiles'] = false;
1161 | }
1162 |
1163 | if (isset($loggingConfiguration['coverageTextShowOnlySummary'])) {
1164 | $arguments['coverageTextShowOnlySummary'] = $loggingConfiguration['coverageTextShowOnlySummary'];
1165 | } else {
1166 | $arguments['coverageTextShowOnlySummary'] = false;
1167 | }
1168 | }
1169 |
1170 | if (isset($loggingConfiguration['coverage-xml']) && !isset($arguments['coverageXml'])) {
1171 | $arguments['coverageXml'] = $loggingConfiguration['coverage-xml'];
1172 | }
1173 |
1174 | if (isset($loggingConfiguration['plain'])) {
1175 | $arguments['listeners'][] = new ResultPrinter(
1176 | $loggingConfiguration['plain'],
1177 | true
1178 | );
1179 | }
1180 |
1181 | if (isset($loggingConfiguration['teamcity']) && !isset($arguments['teamcityLogfile'])) {
1182 | $arguments['teamcityLogfile'] = $loggingConfiguration['teamcity'];
1183 | }
1184 |
1185 | if (isset($loggingConfiguration['junit']) && !isset($arguments['junitLogfile'])) {
1186 | $arguments['junitLogfile'] = $loggingConfiguration['junit'];
1187 | }
1188 |
1189 | if (isset($loggingConfiguration['testdox-html']) && !isset($arguments['testdoxHTMLFile'])) {
1190 | $arguments['testdoxHTMLFile'] = $loggingConfiguration['testdox-html'];
1191 | }
1192 |
1193 | if (isset($loggingConfiguration['testdox-text']) && !isset($arguments['testdoxTextFile'])) {
1194 | $arguments['testdoxTextFile'] = $loggingConfiguration['testdox-text'];
1195 | }
1196 |
1197 | if (isset($loggingConfiguration['testdox-xml']) && !isset($arguments['testdoxXMLFile'])) {
1198 | $arguments['testdoxXMLFile'] = $loggingConfiguration['testdox-xml'];
1199 | }
1200 |
1201 | $testdoxGroupConfiguration = $arguments['configuration']->getTestdoxGroupConfiguration();
1202 |
1203 | if (isset($testdoxGroupConfiguration['include']) &&
1204 | !isset($arguments['testdoxGroups'])) {
1205 | $arguments['testdoxGroups'] = $testdoxGroupConfiguration['include'];
1206 | }
1207 |
1208 | if (isset($testdoxGroupConfiguration['exclude']) &&
1209 | !isset($arguments['testdoxExcludeGroups'])) {
1210 | $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration['exclude'];
1211 | }
1212 | }
1213 |
1214 | $arguments['addUncoveredFilesFromWhitelist'] = $arguments['addUncoveredFilesFromWhitelist'] ?? true;
1215 | $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null;
1216 | $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null;
1217 | $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null;
1218 | $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false;
1219 | $arguments['cacheResult'] = $arguments['cacheResult'] ?? true;
1220 | $arguments['cacheTokens'] = $arguments['cacheTokens'] ?? false;
1221 | $arguments['colors'] = $arguments['colors'] ?? ResultPrinter::COLOR_DEFAULT;
1222 | $arguments['columns'] = $arguments['columns'] ?? 80;
1223 | $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? true;
1224 | $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true;
1225 | $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true;
1226 | $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true;
1227 | $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30;
1228 | $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false;
1229 | $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false;
1230 | $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0;
1231 | $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false;
1232 | $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? [];
1233 | $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false;
1234 | $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false;
1235 | $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT;
1236 | $arguments['groups'] = $arguments['groups'] ?? [];
1237 | $arguments['processIsolation'] = $arguments['processIsolation'] ?? false;
1238 | $arguments['processUncoveredFilesFromWhitelist'] = $arguments['processUncoveredFilesFromWhitelist'] ?? false;
1239 | $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? \time();
1240 | $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false;
1241 | $arguments['repeat'] = $arguments['repeat'] ?? false;
1242 | $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90;
1243 | $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50;
1244 | $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true;
1245 | $arguments['reverseList'] = $arguments['reverseList'] ?? false;
1246 | $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT;
1247 | $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? true;
1248 | $arguments['stopOnError'] = $arguments['stopOnError'] ?? false;
1249 | $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false;
1250 | $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false;
1251 | $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false;
1252 | $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false;
1253 | $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false;
1254 | $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? false;
1255 | $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false;
1256 | $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? [];
1257 | $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? [];
1258 | $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60;
1259 | $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10;
1260 | $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1;
1261 | $arguments['verbose'] = $arguments['verbose'] ?? false;
1262 | }
1263 |
1264 | /**
1265 | * @throws \ReflectionException
1266 | * @throws \InvalidArgumentException
1267 | */
1268 | private function processSuiteFilters(TestSuite $suite, array $arguments): void
1269 | {
1270 | if (!$arguments['filter'] &&
1271 | empty($arguments['groups']) &&
1272 | empty($arguments['excludeGroups'])) {
1273 | return;
1274 | }
1275 |
1276 | $filterFactory = new Factory;
1277 |
1278 | if (!empty($arguments['excludeGroups'])) {
1279 | $filterFactory->addFilter(
1280 | new ReflectionClass(ExcludeGroupFilterIterator::class),
1281 | $arguments['excludeGroups']
1282 | );
1283 | }
1284 |
1285 | if (!empty($arguments['groups'])) {
1286 | $filterFactory->addFilter(
1287 | new ReflectionClass(IncludeGroupFilterIterator::class),
1288 | $arguments['groups']
1289 | );
1290 | }
1291 |
1292 | if ($arguments['filter']) {
1293 | $filterFactory->addFilter(
1294 | new ReflectionClass(NameFilterIterator::class),
1295 | $arguments['filter']
1296 | );
1297 | }
1298 |
1299 | $suite->injectFilter($filterFactory);
1300 | }
1301 |
1302 | private function writeMessage(string $type, string $message): void
1303 | {
1304 | if (!$this->messagePrinted) {
1305 | $this->write("\n");
1306 | }
1307 |
1308 | $this->write(
1309 | \sprintf(
1310 | "%-15s%s\n",
1311 | $type . ':',
1312 | $message
1313 | )
1314 | );
1315 |
1316 | $this->messagePrinted = true;
1317 | }
1318 | }
1319 |
--------------------------------------------------------------------------------