This commit is contained in:
2020-02-01 16:47:12 +07:00
commit 4c619ad6e6
16739 changed files with 3329179 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
.idea
composer.lock
vendor

View File

@@ -0,0 +1,18 @@
language: php
env:
CODECEPTION_VERSION: '2.4.x-dev'
php:
- 5.6
- 7.0
- 7.1
before_script:
- wget https://robo.li/robo.phar
- php robo.phar prepare
- composer update
script:
- php robo.phar test cli
- php robo.phar test "unit -g core"

View File

@@ -0,0 +1,11 @@
# PHPUnit Wrapper
Builds:
* 6.0 [![Build Status](https://travis-ci.org/Codeception/phpunit-wrapper.svg?branch=6.0)](https://travis-ci.org/Codeception/phpunit-wrapper)
* 7.0 [![Build Status](https://travis-ci.org/Codeception/phpunit-wrapper.svg?branch=7.0)](https://travis-ci.org/Codeception/phpunit-wrapper)
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.

View File

@@ -0,0 +1,27 @@
<?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 prepare()
{
$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['replace'] = ['codeception/phpunit-wrapper' => '*'];
file_put_contents(__DIR__ . '/composer.json', json_encode($config));
}
public function test($params)
{
return $this->taskExec(__DIR__ . '/vendor/bin/codecept run ' . $params)
->dir(__DIR__ .'/vendor/codeception/codeception')
->run();
}
}

View File

@@ -0,0 +1,31 @@
{
"name": "codeception/phpunit-wrapper",
"description": "PHPUnit classes used by Codeception",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Davert",
"email": "davert.php@resend.cc"
}
],
"replace": {
"codeception/phpunit-wrapper": "*"
},
"require": {
"phpunit/phpunit": ">=5.7.27 <7.0",
"phpunit/php-code-coverage": ">=4.0.4 <6.0",
"sebastian/comparator": ">=1.2.4 <3.0",
"sebastian/diff": ">=1.4 <4.0"
},
"autoload":{
"psr-4":{
"Codeception\\PHPUnit\\": "src\\"
}
},
"require-dev": {
"vlucas/phpdotenv": "^2.4",
"codeception/specify": "*"
}
}

View File

@@ -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($buffer);
public function printResult(\PHPUnit\Framework\TestResult $result);
}

View File

@@ -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)
{
/** @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)
{
/** @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)
{
$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;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use SebastianBergmann\Comparator\ComparisonFailure;
class CrawlerNot extends Crawler
{
protected function matches($nodes)
{
return !parent::matches($nodes);
}
protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null)
{
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()
{
if ($this->string) {
return 'that contains text "' . $this->string . '"';
}
}
}

View File

@@ -0,0 +1,70 @@
<?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)
{
parent::__construct();
$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)
{
$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
);
}
}
/**
* Returns a string representation of the constraint.
*
* @return string
*/
public function toString()
{
//unused
return '';
}
protected function failureDescription($other)
{
//unused
return '';
}
}

View File

@@ -0,0 +1,64 @@
<?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)
{
parent::__construct();
$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)
{
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()
{
//unused
return '';
}
protected function failureDescription($other)
{
//unused
return '';
}
}

View File

@@ -0,0 +1,79 @@
<?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 = '')
{
parent::__construct();
$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)
{
$other = $this->normalizeText($other);
return mb_stripos($other, $this->string, null, '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()
{
return sprintf(
'contains "%s"',
$this->string
);
}
protected function failureDescription($pageContent)
{
$message = $this->uriMessage('on page');
$message->append("\n--> ");
$message->append(substr($pageContent, 0, 300));
if (strlen($pageContent) > 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;
}
}

View File

@@ -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)
{
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)
{
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)
{
$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;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Codeception\PHPUnit\Constraint;
use SebastianBergmann\Comparator\ComparisonFailure;
use Codeception\Util\Locator;
class WebDriverNot extends WebDriver
{
protected function matches($nodes)
{
return !parent::matches($nodes);
}
protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null)
{
$selectorString = Locator::humanReadableString($selector);
if (!$this->string) {
throw new \PHPUnit\Framework\ExpectationFailedException(
"Element $selectorString was found",
$comparisonFailure
);
}
$output = "There was $selectorString 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()
{
if ($this->string) {
return 'that contains text "' . $this->string . '"';
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Codeception\PHPUnit;
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 \PHPUnit\Runner\Filter\NameFilterIterator
{
public function accept()
{
$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;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Codeception\PHPUnit;
class Init
{
/**
* @api
*/
public static function init()
{
require_once __DIR__ . DIRECTORY_SEPARATOR . 'shim.php';
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace Codeception\PHPUnit;
use Codeception\Event\FailEvent;
use Codeception\Event\SuiteEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\TestInterface;
use Exception;
use PHPUnit\Framework\Test;
use Symfony\Component\EventDispatcher\EventDispatcher;
class Listener implements \PHPUnit\Framework\TestListener
{
/**
* @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 Exception $e
* @param float $time
* @since Method available since Release 4.0.0
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, Exception $e, $time)
{
}
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time)
{
$this->unsuccessfulTests[] = spl_object_hash($test);
$this->fire(Events::TEST_FAIL, new FailEvent($test, $time, $e));
}
public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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, $time)
{
$this->unsuccessfulTests[] = spl_object_hash($test);
$this->fire(Events::TEST_WARNING, new FailEvent($test, $time, $e));
}
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
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, \Exception $e, $time)
{
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)
{
$this->dispatcher->dispatch('suite.start', new SuiteEvent($suite));
}
public function endTestSuite(\PHPUnit\Framework\TestSuite $suite)
{
$this->dispatcher->dispatch('suite.end', new SuiteEvent($suite));
}
public function startTest(\PHPUnit\Framework\Test $test)
{
$this->dispatcher->dispatch(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 (\Exception $e) {
$test->getTestResultObject()->addError($test, $e, 0);
}
}
public function endTest(\PHPUnit\Framework\Test $test, $time)
{
$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->dispatcher->dispatch(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->dispatcher->dispatch($event . '.' . $group, $eventType);
}
}
$this->dispatcher->dispatch($event, $eventType);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Codeception\PHPUnit\Log;
use Codeception\Configuration;
use Codeception\Test\Interfaces\Reported;
use Codeception\Test\Test;
class JUnit extends \PHPUnit\Util\Log\JUnit
{
protected $strictAttributes = ['file', 'name', 'class'];
public function startTest(\PHPUnit\Framework\Test $test)
{
if (!$test instanceof Reported) {
return parent::startTest($test);
}
$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, $time)
{
if ($this->currentTestCase !== null and $test instanceof Test) {
$numAssertions = $test->getNumAssertions();
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
$this->currentTestCase->setAttribute(
'assertions',
$numAssertions
);
}
parent::endTest($test, $time);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Codeception\PHPUnit;
use \PHPUnit\Framework\AssertionFailedError;
use \PHPUnit\Framework\Test;
use \PHPUnit\Runner\BaseTestRunner;
class ResultPrinter extends \PHPUnit\Util\TestDox\ResultPrinter
{
/**
* An error occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \Exception $e
* @param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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, $time)
{
$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, $time)
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING;
$this->warned++;
}
/**
* Incomplete test.
*
* @param \PHPUnit\Framework\Test $test
* @param \Exception $e
* @param float $time
*/
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE;
$this->incomplete++;
}
/**
* Risky test.
*
* @param \PHPUnit\Framework\Test $test
* @param \Exception $e
* @param float $time
*
* @since Method available since Release 4.0.0
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY;
$this->risky++;
}
/**
* Skipped test.
*
* @param \PHPUnit\Framework\Test $test
* @param \Exception $e
* @param float $time
*
* @since Method available since Release 3.0.0
*/
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED;
$this->skipped++;
}
public function startTest(\PHPUnit\Framework\Test $test)
{
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED;
}
}

View File

@@ -0,0 +1,303 @@
<?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;
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($name)
{
}
public function endTest(\PHPUnit\Framework\Test $test, $time)
{
$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 = '';
$subStepsBuffer = '';
$subStepsRendered = [];
foreach ($steps as $step) {
if ($step->getMetaStep()) {
$subStepsRendered[$step->getMetaStep()->getAction()][] = $this->renderStep($step);
}
}
foreach ($steps as $step) {
if ($step->getMetaStep()) {
if (! empty($subStepsRendered[$step->getMetaStep()->getAction()])) {
$subStepsBuffer = implode('', $subStepsRendered[$step->getMetaStep()->getAction()]);
unset($subStepsRendered[$step->getMetaStep()->getAction()]);
$stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer);
}
} else {
$stepsBuffer .= $this->renderStep($step);
}
}
$scenarioTemplate = new \Text_Template(
$this->templatePath . 'scenario.html'
);
$failures = '';
$name = Descriptor::getTestSignatureUnique($test);
if (isset($this->failures[$name])) {
$failTemplate = new \Text_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)
{
$suiteTemplate = new \Text_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()
{
$scenarioHeaderTemplate = new \Text_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 \Text_Template(
$this->templatePath . 'scenarios.html'
);
$scenariosTemplate->setVar(
[
'header' => $header,
'scenarios' => $this->scenarios,
'successfulScenarios' => $this->successful,
'failedScenarios' => $this->failed,
'skippedScenarios' => $this->skipped,
'incompleteScenarios' => $this->incomplete
]
);
$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, \Exception $e, $time)
{
$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, $time)
{
$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)
{
$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 \Text_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 \Text_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();
$msg = str_replace(['<info>','</info>','<bold>','</bold>'], ['','','',''], $msg);
return htmlentities($msg);
}
}

View File

@@ -0,0 +1,64 @@
<?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, $time)
{
$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_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()
{
$this->write("\nCodeception Results\n");
$this->write(sprintf(
"Successful: %s. Failed: %s. Incomplete: %s. Skipped: %s",
$this->successful,
$this->failed,
$this->skipped,
$this->incomplete
) . "\n");
}
public function printResult(\PHPUnit\Framework\TestResult $result)
{
}
public function write($buffer)
{
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Codeception\PHPUnit\ResultPrinter;
use Codeception\Event\FailEvent;
use Codeception\Events;
use Codeception\Test\Unit;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
class UI extends \PHPUnit\TextUI\ResultPrinter
{
/**
* @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, $count)
{
$this->write("\n---------\n");
$this->dispatcher->dispatch(
Events::TEST_FAIL_PRINT,
new FailEvent($defect->failedTest(), null, $defect->thrownException(), $count)
);
}
/**
* @param \PHPUnit\Framework\TestFailure $defect
*/
protected function printDefectTrace(\PHPUnit\Framework\TestFailure $defect)
{
$this->write($defect->getExceptionAsString());
$this->writeNewLine();
$stackTrace = \PHPUnit\Util\Filter::getFilteredStacktrace($defect->thrownException(), false);
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)
{
if ($test instanceof Unit) {
parent::startTest($test);
}
}
public function endTest(\PHPUnit\Framework\Test $test, $time)
{
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, \Exception $e, $time)
{
$this->lastTestFailed = true;
}
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time)
{
$this->lastTestFailed = true;
}
public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, $time)
{
$this->lastTestFailed = true;
}
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->lastTestFailed = true;
}
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->lastTestFailed = true;
}
}

View File

@@ -0,0 +1,5 @@
<tr >
<td class="error">
{fail}
</td>
</tr>

View File

@@ -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>

View File

@@ -0,0 +1,2 @@
<h1>{name} <small>{status} ({time}s)</small></h1>

View File

@@ -0,0 +1,250 @@
<html>
<head>
<title>Test results</title>
<meta charset='utf-8'>
<link href='http://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; }
.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>
</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>
</table>
</div>
</td>
</tr>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,4 @@
<tr>
<td class="stepName {error}">&nbsp;&nbsp;&nbsp;&nbsp;{action}</td>
</tr>

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
<tr>
<td>
<h3>{suite} Tests</h3>
</td>
</tr>

View File

@@ -0,0 +1,184 @@
<?php
namespace Codeception\PHPUnit;
use Codeception\Configuration;
use Codeception\Exception\ConfigurationException;
class Runner extends \PHPUnit\TextUI\TestRunner
{
public static $persistentListeners = [];
protected $defaultListeners = [
'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);
$filterFactory = new \PHPUnit\Runner\Filter\Factory();
if ($arguments['groups']) {
$filterFactory->addFilter(
new \ReflectionClass('PHPUnit\Runner\Filter\IncludeGroupFilterIterator'),
$arguments['groups']
);
}
if ($arguments['excludeGroups']) {
$filterFactory->addFilter(
new \ReflectionClass('PHPUnit\Runner\Filter\ExcludeGroupFilterIterator'),
$arguments['excludeGroups']
);
}
if ($arguments['filter']) {
$filterFactory->addFilter(
new \ReflectionClass('Codeception\PHPUnit\FilterTest'),
$arguments['filter']
);
}
$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'] : [];
$arguments['listeners'][] = new \Symfony\Bridge\PhpUnit\SymfonyTestsListener();
}
$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['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;
}
}

View File

@@ -0,0 +1,612 @@
<?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.
*/
/**
* 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 Exception $e
* @param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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, $time)
{
$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, $time)
{
$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 Exception $e
* @param float $time
*/
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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 Exception $e
* @param float $time
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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 Exception $e
* @param float $time
*/
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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)
{
$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)
{
$this->currentTestSuiteName = '';
$this->currentTestName = '';
$this->writeAll($this->logEvents);
}
/**
* A test started.
*
* @param \PHPUnit\Framework\Test $test
*/
public function startTest(\PHPUnit\Framework\Test $test)
{
$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, $time)
{
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, $time, array $trace = [], $message = '', $test = null)
{
$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 = [])
{
if (count($event_data)) {
array_push($this->logEvents, $event_data);
}
}
/**
* @param array $buffer
*/
public function writeAll($buffer)
{
array_walk_recursive(
$buffer, function (&$input) {
if (is_string($input)) {
$input = \PHPUnit_Util_String::convertToUtf8($input);
}
}
);
parent::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\Exception
*/
public function __construct($out = null)
{
parent::__construct($out);
$this->write("TAP version 13\n");
}
/**
* An error occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param Exception $e
* @param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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, $time)
{
$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, $time)
{
$this->writeNotOk($test, 'Failure');
$message = explode(
"\n",
\PHPUnit\Framework\TestFailure::exceptionToString($e)
);
$diagnostic = [
'message' => $message[0],
'severity' => 'fail'
];
if ($e instanceof \PHPUnit\Framework\ExpectationFailedException) {
$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 \Exception $e
* @param float $time
*/
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->writeNotOk($test, '', 'TODO Incomplete Test');
}
/**
* Risky test.
*
* @param \PHPUnit\Framework\Test $test
* @param Exception $e
* @param float $time
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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 Exception $e
* @param float $time
*/
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$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)
{
$this->testSuiteLevel++;
}
/**
* A testsuite ended.
*
* @param \PHPUnit\Framework\TestSuite $suite
*/
public function endTestSuite(\PHPUnit\Framework\TestSuite $suite)
{
$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)
{
$this->testNumber++;
$this->testSuccessful = true;
}
/**
* A test ended.
*
* @param \PHPUnit\Framework\Test $test
* @param float $time
*/
public function endTest(\PHPUnit\Framework\Test $test, $time)
{
if ($this->testSuccessful === true) {
$this->write(
sprintf(
"ok %d - %s\n",
$this->testNumber,
\PHPUnit\Util\Test::describe($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::describe($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

View File

@@ -0,0 +1,71 @@
<?php
// @codingStandardsIgnoreStart
// Add aliases for PHPUnit 6
namespace {
if (!class_exists('PHPUnit\Framework\Assert') && class_exists('PHPUnit_Framework_Assert')) {
class_alias('PHPUnit_Framework_Assert', 'PHPUnit\Framework\Assert');
}
// load PHPUnit 4.8 classes avoiding its so-called compatibility layer
if (class_exists('PHPUnit_Framework_TestCase') && !class_exists('PHPUnit\Framework\TestCase', false)) {
class_alias('PHPUnit_Framework_AssertionFailedError', 'PHPUnit\Framework\AssertionFailedError');
class_alias('PHPUnit_Framework_Test', 'PHPUnit\Framework\Test');
class_alias('PHPUnit_Framework_TestCase', 'PHPUnit\Framework\TestCase');
class_alias('PHPUnit_Runner_BaseTestRunner', 'PHPUnit\Runner\BaseTestRunner');
class_alias('PHPUnit_Framework_TestListener', 'PHPUnit\Framework\TestListener');
class_alias('PHPUnit_Framework_TestSuite', 'PHPUnit\Framework\TestSuite');
class_alias('PHPUnit_Framework_Constraint', 'PHPUnit\Framework\Constraint\Constraint');
class_alias('PHPUnit_Framework_Constraint_Not', 'PHPUnit\Framework\Constraint\LogicalNot');
class_alias('PHPUnit_Framework_TestSuite_DataProvider', 'PHPUnit\Framework\DataProviderTestSuite');
class_alias('PHPUnit_Framework_Exception', 'PHPUnit\Framework\Exception');
class_alias('PHPUnit_Framework_ExceptionWrapper', 'PHPUnit\Framework\ExceptionWrapper');
class_alias('PHPUnit_Framework_ExpectationFailedException', 'PHPUnit\Framework\ExpectationFailedException');
class_alias('PHPUnit_Framework_IncompleteTestError', 'PHPUnit\Framework\IncompleteTestError');
class_alias('PHPUnit_Framework_SelfDescribing', 'PHPUnit\Framework\SelfDescribing');
class_alias('PHPUnit_Framework_SkippedTestError', 'PHPUnit\Framework\SkippedTestError');
class_alias('PHPUnit_Framework_TestFailure', 'PHPUnit\Framework\TestFailure');
class_alias('PHPUnit_Framework_TestResult', 'PHPUnit\Framework\TestResult');
class_alias('PHPUnit_Framework_Warning', 'PHPUnit\Framework\Warning');
class_alias('PHPUnit_Runner_Filter_Factory', 'PHPUnit\Runner\Filter\Factory');
class_alias('PHPUnit_Runner_Filter_Test', 'PHPUnit\Runner\Filter\NameFilterIterator');
class_alias('PHPUnit_Runner_Filter_Group_Include', 'PHPUnit\Runner\Filter\IncludeGroupFilterIterator');
class_alias('PHPUnit_Runner_Filter_Group_Exclude', 'PHPUnit\Runner\Filter\ExcludeGroupFilterIterator');
class_alias('PHPUnit_Runner_Version', 'PHPUnit\Runner\Version');
class_alias('PHPUnit_TextUI_ResultPrinter', 'PHPUnit\TextUI\ResultPrinter');
class_alias('PHPUnit_TextUI_TestRunner', 'PHPUnit\TextUI\TestRunner');
class_alias('PHPUnit_Util_Log_JUnit', 'PHPUnit\Util\Log\JUnit');
class_alias('PHPUnit_Util_Printer', 'PHPUnit\Util\Printer');
class_alias('PHPUnit_Util_Test', 'PHPUnit\Util\Test');
class_alias('PHPUnit_Util_TestDox_ResultPrinter', 'PHPUnit\Util\TestDox\ResultPrinter');
}
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
}
}
// phpunit codecoverage updates
if (class_exists('PHP_CodeCoverage') && !class_exists('SebastianBergmann\CodeCoverage\CodeCoverage')) {
class_alias('PHP_CodeCoverage', 'SebastianBergmann\CodeCoverage\CodeCoverage');
class_alias('PHP_CodeCoverage_Report_Text', 'SebastianBergmann\CodeCoverage\Report\Text');
class_alias('PHP_CodeCoverage_Report_PHP', 'SebastianBergmann\CodeCoverage\Report\PHP');
class_alias('PHP_CodeCoverage_Report_Clover', 'SebastianBergmann\CodeCoverage\Report\Clover');
class_alias('PHP_CodeCoverage_Report_Crap4j', 'SebastianBergmann\CodeCoverage\Report\Crap4j');
class_alias('PHP_CodeCoverage_Report_HTML', 'SebastianBergmann\CodeCoverage\Report\Html\Facade');
class_alias('PHP_CodeCoverage_Report_XML', 'SebastianBergmann\CodeCoverage\Report\Xml\Facade');
class_alias('PHP_CodeCoverage_Exception', 'SebastianBergmann\CodeCoverage\Exception');
class_alias('PHP_CodeCoverage_Driver', 'SebastianBergmann\CodeCoverage\Driver\Driver');
}
if (class_exists('PHP_Timer') && !class_exists('SebastianBergmann\Timer\Timer')) {
class_alias('PHP_Timer', 'SebastianBergmann\Timer\Timer');
}
if (!class_exists('\PHPUnit\Framework\Constraint\LogicalNot') && class_exists('\PHPUnit\Framework\Constraint\Not')) {
class_alias('\PHPUnit\Framework\Constraint\Not', '\PHPUnit\Framework\Constraint\LogicalNot');
}
}
// @codingStandardsIgnoreEnd