1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace Moodle\BehatExtension\Tester\Cli; 18 19 use Behat\Behat\EventDispatcher\Event\AfterScenarioTested; 20 use Behat\Behat\EventDispatcher\Event\ExampleTested; 21 use Behat\Behat\EventDispatcher\Event\ScenarioTested; 22 use Behat\Testwork\Cli\Controller; 23 use Behat\Testwork\EventDispatcher\Event\ExerciseCompleted; 24 use Behat\Testwork\Tester\Result\TestResult; 25 use Symfony\Component\Console\Command\Command; 26 use Symfony\Component\Console\Input\InputInterface; 27 use Symfony\Component\Console\Input\InputOption; 28 use Symfony\Component\Console\Output\OutputInterface; 29 use Symfony\Component\EventDispatcher\EventDispatcherInterface; 30 31 // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod 32 33 /** 34 * Caches passed scenarios and skip only them if `--skip-passed` option provided. 35 * 36 * @package core 37 * @copyright 2016 onwards Rajesh Taneja 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 final class SkipPassedController implements Controller { 41 /** 42 * @var EventDispatcherInterface 43 */ 44 private $eventdispatcher; 45 46 /** 47 * @var null|string 48 */ 49 private $cachepath; 50 51 /** 52 * @var string 53 */ 54 private $key; 55 56 /** 57 * @var string[] 58 */ 59 private $lines = []; 60 61 /** 62 * @var string 63 */ 64 private $basepath; 65 66 /** 67 * Initializes controller. 68 * 69 * @param EventDispatcherInterface $eventdispatcher 70 * @param null|string $cachepath 71 * @param string $basepath 72 */ 73 public function __construct(EventDispatcherInterface $eventdispatcher, $cachepath, $basepath) { 74 $this->eventdispatcher = $eventdispatcher; 75 $this->cachepath = null !== $cachepath ? rtrim($cachepath, DIRECTORY_SEPARATOR) : null; 76 $this->basepath = $basepath; 77 } 78 79 /** 80 * Configures command to be executable by the controller. 81 * 82 * @param Command $command 83 */ 84 public function configure(Command $command) { 85 $command->addOption('--skip-passed', null, InputOption::VALUE_NONE, 86 'Skip scenarios that passed during last execution.' 87 ); 88 } 89 90 /** 91 * Executes controller. 92 * 93 * @param InputInterface $input 94 * @param OutputInterface $output 95 * 96 * @return null|integer 97 */ 98 public function execute(InputInterface $input, OutputInterface $output) { 99 if (!$input->getOption('skip-passed')) { 100 // If no skip option is passed then remove any old file which we are saving. 101 if (!$this->getFileName()) { 102 return; 103 } 104 if (file_exists($this->getFileName())) { 105 unlink($this->getFileName()); 106 } 107 return; 108 } 109 110 $this->eventdispatcher->addListener(ScenarioTested::AFTER, [$this, 'collectPassedScenario'], -50); 111 $this->eventdispatcher->addListener(ExampleTested::AFTER, [$this, 'collectPassedScenario'], -50); 112 $this->eventdispatcher->addListener(ExerciseCompleted::AFTER, [$this, 'writeCache'], -50); 113 $this->key = $this->generateKey($input); 114 115 if (!$this->getFileName() || !file_exists($this->getFileName())) { 116 return; 117 } 118 $input->setArgument('paths', $this->getFileName()); 119 120 $existing = json_decode(file_get_contents($this->getFileName()), true); 121 if (!empty($existing)) { 122 $this->lines = array_merge_recursive($existing, $this->lines); 123 } 124 } 125 126 /** 127 * Records scenario if it is passed. 128 * 129 * @param AfterScenarioTested $event 130 */ 131 public function collectPassedScenario(AfterScenarioTested $event) { 132 if (!$this->getFileName()) { 133 return; 134 } 135 136 $feature = $event->getFeature(); 137 $suitename = $event->getSuite()->getName(); 138 139 if ( 140 ($event->getTestResult()->getResultCode() !== TestResult::PASSED) && 141 ($event->getTestResult()->getResultCode() !== TestResult::SKIPPED) 142 ) { 143 unset($this->lines[$suitename][$feature->getFile()]); 144 return; 145 } 146 147 $this->lines[$suitename][$feature->getFile()] = $feature->getFile(); 148 } 149 150 /** 151 * Writes passed scenarios cache. 152 */ 153 public function writeCache() { 154 if (!$this->getFileName()) { 155 return; 156 } 157 if (0 === count($this->lines)) { 158 return; 159 } 160 file_put_contents($this->getFileName(), json_encode($this->lines)); 161 } 162 163 /** 164 * Generates cache key. 165 * 166 * @param InputInterface $input 167 * 168 * @return string 169 */ 170 private function generateKey(InputInterface $input) { 171 return md5( 172 $input->getParameterOption(['--profile', '-p']) . 173 $input->getOption('suite') . 174 implode(' ', $input->getOption('name')) . 175 implode(' ', $input->getOption('tags')) . 176 $input->getOption('role') . 177 $input->getArgument('paths') . 178 $this->basepath 179 ); 180 } 181 182 /** 183 * Returns cache filename (if exists). 184 * 185 * @return null|string 186 */ 187 private function getFileName() { 188 if (null === $this->cachepath || null === $this->key) { 189 return null; 190 } 191 if (!is_dir($this->cachepath)) { 192 mkdir($this->cachepath, 0777); 193 } 194 return $this->cachepath . DIRECTORY_SEPARATOR . $this->key . '.passed'; 195 } 196 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body