mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-19 21:34:53 +08:00
Refactor test structure and update paths for improved organization
This commit is contained in:
parent
78375632b4
commit
bde1440617
@ -69,6 +69,6 @@ return (new PhpCsFixer\Config())
|
||||
'php_unit_data_provider_method_order' => false,
|
||||
])
|
||||
->setFinder(
|
||||
PhpCsFixer\Finder::create()->in([__DIR__ . '/src', __DIR__ . '/tests/SPC'])
|
||||
PhpCsFixer\Finder::create()->in([__DIR__ . '/src', __DIR__ . '/tests/StaticPHP'])
|
||||
)
|
||||
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect());
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"SPC\\Tests\\": "tests/SPC"
|
||||
"Tests\\StaticPHP\\": "tests/StaticPHP"
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
|
||||
@ -352,8 +352,6 @@ class ArtifactExtractor
|
||||
* @param string $name Artifact name (for error messages)
|
||||
* @param string $source_file Path to the source file or directory
|
||||
* @param string $cache_type Cache type: archive, git, local
|
||||
*
|
||||
* @throws WrongUsageException if source file does not exist
|
||||
*/
|
||||
protected function validateSourceFile(string $name, string $source_file, string $cache_type): void
|
||||
{
|
||||
|
||||
@ -35,8 +35,6 @@ class ApplicationContext
|
||||
* @param array $options Initialization options
|
||||
* - 'debug': Enable debug mode (disables compilation)
|
||||
* - 'definitions': Additional container definitions
|
||||
*
|
||||
* @throws \RuntimeException If already initialized
|
||||
*/
|
||||
public static function initialize(array $options = []): Container
|
||||
{
|
||||
@ -60,7 +58,8 @@ class ApplicationContext
|
||||
self::$debug = $options['debug'] ?? false;
|
||||
|
||||
self::$container = $builder->build();
|
||||
self::$invoker = new CallbackInvoker(self::$container);
|
||||
// Get invoker from container to ensure singleton consistency
|
||||
self::$invoker = self::$container->get(CallbackInvoker::class);
|
||||
|
||||
return self::$container;
|
||||
}
|
||||
@ -126,7 +125,8 @@ class ApplicationContext
|
||||
public static function getInvoker(): CallbackInvoker
|
||||
{
|
||||
if (self::$invoker === null) {
|
||||
self::$invoker = new CallbackInvoker(self::getContainer());
|
||||
// Get from container to ensure singleton consistency
|
||||
self::$invoker = self::getContainer()->get(CallbackInvoker::class);
|
||||
}
|
||||
return self::$invoker;
|
||||
}
|
||||
@ -139,14 +139,18 @@ class ApplicationContext
|
||||
*/
|
||||
public static function invoke(callable $callback, array $context = []): mixed
|
||||
{
|
||||
logger()->debug('[INVOKE] ' . (is_array($callback) ? (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]) . '::' . $callback[1] : (is_string($callback) ? $callback : 'Closure')));
|
||||
if (function_exists('logger')) {
|
||||
logger()->debug('[INVOKE] ' . (is_array($callback) ? (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]) . '::' . $callback[1] : (is_string($callback) ? $callback : 'Closure')));
|
||||
}
|
||||
|
||||
// get if callback has attribute PatchDescription
|
||||
$ref = new \ReflectionFunction(\Closure::fromCallable($callback));
|
||||
$attributes = $ref->getAttributes(PatchDescription::class);
|
||||
foreach ($attributes as $attribute) {
|
||||
$attrInstance = $attribute->newInstance();
|
||||
logger()->info(ConsoleColor::magenta('[PATCH]') . ConsoleColor::green(" {$attrInstance->description}"));
|
||||
if (function_exists('logger')) {
|
||||
logger()->info(ConsoleColor::magenta('[PATCH]') . ConsoleColor::green(" {$attrInstance->description}"));
|
||||
}
|
||||
}
|
||||
return self::getInvoker()->invoke($callback, $context);
|
||||
}
|
||||
|
||||
@ -5,12 +5,13 @@ declare(strict_types=1);
|
||||
namespace StaticPHP\DI;
|
||||
|
||||
use DI\Container;
|
||||
use StaticPHP\Exception\SPCInternalException;
|
||||
|
||||
/**
|
||||
* CallbackInvoker is responsible for invoking callbacks with automatic dependency injection.
|
||||
* It supports context-based parameter resolution, allowing temporary bindings without polluting the container.
|
||||
*/
|
||||
class CallbackInvoker
|
||||
readonly class CallbackInvoker
|
||||
{
|
||||
public function __construct(
|
||||
private Container $container
|
||||
@ -34,8 +35,6 @@ class CallbackInvoker
|
||||
* @param array $context Context parameters (type => value or name => value)
|
||||
*
|
||||
* @return mixed The return value of the callback
|
||||
*
|
||||
* @throws \RuntimeException If a required parameter cannot be resolved
|
||||
*/
|
||||
public function invoke(callable $callback, array $context = []): mixed
|
||||
{
|
||||
@ -64,8 +63,13 @@ class CallbackInvoker
|
||||
|
||||
// 3. Look up in container by type
|
||||
if ($typeName !== null && !$this->isBuiltinType($typeName) && $this->container->has($typeName)) {
|
||||
$args[] = $this->container->get($typeName);
|
||||
continue;
|
||||
try {
|
||||
$args[] = $this->container->get($typeName);
|
||||
continue;
|
||||
} catch (\Throwable $e) {
|
||||
// Container failed to resolve (e.g., missing constructor params)
|
||||
// Fall through to try default value or nullable
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Use default value if available
|
||||
@ -81,7 +85,7 @@ class CallbackInvoker
|
||||
}
|
||||
|
||||
// Cannot resolve parameter
|
||||
throw new \RuntimeException(
|
||||
throw new SPCInternalException(
|
||||
"Cannot resolve parameter '{$paramName}'" .
|
||||
($typeName ? " of type '{$typeName}'" : '') .
|
||||
' for callback invocation'
|
||||
@ -120,19 +124,20 @@ class CallbackInvoker
|
||||
|
||||
// If value is an object, add mappings for all parent classes and interfaces
|
||||
if (is_object($value)) {
|
||||
$reflection = new \ReflectionClass($value);
|
||||
$originalReflection = new \ReflectionClass($value);
|
||||
|
||||
// Add concrete class
|
||||
$expanded[$reflection->getName()] = $value;
|
||||
$expanded[$originalReflection->getName()] = $value;
|
||||
|
||||
// Add all parent classes
|
||||
$reflection = $originalReflection;
|
||||
while ($parent = $reflection->getParentClass()) {
|
||||
$expanded[$parent->getName()] = $value;
|
||||
$reflection = $parent;
|
||||
}
|
||||
|
||||
// Add all interfaces
|
||||
$interfaces = (new \ReflectionClass($value))->getInterfaceNames();
|
||||
// Add all interfaces - reuse original reflection
|
||||
$interfaces = $originalReflection->getInterfaceNames();
|
||||
foreach ($interfaces as $interface) {
|
||||
$expanded[$interface] = $value;
|
||||
}
|
||||
|
||||
@ -404,8 +404,6 @@ class PackageInstaller
|
||||
|
||||
/**
|
||||
* Validate that a package has required artifacts.
|
||||
*
|
||||
* @throws WrongUsageException if target/library package has no source or platform binary
|
||||
*/
|
||||
private function validatePackageArtifact(Package $package): void
|
||||
{
|
||||
|
||||
@ -25,7 +25,7 @@ class Registry
|
||||
*/
|
||||
public static function loadRegistry(string $registry_file, bool $auto_require = true): void
|
||||
{
|
||||
$yaml = file_get_contents($registry_file);
|
||||
$yaml = @file_get_contents($registry_file);
|
||||
if ($yaml === false) {
|
||||
throw new RegistryException("Failed to read registry file: {$registry_file}");
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class GlobalDefinesTest extends TestCase
|
||||
{
|
||||
public function testGlobalDefines(): void
|
||||
{
|
||||
require __DIR__ . '/../../src/globals/defines.php';
|
||||
$this->assertTrue(defined('WORKING_DIR'));
|
||||
}
|
||||
|
||||
public function testInternalEnv(): void
|
||||
{
|
||||
require __DIR__ . '/../../src/globals/internal-env.php';
|
||||
$this->assertTrue(defined('GNU_ARCH'));
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\InterruptException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class GlobalFunctionsTest extends TestCase
|
||||
{
|
||||
public function testMatchPattern(): void
|
||||
{
|
||||
$this->assertEquals('abc', match_pattern('a*c', 'abc'));
|
||||
$this->assertFalse(match_pattern('a*c', 'abcd'));
|
||||
}
|
||||
|
||||
public function testFExec(): void
|
||||
{
|
||||
$this->assertEquals('abc', f_exec('echo abc', $out, $ret));
|
||||
$this->assertEquals(0, $ret);
|
||||
$this->assertEquals(['abc'], $out);
|
||||
}
|
||||
|
||||
public function testPatchPointInterrupt(): void
|
||||
{
|
||||
$except = patch_point_interrupt(0);
|
||||
$this->assertInstanceOf(InterruptException::class, $except);
|
||||
}
|
||||
}
|
||||
@ -1,246 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\builder;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\builder\BuilderBase;
|
||||
use SPC\builder\BuilderProvider;
|
||||
use SPC\builder\Extension;
|
||||
use SPC\builder\LibraryBase;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\LockFile;
|
||||
use SPC\util\AttributeMapper;
|
||||
use SPC\util\DependencyUtil;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class BuilderTest extends TestCase
|
||||
{
|
||||
private BuilderBase $builder;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
BuilderProvider::makeBuilderByInput(new ArgvInput());
|
||||
BuilderProvider::getBuilder();
|
||||
}
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->builder = BuilderProvider::makeBuilderByInput(new ArgvInput());
|
||||
[$extensions, $libs] = DependencyUtil::getExtsAndLibs(['mbregex']);
|
||||
$this->builder->proveLibs($libs);
|
||||
foreach ($extensions as $extension) {
|
||||
$class = AttributeMapper::getExtensionClassByName($extension) ?? Extension::class;
|
||||
$ext = new $class($extension, $this->builder);
|
||||
$this->builder->addExt($ext);
|
||||
}
|
||||
foreach ($this->builder->getExts() as $ext) {
|
||||
$ext->checkDependency();
|
||||
}
|
||||
}
|
||||
|
||||
public function testMakeBuilderByInput(): void
|
||||
{
|
||||
$this->assertInstanceOf(BuilderBase::class, BuilderProvider::makeBuilderByInput(new ArgvInput()));
|
||||
$this->assertInstanceOf(BuilderBase::class, BuilderProvider::getBuilder());
|
||||
}
|
||||
|
||||
public function testGetLibAndGetLibs()
|
||||
{
|
||||
$this->assertIsArray($this->builder->getLibs());
|
||||
$this->assertInstanceOf(LibraryBase::class, $this->builder->getLib('onig'));
|
||||
}
|
||||
|
||||
public function testGetExtAndGetExts()
|
||||
{
|
||||
$this->assertIsArray($this->builder->getExts());
|
||||
$this->assertInstanceOf(Extension::class, $this->builder->getExt('mbregex'));
|
||||
}
|
||||
|
||||
public function testMakeExtensionArgs()
|
||||
{
|
||||
$this->assertStringContainsString('--enable-mbstring', $this->builder->makeStaticExtensionArgs());
|
||||
}
|
||||
|
||||
public function testIsLibsOnly()
|
||||
{
|
||||
// mbregex is not libs only
|
||||
$this->assertFalse($this->builder->isLibsOnly());
|
||||
}
|
||||
|
||||
public function testGetPHPVersionID()
|
||||
{
|
||||
if (file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) {
|
||||
$file = SOURCE_PATH . '/php-src/main/php_version.h';
|
||||
$cnt = preg_match('/PHP_VERSION_ID (\d+)/m', file_get_contents($file), $match);
|
||||
if ($cnt !== 0) {
|
||||
$this->assertEquals(intval($match[1]), $this->builder->getPHPVersionID());
|
||||
} else {
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->builder->getPHPVersionID();
|
||||
}
|
||||
} else {
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->builder->getPHPVersionID();
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetPHPVersion()
|
||||
{
|
||||
if (file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) {
|
||||
$file = SOURCE_PATH . '/php-src/main/php_version.h';
|
||||
$cnt = preg_match('/PHP_VERSION "(\d+\.\d+\.\d+(?:-[^"]+)?)/', file_get_contents($file), $match);
|
||||
if ($cnt !== 0) {
|
||||
$this->assertEquals($match[1], $this->builder->getPHPVersion());
|
||||
} else {
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->builder->getPHPVersion();
|
||||
}
|
||||
} else {
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->builder->getPHPVersion();
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetPHPVersionFromArchive()
|
||||
{
|
||||
$lock = file_exists(LockFile::LOCK_FILE) ? file_get_contents(LockFile::LOCK_FILE) : false;
|
||||
if ($lock === false) {
|
||||
$this->assertFalse($this->builder->getPHPVersionFromArchive());
|
||||
} else {
|
||||
$lock = json_decode($lock, true);
|
||||
$file = $lock['php-src']['filename'] ?? null;
|
||||
if ($file === null) {
|
||||
$this->assertFalse($this->builder->getPHPVersionFromArchive());
|
||||
} else {
|
||||
$cnt = preg_match('/php-(\d+\.\d+\.\d+)/', $file, $match);
|
||||
if ($cnt !== 0) {
|
||||
$this->assertEquals($match[1], $this->builder->getPHPVersionFromArchive());
|
||||
} else {
|
||||
$this->assertFalse($this->builder->getPHPVersionFromArchive());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetMicroVersion()
|
||||
{
|
||||
$file = FileSystem::convertPath(SOURCE_PATH . '/php-src/sapi/micro/php_micro.h');
|
||||
if (!file_exists($file)) {
|
||||
$this->assertFalse($this->builder->getMicroVersion());
|
||||
} else {
|
||||
$content = file_get_contents($file);
|
||||
$ver = '';
|
||||
preg_match('/#define PHP_MICRO_VER_MAJ (\d)/m', $content, $match);
|
||||
$ver .= $match[1] . '.';
|
||||
preg_match('/#define PHP_MICRO_VER_MIN (\d)/m', $content, $match);
|
||||
$ver .= $match[1] . '.';
|
||||
preg_match('/#define PHP_MICRO_VER_PAT (\d)/m', $content, $match);
|
||||
$ver .= $match[1];
|
||||
$this->assertEquals($ver, $this->builder->getMicroVersion());
|
||||
}
|
||||
}
|
||||
|
||||
public static function providerGetBuildTypeName(): array
|
||||
{
|
||||
return [
|
||||
[BUILD_TARGET_CLI, 'cli'],
|
||||
[BUILD_TARGET_FPM, 'fpm'],
|
||||
[BUILD_TARGET_MICRO, 'micro'],
|
||||
[BUILD_TARGET_EMBED, 'embed'],
|
||||
[BUILD_TARGET_FRANKENPHP, 'frankenphp'],
|
||||
[BUILD_TARGET_ALL, 'cli, micro, fpm, embed, frankenphp, cgi'],
|
||||
[BUILD_TARGET_CLI | BUILD_TARGET_EMBED, 'cli, embed'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerGetBuildTypeName
|
||||
*/
|
||||
public function testGetBuildTypeName(int $target, string $name): void
|
||||
{
|
||||
$this->assertEquals($name, $this->builder->getBuildTypeName($target));
|
||||
}
|
||||
|
||||
public function testGetOption()
|
||||
{
|
||||
// we cannot assure the option exists, so just tests default value
|
||||
$this->assertEquals('foo', $this->builder->getOption('bar', 'foo'));
|
||||
}
|
||||
|
||||
public function testGetOptions()
|
||||
{
|
||||
$this->assertIsArray($this->builder->getOptions());
|
||||
}
|
||||
|
||||
public function testSetOptionIfNotExist()
|
||||
{
|
||||
$this->assertEquals(null, $this->builder->getOption('bar'));
|
||||
$this->builder->setOptionIfNotExist('bar', 'foo');
|
||||
$this->assertEquals('foo', $this->builder->getOption('bar'));
|
||||
}
|
||||
|
||||
public function testSetOption()
|
||||
{
|
||||
$this->assertEquals(null, $this->builder->getOption('bar'));
|
||||
$this->builder->setOption('bar', 'foo');
|
||||
$this->assertEquals('foo', $this->builder->getOption('bar'));
|
||||
}
|
||||
|
||||
public function testGetEnvString()
|
||||
{
|
||||
$this->assertIsString($this->builder->getEnvString());
|
||||
putenv('TEST_SPC_BUILDER=foo');
|
||||
$this->assertStringContainsString('TEST_SPC_BUILDER=foo', $this->builder->getEnvString(['TEST_SPC_BUILDER']));
|
||||
}
|
||||
|
||||
public function testValidateLibsAndExts()
|
||||
{
|
||||
$this->builder->validateLibsAndExts();
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public static function providerEmitPatchPoint(): array
|
||||
{
|
||||
return [
|
||||
['before-libs-extract'],
|
||||
['after-libs-extract'],
|
||||
['before-php-extract'],
|
||||
['after-php-extract'],
|
||||
['before-micro-extract'],
|
||||
['after-micro-extract'],
|
||||
['before-exts-extract'],
|
||||
['after-exts-extract'],
|
||||
['before-php-buildconf'],
|
||||
['before-php-configure'],
|
||||
['before-php-make'],
|
||||
['before-sanity-check'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerEmitPatchPoint
|
||||
*/
|
||||
public function testEmitPatchPoint(string $point)
|
||||
{
|
||||
$code = '<?php if (patch_point() === "' . $point . '") echo "GOOD:' . $point . '";';
|
||||
// emulate patch point
|
||||
$this->builder->setOption('with-added-patch', ['/tmp/patch-point.' . $point . '.php']);
|
||||
FileSystem::writeFile('/tmp/patch-point.' . $point . '.php', $code);
|
||||
$this->expectOutputString('GOOD:' . $point);
|
||||
$this->builder->emitPatchPoint($point);
|
||||
}
|
||||
|
||||
public function testEmitPatchPointNotExists()
|
||||
{
|
||||
$this->expectOutputRegex('/failed to run/');
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->builder->setOption('with-added-patch', ['/tmp/patch-point.not_exsssists.php']);
|
||||
$this->builder->emitPatchPoint('not-exists');
|
||||
}
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\builder;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\builder\BuilderProvider;
|
||||
use SPC\builder\Extension;
|
||||
use SPC\util\AttributeMapper;
|
||||
use SPC\util\DependencyUtil;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ExtensionTest extends TestCase
|
||||
{
|
||||
private Extension $extension;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$builder = BuilderProvider::makeBuilderByInput(new ArgvInput());
|
||||
[$extensions, $libs] = DependencyUtil::getExtsAndLibs(['mbregex']);
|
||||
$builder->proveLibs($libs);
|
||||
foreach ($extensions as $extension) {
|
||||
$class = AttributeMapper::getExtensionClassByName($extension) ?? Extension::class;
|
||||
$ext = new $class($extension, $builder);
|
||||
$builder->addExt($ext);
|
||||
}
|
||||
foreach ($builder->getExts() as $ext) {
|
||||
$ext->checkDependency();
|
||||
}
|
||||
$this->extension = $builder->getExt('mbregex');
|
||||
}
|
||||
|
||||
public function testPatches()
|
||||
{
|
||||
$this->assertFalse($this->extension->patchBeforeBuildconf());
|
||||
$this->assertFalse($this->extension->patchBeforeConfigure());
|
||||
$this->assertFalse($this->extension->patchBeforeMake());
|
||||
}
|
||||
|
||||
public function testGetExtensionDependency()
|
||||
{
|
||||
$this->assertEquals('mbstring', current($this->extension->getExtensionDependency())->getName());
|
||||
}
|
||||
|
||||
public function testGetWindowsConfigureArg()
|
||||
{
|
||||
$this->assertEquals('', $this->extension->getWindowsConfigureArg());
|
||||
}
|
||||
|
||||
public function testGetConfigureArg()
|
||||
{
|
||||
$this->assertEquals('', $this->extension->getUnixConfigureArg());
|
||||
}
|
||||
|
||||
public function testGetExtVersion()
|
||||
{
|
||||
// only swoole has version, we cannot test it
|
||||
$this->assertEquals(null, $this->extension->getExtVersion());
|
||||
}
|
||||
|
||||
public function testGetDistName()
|
||||
{
|
||||
$this->assertEquals('mbregex', $this->extension->getName());
|
||||
}
|
||||
|
||||
public function testRunCliCheckWindows()
|
||||
{
|
||||
if (is_unix()) {
|
||||
$this->markTestSkipped('This test is for Windows only');
|
||||
} else {
|
||||
$this->extension->runCliCheckWindows();
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetName()
|
||||
{
|
||||
$this->assertEquals('mbregex', $this->extension->getName());
|
||||
}
|
||||
|
||||
public function testGetUnixConfigureArg()
|
||||
{
|
||||
$this->assertEquals('', $this->extension->getUnixConfigureArg());
|
||||
}
|
||||
|
||||
public function testGetEnableArg()
|
||||
{
|
||||
$this->assertEquals('', $this->extension->getEnableArg());
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\builder\linux;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\builder\linux\SystemUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SystemUtilTest extends TestCase
|
||||
{
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Linux') {
|
||||
self::markTestSkipped('This test is only for Linux');
|
||||
}
|
||||
}
|
||||
|
||||
public function testIsMuslDistAndGetOSRelease()
|
||||
{
|
||||
$release = SystemUtil::getOSRelease();
|
||||
// we cannot ensure what is the current distro, just test the key exists
|
||||
$this->assertArrayHasKey('dist', $release);
|
||||
$this->assertArrayHasKey('ver', $release);
|
||||
$this->assertTrue($release['dist'] === 'alpine' && SystemUtil::isMuslDist() || $release['dist'] !== 'alpine' && !SystemUtil::isMuslDist());
|
||||
}
|
||||
|
||||
public function testFindStaticLib()
|
||||
{
|
||||
$this->assertIsArray(SystemUtil::findStaticLib('ld-linux-x86-64.so.2'));
|
||||
}
|
||||
|
||||
public function testGetCpuCount()
|
||||
{
|
||||
$this->assertIsInt(SystemUtil::getCpuCount());
|
||||
}
|
||||
|
||||
public function testFindHeader()
|
||||
{
|
||||
$this->assertIsArray(SystemUtil::findHeader('elf.h'));
|
||||
}
|
||||
|
||||
public function testGetSupportedDistros()
|
||||
{
|
||||
$this->assertIsArray(SystemUtil::getSupportedDistros());
|
||||
}
|
||||
|
||||
public function testFindHeaders()
|
||||
{
|
||||
$this->assertIsArray(SystemUtil::findHeaders(['elf.h']));
|
||||
}
|
||||
|
||||
public function testFindStaticLibs()
|
||||
{
|
||||
$this->assertIsArray(SystemUtil::findStaticLibs(['ld-linux-x86-64.so.2']));
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\builder\macos;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\builder\macos\SystemUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SystemUtilTest extends TestCase
|
||||
{
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Darwin') {
|
||||
self::markTestSkipped('This test is only for macOS');
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetCpuCount()
|
||||
{
|
||||
$this->assertIsInt(SystemUtil::getCpuCount());
|
||||
}
|
||||
|
||||
public function testGetArchCFlags()
|
||||
{
|
||||
$this->assertEquals('--target=x86_64-apple-darwin', SystemUtil::getArchCFlags('x86_64'));
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\builder\unix;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\builder\freebsd\SystemUtil as FreebsdSystemUtil;
|
||||
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
|
||||
use SPC\builder\macos\SystemUtil as MacosSystemUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class UnixSystemUtilTest extends TestCase
|
||||
{
|
||||
private FreebsdSystemUtil|LinuxSystemUtil|MacosSystemUtil $util;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$util_class = match (PHP_OS_FAMILY) {
|
||||
'Linux' => 'SPC\builder\linux\SystemUtil',
|
||||
'Darwin' => 'SPC\builder\macos\SystemUtil',
|
||||
'FreeBSD' => 'SPC\builder\freebsd\SystemUtil',
|
||||
default => null,
|
||||
};
|
||||
if ($util_class === null) {
|
||||
self::markTestSkipped('This test is only for Unix');
|
||||
}
|
||||
$this->util = new $util_class();
|
||||
}
|
||||
|
||||
public function testFindCommand()
|
||||
{
|
||||
$this->assertIsString($this->util->findCommand('bash'));
|
||||
}
|
||||
|
||||
public function testMakeEnvVarString()
|
||||
{
|
||||
$this->assertEquals("PATH='/usr/bin' PKG_CONFIG='/usr/bin/pkg-config'", $this->util->makeEnvVarString(['PATH' => '/usr/bin', 'PKG_CONFIG' => '/usr/bin/pkg-config']));
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\doctor;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\doctor\DoctorHandler;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CheckListHandlerTest extends TestCase
|
||||
{
|
||||
public function testRunChecksReturnsListOfCheck(): void
|
||||
{
|
||||
$list = new DoctorHandler();
|
||||
|
||||
$id = $list->getValidCheckList();
|
||||
foreach ($id as $item) {
|
||||
$this->assertInstanceOf('SPC\doctor\AsCheckItem', $item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\globals;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LogLevel;
|
||||
use SPC\exception\ExecutionException;
|
||||
use ZM\Logger\ConsoleLogger;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class GlobalFunctionsTest extends TestCase
|
||||
{
|
||||
private static $logger_cache;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
global $ob_logger;
|
||||
self::$logger_cache = $ob_logger;
|
||||
$ob_logger = new ConsoleLogger(LogLevel::ALERT);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
global $ob_logger;
|
||||
$ob_logger = self::$logger_cache;
|
||||
}
|
||||
|
||||
public function testIsAssocArray(): void
|
||||
{
|
||||
$this->assertTrue(is_assoc_array(['a' => 1, 'b' => 2]));
|
||||
$this->assertFalse(is_assoc_array([1, 2, 3]));
|
||||
}
|
||||
|
||||
public function testLogger(): void
|
||||
{
|
||||
$this->assertInstanceOf('Psr\Log\LoggerInterface', logger());
|
||||
}
|
||||
|
||||
public function testArch2Gnu(): void
|
||||
{
|
||||
$this->assertEquals('x86_64', arch2gnu('x86_64'));
|
||||
$this->assertEquals('x86_64', arch2gnu('x64'));
|
||||
$this->assertEquals('x86_64', arch2gnu('amd64'));
|
||||
$this->assertEquals('aarch64', arch2gnu('arm64'));
|
||||
$this->assertEquals('aarch64', arch2gnu('aarch64'));
|
||||
$this->expectException('SPC\exception\WrongUsageException');
|
||||
arch2gnu('armv7');
|
||||
}
|
||||
|
||||
public function testQuote(): void
|
||||
{
|
||||
$this->assertEquals('"hello"', quote('hello'));
|
||||
$this->assertEquals("'hello'", quote('hello', "'"));
|
||||
}
|
||||
|
||||
public function testFPassthru(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('Windows not support f_passthru');
|
||||
}
|
||||
$this->assertEquals(null, f_passthru('echo ""'));
|
||||
$this->expectException(ExecutionException::class);
|
||||
f_passthru('false');
|
||||
}
|
||||
|
||||
public function testFPutenv(): void
|
||||
{
|
||||
$this->assertTrue(f_putenv('SPC_TEST_ENV=1'));
|
||||
$this->assertEquals('1', getenv('SPC_TEST_ENV'));
|
||||
}
|
||||
|
||||
public function testShell(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('Windows not support shell');
|
||||
}
|
||||
$shell = shell();
|
||||
$this->assertInstanceOf('SPC\util\shell\UnixShell', $shell);
|
||||
$this->assertInstanceOf('SPC\util\shell\UnixShell', $shell->cd('/'));
|
||||
$this->assertInstanceOf('SPC\util\shell\UnixShell', $shell->exec('echo ""'));
|
||||
$this->assertInstanceOf('SPC\util\shell\UnixShell', $shell->setEnv(['SPC_TEST_ENV' => '1']));
|
||||
|
||||
[$code, $out] = $shell->execWithResult('echo "_"');
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertEquals('_', implode('', $out));
|
||||
|
||||
$this->expectException('SPC\exception\ExecutionException');
|
||||
$shell->exec('false');
|
||||
}
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\store;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\FileSystem;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ConfigTest extends TestCase
|
||||
{
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
$testdir = WORKING_DIR . '/.configtest';
|
||||
FileSystem::createDir($testdir);
|
||||
FileSystem::writeFile($testdir . '/lib.json', file_get_contents(ROOT_DIR . '/config/lib.json'));
|
||||
FileSystem::writeFile($testdir . '/ext.json', file_get_contents(ROOT_DIR . '/config/ext.json'));
|
||||
FileSystem::writeFile($testdir . '/source.json', file_get_contents(ROOT_DIR . '/config/source.json'));
|
||||
FileSystem::loadConfigArray('lib', $testdir);
|
||||
FileSystem::loadConfigArray('ext', $testdir);
|
||||
FileSystem::loadConfigArray('source', $testdir);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
FileSystem::removeDir(WORKING_DIR . '/.configtest');
|
||||
}
|
||||
|
||||
public function testGetExts()
|
||||
{
|
||||
$this->assertTrue(is_assoc_array(Config::getExts()));
|
||||
}
|
||||
|
||||
public function testGetLib()
|
||||
{
|
||||
$this->assertIsArray(Config::getLib('zlib'));
|
||||
match (PHP_OS_FAMILY) {
|
||||
'FreeBSD', 'Darwin', 'Linux' => $this->assertStringEndsWith('.a', Config::getLib('zlib', 'static-libs', [])[0]),
|
||||
'Windows' => $this->assertStringEndsWith('.lib', Config::getLib('zlib', 'static-libs', [])[0]),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
public function testGetExt()
|
||||
{
|
||||
$this->assertIsArray(Config::getExt('bcmath'));
|
||||
$this->assertEquals('builtin', Config::getExt('bcmath', 'type'));
|
||||
}
|
||||
|
||||
public function testGetSources()
|
||||
{
|
||||
$this->assertTrue(is_assoc_array(Config::getSources()));
|
||||
}
|
||||
|
||||
public function testGetSource()
|
||||
{
|
||||
$this->assertIsArray(Config::getSource('php-src'));
|
||||
}
|
||||
|
||||
public function testGetLibs()
|
||||
{
|
||||
$this->assertTrue(is_assoc_array(Config::getLibs()));
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\store;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\store\CurlHook;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CurlHookTest extends TestCase
|
||||
{
|
||||
public function testSetupGithubToken()
|
||||
{
|
||||
$header = [];
|
||||
CurlHook::setupGithubToken('GET', 'https://example.com', $header);
|
||||
if (getenv('GITHUB_TOKEN') === false) {
|
||||
$this->assertEmpty($header);
|
||||
} else {
|
||||
$this->assertEquals(['Authorization: Bearer ' . getenv('GITHUB_TOKEN')], $header);
|
||||
}
|
||||
$header = [];
|
||||
putenv('GITHUB_TOKEN=token');
|
||||
CurlHook::setupGithubToken('GET', 'https://example.com', $header);
|
||||
$this->assertEquals(['Authorization: Bearer token'], $header);
|
||||
}
|
||||
}
|
||||
@ -1,113 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\store;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\InterruptException;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\LockFile;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* TODO: Test all methods
|
||||
*/
|
||||
class DownloaderTest extends TestCase
|
||||
{
|
||||
public function testGetLatestGithubTarball()
|
||||
{
|
||||
$this->assertEquals(
|
||||
'https://api.github.com/repos/AOMediaCodec/libavif/tarball/v1.1.1',
|
||||
Downloader::getLatestGithubTarball('libavif', [
|
||||
'type' => 'ghtar',
|
||||
'repo' => 'AOMediaCodec/libavif',
|
||||
])[0]
|
||||
);
|
||||
}
|
||||
|
||||
public function testDownloadGit()
|
||||
{
|
||||
Downloader::downloadGit('setup-static-php', 'https://github.com/static-php/setup-static-php.git', 'main');
|
||||
$this->assertTrue(true);
|
||||
|
||||
// test keyboard interrupt
|
||||
try {
|
||||
Downloader::downloadGit('setup-static-php', 'https://github.com/static-php/setup-static-php.git', 'SIGINT');
|
||||
} catch (InterruptException $e) {
|
||||
$this->assertStringContainsString('interrupted', $e->getMessage());
|
||||
return;
|
||||
}
|
||||
$this->fail('Expected exception not thrown');
|
||||
}
|
||||
|
||||
public function testDownloadFile()
|
||||
{
|
||||
Downloader::downloadFile('fake-file', 'https://fakecmd.com/curlDown', 'curlDown.exe');
|
||||
$this->assertTrue(true);
|
||||
|
||||
// test keyboard interrupt
|
||||
try {
|
||||
Downloader::downloadFile('fake-file', 'https://fakecmd.com/curlDown', 'SIGINT');
|
||||
} catch (InterruptException $e) {
|
||||
$this->assertStringContainsString('interrupted', $e->getMessage());
|
||||
return;
|
||||
}
|
||||
$this->fail('Expected exception not thrown');
|
||||
}
|
||||
|
||||
public function testLockSource()
|
||||
{
|
||||
LockFile::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']);
|
||||
$this->assertFileExists(LockFile::LOCK_FILE);
|
||||
$json = json_decode(file_get_contents(LockFile::LOCK_FILE), true);
|
||||
$this->assertIsArray($json);
|
||||
$this->assertArrayHasKey('fake-file', $json);
|
||||
$this->assertArrayHasKey('source_type', $json['fake-file']);
|
||||
$this->assertArrayHasKey('filename', $json['fake-file']);
|
||||
$this->assertArrayHasKey('move_path', $json['fake-file']);
|
||||
$this->assertArrayHasKey('lock_as', $json['fake-file']);
|
||||
$this->assertEquals(SPC_SOURCE_ARCHIVE, $json['fake-file']['source_type']);
|
||||
$this->assertEquals('fake-file-name', $json['fake-file']['filename']);
|
||||
$this->assertEquals('fake-path', $json['fake-file']['move_path']);
|
||||
$this->assertEquals('fake-lock-as', $json['fake-file']['lock_as']);
|
||||
}
|
||||
|
||||
public function testGetLatestBitbucketTag()
|
||||
{
|
||||
$this->assertEquals(
|
||||
'abc.tar.gz',
|
||||
Downloader::getLatestBitbucketTag('abc', [
|
||||
'repo' => 'MATCHED/def',
|
||||
])[1]
|
||||
);
|
||||
$this->assertEquals(
|
||||
'abc-1.0.0.tar.gz',
|
||||
Downloader::getLatestBitbucketTag('abc', [
|
||||
'repo' => 'abc/def',
|
||||
])[1]
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetLatestGithubRelease()
|
||||
{
|
||||
$this->assertEquals(
|
||||
'ghreltest.tar.gz',
|
||||
Downloader::getLatestGithubRelease('ghrel', [
|
||||
'type' => 'ghrel',
|
||||
'repo' => 'ghreltest/ghrel',
|
||||
'match' => 'ghreltest.tar.gz',
|
||||
])[1]
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetFromFileList()
|
||||
{
|
||||
$filelist = Downloader::getFromFileList('fake-filelist', [
|
||||
'url' => 'https://fakecmd.com/filelist',
|
||||
'regex' => '/href="(?<file>filelist-(?<version>[^"]+)\.tar\.xz)"/',
|
||||
]);
|
||||
$this->assertIsArray($filelist);
|
||||
$this->assertEquals('filelist-4.7.0.tar.xz', $filelist[1]);
|
||||
}
|
||||
}
|
||||
@ -1,176 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\store;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\store\FileSystem;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FileSystemTest extends TestCase
|
||||
{
|
||||
private const TEST_FILE_CONTENT = 'Hello! Bye!';
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
if (file_put_contents(WORKING_DIR . '/.testfile', self::TEST_FILE_CONTENT) === false) {
|
||||
static::markTestSkipped('Current environment or working dir is not writable!');
|
||||
}
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
if (file_exists(WORKING_DIR . '/.testfile')) {
|
||||
unlink(WORKING_DIR . '/.testfile');
|
||||
}
|
||||
}
|
||||
|
||||
public function testReplaceFileRegex()
|
||||
{
|
||||
$file = WORKING_DIR . '/.txt1';
|
||||
file_put_contents($file, 'hello');
|
||||
|
||||
FileSystem::replaceFileRegex($file, '/ll/', '11');
|
||||
$this->assertEquals('he11o', file_get_contents($file));
|
||||
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
public function testFindCommandPath()
|
||||
{
|
||||
$this->assertNull(FileSystem::findCommandPath('randomtestxxxxx'));
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->assertIsString(FileSystem::findCommandPath('explorer'));
|
||||
} elseif (in_array(PHP_OS_FAMILY, ['Linux', 'Darwin', 'FreeBSD'])) {
|
||||
$this->assertIsString(FileSystem::findCommandPath('uname'));
|
||||
}
|
||||
}
|
||||
|
||||
public function testReadFile()
|
||||
{
|
||||
$file = WORKING_DIR . '/.testread';
|
||||
file_put_contents($file, 'haha');
|
||||
$content = FileSystem::readFile($file);
|
||||
$this->assertEquals('haha', $content);
|
||||
@unlink($file);
|
||||
}
|
||||
|
||||
public function testReplaceFileUser()
|
||||
{
|
||||
$file = WORKING_DIR . '/.txt1';
|
||||
file_put_contents($file, 'hello');
|
||||
|
||||
FileSystem::replaceFileUser($file, function ($file) {
|
||||
return str_replace('el', '55', $file);
|
||||
});
|
||||
$this->assertEquals('h55lo', file_get_contents($file));
|
||||
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
public function testExtname()
|
||||
{
|
||||
$this->assertEquals('exe', FileSystem::extname('/tmp/asd.exe'));
|
||||
$this->assertEquals('', FileSystem::extname('/tmp/asd.'));
|
||||
}
|
||||
|
||||
public function testGetClassesPsr4()
|
||||
{
|
||||
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/builder/extension', 'SPC\builder\extension');
|
||||
foreach ($classes as $class) {
|
||||
$this->assertIsString($class);
|
||||
new \ReflectionClass($class);
|
||||
}
|
||||
}
|
||||
|
||||
public function testConvertPath()
|
||||
{
|
||||
$this->assertEquals('phar://C:/pharfile.phar', FileSystem::convertPath('phar://C:/pharfile.phar'));
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
$this->assertEquals('C:\Windows\win.ini', FileSystem::convertPath('C:\Windows/win.ini'));
|
||||
}
|
||||
}
|
||||
|
||||
public function testCreateDir()
|
||||
{
|
||||
FileSystem::createDir(WORKING_DIR . '/.testdir');
|
||||
$this->assertDirectoryExists(WORKING_DIR . '/.testdir');
|
||||
rmdir(WORKING_DIR . '/.testdir');
|
||||
}
|
||||
|
||||
public function testReplaceFileStr()
|
||||
{
|
||||
$file = WORKING_DIR . '/.txt1';
|
||||
file_put_contents($file, 'hello');
|
||||
|
||||
FileSystem::replaceFileStr($file, 'el', '55');
|
||||
$this->assertEquals('h55lo', file_get_contents($file));
|
||||
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
public function testResetDir()
|
||||
{
|
||||
// prepare fake git dir to test
|
||||
FileSystem::createDir(WORKING_DIR . '/.fake_down_test');
|
||||
FileSystem::writeFile(WORKING_DIR . '/.fake_down_test/a.c', 'int main() { return 0; }');
|
||||
FileSystem::resetDir(WORKING_DIR . '/.fake_down_test');
|
||||
$this->assertFileDoesNotExist(WORKING_DIR . '/.fake_down_test/a.c');
|
||||
FileSystem::removeDir(WORKING_DIR . '/.fake_down_test');
|
||||
}
|
||||
|
||||
public function testCopyDir()
|
||||
{
|
||||
// prepare fake git dir to test
|
||||
FileSystem::createDir(WORKING_DIR . '/.fake_down_test');
|
||||
FileSystem::writeFile(WORKING_DIR . '/.fake_down_test/a.c', 'int main() { return 0; }');
|
||||
FileSystem::copyDir(WORKING_DIR . '/.fake_down_test', WORKING_DIR . '/.fake_down_test2');
|
||||
$this->assertDirectoryExists(WORKING_DIR . '/.fake_down_test2');
|
||||
$this->assertFileExists(WORKING_DIR . '/.fake_down_test2/a.c');
|
||||
FileSystem::removeDir(WORKING_DIR . '/.fake_down_test');
|
||||
FileSystem::removeDir(WORKING_DIR . '/.fake_down_test2');
|
||||
}
|
||||
|
||||
public function testRemoveDir()
|
||||
{
|
||||
FileSystem::createDir(WORKING_DIR . '/.fake_down_test');
|
||||
$this->assertDirectoryExists(WORKING_DIR . '/.fake_down_test');
|
||||
FileSystem::removeDir(WORKING_DIR . '/.fake_down_test');
|
||||
$this->assertDirectoryDoesNotExist(WORKING_DIR . '/.fake_down_test');
|
||||
}
|
||||
|
||||
public function testLoadConfigArray()
|
||||
{
|
||||
$arr = FileSystem::loadConfigArray('lib');
|
||||
$this->assertArrayHasKey('zlib', $arr);
|
||||
}
|
||||
|
||||
public function testIsRelativePath()
|
||||
{
|
||||
$this->assertTrue(FileSystem::isRelativePath('.'));
|
||||
$this->assertTrue(FileSystem::isRelativePath('.\sdf'));
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
$this->assertFalse(FileSystem::isRelativePath('C:\asdasd/fwe\asd'));
|
||||
} else {
|
||||
$this->assertFalse(FileSystem::isRelativePath('/fwefwefewf'));
|
||||
}
|
||||
}
|
||||
|
||||
public function testScanDirFiles()
|
||||
{
|
||||
$this->assertFalse(FileSystem::scanDirFiles('wfwefewfewf'));
|
||||
$files = FileSystem::scanDirFiles(ROOT_DIR . '/config', true, true);
|
||||
$this->assertContains('lib.json', $files);
|
||||
}
|
||||
|
||||
public function testWriteFile()
|
||||
{
|
||||
FileSystem::writeFile(WORKING_DIR . '/.txt', 'txt');
|
||||
$this->assertFileExists(WORKING_DIR . '/.txt');
|
||||
$this->assertEquals('txt', FileSystem::readFile(WORKING_DIR . '/.txt'));
|
||||
unlink(WORKING_DIR . '/.txt');
|
||||
}
|
||||
}
|
||||
@ -1,767 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\ValidationException;
|
||||
use SPC\util\ConfigValidator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ConfigValidatorTest extends TestCase
|
||||
{
|
||||
public function testValidateSourceGood(): void
|
||||
{
|
||||
$good_source = [
|
||||
'source1' => [
|
||||
'type' => 'filelist',
|
||||
'url' => 'https://example.com',
|
||||
'regex' => '.*',
|
||||
],
|
||||
'source2' => [
|
||||
'type' => 'git',
|
||||
'url' => 'https://example.com',
|
||||
'rev' => 'master',
|
||||
],
|
||||
'source3' => [
|
||||
'type' => 'ghtagtar',
|
||||
'repo' => 'aaaa/bbbb',
|
||||
],
|
||||
'source4' => [
|
||||
'type' => 'ghtar',
|
||||
'repo' => 'aaa/bbb',
|
||||
'path' => 'path/to/dir',
|
||||
],
|
||||
'source5' => [
|
||||
'type' => 'ghrel',
|
||||
'repo' => 'aaa/bbb',
|
||||
'match' => '.*',
|
||||
],
|
||||
'source6' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
],
|
||||
'source7' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'filename' => 'test.tar.gz',
|
||||
'path' => 'test/path',
|
||||
'provide-pre-built' => true,
|
||||
'license' => [
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
],
|
||||
],
|
||||
'source8' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'alt' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://alt.example.com',
|
||||
],
|
||||
],
|
||||
'source9' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'alt' => false,
|
||||
'license' => [
|
||||
'type' => 'text',
|
||||
'text' => 'MIT License',
|
||||
],
|
||||
],
|
||||
];
|
||||
try {
|
||||
ConfigValidator::validateSource($good_source);
|
||||
$this->assertTrue(true);
|
||||
} catch (ValidationException $e) {
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidateSourceBad(): void
|
||||
{
|
||||
$bad_source = [
|
||||
'source1' => [
|
||||
'type' => 'filelist',
|
||||
'url' => 'https://example.com',
|
||||
// no regex
|
||||
],
|
||||
'source2' => [
|
||||
'type' => 'git',
|
||||
'url' => true, // not string
|
||||
'rev' => 'master',
|
||||
],
|
||||
'source3' => [
|
||||
'type' => 'ghtagtar',
|
||||
'url' => 'aaaa/bbbb', // not repo
|
||||
],
|
||||
'source4' => [
|
||||
'type' => 'ghtar',
|
||||
'repo' => 'aaa/bbb',
|
||||
'path' => true, // not string
|
||||
],
|
||||
'source5' => [
|
||||
'type' => 'ghrel',
|
||||
'repo' => 'aaa/bbb',
|
||||
'match' => 1, // not string
|
||||
],
|
||||
'source6' => [
|
||||
'type' => 'url', // no url
|
||||
],
|
||||
'source7' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'provide-pre-built' => 'not boolean', // not boolean
|
||||
],
|
||||
'source8' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'prefer-stable' => 'not boolean', // not boolean
|
||||
],
|
||||
'source9' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'license' => 'not object', // not object
|
||||
],
|
||||
'source10' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'license' => [
|
||||
'type' => 'invalid', // invalid type
|
||||
],
|
||||
],
|
||||
'source11' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'license' => [
|
||||
'type' => 'file', // missing path
|
||||
],
|
||||
],
|
||||
'source12' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'license' => [
|
||||
'type' => 'text', // missing text
|
||||
],
|
||||
],
|
||||
'source13' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'alt' => 'not object or boolean', // not object or boolean
|
||||
],
|
||||
];
|
||||
foreach ($bad_source as $name => $src) {
|
||||
try {
|
||||
ConfigValidator::validateSource([$name => $src]);
|
||||
$this->fail("should throw ValidationException for source {$name}");
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidateLibsGood(): void
|
||||
{
|
||||
$good_libs = [
|
||||
'lib1' => [
|
||||
'source' => 'source1',
|
||||
],
|
||||
'lib2' => [
|
||||
'source' => 'source2',
|
||||
'lib-depends' => [
|
||||
'lib1',
|
||||
],
|
||||
],
|
||||
'lib3' => [
|
||||
'source' => 'source3',
|
||||
'lib-suggests' => [
|
||||
'lib1',
|
||||
],
|
||||
],
|
||||
'lib4' => [
|
||||
'source' => 'source4',
|
||||
'headers' => [
|
||||
'header1.h',
|
||||
'header2.h',
|
||||
],
|
||||
'headers-windows' => [
|
||||
'windows_header.h',
|
||||
],
|
||||
'bin-unix' => [
|
||||
'binary1',
|
||||
'binary2',
|
||||
],
|
||||
'frameworks' => [
|
||||
'CoreFoundation',
|
||||
'SystemConfiguration',
|
||||
],
|
||||
],
|
||||
'lib5' => [
|
||||
'type' => 'package',
|
||||
'source' => 'source5',
|
||||
'pkg-configs' => [
|
||||
'pkg1',
|
||||
'pkg2',
|
||||
],
|
||||
],
|
||||
'lib6' => [
|
||||
'type' => 'root',
|
||||
],
|
||||
];
|
||||
try {
|
||||
ConfigValidator::validateLibs($good_libs, ['source1' => [], 'source2' => [], 'source3' => [], 'source4' => [], 'source5' => []]);
|
||||
$this->assertTrue(true);
|
||||
} catch (ValidationException $e) {
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidateLibsBad(): void
|
||||
{
|
||||
// lib.json is broken
|
||||
try {
|
||||
ConfigValidator::validateLibs('not array');
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// lib source not exists
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source3']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// lib.json is broken by not assoc array
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1', 'lib2'], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// lib.json lib is not one of "lib", "package", "root", "target"
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'type' => 'not one of']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// lib.json lib if it is "lib" or "package", it must have "source"
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['type' => 'lib']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// lib.json static-libs must be a list
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'static-libs-windows' => 'not list']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// lib.json frameworks must be a list
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'frameworks' => 'not list']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// source must be string
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => true]], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// lib-depends must be list
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'lib-depends' => ['a' => 'not list']]], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// lib-suggests must be list
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'lib-suggests' => ['a' => 'not list']]], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// headers must be list
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'headers' => 'not list']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// bin must be list
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'bin-unix' => 'not list']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidateExts(): void
|
||||
{
|
||||
// Test valid extensions
|
||||
$valid_exts = [
|
||||
'ext1' => [
|
||||
'type' => 'builtin',
|
||||
],
|
||||
'ext2' => [
|
||||
'type' => 'external',
|
||||
'source' => 'source1',
|
||||
],
|
||||
'ext3' => [
|
||||
'type' => 'external',
|
||||
'source' => 'source2',
|
||||
'arg-type' => 'enable',
|
||||
'lib-depends' => ['lib1'],
|
||||
'lib-suggests' => ['lib2'],
|
||||
'ext-depends-windows' => ['ext1'],
|
||||
'support' => [
|
||||
'Windows' => 'wip',
|
||||
'BSD' => 'wip',
|
||||
],
|
||||
'notes' => true,
|
||||
],
|
||||
'ext4' => [
|
||||
'type' => 'external',
|
||||
'source' => 'source3',
|
||||
'arg-type-unix' => 'with-path',
|
||||
'arg-type-windows' => 'with',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateExts($valid_exts);
|
||||
|
||||
// Test invalid data
|
||||
$this->expectException(ValidationException::class);
|
||||
ConfigValidator::validateExts(null);
|
||||
}
|
||||
|
||||
public function testValidateExtsBad(): void
|
||||
{
|
||||
// Test invalid extension type
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'invalid']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test external extension without source
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'external']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test non-object extension
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => 'not object']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid source type
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'external', 'source' => true]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid support
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'support' => 'not object']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid notes
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'notes' => 'not boolean']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid lib-depends
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'lib-depends' => 'not list']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid arg-type
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'arg-type' => 'invalid']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid arg-type with suffix
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'arg-type-unix' => 'invalid']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidatePkgs(): void
|
||||
{
|
||||
// Test valid packages (all supported types)
|
||||
$valid_pkgs = [
|
||||
'pkg1' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com/file.tar.gz',
|
||||
],
|
||||
'pkg2' => [
|
||||
'type' => 'ghrel',
|
||||
'repo' => 'owner/repo',
|
||||
'match' => 'file.+\.tar\.gz',
|
||||
],
|
||||
'pkg3' => [
|
||||
'type' => 'custom',
|
||||
],
|
||||
'pkg4' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com/archive.zip',
|
||||
'filename' => 'archive.zip',
|
||||
'path' => 'extract/path',
|
||||
'extract-files' => [
|
||||
'source/file.exe' => '{pkg_root_path}/bin/file.exe',
|
||||
'source/lib.dll' => '{pkg_root_path}/lib/lib.dll',
|
||||
],
|
||||
],
|
||||
'pkg5' => [
|
||||
'type' => 'ghrel',
|
||||
'repo' => 'owner/repo',
|
||||
'match' => 'release.+\.zip',
|
||||
'extract-files' => [
|
||||
'binary' => '{pkg_root_path}/bin/binary',
|
||||
],
|
||||
],
|
||||
'pkg6' => [
|
||||
'type' => 'filelist',
|
||||
'url' => 'https://example.com/filelist',
|
||||
'regex' => '/href="(?<file>.*\.tar\.gz)"/',
|
||||
],
|
||||
'pkg7' => [
|
||||
'type' => 'git',
|
||||
'url' => 'https://github.com/owner/repo.git',
|
||||
'rev' => 'main',
|
||||
],
|
||||
'pkg8' => [
|
||||
'type' => 'git',
|
||||
'url' => 'https://github.com/owner/repo.git',
|
||||
'rev' => 'v1.0.0',
|
||||
'path' => 'subdir/path',
|
||||
],
|
||||
'pkg9' => [
|
||||
'type' => 'ghtagtar',
|
||||
'repo' => 'owner/repo',
|
||||
],
|
||||
'pkg10' => [
|
||||
'type' => 'ghtar',
|
||||
'repo' => 'owner/repo',
|
||||
'path' => 'subdir',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validatePkgs($valid_pkgs);
|
||||
|
||||
// Test invalid data
|
||||
$this->expectException(ValidationException::class);
|
||||
ConfigValidator::validatePkgs(null);
|
||||
}
|
||||
|
||||
public function testValidatePkgsBad(): void
|
||||
{
|
||||
// Test invalid package type
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'invalid']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test non-object package
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => 'not object']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test filelist type without url
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'filelist', 'regex' => '.*']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test filelist type without regex
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'filelist', 'url' => 'https://example.com']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test git type without url
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'git', 'rev' => 'main']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test git type without rev
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'git', 'url' => 'https://github.com/owner/repo.git']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghtagtar type without repo
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghtagtar']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghtar type without repo
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghtar']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test url type without url
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test url type with non-string url
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => true]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghrel type without repo
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghrel', 'match' => 'pattern']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghrel type without match
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghrel', 'repo' => 'owner/repo']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghrel type with non-string repo
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghrel', 'repo' => true, 'match' => 'pattern']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghrel type with non-string match
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghrel', 'repo' => 'owner/repo', 'match' => 123]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test git type with non-string path
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'git', 'url' => 'https://github.com/owner/repo.git', 'rev' => 'main', 'path' => 123]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test url type with non-string filename
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => 'https://example.com', 'filename' => 123]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid extract-files (not object)
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => 'https://example.com', 'extract-files' => 'not object']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid extract-files mapping (non-string key)
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => 'https://example.com', 'extract-files' => [123 => 'target']]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid extract-files mapping (non-string value)
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => 'https://example.com', 'extract-files' => ['source' => 123]]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidatePreBuilt(): void
|
||||
{
|
||||
// Test valid pre-built configurations
|
||||
$valid_prebuilt = [
|
||||
'basic' => [
|
||||
'repo' => 'static-php/static-php-cli-hosted',
|
||||
'match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz',
|
||||
],
|
||||
'full' => [
|
||||
'repo' => 'static-php/static-php-cli-hosted',
|
||||
'prefer-stable' => true,
|
||||
'match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz',
|
||||
'match-pattern-macos' => '{name}-{arch}-{os}.txz',
|
||||
'match-pattern-windows' => '{name}-{arch}-{os}.tgz',
|
||||
],
|
||||
'prefer-stable-false' => [
|
||||
'repo' => 'owner/repo',
|
||||
'prefer-stable' => false,
|
||||
'match-pattern-macos' => '{name}-{arch}-{os}.tar.gz',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($valid_prebuilt as $name => $config) {
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt($config);
|
||||
$this->assertTrue(true, "Config {$name} should be valid");
|
||||
} catch (ValidationException $e) {
|
||||
$this->fail("Config {$name} should be valid but got: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidatePreBuiltBad(): void
|
||||
{
|
||||
// Test non-array data
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt('invalid');
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test missing repo
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid repo type
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 123, 'match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid prefer-stable type
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'prefer-stable' => 'true', 'match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test no match patterns
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid match pattern type
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => 123]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test missing {name} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{arch}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test missing {arch} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{name}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test missing {os} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{name}-{arch}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test linux pattern missing {libc} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{name}-{arch}-{os}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test linux pattern missing {libcver} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{name}-{arch}-{os}-{libc}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,113 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\util\DependencyUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class DependencyUtilTest extends TestCase
|
||||
{
|
||||
private array $originalConfig;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Save original configuration
|
||||
$this->originalConfig = [
|
||||
'source' => Config::$source,
|
||||
'lib' => Config::$lib,
|
||||
'ext' => Config::$ext,
|
||||
];
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Restore original configuration
|
||||
Config::$source = $this->originalConfig['source'];
|
||||
Config::$lib = $this->originalConfig['lib'];
|
||||
Config::$ext = $this->originalConfig['ext'];
|
||||
}
|
||||
|
||||
public function testGetExtLibsByDeps(): void
|
||||
{
|
||||
// Set up test data
|
||||
Config::$source = [
|
||||
'test1' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://pecl.php.net/get/APCu',
|
||||
'filename' => 'apcu.tgz',
|
||||
'license' => [
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
],
|
||||
],
|
||||
];
|
||||
Config::$lib = [
|
||||
'lib-base' => ['type' => 'root'],
|
||||
'php' => ['type' => 'root'],
|
||||
'libaaa' => [
|
||||
'source' => 'test1',
|
||||
'static-libs' => ['libaaa.a'],
|
||||
'lib-depends' => ['libbbb', 'libccc'],
|
||||
'lib-suggests' => ['libeee'],
|
||||
],
|
||||
'libbbb' => [
|
||||
'source' => 'test1',
|
||||
'static-libs' => ['libbbb.a'],
|
||||
'lib-suggests' => ['libccc'],
|
||||
],
|
||||
'libccc' => [
|
||||
'source' => 'test1',
|
||||
'static-libs' => ['libccc.a'],
|
||||
],
|
||||
'libeee' => [
|
||||
'source' => 'test1',
|
||||
'static-libs' => ['libeee.a'],
|
||||
'lib-suggests' => ['libfff'],
|
||||
],
|
||||
'libfff' => [
|
||||
'source' => 'test1',
|
||||
'static-libs' => ['libfff.a'],
|
||||
],
|
||||
];
|
||||
Config::$ext = [
|
||||
'ext-a' => [
|
||||
'type' => 'builtin',
|
||||
'lib-depends' => ['libaaa'],
|
||||
'ext-suggests' => ['ext-b'],
|
||||
],
|
||||
'ext-b' => [
|
||||
'type' => 'builtin',
|
||||
'lib-depends' => ['libeee'],
|
||||
],
|
||||
];
|
||||
|
||||
// Test dependency resolution
|
||||
[$exts, $libs, $not_included] = DependencyUtil::getExtsAndLibs(['ext-a'], include_suggested_exts: true);
|
||||
$this->assertContains('libbbb', $libs);
|
||||
$this->assertContains('libccc', $libs);
|
||||
$this->assertContains('ext-b', $exts);
|
||||
$this->assertContains('ext-b', $not_included);
|
||||
|
||||
// Test dependency order
|
||||
$this->assertIsInt($b = array_search('libbbb', $libs));
|
||||
$this->assertIsInt($c = array_search('libccc', $libs));
|
||||
$this->assertIsInt($a = array_search('libaaa', $libs));
|
||||
// libbbb, libaaa
|
||||
$this->assertTrue($b < $a);
|
||||
$this->assertTrue($c < $a);
|
||||
$this->assertTrue($c < $b);
|
||||
}
|
||||
|
||||
public function testNotExistExtException(): void
|
||||
{
|
||||
$this->expectException(WrongUsageException::class);
|
||||
DependencyUtil::getExtsAndLibs(['sdsd']);
|
||||
}
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\SPCInternalException;
|
||||
use SPC\util\GlobalEnvManager;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class GlobalEnvManagerTest extends TestCase
|
||||
{
|
||||
private array $originalEnv;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Save original environment variables
|
||||
$this->originalEnv = [
|
||||
'BUILD_ROOT_PATH' => getenv('BUILD_ROOT_PATH'),
|
||||
'SPC_TARGET' => getenv('SPC_TARGET'),
|
||||
'SPC_LIBC' => getenv('SPC_LIBC'),
|
||||
];
|
||||
// Temporarily set private GlobalEnvManager::$initialized to false (use reflection)
|
||||
$reflection = new \ReflectionClass(GlobalEnvManager::class);
|
||||
$property = $reflection->getProperty('initialized');
|
||||
$property->setValue(null, false);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Restore original environment variables
|
||||
foreach ($this->originalEnv as $key => $value) {
|
||||
if ($value === false) {
|
||||
putenv($key);
|
||||
} else {
|
||||
putenv("{$key}={$value}");
|
||||
}
|
||||
}
|
||||
// Temporarily set private GlobalEnvManager::$initialized to false (use reflection)
|
||||
$reflection = new \ReflectionClass(GlobalEnvManager::class);
|
||||
$property = $reflection->getProperty('initialized');
|
||||
$property->setValue(null, true);
|
||||
}
|
||||
|
||||
public function testGetInitializedEnv(): void
|
||||
{
|
||||
// Test that getInitializedEnv returns an array
|
||||
$result = GlobalEnvManager::getInitializedEnv();
|
||||
$this->assertIsArray($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider envVariableProvider
|
||||
*/
|
||||
public function testPutenv(string $envVar): void
|
||||
{
|
||||
// Test putenv functionality
|
||||
GlobalEnvManager::putenv($envVar);
|
||||
|
||||
$env = GlobalEnvManager::getInitializedEnv();
|
||||
$this->assertContains($envVar, $env);
|
||||
$this->assertEquals(explode('=', $envVar, 2)[1], getenv(explode('=', $envVar, 2)[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider pathProvider
|
||||
*/
|
||||
public function testAddPathIfNotExistsOnUnix(string $path): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$originalPath = getenv('PATH');
|
||||
GlobalEnvManager::addPathIfNotExists($path);
|
||||
|
||||
$newPath = getenv('PATH');
|
||||
$this->assertStringContainsString($path, $newPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider pathProvider
|
||||
*/
|
||||
public function testAddPathIfNotExistsWhenPathAlreadyExists(string $path): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
GlobalEnvManager::addPathIfNotExists($path);
|
||||
$pathAfterFirstAdd = getenv('PATH');
|
||||
|
||||
GlobalEnvManager::addPathIfNotExists($path);
|
||||
$pathAfterSecondAdd = getenv('PATH');
|
||||
|
||||
// Should not add the same path twice
|
||||
$this->assertEquals($pathAfterFirstAdd, $pathAfterSecondAdd);
|
||||
}
|
||||
|
||||
public function testInitWithoutBuildRootPath(): void
|
||||
{
|
||||
// Temporarily unset BUILD_ROOT_PATH
|
||||
putenv('BUILD_ROOT_PATH');
|
||||
|
||||
$this->expectException(SPCInternalException::class);
|
||||
GlobalEnvManager::init();
|
||||
}
|
||||
|
||||
public function testAfterInit(): void
|
||||
{
|
||||
// Set required environment variable
|
||||
putenv('BUILD_ROOT_PATH=/test/path');
|
||||
putenv('SPC_SKIP_TOOLCHAIN_CHECK=true');
|
||||
|
||||
// Should not throw exception when SPC_SKIP_TOOLCHAIN_CHECK is true
|
||||
GlobalEnvManager::afterInit();
|
||||
|
||||
$this->assertTrue(true); // Test passes if no exception is thrown
|
||||
}
|
||||
|
||||
public function envVariableProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple-env' => ['TEST_VAR=test_value'],
|
||||
'complex-env' => ['COMPLEX_VAR=complex_value_with_spaces'],
|
||||
'numeric-env' => ['NUMERIC_VAR=123'],
|
||||
'special-chars-env' => ['SPECIAL_VAR=test@#$%'],
|
||||
];
|
||||
}
|
||||
|
||||
public function pathProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple-path' => ['/test/path'],
|
||||
'complex-path' => ['/usr/local/bin'],
|
||||
'home-path' => ['/home/user/bin'],
|
||||
'root-path' => ['/root/bin'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\store\Config;
|
||||
use SPC\util\LicenseDumper;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class LicenseDumperTest extends TestCase
|
||||
{
|
||||
private const DIRECTORY = __DIR__ . '/../../var/license-dump';
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
@rmdir(self::DIRECTORY);
|
||||
@rmdir(dirname(self::DIRECTORY));
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@rmdir(self::DIRECTORY);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
array_map('unlink', glob(self::DIRECTORY . '/*.txt'));
|
||||
}
|
||||
|
||||
public function testDumpWithSingleLicense(): void
|
||||
{
|
||||
$bak = [
|
||||
'source' => Config::$source,
|
||||
'lib' => Config::$lib,
|
||||
];
|
||||
Config::$lib = [
|
||||
'lib-base' => ['type' => 'root'],
|
||||
'php' => ['type' => 'root'],
|
||||
'fake_lib' => [
|
||||
'source' => 'fake_lib',
|
||||
],
|
||||
];
|
||||
Config::$source = [
|
||||
'fake_lib' => [
|
||||
'license' => [
|
||||
'type' => 'text',
|
||||
'text' => 'license',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$dumper = new LicenseDumper();
|
||||
$dumper->addLibs(['fake_lib']);
|
||||
$dumper->dump(self::DIRECTORY);
|
||||
|
||||
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_0.txt');
|
||||
// restore
|
||||
Config::$source = $bak['source'];
|
||||
Config::$lib = $bak['lib'];
|
||||
}
|
||||
|
||||
public function testDumpWithMultipleLicenses(): void
|
||||
{
|
||||
$bak = [
|
||||
'source' => Config::$source,
|
||||
'lib' => Config::$lib,
|
||||
];
|
||||
Config::$lib = [
|
||||
'lib-base' => ['type' => 'root'],
|
||||
'php' => ['type' => 'root'],
|
||||
'fake_lib' => [
|
||||
'source' => 'fake_lib',
|
||||
],
|
||||
];
|
||||
Config::$source = [
|
||||
'fake_lib' => [
|
||||
'license' => [
|
||||
[
|
||||
'type' => 'text',
|
||||
'text' => 'license',
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'text' => 'license',
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'text' => 'license',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$dumper = new LicenseDumper();
|
||||
$dumper->addLibs(['fake_lib']);
|
||||
$dumper->dump(self::DIRECTORY);
|
||||
|
||||
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_0.txt');
|
||||
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_1.txt');
|
||||
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_2.txt');
|
||||
|
||||
// restore
|
||||
Config::$source = $bak['source'];
|
||||
Config::$lib = $bak['lib'];
|
||||
}
|
||||
}
|
||||
@ -1,210 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\ExecutionException;
|
||||
use SPC\util\PkgConfigUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class PkgConfigUtilTest extends TestCase
|
||||
{
|
||||
private static string $originalPath;
|
||||
|
||||
private static string $fakePkgConfigPath;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
// Skip tests on Windows as pkg-config is not typically available
|
||||
self::markTestSkipped('PkgConfigUtil tests are not applicable on Windows.');
|
||||
}
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
// Save original PATH
|
||||
self::$originalPath = getenv('PATH');
|
||||
|
||||
// Create fake pkg-config directory
|
||||
self::$fakePkgConfigPath = sys_get_temp_dir() . '/fake-pkg-config-' . uniqid();
|
||||
mkdir(self::$fakePkgConfigPath, 0755, true);
|
||||
|
||||
// Create fake pkg-config executable
|
||||
self::createFakePkgConfig();
|
||||
|
||||
// Add fake pkg-config to PATH
|
||||
putenv('PATH=' . self::$fakePkgConfigPath . ':' . self::$originalPath);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
// Restore original PATH
|
||||
putenv('PATH=' . self::$originalPath);
|
||||
|
||||
// Clean up fake pkg-config
|
||||
if (is_dir(self::$fakePkgConfigPath)) {
|
||||
self::removeDirectory(self::$fakePkgConfigPath);
|
||||
}
|
||||
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validPackageProvider
|
||||
*/
|
||||
public function testGetCflagsWithValidPackage(string $package, string $expectedCflags): void
|
||||
{
|
||||
$result = PkgConfigUtil::getCflags($package);
|
||||
$this->assertEquals($expectedCflags, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validPackageProvider
|
||||
*/
|
||||
public function testGetLibsArrayWithValidPackage(string $package, string $expectedCflags, array $expectedLibs): void
|
||||
{
|
||||
$result = PkgConfigUtil::getLibsArray($package);
|
||||
$this->assertEquals($expectedLibs, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidPackageProvider
|
||||
*/
|
||||
public function testGetCflagsWithInvalidPackage(string $package): void
|
||||
{
|
||||
$this->expectException(ExecutionException::class);
|
||||
PkgConfigUtil::getCflags($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidPackageProvider
|
||||
*/
|
||||
public function testGetLibsArrayWithInvalidPackage(string $package): void
|
||||
{
|
||||
$this->expectException(ExecutionException::class);
|
||||
PkgConfigUtil::getLibsArray($package);
|
||||
}
|
||||
|
||||
public static function invalidPackageProvider(): array
|
||||
{
|
||||
return [
|
||||
'invalid-package' => ['invalid-package'],
|
||||
'empty-string' => [''],
|
||||
'non-existent-package' => ['non-existent-package'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function validPackageProvider(): array
|
||||
{
|
||||
return [
|
||||
'libxml2' => ['libxml-2.0', '-I/usr/include/libxml2', ['-lxml2', '']],
|
||||
'zlib' => ['zlib', '-I/usr/include', ['-lz', '']],
|
||||
'openssl' => ['openssl', '-I/usr/include/openssl', ['-lssl', '-lcrypto', '']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fake pkg-config executable
|
||||
*/
|
||||
private static function createFakePkgConfig(): void
|
||||
{
|
||||
$pkgConfigScript = self::$fakePkgConfigPath . '/pkg-config';
|
||||
|
||||
$script = <<<'SCRIPT'
|
||||
#!/bin/bash
|
||||
|
||||
# Fake pkg-config script for testing
|
||||
# Shift arguments to get the package name
|
||||
shift
|
||||
|
||||
case "$1" in
|
||||
--cflags-only-other)
|
||||
shift
|
||||
case "$1" in
|
||||
libxml-2.0)
|
||||
echo "-I/usr/include/libxml2"
|
||||
;;
|
||||
zlib)
|
||||
echo "-I/usr/include"
|
||||
;;
|
||||
openssl)
|
||||
echo "-I/usr/include/openssl"
|
||||
;;
|
||||
*)
|
||||
echo "Package '$1' was not found in the pkg-config search path." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--libs-only-l)
|
||||
shift
|
||||
case "$1" in
|
||||
libxml-2.0)
|
||||
echo "-lxml2"
|
||||
;;
|
||||
zlib)
|
||||
echo "-lz"
|
||||
;;
|
||||
openssl)
|
||||
echo "-lssl -lcrypto"
|
||||
;;
|
||||
*)
|
||||
echo "Package '$1' was not found in the pkg-config search path." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--libs-only-other)
|
||||
shift
|
||||
case "$1" in
|
||||
libxml-2.0)
|
||||
echo ""
|
||||
;;
|
||||
zlib)
|
||||
echo ""
|
||||
;;
|
||||
openssl)
|
||||
echo ""
|
||||
;;
|
||||
*)
|
||||
echo "Package '$1' was not found in the pkg-config search path." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: pkg-config [OPTION] [PACKAGE]" >&2
|
||||
echo "Try 'pkg-config --help' for more information." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
SCRIPT;
|
||||
|
||||
file_put_contents($pkgConfigScript, $script);
|
||||
chmod($pkgConfigScript, 0755);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove directory recursively
|
||||
*/
|
||||
private static 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)) {
|
||||
self::removeDirectory($path);
|
||||
} else {
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\builder\BuilderProvider;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\util\SPCConfigUtil;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SPCConfigUtilTest extends TestCase
|
||||
{
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
// Skip tests on Windows as SPCConfigUtil is not applicable
|
||||
self::markTestSkipped('SPCConfigUtil tests are not applicable on Windows.');
|
||||
}
|
||||
$testdir = WORKING_DIR . '/.configtest';
|
||||
FileSystem::createDir($testdir);
|
||||
FileSystem::writeFile($testdir . '/lib.json', file_get_contents(ROOT_DIR . '/config/lib.json'));
|
||||
FileSystem::writeFile($testdir . '/ext.json', file_get_contents(ROOT_DIR . '/config/ext.json'));
|
||||
FileSystem::writeFile($testdir . '/source.json', file_get_contents(ROOT_DIR . '/config/source.json'));
|
||||
FileSystem::loadConfigArray('lib', $testdir);
|
||||
FileSystem::loadConfigArray('ext', $testdir);
|
||||
FileSystem::loadConfigArray('source', $testdir);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
FileSystem::removeDir(WORKING_DIR . '/.configtest');
|
||||
}
|
||||
|
||||
public function testConstruct(): void
|
||||
{
|
||||
$this->assertInstanceOf(SPCConfigUtil::class, new SPCConfigUtil());
|
||||
$this->assertInstanceOf(SPCConfigUtil::class, new SPCConfigUtil(BuilderProvider::makeBuilderByInput(new ArgvInput())));
|
||||
}
|
||||
|
||||
public function testConfig(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Linux') {
|
||||
$this->markTestSkipped('SPCConfigUtil tests are only applicable on Linux.');
|
||||
}
|
||||
// normal
|
||||
$result = (new SPCConfigUtil())->config(['bcmath']);
|
||||
$this->assertStringContainsString(BUILD_ROOT_PATH . '/include', $result['cflags']);
|
||||
$this->assertStringContainsString(BUILD_ROOT_PATH . '/lib', $result['ldflags']);
|
||||
$this->assertStringContainsString('-lphp', $result['libs']);
|
||||
|
||||
// has cpp
|
||||
$result = (new SPCConfigUtil())->config(['rar']);
|
||||
$this->assertStringContainsString(PHP_OS_FAMILY === 'Darwin' ? '-lc++' : '-lstdc++', $result['libs']);
|
||||
|
||||
// has libmimalloc.a in lib dir
|
||||
// backup first
|
||||
if (file_exists(BUILD_LIB_PATH . '/libmimalloc.a')) {
|
||||
$bak = file_get_contents(BUILD_LIB_PATH . '/libmimalloc.a');
|
||||
@unlink(BUILD_LIB_PATH . '/libmimalloc.a');
|
||||
}
|
||||
file_put_contents(BUILD_LIB_PATH . '/libmimalloc.a', '');
|
||||
$result = (new SPCConfigUtil())->config(['bcmath'], ['mimalloc']);
|
||||
$this->assertStringStartsWith(BUILD_LIB_PATH . '/libmimalloc.a', $result['libs']);
|
||||
@unlink(BUILD_LIB_PATH . '/libmimalloc.a');
|
||||
if (isset($bak)) {
|
||||
file_put_contents(BUILD_LIB_PATH . '/libmimalloc.a', $bak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use SPC\util\SPCTarget;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SPCTargetTest extends TestBase
|
||||
{
|
||||
private array $originalEnv;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Save original environment variables
|
||||
$this->originalEnv = [
|
||||
'SPC_TARGET' => getenv('SPC_TARGET'),
|
||||
'SPC_LIBC' => getenv('SPC_LIBC'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Restore original environment variables
|
||||
foreach ($this->originalEnv as $key => $value) {
|
||||
if ($value === false) {
|
||||
putenv($key);
|
||||
} else {
|
||||
putenv("{$key}={$value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider libcProvider
|
||||
*/
|
||||
public function testGetLibc(string $libc, bool $expected): void
|
||||
{
|
||||
putenv("SPC_LIBC={$libc}");
|
||||
|
||||
$result = SPCTarget::getLibc();
|
||||
if ($libc === '') {
|
||||
// When SPC_LIBC is set to empty string, getenv returns empty string, not false
|
||||
$this->assertEquals('', $result);
|
||||
} else {
|
||||
$this->assertEquals($libc, $result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider libcProvider
|
||||
*/
|
||||
public function testGetLibcVersion(string $libc): void
|
||||
{
|
||||
putenv("SPC_LIBC={$libc}");
|
||||
|
||||
$result = SPCTarget::getLibcVersion();
|
||||
// The actual result depends on the system, but it could be null if libc is not available
|
||||
$this->assertIsStringOrNull($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider targetOSProvider
|
||||
*/
|
||||
public function testGetTargetOS(string $target, string $expected): void
|
||||
{
|
||||
putenv("SPC_TARGET={$target}");
|
||||
|
||||
$result = SPCTarget::getTargetOS();
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public function testLibcListConstant(): void
|
||||
{
|
||||
$this->assertIsArray(SPCTarget::LIBC_LIST);
|
||||
$this->assertContains('musl', SPCTarget::LIBC_LIST);
|
||||
$this->assertContains('glibc', SPCTarget::LIBC_LIST);
|
||||
}
|
||||
|
||||
public function libcProvider(): array
|
||||
{
|
||||
return [
|
||||
'musl' => ['musl', true],
|
||||
'glibc' => ['glibc', false],
|
||||
'empty' => ['', false],
|
||||
];
|
||||
}
|
||||
|
||||
public function targetOSProvider(): array
|
||||
{
|
||||
return [
|
||||
'linux-target' => ['native-linux', 'Linux'],
|
||||
'macos-target' => ['native-macos', 'Darwin'],
|
||||
'windows-target' => ['native-windows', 'Windows'],
|
||||
'empty-target' => ['', PHP_OS_FAMILY],
|
||||
];
|
||||
}
|
||||
|
||||
private function assertIsStringOrNull($value): void
|
||||
{
|
||||
$this->assertTrue(is_string($value) || is_null($value), 'Value must be string or null');
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Base test class for util tests with output suppression
|
||||
*/
|
||||
abstract class TestBase extends TestCase
|
||||
{
|
||||
protected $outputBuffer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->suppressOutput();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$this->restoreOutput();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress output during tests
|
||||
*/
|
||||
protected function suppressOutput(): void
|
||||
{
|
||||
// Start output buffering to capture PHP output
|
||||
$this->outputBuffer = ob_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore output after tests
|
||||
*/
|
||||
protected function restoreOutput(): void
|
||||
{
|
||||
// Clean output buffer
|
||||
if ($this->outputBuffer) {
|
||||
ob_end_clean();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a UnixShell instance with debug disabled to suppress logs
|
||||
*/
|
||||
protected function createUnixShell(): \SPC\util\shell\UnixShell
|
||||
{
|
||||
return new \SPC\util\shell\UnixShell(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a WindowsCmd instance with debug disabled to suppress logs
|
||||
*/
|
||||
protected function createWindowsCmd(): \SPC\util\shell\WindowsCmd
|
||||
{
|
||||
return new \SPC\util\shell\WindowsCmd(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test with output suppression
|
||||
*/
|
||||
protected function runWithOutputSuppression(callable $callback)
|
||||
{
|
||||
$this->suppressOutput();
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
$this->restoreOutput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command with output suppression
|
||||
*/
|
||||
protected function execWithSuppression(string $command): array
|
||||
{
|
||||
$this->suppressOutput();
|
||||
try {
|
||||
exec($command, $output, $returnCode);
|
||||
return [$returnCode, $output];
|
||||
} finally {
|
||||
$this->restoreOutput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command with output redirected to /dev/null
|
||||
*/
|
||||
protected function execSilently(string $command): array
|
||||
{
|
||||
$command .= ' 2>/dev/null 1>/dev/null';
|
||||
exec($command, $output, $returnCode);
|
||||
return [$returnCode, $output];
|
||||
}
|
||||
}
|
||||
@ -1,184 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use SPC\exception\EnvironmentException;
|
||||
use SPC\util\shell\UnixShell;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class UnixShellTest extends TestBase
|
||||
{
|
||||
public function testConstructorOnWindows(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Windows') {
|
||||
$this->markTestSkipped('This test is for Windows systems only');
|
||||
}
|
||||
|
||||
$this->expectException(EnvironmentException::class);
|
||||
$this->expectExceptionMessage('Windows cannot use UnixShell');
|
||||
|
||||
new UnixShell();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider envProvider
|
||||
*/
|
||||
public function testSetEnv(array $env): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$result = $shell->setEnv($env);
|
||||
|
||||
$this->assertSame($shell, $result);
|
||||
foreach ($env as $item) {
|
||||
if (trim($item) !== '') {
|
||||
$this->assertStringContainsString($item, $shell->getEnvString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider envProvider
|
||||
*/
|
||||
public function testAppendEnv(array $env): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$shell->setEnv(['CFLAGS' => '-O2']);
|
||||
|
||||
$shell->appendEnv($env);
|
||||
|
||||
$this->assertStringContainsString('-O2', $shell->getEnvString());
|
||||
foreach ($env as $value) {
|
||||
if (trim($value) !== '') {
|
||||
$this->assertStringContainsString($value, $shell->getEnvString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider envProvider
|
||||
*/
|
||||
public function testGetEnvString(array $env): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$shell->setEnv($env);
|
||||
|
||||
$envString = $shell->getEnvString();
|
||||
|
||||
$hasNonEmptyValues = false;
|
||||
foreach ($env as $key => $value) {
|
||||
if (trim($value) !== '') {
|
||||
$this->assertStringContainsString("{$key}=\"{$value}\"", $envString);
|
||||
$hasNonEmptyValues = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If all values are empty, ensure we still have a test assertion
|
||||
if (!$hasNonEmptyValues) {
|
||||
$this->assertIsString($envString);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetEnvStringWithEmptyEnv(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$envString = $shell->getEnvString();
|
||||
|
||||
$this->assertEquals('', trim($envString));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider commandProvider
|
||||
*/
|
||||
public function testExecWithResult(string $command): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
[$code, $output] = $shell->execWithResult($command);
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertIsArray($output);
|
||||
}
|
||||
|
||||
public function testExecWithResultWithLog(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
[$code, $output] = $shell->execWithResult('echo "test"', false);
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertIsArray($output);
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertEquals(['test'], $output);
|
||||
}
|
||||
|
||||
public function testExecWithResultWithCd(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$shell->cd('/tmp');
|
||||
|
||||
[$code, $output] = $shell->execWithResult('pwd');
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertIsArray($output);
|
||||
}
|
||||
|
||||
public static function directoryProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple-directory' => ['/test/directory'],
|
||||
'home-directory' => ['/home/user'],
|
||||
'root-directory' => ['/root'],
|
||||
'tmp-directory' => ['/tmp'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function envProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple-env' => [['CFLAGS' => '-O2', 'LDFLAGS' => '-L/usr/lib']],
|
||||
'complex-env' => [['CXXFLAGS' => '-std=c++11', 'LIBS' => '-lz -lxml']],
|
||||
'empty-env' => [['CFLAGS' => '', 'LDFLAGS' => ' ']],
|
||||
'mixed-env' => [['CFLAGS' => '-O2', 'EMPTY_VAR' => '']],
|
||||
];
|
||||
}
|
||||
|
||||
public static function commandProvider(): array
|
||||
{
|
||||
return [
|
||||
'echo-command' => ['echo "test"'],
|
||||
'pwd-command' => ['pwd'],
|
||||
'ls-command' => ['ls -la'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use SPC\exception\SPCInternalException;
|
||||
use SPC\util\shell\WindowsCmd;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class WindowsCmdTest extends TestBase
|
||||
{
|
||||
public function testConstructorOnUnix(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$this->expectException(SPCInternalException::class);
|
||||
$this->expectExceptionMessage('Only windows can use WindowsCmd');
|
||||
|
||||
new WindowsCmd();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider commandProvider
|
||||
*/
|
||||
public function testExecWithResult(string $command): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Windows') {
|
||||
$this->markTestSkipped('This test is for Windows systems only');
|
||||
}
|
||||
|
||||
$cmd = $this->createWindowsCmd();
|
||||
[$code, $output] = $cmd->execWithResult($command);
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertIsArray($output);
|
||||
$this->assertNotEmpty($output);
|
||||
}
|
||||
|
||||
public function testExecWithResultWithLog(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Windows') {
|
||||
$this->markTestSkipped('This test is for Windows systems only');
|
||||
}
|
||||
|
||||
$cmd = $this->createWindowsCmd();
|
||||
[$code, $output] = $cmd->execWithResult('echo test', false);
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertIsArray($output);
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertEquals(['test'], $output);
|
||||
}
|
||||
|
||||
public static function commandProvider(): array
|
||||
{
|
||||
return [
|
||||
'echo-command' => ['echo test'],
|
||||
'dir-command' => ['dir'],
|
||||
'cd-command' => ['cd'],
|
||||
];
|
||||
}
|
||||
}
|
||||
303
tests/StaticPHP/Config/ArtifactConfigTest.php
Normal file
303
tests/StaticPHP/Config/ArtifactConfigTest.php
Normal file
@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\Config;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StaticPHP\Config\ArtifactConfig;
|
||||
use StaticPHP\Exception\WrongUsageException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ArtifactConfigTest extends TestCase
|
||||
{
|
||||
private string $tempDir;
|
||||
|
||||
/** @noinspection PhpExpressionResultUnusedInspection */
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->tempDir = sys_get_temp_dir() . '/artifact_config_test_' . uniqid();
|
||||
mkdir($this->tempDir, 0755, true);
|
||||
|
||||
// Reset static state
|
||||
$reflection = new \ReflectionClass(ArtifactConfig::class);
|
||||
$property = $reflection->getProperty('artifact_configs');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue([]);
|
||||
}
|
||||
|
||||
/** @noinspection PhpExpressionResultUnusedInspection */
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
// Clean up temp directory
|
||||
if (is_dir($this->tempDir)) {
|
||||
$this->removeDirectory($this->tempDir);
|
||||
}
|
||||
|
||||
// Reset static state
|
||||
$reflection = new \ReflectionClass(ArtifactConfig::class);
|
||||
$property = $reflection->getProperty('artifact_configs');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue([]);
|
||||
}
|
||||
|
||||
public function testLoadFromDirThrowsExceptionWhenDirectoryDoesNotExist(): void
|
||||
{
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->expectExceptionMessage('Directory /nonexistent/path does not exist, cannot load artifact config.');
|
||||
|
||||
ArtifactConfig::loadFromDir('/nonexistent/path');
|
||||
}
|
||||
|
||||
public function testLoadFromDirWithValidArtifactJson(): void
|
||||
{
|
||||
$artifactContent = json_encode([
|
||||
'test-artifact' => [
|
||||
'source' => 'https://example.com/file.tar.gz',
|
||||
],
|
||||
]);
|
||||
|
||||
file_put_contents($this->tempDir . '/artifact.json', $artifactContent);
|
||||
|
||||
ArtifactConfig::loadFromDir($this->tempDir);
|
||||
|
||||
$config = ArtifactConfig::get('test-artifact');
|
||||
$this->assertIsArray($config);
|
||||
$this->assertArrayHasKey('source', $config);
|
||||
}
|
||||
|
||||
public function testLoadFromDirWithMultipleArtifactFiles(): void
|
||||
{
|
||||
$artifact1Content = json_encode([
|
||||
'artifact-1' => [
|
||||
'source' => 'https://example.com/file1.tar.gz',
|
||||
],
|
||||
]);
|
||||
|
||||
$artifact2Content = json_encode([
|
||||
'artifact-2' => [
|
||||
'source' => 'https://example.com/file2.tar.gz',
|
||||
],
|
||||
]);
|
||||
|
||||
file_put_contents($this->tempDir . '/artifact.ext.json', $artifact1Content);
|
||||
file_put_contents($this->tempDir . '/artifact.lib.json', $artifact2Content);
|
||||
file_put_contents($this->tempDir . '/artifact.json', json_encode(['artifact-3' => ['source' => 'custom']]));
|
||||
|
||||
ArtifactConfig::loadFromDir($this->tempDir);
|
||||
|
||||
$this->assertNotNull(ArtifactConfig::get('artifact-1'));
|
||||
$this->assertNotNull(ArtifactConfig::get('artifact-2'));
|
||||
$this->assertNotNull(ArtifactConfig::get('artifact-3'));
|
||||
}
|
||||
|
||||
public function testLoadFromFileThrowsExceptionWhenFileCannotBeRead(): void
|
||||
{
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->expectExceptionMessage('Failed to read artifact config file:');
|
||||
|
||||
ArtifactConfig::loadFromFile('/nonexistent/file.json');
|
||||
}
|
||||
|
||||
public function testLoadFromFileThrowsExceptionWhenJsonIsInvalid(): void
|
||||
{
|
||||
$file = $this->tempDir . '/invalid.json';
|
||||
file_put_contents($file, 'not valid json{');
|
||||
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->expectExceptionMessage('Invalid JSON format in artifact config file:');
|
||||
|
||||
ArtifactConfig::loadFromFile($file);
|
||||
}
|
||||
|
||||
public function testLoadFromFileWithValidJson(): void
|
||||
{
|
||||
$file = $this->tempDir . '/valid.json';
|
||||
$content = json_encode([
|
||||
'my-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com/file.tar.gz',
|
||||
],
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
ArtifactConfig::loadFromFile($file);
|
||||
|
||||
$config = ArtifactConfig::get('my-artifact');
|
||||
$this->assertIsArray($config);
|
||||
$this->assertArrayHasKey('source', $config);
|
||||
}
|
||||
|
||||
public function testGetAllReturnsAllLoadedArtifacts(): void
|
||||
{
|
||||
$file = $this->tempDir . '/artifacts.json';
|
||||
$content = json_encode([
|
||||
'artifact-a' => ['source' => 'custom'],
|
||||
'artifact-b' => ['source' => 'custom'],
|
||||
'artifact-c' => ['source' => 'custom'],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
ArtifactConfig::loadFromFile($file);
|
||||
|
||||
$all = ArtifactConfig::getAll();
|
||||
$this->assertIsArray($all);
|
||||
$this->assertCount(3, $all);
|
||||
$this->assertArrayHasKey('artifact-a', $all);
|
||||
$this->assertArrayHasKey('artifact-b', $all);
|
||||
$this->assertArrayHasKey('artifact-c', $all);
|
||||
}
|
||||
|
||||
public function testGetReturnsNullWhenArtifactNotFound(): void
|
||||
{
|
||||
$this->assertNull(ArtifactConfig::get('non-existent-artifact'));
|
||||
}
|
||||
|
||||
public function testGetReturnsConfigWhenArtifactExists(): void
|
||||
{
|
||||
$file = $this->tempDir . '/artifacts.json';
|
||||
$content = json_encode([
|
||||
'test-artifact' => [
|
||||
'source' => 'custom',
|
||||
'binary' => 'custom',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
ArtifactConfig::loadFromFile($file);
|
||||
|
||||
$config = ArtifactConfig::get('test-artifact');
|
||||
$this->assertIsArray($config);
|
||||
$this->assertEquals('custom', $config['source']);
|
||||
$this->assertIsArray($config['binary']);
|
||||
}
|
||||
|
||||
public function testLoadFromFileWithExpandedUrlInSource(): void
|
||||
{
|
||||
$file = $this->tempDir . '/artifacts.json';
|
||||
$content = json_encode([
|
||||
'test-artifact' => [
|
||||
'source' => 'https://example.com/archive.tar.gz',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
ArtifactConfig::loadFromFile($file);
|
||||
|
||||
$config = ArtifactConfig::get('test-artifact');
|
||||
$this->assertIsArray($config);
|
||||
$this->assertIsArray($config['source']);
|
||||
$this->assertEquals('url', $config['source']['type']);
|
||||
$this->assertEquals('https://example.com/archive.tar.gz', $config['source']['url']);
|
||||
}
|
||||
|
||||
public function testLoadFromFileWithBinaryCustom(): void
|
||||
{
|
||||
$file = $this->tempDir . '/artifacts.json';
|
||||
$content = json_encode([
|
||||
'test-artifact' => [
|
||||
'source' => 'custom',
|
||||
'binary' => 'custom',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
ArtifactConfig::loadFromFile($file);
|
||||
|
||||
$config = ArtifactConfig::get('test-artifact');
|
||||
$this->assertIsArray($config['binary']);
|
||||
$this->assertArrayHasKey('linux-x86_64', $config['binary']);
|
||||
$this->assertArrayHasKey('macos-aarch64', $config['binary']);
|
||||
$this->assertEquals('custom', $config['binary']['linux-x86_64']['type']);
|
||||
}
|
||||
|
||||
public function testLoadFromFileWithBinaryHosted(): void
|
||||
{
|
||||
$file = $this->tempDir . '/artifacts.json';
|
||||
$content = json_encode([
|
||||
'test-artifact' => [
|
||||
'source' => 'custom',
|
||||
'binary' => 'hosted',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
ArtifactConfig::loadFromFile($file);
|
||||
|
||||
$config = ArtifactConfig::get('test-artifact');
|
||||
$this->assertIsArray($config['binary']);
|
||||
$this->assertEquals('hosted', $config['binary']['linux-x86_64']['type']);
|
||||
$this->assertEquals('hosted', $config['binary']['macos-aarch64']['type']);
|
||||
}
|
||||
|
||||
public function testLoadFromFileWithBinaryPlatformSpecific(): void
|
||||
{
|
||||
$file = $this->tempDir . '/artifacts.json';
|
||||
$content = json_encode([
|
||||
'test-artifact' => [
|
||||
'source' => 'custom',
|
||||
'binary' => [
|
||||
'linux-x86_64' => 'https://example.com/linux.tar.gz',
|
||||
'macos-aarch64' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com/macos.tar.gz',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
ArtifactConfig::loadFromFile($file);
|
||||
|
||||
$config = ArtifactConfig::get('test-artifact');
|
||||
$this->assertIsArray($config['binary']);
|
||||
$this->assertEquals('url', $config['binary']['linux-x86_64']['type']);
|
||||
$this->assertEquals('https://example.com/linux.tar.gz', $config['binary']['linux-x86_64']['url']);
|
||||
$this->assertEquals('url', $config['binary']['macos-aarch64']['type']);
|
||||
$this->assertEquals('https://example.com/macos.tar.gz', $config['binary']['macos-aarch64']['url']);
|
||||
}
|
||||
|
||||
public function testLoadFromDirWithEmptyDirectory(): void
|
||||
{
|
||||
// Empty directory should not throw exception
|
||||
ArtifactConfig::loadFromDir($this->tempDir);
|
||||
|
||||
$this->assertEquals([], ArtifactConfig::getAll());
|
||||
}
|
||||
|
||||
public function testMultipleLoadsAppendConfigs(): void
|
||||
{
|
||||
$file1 = $this->tempDir . '/artifact1.json';
|
||||
$file2 = $this->tempDir . '/artifact2.json';
|
||||
|
||||
file_put_contents($file1, json_encode(['art1' => ['source' => 'custom']]));
|
||||
file_put_contents($file2, json_encode(['art2' => ['source' => 'custom']]));
|
||||
|
||||
ArtifactConfig::loadFromFile($file1);
|
||||
ArtifactConfig::loadFromFile($file2);
|
||||
|
||||
$all = ArtifactConfig::getAll();
|
||||
$this->assertCount(2, $all);
|
||||
$this->assertArrayHasKey('art1', $all);
|
||||
$this->assertArrayHasKey('art2', $all);
|
||||
}
|
||||
|
||||
private function removeDirectory(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
$files = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$path = $dir . '/' . $file;
|
||||
is_dir($path) ? $this->removeDirectory($path) : unlink($path);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
196
tests/StaticPHP/Config/ConfigTypeTest.php
Normal file
196
tests/StaticPHP/Config/ConfigTypeTest.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\Config;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StaticPHP\Config\ConfigType;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ConfigTypeTest extends TestCase
|
||||
{
|
||||
public function testConstantValues(): void
|
||||
{
|
||||
$this->assertEquals('list_array', ConfigType::LIST_ARRAY);
|
||||
$this->assertEquals('assoc_array', ConfigType::ASSOC_ARRAY);
|
||||
$this->assertEquals('string', ConfigType::STRING);
|
||||
$this->assertEquals('bool', ConfigType::BOOL);
|
||||
}
|
||||
|
||||
public function testPackageTypesConstant(): void
|
||||
{
|
||||
$expectedTypes = [
|
||||
'library',
|
||||
'php-extension',
|
||||
'target',
|
||||
'virtual-target',
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedTypes, ConfigType::PACKAGE_TYPES);
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldWithValidFileType(): void
|
||||
{
|
||||
$license = [
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
];
|
||||
|
||||
$this->assertTrue(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldWithValidFileTypeArrayPath(): void
|
||||
{
|
||||
$license = [
|
||||
'type' => 'file',
|
||||
'path' => ['LICENSE', 'COPYING'],
|
||||
];
|
||||
|
||||
$this->assertTrue(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldWithValidTextType(): void
|
||||
{
|
||||
$license = [
|
||||
'type' => 'text',
|
||||
'text' => 'MIT License',
|
||||
];
|
||||
|
||||
$this->assertTrue(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldWithListOfLicenses(): void
|
||||
{
|
||||
$licenses = [
|
||||
[
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'text' => 'MIT',
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertTrue(ConfigType::validateLicenseField($licenses));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldWithEmptyList(): void
|
||||
{
|
||||
$licenses = [];
|
||||
|
||||
$this->assertTrue(ConfigType::validateLicenseField($licenses));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldReturnsFalseWhenNotAssocArray(): void
|
||||
{
|
||||
$this->assertFalse(ConfigType::validateLicenseField('string'));
|
||||
$this->assertFalse(ConfigType::validateLicenseField(123));
|
||||
$this->assertFalse(ConfigType::validateLicenseField(true));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldReturnsFalseWhenMissingType(): void
|
||||
{
|
||||
$license = [
|
||||
'path' => 'LICENSE',
|
||||
];
|
||||
|
||||
$this->assertFalse(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldReturnsFalseWithInvalidType(): void
|
||||
{
|
||||
$license = [
|
||||
'type' => 'invalid',
|
||||
'data' => 'something',
|
||||
];
|
||||
|
||||
$this->assertFalse(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldReturnsFalseWhenFileTypeMissingPath(): void
|
||||
{
|
||||
$license = [
|
||||
'type' => 'file',
|
||||
];
|
||||
|
||||
$this->assertFalse(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldReturnsFalseWhenFileTypePathIsInvalid(): void
|
||||
{
|
||||
$license = [
|
||||
'type' => 'file',
|
||||
'path' => 123,
|
||||
];
|
||||
|
||||
$this->assertFalse(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldReturnsFalseWhenTextTypeMissingText(): void
|
||||
{
|
||||
$license = [
|
||||
'type' => 'text',
|
||||
];
|
||||
|
||||
$this->assertFalse(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldReturnsFalseWhenTextTypeTextIsNotString(): void
|
||||
{
|
||||
$license = [
|
||||
'type' => 'text',
|
||||
'text' => ['array'],
|
||||
];
|
||||
|
||||
$this->assertFalse(ConfigType::validateLicenseField($license));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldWithListContainingInvalidItem(): void
|
||||
{
|
||||
$licenses = [
|
||||
[
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
// missing 'text' field
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertFalse(ConfigType::validateLicenseField($licenses));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldWithNestedListsOfLicenses(): void
|
||||
{
|
||||
$licenses = [
|
||||
[
|
||||
[
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertTrue(ConfigType::validateLicenseField($licenses));
|
||||
}
|
||||
|
||||
public function testValidateLicenseFieldWithNestedListContainingInvalidItem(): void
|
||||
{
|
||||
$licenses = [
|
||||
[
|
||||
[
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
],
|
||||
'invalid-string-item',
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertFalse(ConfigType::validateLicenseField($licenses));
|
||||
}
|
||||
}
|
||||
627
tests/StaticPHP/Config/ConfigValidatorTest.php
Normal file
627
tests/StaticPHP/Config/ConfigValidatorTest.php
Normal file
@ -0,0 +1,627 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\Config;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StaticPHP\Config\ConfigValidator;
|
||||
use StaticPHP\Exception\ValidationException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ConfigValidatorTest extends TestCase
|
||||
{
|
||||
public function testValidateAndLintArtifactsThrowsExceptionWhenDataIsNotArray(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('test.json is broken');
|
||||
|
||||
$data = 'not an array';
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsWithCustomSource(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'source' => 'custom',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertEquals('custom', $data['test-artifact']['source']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsExpandsUrlString(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'source' => 'https://example.com/file.tar.gz',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-artifact']['source']);
|
||||
$this->assertEquals('url', $data['test-artifact']['source']['type']);
|
||||
$this->assertEquals('https://example.com/file.tar.gz', $data['test-artifact']['source']['url']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsExpandsHttpUrlString(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'source' => 'http://example.com/file.tar.gz',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-artifact']['source']);
|
||||
$this->assertEquals('url', $data['test-artifact']['source']['type']);
|
||||
$this->assertEquals('http://example.com/file.tar.gz', $data['test-artifact']['source']['url']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsWithSourceObject(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'git',
|
||||
'url' => 'https://github.com/example/repo.git',
|
||||
'rev' => 'main',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-artifact']['source']);
|
||||
$this->assertEquals('git', $data['test-artifact']['source']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsWithBinaryCustom(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'binary' => 'custom',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-artifact']['binary']);
|
||||
$this->assertArrayHasKey('linux-x86_64', $data['test-artifact']['binary']);
|
||||
$this->assertEquals('custom', $data['test-artifact']['binary']['linux-x86_64']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsWithBinaryHosted(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'binary' => 'hosted',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-artifact']['binary']);
|
||||
$this->assertArrayHasKey('macos-aarch64', $data['test-artifact']['binary']);
|
||||
$this->assertEquals('hosted', $data['test-artifact']['binary']['macos-aarch64']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsWithBinaryPlatformObject(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'binary' => [
|
||||
'linux-x86_64' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com/binary.tar.gz',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertEquals('url', $data['test-artifact']['binary']['linux-x86_64']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsExpandsBinaryPlatformUrlString(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'binary' => [
|
||||
'linux-x86_64' => 'https://example.com/binary.tar.gz',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-artifact']['binary']['linux-x86_64']);
|
||||
$this->assertEquals('url', $data['test-artifact']['binary']['linux-x86_64']['type']);
|
||||
$this->assertEquals('https://example.com/binary.tar.gz', $data['test-artifact']['binary']['linux-x86_64']['url']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsWithSourceMirror(): void
|
||||
{
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'source-mirror' => 'https://mirror.example.com/file.tar.gz',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-artifact']['source-mirror']);
|
||||
$this->assertEquals('url', $data['test-artifact']['source-mirror']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionWhenDataIsNotArray(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('pkg.json is broken');
|
||||
|
||||
$data = 'not an array';
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionWhenPackageIsNotAssocArray(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('Package [test-pkg] in pkg.json is not a valid associative array');
|
||||
|
||||
$data = [
|
||||
'test-pkg' => ['list', 'array'],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionWhenTypeMissing(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Package [test-pkg] in pkg.json has invalid or missing 'type' field");
|
||||
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'depends' => [],
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionWhenTypeInvalid(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Package [test-pkg] in pkg.json has invalid or missing 'type' field");
|
||||
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'invalid-type',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesWithValidLibraryType(): void
|
||||
{
|
||||
$data = [
|
||||
'test-lib' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test-artifact',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
|
||||
$this->assertEquals('library', $data['test-lib']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesWithValidPhpExtensionType(): void
|
||||
{
|
||||
$data = [
|
||||
'test-ext' => [
|
||||
'type' => 'php-extension',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
|
||||
$this->assertEquals('php-extension', $data['test-ext']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesWithValidTargetType(): void
|
||||
{
|
||||
$data = [
|
||||
'test-target' => [
|
||||
'type' => 'target',
|
||||
'artifact' => 'test-artifact',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
|
||||
$this->assertEquals('target', $data['test-target']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesWithValidVirtualTargetType(): void
|
||||
{
|
||||
$data = [
|
||||
'test-virtual' => [
|
||||
'type' => 'virtual-target',
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
|
||||
$this->assertEquals('virtual-target', $data['test-virtual']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionWhenLibraryMissingArtifact(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Package [test-lib] in pkg.json of type 'library' must have an 'artifact' field");
|
||||
|
||||
$data = [
|
||||
'test-lib' => [
|
||||
'type' => 'library',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionWhenTargetMissingArtifact(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Package [test-target] in pkg.json of type 'target' must have an 'artifact' field");
|
||||
|
||||
$data = [
|
||||
'test-target' => [
|
||||
'type' => 'target',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesWithPhpExtensionFields(): void
|
||||
{
|
||||
$data = [
|
||||
'test-ext' => [
|
||||
'type' => 'php-extension',
|
||||
'php-extension' => [
|
||||
'zend-extension' => false,
|
||||
'build-shared' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-ext']['php-extension']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionWhenPhpExtensionIsNotObject(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('Package test-ext [php-extension] must be an object');
|
||||
|
||||
$data = [
|
||||
'test-ext' => [
|
||||
'type' => 'php-extension',
|
||||
'php-extension' => 'string',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesWithDependsField(): void
|
||||
{
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'depends' => ['dep1', 'dep2'],
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-pkg']['depends']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionWhenDependsIsNotList(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('Package test-pkg [depends] must be a list');
|
||||
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'depends' => 'not-a-list',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesWithSuffixFields(): void
|
||||
{
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'depends@linux' => ['linux-dep'],
|
||||
'depends@windows' => ['windows-dep'],
|
||||
'headers@unix' => ['header.h'],
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
|
||||
$this->assertIsArray($data['test-pkg']['depends@linux']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionForInvalidSuffixFieldType(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('Package test-pkg [headers@linux] must be a list');
|
||||
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'headers@linux' => 'not-a-list',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionForUnknownField(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('package [test-pkg] has invalid field [unknown-field]');
|
||||
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'unknown-field' => 'value',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionForUnknownPhpExtensionField(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('php-extension [test-ext] has invalid field [unknown]');
|
||||
|
||||
$data = [
|
||||
'test-ext' => [
|
||||
'type' => 'php-extension',
|
||||
'php-extension' => [
|
||||
'unknown' => 'value',
|
||||
],
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidatePlatformStringWithValidPlatforms(): void
|
||||
{
|
||||
ConfigValidator::validatePlatformString('linux-x86_64');
|
||||
ConfigValidator::validatePlatformString('linux-aarch64');
|
||||
ConfigValidator::validatePlatformString('windows-x86_64');
|
||||
ConfigValidator::validatePlatformString('windows-aarch64');
|
||||
ConfigValidator::validatePlatformString('macos-x86_64');
|
||||
ConfigValidator::validatePlatformString('macos-aarch64');
|
||||
|
||||
$this->assertTrue(true); // If no exception thrown, test passes
|
||||
}
|
||||
|
||||
public function testValidatePlatformStringThrowsExceptionForInvalidFormat(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Invalid platform format 'invalid', expected format 'os-arch'");
|
||||
|
||||
ConfigValidator::validatePlatformString('invalid');
|
||||
}
|
||||
|
||||
public function testValidatePlatformStringThrowsExceptionForTooManyParts(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Invalid platform format 'linux-x86_64-extra', expected format 'os-arch'");
|
||||
|
||||
ConfigValidator::validatePlatformString('linux-x86_64-extra');
|
||||
}
|
||||
|
||||
public function testValidatePlatformStringThrowsExceptionForInvalidOS(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Invalid platform OS 'bsd' in platform 'bsd-x86_64'");
|
||||
|
||||
ConfigValidator::validatePlatformString('bsd-x86_64');
|
||||
}
|
||||
|
||||
public function testValidatePlatformStringThrowsExceptionForInvalidArch(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Invalid platform architecture 'arm' in platform 'linux-arm'");
|
||||
|
||||
ConfigValidator::validatePlatformString('linux-arm');
|
||||
}
|
||||
|
||||
public function testArtifactTypeFieldsConstant(): void
|
||||
{
|
||||
$this->assertArrayHasKey('filelist', ConfigValidator::ARTIFACT_TYPE_FIELDS);
|
||||
$this->assertArrayHasKey('git', ConfigValidator::ARTIFACT_TYPE_FIELDS);
|
||||
$this->assertArrayHasKey('ghtagtar', ConfigValidator::ARTIFACT_TYPE_FIELDS);
|
||||
$this->assertArrayHasKey('url', ConfigValidator::ARTIFACT_TYPE_FIELDS);
|
||||
$this->assertArrayHasKey('custom', ConfigValidator::ARTIFACT_TYPE_FIELDS);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsThrowsExceptionForInvalidArtifactType(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact source object has unknown type 'invalid-type'");
|
||||
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'invalid-type',
|
||||
],
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsThrowsExceptionForMissingRequiredField(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact source object of type 'git' must have required field 'url'");
|
||||
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'git',
|
||||
'rev' => 'main',
|
||||
// missing 'url'
|
||||
],
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsThrowsExceptionForMissingTypeInSource(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage("Artifact source object must have a valid 'type' field");
|
||||
|
||||
$data = [
|
||||
'test-artifact' => [
|
||||
'source' => [
|
||||
'url' => 'https://example.com',
|
||||
],
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintArtifactsWithAllArtifactTypes(): void
|
||||
{
|
||||
$data = [
|
||||
'filelist-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'filelist',
|
||||
'url' => 'https://example.com/list',
|
||||
'regex' => '/pattern/',
|
||||
],
|
||||
],
|
||||
'git-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'git',
|
||||
'url' => 'https://github.com/example/repo.git',
|
||||
'rev' => 'main',
|
||||
],
|
||||
],
|
||||
'ghtagtar-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'ghtagtar',
|
||||
'repo' => 'example/repo',
|
||||
],
|
||||
],
|
||||
'url-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com/file.tar.gz',
|
||||
],
|
||||
],
|
||||
'custom-artifact' => [
|
||||
'source' => [
|
||||
'type' => 'custom',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintArtifacts('test.json', $data);
|
||||
|
||||
$this->assertIsArray($data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesWithAllFieldTypes(): void
|
||||
{
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test-artifact',
|
||||
'depends' => ['dep1'],
|
||||
'suggests' => ['sug1'],
|
||||
'license' => [
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
],
|
||||
'lang' => 'c',
|
||||
'frameworks' => ['framework1'],
|
||||
'headers' => ['header.h'],
|
||||
'static-libs' => ['lib.a'],
|
||||
'pkg-configs' => ['pkg.pc'],
|
||||
'static-bins' => ['bin'],
|
||||
],
|
||||
];
|
||||
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
|
||||
$this->assertEquals('library', $data['test-pkg']['type']);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionForWrongTypeString(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('Package test-pkg [artifact] must be string');
|
||||
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => ['not', 'a', 'string'],
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionForWrongTypeBool(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('Package test-ext [zend-extension] must be boolean');
|
||||
|
||||
$data = [
|
||||
'test-ext' => [
|
||||
'type' => 'php-extension',
|
||||
'php-extension' => [
|
||||
'zend-extension' => 'not-a-bool',
|
||||
],
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
|
||||
public function testValidateAndLintPackagesThrowsExceptionForWrongTypeAssocArray(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage('Package test-pkg [support] must be an object');
|
||||
|
||||
$data = [
|
||||
'test-pkg' => [
|
||||
'type' => 'php-extension',
|
||||
'php-extension' => [
|
||||
'support' => 'not-an-object',
|
||||
],
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateAndLintPackages('pkg.json', $data);
|
||||
}
|
||||
}
|
||||
434
tests/StaticPHP/Config/PackageConfigTest.php
Normal file
434
tests/StaticPHP/Config/PackageConfigTest.php
Normal file
@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\Config;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StaticPHP\Config\PackageConfig;
|
||||
use StaticPHP\Exception\WrongUsageException;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PackageConfigTest extends TestCase
|
||||
{
|
||||
private string $tempDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->tempDir = sys_get_temp_dir() . '/package_config_test_' . uniqid();
|
||||
mkdir($this->tempDir, 0755, true);
|
||||
|
||||
// Reset static state
|
||||
$reflection = new \ReflectionClass(PackageConfig::class);
|
||||
$property = $reflection->getProperty('package_configs');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue([]);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
// Clean up temp directory
|
||||
if (is_dir($this->tempDir)) {
|
||||
$this->removeDirectory($this->tempDir);
|
||||
}
|
||||
|
||||
// Reset static state
|
||||
$reflection = new \ReflectionClass(PackageConfig::class);
|
||||
$property = $reflection->getProperty('package_configs');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue([]);
|
||||
}
|
||||
|
||||
public function testLoadFromDirThrowsExceptionWhenDirectoryDoesNotExist(): void
|
||||
{
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->expectExceptionMessage('Directory /nonexistent/path does not exist, cannot load pkg.json config.');
|
||||
|
||||
PackageConfig::loadFromDir('/nonexistent/path');
|
||||
}
|
||||
|
||||
public function testLoadFromDirWithValidPkgJson(): void
|
||||
{
|
||||
$packageContent = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test-artifact',
|
||||
],
|
||||
]);
|
||||
|
||||
file_put_contents($this->tempDir . '/pkg.json', $packageContent);
|
||||
|
||||
PackageConfig::loadFromDir($this->tempDir);
|
||||
|
||||
$this->assertTrue(PackageConfig::isPackageExists('test-pkg'));
|
||||
}
|
||||
|
||||
public function testLoadFromDirWithMultiplePackageFiles(): void
|
||||
{
|
||||
$pkg1Content = json_encode([
|
||||
'pkg-1' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'artifact-1',
|
||||
],
|
||||
]);
|
||||
|
||||
$pkg2Content = json_encode([
|
||||
'pkg-2' => [
|
||||
'type' => 'php-extension',
|
||||
],
|
||||
]);
|
||||
|
||||
file_put_contents($this->tempDir . '/pkg.ext.json', $pkg1Content);
|
||||
file_put_contents($this->tempDir . '/pkg.lib.json', $pkg2Content);
|
||||
file_put_contents($this->tempDir . '/pkg.json', json_encode(['pkg-3' => ['type' => 'virtual-target']]));
|
||||
|
||||
PackageConfig::loadFromDir($this->tempDir);
|
||||
|
||||
$this->assertTrue(PackageConfig::isPackageExists('pkg-1'));
|
||||
$this->assertTrue(PackageConfig::isPackageExists('pkg-2'));
|
||||
$this->assertTrue(PackageConfig::isPackageExists('pkg-3'));
|
||||
}
|
||||
|
||||
public function testLoadFromFileThrowsExceptionWhenFileCannotBeRead(): void
|
||||
{
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->expectExceptionMessage('Failed to read package config file:');
|
||||
|
||||
PackageConfig::loadFromFile('/nonexistent/file.json');
|
||||
}
|
||||
|
||||
public function testLoadFromFileThrowsExceptionWhenJsonIsInvalid(): void
|
||||
{
|
||||
$file = $this->tempDir . '/invalid.json';
|
||||
file_put_contents($file, 'not valid json{');
|
||||
|
||||
$this->expectException(WrongUsageException::class);
|
||||
$this->expectExceptionMessage('Invalid JSON format in package config file:');
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
}
|
||||
|
||||
public function testLoadFromFileWithValidJson(): void
|
||||
{
|
||||
$file = $this->tempDir . '/valid.json';
|
||||
$content = json_encode([
|
||||
'my-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'my-artifact',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$this->assertTrue(PackageConfig::isPackageExists('my-pkg'));
|
||||
}
|
||||
|
||||
public function testIsPackageExistsReturnsFalseWhenPackageNotLoaded(): void
|
||||
{
|
||||
$this->assertFalse(PackageConfig::isPackageExists('non-existent'));
|
||||
}
|
||||
|
||||
public function testIsPackageExistsReturnsTrueWhenPackageLoaded(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$this->assertTrue(PackageConfig::isPackageExists('test-pkg'));
|
||||
}
|
||||
|
||||
public function testGetAllReturnsAllLoadedPackages(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'pkg-a' => ['type' => 'virtual-target'],
|
||||
'pkg-b' => ['type' => 'virtual-target'],
|
||||
'pkg-c' => ['type' => 'virtual-target'],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$all = PackageConfig::getAll();
|
||||
$this->assertIsArray($all);
|
||||
$this->assertCount(3, $all);
|
||||
$this->assertArrayHasKey('pkg-a', $all);
|
||||
$this->assertArrayHasKey('pkg-b', $all);
|
||||
$this->assertArrayHasKey('pkg-c', $all);
|
||||
}
|
||||
|
||||
public function testGetReturnsDefaultWhenPackageNotExists(): void
|
||||
{
|
||||
$result = PackageConfig::get('non-existent', 'field', 'default-value');
|
||||
|
||||
$this->assertEquals('default-value', $result);
|
||||
}
|
||||
|
||||
public function testGetReturnsWholePackageWhenFieldNameIsNull(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'depends' => ['dep1'],
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$result = PackageConfig::get('test-pkg');
|
||||
$this->assertIsArray($result);
|
||||
$this->assertEquals('library', $result['type']);
|
||||
$this->assertEquals('test', $result['artifact']);
|
||||
}
|
||||
|
||||
public function testGetReturnsFieldValueWhenFieldExists(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test-artifact',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$result = PackageConfig::get('test-pkg', 'artifact');
|
||||
$this->assertEquals('test-artifact', $result);
|
||||
}
|
||||
|
||||
public function testGetReturnsDefaultWhenFieldNotExists(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$result = PackageConfig::get('test-pkg', 'non-existent-field', 'default');
|
||||
$this->assertEquals('default', $result);
|
||||
}
|
||||
|
||||
public function testGetWithSuffixFieldsOnLinux(): void
|
||||
{
|
||||
// Mock SystemTarget to return Linux
|
||||
$mockTarget = $this->getMockBuilder(SystemTarget::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'depends' => ['base-dep'],
|
||||
'depends@linux' => ['linux-dep'],
|
||||
'depends@unix' => ['unix-dep'],
|
||||
'depends@windows' => ['windows-dep'],
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
// The get method will check SystemTarget::getTargetOS()
|
||||
// On real Linux systems, it should return 'depends@linux' first
|
||||
$result = PackageConfig::get('test-pkg', 'depends', []);
|
||||
|
||||
// Result should be one of the suffixed versions or base version
|
||||
$this->assertIsArray($result);
|
||||
}
|
||||
|
||||
public function testGetWithSuffixFieldsReturnsBasicFieldWhenNoSuffixMatch(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'depends' => ['base-dep'],
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$result = PackageConfig::get('test-pkg', 'depends');
|
||||
$this->assertEquals(['base-dep'], $result);
|
||||
}
|
||||
|
||||
public function testGetWithNonSuffixedFieldIgnoresSuffixes(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test-artifact',
|
||||
'artifact@linux' => 'linux-artifact', // This should be ignored
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
// 'artifact' is not in SUFFIX_ALLOWED_FIELDS, so it won't check suffixes
|
||||
$result = PackageConfig::get('test-pkg', 'artifact');
|
||||
$this->assertEquals('test-artifact', $result);
|
||||
}
|
||||
|
||||
public function testGetAllSuffixAllowedFields(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'test',
|
||||
'depends@linux' => ['dep1'],
|
||||
'suggests@macos' => ['sug1'],
|
||||
'headers@unix' => ['header.h'],
|
||||
'static-libs@windows' => ['lib.a'],
|
||||
'static-bins@linux' => ['bin'],
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
// These are all suffix-allowed fields
|
||||
$pkg = PackageConfig::get('test-pkg');
|
||||
$this->assertArrayHasKey('depends@linux', $pkg);
|
||||
$this->assertArrayHasKey('suggests@macos', $pkg);
|
||||
$this->assertArrayHasKey('headers@unix', $pkg);
|
||||
$this->assertArrayHasKey('static-libs@windows', $pkg);
|
||||
$this->assertArrayHasKey('static-bins@linux', $pkg);
|
||||
}
|
||||
|
||||
public function testLoadFromDirWithEmptyDirectory(): void
|
||||
{
|
||||
// Empty directory should not throw exception
|
||||
PackageConfig::loadFromDir($this->tempDir);
|
||||
|
||||
$this->assertEquals([], PackageConfig::getAll());
|
||||
}
|
||||
|
||||
public function testMultipleLoadsAppendConfigs(): void
|
||||
{
|
||||
$file1 = $this->tempDir . '/pkg1.json';
|
||||
$file2 = $this->tempDir . '/pkg2.json';
|
||||
|
||||
file_put_contents($file1, json_encode(['pkg1' => ['type' => 'virtual-target']]));
|
||||
file_put_contents($file2, json_encode(['pkg2' => ['type' => 'virtual-target']]));
|
||||
|
||||
PackageConfig::loadFromFile($file1);
|
||||
PackageConfig::loadFromFile($file2);
|
||||
|
||||
$all = PackageConfig::getAll();
|
||||
$this->assertCount(2, $all);
|
||||
$this->assertArrayHasKey('pkg1', $all);
|
||||
$this->assertArrayHasKey('pkg2', $all);
|
||||
}
|
||||
|
||||
public function testGetWithComplexPhpExtensionPackage(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-ext' => [
|
||||
'type' => 'php-extension',
|
||||
'depends' => ['dep1'],
|
||||
'php-extension' => [
|
||||
'zend-extension' => false,
|
||||
'build-shared' => true,
|
||||
'build-static' => false,
|
||||
],
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$phpExt = PackageConfig::get('test-ext', 'php-extension');
|
||||
$this->assertIsArray($phpExt);
|
||||
$this->assertFalse($phpExt['zend-extension']);
|
||||
$this->assertTrue($phpExt['build-shared']);
|
||||
}
|
||||
|
||||
public function testGetReturnsNullAsDefaultWhenNotSpecified(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'test-pkg' => [
|
||||
'type' => 'virtual-target',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$result = PackageConfig::get('test-pkg', 'non-existent');
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testLoadFromFileWithAllPackageTypes(): void
|
||||
{
|
||||
$file = $this->tempDir . '/pkg.json';
|
||||
$content = json_encode([
|
||||
'library-pkg' => [
|
||||
'type' => 'library',
|
||||
'artifact' => 'lib-artifact',
|
||||
],
|
||||
'extension-pkg' => [
|
||||
'type' => 'php-extension',
|
||||
],
|
||||
'target-pkg' => [
|
||||
'type' => 'target',
|
||||
'artifact' => 'target-artifact',
|
||||
],
|
||||
'virtual-pkg' => [
|
||||
'type' => 'virtual-target',
|
||||
],
|
||||
]);
|
||||
file_put_contents($file, $content);
|
||||
|
||||
PackageConfig::loadFromFile($file);
|
||||
|
||||
$this->assertTrue(PackageConfig::isPackageExists('library-pkg'));
|
||||
$this->assertTrue(PackageConfig::isPackageExists('extension-pkg'));
|
||||
$this->assertTrue(PackageConfig::isPackageExists('target-pkg'));
|
||||
$this->assertTrue(PackageConfig::isPackageExists('virtual-pkg'));
|
||||
}
|
||||
|
||||
private function removeDirectory(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
$files = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$path = $dir . '/' . $file;
|
||||
is_dir($path) ? $this->removeDirectory($path) : unlink($path);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
433
tests/StaticPHP/DI/ApplicationContextTest.php
Normal file
433
tests/StaticPHP/DI/ApplicationContextTest.php
Normal file
@ -0,0 +1,433 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\DI;
|
||||
|
||||
use DI\Container;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\DI\CallbackInvoker;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ApplicationContextTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
// Reset ApplicationContext state before each test
|
||||
ApplicationContext::reset();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
// Reset ApplicationContext state after each test
|
||||
ApplicationContext::reset();
|
||||
}
|
||||
|
||||
public function testInitializeCreatesContainer(): void
|
||||
{
|
||||
$container = ApplicationContext::initialize();
|
||||
|
||||
$this->assertInstanceOf(Container::class, $container);
|
||||
$this->assertSame($container, ApplicationContext::getContainer());
|
||||
}
|
||||
|
||||
public function testInitializeWithDebugMode(): void
|
||||
{
|
||||
ApplicationContext::initialize(['debug' => true]);
|
||||
|
||||
$this->assertTrue(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testInitializeWithoutDebugMode(): void
|
||||
{
|
||||
ApplicationContext::initialize(['debug' => false]);
|
||||
|
||||
$this->assertFalse(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testInitializeWithCustomDefinitions(): void
|
||||
{
|
||||
$customValue = 'test_value';
|
||||
ApplicationContext::initialize([
|
||||
'definitions' => [
|
||||
'test.service' => $customValue,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals($customValue, ApplicationContext::get('test.service'));
|
||||
}
|
||||
|
||||
public function testInitializeThrowsExceptionWhenAlreadyInitialized(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('ApplicationContext already initialized');
|
||||
ApplicationContext::initialize();
|
||||
}
|
||||
|
||||
public function testGetContainerAutoInitializes(): void
|
||||
{
|
||||
// Don't call initialize
|
||||
$container = ApplicationContext::getContainer();
|
||||
|
||||
$this->assertInstanceOf(Container::class, $container);
|
||||
}
|
||||
|
||||
public function testGetReturnsServiceFromContainer(): void
|
||||
{
|
||||
ApplicationContext::initialize([
|
||||
'definitions' => [
|
||||
'test.key' => 'test_value',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals('test_value', ApplicationContext::get('test.key'));
|
||||
}
|
||||
|
||||
public function testGetWithClassType(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$container = ApplicationContext::get(Container::class);
|
||||
$this->assertInstanceOf(Container::class, $container);
|
||||
}
|
||||
|
||||
public function testGetContainerInterface(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$container = ApplicationContext::get(ContainerInterface::class);
|
||||
$this->assertInstanceOf(ContainerInterface::class, $container);
|
||||
}
|
||||
|
||||
public function testHasReturnsTrueForExistingService(): void
|
||||
{
|
||||
ApplicationContext::initialize([
|
||||
'definitions' => [
|
||||
'test.service' => 'value',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertTrue(ApplicationContext::has('test.service'));
|
||||
}
|
||||
|
||||
public function testHasReturnsFalseForNonExistingService(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$this->assertFalse(ApplicationContext::has('non.existing.service'));
|
||||
}
|
||||
|
||||
public function testSetAddsServiceToContainer(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
ApplicationContext::set('dynamic.service', 'dynamic_value');
|
||||
|
||||
$this->assertTrue(ApplicationContext::has('dynamic.service'));
|
||||
$this->assertEquals('dynamic_value', ApplicationContext::get('dynamic.service'));
|
||||
}
|
||||
|
||||
public function testSetOverridesExistingService(): void
|
||||
{
|
||||
ApplicationContext::initialize([
|
||||
'definitions' => [
|
||||
'test.service' => 'original',
|
||||
],
|
||||
]);
|
||||
|
||||
ApplicationContext::set('test.service', 'updated');
|
||||
|
||||
$this->assertEquals('updated', ApplicationContext::get('test.service'));
|
||||
}
|
||||
|
||||
public function testBindCommandContextSetsInputAndOutput(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$output = $this->createMock(OutputInterface::class);
|
||||
$output->method('isDebug')->willReturn(false);
|
||||
|
||||
ApplicationContext::bindCommandContext($input, $output);
|
||||
|
||||
$this->assertSame($input, ApplicationContext::get(InputInterface::class));
|
||||
$this->assertSame($output, ApplicationContext::get(OutputInterface::class));
|
||||
}
|
||||
|
||||
public function testBindCommandContextSetsDebugMode(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$output = $this->createMock(OutputInterface::class);
|
||||
$output->method('isDebug')->willReturn(true);
|
||||
|
||||
ApplicationContext::bindCommandContext($input, $output);
|
||||
|
||||
$this->assertTrue(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testBindCommandContextWithNonDebugOutput(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$output = $this->createMock(OutputInterface::class);
|
||||
$output->method('isDebug')->willReturn(false);
|
||||
|
||||
ApplicationContext::bindCommandContext($input, $output);
|
||||
|
||||
$this->assertFalse(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testGetInvokerReturnsCallbackInvoker(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$invoker = ApplicationContext::getInvoker();
|
||||
|
||||
$this->assertInstanceOf(CallbackInvoker::class, $invoker);
|
||||
}
|
||||
|
||||
public function testGetInvokerReturnsSameInstance(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$invoker1 = ApplicationContext::getInvoker();
|
||||
$invoker2 = ApplicationContext::getInvoker();
|
||||
|
||||
$this->assertSame($invoker1, $invoker2);
|
||||
}
|
||||
|
||||
public function testGetInvokerAutoInitializesContainer(): void
|
||||
{
|
||||
// Don't call initialize
|
||||
$invoker = ApplicationContext::getInvoker();
|
||||
|
||||
$this->assertInstanceOf(CallbackInvoker::class, $invoker);
|
||||
}
|
||||
|
||||
public function testInvokeCallsCallback(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$called = false;
|
||||
$callback = function () use (&$called) {
|
||||
$called = true;
|
||||
return 'result';
|
||||
};
|
||||
|
||||
$result = ApplicationContext::invoke($callback);
|
||||
|
||||
$this->assertTrue($called);
|
||||
$this->assertEquals('result', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithContext(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$callback = function (string $param) {
|
||||
return $param;
|
||||
};
|
||||
|
||||
$result = ApplicationContext::invoke($callback, ['param' => 'test_value']);
|
||||
|
||||
$this->assertEquals('test_value', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithDependencyInjection(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$callback = function (Container $container) {
|
||||
return $container;
|
||||
};
|
||||
|
||||
$result = ApplicationContext::invoke($callback);
|
||||
|
||||
$this->assertInstanceOf(Container::class, $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithArrayCallback(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$object = new class {
|
||||
public function method(): string
|
||||
{
|
||||
return 'called';
|
||||
}
|
||||
};
|
||||
|
||||
$result = ApplicationContext::invoke([$object, 'method']);
|
||||
|
||||
$this->assertEquals('called', $result);
|
||||
}
|
||||
|
||||
public function testIsDebugDefaultsFalse(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$this->assertFalse(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testSetDebugChangesDebugMode(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
ApplicationContext::setDebug(true);
|
||||
$this->assertTrue(ApplicationContext::isDebug());
|
||||
|
||||
ApplicationContext::setDebug(false);
|
||||
$this->assertFalse(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testResetClearsContainer(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
ApplicationContext::set('test.service', 'value');
|
||||
|
||||
ApplicationContext::reset();
|
||||
|
||||
// After reset, container should be reinitialized
|
||||
$this->assertFalse(ApplicationContext::has('test.service'));
|
||||
}
|
||||
|
||||
public function testResetClearsInvoker(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
$invoker1 = ApplicationContext::getInvoker();
|
||||
|
||||
ApplicationContext::reset();
|
||||
|
||||
$invoker2 = ApplicationContext::getInvoker();
|
||||
$this->assertNotSame($invoker1, $invoker2);
|
||||
}
|
||||
|
||||
public function testResetClearsDebugMode(): void
|
||||
{
|
||||
ApplicationContext::initialize(['debug' => true]);
|
||||
$this->assertTrue(ApplicationContext::isDebug());
|
||||
|
||||
ApplicationContext::reset();
|
||||
|
||||
// After reset and reinit, debug should be false by default
|
||||
ApplicationContext::initialize();
|
||||
$this->assertFalse(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testResetAllowsReinitialize(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
ApplicationContext::reset();
|
||||
|
||||
// Should not throw exception
|
||||
$container = ApplicationContext::initialize(['debug' => true]);
|
||||
|
||||
$this->assertInstanceOf(Container::class, $container);
|
||||
$this->assertTrue(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testCallbackInvokerIsAvailableInContainer(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$invoker = ApplicationContext::get(CallbackInvoker::class);
|
||||
|
||||
$this->assertInstanceOf(CallbackInvoker::class, $invoker);
|
||||
}
|
||||
|
||||
public function testMultipleGetCallsReturnSameContainer(): void
|
||||
{
|
||||
$container1 = ApplicationContext::getContainer();
|
||||
$container2 = ApplicationContext::getContainer();
|
||||
|
||||
$this->assertSame($container1, $container2);
|
||||
}
|
||||
|
||||
public function testInitializeWithEmptyOptions(): void
|
||||
{
|
||||
$container = ApplicationContext::initialize([]);
|
||||
|
||||
$this->assertInstanceOf(Container::class, $container);
|
||||
$this->assertFalse(ApplicationContext::isDebug());
|
||||
}
|
||||
|
||||
public function testInitializeWithNullDefinitions(): void
|
||||
{
|
||||
$container = ApplicationContext::initialize(['definitions' => null]);
|
||||
|
||||
$this->assertInstanceOf(Container::class, $container);
|
||||
}
|
||||
|
||||
public function testInitializeWithEmptyDefinitions(): void
|
||||
{
|
||||
$container = ApplicationContext::initialize(['definitions' => []]);
|
||||
|
||||
$this->assertInstanceOf(Container::class, $container);
|
||||
}
|
||||
|
||||
public function testSetBeforeInitializeAutoInitializes(): void
|
||||
{
|
||||
// Don't call initialize
|
||||
ApplicationContext::set('test.service', 'value');
|
||||
|
||||
$this->assertEquals('value', ApplicationContext::get('test.service'));
|
||||
}
|
||||
|
||||
public function testHasBeforeInitializeAutoInitializes(): void
|
||||
{
|
||||
// Don't call initialize, should auto-initialize
|
||||
$result = ApplicationContext::has(Container::class);
|
||||
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testGetBeforeInitializeAutoInitializes(): void
|
||||
{
|
||||
// Don't call initialize
|
||||
$container = ApplicationContext::get(Container::class);
|
||||
|
||||
$this->assertInstanceOf(Container::class, $container);
|
||||
}
|
||||
|
||||
public function testInvokerSingletonConsistency(): void
|
||||
{
|
||||
// Test fix for issue #3 and #4 - Invoker instance consistency
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$invoker1 = ApplicationContext::getInvoker();
|
||||
$invoker2 = ApplicationContext::get(CallbackInvoker::class);
|
||||
|
||||
// Both should return the same instance
|
||||
$this->assertSame($invoker1, $invoker2);
|
||||
}
|
||||
|
||||
public function testInvokerSingletonConsistencyAfterReset(): void
|
||||
{
|
||||
ApplicationContext::initialize();
|
||||
$invoker1 = ApplicationContext::getInvoker();
|
||||
|
||||
ApplicationContext::reset();
|
||||
ApplicationContext::initialize();
|
||||
|
||||
$invoker2 = ApplicationContext::getInvoker();
|
||||
$invoker3 = ApplicationContext::get(CallbackInvoker::class);
|
||||
|
||||
// After reset, should be new instance
|
||||
$this->assertNotSame($invoker1, $invoker2);
|
||||
// But getInvoker() and container should still be consistent
|
||||
$this->assertSame($invoker2, $invoker3);
|
||||
}
|
||||
}
|
||||
629
tests/StaticPHP/DI/CallbackInvokerTest.php
Normal file
629
tests/StaticPHP/DI/CallbackInvokerTest.php
Normal file
@ -0,0 +1,629 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\StaticPHP\DI;
|
||||
|
||||
use DI\Container;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use StaticPHP\DI\CallbackInvoker;
|
||||
|
||||
/**
|
||||
* Helper class that requires constructor parameters for testing
|
||||
*/
|
||||
class UnresolvableTestClass
|
||||
{
|
||||
public function __construct(
|
||||
private string $requiredParam
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CallbackInvokerTest extends TestCase
|
||||
{
|
||||
private Container $container;
|
||||
|
||||
private CallbackInvoker $invoker;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->container = new Container();
|
||||
$this->invoker = new CallbackInvoker($this->container);
|
||||
}
|
||||
|
||||
public function testInvokeSimpleCallbackWithoutParameters(): void
|
||||
{
|
||||
$callback = function () {
|
||||
return 'result';
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback);
|
||||
|
||||
$this->assertEquals('result', $result);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithContextByTypeName(): void
|
||||
{
|
||||
$callback = function (string $param) {
|
||||
return $param;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['string' => 'test_value']);
|
||||
|
||||
$this->assertEquals('test_value', $result);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithContextByParameterName(): void
|
||||
{
|
||||
$callback = function (string $myParam) {
|
||||
return $myParam;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['myParam' => 'test_value']);
|
||||
|
||||
$this->assertEquals('test_value', $result);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithContextByTypeNameTakesPrecedence(): void
|
||||
{
|
||||
$callback = function (string $myParam) {
|
||||
return $myParam;
|
||||
};
|
||||
|
||||
// Type name should take precedence over parameter name
|
||||
$result = $this->invoker->invoke($callback, [
|
||||
'string' => 'by_type',
|
||||
'myParam' => 'by_name',
|
||||
]);
|
||||
|
||||
$this->assertEquals('by_type', $result);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithContainerResolution(): void
|
||||
{
|
||||
$this->container->set('test.service', 'service_value');
|
||||
|
||||
$callback = function (string $testService) {
|
||||
return $testService;
|
||||
};
|
||||
|
||||
// Should not resolve from container as 'test.service' is not a type
|
||||
// Will try default value or null
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->invoker->invoke($callback);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithClassTypeFromContainer(): void
|
||||
{
|
||||
$testObject = new \stdClass();
|
||||
$testObject->value = 'test';
|
||||
$this->container->set(\stdClass::class, $testObject);
|
||||
|
||||
$callback = function (\stdClass $obj) {
|
||||
return $obj->value;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback);
|
||||
|
||||
$this->assertEquals('test', $result);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithDefaultValue(): void
|
||||
{
|
||||
$callback = function (string $param = 'default_value') {
|
||||
return $param;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback);
|
||||
|
||||
$this->assertEquals('default_value', $result);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithNullableParameter(): void
|
||||
{
|
||||
$callback = function (?string $param) {
|
||||
return $param ?? 'was_null';
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback);
|
||||
|
||||
$this->assertEquals('was_null', $result);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackThrowsExceptionForUnresolvableParameter(): void
|
||||
{
|
||||
$callback = function (string $required) {
|
||||
return $required;
|
||||
};
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage("Cannot resolve parameter 'required' of type 'string'");
|
||||
$this->invoker->invoke($callback);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackThrowsExceptionForNonExistentClass(): void
|
||||
{
|
||||
// This test uses UnresolvableTestClass which has required constructor params
|
||||
// Container.has() will return true but get() will throw InvalidDefinition
|
||||
// So we test that container exceptions bubble up
|
||||
$callback = function (UnresolvableTestClass $obj) {
|
||||
return $obj;
|
||||
};
|
||||
|
||||
$this->expectException(\Throwable::class);
|
||||
$this->invoker->invoke($callback);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithMultipleParameters(): void
|
||||
{
|
||||
$callback = function (string $first, int $second, bool $third) {
|
||||
return [$first, $second, $third];
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, [
|
||||
'first' => 'value1',
|
||||
'second' => 42,
|
||||
'third' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(['value1', 42, true], $result);
|
||||
}
|
||||
|
||||
public function testInvokeCallbackWithMixedResolutionSources(): void
|
||||
{
|
||||
$this->container->set(\stdClass::class, new \stdClass());
|
||||
|
||||
$callback = function (
|
||||
\stdClass $fromContainer,
|
||||
string $fromContext,
|
||||
int $withDefault = 100
|
||||
) {
|
||||
return [$fromContainer, $fromContext, $withDefault];
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['fromContext' => 'context_value']);
|
||||
|
||||
$this->assertInstanceOf(\stdClass::class, $result[0]);
|
||||
$this->assertEquals('context_value', $result[1]);
|
||||
$this->assertEquals(100, $result[2]);
|
||||
}
|
||||
|
||||
public function testExpandContextHierarchyWithObject(): void
|
||||
{
|
||||
// Create a simple parent-child relationship
|
||||
$childClass = new \ArrayObject(['key' => 'value']);
|
||||
|
||||
$callback = function (\ArrayObject $obj) {
|
||||
return $obj;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, [get_class($childClass) => $childClass]);
|
||||
|
||||
$this->assertSame($childClass, $result);
|
||||
}
|
||||
|
||||
public function testExpandContextHierarchyWithInterface(): void
|
||||
{
|
||||
$object = new class implements \Countable {
|
||||
public function count(): int
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
};
|
||||
|
||||
$callback = function (\Countable $countable) {
|
||||
return $countable->count();
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, [get_class($object) => $object]);
|
||||
|
||||
$this->assertEquals(42, $result);
|
||||
}
|
||||
|
||||
public function testExpandContextHierarchyWithMultipleInterfaces(): void
|
||||
{
|
||||
$object = new class implements \Countable, \IteratorAggregate {
|
||||
public function count(): int
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
public function getIterator(): \Traversable
|
||||
{
|
||||
return new \ArrayIterator([]);
|
||||
}
|
||||
};
|
||||
|
||||
$callback = function (\Countable $c, \IteratorAggregate $i) {
|
||||
return [$c->count(), $i];
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['obj' => $object]);
|
||||
|
||||
$this->assertEquals(5, $result[0]);
|
||||
$this->assertInstanceOf(\IteratorAggregate::class, $result[1]);
|
||||
}
|
||||
|
||||
public function testInvokeWithArrayCallback(): void
|
||||
{
|
||||
$testClass = new class {
|
||||
public function method(string $param): string
|
||||
{
|
||||
return 'called_' . $param;
|
||||
}
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke([$testClass, 'method'], ['param' => 'test']);
|
||||
|
||||
$this->assertEquals('called_test', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithStaticMethod(): void
|
||||
{
|
||||
$testClass = new class {
|
||||
public static function staticMethod(string $param): string
|
||||
{
|
||||
return 'static_' . $param;
|
||||
}
|
||||
};
|
||||
|
||||
$className = get_class($testClass);
|
||||
$result = $this->invoker->invoke([$className, 'staticMethod'], ['param' => 'value']);
|
||||
|
||||
$this->assertEquals('static_value', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithCallableString(): void
|
||||
{
|
||||
$callback = 'Tests\StaticPHP\DI\testFunction';
|
||||
|
||||
if (!function_exists($callback)) {
|
||||
eval('namespace Tests\StaticPHP\DI; function testFunction(string $param) { return "func_" . $param; }');
|
||||
}
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['param' => 'test']);
|
||||
|
||||
$this->assertEquals('func_test', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithNoTypeHintedParameter(): void
|
||||
{
|
||||
$callback = function ($param) {
|
||||
return $param;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['param' => 'value']);
|
||||
|
||||
$this->assertEquals('value', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithNoTypeHintedParameterReturnsNull(): void
|
||||
{
|
||||
// Parameters without type hints are implicitly nullable in PHP
|
||||
$callback = function ($param) {
|
||||
return $param;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback);
|
||||
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testInvokeWithNoTypeHintAndValueInContext(): void
|
||||
{
|
||||
$callback = function ($param) {
|
||||
return $param;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['param' => 'value']);
|
||||
|
||||
$this->assertEquals('value', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithBuiltinTypes(): void
|
||||
{
|
||||
$callback = function (
|
||||
string $str,
|
||||
int $num,
|
||||
float $decimal,
|
||||
bool $flag,
|
||||
array $arr
|
||||
) {
|
||||
return compact('str', 'num', 'decimal', 'flag', 'arr');
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, [
|
||||
'str' => 'test',
|
||||
'num' => 42,
|
||||
'decimal' => 3.14,
|
||||
'flag' => true,
|
||||
'arr' => [1, 2, 3],
|
||||
]);
|
||||
|
||||
$this->assertEquals([
|
||||
'str' => 'test',
|
||||
'num' => 42,
|
||||
'decimal' => 3.14,
|
||||
'flag' => true,
|
||||
'arr' => [1, 2, 3],
|
||||
], $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithEmptyContext(): void
|
||||
{
|
||||
$callback = function () {
|
||||
return 'no_params';
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, []);
|
||||
|
||||
$this->assertEquals('no_params', $result);
|
||||
}
|
||||
|
||||
public function testInvokePreservesCallbackReturnValue(): void
|
||||
{
|
||||
$callback = function () {
|
||||
return ['key' => 'value', 'number' => 123];
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback);
|
||||
|
||||
$this->assertEquals(['key' => 'value', 'number' => 123], $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithNullReturnValue(): void
|
||||
{
|
||||
$callback = function () {
|
||||
return null;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback);
|
||||
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testInvokeWithObjectInContext(): void
|
||||
{
|
||||
$obj = new \stdClass();
|
||||
$obj->value = 'test';
|
||||
|
||||
$callback = function (\stdClass $param) {
|
||||
return $param->value;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['param' => $obj]);
|
||||
|
||||
$this->assertEquals('test', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithInheritanceInContext(): void
|
||||
{
|
||||
$exception = new \RuntimeException('test message');
|
||||
|
||||
$callback = function (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
};
|
||||
|
||||
// RuntimeException should be resolved as Exception via hierarchy expansion
|
||||
$result = $this->invoker->invoke($callback, ['exc' => $exception]);
|
||||
|
||||
$this->assertEquals('test message', $result);
|
||||
}
|
||||
|
||||
public function testInvokeContextValueOverridesContainer(): void
|
||||
{
|
||||
$containerObj = new \stdClass();
|
||||
$containerObj->source = 'container';
|
||||
$this->container->set(\stdClass::class, $containerObj);
|
||||
|
||||
$contextObj = new \stdClass();
|
||||
$contextObj->source = 'context';
|
||||
|
||||
$callback = function (\stdClass $obj) {
|
||||
return $obj->source;
|
||||
};
|
||||
|
||||
// Context should override container
|
||||
$result = $this->invoker->invoke($callback, [\stdClass::class => $contextObj]);
|
||||
|
||||
$this->assertEquals('context', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithDefaultValueNotUsedWhenContextProvided(): void
|
||||
{
|
||||
$callback = function (string $param = 'default') {
|
||||
return $param;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['param' => 'from_context']);
|
||||
|
||||
$this->assertEquals('from_context', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithMixedNullableAndRequired(): void
|
||||
{
|
||||
$callback = function (string $required, ?string $optional) {
|
||||
return [$required, $optional];
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['required' => 'value']);
|
||||
|
||||
$this->assertEquals(['value', null], $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithComplexObjectHierarchy(): void
|
||||
{
|
||||
// Use built-in PHP classes with inheritance
|
||||
// ArrayIterator extends IteratorIterator implements ArrayAccess, SeekableIterator, Countable, Serializable
|
||||
$arrayIterator = new \ArrayIterator(['test' => 'value']);
|
||||
|
||||
// Test that the object can be resolved via interface (Countable)
|
||||
$callback1 = function (\Countable $test) {
|
||||
return $test->count();
|
||||
};
|
||||
|
||||
$result1 = $this->invoker->invoke($callback1, ['obj' => $arrayIterator]);
|
||||
$this->assertEquals(1, $result1);
|
||||
|
||||
// Test that the object can be resolved via another interface (Iterator)
|
||||
$callback2 = function (\Iterator $test) {
|
||||
return $test;
|
||||
};
|
||||
|
||||
$result2 = $this->invoker->invoke($callback2, ['obj' => $arrayIterator]);
|
||||
$this->assertInstanceOf(\ArrayIterator::class, $result2);
|
||||
|
||||
// Test that the object can be resolved via concrete class
|
||||
$callback3 = function (\ArrayIterator $test) {
|
||||
return $test;
|
||||
};
|
||||
|
||||
$result3 = $this->invoker->invoke($callback3, ['obj' => $arrayIterator]);
|
||||
$this->assertSame($arrayIterator, $result3);
|
||||
}
|
||||
|
||||
public function testInvokeWithNonObjectContextValues(): void
|
||||
{
|
||||
$callback = function (string $str, int $num, array $arr, bool $flag) {
|
||||
return compact('str', 'num', 'arr', 'flag');
|
||||
};
|
||||
|
||||
$context = [
|
||||
'str' => 'hello',
|
||||
'num' => 999,
|
||||
'arr' => ['a', 'b'],
|
||||
'flag' => false,
|
||||
];
|
||||
|
||||
$result = $this->invoker->invoke($callback, $context);
|
||||
|
||||
$this->assertEquals($context, $result);
|
||||
}
|
||||
|
||||
public function testInvokeParameterOrderMatters(): void
|
||||
{
|
||||
$callback = function (string $first, string $second, string $third) {
|
||||
return [$first, $second, $third];
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, [
|
||||
'first' => 'A',
|
||||
'second' => 'B',
|
||||
'third' => 'C',
|
||||
]);
|
||||
|
||||
$this->assertEquals(['A', 'B', 'C'], $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithUnionTypeThrowsException(): void
|
||||
{
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
$this->markTestSkipped('Union types require PHP 8.0+');
|
||||
}
|
||||
|
||||
$callback = eval('return function (string|int $param) { return $param; };');
|
||||
|
||||
// Union types are not ReflectionNamedType, should not be resolved from container
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->invoker->invoke($callback);
|
||||
}
|
||||
|
||||
public function testInvokeWithCallableType(): void
|
||||
{
|
||||
$callback = function (callable $fn) {
|
||||
return $fn();
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, [
|
||||
'fn' => fn () => 'called',
|
||||
]);
|
||||
|
||||
$this->assertEquals('called', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithIterableType(): void
|
||||
{
|
||||
$callback = function (iterable $items) {
|
||||
$result = [];
|
||||
foreach ($items as $item) {
|
||||
$result[] = $item;
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, [
|
||||
'items' => [1, 2, 3],
|
||||
]);
|
||||
|
||||
$this->assertEquals([1, 2, 3], $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithObjectType(): void
|
||||
{
|
||||
$callback = function (object $obj) {
|
||||
return get_class($obj);
|
||||
};
|
||||
|
||||
$testObj = new \stdClass();
|
||||
$result = $this->invoker->invoke($callback, ['obj' => $testObj]);
|
||||
|
||||
$this->assertEquals('stdClass', $result);
|
||||
}
|
||||
|
||||
public function testInvokeWithContainerExceptionFallsThrough(): void
|
||||
{
|
||||
// Test fix for issue #1 - Container exceptions should be caught
|
||||
// and fall through to other resolution strategies
|
||||
$callback = function (?UnresolvableTestClass $obj = null) {
|
||||
return $obj;
|
||||
};
|
||||
|
||||
// Should use default value (null) instead of throwing container exception
|
||||
$result = $this->invoker->invoke($callback);
|
||||
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testInvokeWithContainerExceptionAndNoFallback(): void
|
||||
{
|
||||
// When there's no fallback (no default, not nullable), should throw RuntimeException
|
||||
$callback = function (UnresolvableTestClass $obj) {
|
||||
return $obj;
|
||||
};
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage("Cannot resolve parameter 'obj'");
|
||||
|
||||
$this->invoker->invoke($callback);
|
||||
}
|
||||
|
||||
public function testExpandContextHierarchyPerformance(): void
|
||||
{
|
||||
// Test fix for issue #2 - Should not create duplicate ReflectionClass
|
||||
// This is more of a code quality test, ensuring the fix doesn't break functionality
|
||||
$obj = new \ArrayIterator(['a', 'b', 'c']);
|
||||
|
||||
$callback = function (
|
||||
\ArrayIterator $asArrayIterator,
|
||||
\Traversable $asTraversable,
|
||||
\Countable $asCountable
|
||||
) {
|
||||
return [
|
||||
get_class($asArrayIterator),
|
||||
get_class($asTraversable),
|
||||
get_class($asCountable),
|
||||
];
|
||||
};
|
||||
|
||||
$result = $this->invoker->invoke($callback, ['obj' => $obj]);
|
||||
|
||||
$this->assertEquals([
|
||||
'ArrayIterator',
|
||||
'ArrayIterator',
|
||||
'ArrayIterator',
|
||||
], $result);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@ -1,13 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
require_once __DIR__ . '/../src/globals/internal-env.php';
|
||||
require_once __DIR__ . '/mock/SPC_store.php';
|
||||
require_once __DIR__ . '/../src/bootstrap.php';
|
||||
\StaticPHP\Registry\Registry::checkLoadedRegistries();
|
||||
|
||||
\SPC\util\AttributeMapper::init();
|
||||
|
||||
$log_dir = SPC_LOGS_DIR;
|
||||
if (!file_exists($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
logger()->setLevel(LogLevel::ERROR);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user