Skip to content

Commit

Permalink
Improve the reporting of errors during the loading and bootstrapping …
Browse files Browse the repository at this point in the history
…of test runner extensions
  • Loading branch information
sebastianbergmann committed Jun 1, 2023
1 parent 23f779f commit f7b6a5d
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 155 deletions.
5 changes: 0 additions & 5 deletions .psalm/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -507,11 +507,6 @@
<code>stop</code>
</PossiblyNullReference>
</file>
<file src="src/Runner/Exception/ClassCannotBeInstantiatedException.php">
<PossiblyInvalidArgument>
<code><![CDATA[$previous->getCode()]]></code>
</PossiblyInvalidArgument>
</file>
<file src="src/Runner/Filter/GroupFilterIterator.php">
<MissingTemplateParam>
<code>GroupFilterIterator</code>
Expand Down
4 changes: 4 additions & 0 deletions ChangeLog-10.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ All notable changes of the PHPUnit 10.2 release series are documented in this fi
* [#5328](https://github.com/sebastianbergmann/phpunit/issues/5328): Optionally ignore suppression of deprecations, notices, and warnings
* `PHPUnit\Event\Test\DataProviderMethodCalled` and `PHPUnit\Event\Test\DataProviderMethodFinished` events

### Changed

* Improved the reporting of errors during the loading and bootstrapping of test runner extensions

### Deprecated

* `PHPUnit\TextUI\Configuration\Configuration::restrictDeprecations()` (use `source()->restrictDeprecations()` instead)
Expand Down
33 changes: 0 additions & 33 deletions src/Runner/Exception/ClassCannotBeInstantiatedException.php

This file was deleted.

29 changes: 0 additions & 29 deletions src/Runner/Exception/ClassDoesNotExistException.php

This file was deleted.

This file was deleted.

37 changes: 28 additions & 9 deletions src/Runner/Extension/ExtensionBootstrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@
use function class_exists;
use function class_implements;
use function in_array;
use function sprintf;
use PHPUnit\Event;
use PHPUnit\Runner\ClassCannotBeInstantiatedException;
use PHPUnit\Runner\ClassDoesNotExistException;
use PHPUnit\Runner\ClassDoesNotImplementExtensionInterfaceException;
use PHPUnit\Runner\Exception;
use PHPUnit\Event\Facade as EventFacade;
use PHPUnit\TextUI\Configuration\Configuration;
use ReflectionClass;
use Throwable;
Expand All @@ -39,23 +37,44 @@ public function __construct(Configuration $configuration, Facade $facade)
/**
* @psalm-param class-string $className
* @psalm-param array<string, string> $parameters
*
* @throws Exception
*/
public function bootstrap(string $className, array $parameters): void
{
if (!class_exists($className)) {
throw new ClassDoesNotExistException($className);
EventFacade::emitter()->testRunnerTriggeredWarning(
sprintf(
'Cannot bootstrap extension because class %s does not exist',
$className,
),
);

return;
}

if (!in_array(Extension::class, class_implements($className), true)) {
throw new ClassDoesNotImplementExtensionInterfaceException($className);
EventFacade::emitter()->testRunnerTriggeredWarning(
sprintf(
'Cannot bootstrap extension because class %s does not implement interface %s',
$className,
Extension::class,
),
);

return;
}

try {
$instance = (new ReflectionClass($className))->newInstance();
} catch (Throwable $t) {
throw new ClassCannotBeInstantiatedException($className, $t);
EventFacade::emitter()->testRunnerTriggeredWarning(
sprintf(
'Cannot bootstrap extension because class %s cannot be instantiated: %s',
$className,
$t->getMessage(),
),
);

return;
}

assert($instance instanceof Extension);
Expand Down
61 changes: 42 additions & 19 deletions src/Runner/Extension/PharLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use function extension_loaded;
use function implode;
use function is_file;
use function sprintf;
use function str_contains;
use PharIo\Manifest\ApplicationName;
use PharIo\Manifest\Exception as ManifestException;
Expand All @@ -30,30 +31,32 @@
final class PharLoader
{
/**
* @psalm-return array{loadedExtensions: list<string>, notLoadedExtensions: list<string>}
* @psalm-return list<string>
*/
public function loadPharExtensionsInDirectory(string $directory): array
{
$pharExtensionLoaded = extension_loaded('phar');

if (!$pharExtensionLoaded) {
Event\Facade::emitter()->testRunnerTriggeredWarning(
'Loading PHPUnit extension(s) from PHP archive(s) failed, PHAR extension not loaded',
);
}

$loadedExtensions = [];
$notLoadedExtensions = [];

foreach ((new FileIteratorFacade)->getFilesAsArray($directory, '.phar') as $file) {
if (!$pharExtensionLoaded) {
$notLoadedExtensions[] = $file . ' cannot be loaded';
Event\Facade::emitter()->testRunnerTriggeredWarning(
sprintf(
'Cannot load extension from %s because the PHAR extension is not available',
$file,
),
);

continue;
}

if (!is_file('phar://' . $file . '/manifest.xml')) {
$notLoadedExtensions[] = $file . ' is not an extension for PHPUnit';
Event\Facade::emitter()->testRunnerTriggeredWarning(
sprintf(
'%s is not an extension for PHPUnit',
$file,
),
);

continue;
}
Expand All @@ -64,18 +67,35 @@ public function loadPharExtensionsInDirectory(string $directory): array
$manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml');

if (!$manifest->isExtensionFor($applicationName)) {
$notLoadedExtensions[] = $file . ' is not an extension for PHPUnit';
Event\Facade::emitter()->testRunnerTriggeredWarning(
sprintf(
'%s is not an extension for PHPUnit',
$file,
),
);

continue;
}

if (!$manifest->isExtensionFor($applicationName, $version)) {
$notLoadedExtensions[] = $file . ' is not compatible with this version of PHPUnit';
Event\Facade::emitter()->testRunnerTriggeredWarning(
sprintf(
'%s is not compatible with PHPUnit %s',
$file,
Version::series(),
),
);

continue;
}
} catch (ManifestException $e) {
$notLoadedExtensions[] = $file . ': ' . $e->getMessage();
Event\Facade::emitter()->testRunnerTriggeredWarning(
sprintf(
'Cannot load extension from %s: %s',
$file,
$e->getMessage(),
),
);

continue;
}
Expand All @@ -84,7 +104,13 @@ public function loadPharExtensionsInDirectory(string $directory): array
/** @psalm-suppress UnresolvableInclude */
@require $file;
} catch (Throwable $t) {
$notLoadedExtensions[] = $file . ': ' . $t->getMessage();
Event\Facade::emitter()->testRunnerTriggeredWarning(
sprintf(
'Cannot load extension from %s: %s',
$file,
$t->getMessage(),
),
);

continue;
}
Expand All @@ -98,10 +124,7 @@ public function loadPharExtensionsInDirectory(string $directory): array
);
}

return [
'loadedExtensions' => $loadedExtensions,
'notLoadedExtensions' => $notLoadedExtensions,
];
return $loadedExtensions;
}

private function phpunitVersion(): string
Expand Down
29 changes: 6 additions & 23 deletions src/TextUI/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -364,19 +364,10 @@ private function bootstrapExtensions(Configuration $configuration): array
);

foreach ($configuration->extensionBootstrappers() as $bootstrapper) {
try {
$extensionBootstrapper->bootstrap(
$bootstrapper['className'],
$bootstrapper['parameters'],
);
} catch (\PHPUnit\Runner\Exception $e) {
$this->exitWithErrorMessage(
sprintf(
'Error while bootstrapping extension: %s',
$e->getMessage(),
),
);
}
$extensionBootstrapper->bootstrap(
$bootstrapper['className'],
$bootstrapper['parameters'],
);
}

return [
Expand Down Expand Up @@ -478,23 +469,15 @@ private function writeRuntimeInformation(Printer $printer, Configuration $config
}

/**
* @psalm-param ?array{loadedExtensions: list<string>, notLoadedExtensions: list<string>} $pharExtensions
* @psalm-param ?list<string> $pharExtensions
*/
private function writePharExtensionInformation(Printer $printer, ?array $pharExtensions): void
{
if ($pharExtensions === null) {
return;
}

foreach ($pharExtensions['loadedExtensions'] as $extension) {
$this->writeMessage(
$printer,
'Extension',
$extension,
);
}

foreach ($pharExtensions['notLoadedExtensions'] as $extension) {
foreach ($pharExtensions as $extension) {
$this->writeMessage(
$printer,
'Extension',
Expand Down
15 changes: 13 additions & 2 deletions tests/end-to-end/extension/class-cannot-be-instantiated.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ Test runner exits with error when configured extension class cannot be instantia
--FILE--
<?php declare(strict_types=1);
$_SERVER['argv'][] = '--do-not-cache-result';
$_SERVER['argv'][] = '--no-output';
$_SERVER['argv'][] = '--configuration';
$_SERVER['argv'][] = __DIR__ . '/_files/class-cannot-be-instantiated/phpunit.xml';

Expand All @@ -13,4 +12,16 @@ require __DIR__ . '/../../bootstrap.php';
--EXPECTF--
PHPUnit %s by Sebastian Bergmann and contributors.

Error while bootstrapping extension: Class "PHPUnit\TestFixture\Event\MyExtension\MyExtensionBootstrap" cannot be instantiated: message
Runtime: %s
Configuration: %s

. 1 / 1 (100%)

Time: %s, Memory: %s

There was 1 PHPUnit test runner warning:

1) Cannot bootstrap extension because class PHPUnit\TestFixture\Event\MyExtension\MyExtensionBootstrap cannot be instantiated: message

WARNINGS!
Tests: 1, Assertions: 1, Warnings: 1.
Loading

0 comments on commit f7b6a5d

Please sign in to comment.