- 创建AST节点类型测试类AstNodeTypeTest,测试各种节点类型的识别方法 - 添加ClassDef扩展功能测试类ClassDefExtendedTest,验证类定义的各种属性和方法 - 实现CompilerBase API测试类CompilerBaseApiTest,测试类型转换、变量名生成等功能 - 创建FunctionContext扩展测试类Context/FunctionContextExtendedTest,测试作用域管理和变量操作 - 添加FunctionDef扩展测试类Entity/FunctionDefExtendedTest,测试函数定义相关功能pull/1/head
parent
b2d52d5d80
commit
efd6beac87
10 changed files with 2229 additions and 0 deletions
@ -0,0 +1,436 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\CompilerTest; |
||||
use PhpParser\Node; |
||||
use PhpParser\Node\Expr; |
||||
use PhpParser\Node\VariadicPlaceholder; |
||||
|
||||
class AstNodeTypeTest extends TestCase |
||||
{ |
||||
private CompilerTest $compiler; |
||||
private \ReflectionClass $ref; |
||||
private string $tmpDir; |
||||
|
||||
protected function setUp(): void |
||||
{ |
||||
parent::setUp(); |
||||
$this->tmpDir = sys_get_temp_dir() . '/ast_node_type_test_' . uniqid(); |
||||
mkdir($this->tmpDir, 0777, true); |
||||
$this->compiler = CompilerTest::create($this->tmpDir); |
||||
$this->ref = new \ReflectionClass($this->compiler); |
||||
} |
||||
|
||||
protected function tearDown(): void |
||||
{ |
||||
parent::tearDown(); |
||||
if (is_dir($this->tmpDir)) { |
||||
$this->removeDirectory($this->tmpDir); |
||||
} |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
private function invoke(string $method, ...$args): mixed |
||||
{ |
||||
$m = $this->ref->getMethod($method); |
||||
$m->setAccessible(true); |
||||
return $m->invoke($this->compiler, ...$args); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isArrayDimFetch |
||||
// ======================================================================== |
||||
|
||||
public function testIsArrayDimFetch(): void |
||||
{ |
||||
$var = new Expr\Variable('arr'); |
||||
$this->assertTrue($this->invoke('isArrayDimFetch', new Expr\ArrayDimFetch($var))); |
||||
$this->assertFalse($this->invoke('isArrayDimFetch', $var)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isVarExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsVarExpr(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isVarExpr', new Expr\Variable('foo'))); |
||||
$this->assertFalse($this->invoke('isVarExpr', new Node\Scalar\Int_(42))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isIdExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsIdExpr(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isIdExpr', new Node\Identifier('foo'))); |
||||
$this->assertFalse($this->invoke('isIdExpr', new Expr\Variable('foo'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isPropertyFetch |
||||
// ======================================================================== |
||||
|
||||
public function testIsPropertyFetch(): void |
||||
{ |
||||
$obj = new Expr\Variable('obj'); |
||||
$this->assertTrue($this->invoke('isPropertyFetch', new Expr\PropertyFetch($obj, 'prop'))); |
||||
$this->assertFalse($this->invoke('isPropertyFetch', $obj)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isStaticPropertyFetch |
||||
// ======================================================================== |
||||
|
||||
public function testIsStaticPropertyFetch(): void |
||||
{ |
||||
$class = new Node\Name('Foo'); |
||||
$this->assertTrue($this->invoke('isStaticPropertyFetch', new Expr\StaticPropertyFetch($class, 'prop'))); |
||||
$this->assertFalse($this->invoke('isStaticPropertyFetch', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isClassConstFetch |
||||
// ======================================================================== |
||||
|
||||
public function testIsClassConstFetch(): void |
||||
{ |
||||
$class = new Node\Name('Foo'); |
||||
$this->assertTrue($this->invoke('isClassConstFetch', new Expr\ClassConstFetch($class, 'BAR'))); |
||||
$this->assertFalse($this->invoke('isClassConstFetch', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isNewExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsNewExpr(): void |
||||
{ |
||||
$class = new Node\Name('Foo'); |
||||
$this->assertTrue($this->invoke('isNewExpr', new Expr\New_($class))); |
||||
$this->assertFalse($this->invoke('isNewExpr', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isNameExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsNameExpr(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isNameExpr', new Node\Name('Foo'))); |
||||
$this->assertFalse($this->invoke('isNameExpr', new Expr\Variable('Foo'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isFullNameExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsFullNameExpr(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isFullNameExpr', new Node\Name\FullyQualified('Foo\\Bar'))); |
||||
$this->assertFalse($this->invoke('isFullNameExpr', new Node\Name('Foo'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isNamedMethod |
||||
// ======================================================================== |
||||
|
||||
public function testIsNamedMethod(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isNamedMethod', new Node\Identifier('methodName'))); |
||||
$this->assertFalse($this->invoke('isNamedMethod', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isScalarString |
||||
// ======================================================================== |
||||
|
||||
public function testIsScalarString(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isScalarString', new Node\Scalar\String_('hello'))); |
||||
$this->assertFalse($this->invoke('isScalarString', new Node\Scalar\Int_(1))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isFuncCallExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsFuncCallExpr(): void |
||||
{ |
||||
$name = new Node\Name('foo'); |
||||
$this->assertTrue($this->invoke('isFuncCallExpr', new Expr\FuncCall($name))); |
||||
$this->assertFalse($this->invoke('isFuncCallExpr', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isRefvalCall |
||||
// ======================================================================== |
||||
|
||||
public function testIsRefvalCall(): void |
||||
{ |
||||
$refvalCall = new Expr\FuncCall(new Node\Name('refval')); |
||||
$this->assertTrue($this->invoke('isRefvalCall', $refvalCall)); |
||||
|
||||
$otherCall = new Expr\FuncCall(new Node\Name('other')); |
||||
$this->assertFalse($this->invoke('isRefvalCall', $otherCall)); |
||||
|
||||
$this->assertFalse($this->invoke('isRefvalCall', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isMethodCall |
||||
// ======================================================================== |
||||
|
||||
public function testIsMethodCall(): void |
||||
{ |
||||
$obj = new Expr\Variable('obj'); |
||||
$this->assertTrue($this->invoke('isMethodCall', new Expr\MethodCall($obj, 'method'))); |
||||
$this->assertFalse($this->invoke('isMethodCall', $obj)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isStaticCall |
||||
// ======================================================================== |
||||
|
||||
public function testIsStaticCall(): void |
||||
{ |
||||
$class = new Node\Name('Foo'); |
||||
$this->assertTrue($this->invoke('isStaticCall', new Expr\StaticCall($class, 'method'))); |
||||
$this->assertFalse($this->invoke('isStaticCall', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isScalar / isScalarInt / isScalarBool |
||||
// ======================================================================== |
||||
|
||||
public function testIsScalar(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isScalar', new Node\Scalar\Int_(1))); |
||||
$this->assertTrue($this->invoke('isScalar', new Node\Scalar\String_('s'))); |
||||
$this->assertTrue($this->invoke('isScalar', new Node\Scalar\Float_(1.0))); |
||||
$this->assertFalse($this->invoke('isScalar', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
public function testIsScalarInt(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isScalarInt', new Node\Scalar\Int_(42))); |
||||
$this->assertFalse($this->invoke('isScalarInt', new Node\Scalar\String_('42'))); |
||||
} |
||||
|
||||
public function testIsScalarBoolTrue(): void |
||||
{ |
||||
$trueConst = new Expr\ConstFetch(new Node\Name('true')); |
||||
$this->assertTrue($this->invoke('isScalarBool', $trueConst)); |
||||
$this->assertEquals('php::true_', $this->invoke('getBoolValue', $trueConst)); |
||||
} |
||||
|
||||
public function testIsScalarBoolFalse(): void |
||||
{ |
||||
$falseConst = new Expr\ConstFetch(new Node\Name('false')); |
||||
$this->assertTrue($this->invoke('isScalarBool', $falseConst)); |
||||
$this->assertEquals('php::false_', $this->invoke('getBoolValue', $falseConst)); |
||||
} |
||||
|
||||
public function testIsScalarBoolNotBool(): void |
||||
{ |
||||
$nullConst = new Expr\ConstFetch(new Node\Name('null')); |
||||
$this->assertFalse($this->invoke('isScalarBool', $nullConst)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isMatchExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsMatchExpr(): void |
||||
{ |
||||
$cond = new Expr\Variable('x'); |
||||
$this->assertTrue($this->invoke('isMatchExpr', new Expr\Match_($cond))); |
||||
$this->assertFalse($this->invoke('isMatchExpr', $cond)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isConstFetch |
||||
// ======================================================================== |
||||
|
||||
public function testIsConstFetch(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isConstFetch', new Expr\ConstFetch(new Node\Name('FOO')))); |
||||
$this->assertFalse($this->invoke('isConstFetch', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isAssignOp / isAssignExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsAssignOp(): void |
||||
{ |
||||
$var = new Expr\Variable('a'); |
||||
$val = new Node\Scalar\Int_(1); |
||||
// Assign is also an AssignOp |
||||
$this->assertTrue($this->invoke('isAssignOp', new Expr\Assign($var, $val))); |
||||
$this->assertTrue($this->invoke('isAssignOp', new Expr\AssignOp\Plus($var, $val))); |
||||
$this->assertFalse($this->invoke('isAssignOp', $var)); |
||||
} |
||||
|
||||
public function testIsAssignExpr(): void |
||||
{ |
||||
$var = new Expr\Variable('a'); |
||||
$val = new Node\Scalar\Int_(1); |
||||
$this->assertTrue($this->invoke('isAssignExpr', new Expr\Assign($var, $val))); |
||||
$this->assertFalse($this->invoke('isAssignExpr', new Expr\AssignOp\Plus($var, $val))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isCallExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsCallExpr(): void |
||||
{ |
||||
$name = new Node\Name('foo'); |
||||
$obj = new Expr\Variable('obj'); |
||||
$class = new Node\Name('Foo'); |
||||
|
||||
$this->assertTrue($this->invoke('isCallExpr', new Expr\FuncCall($name))); |
||||
$this->assertTrue($this->invoke('isCallExpr', new Expr\MethodCall($obj, 'bar'))); |
||||
$this->assertTrue($this->invoke('isCallExpr', new Expr\StaticCall($class, 'baz'))); |
||||
$this->assertFalse($this->invoke('isCallExpr', $obj)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isPlaceholderExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsPlaceholderExpr(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isPlaceholderExpr', new VariadicPlaceholder())); |
||||
$this->assertFalse($this->invoke('isPlaceholderExpr', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isReturnExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsReturnExpr(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isReturnExpr', new Node\Stmt\Return_(new Node\Scalar\Int_(1)))); |
||||
$this->assertFalse($this->invoke('isReturnExpr', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isBreakExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsBreakExpr(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isBreakExpr', new Node\Stmt\Break_())); |
||||
$this->assertFalse($this->invoke('isBreakExpr', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isThrowExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsThrowExprDirect(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isThrowExpr', new Expr\Throw_(new Expr\Variable('e')))); |
||||
} |
||||
|
||||
public function testIsThrowExprWrapped(): void |
||||
{ |
||||
$throwExpr = new Expr\Throw_(new Expr\Variable('e')); |
||||
$wrapped = new Node\Stmt\Expression($throwExpr); |
||||
$this->assertTrue($this->invoke('isThrowExpr', $wrapped)); |
||||
} |
||||
|
||||
public function testIsThrowExprNotThrow(): void |
||||
{ |
||||
$this->assertFalse($this->invoke('isThrowExpr', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isExitExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsExitExprDirect(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isExitExpr', new Expr\Exit_())); |
||||
} |
||||
|
||||
public function testIsExitExprWrapped(): void |
||||
{ |
||||
$exitExpr = new Expr\Exit_(); |
||||
$wrapped = new Node\Stmt\Expression($exitExpr); |
||||
$this->assertTrue($this->invoke('isExitExpr', $wrapped)); |
||||
} |
||||
|
||||
public function testIsExitExprNotExit(): void |
||||
{ |
||||
$this->assertFalse($this->invoke('isExitExpr', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isEmptyArray |
||||
// ======================================================================== |
||||
|
||||
public function testIsEmptyArray(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isEmptyArray', new Expr\Array_([]))); |
||||
$this->assertFalse($this->invoke('isEmptyArray', new Expr\Array_([new Node\ArrayItem(new Node\Scalar\Int_(1))]))); |
||||
} |
||||
|
||||
public function testIsEmptyArrayNotArray(): void |
||||
{ |
||||
$this->assertFalse($this->invoke('isEmptyArray', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isNull |
||||
// ======================================================================== |
||||
|
||||
public function testIsNull(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isNull', new Expr\ConstFetch(new Node\Name('null')))); |
||||
$this->assertFalse($this->invoke('isNull', new Expr\ConstFetch(new Node\Name('true')))); |
||||
$this->assertFalse($this->invoke('isNull', new Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// Cross-type verification: each is* is false for unrelated types |
||||
// ======================================================================== |
||||
|
||||
public function testNoCrossFalsePositives(): void |
||||
{ |
||||
$var = new Expr\Variable('x'); |
||||
|
||||
$methods = [ |
||||
'isArrayDimFetch', 'isPropertyFetch', 'isStaticPropertyFetch', |
||||
'isClassConstFetch', 'isNewExpr', 'isNameExpr', 'isFullNameExpr', |
||||
'isFuncCallExpr', 'isRefvalCall', 'isMethodCall', 'isStaticCall', |
||||
'isMatchExpr', 'isConstFetch', 'isAssignOp', 'isAssignExpr', |
||||
'isCallExpr', 'isPlaceholderExpr', 'isReturnExpr', 'isBreakExpr', |
||||
'isThrowExpr', 'isExitExpr', 'isEmptyArray', 'isNull', |
||||
]; |
||||
|
||||
foreach ($methods as $method) { |
||||
$this->assertFalse( |
||||
$this->invoke($method, $var), |
||||
"{$method} should return false for a plain Variable" |
||||
); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,338 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\CompilerTest; |
||||
use PhpAot\Php\CompilerBase; |
||||
|
||||
class CompilerBaseApiTest extends TestCase |
||||
{ |
||||
private string $testDir; |
||||
private CompilerTest $compiler; |
||||
private \ReflectionClass $ref; |
||||
|
||||
protected function setUp(): void |
||||
{ |
||||
parent::setUp(); |
||||
$this->testDir = sys_get_temp_dir() . '/compiler_api_test_' . uniqid(); |
||||
mkdir($this->testDir, 0777, true); |
||||
$this->compiler = CompilerTest::create($this->testDir); |
||||
$this->ref = new \ReflectionClass($this->compiler); |
||||
} |
||||
|
||||
protected function tearDown(): void |
||||
{ |
||||
parent::tearDown(); |
||||
// Recursively remove the test directory (compiler creates build/ subdir) |
||||
$this->removeDirectory($this->testDir); |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
private function getPropertyValue(string $name): mixed |
||||
{ |
||||
$prop = $this->ref->getProperty($name); |
||||
$prop->setAccessible(true); |
||||
return $prop->getValue($this->compiler); |
||||
} |
||||
|
||||
private function setPropertyValue(string $name, mixed $value): void |
||||
{ |
||||
$prop = $this->ref->getProperty($name); |
||||
$prop->setAccessible(true); |
||||
$prop->setValue($this->compiler, $value); |
||||
} |
||||
|
||||
private function invokeMethod(string $method, ...$args): mixed |
||||
{ |
||||
$m = $this->ref->getMethod($method); |
||||
$m->setAccessible(true); |
||||
return $m->invoke($this->compiler, ...$args); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getTypeFromZendType |
||||
// ======================================================================== |
||||
|
||||
public function testGetTypeFromZendTypeKnown(): void |
||||
{ |
||||
$this->assertEquals(CompilerBase::TYPE_INT, $this->compiler->getTypeFromZendType('int')); |
||||
$this->assertEquals(CompilerBase::TYPE_FLOAT, $this->compiler->getTypeFromZendType('float')); |
||||
$this->assertEquals(CompilerBase::TYPE_BOOL, $this->compiler->getTypeFromZendType('bool')); |
||||
$this->assertEquals(CompilerBase::TYPE_BOOL, $this->compiler->getTypeFromZendType('true')); |
||||
$this->assertEquals(CompilerBase::TYPE_BOOL, $this->compiler->getTypeFromZendType('false')); |
||||
$this->assertEquals(CompilerBase::TYPE_VOID, $this->compiler->getTypeFromZendType('void')); |
||||
$this->assertEquals(CompilerBase::TYPE_VOID, $this->compiler->getTypeFromZendType('never')); |
||||
$this->assertEquals(CompilerBase::TYPE_STR, $this->compiler->getTypeFromZendType('string')); |
||||
$this->assertEquals(CompilerBase::TYPE_ARRAY, $this->compiler->getTypeFromZendType('array')); |
||||
$this->assertEquals(CompilerBase::TYPE_OBJECT, $this->compiler->getTypeFromZendType('object')); |
||||
$this->assertEquals(CompilerBase::TYPE_VAR, $this->compiler->getTypeFromZendType('mixed')); |
||||
$this->assertEquals(CompilerBase::TYPE_VAR, $this->compiler->getTypeFromZendType('null')); |
||||
$this->assertEquals(CompilerBase::TYPE_VAR, $this->compiler->getTypeFromZendType('callable')); |
||||
$this->assertEquals(CompilerBase::TYPE_VAR, $this->compiler->getTypeFromZendType('iterable')); |
||||
$this->assertEquals(CompilerBase::TYPE_VAR, $this->compiler->getTypeFromZendType('UnsafePtr')); |
||||
} |
||||
|
||||
public function testGetTypeFromZendTypeUnknown(): void |
||||
{ |
||||
$this->assertEquals(CompilerBase::TYPE_VAR, $this->compiler->getTypeFromZendType('unknown_type')); |
||||
$this->assertEquals(CompilerBase::TYPE_VAR, $this->compiler->getTypeFromZendType('SomeClass')); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// genTmpVarName |
||||
// ======================================================================== |
||||
|
||||
public function testGenTmpVarName(): void |
||||
{ |
||||
// context must be initialized before genTmpVarName can be used |
||||
$this->invokeMethod('resetFunction'); |
||||
|
||||
$name1 = $this->compiler->genTmpVarName(); |
||||
$name2 = $this->compiler->genTmpVarName(); |
||||
$name3 = $this->compiler->genTmpVarName(); |
||||
|
||||
$this->assertStringStartsWith('tmp_var_', $name1); |
||||
$this->assertStringStartsWith('tmp_var_', $name2); |
||||
$this->assertStringStartsWith('tmp_var_', $name3); |
||||
|
||||
// Must be sequential and unique |
||||
$this->assertNotEquals($name1, $name2); |
||||
$this->assertNotEquals($name2, $name3); |
||||
$this->assertNotEquals($name1, $name3); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// genAnonClassName |
||||
// ======================================================================== |
||||
|
||||
public function testGenAnonClassName(): void |
||||
{ |
||||
$name1 = $this->compiler->genAnonClassName(); |
||||
$name2 = $this->compiler->genAnonClassName(); |
||||
|
||||
$this->assertStringStartsWith(CompilerBase::ANON_CLASS, $name1); |
||||
$this->assertStringStartsWith(CompilerBase::ANON_CLASS, $name2); |
||||
$this->assertNotEquals($name1, $name2); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getIncludeDir / getBuildDir |
||||
// ======================================================================== |
||||
|
||||
public function testGetBuildDir(): void |
||||
{ |
||||
$buildDir = $this->compiler->getBuildDir(); |
||||
$this->assertStringEndsWith('/build', $buildDir); |
||||
$this->assertStringStartsWith($this->testDir, $buildDir); |
||||
} |
||||
|
||||
public function testGetIncludeDir(): void |
||||
{ |
||||
$includeDir = $this->compiler->getIncludeDir(); |
||||
$buildDir = $this->compiler->getBuildDir(); |
||||
$this->assertEquals($buildDir . '/include', $includeDir); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isWindows / isLinux / isMacos |
||||
// ======================================================================== |
||||
|
||||
public function testPlatformDetectionMethods(): void |
||||
{ |
||||
$isWin = $this->compiler->isWindows(); |
||||
$isLin = $this->compiler->isLinux(); |
||||
$isMac = $this->compiler->isMacos(); |
||||
|
||||
// Exactly one platform must be true |
||||
$sum = ($isWin ? 1 : 0) + ($isLin ? 1 : 0) + ($isMac ? 1 : 0); |
||||
$this->assertEquals(1, $sum, 'Exactly one platform must be detected'); |
||||
|
||||
// All return bool |
||||
$this->assertIsBool($isWin); |
||||
$this->assertIsBool($isLin); |
||||
$this->assertIsBool($isMac); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isScalarInt - public method |
||||
// ======================================================================== |
||||
|
||||
public function testIsScalarIntTrue(): void |
||||
{ |
||||
$this->assertTrue($this->compiler->isScalarInt(new \PhpParser\Node\Scalar\LNumber(42))); |
||||
} |
||||
|
||||
public function testIsScalarIntFalse(): void |
||||
{ |
||||
$this->assertFalse($this->compiler->isScalarInt(new \PhpParser\Node\Expr\Variable('a'))); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getNamespacedClassName - fully qualified |
||||
// ======================================================================== |
||||
|
||||
public function testGetNamespacedClassNameFullyQualified(): void |
||||
{ |
||||
$this->assertEquals( |
||||
'App\\Entity\\User', |
||||
$this->compiler->getNamespacedClassName('\\App\\Entity\\User') |
||||
); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getNamespacedClassName - with use alias |
||||
// ======================================================================== |
||||
|
||||
public function testGetNamespacedClassNameWithUseAlias(): void |
||||
{ |
||||
$this->setPropertyValue('useAliases', ['User' => 'App\\Entity\\User']); |
||||
$this->assertEquals( |
||||
'App\\Entity\\User', |
||||
$this->compiler->getNamespacedClassName('User') |
||||
); |
||||
} |
||||
|
||||
public function testGetNamespacedClassNameWithUseAliasSubNamespace(): void |
||||
{ |
||||
$this->setPropertyValue('useAliases', ['Entity' => 'App\\Entity']); |
||||
$this->assertEquals( |
||||
'App\\Entity\\User', |
||||
$this->compiler->getNamespacedClassName('Entity\\User') |
||||
); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getNamespacedClassName - with use namespace (partial match) |
||||
// ======================================================================== |
||||
|
||||
public function testGetNamespacedClassNameWithUseNamespace(): void |
||||
{ |
||||
$this->setPropertyValue('useNamespaces', ['App\\Entity']); |
||||
// The last segment of 'App\Entity' is 'Entity', matching input 'Entity' |
||||
$this->assertEquals( |
||||
'App\\Entity', |
||||
$this->compiler->getNamespacedClassName('Entity') |
||||
); |
||||
} |
||||
|
||||
public function testGetNamespacedClassNameWithUseNamespaceSub(): void |
||||
{ |
||||
$this->setPropertyValue('useNamespaces', ['App\\Entity']); |
||||
// 'Entity\User' - first part 'Entity' matches the last part of 'App\Entity' |
||||
$this->assertEquals( |
||||
'App\\Entity\\User', |
||||
$this->compiler->getNamespacedClassName('Entity\\User') |
||||
); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getNamespacedClassName - with current namespace |
||||
// ======================================================================== |
||||
|
||||
public function testGetNamespacedClassNameWithCurrentNamespace(): void |
||||
{ |
||||
$this->setPropertyValue('namespace', 'App\\Service'); |
||||
// No matching alias or use namespace |
||||
$this->setPropertyValue('useAliases', []); |
||||
$this->setPropertyValue('useNamespaces', []); |
||||
$this->assertEquals( |
||||
'App\\Service\\MyClass', |
||||
$this->compiler->getNamespacedClassName('MyClass') |
||||
); |
||||
} |
||||
|
||||
public function testGetNamespacedClassNameNoNamespace(): void |
||||
{ |
||||
$this->setPropertyValue('namespace', ''); |
||||
$this->setPropertyValue('useAliases', []); |
||||
$this->setPropertyValue('useNamespaces', []); |
||||
$this->assertEquals( |
||||
'MyClass', |
||||
$this->compiler->getNamespacedClassName('MyClass') |
||||
); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getNamespacedClassName - alias takes priority over use namespace |
||||
// ======================================================================== |
||||
|
||||
public function testGetNamespacedClassNameAliasPriority(): void |
||||
{ |
||||
$this->setPropertyValue('useAliases', ['User' => 'App\\Models\\User']); |
||||
$this->setPropertyValue('useNamespaces', ['App\\Controllers']); |
||||
// Alias should be checked first |
||||
$this->assertEquals( |
||||
'App\\Models\\User', |
||||
$this->compiler->getNamespacedClassName('User') |
||||
); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getNamespacedFuncName |
||||
// ======================================================================== |
||||
|
||||
public function testGetNamespacedFuncNameFullyQualified(): void |
||||
{ |
||||
$this->assertEquals( |
||||
'App\\Lib\\helper_func', |
||||
$this->compiler->getNamespacedFuncName('\\App\\Lib\\helper_func') |
||||
); |
||||
} |
||||
|
||||
public function testGetNamespacedFuncNameWithUseFunction(): void |
||||
{ |
||||
$this->setPropertyValue('useFunctions', [ |
||||
'helper_func' => 'App\\Lib', |
||||
]); |
||||
$this->assertEquals( |
||||
'App\\Lib\\helper_func', |
||||
$this->compiler->getNamespacedFuncName('helper_func') |
||||
); |
||||
} |
||||
|
||||
public function testGetNamespacedFuncNameNoNamespace(): void |
||||
{ |
||||
$this->setPropertyValue('useFunctions', []); |
||||
$this->assertEquals( |
||||
'helper_func', |
||||
$this->compiler->getNamespacedFuncName('helper_func') |
||||
); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getNamespacedFuncName - not in useFunctions returns bare name |
||||
// ======================================================================== |
||||
|
||||
public function testGetNamespacedFuncNameNotInUseFunctions(): void |
||||
{ |
||||
$this->setPropertyValue('useFunctions', ['other' => 'Some\\Ns']); |
||||
$this->assertEquals( |
||||
'my_func', |
||||
$this->compiler->getNamespacedFuncName('my_func') |
||||
); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getPhpDir |
||||
// ======================================================================== |
||||
|
||||
public function testGetPhpDir(): void |
||||
{ |
||||
$phpDir = $this->compiler->getPhpDir(); |
||||
$this->assertIsString($phpDir); |
||||
$this->assertNotEmpty($phpDir); |
||||
} |
||||
} |
||||
@ -0,0 +1,146 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests\Context; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\Context\FunctionContext; |
||||
use PhpAot\Php\Context\ScopeContext; |
||||
|
||||
class FunctionContextExtendedTest extends TestCase |
||||
{ |
||||
public function testMultipleEnterLeaveScopeCycles(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
|
||||
// First cycle |
||||
$ctx->enterScope(); |
||||
$this->assertSame(1, $ctx->scopeLevel); |
||||
$ctx->leaveScope(); |
||||
$this->assertSame(0, $ctx->scopeLevel); |
||||
|
||||
// Second cycle - should still work |
||||
$ctx->enterScope(); |
||||
$this->assertSame(1, $ctx->scopeLevel); |
||||
$ctx->leaveScope(); |
||||
$this->assertSame(0, $ctx->scopeLevel); |
||||
} |
||||
|
||||
public function testDeepScopeNesting(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
for ($i = 1; $i <= 5; $i++) { |
||||
$ctx->enterScope(); |
||||
$this->assertSame($i, $ctx->scopeLevel); |
||||
$this->assertCount($i, $ctx->scopeLayouts); |
||||
$this->assertInstanceOf(ScopeContext::class, $ctx->scopeLayouts[$i - 1]); |
||||
} |
||||
for ($i = 4; $i >= 0; $i--) { |
||||
$ctx->leaveScope(); |
||||
$this->assertSame($i, $ctx->scopeLevel); |
||||
} |
||||
} |
||||
|
||||
public function testStaticVarsManipulation(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->staticVars['counter'] = 'php::Int'; |
||||
$ctx->staticVars['cache'] = 'php::Array'; |
||||
|
||||
$this->assertArrayHasKey('counter', $ctx->staticVars); |
||||
$this->assertArrayHasKey('cache', $ctx->staticVars); |
||||
$this->assertEquals('php::Int', $ctx->staticVars['counter']); |
||||
} |
||||
|
||||
public function testGlobalVarsManipulation(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->globalVars['_SESSION'] = 'php::Array'; |
||||
$ctx->globalVars['_ENV'] = 'php::Array'; |
||||
|
||||
$this->assertArrayHasKey('_SESSION', $ctx->globalVars); |
||||
$this->assertArrayHasKey('_ENV', $ctx->globalVars); |
||||
} |
||||
|
||||
public function testStdArraysManipulation(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->stdArrays['arr1'] = ['kind' => 'array', 'decl' => 'php::StdArray<php::Int, 10>']; |
||||
$ctx->stdArrays['arr2'] = ['kind' => 'array', 'decl' => 'php::StdArray<php::Float, 5>']; |
||||
|
||||
$this->assertArrayHasKey('arr1', $ctx->stdArrays); |
||||
$this->assertArrayHasKey('arr2', $ctx->stdArrays); |
||||
$this->assertCount(2, $ctx->stdArrays); |
||||
} |
||||
|
||||
public function testStdContainersManipulation(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->stdContainers['vec'] = ['kind' => 'vector', 'decl' => 'php::StdVector<php::Int>']; |
||||
$ctx->stdContainers['map'] = ['kind' => 'map', 'decl' => 'php::StdMap<php::Str, php::Int>']; |
||||
|
||||
$this->assertArrayHasKey('vec', $ctx->stdContainers); |
||||
$this->assertArrayHasKey('map', $ctx->stdContainers); |
||||
$this->assertEquals('vector', $ctx->stdContainers['vec']['kind']); |
||||
$this->assertEquals('map', $ctx->stdContainers['map']['kind']); |
||||
} |
||||
|
||||
public function testArgumentsManipulation(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->arguments['arg1'] = 'php::Int'; |
||||
$ctx->arguments['arg2'] = 'php::Str'; |
||||
|
||||
$this->assertArrayHasKey('arg1', $ctx->arguments); |
||||
$this->assertArrayHasKey('arg2', $ctx->arguments); |
||||
} |
||||
|
||||
public function testCeWrappersManipulation(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->ceWrappers['stdClass'] = 'ce_wrapper_0'; |
||||
$ctx->ceWrappers['Exception'] = 'ce_wrapper_1'; |
||||
|
||||
$this->assertArrayHasKey('stdClass', $ctx->ceWrappers); |
||||
$this->assertArrayHasKey('Exception', $ctx->ceWrappers); |
||||
} |
||||
|
||||
public function testObjectPropsManipulation(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->objectProps['_object_prop_obj__name'] = ['type' => 'php::Str', 'class' => '']; |
||||
$ctx->objectProps['_object_prop_obj__age'] = ['type' => 'php::Int', 'class' => '']; |
||||
|
||||
$this->assertArrayHasKey('_object_prop_obj__name', $ctx->objectProps); |
||||
$this->assertArrayHasKey('_object_prop_obj__age', $ctx->objectProps); |
||||
} |
||||
|
||||
public function testScopeLayoutsEntriesAreUniqueInstances(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->enterScope(); |
||||
$ctx->enterScope(); |
||||
|
||||
$this->assertNotSame($ctx->scopeLayouts[0], $ctx->scopeLayouts[1]); |
||||
} |
||||
|
||||
public function testInLoopToggleWithinScope(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$ctx->enterScope(); |
||||
$ctx->inLoop = true; |
||||
$this->assertTrue($ctx->inLoop); |
||||
$ctx->leaveScope(); |
||||
// inLoop is not affected by scope leave |
||||
$this->assertTrue($ctx->inLoop); |
||||
} |
||||
|
||||
public function testTmpVarIndexIncrementsNormally(): void |
||||
{ |
||||
$ctx = new FunctionContext(); |
||||
$indices = []; |
||||
for ($i = 0; $i < 10; $i++) { |
||||
$indices[] = $ctx->tmpVarIndex++; |
||||
} |
||||
$this->assertEquals(range(0, 9), $indices); |
||||
} |
||||
} |
||||
@ -0,0 +1,137 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests\Entity; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\Entity\ClassDef; |
||||
use PhpAot\Php\Entity\MethodDef; |
||||
use PhpParser\Modifiers; |
||||
use PhpParser\Node\Stmt\Trait_; |
||||
|
||||
class ClassDefExtendedTest extends TestCase |
||||
{ |
||||
public function testEnumFlag(): void |
||||
{ |
||||
$class = new ClassDef('Status', Modifiers::PUBLIC); |
||||
$this->assertFalse($class->enum); |
||||
$class->enum = true; |
||||
$this->assertTrue($class->enum); |
||||
} |
||||
|
||||
public function testInheritedFromInternalClass(): void |
||||
{ |
||||
$class = new ClassDef('MyClass', Modifiers::PUBLIC); |
||||
$this->assertFalse($class->inheritedFromInternalClass); |
||||
$class->inheritedFromInternalClass = true; |
||||
$this->assertTrue($class->inheritedFromInternalClass); |
||||
} |
||||
|
||||
public function testCtorInitAndClean(): void |
||||
{ |
||||
$class = new ClassDef('Service', Modifiers::PUBLIC); |
||||
$this->assertEquals('', $class->ctorInit); |
||||
$this->assertEquals('', $class->ctorClean); |
||||
|
||||
$class->ctorInit = 'property_1 = 0;'; |
||||
$class->ctorClean = 'property_1 = 0;'; |
||||
$this->assertEquals('property_1 = 0;', $class->ctorInit); |
||||
$this->assertEquals('property_1 = 0;', $class->ctorClean); |
||||
} |
||||
|
||||
public function testRequireCtorFlag(): void |
||||
{ |
||||
$class = new ClassDef('Entity', Modifiers::PUBLIC); |
||||
$this->assertFalse($class->requireCtor); |
||||
$class->requireCtor = true; |
||||
$this->assertTrue($class->requireCtor); |
||||
} |
||||
|
||||
public function testTraitAssociation(): void |
||||
{ |
||||
$class = new ClassDef('User', Modifiers::PUBLIC); |
||||
$this->assertNull($class->trait); |
||||
|
||||
$traitStmt = new Trait_('SomeTrait'); |
||||
$class->trait = $traitStmt; |
||||
$this->assertSame($traitStmt, $class->trait); |
||||
} |
||||
|
||||
public function testTraitAliasesAndIgnoredCanBeSet(): void |
||||
{ |
||||
$class = new ClassDef('User', Modifiers::PUBLIC); |
||||
$class->traitAliases['Full\\Trait::method'] = ['alias' => 'newName']; |
||||
$class->traitIgnored['Full\\Trait::other'] = true; |
||||
|
||||
$this->assertArrayHasKey('Full\\Trait::method', $class->traitAliases); |
||||
$this->assertArrayHasKey('Full\\Trait::other', $class->traitIgnored); |
||||
$this->assertTrue($class->traitIgnored['Full\\Trait::other']); |
||||
} |
||||
|
||||
public function testExtendsCanBeSet(): void |
||||
{ |
||||
$class = new ClassDef('Derived', Modifiers::PUBLIC); |
||||
$class->extends = 'App\\Entity\\Base'; |
||||
$this->assertEquals('App\\Entity\\Base', $class->extends); |
||||
} |
||||
|
||||
public function testImplementsCanBeSet(): void |
||||
{ |
||||
$class = new ClassDef('Service', Modifiers::PUBLIC); |
||||
$class->implements = ['Serializable', 'JsonSerializable']; |
||||
$this->assertContains('Serializable', $class->implements); |
||||
$this->assertContains('JsonSerializable', $class->implements); |
||||
} |
||||
|
||||
public function testMultipleMethodsWithSameNameCaseInsensitive(): void |
||||
{ |
||||
$class = new ClassDef('Foo', Modifiers::PUBLIC); |
||||
$method = new MethodDef(Modifiers::PUBLIC, 'MyMethod'); |
||||
|
||||
$class->addMethod($method); |
||||
// Adding same method name (case-insensitive) overwrites |
||||
$this->assertTrue($class->hasMethod('MYMETHOD')); |
||||
$this->assertTrue($class->hasMethod('mymethod')); |
||||
$this->assertTrue($class->hasMethod('MyMethod')); |
||||
} |
||||
|
||||
public function testFinalClassModifier(): void |
||||
{ |
||||
$class = new ClassDef('FinalClass', Modifiers::PUBLIC | Modifiers::FINAL); |
||||
$this->assertTrue((bool) ($class->flags & Modifiers::FINAL)); |
||||
$this->assertFalse($class->isAbstract()); |
||||
} |
||||
|
||||
public function testReadonlyClassModifier(): void |
||||
{ |
||||
$class = new ClassDef('ReadonlyClass', Modifiers::PUBLIC | Modifiers::READONLY); |
||||
$this->assertTrue((bool) ($class->flags & Modifiers::READONLY)); |
||||
} |
||||
|
||||
public function testFlagsPropertyIsAccessible(): void |
||||
{ |
||||
$class = new ClassDef('Foo', Modifiers::PUBLIC); |
||||
$this->assertSame(Modifiers::PUBLIC, $class->flags); |
||||
|
||||
$class->flags = Modifiers::PROTECTED; |
||||
$this->assertSame(Modifiers::PROTECTED, $class->flags); |
||||
} |
||||
|
||||
public function testPropertiesDefaultEmptyArray(): void |
||||
{ |
||||
$class = new ClassDef('Foo', Modifiers::PUBLIC); |
||||
$this->assertIsArray($class->properties); |
||||
$this->assertEmpty($class->properties); |
||||
$this->assertIsArray($class->constants); |
||||
$this->assertEmpty($class->constants); |
||||
$this->assertIsArray($class->methods); |
||||
$this->assertEmpty($class->methods); |
||||
} |
||||
|
||||
public function testPropertyContextIsUniquePerInstance(): void |
||||
{ |
||||
$class1 = new ClassDef('Foo', Modifiers::PUBLIC); |
||||
$class2 = new ClassDef('Bar', Modifiers::PUBLIC); |
||||
|
||||
$this->assertNotSame($class1->propertyContext, $class2->propertyContext); |
||||
} |
||||
} |
||||
@ -0,0 +1,104 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests\Entity; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\Entity\FunctionDef; |
||||
use PhpAot\Php\ArgInfo; |
||||
|
||||
class FunctionDefExtendedTest extends TestCase |
||||
{ |
||||
public function testStubFlagDefaultsToFalse(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'php::Int', ''); |
||||
$this->assertFalse($fn->stub); |
||||
} |
||||
|
||||
public function testStubFlagCanBeSet(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'php::Int', ''); |
||||
$fn->stub = true; |
||||
$this->assertTrue($fn->stub); |
||||
} |
||||
|
||||
public function testArgCountRequiredDefault(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'php::Int', ''); |
||||
$this->assertSame(0, $fn->argCountRequired); |
||||
} |
||||
|
||||
public function testArgCountRequiredCanBeSet(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'php::Int', ''); |
||||
$fn->argCountRequired = 3; |
||||
$this->assertSame(3, $fn->argCountRequired); |
||||
} |
||||
|
||||
public function testParamsDefault(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'php::Int', ''); |
||||
$this->assertEquals('', $fn->params); |
||||
} |
||||
|
||||
public function testParamsCanBeSet(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'php::Int', ''); |
||||
$fn->params = 'php::Str a, php::Int b'; |
||||
$this->assertEquals('php::Str a, php::Int b', $fn->params); |
||||
} |
||||
|
||||
public function testReturnClassDefault(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'php::Object', ''); |
||||
$this->assertEquals('', $fn->returnClass); |
||||
} |
||||
|
||||
public function testReturnClassCanBeSet(): void |
||||
{ |
||||
$fn = new FunctionDef('create', 'php::Object', 'App\\Factory'); |
||||
$fn->returnClass = 'App\\Entity\\Product'; |
||||
$this->assertEquals('App\\Entity\\Product', $fn->returnClass); |
||||
} |
||||
|
||||
public function testHasVariadicArgWithMultipleNonVariadicArgs(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'void', ''); |
||||
$fn->argInfoList = [ |
||||
new ArgInfo('a', 'php::Int'), |
||||
new ArgInfo('b', 'php::Str'), |
||||
new ArgInfo('c', 'php::Float'), |
||||
]; |
||||
$this->assertFalse($fn->hasVariadicArg()); |
||||
} |
||||
|
||||
public function testHasVariadicArgWithSingleNonVariadicArg(): void |
||||
{ |
||||
$fn = new FunctionDef('test', 'void', ''); |
||||
$fn->argInfoList = [ |
||||
new ArgInfo('a', 'php::Int'), |
||||
]; |
||||
$this->assertFalse($fn->hasVariadicArg()); |
||||
} |
||||
|
||||
public function testGetNamespacedNameWithoutNamespaceWithParams(): void |
||||
{ |
||||
$fn = new FunctionDef('run', 'void', ''); |
||||
$fn->argCountRequired = 1; |
||||
$fn->params = 'php::Int n'; |
||||
$this->assertEquals('run', $fn->getNamespacedName()); |
||||
} |
||||
|
||||
public function testMethodFlagWithFullSetup(): void |
||||
{ |
||||
$fn = new FunctionDef('handle', 'php::Var', 'App\\Controller'); |
||||
$fn->method = true; |
||||
$fn->argInfoList = [ |
||||
new ArgInfo('request', 'php::Object'), |
||||
]; |
||||
$fn->returnClass = 'App\\Entity\\Response'; |
||||
|
||||
$this->assertTrue($fn->method); |
||||
$this->assertEquals('App\\Controller\\handle', $fn->getNamespacedName()); |
||||
$this->assertEquals('App\\Entity\\Response', $fn->returnClass); |
||||
} |
||||
} |
||||
@ -0,0 +1,69 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests\Entity; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\Entity\MethodDef; |
||||
use PhpAot\Php\Entity\FunctionDef; |
||||
use PhpAot\Php\ArgInfo; |
||||
use PhpParser\Modifiers; |
||||
|
||||
class MethodDefExtendedTest extends TestCase |
||||
{ |
||||
public function testHasDynamicCallDefaultsToFalse(): void |
||||
{ |
||||
$method = new MethodDef(Modifiers::PUBLIC, 'test'); |
||||
$this->assertFalse($method->hasDynamicCall); |
||||
} |
||||
|
||||
public function testHasDynamicCallCanBeSet(): void |
||||
{ |
||||
$method = new MethodDef(Modifiers::PUBLIC, 'test'); |
||||
$method->hasDynamicCall = true; |
||||
$this->assertTrue($method->hasDynamicCall); |
||||
} |
||||
|
||||
public function testStaticMethod(): void |
||||
{ |
||||
$method = new MethodDef(Modifiers::PUBLIC | Modifiers::STATIC, 'factory'); |
||||
$this->assertTrue((bool) ($method->flags & Modifiers::STATIC)); |
||||
} |
||||
|
||||
public function testAbstractMethod(): void |
||||
{ |
||||
$method = new MethodDef(Modifiers::PUBLIC | Modifiers::ABSTRACT, 'handle'); |
||||
$this->assertTrue((bool) ($method->flags & Modifiers::ABSTRACT)); |
||||
} |
||||
|
||||
public function testFinalMethod(): void |
||||
{ |
||||
$method = new MethodDef(Modifiers::PUBLIC | Modifiers::FINAL, 'lock'); |
||||
$this->assertTrue((bool) ($method->flags & Modifiers::FINAL)); |
||||
} |
||||
|
||||
public function testCombinedFlags(): void |
||||
{ |
||||
$method = new MethodDef( |
||||
Modifiers::PUBLIC | Modifiers::STATIC | Modifiers::FINAL, |
||||
'combined' |
||||
); |
||||
$this->assertTrue((bool) ($method->flags & Modifiers::PUBLIC)); |
||||
$this->assertTrue((bool) ($method->flags & Modifiers::STATIC)); |
||||
$this->assertTrue((bool) ($method->flags & Modifiers::FINAL)); |
||||
} |
||||
|
||||
public function testFunctionDefLinkRoundTrip(): void |
||||
{ |
||||
$method = new MethodDef(Modifiers::PUBLIC, 'getValue'); |
||||
$fn = new FunctionDef('getValue', 'php::Int', 'App\\Service'); |
||||
$fn->argInfoList = [ |
||||
new ArgInfo('arg1', 'php::Str'), |
||||
]; |
||||
$fn->params = 'php::Str arg1'; |
||||
$method->functionDef = $fn; |
||||
|
||||
$this->assertSame($fn, $method->functionDef); |
||||
$this->assertEquals('php::Int', $method->getReturnType()); |
||||
$this->assertEquals('App\\Service\\getValue', $fn->getNamespacedName()); |
||||
} |
||||
} |
||||
@ -0,0 +1,320 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests\Generator; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\CompilerTest; |
||||
use PhpAot\Php\CompilerBase; |
||||
|
||||
class UtilsTest extends TestCase |
||||
{ |
||||
private string $testDir; |
||||
private CompilerTest $compiler; |
||||
|
||||
protected function setUp(): void |
||||
{ |
||||
parent::setUp(); |
||||
$this->testDir = sys_get_temp_dir() . '/utils_test_' . uniqid(); |
||||
mkdir($this->testDir, 0777, true); |
||||
$this->compiler = CompilerTest::create($this->testDir); |
||||
} |
||||
|
||||
protected function tearDown(): void |
||||
{ |
||||
parent::tearDown(); |
||||
array_map('unlink', glob($this->testDir . '/*')); |
||||
rmdir($this->testDir); |
||||
} |
||||
|
||||
private function invokeMethod(string $method, ...$args): mixed |
||||
{ |
||||
$ref = new \ReflectionClass($this->compiler); |
||||
$meth = $ref->getMethod($method); |
||||
$meth->setAccessible(true); |
||||
return $meth->invoke($this->compiler, ...$args); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// genCValue |
||||
// ======================================================================== |
||||
|
||||
public function testGenCValueInt(): void |
||||
{ |
||||
$this->assertSame(42, $this->invokeMethod('genCValue', 42)); |
||||
$this->assertSame(-1, $this->invokeMethod('genCValue', -1)); |
||||
$this->assertSame(0, $this->invokeMethod('genCValue', 0)); |
||||
} |
||||
|
||||
public function testGenCValueFloat(): void |
||||
{ |
||||
$result = $this->invokeMethod('genCValue', 3.14); |
||||
$this->assertSame(3.14, $result); |
||||
} |
||||
|
||||
public function testGenCValueBool(): void |
||||
{ |
||||
$this->assertSame(1, $this->invokeMethod('genCValue', true)); |
||||
$this->assertSame(0, $this->invokeMethod('genCValue', false)); |
||||
} |
||||
|
||||
public function testGenCValueString(): void |
||||
{ |
||||
$result = $this->invokeMethod('genCValue', 'hello'); |
||||
$this->assertEquals('"hello"', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// genCharPtr |
||||
// ======================================================================== |
||||
|
||||
public function testGenCharPtr(): void |
||||
{ |
||||
$this->assertEquals('"hello"', $this->invokeMethod('genCharPtr', 'hello')); |
||||
$this->assertEquals('""', $this->invokeMethod('genCharPtr', '')); |
||||
} |
||||
|
||||
public function testGenCharPtrEscape(): void |
||||
{ |
||||
$result = $this->invokeMethod('genCharPtr', 'hello "world"', true); |
||||
$this->assertStringContainsString('\\"', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// genZendStrl |
||||
// ======================================================================== |
||||
|
||||
public function testGenZendStrl(): void |
||||
{ |
||||
$result = $this->invokeMethod('genZendStrl', 'name'); |
||||
$this->assertStringStartsWith('ZEND_STRL("', $result); |
||||
$this->assertStringContainsString('name', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// genArray |
||||
// ======================================================================== |
||||
|
||||
public function testGenArray(): void |
||||
{ |
||||
$result = $this->invokeMethod('genArray', ['1', '2', '3']); |
||||
$this->assertStringStartsWith(CompilerBase::TYPE_ARRAY . '{', $result); |
||||
$this->assertStringContainsString('1, 2, 3', $result); |
||||
} |
||||
|
||||
public function testGenArrayEmpty(): void |
||||
{ |
||||
$result = $this->invokeMethod('genArray', []); |
||||
$this->assertStringStartsWith(CompilerBase::TYPE_ARRAY . '{', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// genRawStr |
||||
// ======================================================================== |
||||
|
||||
public function testGenRawStr(): void |
||||
{ |
||||
$result = $this->invokeMethod('genRawStr', 'hello'); |
||||
$this->assertEquals('R"(hello)"', $result); |
||||
} |
||||
|
||||
public function testGenRawStrMultiLine(): void |
||||
{ |
||||
$result = $this->invokeMethod('genRawStr', "line1\nline2"); |
||||
$this->assertStringStartsWith('R"(', $result); |
||||
$this->assertStringEndsWith(')"', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeString |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeStringSimple(): void |
||||
{ |
||||
$this->assertEquals('hello', $this->invokeMethod('escapeString', 'hello')); |
||||
} |
||||
|
||||
public function testEscapeStringQuotes(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeString', 'he"llo'); |
||||
$this->assertStringContainsString('\\"', $result); |
||||
} |
||||
|
||||
public function testEscapeStringBackslash(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeString', 'a\\b'); |
||||
$this->assertStringContainsString('\\\\', $result); |
||||
} |
||||
|
||||
public function testEscapeStringNewline(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeString', "a\nb"); |
||||
|
||||
// newline may be escaped as \n or literal depending on addcslashes behavior |
||||
$this->assertNotEquals("a\nb", $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeBool |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeBool(): void |
||||
{ |
||||
$this->assertEquals('true', $this->invokeMethod('escapeBool', true)); |
||||
$this->assertEquals('false', $this->invokeMethod('escapeBool', false)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeVarName / unescapeVarName |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeVarNameNormal(): void |
||||
{ |
||||
$this->assertEquals('foo', $this->invokeMethod('escapeVarName', 'foo')); |
||||
$this->assertEquals('bar', $this->invokeMethod('escapeVarName', 'bar')); |
||||
} |
||||
|
||||
public function testEscapeVarNameThis(): void |
||||
{ |
||||
$this->assertEquals('this_', $this->invokeMethod('escapeVarName', 'this')); |
||||
} |
||||
|
||||
public function testEscapeVarNameReservedKeyword(): void |
||||
{ |
||||
// 'class' is in CPP_RESERVED_NAMES |
||||
$result = $this->invokeMethod('escapeVarName', 'class'); |
||||
$this->assertStringStartsWith('_php__var__', $result); |
||||
} |
||||
|
||||
public function testUnescapeVarName(): void |
||||
{ |
||||
$this->assertEquals('foo', $this->invokeMethod('unescapeVarName', '_php__var__foo')); |
||||
$this->assertEquals('bar', $this->invokeMethod('unescapeVarName', 'bar')); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeNamespace |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeNamespace(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeNamespace', 'App\\Lib\\Module'); |
||||
$this->assertEquals('app__lib__module', $result); |
||||
} |
||||
|
||||
public function testEscapeNamespaceNoBackslash(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeNamespace', 'app'); |
||||
$this->assertEquals('app', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeZendFnName / escapeCeName |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeZendFnNameLower(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeZendFnName', 'App\\Foo\\bar', true); |
||||
$this->assertEquals('app_foo_bar', $result); |
||||
} |
||||
|
||||
public function testEscapeZendFnNameNoLower(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeZendFnName', 'App\\Foo\\bar', false); |
||||
$this->assertEquals('App_Foo_bar', $result); |
||||
} |
||||
|
||||
public function testEscapeCeName(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeCeName', 'App\\Entity\\User'); |
||||
$this->assertEquals('App_Entity_User', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeName |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeName(): void |
||||
{ |
||||
$this->assertEquals('foo', $this->invokeMethod('escapeName', 'FOO')); |
||||
$this->assertEquals('bar', $this->invokeMethod('escapeName', 'BAR')); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeClass / escapeFunction |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeClass(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeClass', '\\App\\Lib\\MyClass'); |
||||
$this->assertEquals('app_lib_myclass', $result); |
||||
} |
||||
|
||||
public function testEscapeFunction(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeFunction', 'App\\Foo\\run'); |
||||
$this->assertEquals('app_foo_run', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeFileName |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeFileName(): void |
||||
{ |
||||
$this->assertEquals('my_file', $this->invokeMethod('escapeFileName', 'my-file')); |
||||
$this->assertEquals('a_b_c', $this->invokeMethod('escapeFileName', 'a-b-c')); |
||||
} |
||||
|
||||
public function testEscapeFileNameNoDash(): void |
||||
{ |
||||
$this->assertEquals('already_good', $this->invokeMethod('escapeFileName', 'already_good')); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// escapeGlobalVar |
||||
// ======================================================================== |
||||
|
||||
public function testEscapeGlobalVar(): void |
||||
{ |
||||
$result = $this->invokeMethod('escapeGlobalVar', 'myvar'); |
||||
$this->assertStringStartsWith('_global_var_', $result); |
||||
$this->assertStringEndsWith('myvar', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// isClosedExpr |
||||
// ======================================================================== |
||||
|
||||
public function testIsClosedExprSimple(): void |
||||
{ |
||||
$this->assertTrue($this->invokeMethod('isClosedExpr', '(a + b)', '')); |
||||
$this->assertTrue($this->invokeMethod('isClosedExpr', '(1)', '')); |
||||
} |
||||
|
||||
public function testIsClosedExprNotClosed(): void |
||||
{ |
||||
$this->assertFalse($this->invokeMethod('isClosedExpr', 'a + b', '')); |
||||
$this->assertFalse($this->invokeMethod('isClosedExpr', '(a + b', '')); |
||||
} |
||||
|
||||
public function testIsClosedExprNested(): void |
||||
{ |
||||
$this->assertTrue($this->invokeMethod('isClosedExpr', '((a + b) * c)', '')); |
||||
} |
||||
|
||||
public function testIsClosedExprWithCall(): void |
||||
{ |
||||
$this->assertTrue($this->invokeMethod('isClosedExpr', 'foo(1, 2)', 'foo')); |
||||
$this->assertFalse($this->invokeMethod('isClosedExpr', 'bar(1, 2)', 'foo')); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// trimBrackets |
||||
// ======================================================================== |
||||
|
||||
public function testTrimBrackets(): void |
||||
{ |
||||
$this->assertEquals('a + b', $this->invokeMethod('trimBrackets', '(a + b)')); |
||||
$this->assertEquals('not wrapped', $this->invokeMethod('trimBrackets', 'not wrapped')); |
||||
} |
||||
} |
||||
@ -0,0 +1,229 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\CompilerTest; |
||||
use PhpAot\Php\ArgInfo; |
||||
use PhpParser\Node; |
||||
|
||||
class PreprocessorTest extends TestCase |
||||
{ |
||||
private string $testDir; |
||||
private CompilerTest $compiler; |
||||
private \ReflectionClass $ref; |
||||
|
||||
protected function setUp(): void |
||||
{ |
||||
parent::setUp(); |
||||
$this->testDir = sys_get_temp_dir() . '/preprocessor_test_' . uniqid(); |
||||
mkdir($this->testDir, 0777, true); |
||||
$this->compiler = CompilerTest::create($this->testDir); |
||||
$this->ref = new \ReflectionClass($this->compiler); |
||||
} |
||||
|
||||
protected function tearDown(): void |
||||
{ |
||||
parent::tearDown(); |
||||
if (is_dir($this->testDir)) { |
||||
$this->removeDirectory($this->testDir); |
||||
} |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
private function invokeMethod(string $method, ...$args): mixed |
||||
{ |
||||
$m = $this->ref->getMethod($method); |
||||
$m->setAccessible(true); |
||||
return $m->invoke($this->compiler, ...$args); |
||||
} |
||||
|
||||
private function setProperty(string $name, mixed $value): void |
||||
{ |
||||
$prop = $this->ref->getProperty($name); |
||||
$prop->setAccessible(true); |
||||
$prop->setValue($this->compiler, $value); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// genArgumentDeclaration |
||||
// ======================================================================== |
||||
|
||||
public function testGenArgumentDeclarationSimple(): void |
||||
{ |
||||
$arg = new ArgInfo(); |
||||
$arg->name = 'count'; |
||||
$arg->type = 'php::Int'; |
||||
$result = $this->invokeMethod('genArgumentDeclaration', $arg); |
||||
$this->assertEquals('php::Int count', $result); |
||||
} |
||||
|
||||
public function testGenArgumentDeclarationString(): void |
||||
{ |
||||
$arg = new ArgInfo(); |
||||
$arg->name = 'name'; |
||||
$arg->type = 'php::Str'; |
||||
$result = $this->invokeMethod('genArgumentDeclaration', $arg); |
||||
$this->assertEquals('php::Str name', $result); |
||||
} |
||||
|
||||
public function testGenArgumentDeclarationObject(): void |
||||
{ |
||||
$arg = new ArgInfo(); |
||||
$arg->name = 'obj'; |
||||
$arg->type = 'php::Object'; |
||||
$result = $this->invokeMethod('genArgumentDeclaration', $arg); |
||||
$this->assertEquals('php::Object obj', $result); |
||||
} |
||||
|
||||
public function testGenArgumentDeclarationUnsafePtr(): void |
||||
{ |
||||
$arg = new ArgInfo(); |
||||
$arg->name = 'container'; |
||||
$arg->type = 'php::Var'; |
||||
$arg->unsafePtr = true; |
||||
$result = $this->invokeMethod('genArgumentDeclaration', $arg); |
||||
$this->assertEquals('php::Var container', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getCppFile |
||||
// ======================================================================== |
||||
|
||||
public function testGetCppFile(): void |
||||
{ |
||||
$phpFile = '/home/user/project/src/app.php'; |
||||
$result = $this->compiler->getCppFile($phpFile); |
||||
|
||||
$this->assertStringEndsWith('.cc', $result); |
||||
$this->assertStringStartsWith($this->compiler->getBuildDir(), $result); |
||||
$this->assertStringContainsString('app', $result); |
||||
} |
||||
|
||||
public function testGetCppFilePreservesRelativePath(): void |
||||
{ |
||||
$phpFile = '/var/www/myapp/controllers/UserController.php'; |
||||
$result = $this->compiler->getCppFile($phpFile); |
||||
|
||||
$this->assertStringEndsWith('UserController.cc', $result); |
||||
$this->assertStringStartsWith($this->compiler->getBuildDir(), $result); |
||||
} |
||||
|
||||
public function testGetCppFileDotPhpReplaced(): void |
||||
{ |
||||
$phpFile = '/tmp/test_file.php'; |
||||
$result = $this->compiler->getCppFile($phpFile); |
||||
|
||||
$this->assertStringEndsWith('test_file.cc', $result); |
||||
$this->assertStringNotContainsString('.php', basename($result)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getObjectFile |
||||
// ======================================================================== |
||||
|
||||
public function testGetObjectFile(): void |
||||
{ |
||||
$cppFile = $this->compiler->getBuildDir() . '/include/test.cc'; |
||||
$result = $this->compiler->getObjectFile($cppFile); |
||||
|
||||
$this->assertStringEndsWith('.o', $result); |
||||
$this->assertStringContainsString('test', $result); |
||||
} |
||||
|
||||
public function testGetObjectFileDifferentObjectExtension(): void |
||||
{ |
||||
// On Linux the object extension is .o |
||||
$path = '/some/path/file.cc'; |
||||
$result = $this->compiler->getObjectFile($path); |
||||
$this->assertStringEndsWith('file.o', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getMethodName |
||||
// ======================================================================== |
||||
|
||||
public function testGetMethodName(): void |
||||
{ |
||||
$method = new Node\Stmt\ClassMethod('handle'); |
||||
$result = $this->invokeMethod('getMethodName', $method); |
||||
$this->assertEquals('handle', $result); |
||||
} |
||||
|
||||
public function testGetMethodNameConstructor(): void |
||||
{ |
||||
$method = new Node\Stmt\ClassMethod('__construct'); |
||||
$result = $this->invokeMethod('getMethodName', $method); |
||||
$this->assertEquals('__construct', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// getParentClass |
||||
// ======================================================================== |
||||
|
||||
public function testGetParentClassWithNamespace(): void |
||||
{ |
||||
$extends = new Node\Name('BaseController'); |
||||
$this->setProperty('namespace', 'App\\Controllers'); |
||||
$result = $this->invokeMethod('getParentClass', $extends); |
||||
$this->assertEquals('App\\Controllers\\BaseController', $result); |
||||
} |
||||
|
||||
public function testGetParentClassFullyQualified(): void |
||||
{ |
||||
$extends = new Node\Name\FullyQualified('App\\Entity\\Base'); |
||||
$result = $this->invokeMethod('getParentClass', $extends); |
||||
$this->assertEquals('App\\Entity\\Base', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// hasCppFileCache |
||||
// ======================================================================== |
||||
|
||||
public function testHasCppFileCacheWhenDisabled(): void |
||||
{ |
||||
// enableCache defaults to false, so cache should always return false |
||||
$this->assertFalse($this->compiler->hasCppFileCache('/tmp/test.php')); |
||||
} |
||||
|
||||
public function testHasCppFileCacheNonexistentFile(): void |
||||
{ |
||||
$this->assertFalse($this->compiler->hasCppFileCache('/nonexistent/path/test.php')); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// sortFiles |
||||
// ======================================================================== |
||||
|
||||
public function testSortFilesPreservesOrderForUnrelatedFiles(): void |
||||
{ |
||||
$files = ['/a/file1.php', '/a/file2.php', '/a/file3.php']; |
||||
$this->compiler->sortFiles($files); |
||||
// All original files must still be present |
||||
$this->assertContains('/a/file1.php', $files); |
||||
$this->assertContains('/a/file2.php', $files); |
||||
$this->assertContains('/a/file3.php', $files); |
||||
// Original files are preserved (sortFiles may append, not remove) |
||||
$this->assertGreaterThanOrEqual(3, count($files)); |
||||
} |
||||
|
||||
public function testSortFilesEmpty(): void |
||||
{ |
||||
$files = []; |
||||
$this->compiler->sortFiles($files); |
||||
// Empty array stays empty or nearly empty |
||||
$this->assertIsArray($files); |
||||
} |
||||
} |
||||
@ -0,0 +1,139 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\Reflection; |
||||
|
||||
class ReflectionTest extends TestCase |
||||
{ |
||||
public function testIsInternalClass(): void |
||||
{ |
||||
// Standard PHP internal classes |
||||
$this->assertTrue(Reflection::isInternalClass('stdClass')); |
||||
$this->assertTrue(Reflection::isInternalClass('Exception')); |
||||
$this->assertTrue(Reflection::isInternalClass('DateTime')); |
||||
} |
||||
|
||||
public function testIsInternalClassCaseInsensitive(): void |
||||
{ |
||||
$this->assertTrue(Reflection::isInternalClass('stdclass')); |
||||
$this->assertTrue(Reflection::isInternalClass('EXCEPTION')); |
||||
} |
||||
|
||||
public function testIsInternalClassNonexistent(): void |
||||
{ |
||||
$this->assertFalse(Reflection::isInternalClass('NonExistentClass_' . uniqid())); |
||||
} |
||||
|
||||
public function testIsInternalInterface(): void |
||||
{ |
||||
$this->assertTrue(Reflection::isInternalInterface('Iterator')); |
||||
$this->assertTrue(Reflection::isInternalInterface('ArrayAccess')); |
||||
$this->assertTrue(Reflection::isInternalInterface('JsonSerializable')); |
||||
} |
||||
|
||||
public function testIsInternalInterfaceNonexistent(): void |
||||
{ |
||||
$this->assertFalse(Reflection::isInternalInterface('NonExistentIface_' . uniqid())); |
||||
} |
||||
|
||||
public function testGetFunction(): void |
||||
{ |
||||
$ref = Reflection::getFunction('strlen'); |
||||
$this->assertNotNull($ref); |
||||
$this->assertInstanceOf(\ReflectionFunction::class, $ref); |
||||
$this->assertEquals('strlen', $ref->getName()); |
||||
} |
||||
|
||||
public function testGetFunctionNonexistent(): void |
||||
{ |
||||
$ref = Reflection::getFunction('nonexistent_func_' . uniqid()); |
||||
$this->assertNull($ref); |
||||
} |
||||
|
||||
public function testGetFunctionCaches(): void |
||||
{ |
||||
$ref1 = Reflection::getFunction('strtolower'); |
||||
$ref2 = Reflection::getFunction('strtolower'); |
||||
$this->assertSame($ref1, $ref2); |
||||
} |
||||
|
||||
public function testGetClass(): void |
||||
{ |
||||
$ref = Reflection::getClass('stdClass'); |
||||
$this->assertNotNull($ref); |
||||
$this->assertInstanceOf(\ReflectionClass::class, $ref); |
||||
} |
||||
|
||||
public function testGetClassNonexistent(): void |
||||
{ |
||||
$ref = Reflection::getClass('NonExistent_' . uniqid()); |
||||
$this->assertNull($ref); |
||||
} |
||||
|
||||
public function testGetFunctionReturnType(): void |
||||
{ |
||||
$type = Reflection::getFunctionReturnType('strlen'); |
||||
$this->assertEquals('int', $type); |
||||
} |
||||
|
||||
public function testGetFunctionReturnTypeNonexistent(): void |
||||
{ |
||||
$type = Reflection::getFunctionReturnType('nonexistent_' . uniqid()); |
||||
$this->assertNull($type); |
||||
} |
||||
|
||||
public function testGetFunctionParameter(): void |
||||
{ |
||||
$param = Reflection::getFunctionParameter('strlen', 0); |
||||
$this->assertNotNull($param); |
||||
$this->assertInstanceOf(\ReflectionParameter::class, $param); |
||||
} |
||||
|
||||
public function testGetFunctionParameterOutOfRange(): void |
||||
{ |
||||
$param = Reflection::getFunctionParameter('strlen', 999); |
||||
$this->assertNull($param); |
||||
} |
||||
|
||||
public function testHasMethod(): void |
||||
{ |
||||
$this->assertTrue(Reflection::hasMethod('Exception', 'getMessage')); |
||||
$this->assertTrue(Reflection::hasMethod('Exception', '__construct')); |
||||
|
||||
// Method that doesn't exist |
||||
$this->assertFalse(Reflection::hasMethod('Exception', 'nonexistent_' . uniqid())); |
||||
} |
||||
|
||||
public function testGetClassMethodModifiers(): void |
||||
{ |
||||
$modifiers = Reflection::getClassMethodModifiers('DateTime', 'format'); |
||||
$this->assertNotNull($modifiers); |
||||
$this->assertIsInt($modifiers); |
||||
} |
||||
|
||||
public function testGetClassMethodModifiersNonexistent(): void |
||||
{ |
||||
$modifiers = Reflection::getClassMethodModifiers('NonExistent_' . uniqid(), 'test'); |
||||
$this->assertNull($modifiers); |
||||
} |
||||
|
||||
public function testGetMethodReturnType(): void |
||||
{ |
||||
$type = Reflection::getMethodReturnType('Exception', 'getMessage'); |
||||
$this->assertEquals('string', $type); |
||||
} |
||||
|
||||
public function testGetMethodReturnTypeNonexistent(): void |
||||
{ |
||||
$type = Reflection::getMethodReturnType('NonExistent_' . uniqid(), 'test'); |
||||
$this->assertNull($type); |
||||
} |
||||
|
||||
public function testIsAbstractClass(): void |
||||
{ |
||||
$this->assertFalse(Reflection::isAbstractClass('stdClass')); |
||||
$this->assertFalse(Reflection::isAbstractClass('NonExistent_' . uniqid())); |
||||
} |
||||
} |
||||
@ -0,0 +1,311 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Tests; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use PhpAot\Php\CompilerTest; |
||||
use PhpAot\Php\CompilerBase; |
||||
use PhpAot\Php\ArgInfo; |
||||
|
||||
class TraitsTest extends TestCase |
||||
{ |
||||
private string $testDir; |
||||
private CompilerTest $compiler; |
||||
private \ReflectionClass $ref; |
||||
|
||||
protected function setUp(): void |
||||
{ |
||||
parent::setUp(); |
||||
$this->testDir = sys_get_temp_dir() . '/traits_test_' . uniqid(); |
||||
mkdir($this->testDir, 0777, true); |
||||
$this->compiler = CompilerTest::create($this->testDir); |
||||
$this->ref = new \ReflectionClass($this->compiler); |
||||
} |
||||
|
||||
protected function tearDown(): void |
||||
{ |
||||
parent::tearDown(); |
||||
if (is_dir($this->testDir)) { |
||||
$this->removeDirectory($this->testDir); |
||||
} |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
private function invoke(string $method, ...$args): mixed |
||||
{ |
||||
$m = $this->ref->getMethod($method); |
||||
$m->setAccessible(true); |
||||
return $m->invoke($this->compiler, ...$args); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// MagicMethodDetector::checkArgType |
||||
// ======================================================================== |
||||
|
||||
public function testCheckArgTypeExactMatch(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('checkArgType', 'php::Int', 'php::Int')); |
||||
$this->assertTrue($this->invoke('checkArgType', 'php::Str', 'php::Str')); |
||||
$this->assertTrue($this->invoke('checkArgType', 'php::Array', 'php::Array')); |
||||
} |
||||
|
||||
public function testCheckArgTypeMismatch(): void |
||||
{ |
||||
$this->assertFalse($this->invoke('checkArgType', 'php::Int', 'php::Str')); |
||||
$this->assertFalse($this->invoke('checkArgType', 'php::Float', 'php::Int')); |
||||
} |
||||
|
||||
public function testCheckArgTypeVarMatchesAny(): void |
||||
{ |
||||
// TYPE_VAR matches any expected type when canBeVar is true (default) |
||||
$this->assertTrue($this->invoke('checkArgType', 'php::Var', 'php::Int')); |
||||
$this->assertTrue($this->invoke('checkArgType', 'php::Var', 'php::Str')); |
||||
$this->assertTrue($this->invoke('checkArgType', 'php::Var', 'php::Array')); |
||||
} |
||||
|
||||
public function testCheckArgTypeVarDoesNotMatchWhenCannotBeVar(): void |
||||
{ |
||||
// TYPE_VAR does NOT match when canBeVar is false |
||||
$this->assertFalse($this->invoke('checkArgType', 'php::Var', 'php::Str', false)); |
||||
$this->assertFalse($this->invoke('checkArgType', 'php::Var', 'php::Int', false)); |
||||
} |
||||
|
||||
public function testCheckArgTypeExactMatchWithCannotBeVar(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('checkArgType', 'php::Str', 'php::Str', false)); |
||||
$this->assertTrue($this->invoke('checkArgType', 'php::Array', 'php::Array', false)); |
||||
} |
||||
|
||||
public function testCheckArgTypeVoid(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('checkArgType', 'void', 'void')); |
||||
$this->assertFalse($this->invoke('checkArgType', 'void', 'php::Str')); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// FuncCallOptimizer::isValidDefineName |
||||
// ======================================================================== |
||||
|
||||
public function testIsValidDefineNameValid(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isValidDefineName', 'MY_CONSTANT')); |
||||
$this->assertTrue($this->invoke('isValidDefineName', 'APP_NAME')); |
||||
$this->assertTrue($this->invoke('isValidDefineName', '_PRIVATE')); |
||||
$this->assertTrue($this->invoke('isValidDefineName', 'camelCase')); |
||||
$this->assertTrue($this->invoke('isValidDefineName', 'Test123')); |
||||
$this->assertTrue($this->invoke('isValidDefineName', '_')); |
||||
} |
||||
|
||||
public function testIsValidDefineNameInvalid(): void |
||||
{ |
||||
$this->assertFalse($this->invoke('isValidDefineName', '123abc')); // starts with digit |
||||
$this->assertFalse($this->invoke('isValidDefineName', 'has space')); // contains space |
||||
$this->assertFalse($this->invoke('isValidDefineName', 'has-dash')); // contains dash |
||||
$this->assertFalse($this->invoke('isValidDefineName', '')); // empty |
||||
$this->assertFalse($this->invoke('isValidDefineName', '0abc')); // starts with zero |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// StdContainerParser::isStdContainerType |
||||
// ======================================================================== |
||||
|
||||
public function testIsStdContainerTypeTrue(): void |
||||
{ |
||||
$this->assertTrue($this->invoke('isStdContainerType', CompilerBase::TYPE_STD_ARRAY)); |
||||
$this->assertTrue($this->invoke('isStdContainerType', CompilerBase::TYPE_STD_VECTOR)); |
||||
$this->assertTrue($this->invoke('isStdContainerType', CompilerBase::TYPE_STD_MAP)); |
||||
$this->assertTrue($this->invoke('isStdContainerType', CompilerBase::TYPE_STD_UNORDERED_MAP)); |
||||
} |
||||
|
||||
public function testIsStdContainerTypeFalse(): void |
||||
{ |
||||
$this->assertFalse($this->invoke('isStdContainerType', CompilerBase::TYPE_ARRAY)); |
||||
$this->assertFalse($this->invoke('isStdContainerType', CompilerBase::TYPE_INT)); |
||||
$this->assertFalse($this->invoke('isStdContainerType', CompilerBase::TYPE_VAR)); |
||||
$this->assertFalse($this->invoke('isStdContainerType', CompilerBase::TYPE_OBJECT)); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// StdContainerParser::getStdTypeKey |
||||
// ======================================================================== |
||||
|
||||
public function testGetStdTypeKeyBasic(): void |
||||
{ |
||||
$info = [ |
||||
'kind' => 'vector', |
||||
'decl' => 'php::StdVector<php::Int>', |
||||
'type' => 'php::Int', |
||||
'class' => '', |
||||
]; |
||||
$key = $this->invoke('getStdTypeKey', $info); |
||||
$this->assertStringContainsString('kind=vector', $key); |
||||
$this->assertStringContainsString('decl=php::StdVector<php::Int>', $key); |
||||
$this->assertStringContainsString('type=php::Int', $key); |
||||
$this->assertStringContainsString('class=', $key); |
||||
} |
||||
|
||||
public function testGetStdTypeKeyWithClass(): void |
||||
{ |
||||
$info = [ |
||||
'kind' => 'vector', |
||||
'decl' => 'php::StdVector<php::Object>', |
||||
'type' => 'php::Object', |
||||
'class' => 'App\\Entity\\User', |
||||
]; |
||||
$key = $this->invoke('getStdTypeKey', $info); |
||||
$this->assertStringContainsString('class=App\\Entity\\User', $key); |
||||
} |
||||
|
||||
public function testGetStdTypeKeyWithKeyType(): void |
||||
{ |
||||
$info = [ |
||||
'kind' => 'map', |
||||
'decl' => 'php::StdMap<php::Str, php::Int>', |
||||
'type' => 'php::Int', |
||||
'class' => '', |
||||
'keyType' => 'php::Str', |
||||
]; |
||||
$key = $this->invoke('getStdTypeKey', $info); |
||||
$this->assertStringContainsString('keyType=php::Str', $key); |
||||
} |
||||
|
||||
public function testGetStdTypeKeyWithoutKeyType(): void |
||||
{ |
||||
$info = [ |
||||
'kind' => 'array', |
||||
'decl' => 'php::StdArray<php::Int, 10>', |
||||
'type' => 'php::Int', |
||||
'class' => '', |
||||
]; |
||||
$key = $this->invoke('getStdTypeKey', $info); |
||||
$this->assertStringNotContainsString('keyType', $key); |
||||
} |
||||
|
||||
public function testGetStdTypeKeyUnorderedMap(): void |
||||
{ |
||||
$info = [ |
||||
'kind' => 'unordered_map', |
||||
'decl' => 'php::StdUnorderedMap<php::Str, php::Int>', |
||||
'type' => 'php::Int', |
||||
'class' => '', |
||||
'keyType' => 'php::Str', |
||||
]; |
||||
$key = $this->invoke('getStdTypeKey', $info); |
||||
$this->assertStringContainsString('kind=unordered_map', $key); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// PropertyPromotion::genPropertyPromotion |
||||
// ======================================================================== |
||||
|
||||
public function testGenPropertyPromotion(): void |
||||
{ |
||||
// Need context initialized for genCharPtr |
||||
$this->invoke('resetFunction'); |
||||
|
||||
$argInfo = new ArgInfo(); |
||||
$argInfo->name = 'title'; |
||||
$argInfo->type = 'php::Str'; |
||||
|
||||
$result = $this->invoke('genPropertyPromotion', $argInfo); |
||||
$this->assertStringContainsString('this_.setProperty', $result); |
||||
$this->assertStringContainsString('title', $result); |
||||
} |
||||
|
||||
public function testGenPropertyPromotionWithNumber(): void |
||||
{ |
||||
$this->invoke('resetFunction'); |
||||
|
||||
$argInfo = new ArgInfo(); |
||||
$argInfo->name = 'count'; |
||||
$argInfo->type = 'php::Int'; |
||||
|
||||
$result = $this->invoke('genPropertyPromotion', $argInfo); |
||||
$this->assertStringContainsString('count', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// ClosureGenerator::genScopeSwitchCode |
||||
// ======================================================================== |
||||
|
||||
public function testGenScopeSwitchCode(): void |
||||
{ |
||||
$this->invoke('resetFunction'); |
||||
|
||||
$result = $this->invoke('genScopeSwitchCode'); |
||||
$this->assertStringContainsString('php_switch_scope', $result); |
||||
$this->assertStringContainsString('ON_SCOPE_EXIT', $result); |
||||
$this->assertStringContainsString('php_restore_scope', $result); |
||||
} |
||||
|
||||
public function testGenScopeSwitchCodeTemplate(): void |
||||
{ |
||||
$this->invoke('resetFunction'); |
||||
|
||||
$result = $this->invoke('genScopeSwitchCode'); |
||||
// Should have the pattern: auto tmp_var_X = php_switch_scope(this_); |
||||
$this->assertStringContainsString('auto', $result); |
||||
$this->assertStringContainsString('= php_switch_scope(this_)', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// StdContainerParser::getStdArrayDecl |
||||
// ======================================================================== |
||||
|
||||
public function testGetStdArrayDecl(): void |
||||
{ |
||||
$result = $this->invoke('getStdArrayDecl', 'php::Int', [10]); |
||||
$this->assertEquals('php::StdArray<php::Int, 10>', $result); |
||||
} |
||||
|
||||
public function testGetStdArrayDeclNested(): void |
||||
{ |
||||
$result = $this->invoke('getStdArrayDecl', 'php::Int', [3, 4]); |
||||
$this->assertEquals('php::StdArray<php::StdArray<php::Int, 4>, 3>', $result); |
||||
} |
||||
|
||||
public function testGetStdArrayDeclFloat(): void |
||||
{ |
||||
$result = $this->invoke('getStdArrayDecl', 'php::Float', [5]); |
||||
$this->assertEquals('php::StdArray<php::Float, 5>', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// StdContainerParser::getStdMapDecl |
||||
// ======================================================================== |
||||
|
||||
public function testGetStdMapDecl(): void |
||||
{ |
||||
$result = $this->invoke('getStdMapDecl', 'std::map', 'php::Str', 'php::Int'); |
||||
$this->assertEquals('std::map<php::Str, php::Int>', $result); |
||||
} |
||||
|
||||
public function testGetStdMapDeclUnordered(): void |
||||
{ |
||||
$result = $this->invoke('getStdMapDecl', 'std::unordered_map', 'php::Int', 'php::Str'); |
||||
$this->assertEquals('std::unordered_map<php::Int, php::Str>', $result); |
||||
} |
||||
|
||||
// ======================================================================== |
||||
// StdContainerParser::getStdValueTypeBytes |
||||
// ======================================================================== |
||||
|
||||
public function testGetStdValueTypeBytes(): void |
||||
{ |
||||
$this->assertGreaterThan(0, $this->invoke('getStdValueTypeBytes', CompilerBase::TYPE_INT)); |
||||
$this->assertGreaterThan(0, $this->invoke('getStdValueTypeBytes', CompilerBase::TYPE_FLOAT)); |
||||
$this->assertGreaterThan(0, $this->invoke('getStdValueTypeBytes', CompilerBase::TYPE_BOOL)); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue