| Total Complexity | 126 |
| Total Lines | 875 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like Loader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Loader, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1); |
||
| 35 | final class Loader |
||
| 36 | { |
||
| 37 | public function load(string $filename): Configuration |
||
| 38 | { |
||
| 39 | $document = Xml::loadFile($filename, false, true, true); |
||
| 40 | $xpath = new \DOMXPath($document); |
||
| 41 | |||
| 42 | return new Configuration( |
||
| 43 | $filename, |
||
| 44 | $this->validate($document), |
||
| 45 | $this->extensions($filename, $xpath), |
||
| 46 | $this->filter($filename, $xpath), |
||
| 47 | $this->groups($xpath), |
||
| 48 | $this->testdoxGroups($xpath), |
||
| 49 | $this->listeners($filename, $xpath), |
||
| 50 | $this->logging($filename, $xpath), |
||
| 51 | $this->php($filename, $xpath), |
||
| 52 | $this->phpunit($filename, $document), |
||
| 53 | $this->testSuite($filename, $xpath) |
||
| 54 | ); |
||
| 55 | } |
||
| 56 | |||
| 57 | public function logging(string $filename, \DOMXPath $xpath): Logging |
||
| 58 | { |
||
| 59 | $codeCoverageClover = null; |
||
| 60 | $codeCoverageCrap4j = null; |
||
| 61 | $codeCoverageHtml = null; |
||
| 62 | $codeCoveragePhp = null; |
||
| 63 | $codeCoverageText = null; |
||
| 64 | $codeCoverageXml = null; |
||
| 65 | $junit = null; |
||
| 66 | $plainText = null; |
||
| 67 | $teamCity = null; |
||
| 68 | $testDoxHtml = null; |
||
| 69 | $testDoxText = null; |
||
| 70 | $testDoxXml = null; |
||
| 71 | |||
| 72 | foreach ($xpath->query('logging/log') as $log) { |
||
| 73 | \assert($log instanceof \DOMElement); |
||
| 74 | |||
| 75 | $type = (string) $log->getAttribute('type'); |
||
| 76 | $target = (string) $log->getAttribute('target'); |
||
| 77 | |||
| 78 | if (!$target) { |
||
| 79 | continue; |
||
| 80 | } |
||
| 81 | |||
| 82 | $target = $this->toAbsolutePath($filename, $target); |
||
| 83 | |||
| 84 | switch ($type) { |
||
| 85 | case 'coverage-clover': |
||
| 86 | $codeCoverageClover = new Clover( |
||
| 87 | new File($target) |
||
| 88 | ); |
||
| 89 | |||
| 90 | break; |
||
| 91 | |||
| 92 | case 'coverage-crap4j': |
||
| 93 | $codeCoverageCrap4j = new Crap4j( |
||
| 94 | new File($target), |
||
| 95 | $this->getIntegerAttribute($log, 'threshold', 30) |
||
| 96 | ); |
||
| 97 | |||
| 98 | break; |
||
| 99 | |||
| 100 | case 'coverage-html': |
||
| 101 | $codeCoverageHtml = new CodeCoverageHtml( |
||
| 102 | new Directory($target), |
||
| 103 | $this->getIntegerAttribute($log, 'lowUpperBound', 50), |
||
| 104 | $this->getIntegerAttribute($log, 'highLowerBound', 90) |
||
| 105 | ); |
||
| 106 | |||
| 107 | break; |
||
| 108 | |||
| 109 | case 'coverage-php': |
||
| 110 | $codeCoveragePhp = new CodeCoveragePhp( |
||
| 111 | new File($target) |
||
| 112 | ); |
||
| 113 | |||
| 114 | break; |
||
| 115 | |||
| 116 | case 'coverage-text': |
||
| 117 | $codeCoverageText = new CodeCoverageText( |
||
| 118 | new File($target), |
||
| 119 | $this->getBooleanAttribute($log, 'showUncoveredFiles', false), |
||
| 120 | $this->getBooleanAttribute($log, 'showOnlySummary', false) |
||
| 121 | ); |
||
| 122 | |||
| 123 | break; |
||
| 124 | |||
| 125 | case 'coverage-xml': |
||
| 126 | $codeCoverageXml = new CodeCoverageXml( |
||
| 127 | new Directory($target) |
||
| 128 | ); |
||
| 129 | |||
| 130 | break; |
||
| 131 | |||
| 132 | case 'plain': |
||
| 133 | $plainText = new PlainText( |
||
| 134 | new File($target) |
||
| 135 | ); |
||
| 136 | |||
| 137 | break; |
||
| 138 | |||
| 139 | case 'junit': |
||
| 140 | $junit = new Junit( |
||
| 141 | new File($target) |
||
| 142 | ); |
||
| 143 | |||
| 144 | break; |
||
| 145 | |||
| 146 | case 'teamcity': |
||
| 147 | $teamCity = new TeamCity( |
||
| 148 | new File($target) |
||
| 149 | ); |
||
| 150 | |||
| 151 | break; |
||
| 152 | |||
| 153 | case 'testdox-html': |
||
| 154 | $testDoxHtml = new TestDoxHtml( |
||
| 155 | new File($target) |
||
| 156 | ); |
||
| 157 | |||
| 158 | break; |
||
| 159 | |||
| 160 | case 'testdox-text': |
||
| 161 | $testDoxText = new TestDoxText( |
||
| 162 | new File($target) |
||
| 163 | ); |
||
| 164 | |||
| 165 | break; |
||
| 166 | |||
| 167 | case 'testdox-xml': |
||
| 168 | $testDoxXml = new TestDoxXml( |
||
| 169 | new File($target) |
||
| 170 | ); |
||
| 171 | |||
| 172 | break; |
||
| 173 | } |
||
| 174 | } |
||
| 175 | |||
| 176 | return new Logging( |
||
| 177 | $codeCoverageClover, |
||
| 178 | $codeCoverageCrap4j, |
||
| 179 | $codeCoverageHtml, |
||
| 180 | $codeCoveragePhp, |
||
| 181 | $codeCoverageText, |
||
| 182 | $codeCoverageXml, |
||
| 183 | $junit, |
||
| 184 | $plainText, |
||
| 185 | $teamCity, |
||
| 186 | $testDoxHtml, |
||
| 187 | $testDoxText, |
||
| 188 | $testDoxXml |
||
| 189 | ); |
||
| 190 | } |
||
| 191 | |||
| 192 | /** |
||
| 193 | * @psalm-return array<int,array<int,string>> |
||
| 194 | */ |
||
| 195 | private function validate(\DOMDocument $document): array |
||
| 196 | { |
||
| 197 | $original = \libxml_use_internal_errors(true); |
||
| 198 | $xsdFilename = __DIR__ . '/../../../phpunit.xsd'; |
||
| 199 | |||
| 200 | if (\defined('__PHPUNIT_PHAR_ROOT__')) { |
||
| 201 | $xsdFilename = __PHPUNIT_PHAR_ROOT__ . '/phpunit.xsd'; |
||
| 202 | } |
||
| 203 | |||
| 204 | $document->schemaValidate($xsdFilename); |
||
| 205 | $tmp = \libxml_get_errors(); |
||
| 206 | \libxml_clear_errors(); |
||
| 207 | \libxml_use_internal_errors($original); |
||
| 208 | |||
| 209 | $errors = []; |
||
| 210 | |||
| 211 | foreach ($tmp as $error) { |
||
| 212 | if (!isset($errors[$error->line])) { |
||
| 213 | $errors[$error->line] = []; |
||
| 214 | } |
||
| 215 | |||
| 216 | $errors[$error->line][] = \trim($error->message); |
||
| 217 | } |
||
| 218 | |||
| 219 | return $errors; |
||
| 220 | } |
||
| 221 | |||
| 222 | private function extensions(string $filename, \DOMXPath $xpath): ExtensionCollection |
||
| 223 | { |
||
| 224 | $extensions = []; |
||
| 225 | |||
| 226 | foreach ($xpath->query('extensions/extension') as $extension) { |
||
| 227 | \assert($extension instanceof \DOMElement); |
||
| 228 | |||
| 229 | $extensions[] = $this->getElementConfigurationParameters($filename, $extension); |
||
| 230 | } |
||
| 231 | |||
| 232 | return ExtensionCollection::fromArray($extensions); |
||
| 233 | } |
||
| 234 | |||
| 235 | private function getElementConfigurationParameters(string $filename, \DOMElement $element): Extension |
||
| 236 | { |
||
| 237 | /** @psalm-var class-string $class */ |
||
| 238 | $class = (string) $element->getAttribute('class'); |
||
| 239 | $file = ''; |
||
| 240 | $arguments = $this->getConfigurationArguments($filename, $element->childNodes); |
||
| 241 | |||
| 242 | if ($element->getAttribute('file')) { |
||
| 243 | $file = $this->toAbsolutePath( |
||
| 244 | $filename, |
||
| 245 | (string) $element->getAttribute('file'), |
||
| 246 | true |
||
| 247 | ); |
||
| 248 | } |
||
| 249 | |||
| 250 | return new Extension($class, $file, $arguments); |
||
| 251 | } |
||
| 252 | |||
| 253 | private function toAbsolutePath(string $filename, string $path, bool $useIncludePath = false): string |
||
| 254 | { |
||
| 255 | $path = \trim($path); |
||
| 256 | |||
| 257 | if (\strpos($path, '/') === 0) { |
||
| 258 | return $path; |
||
| 259 | } |
||
| 260 | |||
| 261 | // Matches the following on Windows: |
||
| 262 | // - \\NetworkComputer\Path |
||
| 263 | // - \\.\D: |
||
| 264 | // - \\.\c: |
||
| 265 | // - C:\Windows |
||
| 266 | // - C:\windows |
||
| 267 | // - C:/windows |
||
| 268 | // - c:/windows |
||
| 269 | if (\defined('PHP_WINDOWS_VERSION_BUILD') && |
||
| 270 | ($path[0] === '\\' || (\strlen($path) >= 3 && \preg_match('#^[A-Z]\:[/\\\]#i', \substr($path, 0, 3))))) { |
||
| 271 | return $path; |
||
| 272 | } |
||
| 273 | |||
| 274 | if (\strpos($path, '://') !== false) { |
||
| 275 | return $path; |
||
| 276 | } |
||
| 277 | |||
| 278 | $file = \dirname($filename) . \DIRECTORY_SEPARATOR . $path; |
||
| 279 | |||
| 280 | if ($useIncludePath && !\file_exists($file)) { |
||
| 281 | $includePathFile = \stream_resolve_include_path($path); |
||
| 282 | |||
| 283 | if ($includePathFile) { |
||
| 284 | $file = $includePathFile; |
||
| 285 | } |
||
| 286 | } |
||
| 287 | |||
| 288 | return $file; |
||
| 289 | } |
||
| 290 | |||
| 291 | private function getConfigurationArguments(string $filename, \DOMNodeList $nodes): array |
||
| 292 | { |
||
| 293 | $arguments = []; |
||
| 294 | |||
| 295 | if ($nodes->length === 0) { |
||
| 296 | return $arguments; |
||
| 297 | } |
||
| 298 | |||
| 299 | foreach ($nodes as $node) { |
||
| 300 | if (!$node instanceof \DOMElement) { |
||
| 301 | continue; |
||
| 302 | } |
||
| 303 | |||
| 304 | if ($node->tagName !== 'arguments') { |
||
| 305 | continue; |
||
| 306 | } |
||
| 307 | |||
| 308 | foreach ($node->childNodes as $argument) { |
||
| 309 | if (!$argument instanceof \DOMElement) { |
||
| 310 | continue; |
||
| 311 | } |
||
| 312 | |||
| 313 | if ($argument->tagName === 'file' || $argument->tagName === 'directory') { |
||
| 314 | $arguments[] = $this->toAbsolutePath($filename, (string) $argument->textContent); |
||
| 315 | } else { |
||
| 316 | $arguments[] = Xml::xmlToVariable($argument); |
||
| 317 | } |
||
| 318 | } |
||
| 319 | } |
||
| 320 | |||
| 321 | return $arguments; |
||
| 322 | } |
||
| 323 | |||
| 324 | private function filter(string $filename, \DOMXPath $xpath): Filter |
||
| 325 | { |
||
| 326 | $addUncoveredFilesFromWhitelist = true; |
||
| 327 | $processUncoveredFilesFromWhitelist = false; |
||
| 328 | |||
| 329 | $nodes = $xpath->query('filter/whitelist'); |
||
| 330 | |||
| 331 | if ($nodes->length === 1) { |
||
| 332 | $node = $nodes->item(0); |
||
| 333 | |||
| 334 | \assert($node instanceof \DOMElement); |
||
| 335 | |||
| 336 | if ($node->hasAttribute('addUncoveredFilesFromWhitelist')) { |
||
| 337 | $addUncoveredFilesFromWhitelist = (bool) $this->getBoolean( |
||
| 338 | (string) $node->getAttribute('addUncoveredFilesFromWhitelist'), |
||
| 339 | true |
||
| 340 | ); |
||
| 341 | } |
||
| 342 | |||
| 343 | if ($node->hasAttribute('processUncoveredFilesFromWhitelist')) { |
||
| 344 | $processUncoveredFilesFromWhitelist = (bool) $this->getBoolean( |
||
| 345 | (string) $node->getAttribute('processUncoveredFilesFromWhitelist'), |
||
| 346 | false |
||
| 347 | ); |
||
| 348 | } |
||
| 349 | } |
||
| 350 | |||
| 351 | return new Filter( |
||
| 352 | $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/directory'), |
||
| 353 | $this->readFilterFiles($filename, $xpath, 'filter/whitelist/file'), |
||
| 354 | $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/exclude/directory'), |
||
| 355 | $this->readFilterFiles($filename, $xpath, 'filter/whitelist/exclude/file'), |
||
| 356 | $addUncoveredFilesFromWhitelist, |
||
| 357 | $processUncoveredFilesFromWhitelist |
||
| 358 | ); |
||
| 359 | } |
||
| 360 | |||
| 361 | /** |
||
| 362 | * if $value is 'false' or 'true', this returns the value that $value represents. |
||
| 363 | * Otherwise, returns $default, which may be a string in rare cases. |
||
| 364 | * See PHPUnit\TextUI\ConfigurationTest::testPHPConfigurationIsReadCorrectly |
||
| 365 | * |
||
| 366 | * @param bool|string $default |
||
| 367 | * |
||
| 368 | * @return bool|string |
||
| 369 | */ |
||
| 370 | private function getBoolean(string $value, $default) |
||
| 371 | { |
||
| 372 | if (\strtolower($value) === 'false') { |
||
| 373 | return false; |
||
| 374 | } |
||
| 375 | |||
| 376 | if (\strtolower($value) === 'true') { |
||
| 377 | return true; |
||
| 378 | } |
||
| 379 | |||
| 380 | return $default; |
||
| 381 | } |
||
| 382 | |||
| 383 | private function readFilterDirectories(string $filename, \DOMXPath $xpath, string $query): FilterDirectoryCollection |
||
| 384 | { |
||
| 385 | $directories = []; |
||
| 386 | |||
| 387 | foreach ($xpath->query($query) as $directoryNode) { |
||
| 388 | \assert($directoryNode instanceof \DOMElement); |
||
| 389 | |||
| 390 | $directoryPath = (string) $directoryNode->textContent; |
||
| 391 | |||
| 392 | if (!$directoryPath) { |
||
| 393 | continue; |
||
| 394 | } |
||
| 395 | |||
| 396 | $directories[] = new FilterDirectory( |
||
| 397 | $this->toAbsolutePath($filename, $directoryPath), |
||
| 398 | $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '', |
||
| 399 | $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php', |
||
| 400 | $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT' |
||
| 401 | ); |
||
| 402 | } |
||
| 403 | |||
| 404 | return FilterDirectoryCollection::fromArray($directories); |
||
| 405 | } |
||
| 406 | |||
| 407 | private function readFilterFiles(string $filename, \DOMXPath $xpath, string $query): FilterFileCollection |
||
| 408 | { |
||
| 409 | $files = []; |
||
| 410 | |||
| 411 | foreach ($xpath->query($query) as $file) { |
||
| 412 | $filePath = (string) $file->textContent; |
||
| 413 | |||
| 414 | if ($filePath) { |
||
| 415 | $files[] = new FilterFile($this->toAbsolutePath($filename, $filePath)); |
||
| 416 | } |
||
| 417 | } |
||
| 418 | |||
| 419 | return FilterFileCollection::fromArray($files); |
||
| 420 | } |
||
| 421 | |||
| 422 | private function groups(\DOMXPath $xpath): Groups |
||
| 423 | { |
||
| 424 | return $this->parseGroupConfiguration($xpath, 'groups'); |
||
| 425 | } |
||
| 426 | |||
| 427 | private function testdoxGroups(\DOMXPath $xpath): Groups |
||
| 428 | { |
||
| 429 | return $this->parseGroupConfiguration($xpath, 'testdoxGroups'); |
||
| 430 | } |
||
| 431 | |||
| 432 | private function parseGroupConfiguration(\DOMXPath $xpath, string $root): Groups |
||
| 433 | { |
||
| 434 | $include = []; |
||
| 435 | $exclude = []; |
||
| 436 | |||
| 437 | foreach ($xpath->query($root . '/include/group') as $group) { |
||
| 438 | $include[] = new Group((string) $group->textContent); |
||
| 439 | } |
||
| 440 | |||
| 441 | foreach ($xpath->query($root . '/exclude/group') as $group) { |
||
| 442 | $exclude[] = new Group((string) $group->textContent); |
||
| 443 | } |
||
| 444 | |||
| 445 | return new Groups( |
||
| 446 | GroupCollection::fromArray($include), |
||
| 447 | GroupCollection::fromArray($exclude) |
||
| 448 | ); |
||
| 449 | } |
||
| 450 | |||
| 451 | private function listeners(string $filename, \DOMXPath $xpath): ExtensionCollection |
||
| 452 | { |
||
| 453 | $listeners = []; |
||
| 454 | |||
| 455 | foreach ($xpath->query('listeners/listener') as $listener) { |
||
| 456 | \assert($listener instanceof \DOMElement); |
||
| 457 | |||
| 458 | $listeners[] = $this->getElementConfigurationParameters($filename, $listener); |
||
| 459 | } |
||
| 460 | |||
| 461 | return ExtensionCollection::fromArray($listeners); |
||
| 462 | } |
||
| 463 | |||
| 464 | private function getBooleanAttribute(\DOMElement $element, string $attribute, bool $default): bool |
||
| 465 | { |
||
| 466 | if (!$element->hasAttribute($attribute)) { |
||
| 467 | return $default; |
||
| 468 | } |
||
| 469 | |||
| 470 | return (bool) $this->getBoolean( |
||
| 471 | (string) $element->getAttribute($attribute), |
||
| 472 | false |
||
| 473 | ); |
||
| 474 | } |
||
| 475 | |||
| 476 | private function getIntegerAttribute(\DOMElement $element, string $attribute, int $default): int |
||
| 477 | { |
||
| 478 | if (!$element->hasAttribute($attribute)) { |
||
| 479 | return $default; |
||
| 480 | } |
||
| 481 | |||
| 482 | return $this->getInteger( |
||
| 483 | (string) $element->getAttribute($attribute), |
||
| 484 | $default |
||
| 485 | ); |
||
| 486 | } |
||
| 487 | |||
| 488 | private function getStringAttribute(\DOMElement $element, string $attribute): ?string |
||
| 489 | { |
||
| 490 | if (!$element->hasAttribute($attribute)) { |
||
| 491 | return null; |
||
| 492 | } |
||
| 493 | |||
| 494 | return (string) $element->getAttribute($attribute); |
||
| 495 | } |
||
| 496 | |||
| 497 | private function getInteger(string $value, int $default): int |
||
| 498 | { |
||
| 499 | if (\is_numeric($value)) { |
||
| 500 | return (int) $value; |
||
| 501 | } |
||
| 502 | |||
| 503 | return $default; |
||
| 504 | } |
||
| 505 | |||
| 506 | private function php(string $filename, \DOMXPath $xpath): Php |
||
| 507 | { |
||
| 508 | $includePaths = []; |
||
| 509 | |||
| 510 | foreach ($xpath->query('php/includePath') as $includePath) { |
||
| 511 | $path = (string) $includePath->textContent; |
||
| 512 | |||
| 513 | if ($path) { |
||
| 514 | $includePaths[] = new Directory($this->toAbsolutePath($filename, $path)); |
||
| 515 | } |
||
| 516 | } |
||
| 517 | |||
| 518 | $iniSettings = []; |
||
| 519 | |||
| 520 | foreach ($xpath->query('php/ini') as $ini) { |
||
| 521 | \assert($ini instanceof \DOMElement); |
||
| 522 | |||
| 523 | $iniSettings[] = new IniSetting( |
||
| 524 | (string) $ini->getAttribute('name'), |
||
| 525 | (string) $ini->getAttribute('value') |
||
| 526 | ); |
||
| 527 | } |
||
| 528 | |||
| 529 | $constants = []; |
||
| 530 | |||
| 531 | foreach ($xpath->query('php/const') as $const) { |
||
| 532 | \assert($const instanceof \DOMElement); |
||
| 533 | |||
| 534 | $value = (string) $const->getAttribute('value'); |
||
| 535 | |||
| 536 | $constants[] = new Constant( |
||
| 537 | (string) $const->getAttribute('name'), |
||
| 538 | $this->getBoolean($value, $value) |
||
| 539 | ); |
||
| 540 | } |
||
| 541 | |||
| 542 | $variables = [ |
||
| 543 | 'var' => [], |
||
| 544 | 'env' => [], |
||
| 545 | 'post' => [], |
||
| 546 | 'get' => [], |
||
| 547 | 'cookie' => [], |
||
| 548 | 'server' => [], |
||
| 549 | 'files' => [], |
||
| 550 | 'request' => [], |
||
| 551 | ]; |
||
| 552 | |||
| 553 | foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { |
||
| 554 | foreach ($xpath->query('php/' . $array) as $var) { |
||
| 555 | \assert($var instanceof \DOMElement); |
||
| 556 | |||
| 557 | $name = (string) $var->getAttribute('name'); |
||
| 558 | $value = (string) $var->getAttribute('value'); |
||
| 559 | $force = false; |
||
| 560 | $verbatim = false; |
||
| 561 | |||
| 562 | if ($var->hasAttribute('force')) { |
||
| 563 | $force = (bool) $this->getBoolean($var->getAttribute('force'), false); |
||
| 564 | } |
||
| 565 | |||
| 566 | if ($var->hasAttribute('verbatim')) { |
||
| 567 | $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false); |
||
| 568 | } |
||
| 569 | |||
| 570 | if (!$verbatim) { |
||
| 571 | $value = $this->getBoolean($value, $value); |
||
| 572 | } |
||
| 573 | |||
| 574 | $variables[$array][] = new Variable($name, $value, $force); |
||
| 575 | } |
||
| 576 | } |
||
| 577 | |||
| 578 | return new Php( |
||
| 579 | DirectoryCollection::fromArray($includePaths), |
||
| 580 | IniSettingCollection::fromArray($iniSettings), |
||
| 581 | ConstantCollection::fromArray($constants), |
||
| 582 | VariableCollection::fromArray($variables['var']), |
||
| 583 | VariableCollection::fromArray($variables['env']), |
||
| 584 | VariableCollection::fromArray($variables['post']), |
||
| 585 | VariableCollection::fromArray($variables['get']), |
||
| 586 | VariableCollection::fromArray($variables['cookie']), |
||
| 587 | VariableCollection::fromArray($variables['server']), |
||
| 588 | VariableCollection::fromArray($variables['files']), |
||
| 589 | VariableCollection::fromArray($variables['request']), |
||
| 590 | ); |
||
| 591 | } |
||
| 592 | |||
| 593 | private function phpunit(string $filename, \DOMDocument $document): PHPUnit |
||
| 594 | { |
||
| 595 | $executionOrder = TestSuiteSorter::ORDER_DEFAULT; |
||
| 596 | $defectsFirst = false; |
||
| 597 | $resolveDependencies = $this->getBooleanAttribute($document->documentElement, 'resolveDependencies', true); |
||
| 598 | |||
| 599 | if ($document->documentElement->hasAttribute('executionOrder')) { |
||
| 600 | foreach (\explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) { |
||
| 601 | switch ($order) { |
||
| 602 | case 'default': |
||
| 603 | $executionOrder = TestSuiteSorter::ORDER_DEFAULT; |
||
| 604 | $defectsFirst = false; |
||
| 605 | $resolveDependencies = true; |
||
| 606 | |||
| 607 | break; |
||
| 608 | |||
| 609 | case 'depends': |
||
| 610 | $resolveDependencies = true; |
||
| 611 | |||
| 612 | break; |
||
| 613 | |||
| 614 | case 'no-depends': |
||
| 615 | $resolveDependencies = false; |
||
| 616 | |||
| 617 | break; |
||
| 618 | |||
| 619 | case 'defects': |
||
| 620 | $defectsFirst = true; |
||
| 621 | |||
| 622 | break; |
||
| 623 | |||
| 624 | case 'duration': |
||
| 625 | $executionOrder = TestSuiteSorter::ORDER_DURATION; |
||
| 626 | |||
| 627 | break; |
||
| 628 | |||
| 629 | case 'random': |
||
| 630 | $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; |
||
| 631 | |||
| 632 | break; |
||
| 633 | |||
| 634 | case 'reverse': |
||
| 635 | $executionOrder = TestSuiteSorter::ORDER_REVERSED; |
||
| 636 | |||
| 637 | break; |
||
| 638 | |||
| 639 | case 'size': |
||
| 640 | $executionOrder = TestSuiteSorter::ORDER_SIZE; |
||
| 641 | |||
| 642 | break; |
||
| 643 | } |
||
| 644 | } |
||
| 645 | } |
||
| 646 | |||
| 647 | $printerClass = $this->getStringAttribute($document->documentElement, 'printerClass'); |
||
| 648 | $testdox = $this->getBooleanAttribute($document->documentElement, 'testdox', false); |
||
| 649 | $conflictBetweenPrinterClassAndTestdox = false; |
||
| 650 | |||
| 651 | if ($testdox) { |
||
| 652 | if ($printerClass !== null) { |
||
| 653 | $conflictBetweenPrinterClassAndTestdox = true; |
||
| 654 | } |
||
| 655 | |||
| 656 | $printerClass = CliTestDoxPrinter::class; |
||
| 657 | } |
||
| 658 | |||
| 659 | $cacheResultFile = $this->getStringAttribute($document->documentElement, 'cacheResultFile'); |
||
| 660 | |||
| 661 | if ($cacheResultFile !== null) { |
||
| 662 | $cacheResultFile = $this->toAbsolutePath($filename, $cacheResultFile); |
||
| 663 | } |
||
| 664 | |||
| 665 | $bootstrap = $this->getStringAttribute($document->documentElement, 'bootstrap'); |
||
| 666 | |||
| 667 | if ($bootstrap !== null) { |
||
| 668 | $bootstrap = $this->toAbsolutePath($filename, $bootstrap); |
||
| 669 | } |
||
| 670 | |||
| 671 | $extensionsDirectory = $this->getStringAttribute($document->documentElement, 'extensionsDirectory'); |
||
| 672 | |||
| 673 | if ($extensionsDirectory !== null) { |
||
| 674 | $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory); |
||
| 675 | } |
||
| 676 | |||
| 677 | $testSuiteLoaderFile = $this->getStringAttribute($document->documentElement, 'testSuiteLoaderFile'); |
||
| 678 | |||
| 679 | if ($testSuiteLoaderFile !== null) { |
||
| 680 | $testSuiteLoaderFile = $this->toAbsolutePath($filename, $testSuiteLoaderFile); |
||
| 681 | } |
||
| 682 | |||
| 683 | $printerFile = $this->getStringAttribute($document->documentElement, 'printerFile'); |
||
| 684 | |||
| 685 | if ($printerFile !== null) { |
||
| 686 | $printerFile = $this->toAbsolutePath($filename, $printerFile); |
||
| 687 | } |
||
| 688 | |||
| 689 | return new PHPUnit( |
||
| 690 | $this->getBooleanAttribute($document->documentElement, 'cacheResult', false), |
||
| 691 | $cacheResultFile, |
||
| 692 | $this->getBooleanAttribute($document->documentElement, 'cacheTokens', false), |
||
| 693 | $this->getColumns($document), |
||
| 694 | $this->getColors($document), |
||
| 695 | $this->getBooleanAttribute($document->documentElement, 'stderr', false), |
||
| 696 | $this->getBooleanAttribute($document->documentElement, 'noInteraction', false), |
||
| 697 | $this->getBooleanAttribute($document->documentElement, 'verbose', false), |
||
| 698 | $this->getBooleanAttribute($document->documentElement, 'reverseDefectList', false), |
||
| 699 | $this->getBooleanAttribute($document->documentElement, 'convertDeprecationsToExceptions', true), |
||
| 700 | $this->getBooleanAttribute($document->documentElement, 'convertErrorsToExceptions', true), |
||
| 701 | $this->getBooleanAttribute($document->documentElement, 'convertNoticesToExceptions', true), |
||
| 702 | $this->getBooleanAttribute($document->documentElement, 'convertWarningsToExceptions', true), |
||
| 703 | $this->getBooleanAttribute($document->documentElement, 'forceCoversAnnotation', false), |
||
| 704 | $this->getBooleanAttribute($document->documentElement, 'ignoreDeprecatedCodeUnitsFromCodeCoverage', false), |
||
| 705 | $this->getBooleanAttribute($document->documentElement, 'disableCodeCoverageIgnore', false), |
||
| 706 | $bootstrap, |
||
| 707 | $this->getBooleanAttribute($document->documentElement, 'processIsolation', false), |
||
| 708 | $this->getBooleanAttribute($document->documentElement, 'failOnIncomplete', false), |
||
| 709 | $this->getBooleanAttribute($document->documentElement, 'failOnRisky', false), |
||
| 710 | $this->getBooleanAttribute($document->documentElement, 'failOnSkipped', false), |
||
| 711 | $this->getBooleanAttribute($document->documentElement, 'failOnWarning', false), |
||
| 712 | $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', false), |
||
| 713 | $this->getBooleanAttribute($document->documentElement, 'stopOnError', false), |
||
| 714 | $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', false), |
||
| 715 | $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', false), |
||
| 716 | $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', false), |
||
| 717 | $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', false), |
||
| 718 | $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', false), |
||
| 719 | $extensionsDirectory, |
||
| 720 | $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'), |
||
| 721 | $testSuiteLoaderFile, |
||
| 722 | $printerClass, |
||
| 723 | $printerFile, |
||
| 724 | $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false), |
||
| 725 | $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false), |
||
| 726 | $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', false), |
||
| 727 | $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true), |
||
| 728 | $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', false), |
||
| 729 | $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', false), |
||
| 730 | $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', false), |
||
| 731 | $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1), |
||
| 732 | $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1), |
||
| 733 | $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10), |
||
| 734 | $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60), |
||
| 735 | $this->getStringAttribute($document->documentElement, 'defaultTestSuite'), |
||
| 736 | $executionOrder, |
||
| 737 | $resolveDependencies, |
||
| 738 | $defectsFirst, |
||
| 739 | $this->getBooleanAttribute($document->documentElement, 'backupGlobals', false), |
||
| 740 | $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', false), |
||
| 741 | $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', false), |
||
| 742 | $conflictBetweenPrinterClassAndTestdox |
||
| 743 | ); |
||
| 744 | } |
||
| 745 | |||
| 746 | private function getColors(\DOMDocument $document): string |
||
| 747 | { |
||
| 748 | $colors = DefaultResultPrinter::COLOR_DEFAULT; |
||
| 749 | |||
| 750 | if ($document->documentElement->hasAttribute('colors')) { |
||
| 751 | /* only allow boolean for compatibility with previous versions |
||
| 752 | 'always' only allowed from command line */ |
||
| 753 | if ($this->getBoolean($document->documentElement->getAttribute('colors'), false)) { |
||
| 754 | $colors = DefaultResultPrinter::COLOR_AUTO; |
||
| 755 | } else { |
||
| 756 | $colors = DefaultResultPrinter::COLOR_NEVER; |
||
| 757 | } |
||
| 758 | } |
||
| 759 | |||
| 760 | return $colors; |
||
| 761 | } |
||
| 762 | |||
| 763 | /** |
||
| 764 | * @return int|string |
||
| 765 | */ |
||
| 766 | private function getColumns(\DOMDocument $document) |
||
| 767 | { |
||
| 768 | $columns = 80; |
||
| 769 | |||
| 770 | if ($document->documentElement->hasAttribute('columns')) { |
||
| 771 | $columns = (string) $document->documentElement->getAttribute('columns'); |
||
| 772 | |||
| 773 | if ($columns !== 'max') { |
||
| 774 | $columns = $this->getInteger($columns, 80); |
||
| 775 | } |
||
| 776 | } |
||
| 777 | |||
| 778 | return $columns; |
||
| 779 | } |
||
| 780 | |||
| 781 | private function testSuite(string $filename, \DOMXPath $xpath): TestSuiteCollection |
||
| 782 | { |
||
| 783 | $testSuites = []; |
||
| 784 | |||
| 785 | foreach ($this->getTestSuiteElements($xpath) as $element) { |
||
| 786 | $exclude = []; |
||
| 787 | |||
| 788 | foreach ($element->getElementsByTagName('exclude') as $excludeNode) { |
||
| 789 | $excludeFile = (string) $excludeNode->textContent; |
||
| 790 | |||
| 791 | if ($excludeFile) { |
||
| 792 | $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile)); |
||
| 793 | } |
||
| 794 | } |
||
| 795 | |||
| 796 | $directories = []; |
||
| 797 | |||
| 798 | foreach ($element->getElementsByTagName('directory') as $directoryNode) { |
||
| 799 | \assert($directoryNode instanceof \DOMElement); |
||
| 800 | |||
| 801 | $directory = (string) $directoryNode->textContent; |
||
| 802 | |||
| 803 | if (empty($directory)) { |
||
| 804 | continue; |
||
| 805 | } |
||
| 806 | |||
| 807 | $prefix = ''; |
||
| 808 | |||
| 809 | if ($directoryNode->hasAttribute('prefix')) { |
||
| 810 | $prefix = (string) $directoryNode->getAttribute('prefix'); |
||
| 811 | } |
||
| 812 | |||
| 813 | $suffix = 'Test.php'; |
||
| 814 | |||
| 815 | if ($directoryNode->hasAttribute('suffix')) { |
||
| 816 | $suffix = (string) $directoryNode->getAttribute('suffix'); |
||
| 817 | } |
||
| 818 | |||
| 819 | $phpVersion = \PHP_VERSION; |
||
| 820 | |||
| 821 | if ($directoryNode->hasAttribute('phpVersion')) { |
||
| 822 | $phpVersion = (string) $directoryNode->getAttribute('phpVersion'); |
||
| 823 | } |
||
| 824 | |||
| 825 | $phpVersionOperator = new VersionComparisonOperator('>='); |
||
| 826 | |||
| 827 | if ($directoryNode->hasAttribute('phpVersionOperator')) { |
||
| 828 | $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator')); |
||
| 829 | } |
||
| 830 | |||
| 831 | $directories[] = new TestDirectory( |
||
| 832 | $this->toAbsolutePath($filename, $directory), |
||
| 833 | $prefix, |
||
| 834 | $suffix, |
||
| 835 | $phpVersion, |
||
| 836 | $phpVersionOperator |
||
| 837 | ); |
||
| 838 | } |
||
| 839 | |||
| 840 | $files = []; |
||
| 841 | |||
| 842 | foreach ($element->getElementsByTagName('file') as $fileNode) { |
||
| 843 | \assert($fileNode instanceof \DOMElement); |
||
| 844 | |||
| 845 | $file = (string) $fileNode->textContent; |
||
| 846 | |||
| 847 | if (empty($file)) { |
||
| 848 | continue; |
||
| 849 | } |
||
| 850 | |||
| 851 | $phpVersion = \PHP_VERSION; |
||
| 852 | |||
| 853 | if ($fileNode->hasAttribute('phpVersion')) { |
||
| 854 | $phpVersion = (string) $fileNode->getAttribute('phpVersion'); |
||
| 855 | } |
||
| 856 | |||
| 857 | $phpVersionOperator = new VersionComparisonOperator('>='); |
||
| 858 | |||
| 859 | if ($fileNode->hasAttribute('phpVersionOperator')) { |
||
| 860 | $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator')); |
||
| 861 | } |
||
| 862 | |||
| 863 | $files[] = new TestFile( |
||
| 864 | $this->toAbsolutePath($filename, $file), |
||
| 865 | $phpVersion, |
||
| 866 | $phpVersionOperator |
||
| 867 | ); |
||
| 868 | } |
||
| 869 | |||
| 870 | $testSuites[] = new TestSuiteConfiguration( |
||
| 871 | (string) $element->getAttribute('name'), |
||
| 872 | TestDirectoryCollection::fromArray($directories), |
||
| 873 | TestFileCollection::fromArray($files), |
||
| 874 | FileCollection::fromArray($exclude) |
||
| 875 | ); |
||
| 876 | } |
||
| 877 | |||
| 878 | return TestSuiteCollection::fromArray($testSuites); |
||
| 879 | } |
||
| 880 | |||
| 881 | /** |
||
| 882 | * @return \DOMElement[] |
||
| 883 | */ |
||
| 884 | private function getTestSuiteElements(\DOMXPath $xpath): array |
||
| 885 | { |
||
| 886 | /** @var \DOMElement[] $elements */ |
||
| 887 | $elements = []; |
||
| 888 | |||
| 889 | $testSuiteNodes = $xpath->query('testsuites/testsuite'); |
||
| 890 | |||
| 891 | if ($testSuiteNodes->length === 0) { |
||
| 892 | $testSuiteNodes = $xpath->query('testsuite'); |
||
| 893 | } |
||
| 894 | |||
| 895 | if ($testSuiteNodes->length === 1) { |
||
| 896 | $element = $testSuiteNodes->item(0); |
||
| 897 | |||
| 898 | \assert($element instanceof \DOMElement); |
||
| 899 | |||
| 900 | $elements[] = $element; |
||
| 901 | } else { |
||
| 902 | foreach ($testSuiteNodes as $testSuiteNode) { |
||
| 903 | \assert($testSuiteNode instanceof \DOMElement); |
||
| 904 | |||
| 905 | $elements[] = $testSuiteNode; |
||
| 906 | } |
||
| 907 | } |
||
| 908 | |||
| 909 | return $elements; |
||
| 910 | } |
||
| 912 |