feat: improves fatal exception handling

This commit is contained in:
Nuno Maduro
2024-01-25 21:47:28 +00:00
parent 62d8459627
commit c5ce355f3c
8 changed files with 79 additions and 23 deletions

View File

@ -90,7 +90,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
$result = $kernel->handle($originalArguments, $arguments); $result = $kernel->handle($originalArguments, $arguments);
$kernel->shutdown(); $kernel->terminate();
} catch (Throwable|Error $e) { } catch (Throwable|Error $e) {
Panic::with($e); Panic::with($e);
} }

View File

@ -7,10 +7,10 @@ namespace Pest\Contracts\Plugins;
/** /**
* @internal * @internal
*/ */
interface Shutdownable interface Terminable
{ {
/** /**
* Shutdowns the plugin. * Terminates the plugin.
*/ */
public function shutdown(): void; public function terminate(): void;
} }

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use RuntimeException;
/**
* @internal
*/
final class FatalException extends RuntimeException implements RenderlessTrace
{
//
}

View File

@ -4,19 +4,25 @@ declare(strict_types=1);
namespace Pest; namespace Pest;
use NunoMaduro\Collision\Writer;
use Pest\Contracts\Bootstrapper; use Pest\Contracts\Bootstrapper;
use Pest\Exceptions\FatalException;
use Pest\Exceptions\NoDirtyTestsFound; use Pest\Exceptions\NoDirtyTestsFound;
use Pest\Plugins\Actions\CallsAddsOutput; use Pest\Plugins\Actions\CallsAddsOutput;
use Pest\Plugins\Actions\CallsBoot; use Pest\Plugins\Actions\CallsBoot;
use Pest\Plugins\Actions\CallsHandleArguments; use Pest\Plugins\Actions\CallsHandleArguments;
use Pest\Plugins\Actions\CallsHandleOriginalArguments; use Pest\Plugins\Actions\CallsHandleOriginalArguments;
use Pest\Plugins\Actions\CallsShutdown; use Pest\Plugins\Actions\CallsTerminable;
use Pest\Support\Container; use Pest\Support\Container;
use Pest\Support\Reflection;
use Pest\Support\View;
use PHPUnit\TestRunner\TestResult\Facade; use PHPUnit\TestRunner\TestResult\Facade;
use PHPUnit\TextUI\Application; use PHPUnit\TextUI\Application;
use PHPUnit\TextUI\Configuration\Registry; use PHPUnit\TextUI\Configuration\Registry;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use Whoops\Exception\Inspector;
/** /**
* @internal * @internal
@ -44,7 +50,7 @@ final class Kernel
private readonly Application $application, private readonly Application $application,
private readonly OutputInterface $output, private readonly OutputInterface $output,
) { ) {
register_shutdown_function(fn () => $this->shutdown()); //
} }
/** /**
@ -65,6 +71,8 @@ final class Kernel
$output, $output,
); );
register_shutdown_function(fn () => $kernel->shutdown());
foreach (self::BOOTSTRAPPERS as $bootstrapper) { foreach (self::BOOTSTRAPPERS as $bootstrapper) {
$bootstrapper = Container::getInstance()->get($bootstrapper); $bootstrapper = Container::getInstance()->get($bootstrapper);
assert($bootstrapper instanceof Bootstrapper); assert($bootstrapper instanceof Bootstrapper);
@ -110,16 +118,48 @@ final class Kernel
} }
/** /**
* Shutdown the Kernel. * Terminate the Kernel.
*/ */
public function shutdown(): void public function terminate(): void
{ {
$preBufferOutput = Container::getInstance()->get(KernelDump::class); $preBufferOutput = Container::getInstance()->get(KernelDump::class);
assert($preBufferOutput instanceof KernelDump); assert($preBufferOutput instanceof KernelDump);
$preBufferOutput->shutdown(); $preBufferOutput->terminate();
CallsShutdown::execute(); CallsTerminable::execute();
}
/**
* Shutdowns unexpectedly the Kernel.
*/
public function shutdown(): void
{
$this->terminate();
if (is_array($error = error_get_last())) {
$message = $error['message'];
$file = $error['file'];
$line = $error['line'];
try {
$writer = new Writer(null, $this->output);
$throwable = new FatalException($message);
Reflection::setPropertyValue($throwable, 'line', $line);
Reflection::setPropertyValue($throwable, 'file', $file);
$inspector = new Inspector($throwable);
$writer->write($inspector);
} catch (Throwable) { // @phpstan-ignore-line
View::render('components.badge', [
'type' => 'ERROR',
'content' => sprintf('%s in %s:%d', $message, $file, $line),
]);
}
}
} }
} }

View File

@ -48,9 +48,9 @@ final class KernelDump
} }
/** /**
* Shutdown the output buffering. * Terminate the output buffering.
*/ */
public function shutdown(): void public function terminate(): void
{ {
$this->disable(); $this->disable();
} }

View File

@ -10,20 +10,20 @@ use Pest\Plugin\Loader;
/** /**
* @internal * @internal
*/ */
final class CallsShutdown final class CallsTerminable
{ {
/** /**
* Executes the Plugin action. * Executes the Plugin action.
* *
* Provides an opportunity for any plugins to shutdown. * Provides an opportunity for any plugins to terminate.
*/ */
public static function execute(): void public static function execute(): void
{ {
$plugins = Loader::getPlugins(Plugins\Shutdownable::class); $plugins = Loader::getPlugins(Plugins\Terminable::class);
/** @var Plugins\Shutdownable $plugin */ /** @var Plugins\Terminable $plugin */
foreach ($plugins as $plugin) { foreach ($plugins as $plugin) {
$plugin->shutdown(); $plugin->terminate();
} }
} }
} }

View File

@ -36,11 +36,11 @@ final class Cache implements HandlesArguments
{ {
if (! $this->hasArgument('--cache-directory', $arguments)) { if (! $this->hasArgument('--cache-directory', $arguments)) {
$cliConfiguration = (new CliConfigurationBuilder)->fromParameters([]); $cliConfiguration = (new CliConfigurationBuilder)->fromParameters([]);
$configurationFile = (new XmlConfigurationFileFinder)->find($cliConfiguration); $configurationFile = (new XmlConfigurationFileFinder)->find($cliConfiguration);
$xmlConfiguration = DefaultConfiguration::create(); $xmlConfiguration = DefaultConfiguration::create();
if ($configurationFile) { if (is_string($configurationFile)) {
$xmlConfiguration = (new Loader)->load($configurationFile); $xmlConfiguration = (new Loader)->load($configurationFile);
} }

View File

@ -4,13 +4,13 @@ declare(strict_types=1);
namespace Pest\Plugins; namespace Pest\Plugins;
use Pest\Contracts\Plugins\Shutdownable; use Pest\Contracts\Plugins\Terminable;
use Pest\PendingCalls\TestCall; use Pest\PendingCalls\TestCall;
/** /**
* @internal * @internal
*/ */
final class Only implements Shutdownable final class Only implements Terminable
{ {
/** /**
* The temporary folder. * The temporary folder.
@ -26,7 +26,7 @@ final class Only implements Shutdownable
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function shutdown(): void public function terminate(): void
{ {
$lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock'; $lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock';