3 * This file is part of PHPUnit.
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
11 use SebastianBergmann\Environment\Console;
14 * Prints the result of a TextUI TestRunner run.
16 * @since Class available since Release 2.0.0
18 class PHPUnit_TextUI_ResultPrinter extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
20 const EVENT_TEST_START = 0;
21 const EVENT_TEST_END = 1;
22 const EVENT_TESTSUITE_START = 2;
23 const EVENT_TESTSUITE_END = 3;
25 const COLOR_NEVER = 'never';
26 const COLOR_AUTO = 'auto';
27 const COLOR_ALWAYS = 'always';
28 const COLOR_DEFAULT = self::COLOR_NEVER;
33 private static $ansiCodes = array(
56 protected $column = 0;
66 protected $lastTestFailed = false;
71 protected $numAssertions = 0;
76 protected $numTests = -1;
81 protected $numTestsRun = 0;
86 protected $numTestsWidth;
91 protected $colors = false;
96 protected $debug = false;
101 protected $verbose = false;
106 private $numberOfColumns;
112 * @param bool $verbose
113 * @param string $colors
115 * @param int|string $numberOfColumns
117 * @throws PHPUnit_Framework_Exception
119 * @since Method available since Release 3.0.0
121 public function __construct($out = null, $verbose = false, $colors = self::COLOR_DEFAULT, $debug = false, $numberOfColumns = 80)
123 parent::__construct($out);
125 if (!is_bool($verbose)) {
126 throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'boolean');
129 $availableColors = array(self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS);
131 if (!in_array($colors, $availableColors)) {
132 throw PHPUnit_Util_InvalidArgumentHelper::factory(
134 vsprintf('value from "%s", "%s" or "%s"', $availableColors)
138 if (!is_bool($debug)) {
139 throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'boolean');
142 if (!is_int($numberOfColumns) && $numberOfColumns != 'max') {
143 throw PHPUnit_Util_InvalidArgumentHelper::factory(5, 'integer or "max"');
146 $console = new Console;
147 $maxNumberOfColumns = $console->getNumberOfColumns();
149 if ($numberOfColumns == 'max' || $numberOfColumns > $maxNumberOfColumns) {
150 $numberOfColumns = $maxNumberOfColumns;
153 $this->numberOfColumns = $numberOfColumns;
154 $this->verbose = $verbose;
155 $this->debug = $debug;
157 if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) {
158 $this->colors = true;
160 $this->colors = (self::COLOR_ALWAYS === $colors);
165 * @param PHPUnit_Framework_TestResult $result
167 public function printResult(PHPUnit_Framework_TestResult $result)
169 $this->printHeader();
171 $this->printErrors($result);
172 $printSeparator = $result->errorCount() > 0;
174 if ($printSeparator && $result->failureCount() > 0) {
175 $this->write("\n--\n\n");
178 $printSeparator = $printSeparator || $result->failureCount() > 0;
179 $this->printFailures($result);
181 if ($this->verbose) {
182 if ($printSeparator && $result->riskyCount() > 0) {
183 $this->write("\n--\n\n");
186 $printSeparator = $printSeparator ||
187 $result->riskyCount() > 0;
189 $this->printRisky($result);
191 if ($printSeparator && $result->notImplementedCount() > 0) {
192 $this->write("\n--\n\n");
195 $printSeparator = $printSeparator ||
196 $result->notImplementedCount() > 0;
198 $this->printIncompletes($result);
200 if ($printSeparator && $result->skippedCount() > 0) {
201 $this->write("\n--\n\n");
204 $this->printSkipped($result);
207 $this->printFooter($result);
211 * @param array $defects
212 * @param string $type
214 protected function printDefects(array $defects, $type)
216 $count = count($defects);
224 "There %s %d %s%s:\n",
225 ($count == 1) ? 'was' : 'were',
228 ($count == 1) ? '' : 's'
234 foreach ($defects as $defect) {
235 $this->printDefect($defect, $i++);
240 * @param PHPUnit_Framework_TestFailure $defect
243 protected function printDefect(PHPUnit_Framework_TestFailure $defect, $count)
245 $this->printDefectHeader($defect, $count);
246 $this->printDefectTrace($defect);
250 * @param PHPUnit_Framework_TestFailure $defect
253 protected function printDefectHeader(PHPUnit_Framework_TestFailure $defect, $count)
259 $defect->getTestName()
265 * @param PHPUnit_Framework_TestFailure $defect
267 protected function printDefectTrace(PHPUnit_Framework_TestFailure $defect)
269 $e = $defect->thrownException();
270 $this->write((string) $e);
272 while ($e = $e->getPrevious()) {
273 $this->write("\nCaused by\n" . $e);
278 * @param PHPUnit_Framework_TestResult $result
280 protected function printErrors(PHPUnit_Framework_TestResult $result)
282 $this->printDefects($result->errors(), 'error');
286 * @param PHPUnit_Framework_TestResult $result
288 protected function printFailures(PHPUnit_Framework_TestResult $result)
290 $this->printDefects($result->failures(), 'failure');
294 * @param PHPUnit_Framework_TestResult $result
296 protected function printIncompletes(PHPUnit_Framework_TestResult $result)
298 $this->printDefects($result->notImplemented(), 'incomplete test');
302 * @param PHPUnit_Framework_TestResult $result
304 * @since Method available since Release 4.0.0
306 protected function printRisky(PHPUnit_Framework_TestResult $result)
308 $this->printDefects($result->risky(), 'risky test');
312 * @param PHPUnit_Framework_TestResult $result
314 * @since Method available since Release 3.0.0
316 protected function printSkipped(PHPUnit_Framework_TestResult $result)
318 $this->printDefects($result->skipped(), 'skipped test');
321 protected function printHeader()
323 $this->write("\n\n" . PHP_Timer::resourceUsage() . "\n\n");
327 * @param PHPUnit_Framework_TestResult $result
329 protected function printFooter(PHPUnit_Framework_TestResult $result)
331 if (count($result) === 0) {
332 $this->writeWithColor(
333 'fg-black, bg-yellow',
336 } elseif ($result->wasSuccessful() &&
337 $result->allHarmless() &&
338 $result->allCompletelyImplemented() &&
339 $result->noneSkipped()) {
340 $this->writeWithColor(
341 'fg-black, bg-green',
343 'OK (%d test%s, %d assertion%s)',
345 (count($result) == 1) ? '' : 's',
346 $this->numAssertions,
347 ($this->numAssertions == 1) ? '' : 's'
351 if ($result->wasSuccessful()) {
352 $color = 'fg-black, bg-yellow';
354 if ($this->verbose) {
358 $this->writeWithColor(
360 'OK, but incomplete, skipped, or risky tests!'
363 $color = 'fg-white, bg-red';
366 $this->writeWithColor($color, 'FAILURES!');
369 $this->writeCountString(count($result), 'Tests', $color, true);
370 $this->writeCountString($this->numAssertions, 'Assertions', $color, true);
371 $this->writeCountString($result->errorCount(), 'Errors', $color);
372 $this->writeCountString($result->failureCount(), 'Failures', $color);
373 $this->writeCountString($result->skippedCount(), 'Skipped', $color);
374 $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color);
375 $this->writeCountString($result->riskyCount(), 'Risky', $color);
376 $this->writeWithColor($color, '.', true);
382 public function printWaitPrompt()
384 $this->write("\n<RETURN> to continue\n");
390 * @param PHPUnit_Framework_Test $test
391 * @param Exception $e
394 public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
396 $this->writeProgressWithColor('fg-red, bold', 'E');
397 $this->lastTestFailed = true;
401 * A failure occurred.
403 * @param PHPUnit_Framework_Test $test
404 * @param PHPUnit_Framework_AssertionFailedError $e
407 public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
409 $this->writeProgressWithColor('bg-red, fg-white', 'F');
410 $this->lastTestFailed = true;
416 * @param PHPUnit_Framework_Test $test
417 * @param Exception $e
420 public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
422 $this->writeProgressWithColor('fg-yellow, bold', 'I');
423 $this->lastTestFailed = true;
429 * @param PHPUnit_Framework_Test $test
430 * @param Exception $e
433 * @since Method available since Release 4.0.0
435 public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
437 $this->writeProgressWithColor('fg-yellow, bold', 'R');
438 $this->lastTestFailed = true;
444 * @param PHPUnit_Framework_Test $test
445 * @param Exception $e
448 * @since Method available since Release 3.0.0
450 public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
452 $this->writeProgressWithColor('fg-cyan, bold', 'S');
453 $this->lastTestFailed = true;
457 * A testsuite started.
459 * @param PHPUnit_Framework_TestSuite $suite
461 * @since Method available since Release 2.2.0
463 public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
465 if ($this->numTests == -1) {
466 $this->numTests = count($suite);
467 $this->numTestsWidth = strlen((string) $this->numTests);
468 $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numTestsWidth);
475 * @param PHPUnit_Framework_TestSuite $suite
477 * @since Method available since Release 2.2.0
479 public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
486 * @param PHPUnit_Framework_Test $test
488 public function startTest(PHPUnit_Framework_Test $test)
493 "\nStarting test '%s'.\n",
494 PHPUnit_Util_Test::describe($test)
503 * @param PHPUnit_Framework_Test $test
506 public function endTest(PHPUnit_Framework_Test $test, $time)
508 if (!$this->lastTestFailed) {
509 $this->writeProgress('.');
512 if ($test instanceof PHPUnit_Framework_TestCase) {
513 $this->numAssertions += $test->getNumAssertions();
514 } elseif ($test instanceof PHPUnit_Extensions_PhptTestCase) {
515 $this->numAssertions++;
518 $this->lastTestFailed = false;
520 if ($test instanceof PHPUnit_Framework_TestCase) {
521 if (!$test->hasExpectationOnOutput()) {
522 $this->write($test->getActualOutput());
528 * @param string $progress
530 protected function writeProgress($progress)
532 $this->write($progress);
534 $this->numTestsRun++;
536 if ($this->column == $this->maxColumn) {
539 ' %' . $this->numTestsWidth . 'd / %' .
540 $this->numTestsWidth . 'd (%3s%%)',
543 floor(($this->numTestsRun / $this->numTests) * 100)
547 $this->writeNewLine();
551 protected function writeNewLine()
558 * Formats a buffer with a specified ANSI color sequence if colors are
561 * @param string $color
562 * @param string $buffer
566 * @since Method available since Release 4.0.0
568 protected function formatWithColor($color, $buffer)
570 if (!$this->colors) {
574 $codes = array_map('trim', explode(',', $color));
575 $lines = explode("\n", $buffer);
576 $padding = max(array_map('strlen', $lines));
579 foreach ($codes as $code) {
580 $styles[] = self::$ansiCodes[$code];
583 $style = sprintf("\x1b[%sm", implode(';', $styles));
585 $styledLines = array();
587 foreach ($lines as $line) {
588 $styledLines[] = $style . str_pad($line, $padding) . "\x1b[0m";
591 return implode("\n", $styledLines);
595 * Writes a buffer out with a color sequence if colors are enabled.
597 * @param string $color
598 * @param string $buffer
601 * @since Method available since Release 4.0.0
603 protected function writeWithColor($color, $buffer, $lf = true)
605 $this->write($this->formatWithColor($color, $buffer));
613 * Writes progress with a color sequence if colors are enabled.
615 * @param string $color
616 * @param string $buffer
618 * @since Method available since Release 4.0.0
620 protected function writeProgressWithColor($color, $buffer)
622 $buffer = $this->formatWithColor($color, $buffer);
623 $this->writeProgress($buffer);
628 * @param string $name
629 * @param string $color
630 * @param bool $always
632 * @since Method available since Release 4.6.5
634 private function writeCountString($count, $name, $color, $always = false)
636 static $first = true;
638 if ($always || $count > 0) {
639 $this->writeWithColor(