mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-18 04:44:53 +08:00
582 lines
20 KiB
PHP
582 lines
20 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\StaticPHP\Registry;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use StaticPHP\Attribute\Package\Extension;
|
|
use StaticPHP\Attribute\Package\Library;
|
|
use StaticPHP\Attribute\Package\Target;
|
|
use StaticPHP\Config\PackageConfig;
|
|
use StaticPHP\Exception\RegistryException;
|
|
use StaticPHP\Exception\WrongUsageException;
|
|
use StaticPHP\Package\LibraryPackage;
|
|
use StaticPHP\Package\PhpExtensionPackage;
|
|
use StaticPHP\Package\TargetPackage;
|
|
use StaticPHP\Registry\PackageLoader;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
class PackageLoaderTest extends TestCase
|
|
{
|
|
private string $tempDir;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->tempDir = sys_get_temp_dir() . '/package_loader_test_' . uniqid();
|
|
mkdir($this->tempDir, 0755, true);
|
|
|
|
// Reset PackageLoader state
|
|
$reflection = new \ReflectionClass(PackageLoader::class);
|
|
|
|
$property = $reflection->getProperty('packages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, null);
|
|
|
|
$property = $reflection->getProperty('before_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, []);
|
|
|
|
$property = $reflection->getProperty('after_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, []);
|
|
|
|
$property = $reflection->getProperty('loaded_classes');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, []);
|
|
|
|
// Reset PackageConfig state
|
|
$configReflection = new \ReflectionClass(PackageConfig::class);
|
|
$configProperty = $configReflection->getProperty('package_configs');
|
|
$configProperty->setAccessible(true);
|
|
$configProperty->setValue(null, []);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
parent::tearDown();
|
|
// Clean up temp directory
|
|
if (is_dir($this->tempDir)) {
|
|
$this->removeDirectory($this->tempDir);
|
|
}
|
|
|
|
// Reset PackageLoader state
|
|
$reflection = new \ReflectionClass(PackageLoader::class);
|
|
|
|
$property = $reflection->getProperty('packages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, null);
|
|
|
|
$property = $reflection->getProperty('before_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, []);
|
|
|
|
$property = $reflection->getProperty('after_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, []);
|
|
|
|
$property = $reflection->getProperty('loaded_classes');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, []);
|
|
|
|
// Reset PackageConfig state
|
|
$configReflection = new \ReflectionClass(PackageConfig::class);
|
|
$configProperty = $configReflection->getProperty('package_configs');
|
|
$configProperty->setAccessible(true);
|
|
$configProperty->setValue(null, []);
|
|
}
|
|
|
|
public function testInitPackageInstancesOnlyRunsOnce(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
|
|
PackageLoader::initPackageInstances();
|
|
PackageLoader::initPackageInstances();
|
|
|
|
// Should only initialize once
|
|
$this->assertTrue(PackageLoader::hasPackage('test-lib'));
|
|
}
|
|
|
|
public function testInitPackageInstancesCreatesLibraryPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$package = PackageLoader::getPackage('test-lib');
|
|
$this->assertInstanceOf(LibraryPackage::class, $package);
|
|
}
|
|
|
|
public function testInitPackageInstancesCreatesPhpExtensionPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-ext', 'php-extension');
|
|
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$package = PackageLoader::getPackage('test-ext');
|
|
$this->assertInstanceOf(PhpExtensionPackage::class, $package);
|
|
}
|
|
|
|
public function testInitPackageInstancesCreatesTargetPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-target', 'target');
|
|
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$package = PackageLoader::getPackage('test-target');
|
|
$this->assertInstanceOf(TargetPackage::class, $package);
|
|
}
|
|
|
|
public function testInitPackageInstancesCreatesVirtualTargetPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-virtual-target', 'virtual-target');
|
|
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$package = PackageLoader::getPackage('test-virtual-target');
|
|
$this->assertInstanceOf(TargetPackage::class, $package);
|
|
}
|
|
|
|
public function testInitPackageInstancesThrowsExceptionForUnknownType(): void
|
|
{
|
|
$this->createTestPackageConfig('test-unknown', 'unknown-type');
|
|
|
|
$this->expectException(RegistryException::class);
|
|
$this->expectExceptionMessage('has unknown type');
|
|
|
|
PackageLoader::initPackageInstances();
|
|
}
|
|
|
|
public function testHasPackageReturnsTrueForExistingPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$this->assertTrue(PackageLoader::hasPackage('test-lib'));
|
|
}
|
|
|
|
public function testHasPackageReturnsFalseForNonExistingPackage(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$this->assertFalse(PackageLoader::hasPackage('non-existent'));
|
|
}
|
|
|
|
public function testGetPackageReturnsPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$package = PackageLoader::getPackage('test-lib');
|
|
$this->assertInstanceOf(LibraryPackage::class, $package);
|
|
}
|
|
|
|
public function testGetPackageThrowsExceptionForNonExistingPackage(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$this->expectException(WrongUsageException::class);
|
|
$this->expectExceptionMessage('not found');
|
|
|
|
PackageLoader::getPackage('non-existent');
|
|
}
|
|
|
|
public function testGetTargetPackageReturnsTargetPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-target', 'target');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$package = PackageLoader::getTargetPackage('test-target');
|
|
$this->assertInstanceOf(TargetPackage::class, $package);
|
|
}
|
|
|
|
public function testGetTargetPackageThrowsExceptionForNonTargetPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$this->expectException(WrongUsageException::class);
|
|
$this->expectExceptionMessage('is not a TargetPackage');
|
|
|
|
PackageLoader::getTargetPackage('test-lib');
|
|
}
|
|
|
|
public function testGetLibraryPackageReturnsLibraryPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$package = PackageLoader::getLibraryPackage('test-lib');
|
|
$this->assertInstanceOf(LibraryPackage::class, $package);
|
|
}
|
|
|
|
public function testGetLibraryPackageThrowsExceptionForNonLibraryPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('ext-test-ext', 'php-extension');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$this->expectException(WrongUsageException::class);
|
|
$this->expectExceptionMessage('is not a LibraryPackage');
|
|
|
|
PackageLoader::getLibraryPackage('ext-test-ext');
|
|
}
|
|
|
|
public function testGetPackagesReturnsAllPackages(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
$this->createTestPackageConfig('test-ext', 'php-extension');
|
|
$this->createTestPackageConfig('test-target', 'target');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$packages = iterator_to_array(PackageLoader::getPackages());
|
|
$this->assertCount(3, $packages);
|
|
}
|
|
|
|
public function testGetPackagesWithTypeFilterReturnsFilteredPackages(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
$this->createTestPackageConfig('test-ext', 'php-extension');
|
|
$this->createTestPackageConfig('test-target', 'target');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$packages = iterator_to_array(PackageLoader::getPackages('library'));
|
|
$this->assertCount(1, $packages);
|
|
$this->assertArrayHasKey('test-lib', $packages);
|
|
}
|
|
|
|
public function testGetPackagesWithArrayTypeFilterReturnsFilteredPackages(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
$this->createTestPackageConfig('test-ext', 'php-extension');
|
|
$this->createTestPackageConfig('test-target', 'target');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$packages = iterator_to_array(PackageLoader::getPackages(['library', 'target']));
|
|
$this->assertCount(2, $packages);
|
|
$this->assertArrayHasKey('test-lib', $packages);
|
|
$this->assertArrayHasKey('test-target', $packages);
|
|
}
|
|
|
|
public function testLoadFromClassWithLibraryAttribute(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$class = new #[Library('test-lib')] class {};
|
|
|
|
PackageLoader::loadFromClass(get_class($class));
|
|
|
|
$this->assertTrue(PackageLoader::hasPackage('test-lib'));
|
|
}
|
|
|
|
public function testLoadFromClassWithExtensionAttribute(): void
|
|
{
|
|
$this->createTestPackageConfig('ext-test-ext', 'php-extension');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$class = new #[Extension('ext-test-ext')] class {};
|
|
|
|
PackageLoader::loadFromClass(get_class($class));
|
|
|
|
$this->assertTrue(PackageLoader::hasPackage('ext-test-ext'));
|
|
}
|
|
|
|
public function testLoadFromClassWithTargetAttribute(): void
|
|
{
|
|
$this->createTestPackageConfig('test-target', 'target');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$class = new #[Target('test-target')] class {};
|
|
|
|
PackageLoader::loadFromClass(get_class($class));
|
|
|
|
$this->assertTrue(PackageLoader::hasPackage('test-target'));
|
|
}
|
|
|
|
public function testLoadFromClassThrowsExceptionForUndefinedPackage(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$class = new #[Library('undefined-lib')] class {};
|
|
|
|
$this->expectException(RegistryException::class);
|
|
$this->expectExceptionMessage('not defined in config');
|
|
|
|
PackageLoader::loadFromClass(get_class($class));
|
|
}
|
|
|
|
public function testLoadFromClassThrowsExceptionForTypeMismatch(): void
|
|
{
|
|
$this->createTestPackageConfig('ext-test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
// Try to load with Extension attribute but config says library
|
|
$class = new #[Extension('ext-test-lib')] class {};
|
|
|
|
$this->expectException(RegistryException::class);
|
|
$this->expectExceptionMessage('type mismatch');
|
|
|
|
PackageLoader::loadFromClass(get_class($class));
|
|
}
|
|
|
|
public function testLoadFromClassSkipsDuplicateClasses(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$className = get_class(new #[Library('test-lib')] class {});
|
|
|
|
// Load twice
|
|
PackageLoader::loadFromClass($className);
|
|
PackageLoader::loadFromClass($className);
|
|
|
|
// Should not throw exception
|
|
$this->assertTrue(PackageLoader::hasPackage('test-lib'));
|
|
}
|
|
|
|
public function testLoadFromClassWithNoPackageAttribute(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$class = new class {
|
|
public function regularMethod(): void {}
|
|
};
|
|
|
|
// Should not throw exception
|
|
PackageLoader::loadFromClass(get_class($class));
|
|
|
|
// Verify no side effects
|
|
$this->assertTrue(true);
|
|
}
|
|
|
|
public function testCheckLoadedStageEventsThrowsExceptionForUnknownPackage(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
// Manually add a before_stage for non-existent package
|
|
$reflection = new \ReflectionClass(PackageLoader::class);
|
|
$property = $reflection->getProperty('before_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, [
|
|
'non-existent-package' => [
|
|
'stage-name' => [[fn () => null, null]],
|
|
],
|
|
]);
|
|
|
|
$this->expectException(RegistryException::class);
|
|
$this->expectExceptionMessage('unknown package');
|
|
|
|
PackageLoader::checkLoadedStageEvents();
|
|
}
|
|
|
|
public function testCheckLoadedStageEventsThrowsExceptionForUnknownStage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
// Add a build function for current OS so the stage validation is triggered
|
|
$package = PackageLoader::getPackage('test-lib');
|
|
$package->addBuildFunction(PHP_OS_FAMILY, fn () => null);
|
|
|
|
// Manually add a before_stage for non-existent stage
|
|
$reflection = new \ReflectionClass(PackageLoader::class);
|
|
$property = $reflection->getProperty('before_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, [
|
|
'test-lib' => [
|
|
'non-existent-stage' => [[fn () => null, null]],
|
|
],
|
|
]);
|
|
|
|
$this->expectException(RegistryException::class);
|
|
$this->expectExceptionMessage('is not registered');
|
|
|
|
PackageLoader::checkLoadedStageEvents();
|
|
}
|
|
|
|
public function testCheckLoadedStageEventsThrowsExceptionForUnknownOnlyWhenPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$package = PackageLoader::getPackage('test-lib');
|
|
$package->addStage('test-stage', fn () => null);
|
|
|
|
// Manually add a before_stage with unknown only_when_package_resolved
|
|
$reflection = new \ReflectionClass(PackageLoader::class);
|
|
$property = $reflection->getProperty('before_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, [
|
|
'test-lib' => [
|
|
'test-stage' => [[fn () => null, 'non-existent-package']],
|
|
],
|
|
]);
|
|
|
|
$this->expectException(RegistryException::class);
|
|
$this->expectExceptionMessage('unknown only_when_package_resolved package');
|
|
|
|
PackageLoader::checkLoadedStageEvents();
|
|
}
|
|
|
|
public function testCheckLoadedStageEventsDoesNotThrowForNonCurrentOSPackage(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
// Add a build function for a different OS (not current OS)
|
|
$package = PackageLoader::getPackage('test-lib');
|
|
$otherOS = PHP_OS_FAMILY === 'Windows' ? 'Linux' : 'Windows';
|
|
$package->addBuildFunction($otherOS, fn () => null);
|
|
|
|
// Manually add a before_stage for 'build' stage
|
|
// This should NOT throw an exception because the package has no build function for current OS
|
|
$reflection = new \ReflectionClass(PackageLoader::class);
|
|
$property = $reflection->getProperty('before_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, [
|
|
'test-lib' => [
|
|
'build' => [[fn () => null, null]],
|
|
],
|
|
]);
|
|
|
|
// This should not throw an exception
|
|
PackageLoader::checkLoadedStageEvents();
|
|
|
|
$this->assertTrue(true); // If we get here, the test passed
|
|
}
|
|
|
|
public function testGetBeforeStageCallbacksReturnsCallbacks(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
// Manually add some before_stage callbacks
|
|
$callback1 = fn () => 'callback1';
|
|
$callback2 = fn () => 'callback2';
|
|
|
|
$reflection = new \ReflectionClass(PackageLoader::class);
|
|
$property = $reflection->getProperty('before_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, [
|
|
'test-package' => [
|
|
'test-stage' => [
|
|
[$callback1, null],
|
|
[$callback2, null],
|
|
],
|
|
],
|
|
]);
|
|
|
|
$callbacks = iterator_to_array(PackageLoader::getBeforeStageCallbacks('test-package', 'test-stage'));
|
|
$this->assertCount(2, $callbacks);
|
|
}
|
|
|
|
public function testGetAfterStageCallbacksReturnsCallbacks(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
// Manually add some after_stage callbacks
|
|
$callback1 = fn () => 'callback1';
|
|
$callback2 = fn () => 'callback2';
|
|
|
|
$reflection = new \ReflectionClass(PackageLoader::class);
|
|
$property = $reflection->getProperty('after_stages');
|
|
$property->setAccessible(true);
|
|
$property->setValue(null, [
|
|
'test-package' => [
|
|
'test-stage' => [
|
|
[$callback1, null],
|
|
[$callback2, null],
|
|
],
|
|
],
|
|
]);
|
|
|
|
$callbacks = PackageLoader::getAfterStageCallbacks('test-package', 'test-stage');
|
|
$this->assertCount(2, $callbacks);
|
|
}
|
|
|
|
public function testGetBeforeStageCallbacksReturnsEmptyForNonExistentPackage(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$callbacks = iterator_to_array(PackageLoader::getBeforeStageCallbacks('non-existent', 'stage'));
|
|
$this->assertEmpty($callbacks);
|
|
}
|
|
|
|
public function testGetAfterStageCallbacksReturnsEmptyForNonExistentPackage(): void
|
|
{
|
|
PackageLoader::initPackageInstances();
|
|
|
|
$callbacks = PackageLoader::getAfterStageCallbacks('non-existent', 'stage');
|
|
$this->assertEmpty($callbacks);
|
|
}
|
|
|
|
public function testRegisterAllDefaultStagesRegistersForPhpExtensions(): void
|
|
{
|
|
$this->createTestPackageConfig('test-ext', 'php-extension');
|
|
PackageLoader::initPackageInstances();
|
|
|
|
PackageLoader::registerAllDefaultStages();
|
|
|
|
$package = PackageLoader::getPackage('test-ext');
|
|
$this->assertInstanceOf(PhpExtensionPackage::class, $package);
|
|
// Default stages should be registered (we can't easily verify this without accessing internal state)
|
|
}
|
|
|
|
public function testLoadFromPsr4DirLoadsAllClasses(): void
|
|
{
|
|
$this->createTestPackageConfig('test-lib', 'library');
|
|
|
|
// Create a PSR-4 directory structure
|
|
$psr4Dir = $this->tempDir . '/PackageClasses';
|
|
mkdir($psr4Dir, 0755, true);
|
|
|
|
// Create test class file
|
|
$classContent = '<?php
|
|
namespace Test\Package;
|
|
|
|
use StaticPHP\Attribute\Package\Library;
|
|
|
|
#[Library("test-lib")]
|
|
class TestPackage1 {
|
|
}';
|
|
file_put_contents($psr4Dir . '/TestPackage1.php', $classContent);
|
|
|
|
// Load with auto_require enabled
|
|
PackageLoader::loadFromPsr4Dir($psr4Dir, 'Test\Package', true);
|
|
|
|
$this->assertTrue(PackageLoader::hasPackage('test-lib'));
|
|
}
|
|
|
|
private function removeDirectory(string $dir): void
|
|
{
|
|
if (!is_dir($dir)) {
|
|
return;
|
|
}
|
|
$files = array_diff(scandir($dir), ['.', '..']);
|
|
foreach ($files as $file) {
|
|
$path = $dir . '/' . $file;
|
|
if (is_dir($path)) {
|
|
$this->removeDirectory($path);
|
|
} else {
|
|
unlink($path);
|
|
}
|
|
}
|
|
rmdir($dir);
|
|
}
|
|
|
|
private function createTestPackageConfig(string $name, string $type): void
|
|
{
|
|
$reflection = new \ReflectionClass(PackageConfig::class);
|
|
$property = $reflection->getProperty('package_configs');
|
|
$property->setAccessible(true);
|
|
$configs = $property->getValue();
|
|
$configs[$name] = [
|
|
'type' => $type,
|
|
'deps' => [],
|
|
];
|
|
$property->setValue(null, $configs);
|
|
}
|
|
}
|