// Benchmarks sind Test-Code, liegen aber bewusst außerhalb von test/ (sie // sollen in einem normalen `flutter test` NICHT mitlaufen) – deshalb kennt // der Analyzer sie nicht als Tests. // ignore_for_file: invalid_use_of_visible_for_testing_member @TestOn('windows') library; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as p; import 'package:pyramid/core/app_database.dart'; import '../test/support/synthetic_matrix_data.dart'; import 'support/bench.dart'; /// Migrations-Benchmark: Wie lange dauert die einmalige Umstellung /// Klartext → SQLCipher beim ersten Start nach dem Update (Utas Fall)? /// /// Baut eine Klartext-DB über die echte SDK-Speicherschicht, kopiert sie vor /// jedem Durchlauf frisch und misst `AppDatabase.migratePlaintextToEncrypted` /// (exakt der App-Codepfad: Checkpoint, Backup, Export, Verifikation, Tausch). /// Danach wird geprüft, dass die Raumliste aus der verschlüsselten DB wieder /// vollständig lädt. Ergebnis zusätzlich als ms pro MB – damit lässt sich die /// Dauer für eine echte DB-Größe hochrechnen. const _benchKey = '5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b'; void main() { final skip = findCipherDll() == null ? 'SQLCipher-DLL fehlt – erst "flutter build windows" ausführen' : null; test( 'Migration Klartext → SQLCipher', () async { final rooms = scaled(20); final eventsPerRoom = scaled(1000); final report = BenchReport( 'migration', params: {'rooms': rooms, 'eventsPerRoom': eventsPerRoom}, ); final factory = cipherFactory(); final tempDir = await Directory.systemTemp.createTemp( 'pyramid_bench_migration', ); try { // Vorlage: Klartext-DB wie vor der SQLCipher-Einführung. final templatePath = p.join(tempDir.path, 'template.sqlite'); var db = await factory.openDatabase(templatePath); await applyAppPragmas(db); var client = await SyntheticMatrixData.openClient(db); await SyntheticMatrixData.populate( client, rooms: rooms, eventsPerRoom: eventsPerRoom, ); await client.dispose(closeDatabase: true); // WAL in die Hauptdatei schreiben, damit die Kopie vollständig ist. db = await factory.openDatabase(templatePath); await db.rawQuery('PRAGMA wal_checkpoint(TRUNCATE)'); await db.close(); final sizeMb = dbSizeMb(templatePath); final workPath = p.join(tempDir.path, 'pyramid.sqlite'); Future freshCopy() async { for (final suffix in ['', '-wal', '-shm', '.enc', '.premigration']) { final f = File('$workPath$suffix'); if (f.existsSync()) f.deleteSync(); } await File(templatePath).copy(workPath); } final times = await sample( () async { final ok = await AppDatabase.migratePlaintextToEncrypted( factory: factory, dbPath: workPath, hexKey: _benchKey, ); if (!ok) throw StateError('Migration fehlgeschlagen'); }, setup: freshCopy, warmup: 0, runs: 3, ); // Ergebnis der letzten Migration prüfen: verschlüsselt + vollständig. expect(await AppDatabase.isPlaintextSqlite(File(workPath)), isFalse); db = await AppDatabase.openEncrypted(factory, workPath, _benchKey); client = await SyntheticMatrixData.openClient(db); final loaded = await SyntheticMatrixData.loadRooms(client); final firstPage = await client.database.getEventList( loaded.first, limit: 50, ); await client.dispose(closeDatabase: true); expect(loaded, hasLength(rooms)); expect(firstPage, hasLength(50)); report.addValue('db_size_mb', 'DB-Größe vor Migration', sizeMb, 'MB'); report.addTimings('migrate_ms', 'Migration gesamt', times); report.addValue( 'migrate_ms_per_mb', 'Migration je MB', median(times) / sizeMb, 'ms/MB', compare: true, ); final regressions = await report.finish(); if (strictBench) expect(regressions, isEmpty); } finally { try { await tempDir.delete(recursive: true); } catch (_) {} } }, skip: skip, timeout: const Timeout(Duration(minutes: 20)), ); }