Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/Bootstrappers/BootFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ final class BootFiles implements Bootstrapper

public function boot(): void
{
if ($this->runningInit()) {
return;
}

$rootPath = TestSuite::getInstance()->rootPath;
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();

Expand Down Expand Up @@ -62,6 +66,20 @@ public function boot(): void
$this->bootDatasets($testsPath);
}

/**
* `--init` creates the tests directory, so it cannot be required yet.
*/
private function runningInit(): bool
{
$arguments = $_SERVER['argv'] ?? null;

if (! is_array($arguments)) {
return false;
}

return ($arguments[1] ?? null) === '--init';
}

private function load(string $filename): void
{
if (! Str::endsWith($filename, '.php')) {
Expand Down
8 changes: 7 additions & 1 deletion src/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ final class Kernel
{
private bool $terminated = false;

private bool $booted = false;

/**
* @var array<int, class-string>
*/
Expand Down Expand Up @@ -81,6 +83,8 @@ public static function boot(TestSuite $testSuite, InputInterface $input, OutputI

CallsBoot::execute();

$kernel->booted = true;

Container::getInstance()->add(self::class, $kernel);

return $kernel;
Expand Down Expand Up @@ -137,7 +141,9 @@ public function terminate(): void

public function shutdown(): void
{
$this->terminate();
if ($this->booted) {
$this->terminate();
}

if (is_array($error = error_get_last())) {
if (! in_array($error['type'], [E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR], true)) {
Expand Down
135 changes: 135 additions & 0 deletions tests/Features/Init.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

declare(strict_types=1);

use Symfony\Component\Process\Process;

test('init creates a suite when the tests directory is missing', function (): void {
$root = makeInitProject();

try {
$process = runInitProject($root, '--init');

expect($process->getExitCode())->toBe(0)
->and($process->getErrorOutput())->not->toContain('cannot be resolved')
->and($process->getOutput())->toContain('File created.')
->and(is_file($root.DIRECTORY_SEPARATOR.'phpunit.xml'))->toBeTrue()
->and(is_file($root.DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'Pest.php'))->toBeTrue()
->and(is_file($root.DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'TestCase.php'))->toBeTrue()
->and(is_file($root.DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'Unit'.DIRECTORY_SEPARATOR.'ExampleTest.php'))->toBeTrue();
} finally {
removeInitProject($root);
}
});

test('a missing tests directory does not crash while resolving tia state', function (): void {
$root = makeInitProject();

try {
$process = runInitProject($root, '--version');

expect($process->getExitCode())->not->toBe(0)
->and($process->getOutput())->toContain('does not exist')
->and($process->getErrorOutput())->not->toContain('cannot be resolved')
->and($process->getOutput())->not->toContain('cannot be resolved');
} finally {
removeInitProject($root);
}
});

function makeInitProject(): string
{
$root = sys_get_temp_dir().DIRECTORY_SEPARATOR.'pest-init-'.bin2hex(random_bytes(4));

mkdir($root.DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'bin', 0777, true);

copy(
dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'pest-plugins.json',
$root.DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'pest-plugins.json',
);

$autoload = var_export(
dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php',
true,
);

file_put_contents($root.DIRECTORY_SEPARATOR.'run.php', <<<PHP
<?php

declare(strict_types=1);

use Pest\Kernel;
use Pest\Panic;
use Pest\TestSuite;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

require {$autoload};

\$command = \$argv[1] ?? '--version';

\$GLOBALS['_composer_bin_dir'] = getcwd().'/vendor/bin';

\$_SERVER['argv'] = ['pest', \$command];
\$_SERVER['COLLISION_PRINTER'] = 'DefaultPrinter';

\$input = new ArgvInput(['pest', \$command]);
\$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, false);

\$testSuite = TestSuite::getInstance(getcwd(), 'tests');

try {
\$kernel = Kernel::boot(\$testSuite, \$input, \$output);
\$result = \$kernel->handle(\$_SERVER['argv'], \$_SERVER['argv']);
\$kernel->terminate();
} catch (Throwable \$throwable) {
Panic::with(\$throwable);
}

exit(\$result);
PHP);

return $root;
}

function runInitProject(string $root, string $command): Process
{
$process = new Process(
[PHP_BINARY, 'run.php', $command],
$root,
[
'PEST_NO_SUPPORT' => 'true',
'COLLISION_PRINTER' => 'DefaultPrinter',
'COLLISION_IGNORE_DURATION' => 'true',
],
);

$process->setTimeout(60);
$process->run();

return $process;
}

function removeInitProject(string $root): void
{
if (! is_dir($root)) {
return;
}

$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST,
);

foreach ($items as $item) {
if ($item->isDir() && ! $item->isLink()) {
rmdir($item->getPathname());

continue;
}

unlink($item->getPathname());
}

rmdir($root);
}