These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace ParaTest\Runners\PHPUnit; |
||
3 | |||
4 | use ParaTest\Parser\NoClassInFileException; |
||
5 | use ParaTest\Parser\ParsedClass; |
||
6 | use ParaTest\Parser\ParsedObject; |
||
7 | use ParaTest\Parser\Parser; |
||
8 | |||
9 | class SuiteLoader |
||
10 | { |
||
11 | /** |
||
12 | * The collection of loaded files |
||
13 | * |
||
14 | * @var array |
||
15 | */ |
||
16 | protected $files = []; |
||
17 | |||
18 | /** |
||
19 | * The collection of parsed test classes |
||
20 | * |
||
21 | * @var array |
||
22 | */ |
||
23 | protected $loadedSuites = []; |
||
24 | |||
25 | /** |
||
26 | * @var Options |
||
27 | */ |
||
28 | public $options; |
||
29 | |||
30 | 26 | public function __construct($options = null) |
|
31 | { |
||
32 | 26 | if ($options && !$options instanceof Options) { |
|
33 | 1 | throw new \InvalidArgumentException("SuiteLoader options must be null or of type Options"); |
|
34 | } |
||
35 | |||
36 | 25 | $this->options = $options; |
|
37 | 25 | } |
|
38 | |||
39 | /** |
||
40 | * Returns all parsed suite objects as ExecutableTest |
||
41 | * instances |
||
42 | * |
||
43 | * @return array |
||
44 | */ |
||
45 | 2 | public function getSuites() |
|
46 | { |
||
47 | 2 | return $this->loadedSuites; |
|
48 | } |
||
49 | |||
50 | /** |
||
51 | * Returns a collection of TestMethod objects |
||
52 | * for all loaded ExecutableTest instances |
||
53 | * |
||
54 | * @return array |
||
55 | */ |
||
56 | 3 | public function getTestMethods() |
|
57 | { |
||
58 | 3 | $methods = []; |
|
59 | 3 | foreach ($this->loadedSuites as $suite) { |
|
60 | 2 | $methods = array_merge($methods, $suite->getFunctions()); |
|
61 | } |
||
62 | |||
63 | 3 | return $methods; |
|
64 | } |
||
65 | |||
66 | /** |
||
67 | * Populates the loaded suite collection. Will load suites |
||
68 | * based off a phpunit xml configuration or a specified path |
||
69 | * |
||
70 | * @param string $path |
||
71 | * @throws \RuntimeException |
||
72 | */ |
||
73 | 23 | public function load($path = '') |
|
74 | { |
||
75 | 23 | if (is_object($this->options) && isset($this->options->filtered['configuration'])) { |
|
76 | 16 | $configuration = $this->options->filtered['configuration']; |
|
77 | } else { |
||
78 | 7 | $configuration = new Configuration(''); |
|
79 | } |
||
80 | |||
81 | 23 | if ($path) { |
|
82 | 9 | $testFileLoader = new TestFileLoader($this->options); |
|
83 | 9 | $this->files = array_merge($this->files, $testFileLoader->loadPath($path)); |
|
84 | 14 | } elseif (isset($this->options->testsuite) && $this->options->testsuite) { |
|
85 | 10 | foreach ($configuration->getSuiteByName($this->options->testsuite) as $suite) { |
|
86 | 10 | foreach ($suite as $suitePath) { |
|
87 | 10 | $testFileLoader = new TestFileLoader($this->options); |
|
88 | 10 | $this->files = array_merge($this->files, $testFileLoader->loadSuitePath($suitePath)); |
|
89 | } |
||
90 | } |
||
91 | 4 | } elseif ($suites = $configuration->getSuites()) { |
|
92 | 2 | foreach ($suites as $suite) { |
|
93 | 2 | foreach ($suite as $suitePath) { |
|
94 | 2 | $testFileLoader = new TestFileLoader($this->options); |
|
95 | 2 | $this->files = array_merge($this->files, $testFileLoader->loadSuitePath($suitePath)); |
|
96 | } |
||
97 | } |
||
98 | } |
||
99 | |||
100 | |||
101 | 21 | if (!$this->files) { |
|
102 | 1 | throw new \RuntimeException("No path or configuration provided (tests must end with Test.php)"); |
|
103 | } |
||
104 | |||
105 | 20 | $this->files = array_unique($this->files); // remove duplicates |
|
106 | |||
107 | 20 | $this->initSuites(); |
|
108 | 20 | } |
|
109 | |||
110 | |||
111 | /** |
||
112 | * Called after all files are loaded. Parses loaded files into |
||
113 | * ExecutableTest objects - either Suite or TestMethod |
||
114 | */ |
||
115 | 20 | private function initSuites() |
|
116 | { |
||
117 | 20 | foreach ($this->files as $path) { |
|
118 | try { |
||
119 | 20 | $parser = new Parser($path); |
|
120 | 19 | if ($class = $parser->getClass()) { |
|
121 | 19 | $this->loadedSuites[$path] = $this->createSuite($path, $class); |
|
122 | } |
||
123 | 1 | } catch (NoClassInFileException $e) { |
|
124 | 20 | continue; |
|
125 | } |
||
126 | } |
||
127 | 20 | } |
|
128 | |||
129 | 19 | private function executableTests($path, $class) |
|
130 | { |
||
131 | 19 | $executableTests = []; |
|
132 | 19 | $methodBatches = $this->getMethodBatches($class); |
|
133 | 19 | foreach ($methodBatches as $methodBatch) { |
|
134 | 19 | $executableTest = new TestMethod($path, $methodBatch); |
|
135 | 19 | $executableTests[] = $executableTest; |
|
136 | } |
||
137 | 19 | return $executableTests; |
|
138 | } |
||
139 | |||
140 | /** |
||
141 | * Get method batches. |
||
142 | * |
||
143 | * Identify method dependencies, and group dependents and dependees on a single methodBatch. |
||
144 | * Use max batch size to fill batches. |
||
145 | * |
||
146 | * @param ParsedClass $class |
||
147 | * @return array of MethodBatches. Each MethodBatch has an array of method names |
||
148 | */ |
||
149 | 19 | private function getMethodBatches($class) |
|
150 | { |
||
151 | 19 | $classMethods = $class->getMethods($this->options ? $this->options->annotations : []); |
|
152 | 19 | $maxBatchSize = $this->options && $this->options->functional ? $this->options->maxBatchSize : 0; |
|
153 | 19 | $batches = []; |
|
154 | 19 | foreach ($classMethods as $method) { |
|
155 | 19 | $tests = $this->getMethodTests($class, $method, $maxBatchSize != 0); |
|
156 | // if filter passed to paratest then method tests can be blank if not match to filter |
||
157 | 19 | if (!$tests) { |
|
158 | continue; |
||
159 | } |
||
160 | |||
161 | 19 | if (($dependsOn = $this->methodDependency($method)) != null) { |
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
162 | 1 | $this->addDependentTestsToBatchSet($batches, $dependsOn, $tests); |
|
163 | } else { |
||
164 | 19 | $this->addTestsToBatchSet($batches, $tests, $maxBatchSize); |
|
165 | } |
||
166 | } |
||
167 | 19 | return $batches; |
|
168 | } |
||
169 | |||
170 | 1 | private function addDependentTestsToBatchSet(&$batches, $dependsOn, $tests) |
|
171 | { |
||
172 | 1 | foreach ($batches as $key => $batch) { |
|
173 | 1 | foreach ($batch as $methodName) { |
|
174 | 1 | if ($dependsOn === $methodName) { |
|
175 | 1 | $batches[$key] = array_merge($batches[$key], $tests); |
|
176 | 1 | continue; |
|
177 | } |
||
178 | } |
||
179 | } |
||
180 | 1 | } |
|
181 | |||
182 | 19 | private function addTestsToBatchSet(&$batches, $tests, $maxBatchSize) |
|
183 | { |
||
184 | 19 | foreach ($tests as $test) { |
|
185 | 19 | $lastIndex = count($batches) - 1; |
|
186 | 19 | if ($lastIndex != -1 |
|
187 | 19 | && count($batches[$lastIndex]) < $maxBatchSize |
|
188 | ) { |
||
189 | $batches[$lastIndex][] = $test; |
||
190 | } else { |
||
191 | 19 | $batches[] = [$test]; |
|
192 | } |
||
193 | } |
||
194 | 19 | } |
|
195 | |||
196 | /** |
||
197 | * Get method all available tests. |
||
198 | * |
||
199 | * With empty filter this method returns single test if doesnt' have data provider or |
||
200 | * data provider is not used and return all test if has data provider and data provider is used. |
||
201 | * |
||
202 | * @param ParsedClass $class Parsed class. |
||
203 | * @param ParsedObject $method Parsed method. |
||
204 | * @param bool $useDataProvider Try to use data provider or not. |
||
205 | * @return string[] Array of test names. |
||
206 | */ |
||
207 | 19 | private function getMethodTests($class, $method, $useDataProvider = false) |
|
208 | { |
||
209 | 19 | $result = []; |
|
210 | |||
211 | 19 | $groups = $this->methodGroups($method); |
|
212 | |||
213 | 19 | $dataProvider = $this->methodDataProvider($method); |
|
214 | 19 | if ($useDataProvider && isset($dataProvider)) { |
|
215 | $testFullClassName = "\\" . $class->getName(); |
||
216 | $testClass = new $testFullClassName(); |
||
217 | $result = []; |
||
218 | $datasetKeys = array_keys($testClass->$dataProvider()); |
||
219 | foreach ($datasetKeys as $key) { |
||
220 | $test = sprintf( |
||
221 | "%s with data set %s", |
||
222 | $method->getName(), |
||
223 | is_int($key) ? "#" . $key : "\"" . $key . "\"" |
||
224 | ); |
||
225 | if ($this->testMatchOptions($class->getName(), $test, $groups)) { |
||
226 | $result[] = $test; |
||
227 | } |
||
228 | } |
||
229 | 19 | } elseif ($this->testMatchOptions($class->getName(), $method->getName(), $groups)) { |
|
230 | 19 | $result = [$method->getName()]; |
|
231 | } |
||
232 | |||
233 | 19 | return $result; |
|
234 | } |
||
235 | |||
236 | 19 | private function testMatchGroupOptions($groups) |
|
237 | { |
||
238 | 19 | if (empty($groups)) { |
|
239 | 17 | return true; |
|
240 | } |
||
241 | |||
242 | 15 | if (!empty($this->options->groups) |
|
243 | 15 | && !array_intersect($groups, $this->options->groups) |
|
244 | ) { |
||
245 | return false; |
||
246 | } |
||
247 | |||
248 | 15 | if (!empty($this->options->excludeGroups) |
|
249 | 15 | && array_intersect($groups, $this->options->excludeGroups) |
|
250 | ) { |
||
251 | return false; |
||
252 | } |
||
253 | |||
254 | 15 | return true; |
|
255 | } |
||
256 | |||
257 | 19 | private function testMatchFilterOptions($className, $name, $group) |
|
258 | { |
||
259 | 19 | if (empty($this->options->filter)) { |
|
260 | 19 | return true; |
|
261 | } |
||
262 | |||
263 | $re = substr($this->options->filter, 0, 1) == "/" |
||
264 | ? $this->options->filter |
||
265 | : "/" . $this->options->filter . "/"; |
||
266 | $fullName = $className . "::" . $name; |
||
267 | $result = preg_match($re, $fullName); |
||
268 | |||
269 | return $result; |
||
270 | } |
||
271 | |||
272 | 19 | private function testMatchOptions($className, $name, $group) |
|
273 | { |
||
274 | 19 | $result = $this->testMatchGroupOptions($group) |
|
275 | 19 | && $this->testMatchFilterOptions($className, $name, $group); |
|
276 | |||
277 | 19 | return $result; |
|
278 | } |
||
279 | |||
280 | 19 | private function methodDataProvider($method) |
|
281 | { |
||
282 | 19 | if (preg_match("/@\bdataProvider\b \b(.*)\b/", $method->getDocBlock(), $matches)) { |
|
283 | return $matches[1]; |
||
284 | } |
||
285 | 19 | return null; |
|
286 | } |
||
287 | |||
288 | 19 | private function methodDependency($method) |
|
289 | { |
||
290 | 19 | if (preg_match("/@\bdepends\b \b(.*)\b/", $method->getDocBlock(), $matches)) { |
|
291 | 1 | return $matches[1]; |
|
292 | } |
||
293 | 19 | return null; |
|
294 | } |
||
295 | |||
296 | 19 | private function methodGroups($method) |
|
297 | { |
||
298 | 19 | if (preg_match_all("/@\bgroup\b \b(.*)\b/", $method->getDocBlock(), $matches)) { |
|
299 | 15 | return $matches[1]; |
|
300 | } |
||
301 | 17 | return []; |
|
302 | } |
||
303 | |||
304 | 19 | private function createSuite($path, ParsedClass $class) |
|
305 | { |
||
306 | 19 | return new Suite( |
|
307 | $path, |
||
308 | 19 | $this->executableTests( |
|
309 | $path, |
||
310 | $class |
||
311 | ), |
||
312 | 19 | $class->getName() |
|
313 | ); |
||
314 | } |
||
315 | } |
||
316 |