diff --git a/composer.json b/composer.json index b6a7392..679490c 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "require-dev": { "mensbeam/html-dom": "^1.0", "phpunit/phpunit": "^9.5", - "spatie/fork": "^1.1", - "nikic/php-parser": "^4.15" + "nikic/php-parser": "^4.15", + "eloquent/phony-phpunit": "^7.1" } } diff --git a/composer.lock b/composer.lock index 747a3a9..576c6d8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6e9aedba10b2f2d0dc2c92f4b391e116", + "content-hash": "276b1ae35ddd23e0ebc736f079ecf98f", "packages": [ { "name": "psr/log", @@ -128,6 +128,153 @@ ], "time": "2022-03-03T08:28:38+00:00" }, + { + "name": "eloquent/phony", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/eloquent/phony.git", + "reference": "f34d67d6db6b6f351ea7e8aa8066107e756ec26b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/eloquent/phony/zipball/f34d67d6db6b6f351ea7e8aa8066107e756ec26b", + "reference": "f34d67d6db6b6f351ea7e8aa8066107e756ec26b", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8" + }, + "require-dev": { + "eloquent/code-style": "^1.0", + "eloquent/phpstan-phony": "^0.7", + "errors/exceptions": "^0.2", + "ext-pdo": "*", + "friendsofphp/php-cs-fixer": "^2", + "hamcrest/hamcrest-php": "^2", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1.x-dev" + } + }, + "autoload": { + "files": [ + "src/initialize.php", + "src/functions.php" + ], + "psr-4": { + "Eloquent\\Phony\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Erin Millard", + "email": "ezzatron@gmail.com", + "homepage": "http://ezzatron.com/" + } + ], + "description": "Mocks, stubs, and spies for PHP.", + "homepage": "http://eloquent-software.com/phony/", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "mocking", + "spy", + "stub", + "stubbing", + "test" + ], + "support": { + "issues": "https://github.com/eloquent/phony/issues", + "source": "https://github.com/eloquent/phony/tree/5.0.2" + }, + "time": "2021-02-17T01:45:10+00:00" + }, + { + "name": "eloquent/phony-phpunit", + "version": "7.1.0", + "source": { + "type": "git", + "url": "https://github.com/eloquent/phony-phpunit.git", + "reference": "e77ff95ea6235211d4aae7e5f53488a5faebc2e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/eloquent/phony-phpunit/zipball/e77ff95ea6235211d4aae7e5f53488a5faebc2e0", + "reference": "e77ff95ea6235211d4aae7e5f53488a5faebc2e0", + "shasum": "" + }, + "require": { + "eloquent/phony": "^5", + "php": "^7.3 || ^8", + "phpunit/phpunit": "^9" + }, + "require-dev": { + "eloquent/code-style": "^1", + "eloquent/phpstan-phony": "^0.7", + "errors/exceptions": "^0.2", + "friendsofphp/php-cs-fixer": "^2", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/initialize.php", + "src/functions.php" + ], + "psr-4": { + "Eloquent\\Phony\\Phpunit\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Erin Millard", + "email": "ezzatron@gmail.com", + "homepage": "http://ezzatron.com/" + } + ], + "description": "Phony for PHPUnit.", + "homepage": "http://eloquent-software.com/phony/", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "mocking", + "spy", + "stub", + "stubbing", + "test" + ], + "support": { + "issues": "https://github.com/eloquent/phony-phpunit/issues", + "source": "https://github.com/eloquent/phony-phpunit/tree/7.1.0" + }, + "time": "2020-12-21T09:36:47+00:00" + }, { "name": "mensbeam/framework", "version": "1.0.5", @@ -167,7 +314,7 @@ "homepage": "https://dustinwilson.com/" } ], - "description": "Common classes and traits used in many MensBeam projects", + "description": "Common classes and traits used in many Mensbeam projects", "support": { "issues": "https://github.com/mensbeam/Framework/issues", "source": "https://github.com/mensbeam/Framework/tree/1.0.5" @@ -637,16 +784,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.17", + "version": "9.2.18", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" + "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a", + "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a", "shasum": "" }, "require": { @@ -702,7 +849,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18" }, "funding": [ { @@ -710,7 +857,7 @@ "type": "github" } ], - "time": "2022-08-30T12:24:04+00:00" + "time": "2022-10-27T13:35:33+00:00" }, { "name": "phpunit/php-file-iterator", @@ -955,16 +1102,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.25", + "version": "9.5.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", "shasum": "" }, "require": { @@ -1037,7 +1184,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" }, "funding": [ { @@ -1053,7 +1200,7 @@ "type": "tidelift" } ], - "time": "2022-09-25T03:44:45+00:00" + "time": "2022-10-28T06:00:21+00:00" }, { "name": "psr/http-message", @@ -2072,70 +2219,6 @@ ], "time": "2020-09-28T06:39:44+00:00" }, - { - "name": "spatie/fork", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/spatie/fork.git", - "reference": "d0232e94926c89e3bba7abbff8385140619bd826" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/fork/zipball/d0232e94926c89e3bba7abbff8385140619bd826", - "reference": "d0232e94926c89e3bba7abbff8385140619bd826", - "shasum": "" - }, - "require": { - "ext-pcntl": "*", - "ext-sockets": "*", - "php": "^8.0" - }, - "require-dev": { - "nesbot/carbon": "^2.47", - "phpunit/phpunit": "^9.5", - "spatie/ray": "^1.10" - }, - "type": "library", - "autoload": { - "psr-4": { - "Spatie\\Fork\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Brent Roose", - "email": "brent@spatie.be", - "role": "Developer" - }, - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "role": "Developer" - } - ], - "description": "A lightweight solution for running code concurrently in PHP", - "homepage": "https://github.com/spatie/fork", - "keywords": [ - "fork", - "spatie" - ], - "support": { - "issues": "https://github.com/spatie/fork/issues", - "source": "https://github.com/spatie/fork/tree/1.1.2" - }, - "funding": [ - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2022-10-03T13:44:48+00:00" - }, { "name": "symfony/css-selector", "version": "v5.4.11", diff --git a/lib/Catcher.php b/lib/Catcher.php index 2c8984a..b3e1e41 100644 --- a/lib/Catcher.php +++ b/lib/Catcher.php @@ -15,6 +15,9 @@ use MensBeam\Foundation\Catcher\{ class Catcher { + /** When set to true Catcher won't exit when instructed */ + public static $preventExit = false; + /** * Array of handlers the exceptions are passed to * @@ -46,7 +49,7 @@ class Catcher { return $this->handlers; } - public function getLastThrowable(): \Throwable { + public function getLastThrowable(): ?\Throwable { return $this->lastThrowable; } @@ -150,17 +153,12 @@ class Catcher { */ public function handleError(int $code, string $message, ?string $file = null, ?int $line = null): bool { if ($code !== 0 && error_reporting()) { - $error = new Error($message, $code, $file, $line); - if ($this->isShuttingDown) { - throw $error; - } else { - $this->handleThrowable($error); - } - + $this->handleThrowable(new Error($message, $code, $file, $line)); return true; } - - return false; + + // If preventing exit we don't want a false here to halt processing + return (self::$preventExit); } /** @@ -177,14 +175,14 @@ class Catcher { } $controlCode = $output->controlCode; - if ($controlCode !== Handler::CONTINUE) { + if ($controlCode & Handler::BREAK) { break; } } if ( $this->isShuttingDown || - $controlCode === Handler::EXIT || + $controlCode & Handler::EXIT || $throwable instanceof \Exception || ($throwable instanceof Error && in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ])) || (!$throwable instanceof Error && $throwable instanceof \Error) @@ -193,7 +191,13 @@ class Catcher { $h->dispatch(); } - exit($throwable->getCode()); + $this->lastThrowable = $throwable; + + // Don't want to exit here when shutting down so any shutdown functions further + // down the stack still run. + if (!self::$preventExit && !$this->isShuttingDown) { + $this->exit($throwable->getCode()); + } } $this->lastThrowable = $throwable; @@ -210,8 +214,22 @@ class Catcher { } $this->isShuttingDown = true; - if ($error = error_get_last() && in_array($error['type'], [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_CORE_WARNING, \E_COMPILE_ERROR, \E_COMPILE_WARNING ])) { - $this->handleError($error['type'], $error['message'], $error['file'], $error['line']); + if ($error = $this->getLastError()) { + if (in_array($error['type'], [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_CORE_WARNING, \E_COMPILE_ERROR, \E_COMPILE_WARNING ])) { + $this->handleError($error['type'], $error['message'], $error['file'], $error['line']); + } } } + + + /** Exists so the method may be replaced when mocking in tests */ + protected function exit(int $status): void { + // This won't be shown as executed in code coverage + exit($status); //@codeCoverageIgnore + } + + /** Exists so the method may be replaced when mocking in tests */ + protected function getLastError(): ?array { + return error_get_last(); + } } \ No newline at end of file diff --git a/lib/Catcher/Handler.php b/lib/Catcher/Handler.php index 5343887..8403d2f 100644 --- a/lib/Catcher/Handler.php +++ b/lib/Catcher/Handler.php @@ -15,7 +15,8 @@ abstract class Handler { // Control constants public const CONTINUE = 1; public const BREAK = 2; - public const EXIT = 4; + public const EXIT = 4; // What if this were a bitmask option like NOW? + public const STOP = 8; // Output constants public const OUTPUT = 16; @@ -45,8 +46,8 @@ abstract class Handler { * an error occurred */ protected string $_charset = 'UTF-8'; - /** If true the handler will continue onto the next handler regardless */ - protected bool $_forceContinue = false; + /** If true the handler will force break the loop through the stack of handlers */ + protected bool $_forceBreak = false; /** If true the handler will force an exit */ protected bool $_forceExit = false; /** @@ -150,11 +151,12 @@ abstract class Handler { }*/ protected function getControlCode(): int { - $code = self::BREAK; + $code = self::CONTINUE; + if ($this->_forceBreak) { + $code = self::BREAK; + } if ($this->_forceExit) { - $code = self::EXIT; - } elseif ($this->_forceContinue) { - $code = self::CONTINUE; + $code |= self::EXIT; } return $code; diff --git a/lib/Error.php b/lib/Error.php index 4d4d4d9..aefd1c4 100644 --- a/lib/Error.php +++ b/lib/Error.php @@ -9,7 +9,7 @@ declare(strict_types=1); namespace MensBeam\Foundation; class Error extends \Error { - public function __construct(string $message = '', int $code = 0, ?string $file = null, ?int $line = line, ?\Throwable $previous = null) { + public function __construct(string $message = '', int $code = 0, string $file = '', int $line = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->file = $file; $this->line = $line; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 4455739..0c649f0 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace MensBeam\Foundation; +use MensBeam\Foundation\Catcher; ini_set('memory_limit', '-1'); ini_set('zend.assertions', '1'); @@ -22,3 +23,5 @@ if (function_exists('xdebug_set_filter')) { xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [ "$cwd/lib/" ]); } } + +Catcher::$preventExit = true; \ No newline at end of file diff --git a/tests/cases/TestCatcher.php b/tests/cases/TestCatcher.php index 5b85e7a..bf94a62 100644 --- a/tests/cases/TestCatcher.php +++ b/tests/cases/TestCatcher.php @@ -7,13 +7,16 @@ declare(strict_types=1); namespace MensBeam\Foundation\Catcher\TestCase; -use MensBeam\Foundation\Catcher; +use MensBeam\Foundation\{ + Catcher, + Error +}; use MensBeam\Foundation\Catcher\{ PlainTextHandler, HTMLHandler, JSONHandler }; -use Spatie\Fork\Fork; +use Eloquent\Phony\Phpunit\Phony; class TestCatcher extends \PHPUnit\Framework\TestCase { @@ -284,16 +287,153 @@ class TestCatcher extends \PHPUnit\Framework\TestCase { } /** - * @covers \MensBeam\Foundation\Catcher::handleError() + * @covers \MensBeam\Foundation\Catcher::handleError * - * @covers \MensBeam\Foundation\Catcher::__construct() - * @covers \MensBeam\Foundation\Catcher::handleThrowable() - * @covers \MensBeam\Foundation\Catcher::register() - * @covers \MensBeam\Foundation\Catcher\Handler::__construct() + * @covers \MensBeam\Foundation\Catcher::__construct + * @covers \MensBeam\Foundation\Catcher::getLastThrowable + * @covers \MensBeam\Foundation\Catcher::exit + * @covers \MensBeam\Foundation\Catcher::handleThrowable + * @covers \MensBeam\Foundation\Catcher::pushHandler + * @covers \MensBeam\Foundation\Catcher::register + * @covers \MensBeam\Foundation\Catcher::unregister + * @covers \MensBeam\Foundation\Error::__construct + * @covers \MensBeam\Foundation\Catcher\Handler::__construct + * @covers \MensBeam\Foundation\Catcher\Handler::dispatch + * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode + * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode + * @covers \MensBeam\Foundation\Catcher\Handler::handle + * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable + * @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct + * @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious + * @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable */ - /*public function testMethod_handleError(): void { + public function testMethod_handleError(): void { $c = new Catcher(new PlainTextHandler([ 'silent' => true ])); + + trigger_error('Ook!', \E_USER_NOTICE); + $t = $c->getLastThrowable(); + $this->assertSame(Error::class, $t::class); + $this->assertSame(\E_USER_NOTICE, $t->getCode()); + + trigger_error('Ook!', \E_USER_DEPRECATED); + $t = $c->getLastThrowable(); + $this->assertSame(Error::class, $t::class); + $this->assertSame(\E_USER_DEPRECATED, $t->getCode()); + trigger_error('Ook!', \E_USER_WARNING); - $this->assertEquals(\E_USER_WARNING, $c->getLastThrowable()->getCode()); - }*/ + $t = $c->getLastThrowable(); + $this->assertSame(Error::class, $t::class); + $this->assertSame(\E_USER_WARNING, $t->getCode()); + + trigger_error('Ook!', \E_USER_ERROR); + $t = $c->getLastThrowable(); + $this->assertSame(Error::class, $t::class); + $this->assertSame(\E_USER_ERROR, $t->getCode()); + + $er = error_reporting(); + error_reporting(0); + trigger_error('Ook!', \E_USER_ERROR); + error_reporting($er); + + $c->unregister(); + } + + /** + * @covers \MensBeam\Foundation\Catcher::handleThrowable + * + * @covers \MensBeam\Foundation\Catcher::__construct + * @covers \MensBeam\Foundation\Catcher::getLastThrowable + * @covers \MensBeam\Foundation\Catcher::handleError + * @covers \MensBeam\Foundation\Catcher::pushHandler + * @covers \MensBeam\Foundation\Catcher::register + * @covers \MensBeam\Foundation\Catcher::unregister + * @covers \MensBeam\Foundation\Error::__construct + * @covers \MensBeam\Foundation\Catcher\Handler::__construct + * @covers \MensBeam\Foundation\Catcher\Handler::dispatch + * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode + * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode + * @covers \MensBeam\Foundation\Catcher\Handler::handle + * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable + * @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct + * @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious + * @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable + */ + public function testMethod_handleThrowable(): void { + $c = new Catcher(new PlainTextHandler([ 'silent' => true, 'forceBreak' => true ])); + trigger_error('Ook!', \E_USER_ERROR); + $t = $c->getLastThrowable(); + $this->assertSame(Error::class, $t::class); + $this->assertSame(\E_USER_ERROR, $t->getCode()); + $c->unregister(); + + Catcher::$preventExit = false; + $h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]); + $h->exit->returns(); + $c = $h->get(); + trigger_error('Ook!', \E_USER_ERROR); + $t = $c->getLastThrowable(); + $this->assertSame(Error::class, $t::class); + $this->assertSame(\E_USER_ERROR, $t->getCode()); + $c->unregister(); + Catcher::$preventExit = true; + } + + /** + * @covers \MensBeam\Foundation\Catcher::handleShutdown + * + * @covers \MensBeam\Foundation\Catcher::__construct + * @covers \MensBeam\Foundation\Catcher::getLastError + * @covers \MensBeam\Foundation\Catcher::handleError + * @covers \MensBeam\Foundation\Catcher::handleThrowable + * @covers \MensBeam\Foundation\Catcher::pushHandler + * @covers \MensBeam\Foundation\Catcher::register + * @covers \MensBeam\Foundation\Catcher::unregister + * @covers \MensBeam\Foundation\Error::__construct + * @covers \MensBeam\Foundation\Catcher\Handler::__construct + * @covers \MensBeam\Foundation\Catcher\Handler::dispatch + * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode + * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode + * @covers \MensBeam\Foundation\Catcher\Handler::handle + * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback + * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable + * @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct + * @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious + * @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable + */ + public function testMethod_handleShutdown(): void { + $c = new Catcher(); + $c->handleShutdown(); + $p = new \ReflectionProperty($c, 'isShuttingDown'); + $p->setAccessible(true); + $this->assertTrue($p->getValue($c)); + $c->unregister(); + + $c = new Catcher(); + $c->unregister(); + $c->handleShutdown(); + $p = new \ReflectionProperty($c, 'isShuttingDown'); + $p->setAccessible(true); + $this->assertFalse($p->getValue($c)); + + $h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]); + $h->getLastError->returns([ + 'type' => \E_ERROR, + 'message' => 'Ook!', + 'file' => '/dev/null', + 'line' => 2112 + ]); + $c = $h->get(); + $c->handleShutdown(); + $h->handleError->called(); + $h->handleThrowable->called(); + $c->unregister(); + } } \ No newline at end of file diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml index d8ab3fd..3095c48 100644 --- a/tests/phpunit.dist.xml +++ b/tests/phpunit.dist.xml @@ -8,7 +8,8 @@ convertNoticesToExceptions="false" convertWarningsToExceptions="false" beStrictAboutTestsThatDoNotTestAnything="true" - forceCoversAnnotation="true"> + forceCoversAnnotation="true" + stopOnError="false"> ../lib