Skip to content

Commit

Permalink
Added ImmediateExceptionPrinter and suite of functional tests.
Browse files Browse the repository at this point in the history
Added readme.
  • Loading branch information
Bilge committed Mar 6, 2017
0 parents commit 9ca15be
Show file tree
Hide file tree
Showing 20 changed files with 689 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
/.*/
/vendor/
/composer.lock
35 changes: 35 additions & 0 deletions .travis.yml
@@ -0,0 +1,35 @@
notifications:
email: false

sudo: false

language: php

php:
- 5.6
- 7.0
- 7.1

env:
matrix:
-
- DEPENDENCIES=--prefer-lowest

matrix:
fast_finish: true

cache:
directories:
- .composer/cache

install:
- alias composer=composer\ -n && composer selfupdate
- composer validate
- composer update $DEPENDENCIES

script:
- composer test -- --coverage-clover=build/logs/clover.xml

after_success:
- composer require satooshi/php-coveralls
- vendor/bin/coveralls -v
44 changes: 44 additions & 0 deletions README.md
@@ -0,0 +1,44 @@
PHPUnit Immediate Exception Printer
===================================

[![Latest version][Version image]][Releases]
[![Total downloads][Downloads image]][Downloads]
[![Build status][Build image]][Build]
[![Test coverage][Coverage image]][Coverage]
[![Code style][Style image]][Style]

Immediate Exception Printer is a [PHPUnit][PHPUnit] plug-in that prints out exceptions and assertion failures immediately during a test run. Normally PHPUnit keeps error details secret until the end of the test run, but sometimes we don't want to wait that long. With Immediate Exception Printer, all secrets are immediately revealed, with a few extra benefits, too.

## Benefits

* Immediately prints out exceptions and assertion failures as they occur.
* Displays the execution time of each test in tiered colour bands.
* Displays the name of each test case as it is executed.

## Preview

The following preview is somewhat atypical but shows all tested output cases this printer supports.

![Preview image](https://raw.githubusercontent.com/ScriptFUSION/PHPUnit-Immediate-Exception-Printer/master/doc/images/test%20run%201.0.png)

## Inspiration

Thanks to the following open source projects that inspired this project. Keep being awesome :thumbsup:.

* [diablomedia/phpunit-pretty-printer](https://github.com/diablomedia/phpunit-pretty-printer)
* [whatthejeff/nyancat-phpunit-resultprinter](https://github.com/whatthejeff/nyancat-phpunit-resultprinter)
* [skyzyx/phpunit-result-printer](https://github.com/skyzyx/phpunit-result-printer)


[Releases]: https://github.com/ScriptFUSION/PHPUnit-Immediate-Exception-Printer/releases
[Version image]: https://poser.pugx.org/scriptfusion/phpunit-immediate-exception-printer/version "Latest version"
[Downloads]: https://packagist.org/packages/scriptfusion/phpunit-immediate-exception-printer
[Downloads image]: https://poser.pugx.org/scriptfusion/phpunit-immediate-exception-printer/downloads "Total downloads"
[Build]: https://travis-ci.org/ScriptFUSION/PHPUnit-Immediate-Exception-Printer
[Build image]: https://travis-ci.org/ScriptFUSION/PHPUnit-Immediate-Exception-Printer.svg?branch=master "Build status"
[Coverage]: https://coveralls.io/github/ScriptFUSION/PHPUnit-Immediate-Exception-Printer
[Coverage image]: https://coveralls.io/repos/ScriptFUSION/PHPUnit-Immediate-Exception-Printer/badge.svg "Test coverage"
[Style]: https://styleci.io/repos/83920053
[Style image]: https://styleci.io/repos/83920053/shield?style=flat "Code style"

[PHPUnit]: https://github.com/sebastianbergmann/phpunit
26 changes: 26 additions & 0 deletions composer.json
@@ -0,0 +1,26 @@
{
"name": "scriptfusion/phpunit-immediate-exception-printer",
"description": "Immediately prints any exceptions or assertion failures that occur during testing.",
"authors": [
{
"name": "Bilge",
"email": "bilge@scriptfusion.com"
}
],
"require": {
"phpunit/phpunit": "^5.5"
},
"autoload": {
"psr-4": {
"ScriptFUSION\\PHPUnitImmediateExceptionPrinter\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"ScriptFUSIONTest\\PHPUnitImmediateExceptionPrinter\\": "test"
}
},
"scripts": {
"test": "phpunit -c test"
}
}
Binary file added doc/images/test run 1.0.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
147 changes: 147 additions & 0 deletions src/ImmediateExceptionPrinter.php
@@ -0,0 +1,147 @@
<?php
namespace ScriptFUSION\PHPUnitImmediateExceptionPrinter;

class ImmediateExceptionPrinter extends \PHPUnit_TextUI_ResultPrinter
{
/**
* The exception thrown by the last test.
*
* @var \Exception|null
*/
protected $exception;

/**
* PHPUnit built-in progress indication character, e.g. E for error.
*
* @var string
*/
protected $progress;

/**
* Last colour used by a progress indicator.
*
* @var string
*/
protected $lastColour;

private static $performanceThresholds = [
'fg-red' => 1000,
'fg-yellow' => 200,
'fg-green' => 0,
];

public function startTest(\PHPUnit_Framework_Test $test)
{
parent::startTest($test);

$this->exception = $this->progress = null;
$this->lastColour = 'fg-green,bold';
}

protected function writeProgress($progress)
{
// Record progress so it can be written later instead of at start of line.
$this->progress = $progress;

++$this->numTestsRun;
}

protected function writeProgressWithColor($color, $buffer)
{
parent::writeProgressWithColor($color, $buffer);

$this->lastColour = $color;
}

public function endTest(\PHPUnit_Framework_Test $test, $time)
{
parent::endTest($test, $time);

$this->write(sprintf(
'%3d%% %s ',
round($this->numTestsRun / $this->numTests * 100),
$this->progress
));
$this->writeWithColor($this->lastColour, \PHPUnit_Util_Test::describe($test), false);
$this->writePerformance($time);

if ($this->exception) {
$this->printExceptionTrace($this->exception);
}
}

/**
* Writes the test performance metric formatted as the number of milliseconds elapsed.
*
* @param float $time Number of seconds elapsed.
*/
protected function writePerformance($time)
{
$ms = round($time * 1000);

foreach (self::$performanceThresholds as $colour => $threshold) {
if ($ms > $threshold) {
break;
}
}

$this->writeWithColor($colour, " ($ms ms)");
}

protected function printExceptionTrace(\Exception $exception)
{
$this->writeNewLine();

// Parse nested exception trace line by line.
foreach (explode("\n", $exception) as $line) {
// Print exception name and message.
if (!$exception instanceof \PHPUnit_Framework_AssertionFailedError
&& false !== $pos = strpos($line, ': ')
) {
$whitespace = str_repeat(' ', $pos + 2);
$this->writeWithColor('bg-red,fg-white', $whitespace);

// Exception name.
$this->writeWithColor('bg-red,fg-white', sprintf(' %s ', substr($line, 0, $pos)), false);
// Exception message.
$this->writeWithColor('fg-red', substr($line, $pos + 1));

$this->writeWithColor('bg-red,fg-white', $whitespace);

continue;
}

$this->writeWithColor('fg-red', $line);
}
}

/**
* Called when an exception is thrown in the test runner.
*
* @param \PHPUnit_Framework_Test $test
* @param \Exception $e
* @param float $time
*/
public function addError(\PHPUnit_Framework_Test $test, \Exception $e, $time)
{
$this->writeProgressWithColor('fg-red,bold', 'E');

$this->exception = $e;
$this->lastTestFailed = true;
}

/**
* Called when an assertion fails in the test runner.
*
* @param \PHPUnit_Framework_Test $test
* @param \PHPUnit_Framework_AssertionFailedError $e
* @param float $time
*/
public function addFailure(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_AssertionFailedError $e, $time)
{
$this->writeProgressWithColor('fg-red,bold', 'F');

$this->exception = $e;
$this->lastTestFailed = true;
}
}
60 changes: 60 additions & 0 deletions test/CapabilitiesTest.php
@@ -0,0 +1,60 @@
<?php
namespace ScriptFUSIONTest\PHPUnitImmediateExceptionPrinter;

final class CapabilitiesTest extends \PHPUnit_Framework_TestCase
{
public function testSuccess()
{
self::assertTrue(true);
}

public function testFailure()
{
self::assertTrue(false);
}

public function testException()
{
throw new \LogicException('foo');
}

public function testNestedException()
{
new ExceptionThrower;
}

public function testDiffFailure()
{
self::assertSame('foo', 'LogicException: foo');
}

public function testSkipped()
{
$this->markTestSkipped();
}

public function testRisky()
{
}

public function testIncomplete()
{
$this->markTestIncomplete();
}

/**
* @dataProvider provideData
*/
public function testDataProvider()
{
self::assertTrue(true);
}

public function provideData()
{
return [
'foo' => ['bar'],
'baz' => ['qux'],
];
}
}
10 changes: 10 additions & 0 deletions test/ExceptionThrower.php
@@ -0,0 +1,10 @@
<?php
namespace ScriptFUSIONTest\PHPUnitImmediateExceptionPrinter;

final class ExceptionThrower
{
public function __construct()
{
throw new \LogicException('foo', 0, new \RuntimeException('bar'));
}
}
4 changes: 4 additions & 0 deletions test/functional/PHPUnit runner.php
@@ -0,0 +1,4 @@
<?php
require_once 'vendor/autoload.php';

PHPUnit_TextUI_Command::main();
19 changes: 19 additions & 0 deletions test/functional/data provider.phpt
@@ -0,0 +1,19 @@
--TEST--
A successful test is fed two cases by a data provider.

--ARGS--
-c test --colors=always test/CapabilitiesTest --filter '::testDataProvider\h'

--FILE_EXTERNAL--
PHPUnit runner.php

--EXPECTF--
PHPUnit %s

50% . ScriptFUSIONTest\PHPUnitImmediateExceptionPrinter\CapabilitiesTest::testDataProvider with data set "foo" ('bar') (%i ms)
100% . ScriptFUSIONTest\PHPUnitImmediateExceptionPrinter\CapabilitiesTest::testDataProvider with data set "baz" ('qux') (%i ms)


Time: %s

OK (2 tests, 2 assertions)
41 changes: 41 additions & 0 deletions test/functional/diff failure.phpt
@@ -0,0 +1,41 @@
--TEST--
An assertion fails and produces a diff.

--ARGS--
-c test --colors=always test/CapabilitiesTest --filter ::testDiffFailure$

--FILE_EXTERNAL--
PHPUnit runner.php

--EXPECTF--
PHPUnit %s

100% F ScriptFUSIONTest\PHPUnitImmediateExceptionPrinter\CapabilitiesTest::testDiffFailure (%i ms)

Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-foo
+LogicException: foo

%s%eCapabilitiesTest.php:%i



Time: %s

There was 1 failure:

1) ScriptFUSIONTest\PHPUnitImmediateExceptionPrinter\CapabilitiesTest::testDiffFailure
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-foo
+LogicException: foo

%s%eCapabilitiesTest.php:%i

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

0 comments on commit 9ca15be

Please sign in to comment.