benchmark/ misst Sync-Schreiben, App-Start, Chat oeffnen, lokale Suche und die Migrationsdauer (A-B-B-A, Aufwaermlauf, Median), schreibt build/benchmarks/*.json und vergleicht mit benchmark/baseline/. scripts/test.ps1 fuehrt Analyse, Unit-Tests, optional Live-Test (-Live) und Benchmarks (-Bench) mit einem Befehl aus. Co-Authored-By: Claude Opus 5.5 <[email protected]>
125 lines
4.4 KiB
Dart
125 lines
4.4 KiB
Dart
// 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<void> 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)),
|
||
);
|
||
}
|