JUnit.php (15395B)
1 <?php 2 /** 3 * PHPUnit 4 * 5 * Copyright (c) 2001-2014, Sebastian Bergmann <sebastian@phpunit.de>. 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * * Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * * Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in 17 * the documentation and/or other materials provided with the 18 * distribution. 19 * 20 * * Neither the name of Sebastian Bergmann nor the names of his 21 * contributors may be used to endorse or promote products derived 22 * from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 * 37 * @package PHPUnit 38 * @subpackage Util_Log 39 * @author Sebastian Bergmann <sebastian@phpunit.de> 40 * @copyright 2001-2014 Sebastian Bergmann <sebastian@phpunit.de> 41 * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 42 * @link http://www.phpunit.de/ 43 * @since File available since Release 2.3.0 44 */ 45 46 /** 47 * A TestListener that generates a logfile of the test execution in XML markup. 48 * 49 * The XML markup used is the same as the one that is used by the JUnit Ant task. 50 * 51 * @package PHPUnit 52 * @subpackage Util_Log 53 * @author Sebastian Bergmann <sebastian@phpunit.de> 54 * @copyright 2001-2014 Sebastian Bergmann <sebastian@phpunit.de> 55 * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 56 * @link http://www.phpunit.de/ 57 * @since Class available since Release 2.1.0 58 */ 59 class PHPUnit_Util_Log_JUnit extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener 60 { 61 /** 62 * @var DOMDocument 63 */ 64 protected $document; 65 66 /** 67 * @var DOMElement 68 */ 69 protected $root; 70 71 /** 72 * @var boolean 73 */ 74 protected $logIncompleteSkipped = FALSE; 75 76 /** 77 * @var boolean 78 */ 79 protected $writeDocument = TRUE; 80 81 /** 82 * @var DOMElement[] 83 */ 84 protected $testSuites = array(); 85 86 /** 87 * @var integer[] 88 */ 89 protected $testSuiteTests = array(0); 90 91 /** 92 * @var integer[] 93 */ 94 protected $testSuiteAssertions = array(0); 95 96 /** 97 * @var integer[] 98 */ 99 protected $testSuiteErrors = array(0); 100 101 /** 102 * @var integer[] 103 */ 104 protected $testSuiteFailures = array(0); 105 106 /** 107 * @var integer[] 108 */ 109 protected $testSuiteTimes = array(0); 110 111 /** 112 * @var integer 113 */ 114 protected $testSuiteLevel = 0; 115 116 /** 117 * @var DOMElement 118 */ 119 protected $currentTestCase = NULL; 120 121 /** 122 * @var boolean 123 */ 124 protected $attachCurrentTestCase = TRUE; 125 126 /** 127 * Constructor. 128 * 129 * @param mixed $out 130 * @param boolean $logIncompleteSkipped 131 */ 132 public function __construct($out = NULL, $logIncompleteSkipped = FALSE) 133 { 134 $this->document = new DOMDocument('1.0', 'UTF-8'); 135 $this->document->formatOutput = TRUE; 136 137 $this->root = $this->document->createElement('testsuites'); 138 $this->document->appendChild($this->root); 139 140 parent::__construct($out); 141 142 $this->logIncompleteSkipped = $logIncompleteSkipped; 143 } 144 145 /** 146 * Flush buffer and close output. 147 * 148 */ 149 public function flush() 150 { 151 if ($this->writeDocument === TRUE) { 152 $this->write($this->getXML()); 153 } 154 155 parent::flush(); 156 } 157 158 /** 159 * An error occurred. 160 * 161 * @param PHPUnit_Framework_Test $test 162 * @param Exception $e 163 * @param float $time 164 */ 165 public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) 166 { 167 if ($this->currentTestCase !== NULL) { 168 if ($test instanceof PHPUnit_Framework_SelfDescribing) { 169 $buffer = $test->toString() . "\n"; 170 } else { 171 $buffer = ''; 172 } 173 174 $buffer .= PHPUnit_Framework_TestFailure::exceptionToString($e) . 175 "\n" . 176 PHPUnit_Util_Filter::getFilteredStacktrace($e); 177 178 $error = $this->document->createElement( 179 'error', PHPUnit_Util_XML::prepareString($buffer) 180 ); 181 182 $error->setAttribute('type', get_class($e)); 183 184 $this->currentTestCase->appendChild($error); 185 186 $this->testSuiteErrors[$this->testSuiteLevel]++; 187 } 188 } 189 190 /** 191 * A failure occurred. 192 * 193 * @param PHPUnit_Framework_Test $test 194 * @param PHPUnit_Framework_AssertionFailedError $e 195 * @param float $time 196 */ 197 public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) 198 { 199 if ($this->currentTestCase !== NULL) { 200 if (!$test instanceof PHPUnit_Framework_Warning) { 201 if ($test instanceof PHPUnit_Framework_SelfDescribing) { 202 $buffer = $test->toString() . "\n"; 203 } else { 204 $buffer = ''; 205 } 206 207 $buffer .= PHPUnit_Framework_TestFailure::exceptionToString($e) . 208 "\n" . 209 PHPUnit_Util_Filter::getFilteredStacktrace($e); 210 211 $failure = $this->document->createElement( 212 'failure', PHPUnit_Util_XML::prepareString($buffer) 213 ); 214 215 $failure->setAttribute('type', get_class($e)); 216 217 $this->currentTestCase->appendChild($failure); 218 219 $this->testSuiteFailures[$this->testSuiteLevel]++; 220 } 221 } 222 } 223 224 /** 225 * Incomplete test. 226 * 227 * @param PHPUnit_Framework_Test $test 228 * @param Exception $e 229 * @param float $time 230 */ 231 public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) 232 { 233 if ($this->logIncompleteSkipped && $this->currentTestCase !== NULL) { 234 $error = $this->document->createElement( 235 'error', 236 PHPUnit_Util_XML::prepareString( 237 "Incomplete Test\n" . 238 PHPUnit_Util_Filter::getFilteredStacktrace($e) 239 ) 240 ); 241 242 $error->setAttribute('type', get_class($e)); 243 244 $this->currentTestCase->appendChild($error); 245 246 $this->testSuiteErrors[$this->testSuiteLevel]++; 247 } else { 248 $this->attachCurrentTestCase = FALSE; 249 } 250 } 251 252 /** 253 * Skipped test. 254 * 255 * @param PHPUnit_Framework_Test $test 256 * @param Exception $e 257 * @param float $time 258 * @since Method available since Release 3.0.0 259 */ 260 public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) 261 { 262 if ($this->logIncompleteSkipped && $this->currentTestCase !== NULL) { 263 $error = $this->document->createElement( 264 'error', 265 PHPUnit_Util_XML::prepareString( 266 "Skipped Test\n" . 267 PHPUnit_Util_Filter::getFilteredStacktrace($e) 268 ) 269 ); 270 271 $error->setAttribute('type', get_class($e)); 272 273 $this->currentTestCase->appendChild($error); 274 275 $this->testSuiteErrors[$this->testSuiteLevel]++; 276 } else { 277 $this->attachCurrentTestCase = FALSE; 278 } 279 } 280 281 /** 282 * A testsuite started. 283 * 284 * @param PHPUnit_Framework_TestSuite $suite 285 * @since Method available since Release 2.2.0 286 */ 287 public function startTestSuite(PHPUnit_Framework_TestSuite $suite) 288 { 289 $testSuite = $this->document->createElement('testsuite'); 290 $testSuite->setAttribute('name', $suite->getName()); 291 292 if (class_exists($suite->getName(), FALSE)) { 293 try { 294 $class = new ReflectionClass($suite->getName()); 295 296 $testSuite->setAttribute('file', $class->getFileName()); 297 298 $packageInformation = PHPUnit_Util_Class::getPackageInformation( 299 $suite->getName(), $class->getDocComment() 300 ); 301 302 if (!empty($packageInformation['namespace'])) { 303 $testSuite->setAttribute( 304 'namespace', $packageInformation['namespace'] 305 ); 306 } 307 308 if (!empty($packageInformation['fullPackage'])) { 309 $testSuite->setAttribute( 310 'fullPackage', $packageInformation['fullPackage'] 311 ); 312 } 313 314 if (!empty($packageInformation['category'])) { 315 $testSuite->setAttribute( 316 'category', $packageInformation['category'] 317 ); 318 } 319 320 if (!empty($packageInformation['package'])) { 321 $testSuite->setAttribute( 322 'package', $packageInformation['package'] 323 ); 324 } 325 326 if (!empty($packageInformation['subpackage'])) { 327 $testSuite->setAttribute( 328 'subpackage', $packageInformation['subpackage'] 329 ); 330 } 331 } 332 333 catch (ReflectionException $e) { 334 } 335 } 336 337 if ($this->testSuiteLevel > 0) { 338 $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); 339 } else { 340 $this->root->appendChild($testSuite); 341 } 342 343 $this->testSuiteLevel++; 344 $this->testSuites[$this->testSuiteLevel] = $testSuite; 345 $this->testSuiteTests[$this->testSuiteLevel] = 0; 346 $this->testSuiteAssertions[$this->testSuiteLevel] = 0; 347 $this->testSuiteErrors[$this->testSuiteLevel] = 0; 348 $this->testSuiteFailures[$this->testSuiteLevel] = 0; 349 $this->testSuiteTimes[$this->testSuiteLevel] = 0; 350 } 351 352 /** 353 * A testsuite ended. 354 * 355 * @param PHPUnit_Framework_TestSuite $suite 356 * @since Method available since Release 2.2.0 357 */ 358 public function endTestSuite(PHPUnit_Framework_TestSuite $suite) 359 { 360 $this->testSuites[$this->testSuiteLevel]->setAttribute( 361 'tests', $this->testSuiteTests[$this->testSuiteLevel] 362 ); 363 364 $this->testSuites[$this->testSuiteLevel]->setAttribute( 365 'assertions', $this->testSuiteAssertions[$this->testSuiteLevel] 366 ); 367 368 $this->testSuites[$this->testSuiteLevel]->setAttribute( 369 'failures', $this->testSuiteFailures[$this->testSuiteLevel] 370 ); 371 372 $this->testSuites[$this->testSuiteLevel]->setAttribute( 373 'errors', $this->testSuiteErrors[$this->testSuiteLevel] 374 ); 375 376 $this->testSuites[$this->testSuiteLevel]->setAttribute( 377 'time', sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]) 378 ); 379 380 if ($this->testSuiteLevel > 1) { 381 $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; 382 $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; 383 $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; 384 $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; 385 $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; 386 } 387 388 $this->testSuiteLevel--; 389 } 390 391 /** 392 * A test started. 393 * 394 * @param PHPUnit_Framework_Test $test 395 */ 396 public function startTest(PHPUnit_Framework_Test $test) 397 { 398 if (!$test instanceof PHPUnit_Framework_Warning) { 399 $testCase = $this->document->createElement('testcase'); 400 $testCase->setAttribute('name', $test->getName()); 401 402 if ($test instanceof PHPUnit_Framework_TestCase) { 403 $class = new ReflectionClass($test); 404 $methodName = $test->getName(); 405 406 if ($class->hasMethod($methodName)) { 407 $method = $class->getMethod($test->getName()); 408 409 $testCase->setAttribute('class', $class->getName()); 410 $testCase->setAttribute('file', $class->getFileName()); 411 $testCase->setAttribute('line', $method->getStartLine()); 412 } 413 } 414 415 $this->currentTestCase = $testCase; 416 } 417 } 418 419 /** 420 * A test ended. 421 * 422 * @param PHPUnit_Framework_Test $test 423 * @param float $time 424 */ 425 public function endTest(PHPUnit_Framework_Test $test, $time) 426 { 427 if (!$test instanceof PHPUnit_Framework_Warning) { 428 if ($this->attachCurrentTestCase) { 429 if ($test instanceof PHPUnit_Framework_TestCase) { 430 $numAssertions = $test->getNumAssertions(); 431 $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; 432 433 $this->currentTestCase->setAttribute( 434 'assertions', $numAssertions 435 ); 436 } 437 438 $this->currentTestCase->setAttribute( 439 'time', sprintf('%F', $time) 440 ); 441 442 $this->testSuites[$this->testSuiteLevel]->appendChild( 443 $this->currentTestCase 444 ); 445 446 $this->testSuiteTests[$this->testSuiteLevel]++; 447 $this->testSuiteTimes[$this->testSuiteLevel] += $time; 448 } 449 } 450 451 $this->attachCurrentTestCase = TRUE; 452 $this->currentTestCase = NULL; 453 } 454 455 /** 456 * Returns the XML as a string. 457 * 458 * @return string 459 * @since Method available since Release 2.2.0 460 */ 461 public function getXML() 462 { 463 return $this->document->saveXML(); 464 } 465 466 /** 467 * Enables or disables the writing of the document 468 * in flush(). 469 * 470 * This is a "hack" needed for the integration of 471 * PHPUnit with Phing. 472 * 473 * @return string 474 * @since Method available since Release 2.2.0 475 */ 476 public function setWriteDocument($flag) 477 { 478 if (is_bool($flag)) { 479 $this->writeDocument = $flag; 480 } 481 } 482 }