TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

188 lines
6.6 KiB

<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
final class BinaryEntrypointTest extends TestCase
{
public function testBootstrapConstantsUseTypePhpPrefix(): void
{
self::assertFalse(defined('ROOT_PATH'));
self::assertFalse(defined('DEBUG'));
self::assertSame(realpath(__DIR__ . '/..'), TYPEPHP_ROOT_PATH);
self::assertTrue(TYPEPHP_DEBUG);
}
public function testStubGeneratorDoesNotPolluteTheGlobalSymbolTable(): void
{
self::assertTrue(function_exists('TypePhp\\StubGenerator\\generateStubFile'));
self::assertTrue(class_exists('TypePhp\\StubGenerator\\ClassInfo'));
self::assertFalse(function_exists('generateStubFile'));
self::assertFalse(class_exists('ClassInfo'));
}
public function testSourceCheckoutEntrypointUsesPackageAutoloader(): void
{
$temporaryRoot = sys_get_temp_dir() . '/typephp-source-bin-' . bin2hex(random_bytes(6));
self::assertTrue(mkdir($temporaryRoot, 0777, true));
try {
$result = $this->runCompiler(TYPEPHP_ROOT_PATH . '/bin/tpc.php', $temporaryRoot);
self::assertSame(0, $result['status'], $result['stderr']);
self::assertStringContainsString('TypePHP Compiler (AOT)', $result['stdout']);
} finally {
$this->removeDirectory($temporaryRoot);
}
}
public function testComposerVendorEntrypointUsesProjectAutoloader(): void
{
$temporaryRoot = sys_get_temp_dir() . '/typephp-composer-bin-' . bin2hex(random_bytes(6));
$packageRoot = $temporaryRoot . '/vendor/swoole/typephp';
try {
$proxy = $this->createComposerEntrypoint($temporaryRoot, $packageRoot);
self::assertDirectoryDoesNotExist($packageRoot . '/vendor');
$result = $this->runCompiler($proxy, $temporaryRoot);
self::assertSame(0, $result['status'], $result['stderr']);
self::assertStringContainsString('TypePHP Compiler (AOT)', $result['stdout']);
} finally {
$this->removeDirectory($temporaryRoot);
}
}
public function testComposerVendorEntrypointDryCompilesProject(): void
{
if (!$this->hasPhpxLibrary()) {
self::markTestSkipped('A built PHPX library is required for the Zend PHP compiler integration test');
}
$temporaryRoot = sys_get_temp_dir() . '/typephp-composer-project-' . bin2hex(random_bytes(6));
$packageRoot = $temporaryRoot . '/vendor/swoole/typephp';
try {
$proxy = $this->createComposerEntrypoint($temporaryRoot, $packageRoot);
self::assertNotFalse(file_put_contents(
$temporaryRoot . '/main.php',
"<?php\nfunction main(): void {}\n",
));
self::assertNotFalse(file_put_contents(
$temporaryRoot . '/project.yml',
"name: composer-entrypoint-test\nbuild-dir: build\nsources:\n - main.php\n",
));
$result = $this->runCompiler(
$proxy,
$temporaryRoot,
['project.yml', '--dry', '--no-color'],
);
self::assertSame(0, $result['status'], $result['stderr']);
self::assertStringContainsString('Dry run completed:', $result['stdout']);
} finally {
$this->removeDirectory($temporaryRoot);
}
}
/** @return array{status: int, stdout: string, stderr: string} */
private function runCompiler(
string $entrypoint,
string $workingDirectory,
array $arguments = ['--version', '--no-color'],
): array {
$process = proc_open([PHP_BINARY, $entrypoint, ...$arguments], [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
], $pipes, $workingDirectory, null, ['suppress_errors' => true]);
self::assertIsResource($process);
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
return [
'status' => proc_close($process),
'stdout' => (string) $stdout,
'stderr' => (string) $stderr,
];
}
private function createComposerEntrypoint(string $temporaryRoot, string $packageRoot): string
{
$this->copyPackageEntrypoint($packageRoot);
$proxy = $temporaryRoot . '/vendor/bin/tpc.php';
$proxySource = sprintf(
"<?php\n\$GLOBALS['_composer_autoload_path'] = %s;\nrequire %s;\n",
var_export(TYPEPHP_ROOT_PATH . '/vendor/autoload.php', true),
var_export($packageRoot . '/bin/tpc.php', true),
);
self::assertNotFalse(file_put_contents($proxy, $proxySource));
return $proxy;
}
private function hasPhpxLibrary(): bool
{
$phpxHome = getenv('PHPX_HOME');
$roots = [TYPEPHP_ROOT_PATH . '/vendor/swoole/phpx'];
if (is_string($phpxHome) && $phpxHome !== '') {
array_unshift($roots, $phpxHome);
}
foreach ($roots as $root) {
foreach (['lib/libphpx.so', 'lib/libphpx.a', 'lib/phpx.lib', 'bin/phpx.dll'] as $library) {
if (is_file($root . '/' . $library)) {
return true;
}
}
}
return false;
}
private function copyPackageEntrypoint(string $packageRoot): void
{
$files = [
'bin/bootstrap.php',
'bin/tpc.php',
'src/compiler.php',
'src/gen_stub.php',
'src/polyfills.php',
];
foreach ($files as $file) {
$destination = $packageRoot . '/' . $file;
$directory = dirname($destination);
if (!is_dir($directory)) {
self::assertTrue(mkdir($directory, 0777, true));
}
self::assertTrue(copy(TYPEPHP_ROOT_PATH . '/' . $file, $destination));
}
self::assertTrue(mkdir(dirname($packageRoot, 2) . '/bin', 0777, true));
}
private function removeDirectory(string $directory): void
{
if (!is_dir($directory)) {
return;
}
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST,
);
foreach ($items as $item) {
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
}
rmdir($directory);
}
}