- Add CompilerGeneratorFingerprintTest to verify embedded snapshot usage - Test that compiler snapshots are stable and change with source or options - Verify ordinary programs and extensions do not register compiler snapshots - Add separate process attribute for accurate testing conditions - Test incremental generator fingerprint generation and registration logic feat(build): implement compiler build fingerprint registration - Embed immutable source snapshot in self-hosted compiler builds - Add fallback mechanism for executables built before metadata introduction - Optimize performance by avoiding expensive source file hashing - Generate compiled generator fingerprint registration during translation - Prevent recursive fingerprinting in repeated self-hosted builds - Register compiler build fingerprint only for binary builds with specific criteriamaster
parent
5d4f31a9ab
commit
05c5b591aa
3 changed files with 136 additions and 0 deletions
@ -0,0 +1,90 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace TypePhp\Tests\Build; |
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase; |
||||||
|
use PHPUnit\Framework\Attributes\RunInSeparateProcess; |
||||||
|
use ReflectionClass; |
||||||
|
use TypePhp\CompilerBase; |
||||||
|
use TypePhp\CompilerTest; |
||||||
|
use TypePhp\Entity\ClassDef; |
||||||
|
use TypePhp\Entity\FunctionDef; |
||||||
|
use TypePhp\Type; |
||||||
|
|
||||||
|
final class CompilerGeneratorFingerprintTest extends TestCase |
||||||
|
{ |
||||||
|
#[RunInSeparateProcess] |
||||||
|
public function testEmbeddedSnapshotIsUsedOnlyByNativeEntry(): void |
||||||
|
{ |
||||||
|
define('TYPEPHP_COMPILER_BUILD_FINGERPRINT', str_repeat('a', 64)); |
||||||
|
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH); |
||||||
|
$method = (new ReflectionClass(\TypePhp\Translator::class))->getMethod('getIncrementalGeneratorFingerprint'); |
||||||
|
$native = $method->invoke($compiler); |
||||||
|
self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/D', $native); |
||||||
|
self::assertSame($native, $method->invoke($compiler)); |
||||||
|
define('TYPEPHP_PHP_SCRIPT_ENTRY', true); |
||||||
|
self::assertNotSame($native, $method->invoke($compiler)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testCompilerSnapshotIsStableAndChangesWithSourceOrOptions(): void |
||||||
|
{ |
||||||
|
$compiler = $this->compiler(); |
||||||
|
$registration = $this->registration($compiler); |
||||||
|
self::assertMatchesRegularExpression('/php::fn::define\("TYPEPHP_COMPILER_BUILD_FINGERPRINT", php::Str\("[a-f0-9]{64}"\)\);/', $registration); |
||||||
|
self::assertSame($registration, $this->registration($compiler)); |
||||||
|
|
||||||
|
$this->set($compiler, 'incrementalGeneratorFingerprint', 'a different predecessor'); |
||||||
|
self::assertSame($registration, $this->registration($compiler)); |
||||||
|
$this->set($compiler, 'incrementalSourceHashes', ['compiler.php' => 'changed']); |
||||||
|
self::assertNotSame($registration, $this->registration($compiler)); |
||||||
|
$sourceRegistration = $this->registration($compiler); |
||||||
|
$this->set($compiler, 'optimizeLevel', 1); |
||||||
|
self::assertNotSame($sourceRegistration, $this->registration($compiler)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testOrdinaryProgramsAndExtensionsDoNotRegisterCompilerSnapshot(): void |
||||||
|
{ |
||||||
|
$compiler = $this->compiler(); |
||||||
|
$compiler->setBuildMode(CompilerBase::BUILD_MODE_EXT); |
||||||
|
self::assertSame('', $this->registration($compiler)); |
||||||
|
$compiler->setBuildMode(CompilerBase::BUILD_MODE_BIN); |
||||||
|
$this->set($compiler, 'incrementalPlanInitialized', false); |
||||||
|
self::assertSame('', $this->registration($compiler)); |
||||||
|
$this->set($compiler, 'incrementalPlanInitialized', true); |
||||||
|
$symbols = (new ReflectionClass($compiler))->getProperty('symbols')->getValue($compiler); |
||||||
|
$entry = $symbols->function((new ReflectionClass($compiler))->getMethod('escapeFunction')->invoke($compiler, 'main')); |
||||||
|
$entry->sourceFile = TYPEPHP_ROOT_PATH . '/src/CompilerBase.php'; |
||||||
|
self::assertSame('', $this->registration($compiler)); |
||||||
|
} |
||||||
|
|
||||||
|
private function compiler(): CompilerTest |
||||||
|
{ |
||||||
|
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH); |
||||||
|
$compiler->setBuildMode(CompilerBase::BUILD_MODE_BIN); |
||||||
|
$reflection = new ReflectionClass($compiler); |
||||||
|
$symbols = $reflection->getProperty('symbols')->getValue($compiler); |
||||||
|
$entry = new FunctionDef('main', Type::VOID, ''); |
||||||
|
$entry->sourceFile = TYPEPHP_ROOT_PATH . '/src/compiler.php'; |
||||||
|
$symbols->putFunction($reflection->getMethod('escapeFunction')->invoke($compiler, 'main'), $entry); |
||||||
|
$class = new ClassDef('Translator', 0, 'TypePhp'); |
||||||
|
$class->sourceFile = TYPEPHP_ROOT_PATH . '/src/Translator.php'; |
||||||
|
$symbols->putClass($reflection->getMethod('escapeClass')->invoke($compiler, 'TypePhp\\Translator'), $class); |
||||||
|
$this->set($compiler, 'incrementalPlanInitialized', true); |
||||||
|
$this->set($compiler, 'incrementalSourceHashes', ['compiler.php' => 'original']); |
||||||
|
return $compiler; |
||||||
|
} |
||||||
|
|
||||||
|
private function registration(CompilerTest $compiler): string |
||||||
|
{ |
||||||
|
return (new ReflectionClass($compiler))->getMethod('genCompiledGeneratorFingerprintRegistration')->invoke($compiler); |
||||||
|
} |
||||||
|
|
||||||
|
private function set(CompilerTest $compiler, string $property, mixed $value): void |
||||||
|
{ |
||||||
|
$reflection = new ReflectionClass($compiler); |
||||||
|
while (!$reflection->hasProperty($property)) { |
||||||
|
$reflection = $reflection->getParentClass(); |
||||||
|
} |
||||||
|
$reflection->getProperty($property)->setValue($compiler, $value); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue