Files
michaelschiemer/tests/Framework/DI/ContainerMemoryLeakTest.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

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";
}
}