- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
220 lines
7.1 KiB
PHP
220 lines
7.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\DI;
|
|
|
|
use App\Framework\DI\DefaultContainer;
|
|
use App\Framework\DI\InstanceRegistry;
|
|
use PHPUnit\Framework\TestCase;
|
|
use stdClass;
|
|
|
|
/**
|
|
* Tests to identify and prevent Container memory leaks
|
|
*/
|
|
final class ContainerMemoryLeakTest extends TestCase
|
|
{
|
|
private DefaultContainer $container;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->container = new DefaultContainer();
|
|
|
|
// Prä-Registriere stdClass als echte Klasse um Lazy Loading zu vermeiden
|
|
$this->container->bind(stdClass::class, fn () => new stdClass());
|
|
}
|
|
|
|
/**
|
|
* Test ob der Container unbegrenzt wächst bei wiederholten Aufrufen
|
|
*/
|
|
public function test_container_does_not_grow_indefinitely(): void
|
|
{
|
|
$initialMemory = memory_get_usage(true);
|
|
|
|
// Simuliere viele Requests mit direkten Instanzen (vermeidet Lazy Loading)
|
|
for ($i = 0; $i < 1000; $i++) {
|
|
$serviceName = "service_$i";
|
|
|
|
// Erstelle Objekt direkt und registriere als Instanz
|
|
$obj = new stdClass();
|
|
$obj->id = $i;
|
|
$obj->data = str_repeat('x', 100);
|
|
|
|
$this->container->instance($serviceName, $obj);
|
|
$instance = $this->container->get($serviceName);
|
|
|
|
$this->assertEquals($i, $instance->id);
|
|
}
|
|
|
|
$finalMemory = memory_get_usage(true);
|
|
$memoryGrowth = $finalMemory - $initialMemory;
|
|
|
|
// Memory growth sollte unter 5MB bleiben für 1000 Services
|
|
$this->assertLessThan(
|
|
5 * 1024 * 1024,
|
|
$memoryGrowth,
|
|
"Container memory grew by " . number_format($memoryGrowth) . " bytes for 1000 services"
|
|
);
|
|
|
|
echo "\nMemory growth for 1000 services: " . number_format($memoryGrowth) . " bytes\n";
|
|
}
|
|
|
|
/**
|
|
* Test ob die InstanceRegistry ordnungsgemäß aufräumt
|
|
*/
|
|
public function test_instance_registry_can_be_flushed(): void
|
|
{
|
|
$registry = new InstanceRegistry();
|
|
|
|
// Fülle Registry mit vielen Instanzen
|
|
for ($i = 0; $i < 100; $i++) {
|
|
$serviceName = "TestInstance_$i";
|
|
$instance = (object)['id' => $i, 'data' => "test_$i"];
|
|
$registry->setInstance($serviceName, $instance);
|
|
}
|
|
|
|
// Prüfe dass Instanzen registriert sind
|
|
$this->assertCount(100, $registry->getAllRegistered());
|
|
|
|
// Flush sollte alles löschen
|
|
$registry->flush();
|
|
$this->assertCount(0, $registry->getAllRegistered());
|
|
}
|
|
|
|
/**
|
|
* Test ob Singletons ordnungsgemäß verwaltet werden
|
|
* Test nutzt InstanceRegistry direkt da singleton() auch lazy loading versucht
|
|
*/
|
|
public function test_singleton_lifecycle(): void
|
|
{
|
|
$serviceName = 'singleton_service';
|
|
|
|
// Erstelle Objekt direkt und registriere als Singleton über InstanceRegistry
|
|
$obj = new stdClass();
|
|
$obj->data = 'test';
|
|
$obj->created_at = time();
|
|
|
|
// Direktes Setzen als Singleton über die Registry
|
|
$registry = new InstanceRegistry();
|
|
$registry->setSingleton($serviceName, $obj);
|
|
|
|
// Container mit dieser Registry erstellen
|
|
$container = new DefaultContainer(instances: $registry);
|
|
|
|
// Sollte immer die gleiche Instanz zurückgeben
|
|
$instance1 = $container->get($serviceName);
|
|
$instance2 = $container->get($serviceName);
|
|
|
|
$this->assertSame($instance1, $instance2);
|
|
$this->assertEquals('test', $instance1->data);
|
|
}
|
|
|
|
/**
|
|
* Test ob Container-Cache bei bind() ordnungsgemäß geleert wird
|
|
*/
|
|
public function test_container_cache_clearing(): void
|
|
{
|
|
$serviceName = 'cache_service';
|
|
|
|
// Erste Instanz
|
|
$obj1 = new stdClass();
|
|
$obj1->version = 1;
|
|
$this->container->instance($serviceName, $obj1);
|
|
$instance1 = $this->container->get($serviceName);
|
|
|
|
// Neue Instanz sollte alte überschreiben
|
|
$obj2 = new stdClass();
|
|
$obj2->version = 2;
|
|
$this->container->instance($serviceName, $obj2);
|
|
$instance2 = $this->container->get($serviceName);
|
|
|
|
$this->assertNotSame($instance1, $instance2);
|
|
$this->assertEquals(1, $instance1->version);
|
|
$this->assertEquals(2, $instance2->version);
|
|
}
|
|
|
|
/**
|
|
* Test Memory Usage unter Last
|
|
*/
|
|
public function test_memory_usage_under_load(): void
|
|
{
|
|
$memoryBefore = memory_get_usage(true);
|
|
$maxMemoryUsed = 0;
|
|
|
|
// Simuliere Last von 500 verschiedenen Services
|
|
for ($iteration = 0; $iteration < 5; $iteration++) {
|
|
for ($i = 0; $i < 100; $i++) {
|
|
$serviceName = "load_service_{$iteration}_{$i}";
|
|
|
|
// Erstelle Service-Objekt direkt
|
|
$obj = new stdClass();
|
|
$obj->data = array_fill(0, 100, 'load_test_data');
|
|
$obj->timestamp = microtime(true);
|
|
$obj->random = random_bytes(256);
|
|
|
|
$this->container->instance($serviceName, $obj);
|
|
$instance = $this->container->get($serviceName);
|
|
$this->assertCount(100, $instance->data);
|
|
|
|
$currentMemory = memory_get_usage(true);
|
|
$maxMemoryUsed = max($maxMemoryUsed, $currentMemory - $memoryBefore);
|
|
}
|
|
|
|
// Optional: Garbage collection nach jeder Iteration
|
|
gc_collect_cycles();
|
|
}
|
|
|
|
$finalMemory = memory_get_usage(true);
|
|
$totalGrowth = $finalMemory - $memoryBefore;
|
|
|
|
// Container sollte nicht unbegrenzt wachsen
|
|
$this->assertLessThan(
|
|
20 * 1024 * 1024,
|
|
$totalGrowth,
|
|
"Container grew by " . number_format($totalGrowth) . " bytes under load"
|
|
);
|
|
|
|
echo "\nMemory Statistics:\n";
|
|
echo "- Initial Memory: " . number_format($memoryBefore) . " bytes\n";
|
|
echo "- Final Memory: " . number_format($finalMemory) . " bytes\n";
|
|
echo "- Total Growth: " . number_format($totalGrowth) . " bytes\n";
|
|
echo "- Max Memory Used: " . number_format($maxMemoryUsed) . " bytes\n";
|
|
}
|
|
|
|
/**
|
|
* Test Performance von Container-Lookups
|
|
*/
|
|
public function test_container_lookup_performance(): void
|
|
{
|
|
// Setup: 1000 Services als Instanzen registrieren
|
|
for ($i = 0; $i < 1000; $i++) {
|
|
$serviceName = "perf_service_$i";
|
|
$obj = new stdClass();
|
|
$obj->id = $i;
|
|
$obj->name = "service_$i";
|
|
$this->container->instance($serviceName, $obj);
|
|
}
|
|
|
|
$startTime = microtime(true);
|
|
|
|
// Performance Test: 10000 Lookups
|
|
for ($i = 0; $i < 10000; $i++) {
|
|
$serviceName = "perf_service_" . ($i % 1000);
|
|
$instance = $this->container->get($serviceName);
|
|
$this->assertEquals($i % 1000, $instance->id);
|
|
}
|
|
|
|
$endTime = microtime(true);
|
|
$duration = $endTime - $startTime;
|
|
|
|
// Sollte unter 1 Sekunde bleiben für 10k Lookups
|
|
$this->assertLessThan(
|
|
1.0,
|
|
$duration,
|
|
"Container lookups took {$duration}s for 10k operations"
|
|
);
|
|
|
|
echo "\nPerformance: " . number_format(10000 / $duration, 0) . " lookups/second\n";
|
|
}
|
|
}
|