fix(db): prevent concurrent startup locks

This commit is contained in:
zarzet
2026-08-08 00:01:00 +07:00
parent 6742fe886f
commit 2612da81c3
6 changed files with 185 additions and 54 deletions
+91
View File
@@ -0,0 +1,91 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/services/sqlite_helpers.dart';
void main() {
group('SingleFlightInitializer', () {
test('coalesces concurrent initialization and caches the result', () async {
final initializer = SingleFlightInitializer<int>();
final gate = Completer<void>();
var calls = 0;
Future<int> create() async {
calls++;
await gate.future;
return 42;
}
final first = initializer.getOrCreate(create);
final second = initializer.getOrCreate(create);
expect(identical(first, second), isTrue);
expect(calls, 1);
gate.complete();
expect(await Future.wait([first, second]), [42, 42]);
expect(await initializer.getOrCreate(create), 42);
expect(calls, 1);
});
test('allows retry after initialization fails', () async {
final initializer = SingleFlightInitializer<int>();
var calls = 0;
Future<int> create() {
calls++;
if (calls == 1) throw StateError('first attempt failed');
return Future<int>.value(7);
}
await expectLater(initializer.getOrCreate(create), throwsStateError);
expect(await initializer.getOrCreate(create), 7);
expect(calls, 2);
});
test('reset permits a fresh initialization', () async {
final initializer = SingleFlightInitializer<int>();
var value = 1;
expect(await initializer.getOrCreate(() async => value), 1);
value = 2;
expect(await initializer.getOrCreate(() async => value), 1);
initializer.reset();
expect(await initializer.getOrCreate(() async => value), 2);
});
});
group('SQLite startup configuration', () {
final source = File('lib/services/sqlite_helpers.dart').readAsStringSync();
test('waits for transient locks before configuring the connection', () {
final configureIndex = source.indexOf('onConfigure:');
final busyTimeoutIndex = source.indexOf('PRAGMA busy_timeout');
final journalModeIndex = source.indexOf('PRAGMA journal_mode');
expect(configureIndex, greaterThanOrEqualTo(0));
expect(busyTimeoutIndex, greaterThan(configureIndex));
expect(busyTimeoutIndex, lessThan(journalModeIndex));
});
test('sets incremental auto-vacuum only before app tables exist', () {
final schemaCheckIndex = source.indexOf('FROM sqlite_master');
final emptySchemaGuardIndex = source.indexOf('if (tables.isEmpty)');
final autoVacuumMatches = RegExp(
r"PRAGMA auto_vacuum = INCREMENTAL",
).allMatches(source).toList();
final onCreateIndex = source.indexOf('onCreate:');
expect(autoVacuumMatches, hasLength(1));
expect(schemaCheckIndex, greaterThanOrEqualTo(0));
expect(emptySchemaGuardIndex, greaterThan(schemaCheckIndex));
expect(
autoVacuumMatches.single.start,
greaterThan(emptySchemaGuardIndex),
);
expect(autoVacuumMatches.single.start, lessThan(onCreateIndex));
});
});
}