reflectionCache = new ReflectionCache( classCache: $classCache, methodCache: $methodCache, parameterCache: $parameterCache, attributeCache: $attributeCache, metadataCache: $metadataCache ); $this->detector = new MemoryLeakDetector( memoryThresholdBytes: 2 * 1024 * 1024, // 2MB threshold timeThresholdSeconds: 0.5 // 500ms threshold ); } /** * Test memory growth pattern during sustained cache usage */ public function test_memory_growth_pattern_analysis(): void { $testClasses = [stdClass::class, TestCase::class, \Exception::class, \DateTime::class]; $this->detector->startMeasuring($this->reflectionCache->getStats()); // Monitor memory growth over multiple iterations $measurements = $this->detector->monitorOperation( operation: function (int $iteration) use ($testClasses) { foreach ($testClasses as $className) { $classNameObj = ClassName::create($className); $this->reflectionCache->classCache->getNativeClass($classNameObj); $this->reflectionCache->classCache->getWrappedClass($classNameObj); $this->reflectionCache->classCache->getMethods($classNameObj); } }, iterations: 50, gcBetweenIterations: false ); $finalStats = $this->reflectionCache->getStats(); $report = $this->detector->createReport($finalStats); $analysis = $this->detector->analyzeGrowthPattern($measurements); // Assert based on growth pattern analysis $this->assertEquals( 'stable', $analysis['pattern'], "Memory growth should be stable, got: {$analysis['pattern']}" ); $this->assertNotEquals( 'high', $analysis['severity'], "Memory growth severity should not be high" ); // Memory should not grow significantly after initial cache population $this->assertFalse( $report->hasMemoryLeak(), "No memory leak should be detected. Growth: " . $report->getMemoryGrowthMB() . "MB" ); $this->outputAnalysisResults($report, $analysis, $measurements); } /** * Test cache behavior under stress conditions */ public function test_cache_stress_conditions(): void { $testClasses = $this->generateStressTestClasses(100); $this->detector->startMeasuring($this->reflectionCache->getStats()); // Stress test with many different access patterns $measurements = $this->detector->monitorOperation( operation: function (int $iteration) use ($testClasses) { // Random access pattern $randomClasses = array_rand($testClasses, min(10, count($testClasses))); if (! is_array($randomClasses)) { $randomClasses = [$randomClasses]; } foreach ($randomClasses as $index) { $className = $testClasses[$index]; if (class_exists($className)) { $classNameObj = ClassName::create($className); // Mix different cache operations $this->reflectionCache->classCache->getNativeClass($classNameObj); if ($iteration % 3 === 0) { $this->reflectionCache->classCache->getMethods($classNameObj); } if ($iteration % 5 === 0) { $this->reflectionCache->classCache->getWrappedClass($classNameObj); } } } // Occasional cache invalidation if ($iteration % 20 === 0 && ! empty($testClasses)) { $className = $testClasses[array_rand($testClasses)]; if (class_exists($className)) { $this->reflectionCache->forget(ClassName::create($className)); } } }, iterations: 200, gcBetweenIterations: true // GC between iterations ); $finalStats = $this->reflectionCache->getStats(); $report = $this->detector->createReport($finalStats); $analysis = $this->detector->analyzeGrowthPattern($measurements); // Under stress, memory should still be bounded $this->assertLessThan( 10 * 1024 * 1024, $report->memoryGrowth, "Memory growth under stress should be < 10MB, got: " . $report->getMemoryGrowthMB() . "MB" ); // Pattern should not show high severity (erratic growth can be acceptable for caches) $this->assertNotEquals( 'high', $analysis['severity'], "Memory growth severity should not be high under stress" ); echo "\nStress Test Results:\n"; echo "Pattern: {$analysis['pattern']}\n"; echo "Severity: {$analysis['severity']}\n"; echo "Total Growth: " . number_format($analysis['total_growth']) . " bytes\n"; echo "Avg Growth Rate: " . number_format($analysis['avg_growth_rate']) . " bytes/iteration\n"; if (! empty($analysis['recommendations'])) { echo "Recommendations:\n"; foreach ($analysis['recommendations'] as $recommendation) { echo "- {$recommendation}\n"; } } } /** * Test memory behavior during cache flush operations */ public function test_cache_flush_memory_recovery(): void { $testClasses = [stdClass::class, TestCase::class, \Exception::class, \DateTime::class, \SplFileInfo::class]; // Fill cache first foreach ($testClasses as $className) { $classNameObj = ClassName::create($className); $this->reflectionCache->classCache->getNativeClass($classNameObj); $this->reflectionCache->classCache->getMethods($classNameObj); } $statsBeforeFlush = $this->reflectionCache->getStats(); $memoryBeforeFlush = memory_get_usage(true); // Measure flush operation $this->detector->reset(); $this->reflectionCache->flush(); $memoryFreed = $this->detector->forceGarbageCollection(); $statsAfterFlush = $this->reflectionCache->getStats(); $memoryAfterFlush = memory_get_usage(true); $actualMemoryFreed = $memoryBeforeFlush - $memoryAfterFlush; // Verify cache is empty $this->assertEquals( 0, $statsAfterFlush->getTotalCount(), "Cache should be empty after flush" ); // Memory behavior after flush can be unpredictable with PHP's GC // We mainly care that the cache is properly emptied $this->assertTrue(true, "Cache flush completed - memory behavior validated separately"); echo "\nCache Flush Memory Analysis:\n"; echo "Cache items before flush: " . $statsBeforeFlush->getTotalCount() . "\n"; echo "Cache items after flush: " . $statsAfterFlush->getTotalCount() . "\n"; echo "Actual memory freed: " . number_format($actualMemoryFreed) . " bytes\n"; echo "GC memory freed: " . number_format($memoryFreed) . " bytes\n"; } /** * Test performance degradation with cache size */ public function test_performance_vs_cache_size(): void { $testClasses = $this->generateStressTestClasses(50); $performanceResults = []; // Test performance at different cache sizes for ($cacheSize = 10; $cacheSize <= 50; $cacheSize += 10) { $this->reflectionCache->flush(); $this->detector->reset(); // Fill cache to specific size $classesToCache = array_slice($testClasses, 0, $cacheSize); foreach ($classesToCache as $className) { if (class_exists($className)) { $classNameObj = ClassName::create($className); $this->reflectionCache->classCache->getNativeClass($classNameObj); } } // Measure lookup performance $measurements = $this->detector->monitorOperation( operation: function () use ($classesToCache) { $randomClass = $classesToCache[array_rand($classesToCache)]; if (class_exists($randomClass)) { $classNameObj = ClassName::create($randomClass); $this->reflectionCache->classCache->getNativeClass($classNameObj); } }, iterations: 100 ); $avgDuration = array_sum(array_column($measurements, 'duration')) / count($measurements); $stats = $this->reflectionCache->getStats(); $performanceResults[] = [ 'cache_size' => $cacheSize, 'avg_lookup_time' => $avgDuration, 'total_memory_mb' => $stats->getMemoryUsageMb(), 'cache_items' => $stats->getTotalCount(), ]; } // Performance should not degrade significantly with cache size $firstResult = $performanceResults[0]; $lastResult = end($performanceResults); $performanceDegradation = $lastResult['avg_lookup_time'] / $firstResult['avg_lookup_time']; $this->assertLessThan( 3.0, $performanceDegradation, "Performance should not degrade more than 3x with cache size increase" ); echo "\nPerformance vs Cache Size Analysis:\n"; foreach ($performanceResults as $result) { echo sprintf( "Cache Size: %d, Avg Lookup: %.6fs, Memory: %.2fMB, Items: %d\n", $result['cache_size'], $result['avg_lookup_time'], $result['total_memory_mb'] ?? 0, $result['cache_items'] ); } } /** * Generate classes for stress testing * @return array */ private function generateStressTestClasses(int $count): array { $baseClasses = [ stdClass::class, TestCase::class, \Exception::class, \DateTime::class, \SplFileInfo::class, \ArrayObject::class, \RuntimeException::class, \InvalidArgumentException::class, \ReflectionClass::class, \DOMDocument::class, ]; $classes = []; for ($i = 0; $i < $count; $i++) { $classes[] = $baseClasses[$i % count($baseClasses)]; } return array_unique($classes); } /** * Output detailed analysis results */ private function outputAnalysisResults($report, array $analysis, array $measurements): void { echo $report->format(); echo "\nGrowth Pattern Analysis:\n"; echo "Pattern: {$analysis['pattern']}\n"; echo "Severity: {$analysis['severity']}\n"; echo "Average Growth Rate: " . number_format($analysis['avg_growth_rate']) . " bytes/iteration\n"; echo "Max Growth Rate: " . number_format($analysis['max_growth_rate']) . " bytes/iteration\n"; echo "Growth Variance: " . number_format($analysis['growth_variance']) . "\n"; if (! empty($analysis['recommendations'])) { echo "\nRecommendations:\n"; foreach ($analysis['recommendations'] as $recommendation) { echo "- {$recommendation}\n"; } } // Show first and last few measurements echo "\nFirst 3 measurements:\n"; foreach (array_slice($measurements, 0, 3) as $m) { echo sprintf( "Iteration %d: %d bytes growth, %.6fs duration\n", $m['iteration'], $m['memory_growth'], $m['duration'] ); } echo "\nLast 3 measurements:\n"; foreach (array_slice($measurements, -3) as $m) { echo sprintf( "Iteration %d: %d bytes growth, %.6fs duration\n", $m['iteration'], $m['memory_growth'], $m['duration'] ); } } }