mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-20 23:35:37 +08:00
Refactor test structure and update paths for improved organization
This commit is contained in:
440
tests/StaticPHP/Registry/ArtifactLoaderTest.php
Normal file
440
tests/StaticPHP/Registry/ArtifactLoaderTest.php
Normal file
@@ -0,0 +1,440 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\Registry;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StaticPHP\Artifact\Artifact;
|
||||
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
|
||||
use StaticPHP\Attribute\Artifact\AfterSourceExtract;
|
||||
use StaticPHP\Attribute\Artifact\BinaryExtract;
|
||||
use StaticPHP\Attribute\Artifact\CustomBinary;
|
||||
use StaticPHP\Attribute\Artifact\CustomSource;
|
||||
use StaticPHP\Attribute\Artifact\SourceExtract;
|
||||
use StaticPHP\Config\ArtifactConfig;
|
||||
use StaticPHP\Exception\ValidationException;
|
||||
use StaticPHP\Registry\ArtifactLoader;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ArtifactLoaderTest extends TestCase
|
||||
{
|
||||
private string $tempDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->tempDir = sys_get_temp_dir() . '/artifact_loader_test_' . uniqid();
|
||||
mkdir($this->tempDir, 0755, true);
|
||||
|
||||
// Reset ArtifactLoader and ArtifactConfig state
|
||||
$reflection = new \ReflectionClass(ArtifactLoader::class);
|
||||
$property = $reflection->getProperty('artifacts');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(null, null);
|
||||
|
||||
$configReflection = new \ReflectionClass(ArtifactConfig::class);
|
||||
$configProperty = $configReflection->getProperty('artifact_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 ArtifactLoader and ArtifactConfig state
|
||||
$reflection = new \ReflectionClass(ArtifactLoader::class);
|
||||
$property = $reflection->getProperty('artifacts');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(null, null);
|
||||
|
||||
$configReflection = new \ReflectionClass(ArtifactConfig::class);
|
||||
$configProperty = $configReflection->getProperty('artifact_configs');
|
||||
$configProperty->setAccessible(true);
|
||||
$configProperty->setValue(null, []);
|
||||
}
|
||||
|
||||
public function testInitArtifactInstancesOnlyRunsOnce(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
// Should only initialize once
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertInstanceOf(Artifact::class, $artifact);
|
||||
}
|
||||
|
||||
public function testGetArtifactInstanceReturnsNullForNonExistent(): void
|
||||
{
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
$artifact = ArtifactLoader::getArtifactInstance('non-existent-artifact');
|
||||
$this->assertNull($artifact);
|
||||
}
|
||||
|
||||
public function testGetArtifactInstanceReturnsArtifact(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertInstanceOf(Artifact::class, $artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithCustomSourceAttribute(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[CustomSource('test-artifact')]
|
||||
public function customSource(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassThrowsExceptionForInvalidCustomSourceArtifact(): void
|
||||
{
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[CustomSource('non-existent-artifact')]
|
||||
public function customSource(): void {}
|
||||
};
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact 'non-existent-artifact' not found for #[CustomSource]");
|
||||
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithCustomBinaryAttribute(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[CustomBinary('test-artifact', ['linux-x86_64', 'macos-aarch64'])]
|
||||
public function customBinary(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassThrowsExceptionForInvalidCustomBinaryArtifact(): void
|
||||
{
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[CustomBinary('non-existent-artifact', ['linux-x86_64'])]
|
||||
public function customBinary(): void {}
|
||||
};
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact 'non-existent-artifact' not found for #[CustomBinary]");
|
||||
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithSourceExtractAttribute(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[SourceExtract('test-artifact')]
|
||||
public function sourceExtract(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassThrowsExceptionForInvalidSourceExtractArtifact(): void
|
||||
{
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[SourceExtract('non-existent-artifact')]
|
||||
public function sourceExtract(): void {}
|
||||
};
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact 'non-existent-artifact' not found for #[SourceExtract]");
|
||||
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithBinaryExtractAttribute(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[BinaryExtract('test-artifact')]
|
||||
public function binaryExtract(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithBinaryExtractAttributeAndPlatforms(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[BinaryExtract('test-artifact', platforms: ['linux-x86_64', 'darwin-aarch64'])]
|
||||
public function binaryExtract(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassThrowsExceptionForInvalidBinaryExtractArtifact(): void
|
||||
{
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[BinaryExtract('non-existent-artifact')]
|
||||
public function binaryExtract(): void {}
|
||||
};
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact 'non-existent-artifact' not found for #[BinaryExtract]");
|
||||
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithAfterSourceExtractAttribute(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[AfterSourceExtract('test-artifact')]
|
||||
public function afterSourceExtract(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassThrowsExceptionForInvalidAfterSourceExtractArtifact(): void
|
||||
{
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[AfterSourceExtract('non-existent-artifact')]
|
||||
public function afterSourceExtract(): void {}
|
||||
};
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact 'non-existent-artifact' not found for #[AfterSourceExtract]");
|
||||
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithAfterBinaryExtractAttribute(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[AfterBinaryExtract('test-artifact')]
|
||||
public function afterBinaryExtract(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithAfterBinaryExtractAttributeAndPlatforms(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[AfterBinaryExtract('test-artifact', platforms: ['linux-x86_64'])]
|
||||
public function afterBinaryExtract(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassThrowsExceptionForInvalidAfterBinaryExtractArtifact(): void
|
||||
{
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[AfterBinaryExtract('non-existent-artifact')]
|
||||
public function afterBinaryExtract(): void {}
|
||||
};
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact 'non-existent-artifact' not found for #[AfterBinaryExtract]");
|
||||
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithMultipleAttributes(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact-1');
|
||||
$this->createTestArtifactConfig('test-artifact-2');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[CustomSource('test-artifact-1')]
|
||||
public function customSource(): void {}
|
||||
|
||||
#[CustomBinary('test-artifact-2', ['linux-x86_64'])]
|
||||
public function customBinary(): void {}
|
||||
|
||||
#[SourceExtract('test-artifact-1')]
|
||||
public function sourceExtract(): void {}
|
||||
|
||||
#[AfterSourceExtract('test-artifact-2')]
|
||||
public function afterSourceExtract(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact1 = ArtifactLoader::getArtifactInstance('test-artifact-1');
|
||||
$artifact2 = ArtifactLoader::getArtifactInstance('test-artifact-2');
|
||||
$this->assertNotNull($artifact1);
|
||||
$this->assertNotNull($artifact2);
|
||||
}
|
||||
|
||||
public function testLoadFromClassIgnoresNonPublicMethods(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
#[CustomSource('test-artifact')]
|
||||
public function publicCustomSource(): void {}
|
||||
|
||||
#[CustomSource('test-artifact')]
|
||||
private function privateCustomSource(): void {}
|
||||
|
||||
#[CustomSource('test-artifact')]
|
||||
protected function protectedCustomSource(): void {}
|
||||
};
|
||||
|
||||
// Should only process public method
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromPsr4DirLoadsAllClasses(): void
|
||||
{
|
||||
$this->createTestArtifactConfig('test-artifact');
|
||||
|
||||
// Create a PSR-4 directory structure
|
||||
$psr4Dir = $this->tempDir . '/ArtifactClasses';
|
||||
mkdir($psr4Dir, 0755, true);
|
||||
|
||||
// Create test class file
|
||||
$classContent = '<?php
|
||||
namespace Test\Artifact;
|
||||
|
||||
use StaticPHP\Attribute\Artifact\CustomSource;
|
||||
|
||||
class TestArtifact1 {
|
||||
#[CustomSource("test-artifact")]
|
||||
public function customSource() {}
|
||||
}';
|
||||
file_put_contents($psr4Dir . '/TestArtifact1.php', $classContent);
|
||||
|
||||
// Load with auto_require enabled
|
||||
ArtifactLoader::loadFromPsr4Dir($psr4Dir, 'Test\Artifact', true);
|
||||
|
||||
$artifact = ArtifactLoader::getArtifactInstance('test-artifact');
|
||||
$this->assertNotNull($artifact);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithNoAttributes(): void
|
||||
{
|
||||
ArtifactLoader::initArtifactInstances();
|
||||
|
||||
$class = new class {
|
||||
public function regularMethod(): void {}
|
||||
};
|
||||
|
||||
// Should not throw exception
|
||||
ArtifactLoader::loadFromClass(get_class($class));
|
||||
|
||||
// Verify no side effects
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
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 createTestArtifactConfig(string $name): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(ArtifactConfig::class);
|
||||
$property = $reflection->getProperty('artifact_configs');
|
||||
$property->setAccessible(true);
|
||||
$configs = $property->getValue();
|
||||
$configs[$name] = [
|
||||
'type' => 'source',
|
||||
'url' => 'https://example.com/test.tar.gz',
|
||||
];
|
||||
$property->setValue(null, $configs);
|
||||
}
|
||||
}
|
||||
374
tests/StaticPHP/Registry/DoctorLoaderTest.php
Normal file
374
tests/StaticPHP/Registry/DoctorLoaderTest.php
Normal file
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\Registry;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StaticPHP\Attribute\Doctor\CheckItem;
|
||||
use StaticPHP\Attribute\Doctor\FixItem;
|
||||
use StaticPHP\Attribute\Doctor\OptionalCheck;
|
||||
use StaticPHP\Registry\DoctorLoader;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class DoctorLoaderTest extends TestCase
|
||||
{
|
||||
private string $tempDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->tempDir = sys_get_temp_dir() . '/doctor_loader_test_' . uniqid();
|
||||
mkdir($this->tempDir, 0755, true);
|
||||
|
||||
// Reset DoctorLoader state
|
||||
$reflection = new \ReflectionClass(DoctorLoader::class);
|
||||
$property = $reflection->getProperty('doctor_items');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(null, []);
|
||||
|
||||
$property = $reflection->getProperty('fix_items');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(null, []);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
// Clean up temp directory
|
||||
if (is_dir($this->tempDir)) {
|
||||
$this->removeDirectory($this->tempDir);
|
||||
}
|
||||
|
||||
// Reset DoctorLoader state
|
||||
$reflection = new \ReflectionClass(DoctorLoader::class);
|
||||
$property = $reflection->getProperty('doctor_items');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(null, []);
|
||||
|
||||
$property = $reflection->getProperty('fix_items');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(null, []);
|
||||
}
|
||||
|
||||
public function testGetDoctorItemsReturnsEmptyArrayInitially(): void
|
||||
{
|
||||
$this->assertEmpty(DoctorLoader::getDoctorItems());
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithCheckItemAttribute(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[CheckItem('test-check', level: 1)]
|
||||
public function testCheck(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
$this->assertCount(1, $items);
|
||||
$this->assertInstanceOf(CheckItem::class, $items[0][0]);
|
||||
$this->assertEquals('test-check', $items[0][0]->item_name);
|
||||
$this->assertEquals(1, $items[0][0]->level);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithMultipleCheckItems(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[CheckItem('check-1', level: 2)]
|
||||
public function check1(): void {}
|
||||
|
||||
#[CheckItem('check-2', level: 1)]
|
||||
public function check2(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
$this->assertCount(2, $items);
|
||||
}
|
||||
|
||||
public function testLoadFromClassSortsByLevelDescending(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[CheckItem('low-priority', level: 1)]
|
||||
public function lowCheck(): void {}
|
||||
|
||||
#[CheckItem('high-priority', level: 5)]
|
||||
public function highCheck(): void {}
|
||||
|
||||
#[CheckItem('medium-priority', level: 3)]
|
||||
public function mediumCheck(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
$this->assertCount(3, $items);
|
||||
// Should be sorted by level descending: 5, 3, 1
|
||||
$this->assertEquals(5, $items[0][0]->level);
|
||||
$this->assertEquals(3, $items[1][0]->level);
|
||||
$this->assertEquals(1, $items[2][0]->level);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithoutSorting(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[CheckItem('check-1', level: 1)]
|
||||
public function check1(): void {}
|
||||
|
||||
#[CheckItem('check-2', level: 5)]
|
||||
public function check2(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class), false);
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
// Without sorting, items should be in order they were added
|
||||
$this->assertCount(2, $items);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithFixItemAttribute(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[FixItem('test-fix')]
|
||||
public function testFix(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$fixItem = DoctorLoader::getFixItem('test-fix');
|
||||
$this->assertNotNull($fixItem);
|
||||
$this->assertTrue(is_callable($fixItem));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithMultipleFixItems(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[FixItem('fix-1')]
|
||||
public function fix1(): void {}
|
||||
|
||||
#[FixItem('fix-2')]
|
||||
public function fix2(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$this->assertNotNull(DoctorLoader::getFixItem('fix-1'));
|
||||
$this->assertNotNull(DoctorLoader::getFixItem('fix-2'));
|
||||
}
|
||||
|
||||
public function testGetFixItemReturnsNullForNonExistent(): void
|
||||
{
|
||||
$this->assertNull(DoctorLoader::getFixItem('non-existent-fix'));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithOptionalCheckOnClass(): void
|
||||
{
|
||||
// Note: OptionalCheck expects an array, not a callable directly
|
||||
// This test verifies the structure even though we can't easily test with anonymous classes
|
||||
$class = new class {
|
||||
#[CheckItem('test-check', level: 1)]
|
||||
public function testCheck(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
$this->assertCount(1, $items);
|
||||
// Second element is the optional check callback (null if not set)
|
||||
$this->assertIsArray($items[0]);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithOptionalCheckOnMethod(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[CheckItem('test-check', level: 1)]
|
||||
public function testCheck(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
$this->assertCount(1, $items);
|
||||
}
|
||||
|
||||
public function testLoadFromClassSetsCallbackCorrectly(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[CheckItem('test-check', level: 1)]
|
||||
public function testCheck(): string
|
||||
{
|
||||
return 'test-result';
|
||||
}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
$this->assertCount(1, $items);
|
||||
|
||||
// Test that the callback is set correctly
|
||||
$callback = $items[0][0]->callback;
|
||||
$this->assertIsCallable($callback);
|
||||
$this->assertEquals('test-result', call_user_func($callback));
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithBothCheckAndFixItems(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[CheckItem('test-check', level: 1)]
|
||||
public function testCheck(): void {}
|
||||
|
||||
#[FixItem('test-fix')]
|
||||
public function testFix(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$checkItems = DoctorLoader::getDoctorItems();
|
||||
$this->assertCount(1, $checkItems);
|
||||
|
||||
$fixItem = DoctorLoader::getFixItem('test-fix');
|
||||
$this->assertNotNull($fixItem);
|
||||
}
|
||||
|
||||
public function testLoadFromClassMultipleTimesAccumulatesItems(): void
|
||||
{
|
||||
$class1 = new class {
|
||||
#[CheckItem('check-1', level: 1)]
|
||||
public function check1(): void {}
|
||||
};
|
||||
|
||||
$class2 = new class {
|
||||
#[CheckItem('check-2', level: 2)]
|
||||
public function check2(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class1));
|
||||
DoctorLoader::loadFromClass(get_class($class2));
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
$this->assertCount(2, $items);
|
||||
}
|
||||
|
||||
public function testLoadFromPsr4DirLoadsAllClasses(): void
|
||||
{
|
||||
// Create a PSR-4 directory structure
|
||||
$psr4Dir = $this->tempDir . '/DoctorClasses';
|
||||
mkdir($psr4Dir, 0755, true);
|
||||
|
||||
// Create test class file 1
|
||||
$classContent1 = '<?php
|
||||
namespace Test\Doctor;
|
||||
|
||||
use StaticPHP\Attribute\Doctor\CheckItem;
|
||||
|
||||
class TestDoctor1 {
|
||||
#[CheckItem("psr4-check-1", level: 1)]
|
||||
public function check1() {}
|
||||
}';
|
||||
file_put_contents($psr4Dir . '/TestDoctor1.php', $classContent1);
|
||||
|
||||
// Create test class file 2
|
||||
$classContent2 = '<?php
|
||||
namespace Test\Doctor;
|
||||
|
||||
use StaticPHP\Attribute\Doctor\CheckItem;
|
||||
|
||||
class TestDoctor2 {
|
||||
#[CheckItem("psr4-check-2", level: 2)]
|
||||
public function check2() {}
|
||||
}';
|
||||
file_put_contents($psr4Dir . '/TestDoctor2.php', $classContent2);
|
||||
|
||||
// Load with auto_require enabled
|
||||
DoctorLoader::loadFromPsr4Dir($psr4Dir, 'Test\Doctor', true);
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
// Should have loaded both classes and sorted by level
|
||||
$this->assertGreaterThanOrEqual(0, count($items));
|
||||
}
|
||||
|
||||
public function testLoadFromPsr4DirSortsItemsByLevel(): void
|
||||
{
|
||||
// Create a PSR-4 directory structure
|
||||
$psr4Dir = $this->tempDir . '/DoctorClasses';
|
||||
mkdir($psr4Dir, 0755, true);
|
||||
|
||||
$classContent = '<?php
|
||||
namespace Test\Doctor;
|
||||
|
||||
use StaticPHP\Attribute\Doctor\CheckItem;
|
||||
|
||||
class MultiLevelDoctor {
|
||||
#[CheckItem("low", level: 1)]
|
||||
public function lowPriority() {}
|
||||
|
||||
#[CheckItem("high", level: 10)]
|
||||
public function highPriority() {}
|
||||
}';
|
||||
file_put_contents($psr4Dir . '/MultiLevelDoctor.php', $classContent);
|
||||
|
||||
DoctorLoader::loadFromPsr4Dir($psr4Dir, 'Test\Doctor', true);
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
// Items should be sorted by level descending
|
||||
if (count($items) >= 2) {
|
||||
$this->assertGreaterThanOrEqual($items[1][0]->level, $items[0][0]->level);
|
||||
}
|
||||
}
|
||||
|
||||
public function testLoadFromClassIgnoresNonPublicMethods(): void
|
||||
{
|
||||
$class = new class {
|
||||
#[CheckItem('public-check', level: 1)]
|
||||
public function publicCheck(): void {}
|
||||
|
||||
#[CheckItem('private-check', level: 1)]
|
||||
private function privateCheck(): void {}
|
||||
|
||||
#[CheckItem('protected-check', level: 1)]
|
||||
protected function protectedCheck(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
// Should only load public methods
|
||||
$this->assertCount(1, $items);
|
||||
$this->assertEquals('public-check', $items[0][0]->item_name);
|
||||
}
|
||||
|
||||
public function testLoadFromClassWithNoAttributes(): void
|
||||
{
|
||||
$class = new class {
|
||||
public function regularMethod(): void {}
|
||||
};
|
||||
|
||||
DoctorLoader::loadFromClass(get_class($class));
|
||||
|
||||
// Should not add any items
|
||||
$items = DoctorLoader::getDoctorItems();
|
||||
$this->assertEmpty($items);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
550
tests/StaticPHP/Registry/PackageLoaderTest.php
Normal file
550
tests/StaticPHP/Registry/PackageLoaderTest.php
Normal file
@@ -0,0 +1,550 @@
|
||||
<?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();
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
}
|
||||
378
tests/StaticPHP/Registry/RegistryTest.php
Normal file
378
tests/StaticPHP/Registry/RegistryTest.php
Normal file
@@ -0,0 +1,378 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\Registry;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StaticPHP\Exception\RegistryException;
|
||||
use StaticPHP\Registry\Registry;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RegistryTest extends TestCase
|
||||
{
|
||||
private string $tempDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->tempDir = sys_get_temp_dir() . '/registry_test_' . uniqid();
|
||||
mkdir($this->tempDir, 0755, true);
|
||||
|
||||
// Reset Registry state
|
||||
Registry::reset();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
// Clean up temp directory
|
||||
if (is_dir($this->tempDir)) {
|
||||
$this->removeDirectory($this->tempDir);
|
||||
}
|
||||
|
||||
// Reset Registry state
|
||||
Registry::reset();
|
||||
}
|
||||
|
||||
public function testLoadRegistryWithValidJsonFile(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 'test-registry',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
|
||||
$this->assertContains('test-registry', Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadRegistryWithValidYamlFile(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.yaml';
|
||||
$registryContent = "name: test-registry-yaml\npackage:\n config: []";
|
||||
file_put_contents($registryFile, $registryContent);
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
|
||||
$this->assertContains('test-registry-yaml', Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadRegistryWithValidYmlFile(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.yml';
|
||||
$registryContent = "name: test-registry-yml\npackage:\n config: []";
|
||||
file_put_contents($registryFile, $registryContent);
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
|
||||
$this->assertContains('test-registry-yml', Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadRegistryThrowsExceptionForNonExistentFile(): void
|
||||
{
|
||||
$this->expectException(RegistryException::class);
|
||||
$this->expectExceptionMessage('Failed to read registry file');
|
||||
|
||||
Registry::loadRegistry($this->tempDir . '/non-existent.json');
|
||||
}
|
||||
|
||||
public function testLoadRegistryThrowsExceptionForUnsupportedFormat(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.txt';
|
||||
file_put_contents($registryFile, 'invalid content');
|
||||
|
||||
$this->expectException(RegistryException::class);
|
||||
$this->expectExceptionMessage('Unsupported registry file format');
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
}
|
||||
|
||||
public function testLoadRegistryThrowsExceptionForInvalidJson(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
file_put_contents($registryFile, 'invalid json content');
|
||||
|
||||
$this->expectException(RegistryException::class);
|
||||
$this->expectExceptionMessage('Invalid registry format');
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
}
|
||||
|
||||
public function testLoadRegistryThrowsExceptionForMissingName(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'package' => [],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
$this->expectException(RegistryException::class);
|
||||
$this->expectExceptionMessage("Registry 'name' is missing or invalid");
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
}
|
||||
|
||||
public function testLoadRegistryThrowsExceptionForEmptyName(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => '',
|
||||
'package' => [],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
$this->expectException(RegistryException::class);
|
||||
$this->expectExceptionMessage("Registry 'name' is missing or invalid");
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
}
|
||||
|
||||
public function testLoadRegistryThrowsExceptionForNonStringName(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 123,
|
||||
'package' => [],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
$this->expectException(RegistryException::class);
|
||||
$this->expectExceptionMessage("Registry 'name' is missing or invalid");
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
}
|
||||
|
||||
public function testLoadRegistrySkipsDuplicateRegistry(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 'duplicate-registry',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
// Load first time
|
||||
Registry::loadRegistry($registryFile);
|
||||
$this->assertCount(1, Registry::getLoadedRegistries());
|
||||
|
||||
// Load second time - should skip
|
||||
Registry::loadRegistry($registryFile);
|
||||
$this->assertCount(1, Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadFromEnvOrOptionWithNullRegistries(): void
|
||||
{
|
||||
// Should not throw exception when null is passed and env is not set
|
||||
Registry::loadFromEnvOrOption(null);
|
||||
$this->assertEmpty(Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadFromEnvOrOptionWithEmptyString(): void
|
||||
{
|
||||
Registry::loadFromEnvOrOption('');
|
||||
$this->assertEmpty(Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadFromEnvOrOptionWithSingleRegistry(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 'env-test-registry',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
Registry::loadFromEnvOrOption($registryFile);
|
||||
|
||||
$this->assertContains('env-test-registry', Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadFromEnvOrOptionWithMultipleRegistries(): void
|
||||
{
|
||||
$registryFile1 = $this->tempDir . '/test-registry-1.json';
|
||||
$registryData1 = [
|
||||
'name' => 'env-test-registry-1',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile1, json_encode($registryData1));
|
||||
|
||||
$registryFile2 = $this->tempDir . '/test-registry-2.json';
|
||||
$registryData2 = [
|
||||
'name' => 'env-test-registry-2',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile2, json_encode($registryData2));
|
||||
|
||||
Registry::loadFromEnvOrOption($registryFile1 . ':' . $registryFile2);
|
||||
|
||||
$this->assertContains('env-test-registry-1', Registry::getLoadedRegistries());
|
||||
$this->assertContains('env-test-registry-2', Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadFromEnvOrOptionIgnoresNonExistentFiles(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 'env-test-registry',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
// Mix existing and non-existing files
|
||||
Registry::loadFromEnvOrOption($registryFile . ':' . $this->tempDir . '/non-existent.json');
|
||||
|
||||
// Should only load the existing one
|
||||
$this->assertCount(1, Registry::getLoadedRegistries());
|
||||
$this->assertContains('env-test-registry', Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testGetLoadedRegistriesReturnsEmptyArrayInitially(): void
|
||||
{
|
||||
$this->assertEmpty(Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testGetLoadedRegistriesReturnsCorrectList(): void
|
||||
{
|
||||
$registryFile1 = $this->tempDir . '/test-registry-1.json';
|
||||
$registryData1 = [
|
||||
'name' => 'registry-1',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile1, json_encode($registryData1));
|
||||
|
||||
$registryFile2 = $this->tempDir . '/test-registry-2.json';
|
||||
$registryData2 = [
|
||||
'name' => 'registry-2',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile2, json_encode($registryData2));
|
||||
|
||||
Registry::loadRegistry($registryFile1);
|
||||
Registry::loadRegistry($registryFile2);
|
||||
|
||||
$loaded = Registry::getLoadedRegistries();
|
||||
$this->assertCount(2, $loaded);
|
||||
$this->assertContains('registry-1', $loaded);
|
||||
$this->assertContains('registry-2', $loaded);
|
||||
}
|
||||
|
||||
public function testResetClearsLoadedRegistries(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 'test-registry',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
$this->assertNotEmpty(Registry::getLoadedRegistries());
|
||||
|
||||
Registry::reset();
|
||||
$this->assertEmpty(Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadRegistryWithAutoloadPath(): void
|
||||
{
|
||||
// Create a test autoload file
|
||||
$autoloadFile = $this->tempDir . '/vendor/autoload.php';
|
||||
mkdir(dirname($autoloadFile), 0755, true);
|
||||
file_put_contents($autoloadFile, '<?php // Test autoload');
|
||||
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 'autoload-test-registry',
|
||||
'autoload' => 'vendor/autoload.php',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
// Should not throw exception
|
||||
Registry::loadRegistry($registryFile);
|
||||
|
||||
$this->assertContains('autoload-test-registry', Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
public function testLoadRegistryWithNonExistentAutoloadPath(): void
|
||||
{
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 'autoload-missing-test-registry',
|
||||
'autoload' => 'vendor/non-existent-autoload.php',
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
// Should throw exception when path doesn't exist
|
||||
$this->expectException(RegistryException::class);
|
||||
$this->expectExceptionMessage('Path does not exist');
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
}
|
||||
|
||||
public function testLoadRegistryWithAbsoluteAutoloadPath(): void
|
||||
{
|
||||
// Create a test autoload file with absolute path
|
||||
$autoloadFile = $this->tempDir . '/vendor/autoload.php';
|
||||
mkdir(dirname($autoloadFile), 0755, true);
|
||||
file_put_contents($autoloadFile, '<?php // Test autoload');
|
||||
|
||||
$registryFile = $this->tempDir . '/test-registry.json';
|
||||
$registryData = [
|
||||
'name' => 'absolute-autoload-test-registry',
|
||||
'autoload' => $autoloadFile,
|
||||
'package' => [
|
||||
'config' => [],
|
||||
],
|
||||
];
|
||||
file_put_contents($registryFile, json_encode($registryData));
|
||||
|
||||
Registry::loadRegistry($registryFile);
|
||||
|
||||
$this->assertContains('absolute-autoload-test-registry', Registry::getLoadedRegistries());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user