This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Configuration;
use Codeception\Event\SuiteEvent;
use Codeception\Events;
use Codeception\Lib\Generator\Actions;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class AutoRebuild implements EventSubscriberInterface
{
use Shared\StaticEvents;
public static $events = [
Events::SUITE_INIT => 'updateActor'
];
public function updateActor(SuiteEvent $e)
{
$settings = $e->getSettings();
if (!$settings['actor']) {
codecept_debug('actor is empty');
return; // no actor
}
$modules = $e->getSuite()->getModules();
$actorActionsFile = Configuration::supportDir() . '_generated' . DIRECTORY_SEPARATOR
. $settings['actor'] . 'Actions.php';
if (!file_exists($actorActionsFile)) {
codecept_debug("Generating {$settings['actor']}Actions...");
$this->generateActorActions($actorActionsFile, $settings);
return;
}
// load actor class to see hash
$handle = @fopen($actorActionsFile, "r");
if ($handle and is_writable($actorActionsFile)) {
$line = @fgets($handle);
if (preg_match('~\[STAMP\] ([a-f0-9]*)~', $line, $matches)) {
$hash = $matches[1];
$currentHash = Actions::genHash($modules, $settings);
// regenerate actor class when hashes do not match
if ($hash != $currentHash) {
codecept_debug("Rebuilding {$settings['actor']}...");
@fclose($handle);
$this->generateActorActions($actorActionsFile, $settings);
return;
}
}
@fclose($handle);
}
}
protected function generateActorActions($actorActionsFile, $settings)
{
if (!file_exists(Configuration::supportDir() . '_generated')) {
@mkdir(Configuration::supportDir() . '_generated');
}
$actionsGenerator = new Actions($settings);
$generated = $actionsGenerator->produce();
@file_put_contents($actorActionsFile, $generated);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Event\SuiteEvent;
use Codeception\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class BeforeAfterTest implements EventSubscriberInterface
{
use Shared\StaticEvents;
public static $events = [
Events::SUITE_BEFORE => 'beforeClass',
Events::SUITE_AFTER => ['afterClass', 100]
];
protected $hooks = [];
protected $startedTests = [];
protected $unsuccessfulTests = [];
public function beforeClass(SuiteEvent $e)
{
foreach ($e->getSuite()->tests() as $test) {
/** @var $test \PHPUnit\Framework\Test * */
if ($test instanceof \PHPUnit\Framework\TestSuite\DataProvider) {
$potentialTestClass = strstr($test->getName(), '::', true);
$this->hooks[$potentialTestClass] = \PHPUnit\Util\Test::getHookMethods($potentialTestClass);
}
$testClass = get_class($test);
$this->hooks[$testClass] = \PHPUnit\Util\Test::getHookMethods($testClass);
}
$this->runHooks('beforeClass');
}
public function afterClass(SuiteEvent $e)
{
$this->runHooks('afterClass');
}
protected function runHooks($hookName)
{
foreach ($this->hooks as $className => $hook) {
foreach ($hook[$hookName] as $method) {
if (is_callable([$className, $method])) {
call_user_func([$className, $method]);
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Event\SuiteEvent;
use Codeception\Events;
use Codeception\Exception\ConfigurationException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class Bootstrap implements EventSubscriberInterface
{
use Shared\StaticEvents;
public static $events = [
Events::SUITE_INIT => 'loadBootstrap',
];
public function loadBootstrap(SuiteEvent $e)
{
$settings = $e->getSettings();
if (!isset($settings['bootstrap'])) {
return;
}
if (!$settings['bootstrap']) {
return;
}
$bootstrap = $settings['path'] . $settings['bootstrap'];
if (!is_file($bootstrap)) {
throw new ConfigurationException("Bootstrap file $bootstrap can't be loaded");
}
require_once $bootstrap;
}
}

View File

@@ -0,0 +1,646 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Event\FailEvent;
use Codeception\Event\PrintResultEvent;
use Codeception\Event\StepEvent;
use Codeception\Event\SuiteEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Lib\Console\Message;
use Codeception\Lib\Console\MessageFactory;
use Codeception\Lib\Console\Output;
use Codeception\Lib\Notification;
use Codeception\Step;
use Codeception\Step\Comment;
use Codeception\Suite;
use Codeception\Test\Descriptor;
use Codeception\Test\Interfaces\ScenarioDriven;
use Codeception\Util\Debug;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class Console implements EventSubscriberInterface
{
use Shared\StaticEvents;
/**
* @var string[]
*/
public static $events = [
Events::SUITE_BEFORE => 'beforeSuite',
Events::SUITE_AFTER => 'afterSuite',
Events::TEST_START => 'startTest',
Events::TEST_END => 'endTest',
Events::STEP_BEFORE => 'beforeStep',
Events::STEP_AFTER => 'afterStep',
Events::TEST_SUCCESS => 'testSuccess',
Events::TEST_FAIL => 'testFail',
Events::TEST_ERROR => 'testError',
Events::TEST_INCOMPLETE => 'testIncomplete',
Events::TEST_SKIPPED => 'testSkipped',
Events::TEST_WARNING => 'testWarning',
Events::TEST_FAIL_PRINT => 'printFail',
Events::RESULT_PRINT_AFTER => 'afterResult',
];
/**
* @var Step
*/
protected $metaStep;
/**
* @var Message
*/
protected $message = null;
protected $steps = true;
protected $debug = false;
protected $ansi = true;
protected $silent = false;
protected $lastTestFailed = false;
protected $printedTest = null;
protected $rawStackTrace = false;
protected $traceLength = 5;
protected $width;
/**
* @var OutputInterface
*/
protected $output;
protected $conditionalFails = [];
protected $failedStep = [];
protected $reports = [];
protected $namespace = '';
protected $chars = ['success' => '+', 'fail' => 'x', 'of' => ':'];
protected $options = [
'debug' => false,
'ansi' => false,
'steps' => true,
'verbosity' => 0,
'xml' => null,
'html' => null,
'tap' => null,
'json' => null,
];
/**
* @var MessageFactory
*/
protected $messageFactory;
public function __construct($options)
{
$this->prepareOptions($options);
$this->output = new Output($options);
$this->messageFactory = new MessageFactory($this->output);
if ($this->debug) {
Debug::setOutput($this->output);
}
$this->detectWidth();
if ($this->options['ansi'] && !$this->isWin()) {
$this->chars['success'] = '✔';
$this->chars['fail'] = '✖';
}
foreach (['html', 'xml', 'tap', 'json'] as $report) {
if (!$this->options[$report]) {
continue;
}
$path = $this->absolutePath($this->options[$report]);
$this->reports[] = sprintf(
"- <bold>%s</bold> report generated in <comment>file://%s</comment>",
strtoupper($report),
$path
);
}
}
// triggered for scenario based tests: cept, cest
public function beforeSuite(SuiteEvent $e)
{
$this->namespace = "";
$settings = $e->getSettings();
if (isset($settings['namespace'])) {
$this->namespace = $settings['namespace'];
}
$this->message("%s Tests (%d) ")
->with(ucfirst($e->getSuite()->getName()), $e->getSuite()->count())
->style('bold')
->width($this->width, '-')
->prepend("\n")
->writeln();
if ($e->getSuite() instanceof Suite) {
$message = $this->message(
implode(
', ',
array_map(
function ($module) {
return $module->_getName();
},
$e->getSuite()->getModules()
)
)
);
$message->style('info')
->prepend('Modules: ')
->writeln(OutputInterface::VERBOSITY_VERBOSE);
}
$this->message('')->width($this->width, '-')->writeln(OutputInterface::VERBOSITY_VERBOSE);
}
// triggered for all tests
public function startTest(TestEvent $e)
{
$this->conditionalFails = [];
$test = $e->getTest();
$this->printedTest = $test;
$this->message = null;
if (!$this->output->isInteractive() and !$this->isDetailed($test)) {
return;
}
$this->writeCurrentTest($test);
if ($this->isDetailed($test)) {
$this->output->writeln('');
$this->message(Descriptor::getTestSignature($test))
->style('info')
->prepend('Signature: ')
->writeln();
$this->message(codecept_relative_path(Descriptor::getTestFullName($test)))
->style('info')
->prepend('Test: ')
->writeln();
if ($this->steps) {
$this->message('Scenario --')->style('comment')->writeln();
$this->output->waitForDebugOutput = false;
}
}
}
public function afterStep(StepEvent $e)
{
$step = $e->getStep();
if (!$step->hasFailed()) {
return;
}
if ($step instanceof Step\ConditionalAssertion) {
$this->conditionalFails[] = $step;
return;
}
$this->failedStep[] = $step;
}
/**
* @param PrintResultEvent $event
*/
public function afterResult(PrintResultEvent $event)
{
$result = $event->getResult();
if ($result->skippedCount() + $result->notImplementedCount() > 0 and $this->options['verbosity'] < OutputInterface::VERBOSITY_VERBOSE) {
$this->output->writeln("run with `-v` to get more info about skipped or incomplete tests");
}
foreach ($this->reports as $message) {
$this->output->writeln($message);
}
}
private function absolutePath($path)
{
if ((strpos($path, '/') === 0) or (strpos($path, ':') === 1)) { // absolute path
return $path;
}
return codecept_output_dir() . $path;
}
public function testSuccess(TestEvent $e)
{
if ($this->isDetailed($e->getTest())) {
$this->message('PASSED')->center(' ')->style('ok')->append("\n")->writeln();
return;
}
$this->writelnFinishedTest($e, $this->message($this->chars['success'])->style('ok'));
}
public function endTest(TestEvent $e)
{
$this->metaStep = null;
$this->printedTest = null;
}
public function testWarning(TestEvent $e)
{
if ($this->isDetailed($e->getTest())) {
$this->message('WARNING')->center(' ')->style('pending')->append("\n")->writeln();
return;
}
$this->writelnFinishedTest($e, $this->message('W')->style('pending'));
}
public function testFail(FailEvent $e)
{
if ($this->isDetailed($e->getTest())) {
$this->message('FAIL')->center(' ')->style('fail')->append("\n")->writeln();
return;
}
$this->writelnFinishedTest($e, $this->message($this->chars['fail'])->style('fail'));
}
public function testError(FailEvent $e)
{
if ($this->isDetailed($e->getTest())) {
$this->message('ERROR')->center(' ')->style('fail')->append("\n")->writeln();
return;
}
$this->writelnFinishedTest($e, $this->message('E')->style('fail'));
}
public function testSkipped(FailEvent $e)
{
if ($this->isDetailed($e->getTest())) {
$msg = $e->getFail()->getMessage();
$this->message('SKIPPED')->append($msg ? ": $msg" : '')->center(' ')->style('pending')->writeln();
return;
}
$this->writelnFinishedTest($e, $this->message('S')->style('pending'));
}
public function testIncomplete(FailEvent $e)
{
if ($this->isDetailed($e->getTest())) {
$msg = $e->getFail()->getMessage();
$this->message('INCOMPLETE')->append($msg ? ": $msg" : '')->center(' ')->style('pending')->writeln();
return;
}
$this->writelnFinishedTest($e, $this->message('I')->style('pending'));
}
protected function isDetailed($test)
{
if ($test instanceof ScenarioDriven && $this->steps) {
return true;
}
return false;
}
public function beforeStep(StepEvent $e)
{
if (!$this->steps or !$e->getTest() instanceof ScenarioDriven) {
return;
}
$metaStep = $e->getStep()->getMetaStep();
if ($metaStep and $this->metaStep != $metaStep) {
$this->message(' ' . $metaStep->getPrefix())
->style('bold')
->append($metaStep->__toString())
->writeln();
}
$this->metaStep = $metaStep;
$this->printStep($e->getStep());
}
private function printStep(Step $step)
{
if ($step instanceof Comment and $step->__toString() == '') {
return; // don't print empty comments
}
$msg = $this->message(' ');
if ($this->metaStep) {
$msg->append(' ');
}
$msg->append($step->getPrefix());
$prefixLength = $msg->getLength();
if (!$this->metaStep) {
$msg->style('bold');
}
$maxLength = $this->width - $prefixLength;
$msg->append(OutputFormatter::escape($step->toString($maxLength)));
if ($this->metaStep) {
$msg->style('info');
}
$msg->writeln();
}
public function afterSuite(SuiteEvent $e)
{
$this->message()->width($this->width, '-')->writeln();
$messages = Notification::all();
foreach (array_count_values($messages) as $message => $count) {
if ($count > 1) {
$message = $count . 'x ' . $message;
}
$this->output->notification($message);
}
}
public function printFail(FailEvent $e)
{
$failedTest = $e->getTest();
$fail = $e->getFail();
$this->output->write($e->getCount() . ") ");
$this->writeCurrentTest($failedTest, false);
$this->output->writeln('');
$this->message("<error> Test </error> ")
->append(codecept_relative_path(Descriptor::getTestFullName($failedTest)))
->write();
if ($failedTest instanceof ScenarioDriven) {
$this->printScenarioFail($failedTest, $fail);
return;
}
$this->printException($fail);
$this->printExceptionTrace($fail);
}
public function printException($e, $cause = null)
{
if ($e instanceof \PHPUnit\Framework\SkippedTestError or $e instanceof \PHPUnit\Framework_IncompleteTestError) {
if ($e->getMessage()) {
$this->message(OutputFormatter::escape($e->getMessage()))->prepend("\n")->writeln();
}
return;
}
$class = $e instanceof \PHPUnit\Framework\ExceptionWrapper
? $e->getClassname()
: get_class($e);
if (strpos($class, 'Codeception\Exception') === 0) {
$class = substr($class, strlen('Codeception\Exception\\'));
}
$this->output->writeln('');
$message = $this->message(OutputFormatter::escape($e->getMessage()));
if ($e instanceof \PHPUnit\Framework\ExpectationFailedException) {
$comparisonFailure = $e->getComparisonFailure();
if ($comparisonFailure) {
$message->append($this->messageFactory->prepareComparisonFailureMessage($comparisonFailure));
}
}
$isFailure = $e instanceof \PHPUnit\Framework\AssertionFailedError
|| $class === 'PHPUnit\Framework\ExpectationFailedException'
|| $class === 'PHPUnit\Framework\AssertionFailedError';
if (!$isFailure) {
$message->prepend("[$class] ")->block('error');
}
if ($isFailure && $cause) {
$cause = OutputFormatter::escape(ucfirst($cause));
$message->prepend("<error> Step </error> $cause\n<error> Fail </error> ");
}
$message->writeln();
}
public function printScenarioFail(ScenarioDriven $failedTest, $fail)
{
if ($this->conditionalFails) {
$failedStep = (string) array_shift($this->conditionalFails);
} else {
$failedStep = (string) $failedTest->getScenario()->getMetaStep();
if ($failedStep === '') {
$failedStep = (string) array_shift($this->failedStep);
}
}
$this->printException($fail, $failedStep);
$this->printScenarioTrace($failedTest);
if ($this->output->getVerbosity() == OutputInterface::VERBOSITY_DEBUG) {
$this->printExceptionTrace($fail);
return;
}
if (!$fail instanceof \PHPUnit\Framework\AssertionFailedError) {
$this->printExceptionTrace($fail);
return;
}
}
public function printExceptionTrace($e)
{
static $limit = 10;
if ($e instanceof \PHPUnit\Framework\SkippedTestError or $e instanceof \PHPUnit\Framework_IncompleteTestError) {
return;
}
if ($this->rawStackTrace) {
$this->message(OutputFormatter::escape(\PHPUnit\Util\Filter::getFilteredStacktrace($e, true, false)))->writeln();
return;
}
$trace = \PHPUnit\Util\Filter::getFilteredStacktrace($e, false);
$i = 0;
foreach ($trace as $step) {
if ($i >= $limit) {
break;
}
$i++;
$message = $this->message($i)->prepend('#')->width(4);
if (!isset($step['file'])) {
foreach (['class', 'type', 'function'] as $info) {
if (!isset($step[$info])) {
continue;
}
$message->append($step[$info]);
}
$message->writeln();
continue;
}
$message->append($step['file'] . ':' . $step['line']);
$message->writeln();
}
$prev = $e->getPrevious();
if ($prev) {
$this->printExceptionTrace($prev);
}
}
/**
* @param $failedTest
*/
public function printScenarioTrace(ScenarioDriven $failedTest)
{
$trace = array_reverse($failedTest->getScenario()->getSteps());
$length = $stepNumber = count($trace);
if (!$length) {
return;
}
$this->message("\nScenario Steps:\n")->style('comment')->writeln();
foreach ($trace as $step) {
/**
* @var $step Step
*/
if (!$step->__toString()) {
continue;
}
$message = $this
->message($stepNumber)
->prepend(' ')
->width(strlen($length))
->append(". ");
$message->append(OutputFormatter::escape($step->getPhpCode($this->width - $message->getLength())));
if ($step->hasFailed()) {
$message->style('bold');
}
$line = $step->getLine();
if ($line and (!$step instanceof Comment)) {
$message->append(" at <info>$line</info>");
}
$stepNumber--;
$message->writeln();
if (($length - $stepNumber - 1) >= $this->traceLength) {
break;
}
}
$this->output->writeln("");
}
public function detectWidth()
{
$this->width = 60;
if (!$this->isWin()
&& (php_sapi_name() === "cli")
&& (getenv('TERM'))
&& (getenv('TERM') != 'unknown')
) {
// try to get terminal width from ENV variable (bash), see also https://github.com/Codeception/Codeception/issues/3788
if (getenv('COLUMNS')) {
$this->width = getenv('COLUMNS');
} else {
$this->width = (int) (`command -v tput >> /dev/null 2>&1 && tput cols`) - 2;
}
} elseif ($this->isWin() && (php_sapi_name() === "cli")) {
exec('mode con', $output);
if (isset($output[4])) {
preg_match('/^ +.* +(\d+)$/', $output[4], $matches);
if (!empty($matches[1])) {
$this->width = (int) $matches[1];
}
}
}
return $this->width;
}
private function isWin()
{
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
}
/**
* @param \PHPUnit\Framework\SelfDescribing $test
* @param bool $inProgress
*/
protected function writeCurrentTest(\PHPUnit\Framework\SelfDescribing $test, $inProgress = true)
{
$prefix = ($this->output->isInteractive() and !$this->isDetailed($test) and $inProgress) ? '- ' : '';
$testString = Descriptor::getTestAsString($test);
$testString = preg_replace('~^([^:]+):\s~', "<focus>$1{$this->chars['of']}</focus> ", $testString);
$this
->message($testString)
->prepend($prefix)
->write();
}
protected function writelnFinishedTest(TestEvent $event, Message $result)
{
$test = $event->getTest();
if ($this->isDetailed($test)) {
return;
}
if ($this->output->isInteractive()) {
$this->output->write("\x0D");
}
$result->append(' ')->write();
$this->writeCurrentTest($test, false);
$conditionalFailsMessage = "";
$numFails = count($this->conditionalFails);
if ($numFails == 1) {
$conditionalFailsMessage = "[F]";
} elseif ($numFails) {
$conditionalFailsMessage = "{$numFails}x[F]";
}
$conditionalFailsMessage = "<error>$conditionalFailsMessage</error> ";
$this->message($conditionalFailsMessage)->write();
$this->writeTimeInformation($event);
$this->output->writeln('');
}
/**
* @param $string
* @return Message
*/
private function message($string = '')
{
return $this->messageFactory->message($string);
}
/**
* @param TestEvent $event
*/
protected function writeTimeInformation(TestEvent $event)
{
$time = $event->getTime();
if ($time) {
$this
->message(number_format(round($time, 2), 2))
->prepend('(')
->append('s)')
->style('info')
->write();
}
}
/**
* @param $options
*/
private function prepareOptions($options)
{
$this->options = array_merge($this->options, $options);
$this->debug = $this->options['debug'] || $this->options['verbosity'] >= OutputInterface::VERBOSITY_VERY_VERBOSE;
$this->steps = $this->debug || $this->options['steps'];
$this->rawStackTrace = ($this->options['verbosity'] === OutputInterface::VERBOSITY_DEBUG);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Event\TestEvent;
use Codeception\Test\Descriptor;
use Codeception\Test\Interfaces\Dependent;
use Codeception\TestInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Codeception\Events;
class Dependencies implements EventSubscriberInterface
{
use Shared\StaticEvents;
static $events = [
Events::TEST_START => 'testStart',
Events::TEST_SUCCESS => 'testSuccess'
];
protected $successfulTests = [];
public function testStart(TestEvent $event)
{
$test = $event->getTest();
if (!$test instanceof Dependent) {
return;
}
$testSignatures = $test->fetchDependencies();
foreach ($testSignatures as $signature) {
if (!in_array($signature, $this->successfulTests)) {
$test->getMetadata()->setSkip("This test depends on $signature to pass");
return;
}
}
}
public function testSuccess(TestEvent $event)
{
$test = $event->getTest();
if (!$test instanceof TestInterface) {
return;
}
$this->successfulTests[] = Descriptor::getTestSignature($test);
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Event\SuiteEvent;
use Codeception\Events;
use Codeception\Lib\Notification;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ErrorHandler implements EventSubscriberInterface
{
use Shared\StaticEvents;
public static $events = [
Events::SUITE_BEFORE => 'handle',
Events::SUITE_AFTER => 'onFinish'
];
/**
* @var bool $stopped to keep shutdownHandler from possible looping.
*/
private $stopped = false;
/**
* @var bool $initialized to avoid double error handler substitution
*/
private $initialized = false;
private $deprecationsInstalled = false;
private $oldHandler;
private $suiteFinished = false;
/**
* @var int stores bitmask for errors
*/
private $errorLevel;
public function __construct()
{
$this->errorLevel = E_ALL & ~E_STRICT & ~E_DEPRECATED;
}
public function onFinish(SuiteEvent $e)
{
$this->suiteFinished = true;
}
public function handle(SuiteEvent $e)
{
$settings = $e->getSettings();
if ($settings['error_level']) {
$this->errorLevel = eval("return {$settings['error_level']};");
}
error_reporting($this->errorLevel);
if ($this->initialized) {
return;
}
// We must register shutdown function before deprecation error handler to restore previous error handler
// and silence DeprecationErrorHandler yelling about 'THE ERROR HANDLER HAS CHANGED!'
register_shutdown_function([$this, 'shutdownHandler']);
$this->registerDeprecationErrorHandler();
$this->oldHandler = set_error_handler([$this, 'errorHandler']);
$this->initialized = true;
}
public function errorHandler($errno, $errstr, $errfile, $errline, $context = array())
{
if (E_USER_DEPRECATED === $errno) {
$this->handleDeprecationError($errno, $errstr, $errfile, $errline, $context);
return;
}
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return false;
}
if (strpos($errstr, 'Cannot modify header information') !== false) {
return false;
}
throw new \PHPUnit\Framework\Exception($errstr, $errno);
}
public function shutdownHandler()
{
if ($this->deprecationsInstalled) {
restore_error_handler();
}
if ($this->stopped) {
return;
}
$this->stopped = true;
$error = error_get_last();
if (!$this->suiteFinished && (
$error === null || !in_array($error['type'], [E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR])
)) {
throw new \RuntimeException('Command Did Not Finish Properly');
} elseif (!is_array($error)) {
return;
}
if (error_reporting() === 0) {
return;
}
// not fatal
if ($error['type'] > 1) {
return;
}
echo "\n\n\nFATAL ERROR. TESTS NOT FINISHED.\n";
echo sprintf("%s \nin %s:%d\n", $error['message'], $error['file'], $error['line']);
}
private function registerDeprecationErrorHandler()
{
if (class_exists('\Symfony\Bridge\PhpUnit\DeprecationErrorHandler') && 'disabled' !== getenv('SYMFONY_DEPRECATIONS_HELPER')) {
// DeprecationErrorHandler only will be installed if array('PHPUnit\Util\ErrorHandler', 'handleError')
// is installed or no other error handlers are installed.
// So we will remove Symfony\Component\Debug\ErrorHandler if it's installed.
$old = set_error_handler('var_dump');
restore_error_handler();
if ($old
&& is_array($old)
&& count($old) > 0
&& is_object($old[0])
&& get_class($old[0]) === 'Symfony\Component\Debug\ErrorHandler'
) {
restore_error_handler();
}
$this->deprecationsInstalled = true;
\Symfony\Bridge\PhpUnit\DeprecationErrorHandler::register(getenv('SYMFONY_DEPRECATIONS_HELPER'));
}
}
private function handleDeprecationError($type, $message, $file, $line, $context)
{
if (!($this->errorLevel & $type)) {
return;
}
if ($this->deprecationsInstalled && $this->oldHandler) {
call_user_func($this->oldHandler, $type, $message, $file, $line, $context);
return;
}
Notification::deprecate("$message", "$file:$line");
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Configuration;
use Codeception\Event\SuiteEvent;
use Codeception\Events;
use Codeception\Exception\ConfigurationException;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ExtensionLoader implements EventSubscriberInterface
{
use Shared\StaticEvents;
public static $events = [
Events::MODULE_INIT => 'registerSuiteExtensions',
Events::SUITE_AFTER => 'stopSuiteExtensions'
];
protected $config;
protected $options = [];
protected $globalExtensions = [];
protected $suiteExtensions = [];
/**
* @var EventDispatcher
*/
protected $dispatcher;
public function __construct(EventDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
$this->config = Configuration::config();
}
public function bootGlobalExtensions($options)
{
$this->options = $options;
$this->globalExtensions = $this->bootExtensions($this->config);
}
public function registerGlobalExtensions()
{
foreach ($this->globalExtensions as $extension) {
$this->dispatcher->addSubscriber($extension);
}
}
public function registerSuiteExtensions(SuiteEvent $e)
{
$suiteConfig = $e->getSettings();
$extensions = $this->bootExtensions($suiteConfig);
$this->suiteExtensions = [];
foreach ($extensions as $extension) {
$extensionClass = get_class($extension);
if (isset($this->globalExtensions[$extensionClass])) {
continue; // already globally enabled
}
$this->dispatcher->addSubscriber($extension);
$this->suiteExtensions[$extensionClass] = $extension;
}
}
public function stopSuiteExtensions()
{
foreach ($this->suiteExtensions as $extension) {
$this->dispatcher->removeSubscriber($extension);
}
$this->suiteExtensions = [];
}
protected function bootExtensions($config)
{
$extensions = [];
foreach ($config['extensions']['enabled'] as $extensionClass) {
if (is_array($extensionClass)) {
$extensionClass = key($extensionClass);
}
if (!class_exists($extensionClass)) {
throw new ConfigurationException(
"Class `$extensionClass` is not defined. Autoload it or include into "
. "'_bootstrap.php' file of 'tests' directory"
);
}
$extensionConfig = $this->getExtensionConfig($extensionClass, $config);
$extension = new $extensionClass($extensionConfig, $this->options);
if (!$extension instanceof EventSubscriberInterface) {
throw new ConfigurationException(
"Class $extensionClass is not an EventListener. Please create it as Extension or GroupObject."
);
}
$extensions[get_class($extension)] = $extension;
}
return $extensions;
}
private function getExtensionConfig($extension, $config)
{
$extensionConfig = isset($config['extensions']['config'][$extension])
? $config['extensions']['config'][$extension]
: [];
if (!isset($config['extensions']['enabled'])) {
return $extensionConfig;
}
if (!is_array($config['extensions']['enabled'])) {
return $extensionConfig;
}
foreach ($config['extensions']['enabled'] as $enabledExtensionsConfig) {
if (!is_array($enabledExtensionsConfig)) {
continue;
}
$enabledExtension = key($enabledExtensionsConfig);
if ($enabledExtension === $extension) {
return Configuration::mergeConfigs(reset($enabledExtensionsConfig), $extensionConfig);
}
}
return $extensionConfig;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Event\SuiteEvent;
use Codeception\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class FailFast implements EventSubscriberInterface
{
use Shared\StaticEvents;
public static $events = [
Events::SUITE_BEFORE => 'stopOnFail',
];
public function stopOnFail(SuiteEvent $e)
{
$e->getResult()->stopOnError(true);
$e->getResult()->stopOnFailure(true);
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare (ticks = 1);
namespace Codeception\Subscriber;
use Codeception\Event\SuiteEvent;
use Codeception\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class GracefulTermination implements EventSubscriberInterface
{
const SIGNAL_FUNC = 'pcntl_signal';
const ASYNC_SIGNAL_HANDLING_FUNC = 'pcntl_async_signals';
/**
* @var SuiteEvent
*/
protected $suiteEvent;
public function handleSuite(SuiteEvent $event)
{
if (PHP_MAJOR_VERSION === 7 && PHP_MINOR_VERSION === 0) {
// skip for PHP 7.0: https://github.com/Codeception/Codeception/issues/3607
return;
}
if (function_exists(self::ASYNC_SIGNAL_HANDLING_FUNC)) {
pcntl_async_signals(true);
}
if (function_exists(self::SIGNAL_FUNC)) {
pcntl_signal(SIGTERM, [$this, 'terminate']);
pcntl_signal(SIGINT, [$this, 'terminate']);
}
$this->suiteEvent = $event;
}
public function terminate()
{
if ($this->suiteEvent) {
$this->suiteEvent->getResult()->stopOnError(true);
$this->suiteEvent->getResult()->stopOnFailure(true);
}
throw new \RuntimeException(
"\n\n---------------------------\nTESTS EXECUTION TERMINATED\n---------------------------\n"
);
}
public static function getSubscribedEvents()
{
if (!function_exists(self::SIGNAL_FUNC)) {
return [];
}
return [Events::SUITE_BEFORE => 'handleSuite'];
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Event\FailEvent;
use Codeception\Event\StepEvent;
use Codeception\Event\SuiteEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Suite;
use Codeception\TestInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class Module implements EventSubscriberInterface
{
use Shared\StaticEvents;
public static $events = [
Events::TEST_BEFORE => 'before',
Events::TEST_AFTER => 'after',
Events::STEP_BEFORE => 'beforeStep',
Events::STEP_AFTER => 'afterStep',
Events::TEST_FAIL => 'failed',
Events::TEST_ERROR => 'failed',
Events::SUITE_BEFORE => 'beforeSuite',
Events::SUITE_AFTER => 'afterSuite'
];
protected $modules = [];
public function beforeSuite(SuiteEvent $e)
{
$suite = $e->getSuite();
if (!$suite instanceof Suite) {
return;
}
$this->modules = $suite->getModules();
foreach ($this->modules as $module) {
$module->_beforeSuite($e->getSettings());
}
}
public function afterSuite()
{
foreach ($this->modules as $module) {
$module->_afterSuite();
}
}
public function before(TestEvent $event)
{
if (!$event->getTest() instanceof TestInterface) {
return;
}
foreach ($this->modules as $module) {
$module->_before($event->getTest());
}
}
public function after(TestEvent $e)
{
if (!$e->getTest() instanceof TestInterface) {
return;
}
foreach ($this->modules as $module) {
$module->_after($e->getTest());
$module->_resetConfig();
}
}
public function failed(FailEvent $e)
{
if (!$e->getTest() instanceof TestInterface) {
return;
}
foreach ($this->modules as $module) {
$module->_failed($e->getTest(), $e->getFail());
}
}
public function beforeStep(StepEvent $e)
{
foreach ($this->modules as $module) {
$module->_beforeStep($e->getStep(), $e->getTest());
}
}
public function afterStep(StepEvent $e)
{
foreach ($this->modules as $module) {
$module->_afterStep($e->getStep(), $e->getTest());
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Codeception\Subscriber;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Lib\Di;
use Codeception\Test\Cest;
use Codeception\Test\Unit;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class PrepareTest implements EventSubscriberInterface
{
use Shared\StaticEvents;
public static $events = [
Events::TEST_BEFORE => 'prepare',
];
protected $modules = [];
public function prepare(TestEvent $event)
{
$test = $event->getTest();
/** @var $di Di **/
$prepareMethods = $test->getMetadata()->getParam('prepare');
if (!$prepareMethods) {
return;
}
$di = $test->getMetadata()->getService('di');
foreach ($prepareMethods as $method) {
/** @var $module \Codeception\Module **/
if ($test instanceof Cest) {
$di->injectDependencies($test->getTestClass(), $method);
}
if ($test instanceof Unit) {
$di->injectDependencies($test, $method);
}
}
}
}

View File

@@ -0,0 +1,5 @@
# Event Listeners (Subscribers)
Where this is possible Codeception uses the Observer pattern to separate different parts of framework and make them act independently.
Events are defined in `Codeception\Event`). New features can be added seamlessly when they are created in Subscribers.

View File

@@ -0,0 +1,10 @@
<?php
namespace Codeception\Subscriber\Shared;
trait StaticEvents
{
public static function getSubscribedEvents()
{
return static::$events;
}
}