feat: Benchmarks (DB Klartext vs. SQLCipher, Migration) + scripts/test.ps1
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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f4d334a60b
commit
df1b392ed8
@@ -0,0 +1,124 @@
|
||||
// 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)),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user