Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
272 lines
8.7 KiB
PHP
272 lines
8.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Discovery;
|
|
|
|
use App\Framework\Cache\Cache;
|
|
use App\Framework\Cache\CacheIdentifier;
|
|
use App\Framework\Cache\CacheItem;
|
|
use App\Framework\Cache\CacheResult;
|
|
use App\Framework\Cache\Driver\InMemoryCache;
|
|
use App\Framework\Core\PathProvider;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\DateTime\SystemClock;
|
|
use App\Framework\Discovery\UnifiedDiscoveryService;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Simple Cache implementation for testing
|
|
*/
|
|
final class SimpleCacheWrapper implements Cache
|
|
{
|
|
public function __construct(private InMemoryCache $driver)
|
|
{
|
|
}
|
|
|
|
public function get(CacheIdentifier ...$identifiers): CacheResult
|
|
{
|
|
$keys = array_filter($identifiers, fn ($id) => $id instanceof \App\Framework\Cache\CacheKey);
|
|
|
|
return $this->driver->get(...$keys);
|
|
}
|
|
|
|
public function set(CacheItem ...$items): bool
|
|
{
|
|
return $this->driver->set(...$items);
|
|
}
|
|
|
|
public function has(CacheIdentifier ...$identifiers): array
|
|
{
|
|
$keys = array_filter($identifiers, fn ($id) => $id instanceof \App\Framework\Cache\CacheKey);
|
|
|
|
return $this->driver->has(...$keys);
|
|
}
|
|
|
|
public function forget(CacheIdentifier ...$identifiers): bool
|
|
{
|
|
$keys = array_filter($identifiers, fn ($id) => $id instanceof \App\Framework\Cache\CacheKey);
|
|
|
|
return $this->driver->forget(...$keys);
|
|
}
|
|
|
|
public function clear(): bool
|
|
{
|
|
return $this->driver->clear();
|
|
}
|
|
|
|
public function remember(\App\Framework\Cache\CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
|
|
{
|
|
$result = $this->driver->get($key);
|
|
$item = $result->getItem($key);
|
|
if ($item->isHit) {
|
|
return $item;
|
|
}
|
|
|
|
$value = $callback();
|
|
$newItem = CacheItem::forSet($key, $value, $ttl);
|
|
$this->driver->set($newItem);
|
|
|
|
return CacheItem::hit($key, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simplified memory test focusing only on Discovery core without container dependencies
|
|
*/
|
|
final class SimpleMemoryTest extends TestCase
|
|
{
|
|
public function test_discovery_memory_usage_15_runs(): void
|
|
{
|
|
$iterations = 15;
|
|
$memoryData = [];
|
|
|
|
echo "\n=== Testing Discovery Memory Usage - $iterations Runs ===\n";
|
|
|
|
// Create discovery service directly with mock dependencies
|
|
$pathProvider = new PathProvider('/var/www/html');
|
|
|
|
// Use InMemoryCache with wrapper to implement Cache interface
|
|
$cacheDriver = new InMemoryCache();
|
|
$cache = new SimpleCacheWrapper($cacheDriver);
|
|
$clock = new SystemClock();
|
|
|
|
// Create reflection provider without dependencies
|
|
$reflectionProvider = new \App\Framework\ReflectionLegacy\CachedReflectionProvider();
|
|
|
|
$config = new \App\Framework\Discovery\ValueObjects\DiscoveryConfiguration(
|
|
paths: ['/var/www/html/src'],
|
|
attributeMappers: [
|
|
new \App\Framework\Core\RouteMapper(),
|
|
new \App\Framework\DI\InitializerMapper(),
|
|
],
|
|
targetInterfaces: [],
|
|
useCache: false
|
|
);
|
|
|
|
$discoveryService = new UnifiedDiscoveryService(
|
|
pathProvider: $pathProvider,
|
|
cache: $cache,
|
|
clock: $clock,
|
|
reflectionProvider: $reflectionProvider,
|
|
configuration: $config,
|
|
attributeMappers: [
|
|
new \App\Framework\Core\RouteMapper(),
|
|
new \App\Framework\DI\InitializerMapper(),
|
|
],
|
|
targetInterfaces: []
|
|
);
|
|
|
|
for ($i = 1; $i <= $iterations; $i++) {
|
|
echo "\n--- Discovery Run $i/$iterations ---\n";
|
|
|
|
$memoryBefore = memory_get_usage(true);
|
|
$peakBefore = memory_get_peak_usage(true);
|
|
|
|
// Run discovery
|
|
$registry = $discoveryService->discover();
|
|
|
|
$memoryAfter = memory_get_usage(true);
|
|
$peakAfter = memory_get_peak_usage(true);
|
|
|
|
$memoryDiff = $memoryAfter - $memoryBefore;
|
|
$peakDiff = $peakAfter - $peakBefore;
|
|
|
|
$memoryData[$i] = [
|
|
'memory_before' => $memoryBefore,
|
|
'memory_after' => $memoryAfter,
|
|
'memory_diff' => $memoryDiff,
|
|
'peak_before' => $peakBefore,
|
|
'peak_after' => $peakAfter,
|
|
'peak_diff' => $peakDiff,
|
|
];
|
|
|
|
echo sprintf(
|
|
"Run %2d: Memory %s -> %s (diff: %s), Peak %s -> %s (diff: %s)\n",
|
|
$i,
|
|
$this->formatBytes($memoryBefore),
|
|
$this->formatBytes($memoryAfter),
|
|
$this->formatBytes($memoryDiff),
|
|
$this->formatBytes($peakBefore),
|
|
$this->formatBytes($peakAfter),
|
|
$this->formatBytes($peakDiff)
|
|
);
|
|
|
|
// Log reflection stats every 5 runs
|
|
if ($i % 5 === 0) {
|
|
try {
|
|
$health = $discoveryService->getHealthStatus();
|
|
echo " Health Status: " . json_encode($health) . "\n";
|
|
} catch (\Throwable $e) {
|
|
echo " Health Status: Error - " . $e->getMessage() . "\n";
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
unset($registry);
|
|
gc_collect_cycles();
|
|
|
|
// Check for excessive memory usage
|
|
if ($memoryDiff > 100 * 1024 * 1024) { // 100MB limit
|
|
$this->fail("Run $i: Excessive memory usage: " . $this->formatBytes($memoryDiff));
|
|
}
|
|
}
|
|
|
|
$this->analyzeResults($memoryData);
|
|
}
|
|
|
|
private function analyzeResults(array $memoryData): void
|
|
{
|
|
echo "\n=== Memory Analysis Results ===\n";
|
|
|
|
$memoryDiffs = array_column($memoryData, 'memory_diff');
|
|
$peakDiffs = array_column($memoryData, 'peak_diff');
|
|
|
|
$avgMemory = array_sum($memoryDiffs) / count($memoryDiffs);
|
|
$avgPeak = array_sum($peakDiffs) / count($peakDiffs);
|
|
$maxMemory = max($memoryDiffs);
|
|
$minMemory = min($memoryDiffs);
|
|
$totalGrowth = array_sum($memoryDiffs);
|
|
|
|
echo "Average memory per run: " . $this->formatBytes($avgMemory) . "\n";
|
|
echo "Average peak per run: " . $this->formatBytes($avgPeak) . "\n";
|
|
echo "Max memory per run: " . $this->formatBytes($maxMemory) . "\n";
|
|
echo "Min memory per run: " . $this->formatBytes($minMemory) . "\n";
|
|
echo "Total cumulative growth: " . $this->formatBytes($totalGrowth) . "\n";
|
|
|
|
// Calculate growth trend
|
|
$growthTrend = $this->calculateLinearTrend($memoryDiffs);
|
|
echo "Growth trend (bytes per run): " . $this->formatBytes($growthTrend) . "\n";
|
|
|
|
// Memory leak detection
|
|
if ($growthTrend > 2 * 1024 * 1024) { // 2MB growth per run
|
|
$this->fail("Detected memory leak: " . $this->formatBytes($growthTrend) . " growth per run");
|
|
}
|
|
|
|
if ($avgMemory > 50 * 1024 * 1024) { // 50MB average
|
|
$this->fail("Average memory usage too high: " . $this->formatBytes($avgMemory));
|
|
}
|
|
|
|
// Check for stability - memory usage should be consistent
|
|
$memoryVariance = $this->calculateVariance($memoryDiffs);
|
|
echo "Memory usage variance: " . $this->formatBytes($memoryVariance) . "\n";
|
|
|
|
if ($memoryVariance > 10 * 1024 * 1024) { // 10MB variance
|
|
echo "WARNING: High memory usage variance detected\n";
|
|
}
|
|
|
|
echo "\n✅ Memory test completed successfully!\n";
|
|
echo "✅ No significant memory leaks detected\n";
|
|
echo "✅ Average memory usage within acceptable limits\n";
|
|
}
|
|
|
|
private function calculateLinearTrend(array $values): float
|
|
{
|
|
$n = count($values);
|
|
if ($n < 3) {
|
|
return 0;
|
|
}
|
|
|
|
$sumX = 0;
|
|
$sumY = 0;
|
|
$sumXY = 0;
|
|
$sumX2 = 0;
|
|
|
|
for ($i = 0; $i < $n; $i++) {
|
|
$x = $i + 1;
|
|
$y = $values[$i];
|
|
$sumX += $x;
|
|
$sumY += $y;
|
|
$sumXY += $x * $y;
|
|
$sumX2 += $x * $x;
|
|
}
|
|
|
|
return ($n * $sumXY - $sumX * $sumY) / ($n * $sumX2 - $sumX * $sumX);
|
|
}
|
|
|
|
private function calculateVariance(array $values): float
|
|
{
|
|
$mean = array_sum($values) / count($values);
|
|
$squaredDiffs = array_map(fn ($value) => pow($value - $mean, 2), $values);
|
|
|
|
return array_sum($squaredDiffs) / count($squaredDiffs);
|
|
}
|
|
|
|
private function formatBytes(int|float $bytes): string
|
|
{
|
|
if ($bytes < 0) {
|
|
return '-' . $this->formatBytes(abs($bytes));
|
|
}
|
|
|
|
$units = ['B', 'KB', 'MB', 'GB'];
|
|
$unitIndex = 0;
|
|
|
|
while ($bytes >= 1024 && $unitIndex < count($units) - 1) {
|
|
$bytes /= 1024;
|
|
$unitIndex++;
|
|
}
|
|
|
|
return round($bytes, 1) . ' ' . $units[$unitIndex];
|
|
}
|
|
}
|