From 9ca15bef8c6f60765f83daa7bd297b6e5de8d484 Mon Sep 17 00:00:00 2001 From: Bilge Date: Sun, 5 Mar 2017 22:13:28 +0000 Subject: [PATCH] Added ImmediateExceptionPrinter and suite of functional tests. Added readme. --- .gitignore | 3 + .travis.yml | 35 ++++++ README.md | 44 ++++++++ composer.json | 26 +++++ doc/images/test run 1.0.png | Bin 0 -> 39252 bytes src/ImmediateExceptionPrinter.php | 147 ++++++++++++++++++++++++++ test/CapabilitiesTest.php | 60 +++++++++++ test/ExceptionThrower.php | 10 ++ test/functional/PHPUnit runner.php | 4 + test/functional/data provider.phpt | 19 ++++ test/functional/diff failure.phpt | 41 +++++++ test/functional/exception.phpt | 33 ++++++ test/functional/failure.phpt | 31 ++++++ test/functional/incomplete.phpt | 19 ++++ test/functional/nested exception.phpt | 49 +++++++++ test/functional/repeat.phpt | 97 +++++++++++++++++ test/functional/risky.phpt | 19 ++++ test/functional/skipped.phpt | 19 ++++ test/functional/success.phpt | 18 ++++ test/phpunit.xml | 15 +++ 20 files changed, 689 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100644 composer.json create mode 100644 doc/images/test run 1.0.png create mode 100644 src/ImmediateExceptionPrinter.php create mode 100644 test/CapabilitiesTest.php create mode 100644 test/ExceptionThrower.php create mode 100644 test/functional/PHPUnit runner.php create mode 100644 test/functional/data provider.phpt create mode 100644 test/functional/diff failure.phpt create mode 100644 test/functional/exception.phpt create mode 100644 test/functional/failure.phpt create mode 100644 test/functional/incomplete.phpt create mode 100644 test/functional/nested exception.phpt create mode 100644 test/functional/repeat.phpt create mode 100644 test/functional/risky.phpt create mode 100644 test/functional/skipped.phpt create mode 100644 test/functional/success.phpt create mode 100644 test/phpunit.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b5d08a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.*/ +/vendor/ +/composer.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2a3fb2b --- /dev/null +++ b/.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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b42b8b --- /dev/null +++ b/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 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..88e6735 --- /dev/null +++ b/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" + } +} diff --git a/doc/images/test run 1.0.png b/doc/images/test run 1.0.png new file mode 100644 index 0000000000000000000000000000000000000000..eab6440a64d0a9b8de8fd83ee24aca55f95c2dde GIT binary patch literal 39252 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 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; + } +} diff --git a/test/CapabilitiesTest.php b/test/CapabilitiesTest.php new file mode 100644 index 0000000..4429098 --- /dev/null +++ b/test/CapabilitiesTest.php @@ -0,0 +1,60 @@ +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'], + ]; + } +} diff --git a/test/ExceptionThrower.php b/test/ExceptionThrower.php new file mode 100644 index 0000000..7b0df64 --- /dev/null +++ b/test/ExceptionThrower.php @@ -0,0 +1,10 @@ + + + functional + + + + .. + + +