1<?php 2/* 3 * This file is part of PHPUnit. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11/** 12 * Test helpers. 13 */ 14class PHPUnit_Util_Test 15{ 16 const REGEX_DATA_PROVIDER = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/'; 17 const REGEX_TEST_WITH = '/@testWith\s+/'; 18 const REGEX_EXPECTED_EXCEPTION = '(@expectedException\s+([:.\w\\\\x7f-\xff]+)(?:[\t ]+(\S*))?(?:[\t ]+(\S*))?\s*$)m'; 19 const REGEX_REQUIRES_VERSION = '/@requires\s+(?P<name>PHP(?:Unit)?)\s+(?P<operator>[<>=!]{0,2})\s*(?P<version>[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m'; 20 const REGEX_REQUIRES_OS = '/@requires\s+OS\s+(?P<value>.+?)[ \t]*\r?$/m'; 21 const REGEX_REQUIRES = '/@requires\s+(?P<name>function|extension)\s+(?P<value>([^ ]+?))\s*(?P<operator>[<>=!]{0,2})\s*(?P<version>[\d\.-]+[\d\.]?)?[ \t]*\r?$/m'; 22 23 const UNKNOWN = -1; 24 const SMALL = 0; 25 const MEDIUM = 1; 26 const LARGE = 2; 27 28 private static $annotationCache = []; 29 30 private static $hookMethods = []; 31 32 /** 33 * @param PHPUnit_Framework_Test $test 34 * @param bool $asString 35 * 36 * @return mixed 37 */ 38 public static function describe(PHPUnit_Framework_Test $test, $asString = true) 39 { 40 if ($asString) { 41 if ($test instanceof PHPUnit_Framework_SelfDescribing) { 42 return $test->toString(); 43 } else { 44 return get_class($test); 45 } 46 } else { 47 if ($test instanceof PHPUnit_Framework_TestCase) { 48 return [ 49 get_class($test), $test->getName() 50 ]; 51 } elseif ($test instanceof PHPUnit_Framework_SelfDescribing) { 52 return ['', $test->toString()]; 53 } else { 54 return ['', get_class($test)]; 55 } 56 } 57 } 58 59 /** 60 * @param string $className 61 * @param string $methodName 62 * 63 * @return array|bool 64 * 65 * @throws PHPUnit_Framework_CodeCoverageException 66 */ 67 public static function getLinesToBeCovered($className, $methodName) 68 { 69 $annotations = self::parseTestMethodAnnotations( 70 $className, 71 $methodName 72 ); 73 74 if (isset($annotations['class']['coversNothing']) || isset($annotations['method']['coversNothing'])) { 75 return false; 76 } 77 78 return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); 79 } 80 81 /** 82 * Returns lines of code specified with the @uses annotation. 83 * 84 * @param string $className 85 * @param string $methodName 86 * 87 * @return array 88 */ 89 public static function getLinesToBeUsed($className, $methodName) 90 { 91 return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); 92 } 93 94 /** 95 * @param string $className 96 * @param string $methodName 97 * @param string $mode 98 * 99 * @return array 100 * 101 * @throws PHPUnit_Framework_CodeCoverageException 102 */ 103 private static function getLinesToBeCoveredOrUsed($className, $methodName, $mode) 104 { 105 $annotations = self::parseTestMethodAnnotations( 106 $className, 107 $methodName 108 ); 109 110 $classShortcut = null; 111 112 if (!empty($annotations['class'][$mode . 'DefaultClass'])) { 113 if (count($annotations['class'][$mode . 'DefaultClass']) > 1) { 114 throw new PHPUnit_Framework_CodeCoverageException( 115 sprintf( 116 'More than one @%sClass annotation in class or interface "%s".', 117 $mode, 118 $className 119 ) 120 ); 121 } 122 123 $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; 124 } 125 126 $list = []; 127 128 if (isset($annotations['class'][$mode])) { 129 $list = $annotations['class'][$mode]; 130 } 131 132 if (isset($annotations['method'][$mode])) { 133 $list = array_merge($list, $annotations['method'][$mode]); 134 } 135 136 $codeList = []; 137 138 foreach (array_unique($list) as $element) { 139 if ($classShortcut && strncmp($element, '::', 2) === 0) { 140 $element = $classShortcut . $element; 141 } 142 143 $element = preg_replace('/[\s()]+$/', '', $element); 144 $element = explode(' ', $element); 145 $element = $element[0]; 146 147 $codeList = array_merge( 148 $codeList, 149 self::resolveElementToReflectionObjects($element) 150 ); 151 } 152 153 return self::resolveReflectionObjectsToLines($codeList); 154 } 155 156 /** 157 * Returns the requirements for a test. 158 * 159 * @param string $className 160 * @param string $methodName 161 * 162 * @return array 163 */ 164 public static function getRequirements($className, $methodName) 165 { 166 $reflector = new ReflectionClass($className); 167 $docComment = $reflector->getDocComment(); 168 $reflector = new ReflectionMethod($className, $methodName); 169 $docComment .= "\n" . $reflector->getDocComment(); 170 $requires = []; 171 172 if ($count = preg_match_all(self::REGEX_REQUIRES_OS, $docComment, $matches)) { 173 $requires['OS'] = sprintf( 174 '/%s/i', 175 addcslashes($matches['value'][$count - 1], '/') 176 ); 177 } 178 if ($count = preg_match_all(self::REGEX_REQUIRES_VERSION, $docComment, $matches)) { 179 for ($i = 0; $i < $count; $i++) { 180 $requires[$matches['name'][$i]] = [ 181 'version' => $matches['version'][$i], 182 'operator' => $matches['operator'][$i] 183 ]; 184 } 185 } 186 187 // https://bugs.php.net/bug.php?id=63055 188 $matches = []; 189 190 if ($count = preg_match_all(self::REGEX_REQUIRES, $docComment, $matches)) { 191 for ($i = 0; $i < $count; $i++) { 192 $name = $matches['name'][$i] . 's'; 193 if (!isset($requires[$name])) { 194 $requires[$name] = []; 195 } 196 $requires[$name][] = $matches['value'][$i]; 197 if (empty($matches['version'][$i]) || $name != 'extensions') { 198 continue; 199 } 200 $requires['extension_versions'][$matches['value'][$i]] = [ 201 'version' => $matches['version'][$i], 202 'operator' => $matches['operator'][$i] 203 ]; 204 } 205 } 206 207 return $requires; 208 } 209 210 /** 211 * Returns the missing requirements for a test. 212 * 213 * @param string $className 214 * @param string $methodName 215 * 216 * @return array 217 */ 218 public static function getMissingRequirements($className, $methodName) 219 { 220 $required = static::getRequirements($className, $methodName); 221 $missing = []; 222 223 $operator = empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']; 224 if (!empty($required['PHP']) && !version_compare(PHP_VERSION, $required['PHP']['version'], $operator)) { 225 $missing[] = sprintf('PHP %s %s is required.', $operator, $required['PHP']['version']); 226 } 227 228 if (!empty($required['PHPUnit'])) { 229 $phpunitVersion = PHPUnit_Runner_Version::id(); 230 231 $operator = empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']; 232 if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator)) { 233 $missing[] = sprintf('PHPUnit %s %s is required.', $operator, $required['PHPUnit']['version']); 234 } 235 } 236 237 if (!empty($required['OS']) && !preg_match($required['OS'], PHP_OS)) { 238 $missing[] = sprintf('Operating system matching %s is required.', $required['OS']); 239 } 240 241 if (!empty($required['functions'])) { 242 foreach ($required['functions'] as $function) { 243 $pieces = explode('::', $function); 244 if (2 === count($pieces) && method_exists($pieces[0], $pieces[1])) { 245 continue; 246 } 247 if (function_exists($function)) { 248 continue; 249 } 250 $missing[] = sprintf('Function %s is required.', $function); 251 } 252 } 253 254 if (!empty($required['extensions'])) { 255 foreach ($required['extensions'] as $extension) { 256 if (isset($required['extension_versions'][$extension])) { 257 continue; 258 } 259 if (!extension_loaded($extension)) { 260 $missing[] = sprintf('Extension %s is required.', $extension); 261 } 262 } 263 } 264 265 if (!empty($required['extension_versions'])) { 266 foreach ($required['extension_versions'] as $extension => $required) { 267 $actualVersion = phpversion($extension); 268 269 $operator = empty($required['operator']) ? '>=' : $required['operator']; 270 if (false === $actualVersion || !version_compare($actualVersion, $required['version'], $operator)) { 271 $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator, $required['version']); 272 } 273 } 274 } 275 276 return $missing; 277 } 278 279 /** 280 * Returns the expected exception for a test. 281 * 282 * @param string $className 283 * @param string $methodName 284 * 285 * @return array 286 */ 287 public static function getExpectedException($className, $methodName) 288 { 289 $reflector = new ReflectionMethod($className, $methodName); 290 $docComment = $reflector->getDocComment(); 291 $docComment = substr($docComment, 3, -2); 292 293 if (preg_match(self::REGEX_EXPECTED_EXCEPTION, $docComment, $matches)) { 294 $annotations = self::parseTestMethodAnnotations( 295 $className, 296 $methodName 297 ); 298 299 $class = $matches[1]; 300 $code = null; 301 $message = ''; 302 $messageRegExp = ''; 303 304 if (isset($matches[2])) { 305 $message = trim($matches[2]); 306 } elseif (isset($annotations['method']['expectedExceptionMessage'])) { 307 $message = self::parseAnnotationContent( 308 $annotations['method']['expectedExceptionMessage'][0] 309 ); 310 } 311 312 if (isset($annotations['method']['expectedExceptionMessageRegExp'])) { 313 $messageRegExp = self::parseAnnotationContent( 314 $annotations['method']['expectedExceptionMessageRegExp'][0] 315 ); 316 } 317 318 if (isset($matches[3])) { 319 $code = $matches[3]; 320 } elseif (isset($annotations['method']['expectedExceptionCode'])) { 321 $code = self::parseAnnotationContent( 322 $annotations['method']['expectedExceptionCode'][0] 323 ); 324 } 325 326 if (is_numeric($code)) { 327 $code = (int) $code; 328 } elseif (is_string($code) && defined($code)) { 329 $code = (int) constant($code); 330 } 331 332 return [ 333 'class' => $class, 'code' => $code, 'message' => $message, 'message_regex' => $messageRegExp 334 ]; 335 } 336 337 return false; 338 } 339 340 /** 341 * Parse annotation content to use constant/class constant values 342 * 343 * Constants are specified using a starting '@'. For example: @ClassName::CONST_NAME 344 * 345 * If the constant is not found the string is used as is to ensure maximum BC. 346 * 347 * @param string $message 348 * 349 * @return string 350 */ 351 private static function parseAnnotationContent($message) 352 { 353 if (strpos($message, '::') !== false && count(explode('::', $message)) == 2) { 354 if (defined($message)) { 355 $message = constant($message); 356 } 357 } 358 359 return $message; 360 } 361 362 /** 363 * Returns the provided data for a method. 364 * 365 * @param string $className 366 * @param string $methodName 367 * 368 * @return array When a data provider is specified and exists 369 * null When no data provider is specified 370 * 371 * @throws PHPUnit_Framework_Exception 372 */ 373 public static function getProvidedData($className, $methodName) 374 { 375 $reflector = new ReflectionMethod($className, $methodName); 376 $docComment = $reflector->getDocComment(); 377 378 $data = self::getDataFromDataProviderAnnotation($docComment, $className, $methodName); 379 380 if ($data === null) { 381 $data = self::getDataFromTestWithAnnotation($docComment); 382 } 383 384 if (is_array($data) && empty($data)) { 385 throw new PHPUnit_Framework_SkippedTestError; 386 } 387 388 if ($data !== null) { 389 foreach ($data as $key => $value) { 390 if (!is_array($value)) { 391 throw new PHPUnit_Framework_Exception( 392 sprintf( 393 'Data set %s is invalid.', 394 is_int($key) ? '#' . $key : '"' . $key . '"' 395 ) 396 ); 397 } 398 } 399 } 400 401 return $data; 402 } 403 404 /** 405 * Returns the provided data for a method. 406 * 407 * @param string $docComment 408 * @param string $className 409 * @param string $methodName 410 * 411 * @return array|Iterator when a data provider is specified and exists 412 * null when no data provider is specified 413 * 414 * @throws PHPUnit_Framework_Exception 415 */ 416 private static function getDataFromDataProviderAnnotation($docComment, $className, $methodName) 417 { 418 if (preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { 419 $result = []; 420 421 foreach ($matches[1] as $match) { 422 $dataProviderMethodNameNamespace = explode('\\', $match); 423 $leaf = explode('::', array_pop($dataProviderMethodNameNamespace)); 424 $dataProviderMethodName = array_pop($leaf); 425 426 if (!empty($dataProviderMethodNameNamespace)) { 427 $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\'; 428 } else { 429 $dataProviderMethodNameNamespace = ''; 430 } 431 432 if (!empty($leaf)) { 433 $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf); 434 } else { 435 $dataProviderClassName = $className; 436 } 437 438 $dataProviderClass = new ReflectionClass($dataProviderClassName); 439 $dataProviderMethod = $dataProviderClass->getMethod( 440 $dataProviderMethodName 441 ); 442 443 if ($dataProviderMethod->isStatic()) { 444 $object = null; 445 } else { 446 $object = $dataProviderClass->newInstance(); 447 } 448 449 if ($dataProviderMethod->getNumberOfParameters() == 0) { 450 $data = $dataProviderMethod->invoke($object); 451 } else { 452 $data = $dataProviderMethod->invoke($object, $methodName); 453 } 454 455 if ($data instanceof Iterator) { 456 $data = iterator_to_array($data); 457 } 458 459 if (is_array($data)) { 460 $result = array_merge($result, $data); 461 } 462 } 463 464 return $result; 465 } 466 } 467 468 /** 469 * @param string $docComment full docComment string 470 * 471 * @return array when @testWith annotation is defined 472 * null when @testWith annotation is omitted 473 * 474 * @throws PHPUnit_Framework_Exception when @testWith annotation is defined but cannot be parsed 475 */ 476 public static function getDataFromTestWithAnnotation($docComment) 477 { 478 $docComment = self::cleanUpMultiLineAnnotation($docComment); 479 480 if (preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) { 481 $offset = strlen($matches[0][0]) + $matches[0][1]; 482 $annotationContent = substr($docComment, $offset); 483 $data = []; 484 485 foreach (explode("\n", $annotationContent) as $candidateRow) { 486 $candidateRow = trim($candidateRow); 487 488 if ($candidateRow[0] !== '[') { 489 break; 490 } 491 492 $dataSet = json_decode($candidateRow, true); 493 494 if (json_last_error() != JSON_ERROR_NONE) { 495 throw new PHPUnit_Framework_Exception( 496 'The dataset for the @testWith annotation cannot be parsed: ' . json_last_error_msg() 497 ); 498 } 499 500 $data[] = $dataSet; 501 } 502 503 if (!$data) { 504 throw new PHPUnit_Framework_Exception('The dataset for the @testWith annotation cannot be parsed.'); 505 } 506 507 return $data; 508 } 509 } 510 511 private static function cleanUpMultiLineAnnotation($docComment) 512 { 513 //removing initial ' * ' for docComment 514 $docComment = str_replace("\r\n", "\n", $docComment); 515 $docComment = preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', "\n", $docComment); 516 $docComment = substr($docComment, 0, -1); 517 $docComment = rtrim($docComment, "\n"); 518 519 return $docComment; 520 } 521 522 /** 523 * @param string $className 524 * @param string $methodName 525 * 526 * @return array 527 * 528 * @throws ReflectionException 529 */ 530 public static function parseTestMethodAnnotations($className, $methodName = '') 531 { 532 if (!isset(self::$annotationCache[$className])) { 533 $class = new ReflectionClass($className); 534 self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment()); 535 } 536 537 if (!empty($methodName) && !isset(self::$annotationCache[$className . '::' . $methodName])) { 538 try { 539 $method = new ReflectionMethod($className, $methodName); 540 $annotations = self::parseAnnotations($method->getDocComment()); 541 } catch (ReflectionException $e) { 542 $annotations = []; 543 } 544 self::$annotationCache[$className . '::' . $methodName] = $annotations; 545 } 546 547 return [ 548 'class' => self::$annotationCache[$className], 549 'method' => !empty($methodName) ? self::$annotationCache[$className . '::' . $methodName] : [] 550 ]; 551 } 552 553 /** 554 * @param string $className 555 * @param string $methodName 556 * 557 * @return array 558 */ 559 public static function getInlineAnnotations($className, $methodName) 560 { 561 $method = new ReflectionMethod($className, $methodName); 562 $code = file($method->getFileName()); 563 $lineNumber = $method->getStartLine(); 564 $startLine = $method->getStartLine() - 1; 565 $endLine = $method->getEndLine() - 1; 566 $methodLines = array_slice($code, $startLine, $endLine - $startLine + 1); 567 $annotations = []; 568 569 foreach ($methodLines as $line) { 570 if (preg_match('#/\*\*?\s*@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?\*/$#m', $line, $matches)) { 571 $annotations[strtolower($matches['name'])] = [ 572 'line' => $lineNumber, 573 'value' => $matches['value'] 574 ]; 575 } 576 577 $lineNumber++; 578 } 579 580 return $annotations; 581 } 582 583 /** 584 * @param string $docblock 585 * 586 * @return array 587 */ 588 private static function parseAnnotations($docblock) 589 { 590 $annotations = []; 591 // Strip away the docblock header and footer to ease parsing of one line annotations 592 $docblock = substr($docblock, 3, -2); 593 594 if (preg_match_all('/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m', $docblock, $matches)) { 595 $numMatches = count($matches[0]); 596 597 for ($i = 0; $i < $numMatches; ++$i) { 598 $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i]; 599 } 600 } 601 602 return $annotations; 603 } 604 605 /** 606 * Returns the backup settings for a test. 607 * 608 * @param string $className 609 * @param string $methodName 610 * 611 * @return array 612 */ 613 public static function getBackupSettings($className, $methodName) 614 { 615 return [ 616 'backupGlobals' => self::getBooleanAnnotationSetting( 617 $className, 618 $methodName, 619 'backupGlobals' 620 ), 621 'backupStaticAttributes' => self::getBooleanAnnotationSetting( 622 $className, 623 $methodName, 624 'backupStaticAttributes' 625 ) 626 ]; 627 } 628 629 /** 630 * Returns the dependencies for a test class or method. 631 * 632 * @param string $className 633 * @param string $methodName 634 * 635 * @return array 636 */ 637 public static function getDependencies($className, $methodName) 638 { 639 $annotations = self::parseTestMethodAnnotations( 640 $className, 641 $methodName 642 ); 643 644 $dependencies = []; 645 646 if (isset($annotations['class']['depends'])) { 647 $dependencies = $annotations['class']['depends']; 648 } 649 650 if (isset($annotations['method']['depends'])) { 651 $dependencies = array_merge( 652 $dependencies, 653 $annotations['method']['depends'] 654 ); 655 } 656 657 return array_unique($dependencies); 658 } 659 660 /** 661 * Returns the error handler settings for a test. 662 * 663 * @param string $className 664 * @param string $methodName 665 * 666 * @return bool 667 */ 668 public static function getErrorHandlerSettings($className, $methodName) 669 { 670 return self::getBooleanAnnotationSetting( 671 $className, 672 $methodName, 673 'errorHandler' 674 ); 675 } 676 677 /** 678 * Returns the groups for a test class or method. 679 * 680 * @param string $className 681 * @param string $methodName 682 * 683 * @return array 684 */ 685 public static function getGroups($className, $methodName = '') 686 { 687 $annotations = self::parseTestMethodAnnotations( 688 $className, 689 $methodName 690 ); 691 692 $groups = []; 693 694 if (isset($annotations['method']['author'])) { 695 $groups = $annotations['method']['author']; 696 } elseif (isset($annotations['class']['author'])) { 697 $groups = $annotations['class']['author']; 698 } 699 700 if (isset($annotations['class']['group'])) { 701 $groups = array_merge($groups, $annotations['class']['group']); 702 } 703 704 if (isset($annotations['method']['group'])) { 705 $groups = array_merge($groups, $annotations['method']['group']); 706 } 707 708 if (isset($annotations['class']['ticket'])) { 709 $groups = array_merge($groups, $annotations['class']['ticket']); 710 } 711 712 if (isset($annotations['method']['ticket'])) { 713 $groups = array_merge($groups, $annotations['method']['ticket']); 714 } 715 716 foreach (['method', 'class'] as $element) { 717 foreach (['small', 'medium', 'large'] as $size) { 718 if (isset($annotations[$element][$size])) { 719 $groups[] = $size; 720 break 2; 721 } 722 } 723 } 724 725 return array_unique($groups); 726 } 727 728 /** 729 * Returns the size of the test. 730 * 731 * @param string $className 732 * @param string $methodName 733 * 734 * @return int 735 */ 736 public static function getSize($className, $methodName) 737 { 738 $groups = array_flip(self::getGroups($className, $methodName)); 739 $size = self::UNKNOWN; 740 $class = new ReflectionClass($className); 741 742 if (isset($groups['large']) || 743 (class_exists('PHPUnit_Extensions_Database_TestCase', false) && 744 $class->isSubclassOf('PHPUnit_Extensions_Database_TestCase'))) { 745 $size = self::LARGE; 746 } elseif (isset($groups['medium'])) { 747 $size = self::MEDIUM; 748 } elseif (isset($groups['small'])) { 749 $size = self::SMALL; 750 } 751 752 return $size; 753 } 754 755 /** 756 * Returns the tickets for a test class or method. 757 * 758 * @param string $className 759 * @param string $methodName 760 * 761 * @return array 762 */ 763 public static function getTickets($className, $methodName) 764 { 765 $annotations = self::parseTestMethodAnnotations( 766 $className, 767 $methodName 768 ); 769 770 $tickets = []; 771 772 if (isset($annotations['class']['ticket'])) { 773 $tickets = $annotations['class']['ticket']; 774 } 775 776 if (isset($annotations['method']['ticket'])) { 777 $tickets = array_merge($tickets, $annotations['method']['ticket']); 778 } 779 780 return array_unique($tickets); 781 } 782 783 /** 784 * Returns the process isolation settings for a test. 785 * 786 * @param string $className 787 * @param string $methodName 788 * 789 * @return bool 790 */ 791 public static function getProcessIsolationSettings($className, $methodName) 792 { 793 $annotations = self::parseTestMethodAnnotations( 794 $className, 795 $methodName 796 ); 797 798 if (isset($annotations['class']['runTestsInSeparateProcesses']) || 799 isset($annotations['method']['runInSeparateProcess'])) { 800 return true; 801 } else { 802 return false; 803 } 804 } 805 806 /** 807 * Returns the preserve global state settings for a test. 808 * 809 * @param string $className 810 * @param string $methodName 811 * 812 * @return bool 813 */ 814 public static function getPreserveGlobalStateSettings($className, $methodName) 815 { 816 return self::getBooleanAnnotationSetting( 817 $className, 818 $methodName, 819 'preserveGlobalState' 820 ); 821 } 822 823 /** 824 * @param string $className 825 * 826 * @return array 827 */ 828 public static function getHookMethods($className) 829 { 830 if (!class_exists($className, false)) { 831 return self::emptyHookMethodsArray(); 832 } 833 834 if (!isset(self::$hookMethods[$className])) { 835 self::$hookMethods[$className] = self::emptyHookMethodsArray(); 836 837 try { 838 $class = new ReflectionClass($className); 839 840 foreach ($class->getMethods() as $method) { 841 if (self::isBeforeClassMethod($method)) { 842 self::$hookMethods[$className]['beforeClass'][] = $method->getName(); 843 } 844 845 if (self::isBeforeMethod($method)) { 846 self::$hookMethods[$className]['before'][] = $method->getName(); 847 } 848 849 if (self::isAfterMethod($method)) { 850 self::$hookMethods[$className]['after'][] = $method->getName(); 851 } 852 853 if (self::isAfterClassMethod($method)) { 854 self::$hookMethods[$className]['afterClass'][] = $method->getName(); 855 } 856 } 857 } catch (ReflectionException $e) { 858 } 859 } 860 861 return self::$hookMethods[$className]; 862 } 863 864 /** 865 * @return array 866 */ 867 private static function emptyHookMethodsArray() 868 { 869 return [ 870 'beforeClass' => ['setUpBeforeClass'], 871 'before' => ['setUp'], 872 'after' => ['tearDown'], 873 'afterClass' => ['tearDownAfterClass'] 874 ]; 875 } 876 877 /** 878 * @param string $className 879 * @param string $methodName 880 * @param string $settingName 881 * 882 * @return bool 883 */ 884 private static function getBooleanAnnotationSetting($className, $methodName, $settingName) 885 { 886 $annotations = self::parseTestMethodAnnotations( 887 $className, 888 $methodName 889 ); 890 891 $result = null; 892 893 if (isset($annotations['class'][$settingName])) { 894 if ($annotations['class'][$settingName][0] == 'enabled') { 895 $result = true; 896 } elseif ($annotations['class'][$settingName][0] == 'disabled') { 897 $result = false; 898 } 899 } 900 901 if (isset($annotations['method'][$settingName])) { 902 if ($annotations['method'][$settingName][0] == 'enabled') { 903 $result = true; 904 } elseif ($annotations['method'][$settingName][0] == 'disabled') { 905 $result = false; 906 } 907 } 908 909 return $result; 910 } 911 912 /** 913 * @param string $element 914 * 915 * @return array 916 * 917 * @throws PHPUnit_Framework_InvalidCoversTargetException 918 */ 919 private static function resolveElementToReflectionObjects($element) 920 { 921 $codeToCoverList = []; 922 923 if (strpos($element, '\\') !== false && function_exists($element)) { 924 $codeToCoverList[] = new ReflectionFunction($element); 925 } elseif (strpos($element, '::') !== false) { 926 list($className, $methodName) = explode('::', $element); 927 928 if (isset($methodName[0]) && $methodName[0] == '<') { 929 $classes = [$className]; 930 931 foreach ($classes as $className) { 932 if (!class_exists($className) && 933 !interface_exists($className) && 934 !trait_exists($className)) { 935 throw new PHPUnit_Framework_InvalidCoversTargetException( 936 sprintf( 937 'Trying to @cover or @use not existing class or ' . 938 'interface "%s".', 939 $className 940 ) 941 ); 942 } 943 944 $class = new ReflectionClass($className); 945 $methods = $class->getMethods(); 946 $inverse = isset($methodName[1]) && $methodName[1] == '!'; 947 948 if (strpos($methodName, 'protected')) { 949 $visibility = 'isProtected'; 950 } elseif (strpos($methodName, 'private')) { 951 $visibility = 'isPrivate'; 952 } elseif (strpos($methodName, 'public')) { 953 $visibility = 'isPublic'; 954 } 955 956 foreach ($methods as $method) { 957 if ($inverse && !$method->$visibility()) { 958 $codeToCoverList[] = $method; 959 } elseif (!$inverse && $method->$visibility()) { 960 $codeToCoverList[] = $method; 961 } 962 } 963 } 964 } else { 965 $classes = [$className]; 966 967 foreach ($classes as $className) { 968 if ($className == '' && function_exists($methodName)) { 969 $codeToCoverList[] = new ReflectionFunction( 970 $methodName 971 ); 972 } else { 973 if (!((class_exists($className) || 974 interface_exists($className) || 975 trait_exists($className)) && 976 method_exists($className, $methodName))) { 977 throw new PHPUnit_Framework_InvalidCoversTargetException( 978 sprintf( 979 'Trying to @cover or @use not existing method "%s::%s".', 980 $className, 981 $methodName 982 ) 983 ); 984 } 985 986 $codeToCoverList[] = new ReflectionMethod( 987 $className, 988 $methodName 989 ); 990 } 991 } 992 } 993 } else { 994 $extended = false; 995 996 if (strpos($element, '<extended>') !== false) { 997 $element = str_replace('<extended>', '', $element); 998 $extended = true; 999 } 1000 1001 $classes = [$element]; 1002 1003 if ($extended) { 1004 $classes = array_merge( 1005 $classes, 1006 class_implements($element), 1007 class_parents($element) 1008 ); 1009 } 1010 1011 foreach ($classes as $className) { 1012 if (!class_exists($className) && 1013 !interface_exists($className) && 1014 !trait_exists($className)) { 1015 throw new PHPUnit_Framework_InvalidCoversTargetException( 1016 sprintf( 1017 'Trying to @cover or @use not existing class or ' . 1018 'interface "%s".', 1019 $className 1020 ) 1021 ); 1022 } 1023 1024 $codeToCoverList[] = new ReflectionClass($className); 1025 } 1026 } 1027 1028 return $codeToCoverList; 1029 } 1030 1031 /** 1032 * @param array $reflectors 1033 * 1034 * @return array 1035 */ 1036 private static function resolveReflectionObjectsToLines(array $reflectors) 1037 { 1038 $result = []; 1039 1040 foreach ($reflectors as $reflector) { 1041 $filename = $reflector->getFileName(); 1042 1043 if (!isset($result[$filename])) { 1044 $result[$filename] = []; 1045 } 1046 1047 $result[$filename] = array_merge( 1048 $result[$filename], 1049 range($reflector->getStartLine(), $reflector->getEndLine()) 1050 ); 1051 } 1052 1053 foreach ($result as $filename => $lineNumbers) { 1054 $result[$filename] = array_keys(array_flip($lineNumbers)); 1055 } 1056 1057 return $result; 1058 } 1059 1060 /** 1061 * @param ReflectionMethod $method 1062 * 1063 * @return bool 1064 */ 1065 private static function isBeforeClassMethod(ReflectionMethod $method) 1066 { 1067 return $method->isStatic() && strpos($method->getDocComment(), '@beforeClass') !== false; 1068 } 1069 1070 /** 1071 * @param ReflectionMethod $method 1072 * 1073 * @return bool 1074 */ 1075 private static function isBeforeMethod(ReflectionMethod $method) 1076 { 1077 return preg_match('/@before\b/', $method->getDocComment()); 1078 } 1079 1080 /** 1081 * @param ReflectionMethod $method 1082 * 1083 * @return bool 1084 */ 1085 private static function isAfterClassMethod(ReflectionMethod $method) 1086 { 1087 return $method->isStatic() && strpos($method->getDocComment(), '@afterClass') !== false; 1088 } 1089 1090 /** 1091 * @param ReflectionMethod $method 1092 * 1093 * @return bool 1094 */ 1095 private static function isAfterMethod(ReflectionMethod $method) 1096 { 1097 return preg_match('/@after\b/', $method->getDocComment()); 1098 } 1099} 1100