connection = new MigrationPerformanceMockConnection(); $this->platform = new MySQLPlatform(); $this->clock = new SystemClock(); $this->memoryMonitor = new MemoryMonitor(); $this->operationTracker = new OperationTracker($this->clock, $this->memoryMonitor); $this->migrationRunner = new MigrationRunner( $this->connection, $this->platform, $this->clock, null, // tableConfig null, // logger $this->operationTracker, $this->memoryMonitor ); }); test('migration runner integrates with performance monitoring', function () { $migration = new MigrationPerformanceTestMigration(); $migrations = new MigrationCollection($migration); // Set no applied migrations initially $this->connection->setAppliedMigrations([]); $result = $this->migrationRunner->migrate($migrations); expect($result)->toContain('2024_01_01_000000'); expect($migration->wasExecuted())->toBeTrue(); // Verify performance tracking was used expect($this->operationTracker)->toBeInstanceOf(OperationTracker::class); expect($this->memoryMonitor)->toBeInstanceOf(MemoryMonitor::class); }); test('memory monitor provides memory summary', function () { $memorySummary = $this->memoryMonitor->getSummary(); expect($memorySummary)->toHaveProperties([ 'current', 'peak', 'limit', 'usagePercentage', 'isApproachingLimit', ]); expect($memorySummary->getCurrentHumanReadable())->toBeString(); expect($memorySummary->getPeakHumanReadable())->toBeString(); expect($memorySummary->getUsagePercentageFormatted())->toBeString(); }); test('operation tracker can track operations', function () { $operationId = 'test_migration_batch_' . uniqid(); $snapshot = $this->operationTracker->startOperation( $operationId, \App\Framework\Performance\PerformanceCategory::DATABASE, ['operation_type' => 'migration_test'] ); expect($snapshot)->toBeInstanceOf(\App\Framework\Performance\ValueObjects\PerformanceSnapshot::class); // Complete the operation $finalSnapshot = $this->operationTracker->completeOperation($operationId); expect($finalSnapshot)->toBeInstanceOf(\App\Framework\Performance\ValueObjects\PerformanceSnapshot::class); expect($finalSnapshot->duration)->toBeInstanceOf(\App\Framework\Core\ValueObjects\Duration::class); }); test('memory thresholds can be configured with custom values', function () { $customThresholds = new MemoryThresholds( warning: Percentage::from(60.0), critical: Percentage::from(75.0), abort: Percentage::from(90.0) ); expect($customThresholds->warning->getValue())->toBe(60.0); expect($customThresholds->critical->getValue())->toBe(75.0); expect($customThresholds->abort->getValue())->toBe(90.0); }); test('memory thresholds default configuration is valid', function () { $defaultThresholds = MemoryThresholds::default(); expect($defaultThresholds->warning->getValue())->toBe(75.0); expect($defaultThresholds->critical->getValue())->toBe(85.0); expect($defaultThresholds->abort->getValue())->toBe(95.0); }); test('memory thresholds conservative configuration is valid', function () { $conservativeThresholds = MemoryThresholds::conservative(); expect($conservativeThresholds->warning->getValue())->toBe(60.0); expect($conservativeThresholds->critical->getValue())->toBe(70.0); expect($conservativeThresholds->abort->getValue())->toBe(80.0); }); test('memory thresholds relaxed configuration is valid', function () { $relaxedThresholds = MemoryThresholds::relaxed(); expect($relaxedThresholds->warning->getValue())->toBe(80.0); expect($relaxedThresholds->critical->getValue())->toBe(90.0); expect($relaxedThresholds->abort->getValue())->toBe(98.0); }); test('memory threshold exception can be created for migration', function () { $currentUsage = Percentage::from(96.0); $threshold = Percentage::from(95.0); $currentMemory = \App\Framework\Core\ValueObjects\Byte::fromBytes(256 * 1024 * 1024); $memoryLimit = \App\Framework\Core\ValueObjects\Byte::fromBytes(256 * 1024 * 1024); $exception = MemoryThresholdExceededException::forMigration( '2024_01_01_000001', $currentUsage, $threshold, $currentMemory, $memoryLimit ); expect($exception->getMessage())->toContain('Memory threshold exceeded during migration 2024_01_01_000001'); expect($exception->getMessage())->toContain('96.0%'); expect($exception->getMessage())->toContain('95.0%'); }); test('memory threshold exception can be created for batch abort', function () { $currentUsage = Percentage::from(97.0); $abortThreshold = Percentage::from(95.0); $currentMemory = \App\Framework\Core\ValueObjects\Byte::fromBytes(248 * 1024 * 1024); $memoryLimit = \App\Framework\Core\ValueObjects\Byte::fromBytes(256 * 1024 * 1024); $exception = MemoryThresholdExceededException::batchAborted( $currentUsage, $abortThreshold, $currentMemory, $memoryLimit, 3, 10 ); expect($exception->getMessage())->toContain('Migration batch aborted due to critical memory usage'); expect($exception->getMessage())->toContain('97.0%'); expect($exception->getMessage())->toContain('95.0%'); }); test('migration runner with memory thresholds can handle normal operation', function () { $memoryThresholds = MemoryThresholds::conservative(); $migrationRunner = new MigrationRunner( $this->connection, $this->platform, $this->clock, null, // tableConfig null, // logger $this->operationTracker, $this->memoryMonitor, null, // performanceReporter $memoryThresholds ); $migration = new MigrationPerformanceTestMigration(); $migrations = new MigrationCollection($migration); // Set no applied migrations initially $this->connection->setAppliedMigrations([]); $result = $migrationRunner->migrate($migrations); expect($result)->toContain('2024_01_01_000000'); expect($migration->wasExecuted())->toBeTrue(); }); test('rollback operation includes performance tracking', function () { $migration = new MigrationPerformanceTestMigration(); $migrations = new MigrationCollection($migration); // Set migration as already applied $this->connection->setAppliedMigrations([ ['version' => '2024_01_01_000000', 'description' => 'Performance Test Migration'], ]); $result = $this->migrationRunner->rollback($migrations, 1); expect($result)->toContain($migration); // Verify performance tracking was used during rollback expect($this->operationTracker)->toBeInstanceOf(OperationTracker::class); expect($this->memoryMonitor)->toBeInstanceOf(MemoryMonitor::class); }); test('operation tracker can track multiple operations', function () { $operationId1 = 'test_migration_1'; $operationId2 = 'test_migration_2'; // Start first operation $snapshot1 = $this->operationTracker->startOperation( $operationId1, \App\Framework\Performance\PerformanceCategory::DATABASE, ['operation_type' => 'migration_execution'] ); // Start second operation $snapshot2 = $this->operationTracker->startOperation( $operationId2, \App\Framework\Performance\PerformanceCategory::DATABASE, ['operation_type' => 'migration_execution'] ); usleep(1000); // Small delay to ensure duration > 0 // Complete both operations $finalSnapshot1 = $this->operationTracker->completeOperation($operationId1); $finalSnapshot2 = $this->operationTracker->completeOperation($operationId2); expect($finalSnapshot1)->toBeInstanceOf(\App\Framework\Performance\ValueObjects\PerformanceSnapshot::class); expect($finalSnapshot2)->toBeInstanceOf(\App\Framework\Performance\ValueObjects\PerformanceSnapshot::class); expect($finalSnapshot1->operationId)->toBe($operationId1); expect($finalSnapshot2->operationId)->toBe($operationId2); }); test('migration runner validates memory threshold order', function () { expect(function () { new MemoryThresholds( warning: Percentage::from(90.0), // Warning higher than critical critical: Percentage::from(80.0), abort: Percentage::from(95.0) ); })->toThrow(\InvalidArgumentException::class, 'Warning threshold cannot be higher than critical threshold'); expect(function () { new MemoryThresholds( warning: Percentage::from(70.0), critical: Percentage::from(90.0), // Critical higher than abort abort: Percentage::from(80.0) ); })->toThrow(\InvalidArgumentException::class, 'Critical threshold cannot be higher than abort threshold'); }); test('migration runner can handle pre-flight checks', function () { $migration = new MigrationPerformanceTestMigration(); $migrations = new MigrationCollection($migration); // Set no applied migrations initially $this->connection->setAppliedMigrations([]); // Should pass pre-flight checks for mock connection $result = $this->migrationRunner->migrate($migrations); expect($result)->toContain('2024_01_01_000000'); expect($migration->wasExecuted())->toBeTrue(); }); }); // Test fixtures class MigrationPerformanceTestMigration implements Migration { private bool $executed = false; public function up(ConnectionInterface $connection): void { $this->executed = true; // Simulate migration execution $connection->execute(SqlQuery::create('CREATE TABLE performance_test (id INT)')); } public function down(ConnectionInterface $connection): void { $connection->execute(SqlQuery::create('DROP TABLE performance_test')); } public function getDescription(): string { return 'Performance Test Migration'; } public function getVersion(): MigrationVersion { return MigrationVersion::fromTimestamp('2024_01_01_000000'); } public function wasExecuted(): bool { return $this->executed; } } class MigrationPerformanceMockConnection implements ConnectionInterface { private array $queries = []; private array $appliedMigrations = []; private bool $inTransaction = false; public function setAppliedMigrations(array $migrations): void { $this->appliedMigrations = $migrations; } public function execute(SqlQuery $query): int { $this->queries[] = ['type' => 'execute', 'sql' => $query->sql, 'params' => $query->parameters->toArray()]; return 1; } public function query(SqlQuery $query): ResultInterface { $this->queries[] = ['type' => 'query', 'sql' => $query->sql, 'params' => $query->parameters->toArray()]; return new MigrationPerformanceMockResult($this->appliedMigrations); } public function queryOne(SqlQuery $query): ?array { return null; } public function queryColumn(SqlQuery $query): array { $this->queries[] = ['type' => 'queryColumn', 'sql' => $query->sql, 'params' => $query->parameters->toArray()]; return array_column($this->appliedMigrations, 'version'); } public function queryScalar(SqlQuery $query): mixed { $this->queries[] = ['type' => 'queryScalar', 'sql' => $query->sql, 'params' => $query->parameters->toArray()]; // Return '1' for connectivity test (SELECT 1) if (str_contains($query->sql, 'SELECT 1')) { return '1'; } return null; } public function beginTransaction(): void { $this->inTransaction = true; } public function commit(): void { $this->inTransaction = false; } public function rollback(): void { $this->inTransaction = false; } public function inTransaction(): bool { return $this->inTransaction; } public function lastInsertId(): string { return '1'; } public function getPdo(): \PDO { return new class () extends \PDO { public function __construct() { // Skip parent constructor to avoid actual DB connection } public function getAttribute(int $attribute): mixed { return match($attribute) { \PDO::ATTR_DRIVER_NAME => 'mysql', default => null }; } }; } public function getQueries(): array { return $this->queries; } } class MigrationPerformanceMockResult implements ResultInterface { private array $data; public function __construct(array $data) { $this->data = $data; } public function fetch(): ?array { return $this->data[0] ?? null; } public function fetchAll(): array { return $this->data; } public function fetchOne(): ?array { return $this->data[0] ?? null; } public function fetchColumn(int $column = 0): array { return array_column($this->data, $column); } public function fetchScalar(): mixed { $row = $this->fetchOne(); return $row ? array_values($row)[0] : null; } public function rowCount(): int { return count($this->data); } public function getIterator(): \Traversable { return new \ArrayIterator($this->data); } public function count(): int { return count($this->data); } }