├── .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 | 9 | {steps} 10 |
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 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=9.0) 5 | * 8.0 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=8.0) 6 | * 7.1 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=7.1) 7 | * 6.5 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=6.5) 8 | * 6.0 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=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 | 252 | 253 |
226 |

Summary

227 |
228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 |
Successful scenarios:{successfulScenarios}
Failed scenarios:{failedScenarios}
Skipped scenarios:{skippedScenarios}
Incomplete scenarios:{incompleteScenarios}
Useless scenarios:{uselessScenarios}
250 |
251 |
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 = "
failure screenshot
"; 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 | --------------------------------------------------------------------------------