4 * This file is part of the Behat.
5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
11 namespace Behat\Behat\Tester\Cli;
13 use Behat\Behat\EventDispatcher\Event\AfterScenarioTested;
14 use Behat\Behat\EventDispatcher\Event\ExampleTested;
15 use Behat\Behat\EventDispatcher\Event\ScenarioTested;
16 use Behat\Testwork\Cli\Controller;
17 use Behat\Testwork\EventDispatcher\Event\ExerciseCompleted;
18 use Behat\Testwork\Tester\Result\TestResult;
19 use Symfony\Component\Console\Command\Command;
20 use Symfony\Component\Console\Input\InputInterface;
21 use Symfony\Component\Console\Input\InputOption;
22 use Symfony\Component\Console\Output\OutputInterface;
23 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
26 * Caches failed scenarios and reruns only them if `--rerun` option provided.
28 * @author Konstantin Kudryashov <ever.zet@gmail.com>
30 final class RerunController implements Controller
33 * @var EventDispatcherInterface
35 private $eventDispatcher;
47 private $lines = array();
54 * Initializes controller.
56 * @param EventDispatcherInterface $eventDispatcher
57 * @param null|string $cachePath
58 * @param string $basepath
60 public function __construct(EventDispatcherInterface $eventDispatcher, $cachePath, $basepath)
62 $this->eventDispatcher = $eventDispatcher;
63 $this->cachePath = null !== $cachePath ? rtrim($cachePath, DIRECTORY_SEPARATOR) : null;
64 $this->basepath = $basepath;
68 * Configures command to be executable by the controller.
70 * @param Command $command
72 public function configure(Command $command)
74 $command->addOption('--rerun', null, InputOption::VALUE_NONE,
75 'Re-run scenarios that failed during last execution.'
80 * Executes controller.
82 * @param InputInterface $input
83 * @param OutputInterface $output
85 * @return null|integer
87 public function execute(InputInterface $input, OutputInterface $output)
89 $this->eventDispatcher->addListener(ScenarioTested::AFTER, array($this, 'collectFailedScenario'), -50);
90 $this->eventDispatcher->addListener(ExampleTested::AFTER, array($this, 'collectFailedScenario'), -50);
91 $this->eventDispatcher->addListener(ExerciseCompleted::AFTER, array($this, 'writeCache'), -50);
93 $this->key = $this->generateKey($input);
95 if (!$input->getOption('rerun')) {
99 if (!$this->getFileName() || !file_exists($this->getFileName())) {
103 $input->setArgument('paths', $this->getFileName());
107 * Records scenario if it is failed.
109 * @param AfterScenarioTested $event
111 public function collectFailedScenario(AfterScenarioTested $event)
113 if (!$this->getFileName()) {
117 if ($event->getTestResult()->getResultCode() !== TestResult::FAILED) {
121 $feature = $event->getFeature();
122 $scenario = $event->getScenario();
123 $suitename = $event->getSuite()->getName();
125 $this->lines[$suitename][] = $feature->getFile() . ':' . $scenario->getLine();
129 * Writes failed scenarios cache.
131 public function writeCache()
133 if (!$this->getFileName()) {
137 if (file_exists($this->getFileName())) {
138 unlink($this->getFileName());
141 if (0 === count($this->lines)) {
145 file_put_contents($this->getFileName(), json_encode($this->lines));
149 * Generates cache key.
151 * @param InputInterface $input
155 private function generateKey(InputInterface $input)
158 $input->getParameterOption(array('--profile', '-p')) .
159 $input->getOption('suite') .
160 implode(' ', $input->getOption('name')) .
161 implode(' ', $input->getOption('tags')) .
162 $input->getOption('role') .
163 $input->getArgument('paths') .
169 * Returns cache filename (if exists).
171 * @return null|string
173 private function getFileName()
175 if (null === $this->cachePath || null === $this->key) {
179 if (!is_dir($this->cachePath)) {
180 mkdir($this->cachePath, 0777);
183 return $this->cachePath . DIRECTORY_SEPARATOR . $this->key . '.rerun';