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 /** 18 * PHPUnit autoloader for Moodle. 19 * 20 * @package core 21 * @category phpunit 22 * @copyright 2013 Petr Skoda {@link http://skodak.org} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 /** 27 * Class phpunit_autoloader. 28 * 29 * Please notice that phpunit testcases obey frankenstyle naming rules, 30 * that is full component prefix + _testcase postfix. The files are expected 31 * in tests directory inside each component. There are some extra tests 32 * directories which require both classname and file path. 33 * 34 * Examples: 35 * 36 * vendor/bin/phpunit core_component_testcase 37 * vendor/bin/phpunit lib/tests/component_test.php 38 * vendor/bin/phpunit core_component_testcase lib/tests/component_test.php 39 * 40 * @package core 41 * @category phpunit 42 * @copyright 2013 Petr Skoda {@link http://skodak.org} 43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 */ 45 class phpunit_autoloader implements \PHPUnit\Runner\TestSuiteLoader { 46 public function load(string $suiteClassName, string $suiteClassFile = ''): ReflectionClass { 47 global $CFG; 48 49 // Let's guess what user entered on the commandline... 50 if ($suiteClassFile) { 51 // This means they either entered the class+path or path only. 52 if (strpos($suiteClassName, '/') !== false) { 53 // Class names can not contain slashes, 54 // user entered only path without testcase class name. 55 return $this->guess_class_from_path($suiteClassFile); 56 } 57 if (strpos($suiteClassName, '\\') !== false and strpos($suiteClassFile, $suiteClassName.'.php') !== false) { 58 // This must be backslashed windows path. 59 return $this->guess_class_from_path($suiteClassFile); 60 } 61 } 62 63 if (class_exists($suiteClassName, false)) { 64 $class = new ReflectionClass($suiteClassName); 65 return $class; 66 } 67 68 if ($suiteClassFile) { 69 PHPUnit\Util\Fileloader::checkAndLoad($suiteClassFile); 70 if (class_exists($suiteClassName, false)) { 71 $class = new ReflectionClass($suiteClassName); 72 return $class; 73 } 74 75 throw new PHPUnit\Framework\Exception( 76 sprintf("Class '%s' could not be found in '%s'.", $suiteClassName, $suiteClassFile) 77 ); 78 } 79 80 /* 81 * Try standard testcase naming rules based on frankenstyle component: 82 * 1/ test classes should use standard frankenstyle class names plus suffix "_testcase" 83 * 2/ test classes should be stored in files with suffix "_test" 84 */ 85 86 $parts = explode('_', $suiteClassName); 87 $suffix = end($parts); 88 $component = ''; 89 90 if ($suffix === 'testcase') { 91 unset($parts[key($parts)]); 92 while($parts) { 93 if (!$component) { 94 $component = array_shift($parts); 95 } else { 96 $component = $component . '_' . array_shift($parts); 97 } 98 // Try standard plugin and core subsystem locations. 99 if ($fulldir = core_component::get_component_directory($component)) { 100 $testfile = implode('_', $parts); 101 $fullpath = "{$fulldir}/tests/{$testfile}_test.php"; 102 if (is_readable($fullpath)) { 103 include_once($fullpath); 104 if (class_exists($suiteClassName, false)) { 105 $class = new ReflectionClass($suiteClassName); 106 return $class; 107 } 108 } 109 } 110 } 111 // The last option is testsuite directories in main phpunit.xml file. 112 $xmlfile = "$CFG->dirroot/phpunit.xml"; 113 if (is_readable($xmlfile) and $xml = file_get_contents($xmlfile)) { 114 $dom = new DOMDocument(); 115 $dom->loadXML($xml); 116 $nodes = $dom->getElementsByTagName('testsuite'); 117 foreach ($nodes as $node) { 118 /** @var DOMNode $node */ 119 $suitename = trim($node->attributes->getNamedItem('name')->nodeValue); 120 if (strpos($suitename, 'core') !== 0 or strpos($suitename, ' ') !== false) { 121 continue; 122 } 123 // This is a nasty hack: testsuit names are sometimes used as prefix for testcases 124 // in non-standard core subsystem locations. 125 if (strpos($suiteClassName, $suitename) !== 0) { 126 continue; 127 } 128 foreach ($node->childNodes as $dirnode) { 129 /** @var DOMNode $dirnode */ 130 $dir = trim($dirnode->textContent); 131 if (!$dir) { 132 continue; 133 } 134 $dir = $CFG->dirroot.'/'.$dir; 135 $parts = explode('_', $suitename); 136 $prefix = ''; 137 while ($parts) { 138 if ($prefix) { 139 $prefix = $prefix.'_'.array_shift($parts); 140 } else { 141 $prefix = array_shift($parts); 142 } 143 $filename = substr($suiteClassName, strlen($prefix)+1); 144 $filename = preg_replace('/testcase$/', 'test', $filename); 145 if (is_readable("$dir/$filename.php")) { 146 include_once("$dir/$filename.php"); 147 if (class_exists($suiteClassName, false)) { 148 $class = new ReflectionClass($suiteClassName); 149 return $class; 150 } 151 } 152 } 153 } 154 } 155 } 156 } 157 158 throw new PHPUnit\Framework\Exception( 159 sprintf("Class '%s' could not be found in '%s'.", $suiteClassName, $suiteClassFile) 160 ); 161 } 162 163 protected function guess_class_from_path($file) { 164 // Somebody is using just the file name, we need to look inside the file and guess the testcase 165 // class name. Let's throw fatal error if there are more testcases in one file. 166 167 $classes = get_declared_classes(); 168 PHPUnit\Util\Fileloader::checkAndLoad($file); 169 $includePathFilename = stream_resolve_include_path($file); 170 $loadedClasses = array_diff(get_declared_classes(), $classes); 171 172 $candidates = array(); 173 174 foreach ($loadedClasses as $loadedClass) { 175 $class = new ReflectionClass($loadedClass); 176 177 if ($class->isSubclassOf('PHPUnit\Framework\TestCase') and !$class->isAbstract()) { 178 if (realpath($includePathFilename) === realpath($class->getFileName())) { 179 $candidates[] = $loadedClass; 180 } 181 } 182 } 183 184 if (count($candidates) == 0) { 185 throw new PHPUnit\Framework\Exception( 186 sprintf("File '%s' does not contain any test cases.", $file) 187 ); 188 } 189 190 if (count($candidates) > 1) { 191 throw new PHPUnit\Framework\Exception( 192 sprintf("File '%s' contains multiple test cases: ".implode(', ', $candidates), $file) 193 ); 194 } 195 196 $classname = reset($candidates); 197 return new ReflectionClass($classname); 198 } 199 200 public function reload(ReflectionClass $aClass): ReflectionClass { 201 return $aClass; 202 } 203 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body