1 | <?php namespace Chekote\BehatRetryExtension\Tester; |
||||
2 | |||||
3 | use Behat\Behat\Definition\Call\DefinitionCall; |
||||
4 | use Behat\Behat\Definition\DefinitionFinder; |
||||
5 | use Behat\Behat\Definition\Exception\SearchException; |
||||
6 | use Behat\Behat\Definition\SearchResult; |
||||
7 | use Behat\Behat\Tester\Exception\PendingException; |
||||
8 | use Behat\Behat\Tester\Result\ExecutedStepResult; |
||||
9 | use Behat\Behat\Tester\Result\FailedStepSearchResult; |
||||
10 | use Behat\Behat\Tester\Result\SkippedStepResult; |
||||
11 | use Behat\Behat\Tester\Result\StepResult; |
||||
12 | use Behat\Behat\Tester\Result\UndefinedStepResult; |
||||
13 | use Behat\Behat\Tester\StepTester; |
||||
14 | use Behat\Gherkin\Node\FeatureNode; |
||||
15 | use Behat\Gherkin\Node\StepNode; |
||||
16 | use Behat\Testwork\Call\CallCenter; |
||||
17 | use Behat\Testwork\Call\CallResult; |
||||
18 | use Behat\Testwork\Environment\Environment; |
||||
19 | use Behat\Testwork\Tester\Setup\SuccessfulSetup; |
||||
20 | use Behat\Testwork\Tester\Setup\SuccessfulTeardown; |
||||
21 | use Chekote\BehatRetryExtension\Definition\Exception\StrictKeywordException; |
||||
22 | |||||
23 | /** |
||||
24 | * Tester executing step tests in the runtime. |
||||
25 | * |
||||
26 | * This class is a copy of \Behat\Behat\Tester\Runtime\RuntimeStepTester by Konstantin Kudryashov <[email protected]>. |
||||
27 | * It has a modified testDefinition() method to implement the retry functionality, and support strict keywords. |
||||
28 | * |
||||
29 | * I would ideally like to extend or wrap the existing RuntimeStepTester, but neither is possible because the class |
||||
30 | * is final, and the method is private. v_v |
||||
31 | */ |
||||
32 | final class RuntimeStepTester implements StepTester |
||||
33 | { |
||||
34 | /** @var float Number of seconds to attempt "Then" steps before accepting a failure */ |
||||
35 | public static $timeout; |
||||
36 | |||||
37 | /** @var int number of nanoseconds to wait between each retry of "Then" steps */ |
||||
38 | public static $interval; |
||||
39 | |||||
40 | /** |
||||
41 | * @var bool true if steps should only match when the correct keyword is used. false steps should match regardless |
||||
42 | * of keyword |
||||
43 | */ |
||||
44 | public static $strictKeywords; |
||||
45 | |||||
46 | /** @var array list of Gherkin keywords */ |
||||
47 | protected static $keywords = ['Given', 'When', 'Then']; |
||||
48 | |||||
49 | /** |
||||
50 | * @var DefinitionFinder |
||||
51 | */ |
||||
52 | private $definitionFinder; |
||||
53 | |||||
54 | /** |
||||
55 | * @var CallCenter |
||||
56 | */ |
||||
57 | private $callCenter; |
||||
58 | |||||
59 | /** @var string The last "Given", "When", or "Then" keyword encountered */ |
||||
60 | protected $lastKeyword; |
||||
61 | |||||
62 | /** |
||||
63 | * Initialize tester. |
||||
64 | * |
||||
65 | * @param DefinitionFinder $definitionFinder |
||||
66 | * @param CallCenter $callCenter |
||||
67 | */ |
||||
68 | public function __construct(DefinitionFinder $definitionFinder, CallCenter $callCenter) |
||||
69 | { |
||||
70 | $this->definitionFinder = $definitionFinder; |
||||
71 | $this->callCenter = $callCenter; |
||||
72 | } |
||||
73 | |||||
74 | /** |
||||
75 | * {@inheritdoc} |
||||
76 | */ |
||||
77 | public function setUp(Environment $env, FeatureNode $feature, StepNode $step, $skip) |
||||
78 | { |
||||
79 | return new SuccessfulSetup(); |
||||
80 | } |
||||
81 | |||||
82 | /** |
||||
83 | * {@inheritdoc} |
||||
84 | */ |
||||
85 | public function test(Environment $env, FeatureNode $feature, StepNode $step, $skip = false) |
||||
86 | { |
||||
87 | $this->updateLastKeyword($step); |
||||
88 | |||||
89 | try { |
||||
90 | $search = $this->searchDefinition($env, $feature, $step); |
||||
91 | $result = $this->testDefinition($env, $feature, $step, $search, $skip); |
||||
92 | } catch (SearchException $exception) { |
||||
93 | $result = new FailedStepSearchResult($exception); |
||||
94 | } |
||||
95 | |||||
96 | return $result; |
||||
97 | } |
||||
98 | |||||
99 | /** |
||||
100 | * {@inheritdoc} |
||||
101 | */ |
||||
102 | public function tearDown(Environment $env, FeatureNode $feature, StepNode $step, $skip, StepResult $result) |
||||
103 | { |
||||
104 | return new SuccessfulTeardown(); |
||||
105 | } |
||||
106 | |||||
107 | /** |
||||
108 | * Searches for a definition. |
||||
109 | * |
||||
110 | * @param Environment $env |
||||
111 | * @param FeatureNode $feature |
||||
112 | * @param StepNode $step |
||||
113 | * |
||||
114 | * @return SearchResult |
||||
115 | */ |
||||
116 | private function searchDefinition(Environment $env, FeatureNode $feature, StepNode $step) |
||||
117 | { |
||||
118 | $result = $this->definitionFinder->findDefinition($env, $feature, $step); |
||||
119 | |||||
120 | if (self::$strictKeywords && $result->hasMatch() && $this->lastKeyword != $result->getMatchedDefinition()->getType()) { |
||||
121 | throw new StrictKeywordException($this->lastKeyword, $result->getMatchedDefinition()); |
||||
122 | } |
||||
123 | |||||
124 | return $result; |
||||
125 | } |
||||
126 | |||||
127 | /** |
||||
128 | * Tests found definition. |
||||
129 | * |
||||
130 | * @param Environment $env |
||||
131 | * @param FeatureNode $feature |
||||
132 | * @param StepNode $step |
||||
133 | * @param SearchResult $search |
||||
134 | * @param bool $skip |
||||
135 | * |
||||
136 | * @return StepResult |
||||
137 | */ |
||||
138 | private function testDefinition(Environment $env, FeatureNode $feature, StepNode $step, SearchResult $search, $skip) |
||||
139 | { |
||||
140 | if (!$search->hasMatch()) { |
||||
141 | return new UndefinedStepResult(); |
||||
142 | } |
||||
143 | |||||
144 | if ($skip) { |
||||
145 | return new SkippedStepResult($search); |
||||
146 | } |
||||
147 | |||||
148 | $call = $this->createDefinitionCall($env, $feature, $search, $step); |
||||
149 | |||||
150 | $result = $this->makeCall($call); |
||||
151 | |||||
152 | return new ExecutedStepResult($search, $result); |
||||
153 | } |
||||
154 | |||||
155 | /** |
||||
156 | * Records the keyword for the step. |
||||
157 | * |
||||
158 | * This allows us to know where we are when processing And or But steps. |
||||
159 | * |
||||
160 | * @param StepNode $step |
||||
161 | * @return void |
||||
162 | */ |
||||
163 | protected function updateLastKeyword(StepNode $step) |
||||
164 | { |
||||
165 | $keyword = $step->getKeyword(); |
||||
166 | if (in_array($keyword, self::$keywords)) { |
||||
167 | $this->lastKeyword = $keyword; |
||||
168 | } |
||||
169 | } |
||||
170 | |||||
171 | /** |
||||
172 | * Calls the specified definition, either directly, or via spin() if self::$timeout is not 0. |
||||
173 | * |
||||
174 | * @param DefinitionCall $call the call to make. |
||||
175 | * @return CallResult the result of the call. |
||||
176 | */ |
||||
177 | protected function makeCall(DefinitionCall $call) |
||||
178 | { |
||||
179 | // @todo We can only "spin" if we are interacting with a remote browser. If the browser is |
||||
180 | // running in the same thread as this test (such as with Goutte or Zombie), then spinning |
||||
181 | // will only prevent that process from continuing, and the test will either pass immediately, |
||||
182 | // or not at all. We need to find out how to check what Driver we're using... |
||||
183 | if ($this->lastKeyword == 'Then' && self::$timeout) { |
||||
184 | return $this->spin(function () use ($call) { |
||||
185 | return $this->callCenter->makeCall($call); |
||||
186 | }); |
||||
187 | } else { |
||||
188 | return $this->callCenter->makeCall($call); |
||||
189 | } |
||||
190 | } |
||||
191 | |||||
192 | /** |
||||
193 | * Continually calls an assertion until it passes or the timeout is reached. |
||||
194 | * |
||||
195 | * @param callable $lambda The lambda assertion to call. Must take no arguments and return |
||||
196 | * a CallResult. |
||||
197 | * @return CallResult |
||||
198 | */ |
||||
199 | protected function spin(callable $lambda) |
||||
200 | { |
||||
201 | $start = microtime(true); |
||||
202 | |||||
203 | $result = null; |
||||
204 | while (microtime(true) - $start < self::$timeout) { |
||||
205 | /** @var $result CallResult */ |
||||
206 | $result = $lambda(); |
||||
207 | |||||
208 | if (!$result->hasException() || ($result->getException() instanceof PendingException)) { |
||||
209 | break; |
||||
210 | } |
||||
211 | |||||
212 | time_nanosleep(0, self::$interval); |
||||
213 | } |
||||
214 | |||||
215 | return $result; |
||||
216 | } |
||||
217 | |||||
218 | /** |
||||
219 | * Creates definition call. |
||||
220 | * |
||||
221 | * @param Environment $env |
||||
222 | * @param FeatureNode $feature |
||||
223 | * @param SearchResult $search |
||||
224 | * @param StepNode $step |
||||
225 | * |
||||
226 | * @return DefinitionCall |
||||
227 | */ |
||||
228 | private function createDefinitionCall(Environment $env, FeatureNode $feature, SearchResult $search, StepNode $step) |
||||
229 | { |
||||
230 | $definition = $search->getMatchedDefinition(); |
||||
231 | $arguments = $search->getMatchedArguments(); |
||||
232 | |||||
233 | return new DefinitionCall($env, $feature, $step, $definition, $arguments); |
||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
It seems like
$definition can also be of type null ; however, parameter $definition of Behat\Behat\Definition\C...tionCall::__construct() does only seem to accept Behat\Behat\Definition\Definition , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
234 | } |
||||
235 | } |
||||
236 |