Initial commit

This commit is contained in:
ftc
2023-05-17 23:24:39 +03:00
commit 696600086f
5571 changed files with 2035224 additions and 0 deletions
@@ -0,0 +1,39 @@
name: CI
on: [push, pull_request]
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: [7.3, 7.4, 8.0]
env:
CODECEPTION_VERSION: 'dev-4.2-backport-useless-test-event as 4.2.0'
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
- name: Prepare dependencies
run: |
composer update
php ./vendor/bin/robo prepare:dependencies
composer update --prefer-source
php ./vendor/bin/robo prepare:tests
php ./vendor/bin/robo prepare:test-autoloading
composer dump-autoload
- name: Run test suite
run: |
php ./codecept run -c vendor/codeception/module-asserts/
php ./codecept run unit -g core
php ./codecept run cli
@@ -0,0 +1,6 @@
/.idea
/composer.lock
/vendor
/tests
/codecept
/codeception.yml
+14
View File
@@ -0,0 +1,14 @@
# PHPUnit Wrapper
Builds:
* 9.0 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=9.0)
* 8.0 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=8.0)
* 7.1 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=7.1)
* 6.5 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=6.5)
* 6.0 ![Build Status](https://github.com/Codeception/phpunit-wrapper/workflows/CI/badge.svg?branch=6.0)
Codeception heavily relies on PHPUnit for running and managing tests.
Not all PHPUnit classes fit the needs of Codeception, that's why they were extended or redefined.
Releases follow major PHPUnit versions.
+46
View File
@@ -0,0 +1,46 @@
<?php
/**
* This is project's console commands configuration for Robo task runner.
*
* @see http://robo.li/
*/
class RoboFile extends \Robo\Tasks
{
// define public methods as commands
public function prepareDependencies()
{
$config = json_decode(file_get_contents(__DIR__ . '/composer.json'), true);
$config['name'] = 'codeception/phpunit-wrapper-test';
$config['require-dev']['codeception/codeception'] = getenv('CODECEPTION_VERSION');
$config['require-dev']['codeception/module-asserts'] = '^1.0 | ^2.0';
$config['require-dev']['codeception/module-cli'] = '^1.0 | ^2.0';
$config['require-dev']['codeception/module-db'] = '^1.0 | ^2.0';
$config['require-dev']['codeception/module-filesystem'] = '^1.0 | ^2.0';
$config['require-dev']['codeception/module-phpbrowser'] = '^1.0.3 | ^2.0.3';
$config['require-dev']['codeception/util-universalframework'] = '*';
$config['replace'] = ['codeception/phpunit-wrapper' => '*'];
file_put_contents(__DIR__ . '/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
public function prepareTests()
{
$this->_copyDir(__DIR__ . '/vendor/codeception/codeception/tests', __DIR__ . '/tests');
$this->_copy(__DIR__ . '/vendor/codeception/codeception/codeception.yml', __DIR__ .'/codeception.yml');
$this->_symlink(__DIR__ . '/vendor/bin/codecept', __DIR__ . '/codecept');
}
public function prepareTestAutoloading()
{
$config = json_decode(file_get_contents(__DIR__ . '/composer.json'), true);
$config['autoload-dev'] = [
'classmap' => [
'tests/cli/_steps',
'tests/data/DummyClass.php',
'tests/data/claypit/tests/_data'
]
];
file_put_contents(__DIR__ . '/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"name": "codeception/phpunit-wrapper",
"description": "PHPUnit classes used by Codeception",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Davert",
"email": "davert.php@resend.cc"
},
{
"name": "Naktibalda"
}
],
"require": {
"php": ">=7.2",
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"Codeception\\PHPUnit\\": "src/"
}
},
"require-dev": {
"vlucas/phpdotenv": "^3.0",
"codeception/specify": "*",
"consolidation/robo": "^3.0.0-alpha3"
}
}
@@ -0,0 +1,15 @@
<?php
namespace Codeception\PHPUnit;
/**
* Printer implementing this interface prints output to console, thus should be marked as printer and not just a logger
*
* Interface ConsolePrinter
* @package Codeception\PHPUnit
*/
interface ConsolePrinter
{
public function write(string $buffer);
public function printResult(\PHPUnit\Framework\TestResult $result);
}
@@ -0,0 +1,74 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use Codeception\Exception\ElementNotFound;
use Codeception\Lib\Console\Message;
use Symfony\Component\DomCrawler\Crawler as DomCrawler;
use SebastianBergmann\Comparator\ComparisonFailure;
class Crawler extends Page
{
protected function matches($nodes) : bool
{
/** @var $nodes DomCrawler * */
if (!$nodes->count()) {
return false;
}
if ($this->string === '') {
return true;
}
foreach ($nodes as $node) {
if (parent::matches($node->nodeValue)) {
return true;
}
}
return false;
}
protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null):void
{
/** @var $nodes DomCrawler * */
if (!$nodes->count()) {
throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath');
}
$output = "Failed asserting that any element by '$selector'";
$output .= $this->uriMessage('on page');
$output .= " ";
if ($nodes->count() < 10) {
$output .= $this->nodesList($nodes);
} else {
$message = new Message("[total %s elements]");
$output .= $message->with($nodes->count())->getMessage();
}
$output .= "\ncontains text '{$this->string}'";
throw new \PHPUnit\Framework\ExpectationFailedException(
$output,
$comparisonFailure
);
}
protected function failureDescription($other) : string
{
$desc = '';
foreach ($other as $o) {
$desc .= parent::failureDescription($o->textContent);
}
return $desc;
}
protected function nodesList(DomCrawler $nodes, $contains = null)
{
$output = "";
foreach ($nodes as $node) {
if ($contains && strpos($node->nodeValue, $contains) === false) {
continue;
}
$output .= "\n+ " . $node->C14N();
}
return $output;
}
}
@@ -0,0 +1,40 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use SebastianBergmann\Comparator\ComparisonFailure;
class CrawlerNot extends Crawler
{
protected function matches($nodes) : bool
{
return !parent::matches($nodes);
}
protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null) : void
{
if (!$this->string) {
throw new \PHPUnit\Framework\ExpectationFailedException(
"Element '$selector' was found",
$comparisonFailure
);
}
/** @var $nodes DomCrawler * */
$output = "There was '$selector' element";
$output .= $this->uriMessage('on page');
$output .= $this->nodesList($nodes, $this->string);
$output .= "\ncontaining '{$this->string}'";
throw new \PHPUnit\Framework\ExpectationFailedException(
$output,
$comparisonFailure
);
}
public function toString() : string
{
if ($this->string) {
return 'that contains text "' . $this->string . '"';
}
}
}
@@ -0,0 +1,71 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use SebastianBergmann\Comparator\ComparisonFailure;
use SebastianBergmann\Comparator\ArrayComparator;
use SebastianBergmann\Comparator\Factory;
use Codeception\Util\JsonArray;
class JsonContains extends \PHPUnit\Framework\Constraint\Constraint
{
/**
* @var
*/
protected $expected;
public function __construct(array $expected)
{
$this->expected = $expected;
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other Value or object to evaluate.
*
* @return bool
*/
protected function matches($other) : bool
{
$jsonResponseArray = new JsonArray($other);
if (!is_array($jsonResponseArray->toArray())) {
throw new \PHPUnit\Framework\AssertionFailedError('JSON response is not an array: ' . $other);
}
if ($jsonResponseArray->containsArray($this->expected)) {
return true;
}
$comparator = new ArrayComparator();
$comparator->setFactory(new Factory);
try {
$comparator->assertEquals($this->expected, $jsonResponseArray->toArray());
} catch (ComparisonFailure $failure) {
throw new \PHPUnit\Framework\ExpectationFailedException(
"Response JSON does not contain the provided JSON\n",
$failure
);
}
return false;
}
/**
* Returns a string representation of the constraint.
*
* @return string
*/
public function toString() : string
{
//unused
return '';
}
protected function failureDescription($other) : string
{
//unused
return '';
}
}
@@ -0,0 +1,63 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use Codeception\Util\JsonType as JsonTypeUtil;
use Codeception\Util\JsonArray;
class JsonType extends \PHPUnit\Framework\Constraint\Constraint
{
protected $jsonType;
private $match;
public function __construct(array $jsonType, $match = true)
{
$this->jsonType = $jsonType;
$this->match = $match;
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $jsonArray Value or object to evaluate.
*
* @return bool
*/
protected function matches($jsonArray) : bool
{
if ($jsonArray instanceof JsonArray) {
$jsonArray = $jsonArray->toArray();
}
$matched = (new JsonTypeUtil($jsonArray))->matches($this->jsonType);
if ($this->match) {
if ($matched !== true) {
throw new \PHPUnit\Framework\ExpectationFailedException($matched);
}
} else {
if ($matched === true) {
throw new \PHPUnit\Framework\ExpectationFailedException('Unexpectedly response matched: ' . json_encode($jsonArray));
}
}
return true;
}
/**
* Returns a string representation of the constraint.
*
* @return string
*/
public function toString() : string
{
//unused
return '';
}
protected function failureDescription($other) : string
{
//unused
return '';
}
}
@@ -0,0 +1,78 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use Codeception\Lib\Console\Message;
class Page extends \PHPUnit\Framework\Constraint\Constraint
{
protected $uri;
protected $string;
public function __construct($string, $uri = '')
{
$this->string = $this->normalizeText((string)$string);
$this->uri = $uri;
}
/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other Value or object to evaluate.
*
* @return bool
*/
protected function matches($other) : bool
{
$other = $this->normalizeText($other);
return mb_stripos($other, $this->string, 0, 'UTF-8') !== false;
}
/**
* @param $text
* @return string
*/
private function normalizeText($text)
{
$text = strtr($text, "\r\n", " ");
return trim(preg_replace('/\\s{2,}/', ' ', $text));
}
/**
* Returns a string representation of the constraint.
*
* @return string
*/
public function toString() : string
{
return sprintf(
'contains "%s"',
$this->string
);
}
protected function failureDescription($pageContent) : string
{
$message = $this->uriMessage('on page');
$message->append("\n--> ");
$message->append(mb_substr($pageContent, 0, 300, 'utf-8'));
if (mb_strlen($pageContent, 'utf-8') > 300) {
$debugMessage = new Message(
"[Content too long to display. See complete response in '" . codecept_output_dir() . "' directory]"
);
$message->append("\n")->append($debugMessage);
}
$message->append("\n--> ");
return $message->getMessage() . $this->toString();
}
protected function uriMessage($onPage = "")
{
if (!$this->uri) {
return new Message('');
}
$message = new Message($this->uri);
$message->prepend(" $onPage ");
return $message;
}
}
@@ -0,0 +1,79 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use Codeception\Exception\ElementNotFound;
use Codeception\Lib\Console\Message;
use Codeception\Util\Locator;
use SebastianBergmann\Comparator\ComparisonFailure;
class WebDriver extends Page
{
protected function matches($nodes) : bool
{
if (!count($nodes)) {
return false;
}
if ($this->string === '') {
return true;
}
foreach ($nodes as $node) {
/** @var $node \WebDriverElement * */
if (!$node->isDisplayed()) {
continue;
}
if (parent::matches(htmlspecialchars_decode($node->getText()))) {
return true;
}
}
return false;
}
protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null) : void
{
if (!count($nodes)) {
throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath');
}
$output = "Failed asserting that any element by " . Locator::humanReadableString($selector);
$output .= $this->uriMessage('on page');
if (count($nodes) < 5) {
$output .= "\nElements: ";
$output .= $this->nodesList($nodes);
} else {
$message = new Message("[total %s elements]");
$output .= $message->with(count($nodes));
}
$output .= "\ncontains text '" . $this->string . "'";
throw new \PHPUnit\Framework\ExpectationFailedException(
$output,
$comparisonFailure
);
}
protected function failureDescription($nodes) : string
{
$desc = '';
foreach ($nodes as $node) {
$desc .= parent::failureDescription($node->getText());
}
return $desc;
}
protected function nodesList($nodes, $contains = null)
{
$output = "";
foreach ($nodes as $node) {
if ($contains && strpos($node->getText(), $contains) === false) {
continue;
}
/** @var $node \WebDriverElement * */
$message = new Message("\n+ <%s> %s");
$output .= $message->with($node->getTagName(), $node->getText());
}
return $output;
}
}
@@ -0,0 +1,43 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use SebastianBergmann\Comparator\ComparisonFailure;
use Codeception\Util\Locator;
class WebDriverNot extends WebDriver
{
protected function matches($nodes) : bool
{
return !parent::matches($nodes);
}
protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null) : void
{
if (!is_string($selector) || strpos($selector, "'") === false) {
$selector = Locator::humanReadableString($selector);
}
if (!$this->string) {
throw new \PHPUnit\Framework\ExpectationFailedException(
"Element $selector was found",
$comparisonFailure
);
}
$output = "There was $selector element";
$output .= $this->uriMessage("on page");
$output .= $this->nodesList($nodes, $this->string);
$output .= "\ncontaining '{$this->string}'";
throw new \PHPUnit\Framework\ExpectationFailedException(
$output,
$comparisonFailure
);
}
public function toString() : string
{
if ($this->string) {
return 'that contains text "' . $this->string . '"';
}
}
}
@@ -0,0 +1,29 @@
<?php
namespace Codeception\PHPUnit;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
trait DispatcherWrapper
{
/**
* Compatibility wrapper for dispatcher change between Symfony 4 and 5
* @param EventDispatcher $dispatcher
* @param string $eventType
* @param Event $eventObject
*/
protected function dispatch(EventDispatcher $dispatcher, $eventType, Event $eventObject)
{
// The `EventDispatcherInterface` of `Symfony\Contracts` is only implemented in Symfony 4.3 or higher
if ($dispatcher instanceof ContractsEventDispatcherInterface) {
//Symfony 4.3 or higher
$dispatcher->dispatch($eventObject, $eventType);
} else {
//Symfony 4.2 or lower
$dispatcher->dispatch($eventType, $eventObject);
}
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace Codeception\PHPUnit;
use Codeception\PHPUnit\NonFinal\NameFilterIterator;
use Codeception\Test\Descriptor;
/**
* Extended Filter Test from PHPUnit to use Codeception's Descriptor to locate tests.
*
* Class FilterTest
* @package Codeception\PHPUnit
*/
class FilterTest extends NameFilterIterator
{
public function accept():bool
{
$test = $this->getInnerIterator()->current();
if ($test instanceof \PHPUnit\Framework\TestSuite) {
return true;
}
$name = Descriptor::getTestSignature($test);
$index = Descriptor::getTestDataSetIndex($test);
if (!is_null($index)) {
$name .= " with data set #{$index}";
}
$accepted = preg_match($this->filter, $name, $matches);
// This fix the issue when an invalid dataprovider method generate a warning
// See issue https://github.com/Codeception/Codeception/issues/4888
if($test instanceof \PHPUnit\Framework\WarningTestCase) {
$message = $test->getMessage();
$accepted = preg_match($this->filter, $message, $matches);
}
if ($accepted && isset($this->filterMax)) {
$set = end($matches);
$accepted = $set >= $this->filterMin && $set <= $this->filterMax;
}
return $accepted;
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Codeception\PHPUnit;
class Init
{
/**
* @api
*/
public static function init()
{
require_once __DIR__ . DIRECTORY_SEPARATOR . 'phpunit7-interfaces.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'shim.php';
}
}
+138
View File
@@ -0,0 +1,138 @@
<?php
namespace Codeception\PHPUnit;
use Codeception\PHPUnit\DispatcherWrapper;
use Codeception\Event\FailEvent;
use Codeception\Event\SuiteEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\TestInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
class Listener implements \PHPUnit\Framework\TestListener
{
use DispatcherWrapper;
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcher
*/
protected $dispatcher;
protected $unsuccessfulTests = [];
protected $skippedTests = [];
protected $startedTests = [];
public function __construct(EventDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* Risky test.
*
* @param PHPUnit\Framework\Test $test
* @param \Throwable $e
* @param float $time
* @since Method available since Release 4.0.0
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->unsuccessfulTests[] = spl_object_hash($test);
$this->fire('test.useless', new FailEvent($test, $time, $e));
}
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void
{
$this->unsuccessfulTests[] = spl_object_hash($test);
$this->fire(Events::TEST_FAIL, new FailEvent($test, $time, $e));
}
public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->unsuccessfulTests[] = spl_object_hash($test);
$this->fire(Events::TEST_ERROR, new FailEvent($test, $time, $e));
}
// This method was added in PHPUnit 6
public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void
{
$this->unsuccessfulTests[] = spl_object_hash($test);
$this->fire(Events::TEST_WARNING, new FailEvent($test, $time, $e));
}
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
if (in_array(spl_object_hash($test), $this->skippedTests)) {
return;
}
$this->unsuccessfulTests[] = spl_object_hash($test);
$this->fire(Events::TEST_INCOMPLETE, new FailEvent($test, $time, $e));
$this->skippedTests[] = spl_object_hash($test);
}
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
if (in_array(spl_object_hash($test), $this->skippedTests)) {
return;
}
$this->unsuccessfulTests[] = spl_object_hash($test);
$this->fire(Events::TEST_SKIPPED, new FailEvent($test, $time, $e));
$this->skippedTests[] = spl_object_hash($test);
}
public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void
{
$this->dispatch($this->dispatcher, 'suite.start', new SuiteEvent($suite));
}
public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void
{
$this->dispatch($this->dispatcher, 'suite.end', new SuiteEvent($suite));
}
public function startTest(\PHPUnit\Framework\Test $test) : void
{
$this->dispatch($this->dispatcher, Events::TEST_START, new TestEvent($test));
if (!$test instanceof TestInterface) {
return;
}
if ($test->getMetadata()->isBlocked()) {
return;
}
try {
$this->startedTests[] = spl_object_hash($test);
$this->fire(Events::TEST_BEFORE, new TestEvent($test));
} catch (\PHPUnit\Framework\IncompleteTestError $e) {
$test->getTestResultObject()->addFailure($test, $e, 0);
} catch (\PHPUnit\Framework\SkippedTestError $e) {
$test->getTestResultObject()->addFailure($test, $e, 0);
} catch (\Throwable $e) {
$test->getTestResultObject()->addError($test, $e, 0);
}
}
public function endTest(\PHPUnit\Framework\Test $test, float $time) : void
{
$hash = spl_object_hash($test);
if (!in_array($hash, $this->unsuccessfulTests)) {
$this->fire(Events::TEST_SUCCESS, new TestEvent($test, $time));
}
if (in_array($hash, $this->startedTests)) {
$this->fire(Events::TEST_AFTER, new TestEvent($test, $time));
}
$this->dispatch($this->dispatcher, Events::TEST_END, new TestEvent($test, $time));
}
protected function fire($event, TestEvent $eventType)
{
$test = $eventType->getTest();
if ($test instanceof TestInterface) {
foreach ($test->getMetadata()->getGroups() as $group) {
$this->dispatch($this->dispatcher, $event . '.' . $group, $eventType);
}
}
$this->dispatch($this->dispatcher, $event, $eventType);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace Codeception\PHPUnit\Log;
use Codeception\Configuration;
use Codeception\Test\Interfaces\Reported;
use Codeception\Test\Test;
use PHPUnit\Framework\TestCase;
class JUnit extends \Codeception\PHPUnit\NonFinal\JUnit
{
protected $strictAttributes = ['file', 'name', 'class'];
public function startTest(\PHPUnit\Framework\Test $test):void
{
if (!$test instanceof Reported) {
parent::startTest($test);
return;
}
$this->currentTestCase = $this->document->createElement('testcase');
$isStrict = Configuration::config()['settings']['strict_xml'];
foreach ($test->getReportFields() as $attr => $value) {
if ($isStrict and !in_array($attr, $this->strictAttributes)) {
continue;
}
$this->currentTestCase->setAttribute($attr, $value);
}
}
public function endTest(\PHPUnit\Framework\Test $test, float $time):void
{
if ($this->currentTestCase !== null and $test instanceof Test) {
$numAssertions = $test->getNumAssertions();
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
$this->currentTestCase->setAttribute(
'assertions',
$numAssertions
);
}
if ($test instanceof TestCase) {
parent::endTest($test, $time);
return;
}
// since PhpUnit 7.4.0, parent::endTest ignores tests that aren't instances of TestCase
// so I copied this code from PhpUnit 7.3.5
$this->currentTestCase->setAttribute(
'time',
\sprintf('%F', $time)
);
$this->testSuites[$this->testSuiteLevel]->appendChild(
$this->currentTestCase
);
$this->testSuiteTests[$this->testSuiteLevel]++;
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
$this->currentTestCase = null;
}
}
+125
View File
@@ -0,0 +1,125 @@
<?php
namespace Codeception\PHPUnit\Log;
use Codeception\Configuration;
use Codeception\Test\Interfaces\Reported;
use Codeception\Test\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
class PhpUnit extends \Codeception\PHPUnit\NonFinal\JUnit
{
const SUITE_LEVEL = 1;
const FILE_LEVEL = 2;
protected $strictAttributes = ['file', 'name', 'class'];
private $currentFile;
private $currentFileSuite;
public function startTest(\PHPUnit\Framework\Test $test):void
{
if (method_exists($test, 'getFileName') ) {
$filename = $test->getFileName();
} else {
$reflector = new \ReflectionClass($test);
$filename = $reflector->getFileName();
}
if ($filename !== $this->currentFile) {
if ($this->currentFile !== null) {
parent::endTestSuite(new TestSuite());
}
//initialize all values to avoid warnings
$this->testSuiteAssertions[self::FILE_LEVEL] = 0;
$this->testSuiteTests[self::FILE_LEVEL] = 0;
$this->testSuiteTimes[self::FILE_LEVEL] = 0;
$this->testSuiteErrors[self::FILE_LEVEL] = 0;
$this->testSuiteFailures[self::FILE_LEVEL] = 0;
$this->testSuiteSkipped[self::FILE_LEVEL] = 0;
$this->testSuiteLevel = self::FILE_LEVEL;
$this->currentFile = $filename;
$this->currentFileSuite = $this->document->createElement('testsuite');
if ($test instanceof Reported) {
$reportFields = $test->getReportFields();
$class = isset($reportFields['class']) ? $reportFields['class'] : $reportFields['name'];
$this->currentFileSuite->setAttribute('name', $class);
} else {
$this->currentFileSuite->setAttribute('name', get_class($test));
}
$this->currentFileSuite->setAttribute('file', $filename);
$this->testSuites[self::SUITE_LEVEL]->appendChild($this->currentFileSuite);
$this->testSuites[self::FILE_LEVEL] = $this->currentFileSuite;
}
if (!$test instanceof Reported) {
parent::startTest($test);
return;
}
$this->currentTestCase = $this->document->createElement('testcase');
$isStrict = Configuration::config()['settings']['strict_xml'];
foreach ($test->getReportFields() as $attr => $value) {
if ($isStrict and !in_array($attr, $this->strictAttributes)) {
continue;
}
$this->currentTestCase->setAttribute($attr, $value);
}
}
public function endTest(\PHPUnit\Framework\Test $test, float $time):void
{
if ($this->currentTestCase !== null && $test instanceof Test) {
$numAssertions = $test->getNumAssertions();
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
$this->currentTestCase->setAttribute(
'assertions',
$numAssertions
);
}
if ($test instanceof TestCase) {
parent::endTest($test, $time);
return;
}
// In PhpUnit 7.4.*, parent::endTest ignores tests that aren't instances of TestCase
// so I copied this code from PhpUnit 7.3.5
$this->currentTestCase->setAttribute(
'time',
\sprintf('%F', $time)
);
$this->testSuites[$this->testSuiteLevel]->appendChild(
$this->currentTestCase
);
$this->testSuiteTests[$this->testSuiteLevel]++;
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
$this->currentTestCase = null;
}
/**
* Cleans the mess caused by test suite manipulation in startTest
*/
public function endTestSuite(TestSuite $suite): void
{
if ($suite->getName()) {
if ($this->currentFile) {
//close last file in the test suite
parent::endTestSuite(new TestSuite());
$this->currentFile = null;
}
$this->testSuiteLevel = self::SUITE_LEVEL;
}
parent::endTestSuite($suite);
}
}
@@ -0,0 +1,436 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Codeception\PHPUnit\NonFinal;
use DOMDocument;
use DOMElement;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExceptionWrapper;
use PHPUnit\Framework\SelfDescribing;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestFailure;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Filter;
use PHPUnit\Util\Printer;
use PHPUnit\Util\Xml;
use ReflectionClass;
use ReflectionException;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
class JUnit extends Printer implements TestListener
{
/**
* @var DOMDocument
*/
protected $document;
/**
* @var DOMElement
*/
protected $root;
/**
* @var bool
*/
protected $reportUselessTests = false;
/**
* @var bool
*/
protected $writeDocument = true;
/**
* @var DOMElement[]
*/
protected $testSuites = [];
/**
* @var int[]
*/
protected $testSuiteTests = [0];
/**
* @var int[]
*/
protected $testSuiteAssertions = [0];
/**
* @var int[]
*/
protected $testSuiteErrors = [0];
/**
* @var int[]
*/
protected $testSuiteFailures = [0];
/**
* @var int[]
*/
protected $testSuiteSkipped = [0];
/**
* @var int[]
*/
protected $testSuiteTimes = [0];
/**
* @var int
*/
protected $testSuiteLevel = 0;
/**
* @var DOMElement
*/
protected $currentTestCase;
/**
* Constructor.
*
* @param null|mixed $out
*
* @throws \PHPUnit\Framework\Exception
*/
public function __construct($out = null, bool $reportUselessTests = false)
{
$this->document = new DOMDocument('1.0', 'UTF-8');
$this->document->formatOutput = true;
$this->root = $this->document->createElement('testsuites');
$this->document->appendChild($this->root);
parent::__construct($out);
$this->reportUselessTests = $reportUselessTests;
}
/**
* Flush buffer and close output.
*/
public function flush(): void
{
if ($this->writeDocument === true) {
$this->write($this->getXML());
}
parent::flush();
}
/**
* An error occurred.
*
* @throws \InvalidArgumentException
* @throws ReflectionException
*/
public function addError(Test $test, \Throwable $t, float $time): void
{
$this->doAddFault($test, $t, $time, 'error');
$this->testSuiteErrors[$this->testSuiteLevel]++;
}
/**
* A warning occurred.
*
* @throws \InvalidArgumentException
* @throws ReflectionException
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->doAddFault($test, $e, $time, 'warning');
$this->testSuiteFailures[$this->testSuiteLevel]++;
}
/**
* A failure occurred.
*
* @throws \InvalidArgumentException
* @throws ReflectionException
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->doAddFault($test, $e, $time, 'failure');
$this->testSuiteFailures[$this->testSuiteLevel]++;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, \Throwable $t, float $time): void
{
$this->doAddSkipped($test);
}
/**
* Risky test.
*
* @throws ReflectionException
*/
public function addRiskyTest(Test $test, \Throwable $t, float $time): void
{
if (!$this->reportUselessTests || $this->currentTestCase === null) {
return;
}
$error = $this->document->createElement(
'error',
Xml::prepareString(
"Risky Test\n" .
Filter::getFilteredStacktrace($t)
)
);
$error->setAttribute('type', \get_class($t));
$this->currentTestCase->appendChild($error);
$this->testSuiteErrors[$this->testSuiteLevel]++;
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, \Throwable $t, float $time): void
{
$this->doAddSkipped($test);
}
/**
* A testsuite started.
*/
public function startTestSuite(TestSuite $suite): void
{
$testSuite = $this->document->createElement('testsuite');
$testSuite->setAttribute('name', $suite->getName());
if (\class_exists($suite->getName(), false)) {
try {
$class = new ReflectionClass($suite->getName());
$testSuite->setAttribute('file', $class->getFileName());
} catch (ReflectionException $e) {
}
}
if ($this->testSuiteLevel > 0) {
$this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
} else {
$this->root->appendChild($testSuite);
}
$this->testSuiteLevel++;
$this->testSuites[$this->testSuiteLevel] = $testSuite;
$this->testSuiteTests[$this->testSuiteLevel] = 0;
$this->testSuiteAssertions[$this->testSuiteLevel] = 0;
$this->testSuiteErrors[$this->testSuiteLevel] = 0;
$this->testSuiteFailures[$this->testSuiteLevel] = 0;
$this->testSuiteSkipped[$this->testSuiteLevel] = 0;
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
}
/**
* A testsuite ended.
*/
public function endTestSuite(TestSuite $suite): void
{
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'tests',
(string) $this->testSuiteTests[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'assertions',
(string) $this->testSuiteAssertions[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'errors',
(string) $this->testSuiteErrors[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'failures',
(string) $this->testSuiteFailures[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'skipped',
(string) $this->testSuiteSkipped[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'time',
\sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
);
if ($this->testSuiteLevel > 1) {
$this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
$this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
$this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
$this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
$this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
$this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
}
$this->testSuiteLevel--;
}
/**
* A test started.
*
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
* @throws ReflectionException
*/
public function startTest(Test $test): void
{
$usesDataprovider = false;
if (\method_exists($test, 'usesDataProvider')) {
$usesDataprovider = $test->usesDataProvider();
}
$testCase = $this->document->createElement('testcase');
$testCase->setAttribute('name', $test->getName());
$class = new ReflectionClass($test);
$methodName = $test->getName(!$usesDataprovider);
if ($class->hasMethod($methodName)) {
$method = $class->getMethod($methodName);
$testCase->setAttribute('class', $class->getName());
$testCase->setAttribute('classname', \str_replace('\\', '.', $class->getName()));
$testCase->setAttribute('file', $class->getFileName());
$testCase->setAttribute('line', (string) $method->getStartLine());
}
$this->currentTestCase = $testCase;
}
/**
* A test ended.
*/
public function endTest(Test $test, float $time): void
{
$numAssertions = 0;
if (\method_exists($test, 'getNumAssertions')) {
$numAssertions = $test->getNumAssertions();
}
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
$this->currentTestCase->setAttribute(
'assertions',
(string) $numAssertions
);
$this->currentTestCase->setAttribute(
'time',
\sprintf('%F', $time)
);
$this->testSuites[$this->testSuiteLevel]->appendChild(
$this->currentTestCase
);
$this->testSuiteTests[$this->testSuiteLevel]++;
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
$testOutput = '';
if (\method_exists($test, 'hasOutput') && \method_exists($test, 'getActualOutput')) {
$testOutput = $test->hasOutput() ? $test->getActualOutput() : '';
}
if (!empty($testOutput)) {
$systemOut = $this->document->createElement(
'system-out',
Xml::prepareString($testOutput)
);
$this->currentTestCase->appendChild($systemOut);
}
$this->currentTestCase = null;
}
/**
* Returns the XML as a string.
*/
public function getXML(): string
{
return $this->document->saveXML();
}
/**
* Enables or disables the writing of the document
* in flush().
*
* This is a "hack" needed for the integration of
* PHPUnit with Phing.
*/
public function setWriteDocument(/*bool*/ $flag): void
{
if (\is_bool($flag)) {
$this->writeDocument = $flag;
}
}
/**
* Method which generalizes addError() and addFailure()
*
* @throws \InvalidArgumentException
* @throws ReflectionException
*/
private function doAddFault(Test $test, \Throwable $t, float $time, $type): void
{
if ($this->currentTestCase === null) {
return;
}
if ($test instanceof SelfDescribing) {
$buffer = $test->toString() . "\n";
} else {
$buffer = '';
}
$buffer .= TestFailure::exceptionToString($t) . "\n" .
Filter::getFilteredStacktrace($t);
$fault = $this->document->createElement(
$type,
Xml::prepareString($buffer)
);
if ($t instanceof ExceptionWrapper) {
$fault->setAttribute('type', $t->getClassName());
} else {
$fault->setAttribute('type', \get_class($t));
}
$this->currentTestCase->appendChild($fault);
}
private function doAddSkipped(Test $test): void
{
if ($this->currentTestCase === null) {
return;
}
$skipped = $this->document->createElement('skipped');
$this->currentTestCase->appendChild($skipped);
$this->testSuiteSkipped[$this->testSuiteLevel]++;
}
}
@@ -0,0 +1,128 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Codeception\PHPUnit\NonFinal;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\WarningTestCase;
use PHPUnit\Util\RegularExpression;
use RecursiveFilterIterator;
use RecursiveIterator;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
class NameFilterIterator extends RecursiveFilterIterator
{
/**
* @var string
*/
protected $filter;
/**
* @var int
*/
protected $filterMin;
/**
* @var int
*/
protected $filterMax;
/**
* @throws \Exception
*/
public function __construct(RecursiveIterator $iterator, string $filter)
{
parent::__construct($iterator);
$this->setFilter($filter);
}
/**
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
public function accept(): bool
{
$test = $this->getInnerIterator()->current();
if ($test instanceof TestSuite) {
return true;
}
$tmp = \PHPUnit\Util\Test::describe($test);
if ($test instanceof WarningTestCase) {
$name = $test->getMessage();
} else {
if ($tmp[0] !== '') {
$name = \implode('::', $tmp);
} else {
$name = $tmp[1];
}
}
$accepted = @\preg_match($this->filter, $name, $matches);
if ($accepted && isset($this->filterMax)) {
$set = \end($matches);
$accepted = $set >= $this->filterMin && $set <= $this->filterMax;
}
return (bool) $accepted;
}
/**
* @throws \Exception
*/
protected function setFilter(string $filter): void
{
if (RegularExpression::safeMatch($filter, '') === false) {
// Handles:
// * testAssertEqualsSucceeds#4
// * testAssertEqualsSucceeds#4-8
if (\preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
if (isset($matches[3]) && $matches[2] < $matches[3]) {
$filter = \sprintf(
'%s.*with data set #(\d+)$',
$matches[1]
);
$this->filterMin = $matches[2];
$this->filterMax = $matches[3];
} else {
$filter = \sprintf(
'%s.*with data set #%s$',
$matches[1],
$matches[2]
);
}
} // Handles:
// * testDetermineJsonError@JSON_ERROR_NONE
// * testDetermineJsonError@JSON.*
elseif (\preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
$filter = \sprintf(
'%s.*with data set "%s"$',
$matches[1],
$matches[2]
);
}
// Escape delimiters in regular expression. Do NOT use preg_quote,
// to keep magic characters.
$filter = \sprintf('/%s/i', \str_replace(
'/',
'\\/',
$filter
));
}
$this->filter = $filter;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,117 @@
<?php
namespace PHPUnit\Util;
// @codingStandardsIgnoreStart
class Filter
{
// @codingStandardsIgnoreEnd
protected static $filteredClassesPattern = [
'Symfony\Component\Console',
'Codeception\Command\\',
'Codeception\TestCase\\',
];
public static function getFilteredStackTrace($e, $asString = true, $filter = true)
{
$stackTrace = $asString ? '' : [];
$trace = $e->getPrevious() ? $e->getPrevious()->getTrace() : $e->getTrace();
if ($e instanceof \PHPUnit\Framework\ExceptionWrapper) {
$trace = $e->getSerializableTrace();
}
$eFile = $e->getFile();
$eLine = $e->getLine();
if (!self::frameExists($trace, $eFile, $eLine)) {
array_unshift(
$trace,
['file' => $eFile, 'line' => $eLine]
);
}
foreach ($trace as $step) {
if (self::classIsFiltered($step) and $filter) {
continue;
}
if (self::fileIsFiltered($step) and $filter) {
continue;
}
if (!$asString) {
$stackTrace[] = $step;
continue;
}
if (!isset($step['file'])) {
continue;
}
$stackTrace .= $step['file'] . ':' . $step['line'] . "\n";
}
return $stackTrace;
}
protected static function classIsFiltered($step)
{
if (!isset($step['class'])) {
return false;
}
$className = $step['class'];
foreach (self::$filteredClassesPattern as $filteredClassName) {
if (strpos($className, $filteredClassName) === 0) {
return true;
}
}
return false;
}
protected static function fileIsFiltered($step)
{
if (!isset($step['file'])) {
return false;
}
if (strpos($step['file'], 'codecept.phar/') !== false) {
return true;
}
if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'phpunit') !== false) {
return true;
}
if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'codeception') !== false) {
return true;
}
$modulePath = 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR . 'Module';
if (strpos($step['file'], $modulePath) !== false) {
return false; // don`t filter modules
}
if (strpos($step['file'], 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR) !== false) {
return true;
}
return false;
}
/**
* @param array $trace
* @param string $file
* @param int $line
*
* @return bool
*/
private static function frameExists(array $trace, $file, $line)
{
foreach ($trace as $frame) {
if (isset($frame['file']) && $frame['file'] == $file &&
isset($frame['line']) && $frame['line'] == $line) {
return true;
}
}
return false;
}
}
+102
View File
@@ -0,0 +1,102 @@
<?php
namespace Codeception\PHPUnit;
use \PHPUnit\Framework\AssertionFailedError;
use \PHPUnit\Framework\Test;
use PHPUnit\Framework\TestResult;
use \PHPUnit\Runner\BaseTestRunner;
class ResultPrinter extends \PHPUnit\Util\TestDox\ResultPrinter
{
/**
* An error occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \Throwable $e
* @param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR;
$this->failed++;
}
/**
* A failure occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \PHPUnit\Framework\AssertionFailedError $e
* @param float $time
*/
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE;
$this->failed++;
}
/**
* A warning occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \PHPUnit\Framework\Warning $e
* @param float $time
*/
public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING;
$this->warned++;
}
/**
* Incomplete test.
*
* @param \PHPUnit\Framework\Test $test
* @param \Throwable $e
* @param float $time
*/
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE;
$this->incomplete++;
}
/**
* Risky test.
*
* @param \PHPUnit\Framework\Test $test
* @param \Throwable $e
* @param float $time
*
* @since Method available since Release 4.0.0
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY;
$this->risky++;
}
/**
* Skipped test.
*
* @param \PHPUnit\Framework\Test $test
* @param \Throwable $e
* @param float $time
*
* @since Method available since Release 3.0.0
*/
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED;
$this->skipped++;
}
public function startTest(\PHPUnit\Framework\Test $test) : void
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED;
}
public function printResult(TestResult $result): void
{
// TODO: Implement printResult() method.
}
}
@@ -0,0 +1,313 @@
<?php
namespace Codeception\PHPUnit\ResultPrinter;
use Codeception\PHPUnit\ResultPrinter as CodeceptionResultPrinter;
use Codeception\Step;
use Codeception\Step\Meta;
use Codeception\Test\Descriptor;
use Codeception\Test\Interfaces\ScenarioDriven;
use Codeception\TestInterface;
use Codeception\Util\PathResolver;
use PHPUnit\Framework\TestResult;
use SebastianBergmann\Template\Template;
class HTML extends CodeceptionResultPrinter
{
/**
* @var boolean
*/
protected $printsHTML = true;
/**
* @var integer
*/
protected $id = 0;
/**
* @var string
*/
protected $scenarios = '';
/**
* @var string
*/
protected $templatePath;
/**
* @var int
*/
protected $timeTaken = 0;
protected $failures = [];
/**
* Constructor.
*
* @param mixed $out
* @throws InvalidArgumentException
*/
public function __construct($out = null)
{
parent::__construct($out);
$this->templatePath = sprintf(
'%s%stemplate%s',
__DIR__,
DIRECTORY_SEPARATOR,
DIRECTORY_SEPARATOR
);
}
/**
* Handler for 'start class' event.
*
* @param string $name
*/
protected function startClass(string $name):void
{
}
public function endTest(\PHPUnit\Framework\Test $test, float $time) : void
{
$steps = [];
$success = ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED);
if ($success) {
$this->successful++;
}
if ($test instanceof ScenarioDriven) {
$steps = $test->getScenario()->getSteps();
}
$this->timeTaken += $time;
switch ($this->testStatus) {
case \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE:
$scenarioStatus = 'scenarioFailed';
break;
case \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED:
$scenarioStatus = 'scenarioSkipped';
break;
case \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE:
$scenarioStatus = 'scenarioIncomplete';
break;
case \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR:
$scenarioStatus = 'scenarioFailed';
break;
default:
$scenarioStatus = 'scenarioSuccess';
}
$stepsBuffer = '';
$subStepsRendered = [];
foreach ($steps as $step) {
if ($step->getMetaStep()) {
$key = $step->getMetaStep()->getLine() . $step->getMetaStep()->getAction();
$subStepsRendered[$key][] = $this->renderStep($step);
}
}
foreach ($steps as $step) {
if ($step->getMetaStep()) {
$key = $step->getMetaStep()->getLine() . $step->getMetaStep()->getAction();
if (! empty($subStepsRendered[$key])) {
$subStepsBuffer = implode('', $subStepsRendered[$key]);
unset($subStepsRendered[$key]);
$stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer);
}
} else {
$stepsBuffer .= $this->renderStep($step);
}
}
$scenarioTemplate = new Template(
$this->templatePath . 'scenario.html'
);
$failures = '';
$name = Descriptor::getTestSignatureUnique($test);
if (isset($this->failures[$name])) {
$failTemplate = new Template(
$this->templatePath . 'fail.html'
);
foreach ($this->failures[$name] as $failure) {
$failTemplate->setVar(['fail' => nl2br($failure)]);
$failures .= $failTemplate->render() . PHP_EOL;
}
$this->failures[$name] = [];
}
$png = '';
$html = '';
if ($test instanceof TestInterface) {
$reports = $test->getMetadata()->getReports();
if (isset($reports['png'])) {
$localPath = PathResolver::getRelativeDir($reports['png'], codecept_output_dir());
$png = "<tr><td class='error'><div class='screenshot'><img src='$localPath' alt='failure screenshot'></div></td></tr>";
}
if (isset($reports['html'])) {
$localPath = PathResolver::getRelativeDir($reports['html'], codecept_output_dir());
$html = "<tr><td class='error'>See <a href='$localPath' target='_blank'>HTML snapshot</a> of a failed page</td></tr>";
}
}
$toggle = $stepsBuffer ? '<span class="toggle">+</span>' : '';
$testString = htmlspecialchars(ucfirst(Descriptor::getTestAsString($test)));
$testString = preg_replace('~^([\s\w\\\]+):\s~', '<span class="quiet">$1 &raquo;</span> ', $testString);
$scenarioTemplate->setVar(
[
'id' => ++$this->id,
'name' => $testString,
'scenarioStatus' => $scenarioStatus,
'steps' => $stepsBuffer,
'toggle' => $toggle,
'failure' => $failures,
'png' => $png,
'html' => $html,
'time' => round($time, 2)
]
);
$this->scenarios .= $scenarioTemplate->render();
}
public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void
{
$suiteTemplate = new Template(
$this->templatePath . 'suite.html'
);
if (!$suite->getName()) {
return;
}
$suiteTemplate->setVar(['suite' => ucfirst($suite->getName())]);
$this->scenarios .= $suiteTemplate->render();
}
/**
* Handler for 'end run' event.
*/
protected function endRun():void
{
$scenarioHeaderTemplate = new Template(
$this->templatePath . 'scenario_header.html'
);
$status = !$this->failed
? '<span style="color: green">OK</span>'
: '<span style="color: #e74c3c">FAILED</span>';
$scenarioHeaderTemplate->setVar(
[
'name' => 'Codeception Results',
'status' => $status,
'time' => round($this->timeTaken, 1)
]
);
$header = $scenarioHeaderTemplate->render();
$scenariosTemplate = new Template(
$this->templatePath . 'scenarios.html'
);
$scenariosTemplate->setVar(
[
'header' => $header,
'scenarios' => $this->scenarios,
'successfulScenarios' => $this->successful,
'failedScenarios' => $this->failed,
'skippedScenarios' => $this->skipped,
'incompleteScenarios' => $this->incomplete,
'uselessScenarios' => $this->risky,
]
);
$this->write($scenariosTemplate->render());
}
/**
* An error occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \Exception $e
* @param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e);
parent::addError($test, $e, $time);
}
/**
* A failure occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \PHPUnit\Framework\AssertionFailedError $e
* @param float $time
*/
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void
{
$this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e);
parent::addFailure($test, $e, $time);
}
/**
* Starts test
*
* @param \PHPUnit\Framework\Test $test
*/
public function startTest(\PHPUnit\Framework\Test $test):void
{
$name = Descriptor::getTestSignatureUnique($test);
if (isset($this->failures[$name])) {
// test failed in before hook
return;
}
// start test and mark initialize as passed
parent::startTest($test);
}
/**
* @param $step
* @return string
*/
protected function renderStep(Step $step)
{
$stepTemplate = new Template($this->templatePath . 'step.html');
$stepTemplate->setVar(['action' => $step->getHtml(), 'error' => $step->hasFailed() ? 'failedStep' : '']);
return $stepTemplate->render();
}
/**
* @param $metaStep
* @param $substepsBuffer
* @return string
*/
protected function renderSubsteps(Meta $metaStep, $substepsBuffer)
{
$metaTemplate = new Template($this->templatePath . 'substeps.html');
$metaTemplate->setVar(['metaStep' => $metaStep->getHtml(), 'error' => $metaStep->hasFailed() ? 'failedStep' : '', 'steps' => $substepsBuffer, 'id' => uniqid()]);
return $metaTemplate->render();
}
private function cleanMessage($exception)
{
$msg = $exception->getMessage();
if ($exception instanceof \PHPUnit\Framework\ExpectationFailedException && $exception->getComparisonFailure()) {
$msg .= $exception->getComparisonFailure()->getDiff();
}
$msg = str_replace(['<info>','</info>','<bold>','</bold>'], ['','','',''], $msg);
return htmlentities($msg);
}
public function printResult(TestResult $result): void
{
}
}
@@ -0,0 +1,67 @@
<?php
namespace Codeception\PHPUnit\ResultPrinter;
use Codeception\Lib\Console\Output;
use Codeception\PHPUnit\ConsolePrinter;
use Codeception\PHPUnit\ResultPrinter;
use Codeception\Test\Descriptor;
class Report extends ResultPrinter implements ConsolePrinter
{
/**
* @param \PHPUnit\Framework\Test $test
* @param float $time
*/
public function endTest(\PHPUnit\Framework\Test $test, float $time) : void
{
$name = Descriptor::getTestAsString($test);
$success = ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED);
if ($success) {
$this->successful++;
}
if ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE) {
$status = "\033[41;37mFAIL\033[0m";
} elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED) {
$status = 'Skipped';
} elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) {
$status = 'Incomplete';
} elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY) {
$status = 'Useless';
} elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR) {
$status = 'ERROR';
} else {
$status = 'Ok';
}
if (strlen($name) > 75) {
$name = substr($name, 0, 70);
}
$line = $name . str_repeat('.', 75 - strlen($name));
$line .= $status;
$this->write($line . "\n");
}
protected function endRun() : void
{
}
public function printResult(\PHPUnit\Framework\TestResult $result): void
{
$this->write("\nCodeception Results\n");
$this->write(sprintf(
"Successful: %d. Failed: %d. Incomplete: %d. Skipped: %d. Useless: %d",
$this->successful,
$this->failed,
$this->incomplete,
$this->skipped,
$this->risky
) . "\n");
}
public function write(string $buffer) : void
{
parent::write($buffer);
}
}
@@ -0,0 +1,109 @@
<?php
namespace Codeception\PHPUnit\ResultPrinter;
use Codeception\Event\FailEvent;
use Codeception\Events;
use Codeception\PHPUnit\DispatcherWrapper;
use Codeception\Test\Unit;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
class UI extends \PHPUnit\TextUI\DefaultResultPrinter
{
use DispatcherWrapper;
/**
* @var EventDispatcher
*/
protected $dispatcher;
public function __construct(EventDispatcher $dispatcher, $options, $out = null)
{
parent::__construct($out, $options['verbosity'] > OutputInterface::VERBOSITY_NORMAL, $options['colors'] ? 'always' : 'never');
$this->dispatcher = $dispatcher;
}
protected function printDefect(\PHPUnit\Framework\TestFailure $defect, int $count): void
{
$this->write("\n---------\n");
$this->dispatch(
$this->dispatcher,
Events::TEST_FAIL_PRINT,
new FailEvent($defect->failedTest(), null, $defect->thrownException(), $count)
);
}
/**
* @param \PHPUnit\Framework\TestFailure $defect
*/
protected function printDefectTrace(\PHPUnit\Framework\TestFailure $defect): void
{
$this->write($defect->getExceptionAsString());
$this->writeNewLine();
$stackTrace = \PHPUnit\Util\Filter::getFilteredStacktrace($defect->thrownException());
foreach ($stackTrace as $i => $frame) {
if (!isset($frame['file'])) {
continue;
}
$this->write(
sprintf(
"#%d %s(%s)",
$i + 1,
$frame['file'],
isset($frame['line']) ? $frame['line'] : '?'
)
);
$this->writeNewLine();
}
}
public function startTest(\PHPUnit\Framework\Test $test) : void
{
if ($test instanceof Unit) {
parent::startTest($test);
}
}
public function endTest(\PHPUnit\Framework\Test $test, float $time) : void
{
if ($test instanceof \PHPUnit\Framework\TestCase or $test instanceof \Codeception\Test\Test) {
$this->numAssertions += $test->getNumAssertions();
}
$this->lastTestFailed = false;
}
public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->lastTestFailed = true;
}
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void
{
$this->lastTestFailed = true;
}
public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void
{
$this->lastTestFailed = true;
}
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->lastTestFailed = true;
}
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->lastTestFailed = true;
}
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void
{
$this->lastTestFailed = true;
}
}
@@ -0,0 +1,5 @@
<tr >
<td class="error">
{fail}
</td>
</tr>
@@ -0,0 +1,22 @@
<tr class="scenarioRow {scenarioStatus}">
<td>
<p class="{scenarioStatus}" onclick="showHide('{id}', this)">{toggle}
{name} <span style="color: #34495e; font-size: 70%;">{time}s</span></p>
</td>
</tr>
<tr class="scenarioRow {scenarioStatus}">
<td>
<table border="0" width="100%" class="{scenarioStatus} scenarioStepsTable" id="stepContainer{id}">
{steps}
{failure}
{png}
{html}
</table>
</td>
</tr>
@@ -0,0 +1,2 @@
<h1>{name} <small>{status} ({time}s)</small></h1>
@@ -0,0 +1,256 @@
<html>
<head>
<title>Test results</title>
<meta charset='utf-8'>
<link href='https://fonts.googleapis.com/css?family=Varela+Round&v2' rel='stylesheet' type='text/css'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.layout {
margin: 0 auto;
max-width: 1000px;
}
body { font-family: arial, serif; margin: 0; padding: 0; background: #ecf0f1; font-size: 20px; }
h1,h2,h3 { font-family: arial, serif; color: #7f8c8d; }
h1 { font-size: 2.5em; }
h2 { font-size: 1.3em; }
h3 { font-size: 1em; color: #84BBDD; margin: 0.5em 0; }
table { border: none; margin: 0; padding: 0; font-size: 0.9em;}
.scenarioStepsTable .stepName { padding: 5px; }
.scenarioStepsTable td {
background: #fff;
}
.quiet {
color: #333;
font-size: 0.8em;
}
.screenshot {
max-height: 400px;
overflow-y: scroll;
display: block;
}
.screenshot img {
zoom: 0.5;
}
@media (max-width: 1200px) {
#toolbar-filter {
display: none !important;
}
}
.scenarioStepsTable .nostyle {
background: none;
border: none;
}
p {
cursor: pointer;
}
.scenarioRow>td>p {
padding: 5px;
}
.scenarioStepsTable .failedStep {
padding: 10px;
background: #ecf0f1;
border: 2px solid #e74c3c;
border-radius: 0px;
color: #e74c3c;
}
.scenarioStepsTable .error {
background: #999;
padding: 10px;
color: #fff;
border-radius: 0px;
}
.scenarioStepsTable .error a {
color: #eef;
}
.scenarioStepsTable.substeps td {
background: #bdc3c7;
}
.header { font-size: large; font-weight: bold; }
p.scenarioSuccess {
background: rgb(157,213,58); /* Old browsers */
}
.scenario { color: black; }
p.scenarioFailed, p.scenarioError { color: black;
background: #e74c3c
}
table.scenarioFailed tr:last-child { font-weight: bold; }
td.scenarioSuccess { color: green }
td.scenarioFailed { color: red }
.scenarioSkipped { color: teal; }
.scenarioIncomplete { color: gray; }
.scenarioUseless { color: orange; }
.scenarioStepsTable { margin-left: 10px; display: none; color: #333; }
#stepContainerSummary {
background: white;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
padding: 20px;
}
.toggle {
background: rgba(255,255,255,0.5);
border-radius: 10px;
display: inline-block;
width: 20px;
height: 20px;
text-align: center;
margin: auto;
color: #666
}
ul#toolbar-filter {
display: block;
position: fixed;
top: 20px;
left: 0px;
padding: 0px;
}
ul#toolbar-filter li {
list-style: none;
text-align: center;
padding: 20px;
background-color: #3498db;
}
ul#toolbar-filter li a, ul#toolbar-filter li a:hover, ul#toolbar-filter li a:visited {
color: #34495e;
text-decoration: none;
}
ul#toolbar-filter li.disabled {
background-color: #bdc3c7;
}
</style>
<script type="text/javascript">
var showAll = true;
function showHide(nodeId, linkObj)
{
var subObj = document.getElementById('stepContainer' + nodeId);
var toggle = linkObj.childNodes[0];
if (toggle.innerHTML != '-') {
toggle.innerHTML = '-';
subObj.style.display='block';
subObj.style.width = '100%';
} else {
toggle.innerHTML = '+';
subObj.style.display='none';
}
}
function showAllScenarios() {
var toolbar = document.getElementById('toolbar-filter');
for (var i = 0; i < toolbar.children.length; i++) {
toolbar.children[i].className = '';
}
var trs = document.getElementsByTagName('tr');
for(var z = 0; z < trs.length; z++) {
trs[z].style.display = '';
}
showAll = true;
}
function toggleScenarios(name, linkObj) {
var links = document.getElementById('toolbar-filter').children;
var rows = document.getElementsByClassName('scenarioRow');
if (showAll) {
for (var i = 0; i < links.length; i++) {
links[i].className = 'disabled';
}
for (var i = 0; i < rows.length; i++) {
rows[i].style.display = 'none';
}
}
showAll = false;
if (linkObj.className == '') {
linkObj.className = 'disabled';
for (var i = 0; i < rows.length; i++) {
if (rows[i].classList.contains(name)) {
rows[i].style.display = 'none';
}
}
return;
}
if (linkObj.className == 'disabled') {
linkObj.className = '';
for (var i = 0; i < rows.length; i++) {
if (rows[i].classList.contains(name)) {
rows[i].style.display = 'table-row';
}
}
return;
}
}
</script>
</head>
<body>
<ul id="toolbar-filter">
<li> <a href="#" title="Show all" onClick="showAllScenarios()">◯</a></li>
<li> <a href="#" title="Successful" onClick="toggleScenarios('scenarioSuccess', this.parentElement)"><strong>✔</strong> {successfulScenarios}</a></li>
<li> <a href="#" title="Failed" onClick="toggleScenarios('scenarioFailed', this.parentElement)"><strong>✗</strong> {failedScenarios}</a></li>
<li> <a href="#" title="Skipped" onClick="toggleScenarios('scenarioSkipped', this.parentElement)"><strong>S</strong> {skippedScenarios}</a></li>
<li> <a href="#" title="Incomplete" onClick="toggleScenarios('scenarioIncomplete', this.parentElement)"><strong>I</strong> {incompleteScenarios}</a></li>
<li> <a href="#" title="Useless" onClick="toggleScenarios('scenarioUseless', this.parentElement)"><strong>U</strong> {uselessScenarios}</a></li>
</ul>
<div class="layout">
{header}
<table border="0" style="width: 100%;">
{scenarios}
<tr>
<td>
<h2>Summary</h2>
<div id="stepContainerSummary">
<table border="0">
<tr>
<td width="250" class="scenarioSuccess">Successful scenarios:</td>
<td class="scenarioSuccessValue"><strong>{successfulScenarios}</strong></td>
</tr>
<tr>
<td class="scenarioFailed">Failed scenarios:</td>
<td class="scenarioFailedValue"><strong>{failedScenarios}</strong></td>
</tr>
<tr>
<td class="scenarioSkipped">Skipped scenarios:</td>
<td class="scenarioSkippedValue"><strong>{skippedScenarios}</strong></td>
</tr>
<tr>
<td class="scenarioIncomplete">Incomplete scenarios:</td>
<td class="scenarioIncompleteValue"><strong>{incompleteScenarios}</strong></td>
</tr>
<tr>
<td class="scenarioUseless">Useless scenarios:</td>
<td class="scenarioUselessValue"><strong>{uselessScenarios}</strong></td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</div>
</body>
</html>
@@ -0,0 +1,4 @@
<tr>
<td class="stepName {error}">&nbsp;&nbsp;&nbsp;&nbsp;{action}</td>
</tr>
@@ -0,0 +1,12 @@
<tr>
<td class="stepName {error}" ><p onclick="showHide('{id}', this)"><span class="toggle">+</span> {metaStep}</p>
</td>
</tr>
<tr>
<td class="nostyle">
<table border="0" width="100%" class="substeps scenarioStepsTable" id="stepContainer{id}">
{steps}
</table>
</td>
</tr>
@@ -0,0 +1,5 @@
<tr>
<td>
<h3>{suite} Tests</h3>
</td>
</tr>
+202
View File
@@ -0,0 +1,202 @@
<?php
namespace Codeception\PHPUnit;
use Codeception\Configuration;
use Codeception\Exception\ConfigurationException;
class Runner extends NonFinal\TestRunner
{
public static $persistentListeners = [];
protected $defaultListeners = [
'xml' => false,
'phpunit-xml' => false,
'html' => false,
'tap' => false,
'json' => false,
'report' => false
];
protected $config = [];
protected $logDir = null;
public function __construct()
{
$this->config = Configuration::config();
$this->logDir = Configuration::outputDir(); // prepare log dir
$this->phpUnitOverriders();
parent::__construct();
}
public function phpUnitOverriders()
{
require_once __DIR__ . DIRECTORY_SEPARATOR . 'Overrides/Filter.php';
}
/**
* @return null|\PHPUnit\TextUI\ResultPrinter
*/
public function getPrinter()
{
return $this->printer;
}
public function prepareSuite(\PHPUnit\Framework\Test $suite, array &$arguments)
{
$this->handleConfiguration($arguments);
$filterAdded = false;
$filterFactory = new \PHPUnit\Runner\Filter\Factory();
if ($arguments['groups']) {
$filterAdded = true;
$filterFactory->addFilter(
new \ReflectionClass('PHPUnit\Runner\Filter\IncludeGroupFilterIterator'),
$arguments['groups']
);
}
if ($arguments['excludeGroups']) {
$filterAdded = true;
$filterFactory->addFilter(
new \ReflectionClass('PHPUnit\Runner\Filter\ExcludeGroupFilterIterator'),
$arguments['excludeGroups']
);
}
if ($arguments['filter']) {
$filterAdded = true;
$filterFactory->addFilter(
new \ReflectionClass('Codeception\PHPUnit\FilterTest'),
$arguments['filter']
);
}
if ($filterAdded) {
$suite->injectFilter($filterFactory);
}
}
public function doEnhancedRun(
\PHPUnit\Framework\Test $suite,
\PHPUnit\Framework\TestResult $result,
array $arguments = []
) {
unset($GLOBALS['app']); // hook for not to serialize globals
$result->convertErrorsToExceptions(false);
if (isset($arguments['report_useless_tests'])) {
$result->beStrictAboutTestsThatDoNotTestAnything((bool)$arguments['report_useless_tests']);
}
if (isset($arguments['disallow_test_output'])) {
$result->beStrictAboutOutputDuringTests((bool)$arguments['disallow_test_output']);
}
if (empty(self::$persistentListeners)) {
$this->applyReporters($result, $arguments);
}
if (class_exists('\Symfony\Bridge\PhpUnit\SymfonyTestsListener')) {
$arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : [];
$listener = new \Symfony\Bridge\PhpUnit\SymfonyTestsListener();
$listener->globalListenerDisabled();
$arguments['listeners'][] = $listener;
}
$arguments['listeners'][] = $this->printer;
// clean up listeners between suites
foreach ($arguments['listeners'] as $listener) {
$result->addListener($listener);
}
$suite->run($result);
unset($suite);
foreach ($arguments['listeners'] as $listener) {
$result->removeListener($listener);
}
return $result;
}
/**
* @param \PHPUnit\Framework\TestResult $result
* @param array $arguments
*
* @return array
*/
protected function applyReporters(\PHPUnit\Framework\TestResult $result, array $arguments)
{
foreach ($this->defaultListeners as $listener => $value) {
if (!isset($arguments[$listener])) {
$arguments[$listener] = $value;
}
}
if ($arguments['report']) {
self::$persistentListeners[] = $this->instantiateReporter('report');
}
if ($arguments['html']) {
codecept_debug('Printing HTML report into ' . $arguments['html']);
self::$persistentListeners[] = $this->instantiateReporter(
'html',
[$this->absolutePath($arguments['html'])]
);
}
if ($arguments['xml']) {
codecept_debug('Printing JUNIT report into ' . $arguments['xml']);
self::$persistentListeners[] = $this->instantiateReporter(
'xml',
[$this->absolutePath($arguments['xml']), (bool)$arguments['log_incomplete_skipped']]
);
}
if ($arguments['phpunit-xml']) {
codecept_debug('Printing PHPUNIT report into ' . $arguments['phpunit-xml']);
self::$persistentListeners[] = $this->instantiateReporter(
'phpunit-xml',
[$this->absolutePath($arguments['phpunit-xml']), (bool)$arguments['log_incomplete_skipped']]
);
}
if ($arguments['tap']) {
codecept_debug('Printing TAP report into ' . $arguments['tap']);
self::$persistentListeners[] = $this->instantiateReporter('tap', [$this->absolutePath($arguments['tap'])]);
}
if ($arguments['json']) {
codecept_debug('Printing JSON report into ' . $arguments['json']);
self::$persistentListeners[] = $this->instantiateReporter(
'json',
[$this->absolutePath($arguments['json'])]
);
}
foreach (self::$persistentListeners as $listener) {
if ($listener instanceof ConsolePrinter) {
$this->printer = $listener;
continue;
}
$result->addListener($listener);
}
}
protected function instantiateReporter($name, $args = [])
{
if (!isset($this->config['reporters'][$name])) {
throw new ConfigurationException("Reporter $name not defined");
}
return (new \ReflectionClass($this->config['reporters'][$name]))->newInstanceArgs($args);
}
private function absolutePath($path)
{
if ((strpos($path, '/') === 0) or (strpos($path, ':') === 1)) { // absolute path
return $path;
}
return $this->logDir . $path;
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace Codeception\PHPUnit;
abstract class TestCase extends \PHPUnit\Framework\TestCase
{
protected function setUp(): void
{
if (method_exists($this, '_setUp')) {
$this->_setUp();
}
}
protected function tearDown(): void
{
if (method_exists($this, '_tearDown')) {
$this->_tearDown();
}
}
public static function setUpBeforeClass(): void
{
if (method_exists(get_called_class(), '_setUpBeforeClass')) {
static::_setUpBeforeClass();
}
}
public static function tearDownAfterClass(): void
{
if (method_exists(get_called_class(), '_tearDownAfterClass')) {
static::_tearDownAfterClass();
}
}
public function expectExceptionMessageRegExp(string $regularExpression): void
{
$this->expectExceptionMessageMatches($regularExpression);
}
public static function assertRegExp(string $pattern, string $string, string $message = ''): void
{
parent::assertMatchesRegularExpression($pattern, $string, $message);
}
public static function assertNotRegExp(string $pattern, string $string, string $message = ''): void
{
parent::assertDoesNotMatchRegularExpression($pattern, $string, $message);
}
public static function assertFileNotExists(string $filename, string $message = ''): void
{
parent::assertFileDoesNotExist($filename, $message);
}
}
@@ -0,0 +1,620 @@
<?php
// @codingStandardsIgnoreStart
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace {
if (!class_exists('PHPUnit_Util_String')) {
/**
* String helpers.
*/
class PHPUnit_Util_String
{
/**
* Converts a string to UTF-8 encoding.
*
* @param string $string
*
* @return string
*/
public static function convertToUtf8($string)
{
return mb_convert_encoding($string, 'UTF-8');
}
/**
* Checks a string for UTF-8 encoding.
*
* @param string $string
*
* @return bool
*/
protected static function isUtf8($string)
{
$length = strlen($string);
for ($i = 0; $i < $length; $i++) {
if (ord($string[$i]) < 0x80) {
$n = 0;
} elseif ((ord($string[$i]) & 0xE0) == 0xC0) {
$n = 1;
} elseif ((ord($string[$i]) & 0xF0) == 0xE0) {
$n = 2;
} elseif ((ord($string[$i]) & 0xF0) == 0xF0) {
$n = 3;
} else {
return false;
}
for ($j = 0; $j < $n; $j++) {
if ((++$i == $length) || ((ord($string[$i]) & 0xC0) != 0x80)) {
return false;
}
}
}
return true;
}
}
}
}
namespace PHPUnit\Util\Log {
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Codeception\Test\Descriptor;
/**
* A TestListener that generates JSON messages.
*/
if (!class_exists('\PHPUnit\Util\Log\JSON')) {
class JSON extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener
{
/**
* @var string
*/
protected $currentTestSuiteName = '';
/**
* @var string
*/
protected $currentTestName = '';
/**
* @var bool
*/
protected $currentTestPass = true;
/**
* @var array
*/
protected $logEvents = [];
/**
* An error occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \Throwable $e
* @param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->writeCase(
'error',
$time,
\PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
\PHPUnit\Framework\TestFailure::exceptionToString($e),
$test
);
$this->currentTestPass = false;
}
/**
* A warning occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \PHPUnit\Framework\Warning $e
* @param float $time
*/
public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void
{
$this->writeCase(
'warning',
$time,
\PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
\PHPUnit\Framework\TestFailure::exceptionToString($e),
$test
);
$this->currentTestPass = false;
}
/**
* A failure occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \PHPUnit\Framework\AssertionFailedError $e
* @param float $time
*/
public function addFailure(
\PHPUnit\Framework\Test $test,
\PHPUnit\Framework\AssertionFailedError $e,
float $time
): void{
$this->writeCase(
'fail',
$time,
\PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
\PHPUnit\Framework\TestFailure::exceptionToString($e),
$test
);
$this->currentTestPass = false;
}
/**
* Incomplete test.
*
* @param \PHPUnit\Framework\Test $test
* @param Throwable $e
* @param float $time
*/
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->writeCase(
'error',
$time,
\PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
'Incomplete Test: ' . $e->getMessage(),
$test
);
$this->currentTestPass = false;
}
/**
* Risky test.
*
* @param \PHPUnit\Framework\Test $test
* @param Throwable $e
* @param float $time
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->writeCase(
'error',
$time,
\PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
'Risky Test: ' . $e->getMessage(),
$test
);
$this->currentTestPass = false;
}
/**
* Skipped test.
*
* @param \PHPUnit\Framework\Test $test
* @param Throwable $e
* @param float $time
*/
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->writeCase(
'error',
$time,
\PHPUnit\Util\Filter::getFilteredStacktrace($e, false),
'Skipped Test: ' . $e->getMessage(),
$test
);
$this->currentTestPass = false;
}
/**
* A testsuite started.
*
* @param \PHPUnit\Framework\TestSuite $suite
*/
public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void
{
$this->currentTestSuiteName = $suite->getName();
$this->currentTestName = '';
$this->addLogEvent(
[
'event' => 'suiteStart',
'suite' => $this->currentTestSuiteName,
'tests' => count($suite)
]
);
}
/**
* A testsuite ended.
*
* @param \PHPUnit\Framework\TestSuite $suite
*/
public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void
{
$this->currentTestSuiteName = '';
$this->currentTestName = '';
$this->writeArray($this->logEvents);
}
/**
* A test started.
*
* @param \PHPUnit\Framework\Test $test
*/
public function startTest(\PHPUnit\Framework\Test $test): void
{
$this->currentTestName = \PHPUnit\Util\Test::describe($test);
$this->currentTestPass = true;
$this->addLogEvent(
[
'event' => 'testStart',
'suite' => $this->currentTestSuiteName,
'test' => $this->currentTestName
]
);
}
/**
* A test ended.
*
* @param \PHPUnit\Framework\Test $test
* @param float $time
*/
public function endTest(\PHPUnit\Framework\Test $test, float $time): void
{
if ($this->currentTestPass) {
$this->writeCase('pass', $time, [], '', $test);
}
}
/**
* @param string $status
* @param float $time
* @param array $trace
* @param string $message
* @param \PHPUnit\Framework\TestCase|null $test
*/
protected function writeCase($status, float $time, array $trace = [], $message = '', $test = null): void
{
$output = '';
// take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput
if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) {
$output = $test->getActualOutput();
}
$this->addLogEvent(
[
'event' => 'test',
'suite' => $this->currentTestSuiteName,
'test' => $this->currentTestName,
'status' => $status,
'time' => $time,
'trace' => $trace,
'message' => \PHPUnit_Util_String::convertToUtf8($message),
'output' => $output,
]
);
}
/**
* @param array $event_data
*/
protected function addLogEvent($event_data = []): void
{
if (count($event_data)) {
array_push($this->logEvents, $event_data);
}
}
/**
* @param array $buffer
*/
public function writeArray($buffer)
{
array_walk_recursive(
$buffer, function (&$input){
if (is_string($input)) {
$input = \PHPUnit_Util_String::convertToUtf8($input);
}
}
);
$this->write(json_encode($buffer, JSON_PRETTY_PRINT));
}
}
}
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!class_exists('\PHPUnit\Util\Log\TAP')) {
/**
* A TestListener that generates a logfile of the
* test execution using the Test Anything Protocol (TAP).
*/
class TAP extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener
{
/**
* @var int
*/
protected $testNumber = 0;
/**
* @var int
*/
protected $testSuiteLevel = 0;
/**
* @var bool
*/
protected $testSuccessful = true;
/**
* Constructor.
*
* @param mixed $out
*
* @throws \PHPUnit\Framework\Throwable
*/
public function __construct($out = null)
{
parent::__construct($out);
$this->write("TAP version 13\n");
}
/**
* An error occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param Throwable $e
* @param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->writeNotOk($test, 'Error');
}
/**
* A warning occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \PHPUnit\Framework\Warning $e
* @param float $time
*/
public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void
{
$this->writeNotOk($test, 'Warning');
}
/**
* A failure occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \PHPUnit\Framework\AssertionFailedError $e
* @param float $time
*/
public function addFailure(
\PHPUnit\Framework\Test $test,
\PHPUnit\Framework\AssertionFailedError $e,
float $time
): void{
$this->writeNotOk($test, 'Failure');
$message = explode(
"\n",
\PHPUnit\Framework\TestFailure::exceptionToString($e)
);
$diagnostic = [
'message' => $message[0],
'severity' => 'fail'
];
if ($e instanceof \PHPUnit\Framework\ExpectationFailedThrowable) {
$cf = $e->getComparisonFailure();
if ($cf !== null) {
$diagnostic['data'] = [
'got' => $cf->getActual(),
'expected' => $cf->getExpected()
];
}
}
$yaml = new \Symfony\Component\Yaml\Dumper;
$this->write(
sprintf(
" ---\n%s ...\n",
$yaml->dump($diagnostic, 2, 2)
)
);
}
/**
* Incomplete test.
*
* @param \PHPUnit\Framework\Test $test
* @param \Throwable $e
* @param float $time
*/
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->writeNotOk($test, '', 'TODO Incomplete Test');
}
/**
* Risky test.
*
* @param \PHPUnit\Framework\Test $test
* @param Throwable $e
* @param float $time
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->write(
sprintf(
"ok %d - # RISKY%s\n",
$this->testNumber,
$e->getMessage() != '' ? ' ' . $e->getMessage() : ''
)
);
$this->testSuccessful = false;
}
/**
* Skipped test.
*
* @param \PHPUnit\Framework\Test $test
* @param Throwable $e
* @param float $time
*/
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->write(
sprintf(
"ok %d - # SKIP%s\n",
$this->testNumber,
$e->getMessage() != '' ? ' ' . $e->getMessage() : ''
)
);
$this->testSuccessful = false;
}
/**
* A testsuite started.
*
* @param \PHPUnit\Framework\TestSuite $suite
*/
public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void
{
$this->testSuiteLevel++;
}
/**
* A testsuite ended.
*
* @param \PHPUnit\Framework\TestSuite $suite
*/
public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void
{
$this->testSuiteLevel--;
if ($this->testSuiteLevel == 0) {
$this->write(sprintf("1..%d\n", $this->testNumber));
}
}
/**
* A test started.
*
* @param \PHPUnit\Framework\Test $test
*/
public function startTest(\PHPUnit\Framework\Test $test): void
{
$this->testNumber++;
$this->testSuccessful = true;
}
/**
* A test ended.
*
* @param \PHPUnit\Framework\Test $test
* @param float $time
*/
public function endTest(\PHPUnit\Framework\Test $test, float $time): void
{
if ($this->testSuccessful === true) {
$this->write(
sprintf(
"ok %d - %s\n",
$this->testNumber,
Descriptor::getTestSignature($test)
)
);
}
$this->writeDiagnostics($test);
}
/**
* @param \PHPUnit\Framework\Test $test
* @param string $prefix
* @param string $directive
*/
protected function writeNotOk(\PHPUnit\Framework\Test $test, $prefix = '', $directive = '')
{
$this->write(
sprintf(
"not ok %d - %s%s%s\n",
$this->testNumber,
$prefix != '' ? $prefix . ': ' : '',
\PHPUnit\Util\Test::describeAsString($test),
$directive != '' ? ' # ' . $directive : ''
)
);
$this->testSuccessful = false;
}
/**
* @param \PHPUnit\Framework\Test $test
*/
private function writeDiagnostics(\PHPUnit\Framework\Test $test)
{
if (!$test instanceof \PHPUnit\Framework\TestCase) {
return;
}
if (!$test->hasOutput()) {
return;
}
foreach (explode("\n", trim($test->getActualOutput())) as $line) {
$this->write(
sprintf(
"# %s\n",
$line
)
);
}
}
}
}
}
// @codingStandardsIgnoreEnd
@@ -0,0 +1,20 @@
<?php
// @codingStandardsIgnoreStart
// PHPUnit 6 compatibility
namespace PHPUnit\Framework {
if (!interface_exists(Test::class, false)) {
interface Test extends \Countable {
public function run(TestResult $result = null);
}
}
if (!interface_exists(SelfDescribing::class, false)) {
interface SelfDescribing
{
public function toString();
}
}
}
// @codingStandardsIgnoreEnd
+21
View File
@@ -0,0 +1,21 @@
<?php
// @codingStandardsIgnoreStart
// Add aliases for PHPUnit 6
namespace {
if (!class_exists('PHPUnit_Framework_Assert')) {
class_alias('PHPUnit\Framework\Assert', 'PHPUnit_Framework_Assert');
}
if (!class_exists('PHPUnit_Framework_TestCase')) {
class_alias('PHPUnit\Framework\TestCase', 'PHPUnit_Framework_TestCase');
}
if (!class_exists('PHPUnit\Util\Log\JSON') || !class_exists('PHPUnit\Util\Log\TAP')) {
if (class_exists('PHPUnit\Util\Printer')) {
require_once __DIR__ . '/phpunit5-loggers.php'; // TAP and JSON loggers were removed in PHPUnit 6
}
}
}
// @codingStandardsIgnoreEnd