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.
 
 

619 lines
21 KiB

<?php
namespace TypePhp\Tests\Backend;
use PHPUnit\Framework\TestCase;
use TypePhp\Platform\Windows;
use TypePhp\Platform\Linux;
use TypePhp\Platform\Macos;
use TypePhp\Backend\Msvc;
use TypePhp\Backend\Gcc;
use TypePhp\Backend\Clang;
use TypePhp\Backend\CompilerFactory;
use TypePhp\CompilerTest;
use TypePhp\Exception\TestError;
class BackendTest extends TestCase
{
private array $temporaryDirectories = [];
protected function tearDown(): void
{
parent::tearDown();
foreach ($this->temporaryDirectories as $dir) {
$this->removeDirectory($dir);
}
$this->temporaryDirectories = [];
}
private function createTemporaryDirectory(string $prefix): string
{
$dir = sys_get_temp_dir() . '/' . $prefix . '_' . uniqid();
mkdir($dir, 0777, true);
$this->temporaryDirectories[] = $dir;
return $dir;
}
private function removeDirectory(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
is_dir($path) ? $this->removeDirectory($path) : unlink($path);
}
rmdir($dir);
}
/**
* 测试 MSVC 编译器基本信息
*/
public function testMsvcBasic(): void
{
$platform = new Windows();
$compiler = new Msvc($platform);
$this->assertEquals('MSVC', $compiler->getName());
$this->assertEquals('cl', $compiler->getCompilerCommand());
$this->assertEquals('link', $compiler->getLinkerCommand());
}
/**
* 测试 MSVC 编译单个文件
*/
public function testMsvcCompileFile(): void
{
$platform = new Windows();
$compiler = new Msvc($platform);
$cmd = $compiler->compileFile(
'test.cpp',
'test.obj',
['C:\PHP\include'],
['ZEND_WIN32']
);
$this->assertStringContainsString('cl', $cmd);
$this->assertStringContainsString('/c', $cmd);
$this->assertStringContainsString('test.cpp', $cmd);
$this->assertStringContainsString('/Fo', $cmd);
$this->assertStringContainsString('test.obj', $cmd);
$this->assertStringContainsString('/I', $cmd);
$this->assertStringContainsString('/DZEND_WIN32', $cmd);
}
/**
* 测试 MSVC 链接对象文件
*/
public function testMsvcLinkObjects(): void
{
$platform = new Windows();
$compiler = new Msvc($platform);
$cmd = $compiler->linkObjects(
['test.obj'],
'output.exe',
['C:\PHP\lib'],
['php8.lib']
);
$this->assertStringContainsString('link', $cmd);
$this->assertStringContainsString('@', $cmd);
$this->assertStringContainsString('.rsp', $cmd);
$this->assertStringContainsString('/OUT:', $cmd);
$this->assertStringContainsString('output.exe', $cmd);
$this->assertStringContainsString('/LIBPATH:', $cmd);
$compiler->cleanupResponseFile();
}
/**
* 测试 MSVC 完整编译命令
*/
public function testMsvcBuildCompileCommand(): void
{
$platform = new Windows([], true); // ZTS mode
$compiler = new Msvc($platform);
$cmd = $compiler->buildCompileCommand(
'test.cpp',
'test.obj',
[
'optimize' => 2,
'cpp_std' => 'c++17',
]
);
$this->assertStringContainsString('cl', $cmd);
$this->assertStringContainsString('/c', $cmd);
$this->assertStringContainsString('/DZEND_WIN32', $cmd);
$this->assertStringContainsString('/DPHP_WIN32', $cmd);
$this->assertStringContainsString('/DZTS', $cmd); // ZTS enabled
$this->assertStringContainsString('/O2', $cmd);
$this->assertStringContainsString('/W3', $cmd);
$this->assertStringContainsString('/std:c++17', $cmd);
$this->assertStringContainsString('/EHsc', $cmd);
$this->assertStringContainsString('/MD', $cmd);
$this->assertStringContainsString('/nologo', $cmd);
}
public function testMsvcBuildCCompileCommandKeepsSharedCompilerOptions(): void
{
$platform = new Windows([], true);
$compiler = new Msvc($platform);
$cmd = $compiler->buildCCompileCommand('misc.c', 'misc.obj', [
'sanitize' => 'address',
'enable_profiler' => true,
'prof_output' => 'app.prof',
'user_defines' => ['FEATURE_X=1'],
'lto' => true,
'is_zts' => true,
]);
$this->assertStringContainsString('/TC', $cmd);
$this->assertStringContainsString('/fsanitize=address', $cmd);
$this->assertStringContainsString('/DPPROF_ON=1', $cmd);
$this->assertStringContainsString('/D' . escapeshellarg('PROF_OUTPUT_FILE="app.prof"'), $cmd);
$this->assertStringContainsString('/DFEATURE_X=1', $cmd);
$this->assertStringContainsString('/GL', $cmd);
$this->assertStringContainsString('/DZTS', $cmd);
$this->assertStringNotContainsString('/EHsc', $cmd);
$this->assertStringNotContainsString('/std:', $cmd);
}
/**
* 测试 MSVC 完整链接命令
*/
public function testMsvcBuildLinkCommand(): void
{
$platform = new Windows();
$compiler = new Msvc($platform);
$cmd = $compiler->buildLinkCommand(
['test.obj'],
'output.exe',
[
'debug' => true,
'no_console' => false,
]
);
$this->assertStringContainsString('link', $cmd);
$this->assertStringContainsString('/OUT:', $cmd);
$this->assertStringContainsString('/DEBUG', $cmd);
$this->assertStringContainsString('/NODEFAULTLIB:LIBCMT', $cmd);
$this->assertStringContainsString('/nologo', $cmd);
$compiler->cleanupResponseFile();
}
/**
* 测试 MSVC 完整编译选项
*/
public function testMsvcFullCompileOptions(): void
{
$platform = new Windows([], true); // ZTS
$compiler = new Msvc($platform);
$options = $compiler->buildFullCompileOptions([
'optimize' => 2,
'debug' => false,
'sanitize' => null,
'cpp_std' => 'c++17',
'suppressed_warnings' => [
'4996' => 'deprecated function',
'4267' => 'size_t conversion',
],
]);
$this->assertStringContainsString('/DZEND_WIN32', $options);
$this->assertStringContainsString('/DZTS', $options);
$this->assertStringContainsString('/O2', $options);
$this->assertStringContainsString('/W3', $options);
$this->assertStringContainsString('/wd4996', $options);
$this->assertStringContainsString('/wd4267', $options);
$this->assertStringContainsString('/EHsc', $options);
$this->assertStringContainsString('/std:c++17', $options);
$this->assertStringContainsString('/MD', $options);
$this->assertStringContainsString('/nologo', $options);
}
/**
* 测试 MSVC 调试模式编译选项
*/
public function testMsvcDebugCompileOptions(): void
{
$platform = new Windows();
$compiler = new Msvc($platform);
$options = $compiler->buildFullCompileOptions([
'debug' => true,
]);
$this->assertStringContainsString('/Od', $options); // 禁用优化
$this->assertStringContainsString('/Zi', $options); // 生成调试信息
}
/**
* 测试 MSVC 完整链接选项
*/
public function testMsvcFullLinkOptions(): void
{
$platform = new Windows();
$compiler = new Msvc($platform);
$options = $compiler->buildFullLinkOptions([
'debug' => true,
'no_console' => true,
'shared' => true,
]);
$this->assertStringContainsString('/DEBUG', $options);
$this->assertStringContainsString('/SUBSYSTEM:WINDOWS', $options);
$this->assertStringContainsString('/ENTRY:mainCRTStartup', $options);
$this->assertStringContainsString('/NODEFAULTLIB:LIBCMT', $options);
$this->assertStringContainsString('/DLL', $options);
$this->assertStringContainsString('/nologo', $options);
}
/**
* 测试 GCC 编译器基本信息
*/
public function testGccBasic(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$this->assertEquals('GCC', $compiler->getName());
$this->assertEquals('g++', $compiler->getCompilerCommand());
$this->assertEquals('g++', $compiler->getLinkerCommand());
}
/**
* 测试 GCC 编译单个文件
*/
public function testGccCompileFile(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$cmd = $compiler->compileFile(
'test.cpp',
'test.o',
['/usr/include/php'],
['ZEND_WIN32']
);
$this->assertStringContainsString('g++', $cmd);
$this->assertStringContainsString('-c', $cmd);
$this->assertStringContainsString('test.cpp', $cmd);
$this->assertStringContainsString('-o', $cmd);
$this->assertStringContainsString('test.o', $cmd);
$this->assertStringContainsString('-I', $cmd);
$this->assertStringContainsString('-DZEND_WIN32', $cmd);
}
public function testGccBuildCompileCommandUsesCustomCompilerAndIncludes(): void
{
$platform = new Linux();
$compiler = new Gcc($platform, '/opt/toolchain/bin/g++');
$cmd = $compiler->buildCompileCommand('test.cpp', 'test.o', [
'include_paths' => ['/usr/include/php'],
'cpp_std' => 'c++20',
'cxxflags' => '-fno-rtti',
]);
$this->assertStringStartsWith('/opt/toolchain/bin/g++', $cmd);
$this->assertStringContainsString('-I' . escapeshellarg('/usr/include/php'), $cmd);
$this->assertStringContainsString('-std=c++20', $cmd);
$this->assertStringContainsString('-fno-rtti', $cmd);
}
public function testGccBuildCCompileCommandKeepsSharedCompilerOptions(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$cmd = $compiler->buildCCompileCommand('misc.c', 'misc.o', [
'sanitize' => 'address',
'enable_profiler' => true,
'prof_output' => 'app.prof',
'user_defines' => ['FEATURE_X=1'],
'lto' => true,
'march' => 'native',
'target_platform' => 'aarch64-linux-gnu',
'build_mode' => 'ext',
]);
$this->assertStringContainsString('-fsanitize=address', $cmd);
$this->assertStringContainsString('-DPPROF_ON=1', $cmd);
$this->assertStringContainsString('-D' . escapeshellarg('PROF_OUTPUT_FILE="app.prof"'), $cmd);
$this->assertStringContainsString('-DFEATURE_X=1', $cmd);
$this->assertStringContainsString('-flto', $cmd);
$this->assertStringContainsString('-march=native', $cmd);
$this->assertStringContainsString('--target=aarch64-linux-gnu', $cmd);
$this->assertStringContainsString('-fPIC', $cmd);
}
public function testGccBuildLinkCommandIncludesPlatformPathsOptionsAndLibraries(): void
{
$platform = new Linux();
$compiler = new Gcc($platform, 'g++');
$cmd = $compiler->buildLinkCommand(['a.o', 'b.o'], 'app', [
'library_paths' => ['/usr/lib'],
'libraries' => ['/usr/lib/libphpx.so', 'php'],
'ldflags' => '-Wl,--as-needed',
'build_mode' => 'ext',
]);
$this->assertStringStartsWith('g++', $cmd);
$this->assertStringContainsString('-L' . escapeshellarg('/usr/lib'), $cmd);
$this->assertStringContainsString('-Wl,--as-needed', $cmd);
$this->assertStringContainsString('-shared', $cmd);
$this->assertStringContainsString('-lphpx', $cmd);
$this->assertStringContainsString('-lphp', $cmd);
$compiler->cleanupResponseFile();
}
public function testBuildCleansResponseFileWhenLinkFails(): void
{
$dir = $this->createTemporaryDirectory('backend_link_failure');
$target = $dir . '/app';
$backend = new Gcc(new Linux(), 'false');
$compiler = new class(ROOT_PATH, $target, $backend) extends CompilerTest {
public function __construct(
string $rootPath,
private readonly string $testTarget,
\TypePhp\Backend\CompilerBackend $backend
) {
parent::__construct($rootPath);
$this->forTest = true;
$this->compilerBackend = $backend;
}
protected function getTargetFileName(): string
{
return $this->testTarget;
}
};
try {
$compiler->build([$dir . '/missing.o']);
$this->fail('The failing linker command should abort the build');
} catch (TestError $e) {
$this->assertStringContainsString('link failed', $e->getMessage());
}
$this->assertFileDoesNotExist($target . '.rsp');
}
public function testResponseFileArgumentIsEscapedForPathsWithSpaces(): void
{
$platform = new Linux();
$compiler = new Gcc($platform, 'g++');
$dir = $this->createTemporaryDirectory('backend link path');
$target = $dir . '/my app';
$rspFile = $target . '.rsp';
$objectWithSpace = $dir . '/object one.o';
$objectWithoutSpace = $dir . '/object_two.o';
$cmd = $compiler->buildLinkCommand([$objectWithSpace, $objectWithoutSpace], $target);
$this->assertStringContainsString(escapeshellarg('@' . $rspFile), $cmd);
$this->assertStringContainsString('-o ' . escapeshellarg($target), $cmd);
$this->assertFileExists($rspFile);
$lines = file($rspFile, FILE_IGNORE_NEW_LINES);
$this->assertSame('"' . $objectWithSpace . '"', $lines[0]);
$this->assertSame('"' . $objectWithoutSpace . '"', $lines[1]);
}
/**
* 测试 GCC 完整编译选项
*/
public function testGccFullCompileOptions(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$options = $compiler->buildFullCompileOptions([
'optimize' => 2,
'debug' => false,
'cpp_std' => 'c++17',
'sanitize' => 'address',
'pic' => true,
]);
$this->assertStringContainsString('-O2', $options);
$this->assertStringContainsString('-Wall', $options);
$this->assertStringContainsString('-std=c++17', $options);
$this->assertStringContainsString('-fsanitize=address', $options);
$this->assertStringContainsString('-fPIC', $options);
}
/**
* 测试 GCC 调试模式编译选项
*/
public function testGccDebugCompileOptions(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$options = $compiler->buildFullCompileOptions([
'debug' => true,
]);
$this->assertStringContainsString('-O0', $options);
$this->assertStringContainsString('-g', $options);
}
/**
* 测试 GCC 完整链接选项
*/
public function testGccFullLinkOptions(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$options = $compiler->buildFullLinkOptions([
'shared' => true,
'rpath' => ['/usr/lib', '/usr/local/lib'],
'sanitize' => 'address',
]);
$this->assertStringContainsString('-shared', $options);
$this->assertStringContainsString('-Wl,-rpath', $options);
$this->assertStringContainsString('-fsanitize=address', $options);
}
/**
* 测试 Clang 编译器基本信息
*/
public function testClangBasic(): void
{
$platform = new Linux();
$compiler = new Clang($platform);
$this->assertEquals('Clang', $compiler->getName());
$this->assertEquals('clang++', $compiler->getCompilerCommand());
$this->assertEquals('clang++', $compiler->getLinkerCommand());
}
/**
* 测试 Clang Windows 平台链接器
*/
public function testClangWindowsLinker(): void
{
$platform = new Windows();
$compiler = new Clang($platform);
// Windows 下 Clang 使用 link.exe
$this->assertEquals('link', $compiler->getLinkerCommand());
}
public function testCompilerFactoryKeepsConfiguredCompilerCommand(): void
{
$compiler = CompilerFactory::createByName('/opt/llvm/bin/clang++', new Linux());
$this->assertInstanceOf(Clang::class, $compiler);
$this->assertSame('/opt/llvm/bin/clang++', $compiler->getCompilerCommand());
$this->assertSame('/opt/llvm/bin/clang++', $compiler->getLinkerCommand());
}
/**
* 测试 Clang 完整编译选项(Unix)
*/
public function testClangUnixCompileOptions(): void
{
$platform = new Linux();
$compiler = new Clang($platform);
$options = $compiler->buildFullCompileOptions([
'optimize' => 2,
'cpp_std' => 'c++17',
]);
$this->assertStringContainsString('-O2', $options);
$this->assertStringContainsString('-Wall', $options);
$this->assertStringContainsString('-std=c++17', $options);
$this->assertStringNotContainsString('-fms-compatibility', $options);
}
/**
* 测试 Clang Windows 编译选项
*/
public function testClangWindowsCompileOptions(): void
{
$platform = new Windows();
$compiler = new Clang($platform);
$options = $compiler->buildFullCompileOptions([
'optimize' => 2,
]);
// Windows 下需要 MSVC 兼容模式
$this->assertStringContainsString('-fms-compatibility', $options);
$this->assertStringContainsString('-fms-compatibility-version=19.40', $options);
$this->assertStringContainsString('-fdelayed-template-parsing', $options);
$this->assertStringContainsString('-fms-extensions', $options);
}
/**
* 测试 Clang 完整链接选项(Windows)
*/
public function testClangWindowsLinkOptions(): void
{
$platform = new Windows();
$compiler = new Clang($platform);
$options = $compiler->buildFullLinkOptions([
'debug' => true,
'no_console' => true,
]);
$this->assertStringContainsString('/DEBUG', $options);
$this->assertStringContainsString('/SUBSYSTEM:WINDOWS', $options);
$this->assertStringContainsString('/NODEFAULTLIB:LIBCMT', $options);
}
/**
* 测试 Clang 完整链接选项(Unix)
*/
public function testClangUnixLinkOptions(): void
{
$platform = new Linux();
$compiler = new Clang($platform);
$options = $compiler->buildFullLinkOptions([
'shared' => true,
'rpath' => ['/usr/lib'],
]);
$this->assertStringContainsString('-shared', $options);
$this->assertStringContainsString('-Wl,-rpath', $options);
}
/**
* 测试不同优化级别
*/
public function testOptimizationLevels(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
// O0 - 禁用优化
$opt0 = $compiler->buildFullCompileOptions(['optimize' => 0]);
$this->assertStringContainsString('-O0', $opt0);
// O1
$opt1 = $compiler->buildFullCompileOptions(['optimize' => 1]);
$this->assertStringContainsString('-O1', $opt1);
// O2
$opt2 = $compiler->buildFullCompileOptions(['optimize' => 2]);
$this->assertStringContainsString('-O2', $opt2);
// O3
$opt3 = $compiler->buildFullCompileOptions(['optimize' => 3]);
$this->assertStringContainsString('-O3', $opt3);
}
/**
* 测试默认值
*/
public function testDefaultValues(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
// 不提供选项时使用默认值
$options = $compiler->buildFullCompileOptions([]);
$this->assertStringContainsString('-O2', $options); // 默认优化级别
$this->assertStringContainsString('-Wall', $options); // 默认警告
}
}