| 1 | efrain | 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\EventDispatcher\Tester;
 | 
        
           |  |  | 18 |   | 
        
           |  |  | 19 | use Behat\Behat\EventDispatcher\Event\AfterStepSetup;
 | 
        
           |  |  | 20 | use Behat\Behat\EventDispatcher\Event\AfterStepTested;
 | 
        
           |  |  | 21 | use Behat\Behat\EventDispatcher\Event\BeforeStepTeardown;
 | 
        
           |  |  | 22 | use Behat\Behat\EventDispatcher\Event\BeforeStepTested;
 | 
        
           |  |  | 23 | use Behat\Behat\Tester\Result\ExecutedStepResult;
 | 
        
           |  |  | 24 | use Behat\Behat\Tester\Result\SkippedStepResult;
 | 
        
           |  |  | 25 | use Behat\Behat\Tester\Result\StepResult;
 | 
        
           |  |  | 26 | use Behat\Behat\Tester\Result\UndefinedStepResult;
 | 
        
           |  |  | 27 | use Behat\Behat\Tester\StepTester;
 | 
        
           |  |  | 28 | use Behat\Gherkin\Node\FeatureNode;
 | 
        
           |  |  | 29 | use Behat\Gherkin\Node\StepNode;
 | 
        
           |  |  | 30 | use Behat\Testwork\Call\CallResult;
 | 
        
           |  |  | 31 | use Behat\Testwork\Environment\Environment;
 | 
        
           |  |  | 32 | use Behat\Testwork\EventDispatcher\TestworkEventDispatcher;
 | 
        
           |  |  | 33 | use Moodle\BehatExtension\Context\Step\ChainedStep;
 | 
        
           |  |  | 34 | use Moodle\BehatExtension\Exception\SkippedException;
 | 
        
           |  |  | 35 | use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 | 
        
           |  |  | 36 |   | 
        
           |  |  | 37 | // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod
 | 
        
           |  |  | 38 |   | 
        
           |  |  | 39 | /**
 | 
        
           |  |  | 40 |  * Override step tester to ensure chained steps gets executed.
 | 
        
           |  |  | 41 |  *
 | 
        
           |  |  | 42 |  * @package    core
 | 
        
           |  |  | 43 |  * @copyright  2016 Rajesh Taneja <rajesh@moodle.com>
 | 
        
           |  |  | 44 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 45 |  */
 | 
        
           |  |  | 46 | class ChainedStepTester implements StepTester {
 | 
        
           |  |  | 47 |     /**
 | 
        
           |  |  | 48 |      * The text of the step to look for exceptions / debugging messages.
 | 
        
           |  |  | 49 |      */
 | 
        
           |  |  | 50 |     const EXCEPTIONS_STEP_TEXT = 'I look for exceptions';
 | 
        
           |  |  | 51 |   | 
        
           |  |  | 52 |     /**
 | 
        
           |  |  | 53 |      * @var StepTester Base step tester.
 | 
        
           |  |  | 54 |      */
 | 
        
           |  |  | 55 |     private $singlesteptester;
 | 
        
           |  |  | 56 |   | 
        
           |  |  | 57 |     /**
 | 
        
           |  |  | 58 |      * @var EventDispatcher keep step event dispatcher.
 | 
        
           |  |  | 59 |      */
 | 
        
           |  |  | 60 |     private $eventdispatcher;
 | 
        
           |  |  | 61 |   | 
        
           |  |  | 62 |     /**
 | 
        
           |  |  | 63 |      * Keep status of chained steps if used.
 | 
        
           |  |  | 64 |      * @var bool
 | 
        
           |  |  | 65 |      */
 | 
        
           |  |  | 66 |     protected static $chainedstepused = false;
 | 
        
           |  |  | 67 |   | 
        
           |  |  | 68 |     /**
 | 
        
           |  |  | 69 |      * Constructor.
 | 
        
           |  |  | 70 |      *
 | 
        
           |  |  | 71 |      * @param StepTester $steptester single step tester.
 | 
        
           |  |  | 72 |      */
 | 
        
           |  |  | 73 |     public function __construct(StepTester $steptester) {
 | 
        
           |  |  | 74 |         $this->singlesteptester = $steptester;
 | 
        
           |  |  | 75 |     }
 | 
        
           |  |  | 76 |   | 
        
           |  |  | 77 |     /**
 | 
        
           |  |  | 78 |      * Set event dispatcher to use for events.
 | 
        
           |  |  | 79 |      *
 | 
        
           |  |  | 80 |      * @param EventDispatcherInterface $eventdispatcher
 | 
        
           |  |  | 81 |      */
 | 
        
           |  |  | 82 |     public function setEventDispatcher(EventDispatcherInterface $eventdispatcher) {
 | 
        
           |  |  | 83 |         $this->eventdispatcher = $eventdispatcher;
 | 
        
           |  |  | 84 |     }
 | 
        
           |  |  | 85 |   | 
        
           |  |  | 86 |     /**
 | 
        
           |  |  | 87 |      * Sets up step for a test.
 | 
        
           |  |  | 88 |      *
 | 
        
           |  |  | 89 |      * @param Environment $env
 | 
        
           |  |  | 90 |      * @param FeatureNode $feature
 | 
        
           |  |  | 91 |      * @param StepNode    $step
 | 
        
           |  |  | 92 |      * @param bool     $skip
 | 
        
           |  |  | 93 |      *
 | 
        
           |  |  | 94 |      * @return Setup
 | 
        
           |  |  | 95 |      */
 | 
        
           |  |  | 96 |     public function setUp(Environment $env, FeatureNode $feature, StepNode $step, $skip) {
 | 
        
           |  |  | 97 |         return $this->singlesteptester->setUp($env, $feature, $step, $skip);
 | 
        
           |  |  | 98 |     }
 | 
        
           |  |  | 99 |   | 
        
           |  |  | 100 |     /**
 | 
        
           |  |  | 101 |      * Tests step.
 | 
        
           |  |  | 102 |      *
 | 
        
           |  |  | 103 |      * @param Environment $env
 | 
        
           |  |  | 104 |      * @param FeatureNode $feature
 | 
        
           |  |  | 105 |      * @param StepNode    $step
 | 
        
           |  |  | 106 |      * @param bool     $skip
 | 
        
           |  |  | 107 |      * @return StepResult
 | 
        
           |  |  | 108 |      */
 | 
        
           |  |  | 109 |     public function test(Environment $env, FeatureNode $feature, StepNode $step, $skip) {
 | 
        
           |  |  | 110 |         $result = $this->singlesteptester->test($env, $feature, $step, $skip);
 | 
        
           |  |  | 111 |   | 
        
           |  |  | 112 |         if (!($result instanceof ExecutedStepResult) || !$this->supportsResult($result->getCallResult())) {
 | 
        
           |  |  | 113 |             $result = $this->checkSkipResult($result);
 | 
        
           |  |  | 114 |   | 
        
           |  |  | 115 |             // If undefined step then don't continue chained steps.
 | 
        
           |  |  | 116 |             if ($result instanceof UndefinedStepResult) {
 | 
        
           |  |  | 117 |                 return $result;
 | 
        
           |  |  | 118 |             }
 | 
        
           |  |  | 119 |   | 
        
           |  |  | 120 |             // If exception caught, then don't continue chained steps.
 | 
        
           |  |  | 121 |             if (($result instanceof ExecutedStepResult) && $result->hasException()) {
 | 
        
           |  |  | 122 |                 return $result;
 | 
        
           |  |  | 123 |             }
 | 
        
           |  |  | 124 |   | 
        
           |  |  | 125 |             // If step is skipped, then return. no need to continue chain steps.
 | 
        
           |  |  | 126 |             if ($result instanceof SkippedStepResult) {
 | 
        
           |  |  | 127 |                 return $result;
 | 
        
           |  |  | 128 |             }
 | 
        
           |  |  | 129 |   | 
        
           |  |  | 130 |             // Check for exceptions.
 | 
        
           |  |  | 131 |             // Extra step, looking for a moodle exception, a debugging() message or a PHP debug message.
 | 
        
           |  |  | 132 |             $checkingstep = new StepNode('Given', self::EXCEPTIONS_STEP_TEXT, [], $step->getLine());
 | 
        
           |  |  | 133 |             $afterexceptioncheckingevent = $this->singlesteptester->test($env, $feature, $checkingstep, $skip);
 | 
        
           |  |  | 134 |             $exceptioncheckresult = $this->checkSkipResult($afterexceptioncheckingevent);
 | 
        
           |  |  | 135 |   | 
        
           |  |  | 136 |             if (!$exceptioncheckresult->isPassed()) {
 | 
        
           |  |  | 137 |                 return $exceptioncheckresult;
 | 
        
           |  |  | 138 |             }
 | 
        
           |  |  | 139 |   | 
        
           |  |  | 140 |             return $result;
 | 
        
           |  |  | 141 |         }
 | 
        
           |  |  | 142 |   | 
        
           |  |  | 143 |         return $this->runChainedSteps($env, $feature, $result, $skip);
 | 
        
           |  |  | 144 |     }
 | 
        
           |  |  | 145 |   | 
        
           |  |  | 146 |     /**
 | 
        
           |  |  | 147 |      * Tears down step after a test.
 | 
        
           |  |  | 148 |      *
 | 
        
           |  |  | 149 |      * @param Environment $env
 | 
        
           |  |  | 150 |      * @param FeatureNode $feature
 | 
        
           |  |  | 151 |      * @param StepNode    $step
 | 
        
           |  |  | 152 |      * @param bool     $skip
 | 
        
           |  |  | 153 |      * @param StepResult  $result
 | 
        
           |  |  | 154 |      * @return Teardown
 | 
        
           |  |  | 155 |      */
 | 
        
           |  |  | 156 |     public function tearDown(Environment $env, FeatureNode $feature, StepNode $step, $skip, StepResult $result) {
 | 
        
           |  |  | 157 |         return $this->singlesteptester->tearDown($env, $feature, $step, $skip, $result);
 | 
        
           |  |  | 158 |     }
 | 
        
           |  |  | 159 |   | 
        
           |  |  | 160 |     /**
 | 
        
           |  |  | 161 |      * Check if results supported.
 | 
        
           |  |  | 162 |      *
 | 
        
           |  |  | 163 |      * @param CallResult $result
 | 
        
           |  |  | 164 |      * @return bool
 | 
        
           |  |  | 165 |      */
 | 
        
           |  |  | 166 |     private function supportsResult(CallResult $result) {
 | 
        
           |  |  | 167 |         $return = $result->getReturn();
 | 
        
           |  |  | 168 |         if ($return instanceof ChainedStep) {
 | 
        
           |  |  | 169 |             return true;
 | 
        
           |  |  | 170 |         }
 | 
        
           |  |  | 171 |         if (!is_array($return) || empty($return)) {
 | 
        
           |  |  | 172 |             return false;
 | 
        
           |  |  | 173 |         }
 | 
        
           |  |  | 174 |         foreach ($return as $value) {
 | 
        
           |  |  | 175 |             if (!$value instanceof ChainedStep) {
 | 
        
           |  |  | 176 |                 return false;
 | 
        
           |  |  | 177 |             }
 | 
        
           |  |  | 178 |         }
 | 
        
           |  |  | 179 |         return true;
 | 
        
           |  |  | 180 |     }
 | 
        
           |  |  | 181 |   | 
        
           |  |  | 182 |     /**
 | 
        
           |  |  | 183 |      * Run chained steps.
 | 
        
           |  |  | 184 |      *
 | 
        
           |  |  | 185 |      * @param Environment $env
 | 
        
           |  |  | 186 |      * @param FeatureNode $feature
 | 
        
           |  |  | 187 |      * @param ExecutedStepResult $result
 | 
        
           |  |  | 188 |      * @param bool $skip
 | 
        
           |  |  | 189 |      * @return ExecutedStepResult|StepResult
 | 
        
           |  |  | 190 |      */
 | 
        
           |  |  | 191 |     private function runChainedSteps(Environment $env, FeatureNode $feature, ExecutedStepResult $result, $skip) {
 | 
        
           |  |  | 192 |         // Set chained setp is used, so it can be used by formatter to o/p.
 | 
        
           |  |  | 193 |         self::$chainedstepused = true;
 | 
        
           |  |  | 194 |   | 
        
           |  |  | 195 |         $callresult = $result->getCallResult();
 | 
        
           |  |  | 196 |         $steps = $callresult->getReturn();
 | 
        
           |  |  | 197 |   | 
        
           |  |  | 198 |         if (!is_array($steps)) {
 | 
        
           |  |  | 199 |             // Test it, no need to dispatch events for single chain.
 | 
        
           |  |  | 200 |             $stepresult = $this->test($env, $feature, $steps, $skip);
 | 
        
           |  |  | 201 |             return $this->checkSkipResult($stepresult);
 | 
        
           |  |  | 202 |         }
 | 
        
           |  |  | 203 |   | 
        
           |  |  | 204 |         // Test all steps.
 | 
        
           |  |  | 205 |         foreach ($steps as $step) {
 | 
        
           |  |  | 206 |             // Setup new step.
 | 
        
           |  |  | 207 |             $event = new BeforeStepTested($env, $feature, $step);
 | 
        
           |  |  | 208 |             if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) {
 | 
        
           |  |  | 209 |                 // Symfony 4.3 and up.
 | 
        
           |  |  | 210 |                 $this->eventdispatcher->dispatch($event, $event::BEFORE);
 | 
        
           |  |  | 211 |             } else {
 | 
        
           |  |  | 212 |                 // TODO: Remove when our min supported version is >= 4.3.
 | 
        
           |  |  | 213 |                 $this->eventdispatcher->dispatch($event::BEFORE, $event);
 | 
        
           |  |  | 214 |             }
 | 
        
           |  |  | 215 |   | 
        
           |  |  | 216 |             $setup = $this->setUp($env, $feature, $step, $skip);
 | 
        
           |  |  | 217 |   | 
        
           |  |  | 218 |             $event = new AfterStepSetup($env, $feature, $step, $setup);
 | 
        
           |  |  | 219 |             if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) {
 | 
        
           |  |  | 220 |                 // Symfony 4.3 and up.
 | 
        
           |  |  | 221 |                 $this->eventdispatcher->dispatch($event, $event::AFTER_SETUP);
 | 
        
           |  |  | 222 |             } else {
 | 
        
           |  |  | 223 |                 // TODO: Remove when our min supported version is >= 4.3.
 | 
        
           |  |  | 224 |                 $this->eventdispatcher->dispatch($event::AFTER_SETUP, $event);
 | 
        
           |  |  | 225 |             }
 | 
        
           |  |  | 226 |   | 
        
           |  |  | 227 |             // Test it.
 | 
        
           |  |  | 228 |             $stepresult = $this->test($env, $feature, $step, $skip);
 | 
        
           |  |  | 229 |   | 
        
           |  |  | 230 |             // Tear down.
 | 
        
           |  |  | 231 |             $event = new BeforeStepTeardown($env, $feature, $step, $result);
 | 
        
           |  |  | 232 |             if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) {
 | 
        
           |  |  | 233 |                 // Symfony 4.3 and up.
 | 
        
           |  |  | 234 |                 $this->eventdispatcher->dispatch($event, $event::BEFORE_TEARDOWN);
 | 
        
           |  |  | 235 |             } else {
 | 
        
           |  |  | 236 |                 // TODO: Remove when our min supported version is >= 4.3.
 | 
        
           |  |  | 237 |                 $this->eventdispatcher->dispatch($event::BEFORE_TEARDOWN, $event);
 | 
        
           |  |  | 238 |             }
 | 
        
           |  |  | 239 |   | 
        
           |  |  | 240 |             $teardown = $this->tearDown($env, $feature, $step, $skip, $result);
 | 
        
           |  |  | 241 |   | 
        
           |  |  | 242 |             $event = new AfterStepTested($env, $feature, $step, $result, $teardown);
 | 
        
           |  |  | 243 |             if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) {
 | 
        
           |  |  | 244 |                 // Symfony 4.3 and up.
 | 
        
           |  |  | 245 |                 $this->eventdispatcher->dispatch($event, $event::AFTER);
 | 
        
           |  |  | 246 |             } else {
 | 
        
           |  |  | 247 |                 // TODO: Remove when our min supported version is >= 4.3.
 | 
        
           |  |  | 248 |                 $this->eventdispatcher->dispatch($event::AFTER, $event);
 | 
        
           |  |  | 249 |             }
 | 
        
           |  |  | 250 |   | 
        
           |  |  | 251 |             if (!$stepresult->isPassed()) {
 | 
        
           |  |  | 252 |                 return $this->checkSkipResult($stepresult);
 | 
        
           |  |  | 253 |             }
 | 
        
           |  |  | 254 |         }
 | 
        
           |  |  | 255 |         return $this->checkSkipResult($stepresult);
 | 
        
           |  |  | 256 |     }
 | 
        
           |  |  | 257 |   | 
        
           |  |  | 258 |     /**
 | 
        
           |  |  | 259 |      * Handle skip exception.
 | 
        
           |  |  | 260 |      *
 | 
        
           |  |  | 261 |      * @param StepResult $result
 | 
        
           |  |  | 262 |      *
 | 
        
           |  |  | 263 |      * @return ExecutedStepResult|SkippedStepResult
 | 
        
           |  |  | 264 |      */
 | 
        
           |  |  | 265 |     private function checkSkipResult(StepResult $result) {
 | 
        
           |  |  | 266 |         if ((method_exists($result, 'getException')) && ($result->getException() instanceof SkippedException)) {
 | 
        
           |  |  | 267 |             return new SkippedStepResult($result->getSearchResult());
 | 
        
           |  |  | 268 |         } else {
 | 
        
           |  |  | 269 |             return $result;
 | 
        
           |  |  | 270 |         }
 | 
        
           |  |  | 271 |     }
 | 
        
           |  |  | 272 |   | 
        
           |  |  | 273 |     /**
 | 
        
           |  |  | 274 |      * Returns if cahined steps are used.
 | 
        
           |  |  | 275 |      * @return bool.
 | 
        
           |  |  | 276 |      */
 | 
        
           |  |  | 277 |     public static function is_chained_step_used() {
 | 
        
           |  |  | 278 |         return self::$chainedstepused;
 | 
        
           |  |  | 279 |     }
 | 
        
           |  |  | 280 | }
 |