From 96138c30e91c225a77a216b357d9bd412383f49a Mon Sep 17 00:00:00 2001 From: Davert Date: Mon, 25 Apr 2016 04:53:16 +0300 Subject: [PATCH] added dry-run command --- CHANGELOG.md | 1 + codecept | 1 + src/Codeception/Command/DryRun.php | 136 ++++++++++++++++++ src/Codeception/Command/Run.php | 9 ++ src/Codeception/Command/Shared/Style.php | 2 + src/Codeception/Lib/Console/Output.php | 4 +- src/Codeception/Lib/ModuleContainer.php | 5 + src/Codeception/PHPUnit/ResultPrinter.php | 19 ++- src/Codeception/Subscriber/Console.php | 25 ++-- src/Codeception/SuiteManager.php | 1 - src/Codeception/Test/Cest.php | 1 + src/Codeception/Test/Gherkin.php | 3 +- tests/cli/DryRunCest.php | 26 ++++ .../_helpers/_generated/TestGuyActions.php | 2 +- .../_helpers/_generated/TestGuyActions.php | 2 +- .../_helpers/_generated/TestGuyActions.php | 2 +- .../_support/_generated/UnitTesterActions.php | 2 +- .../_support/_generated/UnitTesterActions.php | 2 +- .../_support/_generated/UnitTesterActions.php | 2 +- .../_generated/DummyTesterActions.php | 2 +- .../support/_generated/AngularGuyActions.php | 11 +- tests/support/_generated/CliGuyActions.php | 2 +- tests/support/_generated/CoverGuyActions.php | 2 +- 23 files changed, 229 insertions(+), 33 deletions(-) create mode 100644 src/Codeception/Command/DryRun.php create mode 100644 tests/cli/DryRunCest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e68c95d0..6e52209454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * **Params** loading from yml, env files or environment. [Announcement](http://codeception.com/03-05-2016/codeception-2.2.-upcoming-features.html#params) * **Test dependencies** with `@depends` annotation. [Announcement](http://codeception.com/03-05-2016/codeception-2.2.-upcoming-features.html#test-dependencies) * **Custom Commands** inject your own commands as as simple as extension. [Announcement](http://codeception.com/03-10-2016/even-more-features-of-codeception.html#custom-commands) +* `codecept dry-run` command added to show scenario steps without executing them. * [WebDriver] allows getting current browser and capabilities in test. [Announcement](http://codeception.com/03-10-2016/even-more-features-of-codeception.html#Getting-current-browser-and-capabilities-in-tests) * [AngularJS] module added. Extends WebDriver module for AngularJS testing. [Announcement](http://codeception.com/03-10-2016/even-more-features-of-codeception.html#angularjs) * [DataFactory] module added. Performs data generation using FactoryMuffin library [Announcement](http://codeception.com/03-10-2016/even-more-features-of-codeception.html#datafactory) diff --git a/codecept b/codecept index e2b9afa568..990a8656d9 100755 --- a/codecept +++ b/codecept @@ -28,5 +28,6 @@ $app->add(new Codeception\Command\GenerateEnvironment('generate:environment')); $app->add(new Codeception\Command\GenerateFeature('generate:feature')); $app->add(new Codeception\Command\GherkinSnippets('gherkin:snippets')); $app->add(new Codeception\Command\GherkinSteps('gherkin:steps')); +$app->add(new Codeception\Command\DryRun('dry-run')); $app->registerCustomCommands(); $app->run(); diff --git a/src/Codeception/Command/DryRun.php b/src/Codeception/Command/DryRun.php new file mode 100644 index 0000000000..72ac7dd0e5 --- /dev/null +++ b/src/Codeception/Command/DryRun.php @@ -0,0 +1,136 @@ +setDefinition( + [ + new InputArgument('suite', InputArgument::REQUIRED, 'suite to scan for feature files'), + new InputArgument('test', InputArgument::OPTIONAL, 'tests to be loaded'), + new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config'), + ] + ); + parent::configure(); + } + + public function getDescription() + { + return 'Prints step-by-step scenario-driven test or a feature'; + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $this->addStyles($output); + $suite = $input->getArgument('suite'); + $test = $input->getArgument('test'); + + $config = Configuration::config( $input->getOption('config')); + if (! Configuration::isEmpty() && ! $test && strpos($suite, $config['paths']['tests']) === 0) { + list(, $suite, $test) = $this->matchTestFromFilename($suite, $config['paths']['tests']); + } + $settings = $this->getSuiteConfig($suite, $input->getOption('config')); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new ConsolePrinter([ + 'colors' => $input->getOption('ansi'), + 'steps' => true, + 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, + ])); + $dispatcher->addSubscriber(new BootstrapLoader()); + + $suiteManager = new SuiteManager($dispatcher, $suite, $settings); + $moduleContainer = $suiteManager->getModuleContainer(); + foreach (Configuration::modules($settings) as $module) { + $moduleContainer->mock($module, new Maybe()); + } + $suiteManager->loadTests($test); + $tests = $suiteManager->getSuite()->tests(); + + $dispatcher->dispatch(Events::SUITE_INIT, new SuiteEvent($suiteManager->getSuite(), null, $settings)); + $dispatcher->dispatch(Events::SUITE_BEFORE, new SuiteEvent($suiteManager->getSuite(), null, $settings)); + foreach ($tests as $test) { + if ($test instanceof \PHPUnit_Framework_TestSuite_DataProvider) { + foreach ($test as $t) { + if ($t instanceof Test) { + $this->dryRunTest($output, $dispatcher, $t); + } + } + } + if ($test instanceof Test and $test instanceof ScenarioDriven) { + $this->dryRunTest($output, $dispatcher, $test); + } + } + $dispatcher->dispatch(Events::SUITE_AFTER, new SuiteEvent($suiteManager->getSuite())); + } + + + protected function matchTestFromFilename($filename, $tests_path) + { + $filename = str_replace(['//', '\/', '\\'], '/', $filename); + $res = preg_match("~^$tests_path/(.*?)/(.*)$~", $filename, $matches); + if (!$res) { + throw new \InvalidArgumentException("Test file can't be matched"); + } + + return $matches; + } + + /** + * @param OutputInterface $output + * @param $dispatcher + * @param $test + */ + protected function dryRunTest(OutputInterface $output, EventDispatcher $dispatcher, Test $test) + { + $dispatcher->dispatch(Events::TEST_START, new TestEvent($test)); + $dispatcher->dispatch(Events::TEST_BEFORE, new TestEvent($test)); + try { + $test->test(); + } catch (\Exception $e) { + } + $dispatcher->dispatch(Events::TEST_AFTER, new TestEvent($test)); + $dispatcher->dispatch(Events::TEST_END, new TestEvent($test)); + if ($test->getMetadata()->isBlocked()) { + $output->writeln(''); + if ($skip = $test->getMetadata()->getSkip()) { + $output->writeln(" SKIPPED " . $skip); + } + if ($incomplete = $test->getMetadata()->getIncomplete()) { + $output->writeln(" INCOMPLETE " . $incomplete); + } + } + $output->writeln(''); + } +} \ No newline at end of file diff --git a/src/Codeception/Command/Run.php b/src/Codeception/Command/Run.php index 1a9be4914f..b2e986b6c9 100644 --- a/src/Codeception/Command/Run.php +++ b/src/Codeception/Command/Run.php @@ -12,6 +12,15 @@ /** * Executes tests. * + * Usage: + * + * * `codecept run acceptance` - run all acceptance tests + * * `codecept run tests/acceptance/MyCept.php` - run only MyCept + * * `codecept run acceptance MyCept` - same as above + * * `codecept run acceptance MyCest:myTestInIt` - run one test from a Cest + * * `codecept run acceptance checkout.feature` - run feature-file + * + * Full reference: * ``` * Arguments: * suite suite to be tested diff --git a/src/Codeception/Command/Shared/Style.php b/src/Codeception/Command/Shared/Style.php index bf2863570a..673754919b 100644 --- a/src/Codeception/Command/Shared/Style.php +++ b/src/Codeception/Command/Shared/Style.php @@ -12,5 +12,7 @@ public function addStyles(OutputInterface $output) $output->getFormatter()->setStyle('notice', $style); $style = new OutputFormatterStyle(null, null, ['bold']); $output->getFormatter()->setStyle('bold', $style); + $style = new OutputFormatterStyle(null, 'yellow', ['bold']); + $output->getFormatter()->setStyle('warning', $style); } } diff --git a/src/Codeception/Lib/Console/Output.php b/src/Codeception/Lib/Console/Output.php index c60c1f3088..facf5b338c 100644 --- a/src/Codeception/Lib/Console/Output.php +++ b/src/Codeception/Lib/Console/Output.php @@ -23,7 +23,7 @@ class Output extends ConsoleOutput protected $isInteractive = false; - function __construct($config) + public function __construct($config) { $this->config = array_merge($this->config, $config); @@ -72,7 +72,7 @@ public function debug($message) $this->writeln(" $message"); } - function message($message) + public function message($message) { $message = call_user_func_array('sprintf', func_get_args()); return new Message($message, $this); diff --git a/src/Codeception/Lib/ModuleContainer.php b/src/Codeception/Lib/ModuleContainer.php index c7bd20fb0d..a23f0526e3 100644 --- a/src/Codeception/Lib/ModuleContainer.php +++ b/src/Codeception/Lib/ModuleContainer.php @@ -96,6 +96,11 @@ public function all() return $this->modules; } + public function mock($moduleName, $mock) + { + $this->modules[$moduleName] = $mock; + } + private function instantiate($name, $class, $config) { $module = $this->di->instantiate($class, [$this, $config], false); diff --git a/src/Codeception/PHPUnit/ResultPrinter.php b/src/Codeception/PHPUnit/ResultPrinter.php index afdb44ef24..38cca9549f 100644 --- a/src/Codeception/PHPUnit/ResultPrinter.php +++ b/src/Codeception/PHPUnit/ResultPrinter.php @@ -1,9 +1,6 @@ testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_ERROR; $this->failed++; @@ -40,10 +37,10 @@ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_Asser * Incomplete test. * * @param PHPUnit_Framework_Test $test - * @param Exception $e + * @param \Exception $e * @param float $time */ - public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) + public function addIncompleteTest(PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE; $this->incomplete++; @@ -53,12 +50,12 @@ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $t * Risky test. * * @param PHPUnit_Framework_Test $test - * @param Exception $e + * @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 addRiskyTest(PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_RISKY; $this->risky++; @@ -68,12 +65,12 @@ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) * Skipped test. * * @param PHPUnit_Framework_Test $test - * @param Exception $e + * @param \Exception $e * @param float $time * * @since Method available since Release 3.0.0 */ - public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) + public function addSkippedTest(PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED; $this->skipped++; diff --git a/src/Codeception/Subscriber/Console.php b/src/Codeception/Subscriber/Console.php index 188757f903..1d8bd9a9ce 100644 --- a/src/Codeception/Subscriber/Console.php +++ b/src/Codeception/Subscriber/Console.php @@ -50,7 +50,6 @@ class Console implements EventSubscriberInterface protected $message = null; protected $steps = true; protected $debug = false; - protected $color = true; protected $ansi = true; protected $silent = false; protected $lastTestFailed = false; @@ -59,25 +58,35 @@ class Console implements EventSubscriberInterface protected $traceLength = 5; protected $width; protected $output; - protected $options; protected $fails = []; 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, + ]; + public function __construct($options) { - $this->options = $options; - $this->debug = $options['debug'] || $options['verbosity'] >= OutputInterface::VERBOSITY_VERY_VERBOSE; - $this->steps = $this->debug || $options['steps']; - $this->rawStackTrace = ($options['verbosity'] === OutputInterface::VERBOSITY_DEBUG); + $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); $this->output = new Output($options); if ($this->debug) { Debug::setOutput($this->output); } $this->detectWidth(); - if ($options['ansi'] && !$this->isWin()) { + if ($this->options['ansi'] && !$this->isWin()) { $this->chars['success'] = '✔'; $this->chars['fail'] = '✖'; } @@ -246,7 +255,7 @@ public function testIncomplete(FailEvent $e) protected function isDetailed($test) { if ($test instanceof ScenarioDriven && $this->steps) { - return !$test->getMetadata()->isBlocked(); + return true; } return false; } diff --git a/src/Codeception/SuiteManager.php b/src/Codeception/SuiteManager.php index 92db2a096a..6d92d64d62 100644 --- a/src/Codeception/SuiteManager.php +++ b/src/Codeception/SuiteManager.php @@ -146,7 +146,6 @@ public function run(PHPUnit\Runner $runner, \PHPUnit_Framework_TestResult $resul $this->dispatcher->dispatch(Events::SUITE_AFTER, new Event\SuiteEvent($this->suite, $result, $this->settings)); } - /** * @return \Codeception\Suite */ diff --git a/src/Codeception/Test/Cest.php b/src/Codeception/Test/Cest.php index 67535efb59..7791f334e1 100644 --- a/src/Codeception/Test/Cest.php +++ b/src/Codeception/Test/Cest.php @@ -108,6 +108,7 @@ protected function executeAfterMethods($testMethod, $I) } } } + protected function executeContextMethod($context, $I) { if (method_exists($this->testClassInstance, $context)) { diff --git a/src/Codeception/Test/Gherkin.php b/src/Codeception/Test/Gherkin.php index 88e0b54fe6..bf2ee816a1 100644 --- a/src/Codeception/Test/Gherkin.php +++ b/src/Codeception/Test/Gherkin.php @@ -93,7 +93,8 @@ protected function validateStep(StepNode $stepNode) } return; } - $this->getMetadata()->setIncomplete("Step definition for `$stepText` not found in contexts"); + $incomplete = $this->getMetadata()->getIncomplete(); + $this->getMetadata()->setIncomplete("$incomplete\nStep definition for `$stepText` not found in contexts"); } protected function runStep(StepNode $stepNode) diff --git a/tests/cli/DryRunCest.php b/tests/cli/DryRunCest.php new file mode 100644 index 0000000000..7f86929c48 --- /dev/null +++ b/tests/cli/DryRunCest.php @@ -0,0 +1,26 @@ +amInPath('tests/data/sandbox'); + } + + public function runCestWithExamples(CliGuy $I) + { + $I->executeCommand('dry-run scenario ExamplesCest --no-ansi'); + $I->seeInShellOutput('ExamplesCest: Files exists annotation'); + } + + public function runFeature(CliGuy $I) + { + $I->executeCommand('dry-run scenario File.feature --no-ansi'); + $I->seeInShellOutput('Run gherkin: Check file exists'); + $I->seeInShellOutput('In order to test a feature'); + $I->seeInShellOutput('As a user'); + $I->seeInShellOutput('Given i have terminal opened'); + $I->seeInShellOutput('INCOMPLETE'); + $I->seeInShellOutput('Step definition for `I have only idea of what\'s going on here` not found'); + + } +} diff --git a/tests/data/included/jazz/pianist/tests/_helpers/_generated/TestGuyActions.php b/tests/data/included/jazz/pianist/tests/_helpers/_generated/TestGuyActions.php index 58e0530c50..fabd0d7e68 100644 --- a/tests/data/included/jazz/pianist/tests/_helpers/_generated/TestGuyActions.php +++ b/tests/data/included/jazz/pianist/tests/_helpers/_generated/TestGuyActions.php @@ -1,4 +1,4 @@ - * ``` * + * Or provide an associative array for the second argument to specifically define which selection method should be used: + * + * ``` php + * selectOption('Which OS do you use?', array('text' => 'Windows')); // Only search by text 'Windows' + * $I->selectOption('Which OS do you use?', array('value' => 'windows')); // Only search by value 'windows' + * ?> + + ``` + * * @param $select * @param $option * @see \Codeception\Module\WebDriver::selectOption() diff --git a/tests/support/_generated/CliGuyActions.php b/tests/support/_generated/CliGuyActions.php index 7b9ce58ca6..51215df6d3 100644 --- a/tests/support/_generated/CliGuyActions.php +++ b/tests/support/_generated/CliGuyActions.php @@ -1,4 +1,4 @@ -