parent
560ea46692
commit
09b99e357d
1 changed files with 155 additions and 0 deletions
@ -0,0 +1,155 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace TypePhp\Tests; |
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase; |
||||||
|
|
||||||
|
/** |
||||||
|
* Verifies the process exit code of AOT-compiled binaries. |
||||||
|
* |
||||||
|
* PHP CLI semantics require: |
||||||
|
* - explicit exit(N) -> process exits with N |
||||||
|
* - uncaught exception -> process exits with 255 |
||||||
|
* - fatal error -> process exits with 255 |
||||||
|
* - normal completion -> process exits with 0 |
||||||
|
*/ |
||||||
|
class ExitCodeTest extends TestCase |
||||||
|
{ |
||||||
|
private string $workDir; |
||||||
|
|
||||||
|
protected function setUp(): void |
||||||
|
{ |
||||||
|
parent::setUp(); |
||||||
|
$this->workDir = sys_get_temp_dir() . '/typephp-exitcode-' . bin2hex(random_bytes(6)); |
||||||
|
mkdir($this->workDir, 0777, true); |
||||||
|
} |
||||||
|
|
||||||
|
protected function tearDown(): void |
||||||
|
{ |
||||||
|
$this->removeDirectory($this->workDir); |
||||||
|
parent::tearDown(); |
||||||
|
} |
||||||
|
|
||||||
|
public function testExplicitExitCodeIsPreserved(): void |
||||||
|
{ |
||||||
|
$exitCode = $this->buildAndRun(<<<'PHP' |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
exit(3); |
||||||
|
} |
||||||
|
PHP); |
||||||
|
|
||||||
|
$this->assertSame(3, $exitCode); |
||||||
|
} |
||||||
|
|
||||||
|
public function testUncaughtExceptionExitsWith255(): void |
||||||
|
{ |
||||||
|
$exitCode = $this->buildAndRun(<<<'PHP' |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
throw new RuntimeException('intentional uncaught exception'); |
||||||
|
} |
||||||
|
PHP); |
||||||
|
|
||||||
|
$this->assertSame(255, $exitCode); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFatalErrorExitsWith255(): void |
||||||
|
{ |
||||||
|
$exitCode = $this->buildAndRun(<<<'PHP' |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
trigger_error('intentional fatal error', E_USER_ERROR); |
||||||
|
} |
||||||
|
PHP); |
||||||
|
|
||||||
|
$this->assertSame(255, $exitCode); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNormalCompletionExitsWithZero(): void |
||||||
|
{ |
||||||
|
$exitCode = $this->buildAndRun(<<<'PHP' |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
echo "ok\n"; |
||||||
|
} |
||||||
|
PHP); |
||||||
|
|
||||||
|
$this->assertSame(0, $exitCode); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Compile a single-file bin project and return the exit code of the binary. |
||||||
|
*/ |
||||||
|
private function buildAndRun(string $source): int |
||||||
|
{ |
||||||
|
$srcDir = $this->workDir . '/src'; |
||||||
|
$binDir = $this->workDir . '/bin'; |
||||||
|
$buildDir = $this->workDir . '/build'; |
||||||
|
mkdir($srcDir, 0777, true); |
||||||
|
mkdir($binDir, 0777, true); |
||||||
|
|
||||||
|
file_put_contents($srcDir . '/main.php', $source); |
||||||
|
file_put_contents($srcDir . '/project.yml', sprintf( |
||||||
|
"name: exit-code-test\n" |
||||||
|
. "version: 0.1.0\n" |
||||||
|
. "mode: bin\n" |
||||||
|
. "output: %s/exit_code_test\n" |
||||||
|
. "build-dir: %s\n" |
||||||
|
. "optimize: 0\n" |
||||||
|
. "no-progress: true\n" |
||||||
|
. "\n" |
||||||
|
. "sources:\n" |
||||||
|
. " - main.php\n", |
||||||
|
$binDir, |
||||||
|
$buildDir |
||||||
|
)); |
||||||
|
|
||||||
|
$tpc = dirname(__DIR__, 2) . '/bin/tpc.php'; |
||||||
|
$buildCommand = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($tpc) |
||||||
|
. ' ' . escapeshellarg($srcDir . '/project.yml') . ' -j 1'; |
||||||
|
exec($buildCommand . ' 2>&1', $buildOutput, $buildExitCode); |
||||||
|
$this->assertSame( |
||||||
|
0, |
||||||
|
$buildExitCode, |
||||||
|
'AOT compilation failed: ' . implode("\n", $buildOutput) |
||||||
|
); |
||||||
|
|
||||||
|
$binary = $binDir . '/exit_code_test'; |
||||||
|
exec(escapeshellarg($binary) . ' 2>&1', $runOutput, $exitCode); |
||||||
|
|
||||||
|
return $exitCode; |
||||||
|
} |
||||||
|
|
||||||
|
private function removeDirectory(string $dir): void |
||||||
|
{ |
||||||
|
if (!is_dir($dir)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
$items = scandir($dir); |
||||||
|
foreach ($items ?: [] as $item) { |
||||||
|
if ($item === '.' || $item === '..') { |
||||||
|
continue; |
||||||
|
} |
||||||
|
$path = $dir . DIRECTORY_SEPARATOR . $item; |
||||||
|
is_dir($path) ? $this->removeDirectory($path) : @unlink($path); |
||||||
|
} |
||||||
|
@rmdir($dir); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue