-
-
Save Naktibalda/16395382be61a27e69357cf3d0cf1e7d to your computer and use it in GitHub Desktop.
Codeception.php from PhpStorm plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
if (!isset($_SERVER['IDE_CODECEPTION_EXE'])) { | |
fwrite(STDERR, "The value of Codeception executable is not specified" . PHP_EOL); | |
exit(1); | |
} | |
$exe = realpath($_SERVER['IDE_CODECEPTION_EXE']); | |
if (!file_exists($exe)) { | |
fwrite(STDERR, "The value of Codeception executable is specified, but file doesn't exist '$exe'" . PHP_EOL); | |
exit(1); | |
} | |
if (Phar::isValidPharFilename(basename($exe), true)) { | |
require_once 'phar://' . $exe . '/autoload.php'; | |
} | |
else { | |
require_once dirname($exe) .'/autoload.php'; | |
} | |
class PhpStorm_Codeception_ReportPrinter extends PHPUnit_TextUI_ResultPrinter | |
{ | |
protected $testStatus = \PHPUnit_Runner_BaseTestRunner::STATUS_PASSED; | |
protected $failures = []; | |
/** | |
* @var bool | |
*/ | |
private $isSummaryTestCountPrinted = false; | |
/** | |
* @var string | |
*/ | |
private $startedTestName; | |
/** | |
* @var string | |
*/ | |
private $flowId; | |
/** | |
* @param string $progress | |
*/ | |
protected function writeProgress($progress) | |
{ | |
} | |
/** | |
* @param PHPUnit_Framework_TestResult $result | |
*/ | |
public function printResult(PHPUnit_Framework_TestResult $result) | |
{ | |
$this->printHeader(); | |
$this->printFooter($result); | |
} | |
/** | |
* 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->addFail(\PHPUnit_Runner_BaseTestRunner::STATUS_ERROR, $test, $e); | |
} | |
/** | |
* A warning occurred. | |
* | |
* @param PHPUnit_Framework_Test $test | |
* @param PHPUnit_Framework_Warning $e | |
* @param float $time | |
* | |
* @since Method available since Release 5.1.0 | |
*/ | |
public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time) | |
{ | |
$this->addFail(\PHPUnit_Runner_BaseTestRunner::STATUS_ERROR, $test, $e); | |
} | |
/** | |
* 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) | |
{ | |
$parameters = []; | |
if ($e instanceof PHPUnit_Framework_ExpectationFailedException) { | |
$comparisonFailure = $e->getComparisonFailure(); | |
if ($comparisonFailure instanceof \SebastianBergmann\Comparator\ComparisonFailure) { | |
$expectedString = $comparisonFailure->getExpectedAsString(); | |
if (is_null($expectedString) || empty($expectedString)) { | |
$expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); | |
} | |
$actualString = $comparisonFailure->getActualAsString(); | |
if (is_null($actualString) || empty($actualString)) { | |
$actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); | |
} | |
if (!is_null($actualString) && !is_null($expectedString)) { | |
$parameters['type'] = 'comparisonFailure'; | |
$parameters['actual'] = $actualString; | |
$parameters['expected'] = $expectedString; | |
} | |
} | |
} | |
$this->addFail(\PHPUnit_Runner_BaseTestRunner::STATUS_ERROR, $test, $e, $parameters); | |
} | |
/** | |
* Incomplete test. | |
* | |
* @param PHPUnit_Framework_Test $test | |
* @param Exception $e | |
* @param float $time | |
*/ | |
public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) | |
{ | |
$this->addIgnoredTest($test, $e); | |
} | |
/** | |
* Risky test. | |
* | |
* @param PHPUnit_Framework_Test $test | |
* @param Exception $e | |
* @param float $time | |
*/ | |
public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) | |
{ | |
$this->addError($test, $e, $time); | |
} | |
/** | |
* Skipped test. | |
* | |
* @param PHPUnit_Framework_Test $test | |
* @param Exception $e | |
* @param float $time | |
*/ | |
public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) | |
{ | |
$testName = \Codeception\Test\Descriptor::getTestAsString($test); | |
if ($this->startedTestName != $testName) { | |
$this->startTest($test); | |
$this->printEvent( | |
'testIgnored', | |
[ | |
'name' => $testName, | |
'message' => self::getMessage($e), | |
'details' => self::getDetails($e), | |
] | |
); | |
$this->endTest($test, $time); | |
} else { | |
$this->addIgnoredTest($test, $e); | |
} | |
} | |
public function addIgnoredTest(PHPUnit_Framework_Test $test, Exception $e) { | |
$this->addFail(\PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED, $test, $e); | |
} | |
private function addFail($status, PHPUnit_Framework_Test $test, $e, $parameters = []) { | |
$key = \Codeception\Test\Descriptor::getTestSignature($test); | |
$this->testStatus = $status; | |
$parameters['message'] = self::getMessage($e); | |
$parameters['details'] = self::getDetails($e); | |
$this->failures[$key][] = $parameters; | |
} | |
/** | |
* A testsuite started. | |
* | |
* @param PHPUnit_Framework_TestSuite $suite | |
*/ | |
public function startTestSuite(PHPUnit_Framework_TestSuite $suite) | |
{ | |
if (stripos(ini_get('disable_functions'), 'getmypid') === false) { | |
$this->flowId = getmypid(); | |
} else { | |
$this->flowId = false; | |
} | |
if (!$this->isSummaryTestCountPrinted) { | |
$this->isSummaryTestCountPrinted = true; | |
$this->printEvent( | |
'testCount', | |
['count' => count($suite)] | |
); | |
} | |
$suiteName = $suite->getName(); | |
if (empty($suiteName)) { | |
return; | |
} | |
//TODO: configure 'locationHint' to navigate to 'unit', 'acceptance', 'functional' test suite | |
//TODO: configure 'locationHint' to navigate to DataProvider tests for Codeception earlier 2.2.6 | |
$parameters = ['name' => $suiteName]; | |
$this->printEvent('testSuiteStarted', $parameters); | |
} | |
/** | |
* A testsuite ended. | |
* | |
* @param PHPUnit_Framework_TestSuite $suite | |
*/ | |
public function endTestSuite(PHPUnit_Framework_TestSuite $suite) | |
{ | |
$suiteName = $suite->getName(); | |
if (empty($suiteName)) { | |
return; | |
} | |
$parameters = ['name' => $suiteName]; | |
$this->printEvent('testSuiteFinished', $parameters); | |
} | |
/** | |
* A test started. | |
* | |
* @param PHPUnit_Framework_Test $test | |
*/ | |
public function startTest(PHPUnit_Framework_Test $test) | |
{ | |
$testName = \Codeception\Test\Descriptor::getTestAsString($test); | |
$this->startedTestName = $testName; | |
$location = "php_qn://" . \Codeception\Test\Descriptor::getTestFullName($test); | |
$gherkin = self::getGherkinTestLocation($test); | |
if ($gherkin != null) { | |
$location = $gherkin; | |
} | |
$params = ['name' => $testName, 'locationHint' => $location]; | |
if ($test instanceof \Codeception\Test\Interfaces\ScenarioDriven) { | |
$this->printEvent('testSuiteStarted', $params); | |
} | |
else { | |
$this->printEvent('testStarted', $params); | |
} | |
} | |
/** | |
* A test ended. | |
* | |
* @param PHPUnit_Framework_Test $test | |
* @param float $time | |
*/ | |
public function endTest(PHPUnit_Framework_Test $test, $time) | |
{ | |
$result = null; | |
switch ($this->testStatus) { | |
case \PHPUnit_Runner_BaseTestRunner::STATUS_ERROR: | |
case \PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE: | |
$result = 'testFailed'; | |
break; | |
case \PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED: | |
$result = 'testIgnored'; | |
break; | |
} | |
$name = \Codeception\Test\Descriptor::getTestAsString($test); | |
if ($this->startedTestName != $name && mb_strtolower($this->startedTestName) == mb_strtolower($name)) { | |
$name = $this->startedTestName; | |
} | |
$gherkin = self::getGherkinTestLocation($test); | |
$duration = (int)(round($time, 2) * 1000); | |
if ($test instanceof \Codeception\Test\Interfaces\ScenarioDriven) { | |
$steps = $test->getScenario()->getSteps(); | |
$len = sizeof($steps); | |
$printed = 0; | |
for ($i = 0; $i < $len; $i++) { | |
$step = $steps[$i]; | |
if ($step->getAction() == null && $step->getMetaStep()) { | |
$step = $step->getMetaStep(); | |
} | |
if ($step instanceof \Codeception\Step\Comment) { | |
// TODO: render comments in grey color? | |
// comments are not shown because at the moment it's hard to distinguish them from real tests. | |
// e.g. comment steps show descriptions from *.feature tests. | |
continue; | |
} | |
$printed++; | |
$testName = sprintf('%s %s %s', | |
ucfirst($step->getPrefix()), | |
$step->getHumanizedActionWithoutArguments(), | |
$step->getHumanizedArguments() | |
); | |
$location = $gherkin != null ? $gherkin : $step->getLine(); | |
$this->printEvent('testStarted', | |
[ | |
'name' => $testName, | |
'locationHint' => "file://$location" | |
]); | |
$params = ['name' => $testName]; | |
if ($i == $len - 1) { | |
parent::endTest($test, $time); | |
$this->printError($test, $result, $testName); | |
$params['duration'] = $duration; | |
} | |
$this->printEvent('testFinished', $params); | |
} | |
if ($printed == 0 && $result != null) { | |
$this->printEvent('testStarted', ['name' => $name]); | |
parent::endTest($test, $time); | |
$this->printError($test, $result, $name); | |
$this->printEvent('testFinished', [ | |
'name' => $name, | |
'duration' => $duration | |
]); | |
} | |
$this->printEvent('testSuiteFinished', ['name' => $name]); | |
} | |
else { | |
parent::endTest($test, $time); | |
$this->printError($test, $result, \Codeception\Test\Descriptor::getTestAsString($test)); | |
$this->printEvent( | |
'testFinished', | |
[ | |
'name' => \Codeception\Test\Descriptor::getTestAsString($test), | |
'duration' => $duration | |
] | |
); | |
} | |
} | |
private function printError(PHPUnit_Framework_Test $test, $result, $name) { | |
if ($result != null) { | |
$this->testStatus = \PHPUnit_Runner_BaseTestRunner::STATUS_PASSED; | |
$key = \Codeception\Test\Descriptor::getTestSignature($test); | |
if (isset($this->failures[$key])) { | |
$failures = $this->failures[$key]; | |
//TODO: check if it's possible to have sizeof($params) > 1 | |
assert(sizeof($failures) == 1); | |
$params = $failures[0]; | |
$params['name'] = $name; | |
if ($result === 'testFailed') { | |
$params['message'] = "Step $name failed\n{$params['message']}"; | |
} | |
$this->printEvent($result, $params); | |
unset($this->failures[$key]); | |
} | |
} | |
} | |
/** | |
* @param string $eventName | |
* @param array $params | |
*/ | |
private function printEvent($eventName, $params = []) | |
{ | |
$this->write("\n##teamcity[$eventName"); | |
if ($this->flowId) { | |
$params['flowId'] = $this->flowId; | |
} | |
foreach ($params as $key => $value) { | |
$escapedValue = self::escapeValue($value); | |
$this->write(" $key='$escapedValue'"); | |
} | |
$this->write("]\n"); | |
} | |
private static function getGherkinTestLocation(PHPUnit_Framework_Test $test) { | |
if ($test instanceof \Codeception\Test\Gherkin) { | |
$feature = $test->getFeatureNode(); | |
$scenario = $test->getScenarioNode(); | |
if ($feature != null && $scenario != null) { | |
return "file://" . $test->getFeatureNode()->getFile() . ":" . $test->getScenarioNode()->getLine(); | |
} | |
} | |
return null; | |
} | |
/** | |
* @param Exception $e | |
* | |
* @return string | |
*/ | |
private static function getMessage(Exception $e) | |
{ | |
$message = ''; | |
if (!$e instanceof PHPUnit_Framework_Exception) { | |
if (strlen(get_class($e)) != 0) { | |
$message = $message . get_class($e); | |
} | |
if (strlen($message) != 0 && strlen($e->getMessage()) != 0) { | |
$message = $message . ' : '; | |
} | |
} | |
return $message . $e->getMessage(); | |
} | |
/** | |
* @param Exception $e | |
* | |
* @return string | |
*/ | |
private static function getDetails(Exception $e) | |
{ | |
$stackTrace = PHPUnit_Util_Filter::getFilteredStacktrace($e); | |
$previous = $e->getPrevious(); | |
while ($previous) { | |
$stackTrace .= "\nCaused by\n" . | |
PHPUnit_Framework_TestFailure::exceptionToString($previous) . "\n" . | |
PHPUnit_Util_Filter::getFilteredStacktrace($previous); | |
$previous = $previous->getPrevious(); | |
} | |
return ' ' . str_replace("\n", "\n ", $stackTrace); | |
} | |
/** | |
* @param mixed $value | |
* | |
* @return string | |
*/ | |
private static function getPrimitiveValueAsString($value) | |
{ | |
if (is_null($value)) { | |
return 'null'; | |
} elseif (is_bool($value)) { | |
return $value == true ? 'true' : 'false'; | |
} elseif (is_scalar($value)) { | |
return print_r($value, true); | |
} | |
return; | |
} | |
/** | |
* @param $text | |
* | |
* @return string | |
*/ | |
private static function escapeValue($text) | |
{ | |
$text = str_replace('|', '||', $text); | |
$text = str_replace("'", "|'", $text); | |
$text = str_replace("\n", '|n', $text); | |
$text = str_replace("\r", '|r', $text); | |
$text = str_replace(']', '|]', $text); | |
$text = str_replace('[', '|[', $text); | |
return $text; | |
} | |
/** | |
* @param string $className | |
* | |
* @return string | |
*/ | |
private static function getFileName($className) | |
{ | |
$reflectionClass = new ReflectionClass($className); | |
$fileName = $reflectionClass->getFileName(); | |
return $fileName; | |
} | |
} | |
$app = new \Codeception\Application('Codeception', \Codeception\Codecept::VERSION); | |
if (version_compare(\Codeception\Codecept::VERSION, "2.2.6") >= 0) { | |
$app->add(new \Codeception\Command\Run('run')); | |
$app->run(); | |
} | |
else { | |
class PhpStorm_Codeception_Command_Run extends \Codeception\Command\Run { | |
public function execute(\Symfony\Component\Console\Input\InputInterface $input, | |
\Symfony\Component\Console\Output\OutputInterface $output) | |
{ | |
$this->ensureCurlIsAvailable(); | |
$this->options = $input->getOptions(); | |
$this->output = $output; | |
$config = \Codeception\Configuration::config($this->options['config']); | |
if (!$this->options['colors']) { | |
$this->options['colors'] = $config['settings']['colors']; | |
} | |
if (!$this->options['silent']) { | |
$this->output->writeln( | |
\Codeception\Codecept::versionString() . "\nPowered by " . \PHPUnit_Runner_Version::getVersionString() | |
); | |
} | |
if ($this->options['debug']) { | |
$this->output->setVerbosity(\Symfony\Component\Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE); | |
} | |
$userOptions = array_intersect_key($this->options, array_flip($this->passedOptionKeys($input))); | |
$userOptions = array_merge( | |
$userOptions, | |
$this->booleanOptions($input, ['xml', 'html', 'json', 'tap', 'coverage', 'coverage-xml', 'coverage-html']) | |
); | |
$userOptions['verbosity'] = $this->output->getVerbosity(); | |
$userOptions['interactive'] = !$input->hasParameterOption(['--no-interaction', '-n']); | |
$userOptions['ansi'] = (!$input->hasParameterOption('--no-ansi') xor $input->hasParameterOption('ansi')); | |
if ($this->options['no-colors'] || !$userOptions['ansi']) { | |
$userOptions['colors'] = false; | |
} | |
if ($this->options['group']) { | |
$userOptions['groups'] = $this->options['group']; | |
} | |
if ($this->options['skip-group']) { | |
$userOptions['excludeGroups'] = $this->options['skip-group']; | |
} | |
if ($this->options['report']) { | |
$userOptions['silent'] = true; | |
} | |
if ($this->options['coverage-xml'] or $this->options['coverage-html'] or $this->options['coverage-text']) { | |
$this->options['coverage'] = true; | |
} | |
if (!$userOptions['ansi'] && $input->getOption('colors')) { | |
$userOptions['colors'] = true; // turn on colors even in non-ansi mode if strictly passed | |
} | |
$suite = $input->getArgument('suite'); | |
$test = $input->getArgument('test'); | |
if (! \Codeception\Configuration::isEmpty() && ! $test && strpos($suite, $config['paths']['tests']) === 0) { | |
list(, $suite, $test) = $this->matchTestFromFilename($suite, $config['paths']['tests']); | |
} | |
if ($this->options['group']) { | |
$this->output->writeln(sprintf("[Groups] <info>%s</info> ", implode(', ', $this->options['group']))); | |
} | |
if ($input->getArgument('test')) { | |
$this->options['steps'] = true; | |
} | |
if ($test) { | |
$filter = $this->matchFilteredTestName($test); | |
$userOptions['filter'] = $filter; | |
} | |
$this->codecept = new PhpStorm_Codeception_Codecept($userOptions); | |
if ($suite and $test) { | |
$this->codecept->run($suite, $test); | |
} | |
if (!$test) { | |
$suites = $suite ? explode(',', $suite) : \Codeception\Configuration::suites(); | |
$this->executed = $this->runSuites($suites, $this->options['skip']); | |
if (!empty($config['include']) and !$suite) { | |
$current_dir = \Codeception\Configuration::projectDir(); | |
$suites += $config['include']; | |
$this->runIncludedSuites($config['include'], $current_dir); | |
} | |
if ($this->executed === 0) { | |
throw new \RuntimeException( | |
sprintf("Suite '%s' could not be found", implode(', ', $suites)) | |
); | |
} | |
} | |
$this->codecept->printResult(); | |
if (!$input->getOption('no-exit')) { | |
if (!$this->codecept->getResult()->wasSuccessful()) { | |
exit(1); | |
} | |
} | |
} | |
private function matchFilteredTestName(&$path) | |
{ | |
if (version_compare(\Codeception\Codecept::VERSION, "2.2.5") >= 0) { | |
$test_parts = explode(':', $path, 2); | |
if (count($test_parts) > 1) { | |
list($path, $filter) = $test_parts; | |
// use carat to signify start of string like in normal regex | |
// phpunit --filter matches against the fully qualified method name, so tests actually begin with : | |
$carat_pos = strpos($filter, '^'); | |
if ($carat_pos !== false) { | |
$filter = substr_replace($filter, ':', $carat_pos, 1); | |
} | |
return $filter; | |
} | |
return null; | |
} | |
else { | |
$test_parts = explode(':', $path); | |
if (count($test_parts) > 1) { | |
list($path, $filter) = $test_parts; | |
return $filter; | |
} | |
return null; | |
} | |
} | |
private function ensureCurlIsAvailable() | |
{ | |
if (!extension_loaded('curl')) { | |
throw new \Exception( | |
"Codeception requires CURL extension installed to make tests run\n" | |
. "If you are not sure, how to install CURL, please refer to StackOverflow\n\n" | |
. "Notice: PHP for Apache/Nginx and CLI can have different php.ini files.\n" | |
. "Please make sure that your PHP you run from console has CURL enabled." | |
); | |
} | |
} | |
} | |
class PhpStorm_Codeception_Codecept extends \Codeception\Codecept { | |
public function __construct($options = []) | |
{ | |
parent::__construct($options); | |
$printer = new PhpStorm_Codeception_ReportPrinter(); | |
$this->runner = new \Codeception\PHPUnit\Runner(); | |
$this->runner->setPrinter($printer); | |
} | |
} | |
$app->add(new PhpStorm_Codeception_Command_Run('run')); | |
$app->run(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment