Fixes --version and --help

This commit is contained in:
Nuno Maduro
2022-09-17 23:47:47 +01:00
parent 08b62f6633
commit 0e0e2adfbe
26 changed files with 511 additions and 86 deletions

View File

@ -1,7 +1,6 @@
1. TeamCity
2. Junit
3. Help display
4. Thanks display
5. Check all plugins
6. Parallel testing.
7. Finish Collision's todo
3. Thanks display
4. Check all plugins
5. Parallel testing.
6. Finish Collision's todo

View File

@ -19,7 +19,8 @@
"require": {
"php": "^8.1.0",
"nunomaduro/collision": "dev-next",
"pestphp/pest-plugin": "^1.0.0",
"nunomaduro/termwind": "^1.14",
"pestphp/pest-plugin": "dev-master",
"phpunit/phpunit": "10.0.x-dev"
},
"autoload": {
@ -95,6 +96,7 @@
"Pest\\Plugins\\Coverage",
"Pest\\Plugins\\Init",
"Pest\\Plugins\\Environment",
"Pest\\Plugins\\Help",
"Pest\\Plugins\\Memory",
"Pest\\Plugins\\Printer",
"Pest\\Plugins\\Retry",

View File

@ -0,0 +1,12 @@
<div class="flex mx-2 max-w-150">
<span>
<?php echo htmlspecialchars($left) ?>
</span>
<span class="flex-1 content-repeat-[.] text-gray ml-1"></span>
<?php if ($right !== '') { ?>
<span class="ml-1 text-gray">
<?php echo htmlspecialchars($right) ?>
</span>
<?php } ?>
</div>

View File

@ -0,0 +1,4 @@
<div class="mx-2">
<span class="text-blue font-bold">USAGE:</span><span class="ml-1">pest</span><span class="ml-1 text-gray"><?php echo htmlspecialchars('<file>') ?> [options]</span>
</div>

View File

@ -0,0 +1,3 @@
<div class="my-1 mx-2">
<span>Pest Testing Framework</span><span class="ml-1 text-blue font-bold"><?php echo htmlspecialchars($version) ?></span><span>.</span>
</div>

View File

@ -59,16 +59,18 @@ final class BootFiles
}
/**
* Loads the given filename, if possible.
* Loads, if possible, the given file.
*/
private function load(string $filename): void
{
if (! Str::endsWith($filename, '.php')) {
return;
}
if (! file_exists($filename)) {
return;
}
include_once $filename;
}
}

View File

@ -6,6 +6,7 @@ namespace Pest\Bootstrappers;
use Pest\Subscribers;
use PHPUnit\Event;
use PHPUnit\Event\Subscriber;
/**
* @internal
@ -15,7 +16,7 @@ final class BootSubscribers
/**
* The Kernel subscribers.
*
* @var array<int, class-string<\PHPUnit\Event\Subscriber>>
* @var array<int, class-string<Subscriber>>
*/
private const SUBSCRIBERS = [
Subscribers\EnsureConfigurationIsValid::class,

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Support\View;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class BootView
{
public function __construct(
private readonly OutputInterface $output
) {
// ..
}
/**
* Boots the view renderer.
*/
public function __invoke(): void
{
View::renderUsing($this->output);
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts\Plugins;
/**
* @internal
*/
interface Bootable
{
/**
* Boots the plugin.
*/
public function boot(): void;
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts\Plugins;
/**
* @internal
*/
interface Shutdownable
{
/**
* Shutdowns the plugin.
*/
public function shutdown(): void;
}

View File

@ -4,6 +4,10 @@ declare(strict_types=1);
namespace Pest;
use Pest\Plugins\Actions\CallsAddsOutput;
use Pest\Plugins\Actions\CallsBoot;
use Pest\Plugins\Actions\CallsShutdown;
use Pest\Support\Container;
use PHPUnit\TextUI\Application;
use PHPUnit\TextUI\Exception;
@ -21,6 +25,7 @@ final class Kernel
Bootstrappers\BootExceptionHandler::class,
Bootstrappers\BootSubscribers::class,
Bootstrappers\BootFiles::class,
Bootstrappers\BootView::class,
];
/**
@ -29,7 +34,13 @@ final class Kernel
public function __construct(
private readonly Application $application
) {
// ..
register_shutdown_function(function (): void {
if (error_get_last() !== null) {
return;
}
$this->shutdown();
});
}
/**
@ -38,9 +49,11 @@ final class Kernel
public static function boot(): self
{
foreach (self::BOOTSTRAPPERS as $bootstrapper) {
(new $bootstrapper())->__invoke();
Container::getInstance()->get($bootstrapper)->__invoke();
}
(new CallsBoot())->__invoke();
return new self(new Application());
}
@ -53,13 +66,13 @@ final class Kernel
*/
public function handle(array $argv): int
{
$argv = (new Plugins\Actions\HandleArguments())->__invoke($argv);
$argv = (new Plugins\Actions\CallsHandleArguments())->__invoke($argv);
$this->application->run(
$argv, false,
);
return (new Plugins\Actions\AddsOutput())->__invoke(
return (new CallsAddsOutput())->__invoke(
Result::exitCode(),
);
}
@ -69,6 +82,6 @@ final class Kernel
*/
public function shutdown(): void
{
// ..
(new CallsShutdown())->__invoke();
}
}

View File

@ -10,7 +10,7 @@ use Pest\Plugin\Loader;
/**
* @internal
*/
final class AddsOutput
final class CallsAddsOutput
{
/**
* Executes the Plugin action.

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins\Actions;
use Pest\Contracts\Plugins;
use Pest\Plugin\Loader;
/**
* @internal
*/
final class CallsBoot
{
/**
* Executes the Plugin action.
*
* Provides an opportunity for any plugins to boot..
*/
public function __invoke(): void
{
$plugins = Loader::getPlugins(Plugins\Bootable::class);
/** @var Plugins\Bootable $plugin */
foreach ($plugins as $plugin) {
$plugin->boot();
}
}
}

View File

@ -10,7 +10,7 @@ use Pest\Plugin\Loader;
/**
* @internal
*/
final class HandleArguments
final class CallsHandleArguments
{
/**
* Executes the Plugin action.

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins\Actions;
use Pest\Contracts\Plugins;
use Pest\Plugin\Loader;
/**
* @internal
*/
final class CallsShutdown
{
/**
* Executes the Plugin action.
*
* Provides an opportunity for any plugins to shutdown.
*/
public function __invoke(): void
{
$plugins = Loader::getPlugins(Plugins\Shutdownable::class);
/** @var Plugins\Shutdownable $plugin */
foreach ($plugins as $plugin) {
$plugin->shutdown();
}
}
}

105
src/Plugins/Help.php Normal file
View File

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins;
use Pest\Contracts\Plugins\HandlesArguments;
use Pest\Support\View;
use function Pest\version;
use PHPUnit\TextUI\Help as PHPUnitHelp;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class Help implements HandlesArguments
{
use Concerns\HandleArguments;
/**
* Creates a new Plugin instance.
*/
public function __construct(
private readonly OutputInterface $output
) {
// ..
}
/**
* {@inheritDoc}
*/
public function handleArguments(array $arguments): array
{
if ($this->hasArgument('--help', $arguments)) {
View::render('version', [
'version' => version(),
]);
View::render('usage');
foreach ($this->getContent() as $title => $options) {
if ($title === 'Usage') {
continue;
}
$this->output->writeln([
'',
sprintf(' <fg=blue;options=bold>%s:</>', mb_strtoupper($title)),
]);
foreach ($options as $option) {
if (! array_key_exists('arg', $option)) {
continue;
}
[
'arg' => $argument,
'desc' => $description,
] = $option;
View::render('components.two-column-detail', [
'left' => $argument,
'right' => $description,
]);
}
}
$this->output->write('', true);
exit(0);
}
return $arguments;
}
/**
* @return array<string, array<int, array{arg?: string, desc: string}>>
*/
private function getContent(): array
{
// Access the PHPUnit help class's private const HELP
$helpReflection = new \ReflectionClass(PHPUnitHelp::class);
/** @var array<string, array<int, array{arg: string, desc: string}>> $content */
$content = $helpReflection->getConstant('HELP_TEXT');
$content['Configuration'] = [[
'arg' => '--init',
'desc' => 'Initialise a standard Pest configuration',
]] + $content['Configuration'];
$content['Code Coverage'] = [
[
'arg' => '--coverage ',
'desc' => 'Generate code coverage report and output to standard output',
],
[
'arg' => '--coverage --min',
'desc' => 'Set the minimum required coverage percentage, and fail if not met',
],
] + $content['Code Coverage'];
return $content;
}
}

View File

@ -5,8 +5,8 @@ declare(strict_types=1);
namespace Pest\Plugins;
use Pest\Contracts\Plugins\HandlesArguments;
use Pest\Support\View;
use function Pest\version;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
@ -15,24 +15,17 @@ final class Version implements HandlesArguments
{
use Concerns\HandleArguments;
/**
* Creates a new Plugin instance.
*/
public function __construct(
private readonly OutputInterface $output
) {
// ..
}
/**
* {@inheritDoc}
*/
public function handleArguments(array $arguments): array
{
if ($this->hasArgument('--version', $arguments)) {
$this->output->writeln(
sprintf('Pest %s', version()),
);
View::render('version', [
'version' => version(),
]);
exit(0);
}
return $arguments;

View File

@ -35,16 +35,21 @@ final class Container
/**
* Gets a dependency from the container.
*
* @param class-string $id
* @return mixed
* @template TObject of object
*
* @param class-string<TObject> $id
* @return TObject
*/
public function get(string $id)
public function get(string $id): mixed
{
if (! array_key_exists($id, $this->instances)) {
$this->instances[$id] = $this->build($id);
}
return $this->instances[$id];
/** @var TObject $concrete */
$concrete = $this->instances[$id];
return $concrete;
}
/**
@ -58,9 +63,12 @@ final class Container
/**
* Tries to build the given instance.
*
* @param class-string $id
* @template TObject of object
*
* @param class-string<TObject> $id
* @return TObject
*/
private function build(string $id): object
private function build(string $id): mixed
{
$reflectionClass = new ReflectionClass($id);

71
src/Support/View.php Normal file
View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Pest\Support;
use Symfony\Component\Console\Output\OutputInterface;
use function Termwind\render;
use function Termwind\renderUsing;
use Termwind\Termwind;
/**
* @internal
*/
final class View
{
/**
* The implementation of the output.
*/
private static OutputInterface $output;
/**
* Renders views using the given Output instance.
*/
public static function renderUsing(OutputInterface $output): void
{
self::$output = $output;
}
/**
* Renders the given view.
*
* @param array<string, mixed> $data
*/
public static function render(string $path, array $data = []): void
{
$contents = self::compile($path, $data);
$existing = Termwind::getRenderer();
renderUsing(self::$output);
try {
render($contents);
} finally {
renderUsing($existing);
}
}
/**
* Compiles the given view.
*
* @param array<string, mixed> $data
*/
private static function compile(string $path, array $data): string
{
extract($data);
ob_start();
$path = str_replace('.', '/', $path);
include sprintf('%s/../../resources/views/%s.php', __DIR__, $path);
$contents = ob_get_contents();
ob_clean();
return (string) $contents;
}
}

View File

@ -1,5 +1,103 @@
Pest Options:
--init Initialise a standard Pest configuration
--coverage Enable coverage and output to standard output
--min=<N> Set the minimum required coverage percentage (<N>), and fail if not met
--group=<name> Only runs tests from the specified group(s)
Pest Testing Framework 2.x-dev.
USAGE: pest <file> [options]
CONFIGURATION:
--init ............................ Initialise a standard Pest configuration
-c|--configuration <file> ....................... Read configuration from XML file
--no-configuration ......... Ignore default configuration file (phpunit.xml)
--no-extensions ............................. Do not load PHPUnit extensions
--include-path <path(s)> ..... Prepend PHP's include_path with given path(s)
-d <key[=value]> ...................................... Sets a php.ini value
--cache-directory <dir> ................................. Specify cache directory
--generate-configuration Generate configuration file with suggested settings
--migrate-configuration ....... Migrate configuration file to current format
SELECTION:
--list-suites ................................... List available test suites
--testsuite <name> ............... Only run tests from the specified test suite(s)
--exclude-testsuite <name> ........ Exclude tests from the specified test suite(s)
--list-groups ................................... List available test groups
--group <name> ........................ Only run tests from the specified group(s)
--exclude-group <name> ................. Exclude tests from the specified group(s)
--covers <name> ......................... Only run tests annotated with "@covers <name>"
--uses <name> ............................. Only run tests annotated with "@uses <name>"
--list-tests .......................................... List available tests
--list-tests-xml <file> ....................... List available tests in XML format
--filter <pattern> ........................................ Filter which tests to run
--test-suffix <suffixes> Only search for test in files with specified suffix(es). Default: Test.php,.phpt
EXECUTION:
--process-isolation ................ Run each test in a separate PHP process
--globals-backup ................. Backup and restore $GLOBALS for each test
--static-backup ......... Backup and restore static properties for each test
--strict-coverage . Be strict about code coverage attributes and annotations
--strict-global-state .............. Be strict about changes to global state
--disallow-test-output ................. Be strict about output during tests
--enforce-time-limit ................. Enforce time limit based on test size
--default-time-limit <sec> Timeout in seconds for tests that have no declared size
--dont-report-useless-tests .. Do not report tests that do not test anything
--stop-on-defect ................. Stop execution upon first not-passed test
--stop-on-error ............................ Stop execution upon first error
--stop-on-failure ............... Stop execution upon first error or failure
--stop-on-warning ........................ Stop execution upon first warning
--stop-on-risky ....................... Stop execution upon first risky test
--stop-on-skipped ................... Stop execution upon first skipped test
--stop-on-incomplete ............. Stop execution upon first incomplete test
--fail-on-incomplete .................... Treat incomplete tests as failures
--fail-on-risky .............................. Treat risky tests as failures
--fail-on-skipped .......................... Treat skipped tests as failures
--fail-on-warning .................... Treat tests with warnings as failures
--repeat <times> ...................................... Runs the test(s) repeatedly
--cache-result ............................ Write test results to cache file
--do-not-cache-result .............. Do not write test results to cache file
--order-by <order> Run tests in order: default|defects|duration|no-depends|random|reverse|size
--random-order-seed <N> .......... Use a specific random seed <N> for random order
REPORTING:
--colors <flag> ............... Use colors in output ("never", "auto" or "always")
--columns <n> .................... Number of columns to use for progress output
--columns max ............ Use maximum number of columns for progress output
--stderr ................................. Write to STDERR instead of STDOUT
--display-incomplete .................. Display details for incomplete tests
--display-skipped ........................ Display details for skipped tests
--display-deprecations . Display details for deprecations triggered by tests
--display-errors ............. Display details for errors triggered by tests
--display-notices ........... Display details for notices triggered by tests
--display-warnings ......... Display details for warnings triggered by tests
--reverse-list .............................. Print defects in reverse order
--teamcity ............... Report test execution progress in TeamCity format
--testdox ................. Report test execution progress in TestDox format
--no-interaction ........................ Disable TestDox progress animation
LOGGING:
--log-junit <file> ................ Log test execution in JUnit XML format to file
--log-teamcity <file> .............. Log test execution in TeamCity format to file
--testdox-html <file> ........... Write agile documentation in HTML format to file
--testdox-text <file> ........... Write agile documentation in Text format to file
--testdox-xml <file> ............. Write agile documentation in XML format to file
--log-events-text <file> ..................... Stream events as plain text to file
--log-events-verbose-text <file> Stream events as plain text to file (with telemetry information)
--no-logging .................................. Ignore logging configuration
CODE COVERAGE:
--coverage ..... Generate code coverage report and output to standard output
--coverage --min Set the minimum required coverage percentage, and fail if not met
--coverage-crap4j <file> ...... Generate code coverage report in Crap4J XML format
--coverage-html <dir> .............. Generate code coverage report in HTML format
--coverage-php <file> ..................... Export PHP_CodeCoverage object to file
--coverage-text=<file> Generate code coverage report in text format [default: standard output]
--coverage-xml <dir> ........ Generate code coverage report in PHPUnit XML format
--warm-coverage-cache ........................... Warm static analysis cache
--coverage-filter <dir> ...................... Include <dir> in code coverage analysis
--path-coverage ............................. Perform path coverage analysis
--disable-coverage-ignore Disable attributes and annotations for ignoring code coverage
--no-coverage ........................... Ignore code coverage configuration
MISCELLANEOUS:
-h|--help .................................... Prints this usage information
--version ..................................... Prints the version and exits
--atleast-version <min> ....... Checks that version is greater than min and exits
--check-version ................ Check whether PHPUnit is the latest version

View File

@ -729,10 +729,6 @@
PASS Tests\Unit\Plugins\Retry
✓ it retries if --retry argument is used
PASS Tests\Unit\Plugins\Version
✓ it outputs the version when --version is used
✓ it do not outputs version when --version is not used
PASS Tests\Unit\Support\Backtrace
✓ it gets file name from called file
@ -756,8 +752,8 @@
✓ it can filter the test suite filenames to those with the only method
✓ it does not filter the test suite filenames to those with the only method when working in CI pipeline
WARN Tests\Visual\Help
- visual snapshot of help command output → Not supported yet.
PASS Tests\Visual\Help
visual snapshot of help command output
WARN Tests\Visual\JUnit
- it is can successfully call all public methods → Not supported yet.
@ -773,4 +769,7 @@
WARN Tests\Visual\TeamCity
- it is can successfully call all public methods → Not supported yet.
Tests: 4 incomplete, 1 todo, 19 skipped, 514 passed (1289 assertions)
PASS Tests\Visual\Version
✓ visual snapshot of help command output
Tests: 4 incomplete, 1 todo, 18 skipped, 514 passed (1289 assertions)

View File

@ -0,0 +1,3 @@
Pest Testing Framework 2.x-dev.

View File

@ -11,19 +11,19 @@ it('adds coverage if --coverage exist', function () {
expect($plugin->coverage)->toBeFalse();
$arguments = $plugin->handleArguments([]);
expect($arguments)->toEqual([]);
expect($plugin->coverage)->toBeFalse();
expect($arguments)->toEqual([])
->and($plugin->coverage)->toBeFalse();
$arguments = $plugin->handleArguments(['--coverage']);
expect($arguments)->toEqual(['--coverage-php', Coverage::getPath()]);
expect($plugin->coverage)->toBeTrue();
expect($arguments)->toEqual(['--coverage-php', Coverage::getPath()])
->and($plugin->coverage)->toBeTrue();
})->skip(! \Pest\Support\Coverage::isAvailable() || ! in_array('coverage', xdebug_info('mode'), true), 'Coverage is not available');
it('adds coverage if --min exist', function () {
$plugin = new CoveragePlugin(new ConsoleOutput());
expect($plugin->coverageMin)->toEqual(0.0);
expect($plugin->coverageMin)->toEqual(0.0)
->and($plugin->coverage)->toBeFalse();
expect($plugin->coverage)->toBeFalse();
$plugin->handleArguments([]);
expect($plugin->coverageMin)->toEqual(0.0);

View File

@ -1,21 +0,0 @@
<?php
use Pest\Plugins\Version;
use function Pest\version;
use Symfony\Component\Console\Output\BufferedOutput;
it('outputs the version when --version is used', function () {
$output = new BufferedOutput();
$plugin = new Version($output);
$plugin->handleArguments(['foo', '--version']);
expect($output->fetch())->toContain('Pest '.version());
});
it('do not outputs version when --version is not used', function () {
$output = new BufferedOutput();
$plugin = new Version($output);
$plugin->handleArguments(['foo', 'bar']);
expect($output->fetch())->toBe('');
});

View File

@ -1,20 +1,8 @@
<?php
use Pest\Console\Help;
use Symfony\Component\Console\Output\BufferedOutput;
test('visual snapshot of help command output', function () {
$snapshot = __DIR__.'/../.snapshots/help-command.txt';
if (getenv('REBUILD_SNAPSHOTS')) {
$outputBuffer = new BufferedOutput();
$plugin = new Help($outputBuffer);
$plugin();
file_put_contents($snapshot, $outputBuffer->fetch());
}
$output = function () {
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest', '--help'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true']));
@ -23,5 +11,11 @@ test('visual snapshot of help command output', function () {
return preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput());
};
if (getenv('REBUILD_SNAPSHOTS')) {
file_put_contents($snapshot, $output());
$this->markTestSkipped('Snapshot rebuilt.');
}
expect($output())->toContain(file_get_contents($snapshot));
})->skip(PHP_OS_FAMILY === 'Windows')->skip('Not supported yet.');
})->skip(PHP_OS_FAMILY === 'Windows');

21
tests/Visual/Version.php Normal file
View File

@ -0,0 +1,21 @@
<?php
test('visual snapshot of help command output', function () {
$snapshot = __DIR__.'/../.snapshots/version-command.txt';
$output = function () {
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest', '--version'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true']));
$process->run();
return preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput());
};
if (getenv('REBUILD_SNAPSHOTS')) {
file_put_contents($snapshot, $output());
$this->markTestSkipped('Snapshot rebuilt.');
}
expect($output())->toContain(file_get_contents($snapshot));
})->skip(PHP_OS_FAMILY === 'Windows');