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 * Runner for PHPT test cases. 13 */ 14class PHPUnit_Extensions_PhptTestCase implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing 15{ 16 /** 17 * @var string 18 */ 19 private $filename; 20 21 /** 22 * @var PHPUnit_Util_PHP 23 */ 24 private $phpUtil; 25 26 /** 27 * @var array 28 */ 29 private $settings = [ 30 'allow_url_fopen=1', 31 'auto_append_file=', 32 'auto_prepend_file=', 33 'disable_functions=', 34 'display_errors=1', 35 'docref_root=', 36 'docref_ext=.html', 37 'error_append_string=', 38 'error_prepend_string=', 39 'error_reporting=-1', 40 'html_errors=0', 41 'log_errors=0', 42 'magic_quotes_runtime=0', 43 'output_handler=', 44 'open_basedir=', 45 'output_buffering=Off', 46 'report_memleaks=0', 47 'report_zend_debug=0', 48 'safe_mode=0', 49 'xdebug.default_enable=0' 50 ]; 51 52 /** 53 * Constructs a test case with the given filename. 54 * 55 * @param string $filename 56 * @param PHPUnit_Util_PHP $phpUtil 57 * 58 * @throws PHPUnit_Framework_Exception 59 */ 60 public function __construct($filename, $phpUtil = null) 61 { 62 if (!is_string($filename)) { 63 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); 64 } 65 66 if (!is_file($filename)) { 67 throw new PHPUnit_Framework_Exception( 68 sprintf( 69 'File "%s" does not exist.', 70 $filename 71 ) 72 ); 73 } 74 75 $this->filename = $filename; 76 $this->phpUtil = $phpUtil ?: PHPUnit_Util_PHP::factory(); 77 } 78 79 /** 80 * Counts the number of test cases executed by run(TestResult result). 81 * 82 * @return int 83 */ 84 public function count() 85 { 86 return 1; 87 } 88 89 /** 90 * @param array $sections 91 * @param string $output 92 */ 93 private function assertPhptExpectation(array $sections, $output) 94 { 95 $assertions = [ 96 'EXPECT' => 'assertEquals', 97 'EXPECTF' => 'assertStringMatchesFormat', 98 'EXPECTREGEX' => 'assertRegExp', 99 ]; 100 101 $actual = preg_replace('/\r\n/', "\n", trim($output)); 102 103 foreach ($assertions as $sectionName => $sectionAssertion) { 104 if (isset($sections[$sectionName])) { 105 $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName])); 106 $assertion = $sectionAssertion; 107 $expected = $sectionName == 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; 108 109 break; 110 } 111 } 112 113 PHPUnit_Framework_Assert::$assertion($expected, $actual); 114 } 115 116 /** 117 * Runs a test and collects its result in a TestResult instance. 118 * 119 * @param PHPUnit_Framework_TestResult $result 120 * 121 * @return PHPUnit_Framework_TestResult 122 */ 123 public function run(PHPUnit_Framework_TestResult $result = null) 124 { 125 $sections = $this->parse(); 126 $code = $this->render($sections['FILE']); 127 128 if ($result === null) { 129 $result = new PHPUnit_Framework_TestResult; 130 } 131 132 $skip = false; 133 $xfail = false; 134 $time = 0; 135 $settings = $this->settings; 136 137 $result->startTest($this); 138 139 if (isset($sections['INI'])) { 140 $settings = array_merge($settings, $this->parseIniSection($sections['INI'])); 141 } 142 143 if (isset($sections['ENV'])) { 144 $env = $this->parseEnvSection($sections['ENV']); 145 $this->phpUtil->setEnv($env); 146 } 147 148 // Redirects STDERR to STDOUT 149 $this->phpUtil->setUseStderrRedirection(true); 150 151 if ($result->enforcesTimeLimit()) { 152 $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); 153 } 154 155 if (isset($sections['SKIPIF'])) { 156 $jobResult = $this->phpUtil->runJob($sections['SKIPIF'], $settings); 157 158 if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { 159 if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $message)) { 160 $message = substr($message[1], 2); 161 } else { 162 $message = ''; 163 } 164 165 $result->addFailure($this, new PHPUnit_Framework_SkippedTestError($message), 0); 166 167 $skip = true; 168 } 169 } 170 171 if (isset($sections['XFAIL'])) { 172 $xfail = trim($sections['XFAIL']); 173 } 174 175 if (!$skip) { 176 if (isset($sections['STDIN'])) { 177 $this->phpUtil->setStdin($sections['STDIN']); 178 } 179 180 if (isset($sections['ARGS'])) { 181 $this->phpUtil->setArgs($sections['ARGS']); 182 } 183 184 PHP_Timer::start(); 185 186 $jobResult = $this->phpUtil->runJob($code, $settings); 187 $time = PHP_Timer::stop(); 188 189 try { 190 $this->assertPhptExpectation($sections, $jobResult['stdout']); 191 } catch (PHPUnit_Framework_AssertionFailedError $e) { 192 if ($xfail !== false) { 193 $result->addFailure( 194 $this, 195 new PHPUnit_Framework_IncompleteTestError( 196 $xfail, 197 0, 198 $e 199 ), 200 $time 201 ); 202 } else { 203 $result->addFailure($this, $e, $time); 204 } 205 } catch (Throwable $t) { 206 $result->addError($this, $t, $time); 207 } catch (Exception $e) { 208 $result->addError($this, $e, $time); 209 } 210 211 if ($result->allCompletelyImplemented() && $xfail !== false) { 212 $result->addFailure( 213 $this, 214 new PHPUnit_Framework_IncompleteTestError( 215 'XFAIL section but test passes' 216 ), 217 $time 218 ); 219 } 220 221 $this->phpUtil->setStdin(''); 222 $this->phpUtil->setArgs(''); 223 224 if (isset($sections['CLEAN'])) { 225 $cleanCode = $this->render($sections['CLEAN']); 226 227 $this->phpUtil->runJob($cleanCode, $this->settings); 228 } 229 } 230 231 $result->endTest($this, $time); 232 233 return $result; 234 } 235 236 /** 237 * Returns the name of the test case. 238 * 239 * @return string 240 */ 241 public function getName() 242 { 243 return $this->toString(); 244 } 245 246 /** 247 * Returns a string representation of the test case. 248 * 249 * @return string 250 */ 251 public function toString() 252 { 253 return $this->filename; 254 } 255 256 /** 257 * @return array 258 * 259 * @throws PHPUnit_Framework_Exception 260 */ 261 private function parse() 262 { 263 $sections = []; 264 $section = ''; 265 266 $allowExternalSections = [ 267 'FILE', 268 'EXPECT', 269 'EXPECTF', 270 'EXPECTREGEX' 271 ]; 272 273 $requiredSections = [ 274 'FILE', 275 [ 276 'EXPECT', 277 'EXPECTF', 278 'EXPECTREGEX' 279 ] 280 ]; 281 282 $unsupportedSections = [ 283 'REDIRECTTEST', 284 'REQUEST', 285 'POST', 286 'PUT', 287 'POST_RAW', 288 'GZIP_POST', 289 'DEFLATE_POST', 290 'GET', 291 'COOKIE', 292 'HEADERS', 293 'CGI', 294 'EXPECTHEADERS', 295 'EXTENSIONS', 296 'PHPDBG' 297 ]; 298 299 foreach (file($this->filename) as $line) { 300 if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { 301 $section = $result[1]; 302 $sections[$section] = ''; 303 304 continue; 305 } elseif (empty($section)) { 306 throw new PHPUnit_Framework_Exception('Invalid PHPT file'); 307 } 308 309 $sections[$section] .= $line; 310 } 311 312 if (isset($sections['FILEEOF'])) { 313 $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); 314 unset($sections['FILEEOF']); 315 } 316 317 $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR; 318 319 foreach ($allowExternalSections as $section) { 320 if (isset($sections[$section . '_EXTERNAL'])) { 321 // do not allow directory traversal 322 $externalFilename = str_replace('..', '', trim($sections[$section . '_EXTERNAL'])); 323 324 // only allow files from the test directory 325 if (!is_file($testDirectory . $externalFilename) || !is_readable($testDirectory . $externalFilename)) { 326 throw new PHPUnit_Framework_Exception( 327 sprintf( 328 'Could not load --%s-- %s for PHPT file', 329 $section . '_EXTERNAL', 330 $testDirectory . $externalFilename 331 ) 332 ); 333 } 334 335 $sections[$section] = file_get_contents($testDirectory . $externalFilename); 336 337 unset($sections[$section . '_EXTERNAL']); 338 } 339 } 340 341 $isValid = true; 342 343 foreach ($requiredSections as $section) { 344 if (is_array($section)) { 345 $foundSection = false; 346 347 foreach ($section as $anySection) { 348 if (isset($sections[$anySection])) { 349 $foundSection = true; 350 351 break; 352 } 353 } 354 355 if (!$foundSection) { 356 $isValid = false; 357 358 break; 359 } 360 } else { 361 if (!isset($sections[$section])) { 362 $isValid = false; 363 364 break; 365 } 366 } 367 } 368 369 if (!$isValid) { 370 throw new PHPUnit_Framework_Exception('Invalid PHPT file'); 371 } 372 373 foreach ($unsupportedSections as $section) { 374 if (isset($sections[$section])) { 375 throw new PHPUnit_Framework_Exception( 376 'PHPUnit does not support this PHPT file' 377 ); 378 } 379 } 380 381 return $sections; 382 } 383 384 /** 385 * @param string $code 386 * 387 * @return string 388 */ 389 private function render($code) 390 { 391 return str_replace( 392 [ 393 '__DIR__', 394 '__FILE__' 395 ], 396 [ 397 "'" . dirname($this->filename) . "'", 398 "'" . $this->filename . "'" 399 ], 400 $code 401 ); 402 } 403 404 /** 405 * Parse --INI-- section key value pairs and return as array. 406 * 407 * @param string 408 * 409 * @return array 410 */ 411 protected function parseIniSection($content) 412 { 413 return preg_split('/\n|\r/', $content, -1, PREG_SPLIT_NO_EMPTY); 414 } 415 416 protected function parseEnvSection($content) 417 { 418 $env = []; 419 420 foreach (explode("\n", trim($content)) as $e) { 421 $e = explode('=', trim($e), 2); 422 423 if (!empty($e[0]) && isset($e[1])) { 424 $env[$e[0]] = $e[1]; 425 } 426 } 427 428 return $env; 429 } 430} 431