chore: enable discarded_futures lint

BREAKING CHANGE: This changes the runInRoot method to not return a
future. As a user, if you need the result of an async computation passed
to runInRoot, please await it directly. Also the KeyVerification start
and a few call methods now return a future.
This commit is contained in:
Nicolas Werner 2023-11-02 13:02:38 +01:00
parent 5dc3adadfc
commit 8b8a647cf9
No known key found for this signature in database
GPG Key ID: B38119FF80087618
26 changed files with 115 additions and 90 deletions

View File

@ -6,6 +6,8 @@ linter:
always_use_package_imports: true always_use_package_imports: true
avoid_bool_literals_in_conditional_expressions: true avoid_bool_literals_in_conditional_expressions: true
avoid_print: true avoid_print: true
cancel_subscriptions: true
discarded_futures: true
non_constant_identifier_names: false # seems to wrongly diagnose static const variables non_constant_identifier_names: false # seems to wrongly diagnose static const variables
prefer_final_in_for_each: true prefer_final_in_for_each: true
prefer_final_locals: true prefer_final_locals: true

View File

@ -100,11 +100,12 @@ class Encryption {
void handleDeviceOneTimeKeysCount( void handleDeviceOneTimeKeysCount(
Map<String, int>? countJson, List<String>? unusedFallbackKeyTypes) { Map<String, int>? countJson, List<String>? unusedFallbackKeyTypes) {
runInRoot(() => olmManager.handleDeviceOneTimeKeysCount( runInRoot(() async => olmManager.handleDeviceOneTimeKeysCount(
countJson, unusedFallbackKeyTypes)); countJson, unusedFallbackKeyTypes));
} }
void onSync() { void onSync() {
// ignore: discarded_futures
keyVerificationManager.cleanup(); keyVerificationManager.cleanup();
} }
@ -118,30 +119,25 @@ class Encryption {
.contains(event.type)) { .contains(event.type)) {
// "just" room key request things. We don't need these asap, so we handle // "just" room key request things. We don't need these asap, so we handle
// them in the background // them in the background
// ignore: unawaited_futures
runInRoot(() => keyManager.handleToDeviceEvent(event)); runInRoot(() => keyManager.handleToDeviceEvent(event));
} }
if (event.type == EventTypes.Dummy) { if (event.type == EventTypes.Dummy) {
// the previous device just had to create a new olm session, due to olm session // the previous device just had to create a new olm session, due to olm session
// corruption. We want to try to send it the last message we just sent it, if possible // corruption. We want to try to send it the last message we just sent it, if possible
// ignore: unawaited_futures
runInRoot(() => olmManager.handleToDeviceEvent(event)); runInRoot(() => olmManager.handleToDeviceEvent(event));
} }
if (event.type.startsWith('m.key.verification.')) { if (event.type.startsWith('m.key.verification.')) {
// some key verification event. No need to handle it now, we can easily // some key verification event. No need to handle it now, we can easily
// do this in the background // do this in the background
// ignore: unawaited_futures
runInRoot(() => keyVerificationManager.handleToDeviceEvent(event)); runInRoot(() => keyVerificationManager.handleToDeviceEvent(event));
} }
if (event.type.startsWith('m.secret.')) { if (event.type.startsWith('m.secret.')) {
// some ssss thing. We can do this in the background // some ssss thing. We can do this in the background
// ignore: unawaited_futures
runInRoot(() => ssss.handleToDeviceEvent(event)); runInRoot(() => ssss.handleToDeviceEvent(event));
} }
if (event.sender == client.userID) { if (event.sender == client.userID) {
// maybe we need to re-try SSSS secrets // maybe we need to re-try SSSS secrets
// ignore: unawaited_futures
runInRoot(() => ssss.periodicallyRequestMissingCache()); runInRoot(() => ssss.periodicallyRequestMissingCache());
} }
} }
@ -157,14 +153,11 @@ class Encryption {
update.content['content']['msgtype'] update.content['content']['msgtype']
.startsWith('m.key.verification.'))) { .startsWith('m.key.verification.'))) {
// "just" key verification, no need to do this in sync // "just" key verification, no need to do this in sync
// ignore: unawaited_futures
runInRoot(() => keyVerificationManager.handleEventUpdate(update)); runInRoot(() => keyVerificationManager.handleEventUpdate(update));
} }
if (update.content['sender'] == client.userID && if (update.content['sender'] == client.userID &&
update.content['unsigned']?['transaction_id'] == null) { update.content['unsigned']?['transaction_id'] == null) {
// maybe we need to re-try SSSS secrets // maybe we need to re-try SSSS secrets
// ignore: unawaited_futures
runInRoot(() => ssss.periodicallyRequestMissingCache()); runInRoot(() => ssss.periodicallyRequestMissingCache());
} }
} }
@ -239,6 +232,7 @@ class Encryption {
// the entry should always exist. In the case it doesn't, the following // the entry should always exist. In the case it doesn't, the following
// line *could* throw an error. As that is a future, though, and we call // line *could* throw an error. As that is a future, though, and we call
// it un-awaited here, nothing happens, which is exactly the result we want // it un-awaited here, nothing happens, which is exactly the result we want
// ignore: discarded_futures
client.database?.updateInboundGroupSessionIndexes( client.database?.updateInboundGroupSessionIndexes(
json.encode(inboundGroupSession.indexes), roomId, sessionId); json.encode(inboundGroupSession.indexes), roomId, sessionId);
} }
@ -252,7 +246,7 @@ class Encryption {
?.session_id() ?? ?.session_id() ??
'') == '') ==
content.sessionId) { content.sessionId) {
runInRoot(() => runInRoot(() async =>
keyManager.clearOrUseOutboundGroupSession(roomId, wipe: true)); keyManager.clearOrUseOutboundGroupSession(roomId, wipe: true));
} }
if (canRequestSession) { if (canRequestSession) {

View File

@ -244,7 +244,8 @@ class KeyManager {
!client.isUnknownSession) { !client.isUnknownSession) {
// do e2ee recovery // do e2ee recovery
_requestedSessionIds.add(requestIdent); _requestedSessionIds.add(requestIdent);
runInRoot(() => request(
runInRoot(() async => request(
room, room,
sessionId, sessionId,
senderKey, senderKey,
@ -775,8 +776,8 @@ class KeyManager {
Future<void>? _uploadingFuture; Future<void>? _uploadingFuture;
void startAutoUploadKeys() { void startAutoUploadKeys() {
_uploadKeysOnSync = encryption.client.onSync.stream _uploadKeysOnSync = encryption.client.onSync.stream.listen(
.listen((_) => uploadInboundGroupSessions(skipIfInProgress: true)); (_) async => uploadInboundGroupSessions(skipIfInProgress: true));
} }
/// This task should be performed after sync processing but should not block /// This task should be performed after sync processing but should not block
@ -1064,6 +1065,7 @@ class KeyManager {
StreamSubscription<SyncUpdate>? _uploadKeysOnSync; StreamSubscription<SyncUpdate>? _uploadKeysOnSync;
void dispose() { void dispose() {
// ignore: discarded_futures
_uploadKeysOnSync?.cancel(); _uploadKeysOnSync?.cancel();
for (final sess in _outboundGroupSessions.values) { for (final sess in _outboundGroupSessions.values) {
sess.dispose(); sess.dispose();

View File

@ -396,20 +396,24 @@ class OlmManager {
final device = client.userDeviceKeys[event.sender]?.deviceKeys.values final device = client.userDeviceKeys[event.sender]?.deviceKeys.values
.firstWhereOrNull((d) => d.curve25519Key == senderKey); .firstWhereOrNull((d) => d.curve25519Key == senderKey);
final existingSessions = olmSessions[senderKey]; final existingSessions = olmSessions[senderKey];
Future<void> updateSessionUsage([OlmSession? session]) => Future<void> updateSessionUsage([OlmSession? session]) async {
runInRoot(() async { try {
if (session != null) { if (session != null) {
session.lastReceived = DateTime.now(); session.lastReceived = DateTime.now();
await storeOlmSession(session); await storeOlmSession(session);
} }
if (device != null) { if (device != null) {
device.lastActive = DateTime.now(); device.lastActive = DateTime.now();
await encryption.olmDatabase?.setLastActiveUserDeviceKey( await encryption.olmDatabase?.setLastActiveUserDeviceKey(
device.lastActive.millisecondsSinceEpoch, device.lastActive.millisecondsSinceEpoch,
device.userId, device.userId,
device.deviceId!); device.deviceId!);
} }
}); } catch (e, s) {
Logs().e('Error while updating olm session timestamp', e, s);
}
}
if (existingSessions != null) { if (existingSessions != null) {
for (final session in existingSessions) { for (final session in existingSessions) {
if (session.session == null) { if (session.session == null) {
@ -446,14 +450,16 @@ class OlmManager {
newSession.create_inbound_from(_olmAccount!, senderKey, body); newSession.create_inbound_from(_olmAccount!, senderKey, body);
_olmAccount!.remove_one_time_keys(newSession); _olmAccount!.remove_one_time_keys(newSession);
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
plaintext = newSession.decrypt(type, body); plaintext = newSession.decrypt(type, body);
await runInRoot(() => storeOlmSession(OlmSession(
key: client.userID!, await storeOlmSession(OlmSession(
identityKey: senderKey, key: client.userID!,
sessionId: newSession.session_id(), identityKey: senderKey,
session: newSession, sessionId: newSession.session_id(),
lastReceived: DateTime.now(), session: newSession,
))); lastReceived: DateTime.now(),
));
await updateSessionUsage(); await updateSessionUsage();
} catch (e) { } catch (e) {
newSession.free(); newSession.free();
@ -570,8 +576,6 @@ class OlmManager {
return _decryptToDeviceEvent(event); return _decryptToDeviceEvent(event);
} catch (_) { } catch (_) {
// okay, the thing errored while decrypting. It is safe to assume that the olm session is corrupt and we should generate a new one // okay, the thing errored while decrypting. It is safe to assume that the olm session is corrupt and we should generate a new one
// ignore: unawaited_futures
runInRoot(() => restoreOlmSession(event.senderId, senderKey)); runInRoot(() => restoreOlmSession(event.senderId, senderKey));
rethrow; rethrow;
@ -658,14 +662,18 @@ class OlmManager {
final encryptResult = sess.first.session!.encrypt(json.encode(fullPayload)); final encryptResult = sess.first.session!.encrypt(json.encode(fullPayload));
await storeOlmSession(sess.first); await storeOlmSession(sess.first);
if (encryption.olmDatabase != null) { if (encryption.olmDatabase != null) {
await runInRoot( try {
() async => encryption.olmDatabase?.setLastSentMessageUserDeviceKey( await encryption.olmDatabase?.setLastSentMessageUserDeviceKey(
json.encode({ json.encode({
'type': type, 'type': type,
'content': payload, 'content': payload,
}), }),
device.userId, device.userId,
device.deviceId!)); device.deviceId!);
} catch (e, s) {
// we can ignore this error, since it would just make us use a different olm session possibly
Logs().w('Error while updating olm usage timestamp', e, s);
}
} }
final encryptedBody = <String, dynamic>{ final encryptedBody = <String, dynamic>{
'algorithm': AlgorithmTypes.olmV1Curve25519AesSha2, 'algorithm': AlgorithmTypes.olmV1Curve25519AesSha2,

View File

@ -31,7 +31,6 @@ import 'package:matrix/encryption/utils/ssss_cache.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart';
import 'package:matrix/src/utils/crypto/crypto.dart' as uc; import 'package:matrix/src/utils/crypto/crypto.dart' as uc;
import 'package:matrix/src/utils/run_in_root.dart';
const cacheTypes = <String>{ const cacheTypes = <String>{
EventTypes.CrossSigningSelfSigning, EventTypes.CrossSigningSelfSigning,
@ -722,7 +721,11 @@ class OpenSSSS {
throw InvalidPassphraseException('Inalid key'); throw InvalidPassphraseException('Inalid key');
} }
if (postUnlock) { if (postUnlock) {
await runInRoot(() => _postUnlock()); try {
await _postUnlock();
} catch (e, s) {
Logs().e('Error during post unlock', e, s);
}
} }
} }

View File

@ -3,7 +3,7 @@ import 'dart:typed_data';
/// decodes base64 /// decodes base64
/// ///
/// Dart's native [base64.decode] requires a padded base64 input String. /// Dart's native [base64.decode()] requires a padded base64 input String.
/// This function allows unpadded base64 too. /// This function allows unpadded base64 too.
/// ///
/// See: https://github.com/dart-lang/sdk/issues/39510 /// See: https://github.com/dart-lang/sdk/issues/39510

View File

@ -1636,7 +1636,7 @@ class Client extends MatrixApi {
set backgroundSync(bool enabled) { set backgroundSync(bool enabled) {
_backgroundSync = enabled; _backgroundSync = enabled;
if (_backgroundSync) { if (_backgroundSync) {
_sync(); runInRoot(() async => _sync());
} }
} }
@ -2232,7 +2232,7 @@ class Client extends MatrixApi {
requestHistoryOnLimitedTimeline) { requestHistoryOnLimitedTimeline) {
Logs().v( Logs().v(
'Limited timeline for ${rooms[roomIndex].id} request history now'); 'Limited timeline for ${rooms[roomIndex].id} request history now');
unawaited(runInRoot(rooms[roomIndex].requestHistory)); runInRoot(rooms[roomIndex].requestHistory);
} }
} }
return room; return room;

View File

@ -128,6 +128,7 @@ class Event extends MatrixEvent {
final json = toJson(); final json = toJson();
json['unsigned'] ??= <String, dynamic>{}; json['unsigned'] ??= <String, dynamic>{};
json['unsigned'][messageSendingStatusKey] = EventStatus.error.intValue; json['unsigned'][messageSendingStatusKey] = EventStatus.error.intValue;
// ignore: discarded_futures
room.client.handleSync( room.client.handleSync(
SyncUpdate( SyncUpdate(
nextBatch: '', nextBatch: '',
@ -154,6 +155,7 @@ class Event extends MatrixEvent {
MessageTypes.File, MessageTypes.File,
}.contains(messageType) && }.contains(messageType) &&
!room.sendingFilePlaceholders.containsKey(eventId)) { !room.sendingFilePlaceholders.containsKey(eventId)) {
// ignore: discarded_futures
remove(); remove();
} }
} }

View File

@ -1620,6 +1620,7 @@ class Room {
return user.asUser; return user.asUser;
} else { } else {
if (mxID.isValidMatrixId) { if (mxID.isValidMatrixId) {
// ignore: discarded_futures
requestUser( requestUser(
mxID, mxID,
ignoreErrors: true, ignoreErrors: true,

View File

@ -300,8 +300,11 @@ class Timeline {
/// Don't forget to call this before you dismiss this object! /// Don't forget to call this before you dismiss this object!
void cancelSubscriptions() { void cancelSubscriptions() {
// ignore: discarded_futures
sub?.cancel(); sub?.cancel();
// ignore: discarded_futures
roomSub?.cancel(); roomSub?.cancel();
// ignore: discarded_futures
sessionIdReceivedSub?.cancel(); sessionIdReceivedSub?.cancel();
} }

View File

@ -504,7 +504,7 @@ class DeviceKeys extends SignableKey {
lastActive = DateTime.fromMillisecondsSinceEpoch(0); lastActive = DateTime.fromMillisecondsSinceEpoch(0);
} }
KeyVerification startVerification() { Future<KeyVerification> startVerification() async {
if (!isValid) { if (!isValid) {
throw Exception('setVerification called on invalid key'); throw Exception('setVerification called on invalid key');
} }
@ -516,7 +516,7 @@ class DeviceKeys extends SignableKey {
final request = KeyVerification( final request = KeyVerification(
encryption: encryption, userId: userId, deviceId: deviceId!); encryption: encryption, userId: userId, deviceId: deviceId!);
request.start(); await request.start();
encryption.keyVerificationManager.addRequest(request); encryption.keyVerificationManager.addRequest(request);
return request; return request;
} }

View File

@ -50,10 +50,9 @@ abstract class NativeImplementations {
bool retryInDummy = false, bool retryInDummy = false,
}); });
@override
/// this implementation will catch any non-implemented method /// this implementation will catch any non-implemented method
dynamic noSuchMethod(Invocation invocation) { @override
dynamic noSuchMethod(Invocation invocation) async {
final dynamic argument = invocation.positionalArguments.single; final dynamic argument = invocation.positionalArguments.single;
final memberName = invocation.memberName.toString().split('"')[1]; final memberName = invocation.memberName.toString().split('"')[1];

View File

@ -20,13 +20,13 @@ import 'dart:async';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
Future<T?> runInRoot<T>(FutureOr<T> Function() fn) async { void runInRoot<T>(FutureOr<T> Function() fn) {
return await Zone.root.run(() async { // ignore: discarded_futures
Zone.root.run(() async {
try { try {
return await fn(); await fn();
} catch (e, s) { } catch (e, s) {
Logs().e('Error thrown in root zone', e, s); Logs().e('Error thrown in root zone', e, s);
} }
return null;
}); });
} }

View File

@ -52,6 +52,7 @@ class UiaRequest<T> {
} }
UiaRequest({this.onUpdate, required this.request}) { UiaRequest({this.onUpdate, required this.request}) {
// ignore: discarded_futures
_run(); _run();
} }

View File

@ -37,7 +37,7 @@ class NativeImplementationsWebWorker extends NativeImplementations {
return completer.future.timeout(timeout); return completer.future.timeout(timeout);
} }
void _handleIncomingMessage(MessageEvent event) { Future<void> _handleIncomingMessage(MessageEvent event) async {
final data = event.data; final data = event.data;
// don't forget handling errors of our second thread... // don't forget handling errors of our second thread...
if (data['label'] == 'stacktrace') { if (data['label'] == 'stacktrace') {
@ -46,12 +46,10 @@ class NativeImplementationsWebWorker extends NativeImplementations {
final error = event.data['error']!; final error = event.data['error']!;
Future.value( final stackTrace =
onStackTrace.call(event.data['stacktrace'] as String), await onStackTrace.call(event.data['stacktrace'] as String);
).then( completer?.completeError(
(stackTrace) => completer?.completeError( WebWorkerError(error: error, stackTrace: stackTrace),
WebWorkerError(error: error, stackTrace: stackTrace),
),
); );
} else { } else {
final response = WebWorkerData.fromJson(event.data); final response = WebWorkerData.fromJson(event.data);

View File

@ -522,17 +522,18 @@ class CallSession {
await gotCallFeedsForAnswer(callFeeds); await gotCallFeedsForAnswer(callFeeds);
} }
void replacedBy(CallSession newCall) { Future<void> replacedBy(CallSession newCall) async {
if (state == CallState.kWaitLocalMedia) { if (state == CallState.kWaitLocalMedia) {
Logs().v('Telling new call to wait for local media'); Logs().v('Telling new call to wait for local media');
newCall.waitForLocalAVStream = true; newCall.waitForLocalAVStream = true;
} else if (state == CallState.kCreateOffer || } else if (state == CallState.kCreateOffer ||
state == CallState.kInviteSent) { state == CallState.kInviteSent) {
Logs().v('Handing local stream to new call'); Logs().v('Handing local stream to new call');
newCall.gotCallFeedsForAnswer(getLocalStreams); await newCall.gotCallFeedsForAnswer(getLocalStreams);
} }
successor = newCall; successor = newCall;
onCallReplaced.add(newCall); onCallReplaced.add(newCall);
// ignore: unawaited_futures
hangup(CallErrorCode.Replaced, true); hangup(CallErrorCode.Replaced, true);
} }
@ -1510,8 +1511,9 @@ class CallSession {
return pc; return pc;
} }
void createDataChannel(String label, RTCDataChannelInit dataChannelDict) { Future<void> createDataChannel(
pc?.createDataChannel(label, dataChannelDict); String label, RTCDataChannelInit dataChannelDict) async {
await pc?.createDataChannel(label, dataChannelDict);
} }
Future<void> tryRemoveStopedStreams() async { Future<void> tryRemoveStopedStreams() async {

View File

@ -45,11 +45,15 @@ class ConnectionTester {
await pc1!.setRemoteDescription(answer); await pc1!.setRemoteDescription(answer);
void dispose() { Future<void> dispose() async {
pc1!.close(); await Future.wait([
pc1!.dispose(); pc1!.close(),
pc2!.close(); pc2!.close(),
pc2!.dispose(); ]);
await Future.wait([
pc1!.dispose(),
pc2!.dispose(),
]);
} }
bool connected = false; bool connected = false;
@ -69,6 +73,7 @@ class ConnectionTester {
.e('[VOIP] ConnectionTester Error while testing TURN server: ', e, s); .e('[VOIP] ConnectionTester Error while testing TURN server: ', e, s);
} }
// ignore: unawaited_futures
dispose(); dispose();
return connected; return connected;
} }

View File

@ -232,11 +232,11 @@ class GroupCall {
this.groupCallId = groupCallId ?? genCallID(); this.groupCallId = groupCallId ?? genCallID();
} }
GroupCall create() { Future<GroupCall> create() async {
voip.groupCalls[groupCallId] = this; voip.groupCalls[groupCallId] = this;
voip.groupCalls[room.id] = this; voip.groupCalls[room.id] = this;
client.setRoomStateWithKey( await client.setRoomStateWithKey(
room.id, room.id,
EventTypes.GroupCallPrefix, EventTypes.GroupCallPrefix,
groupCallId, groupCallId,
@ -413,6 +413,7 @@ class GroupCall {
if (localUserMediaStream != null) { if (localUserMediaStream != null) {
final oldStream = localUserMediaStream!.stream; final oldStream = localUserMediaStream!.stream;
localUserMediaStream!.setNewStream(stream.stream!); localUserMediaStream!.setNewStream(stream.stream!);
// ignore: discarded_futures
stopMediaStream(oldStream); stopMediaStream(oldStream);
} }
} }

View File

@ -53,6 +53,7 @@ class VoIP {
for (final room in client.rooms) { for (final room in client.rooms) {
if (room.activeGroupCallEvents.isNotEmpty) { if (room.activeGroupCallEvents.isNotEmpty) {
for (final groupCall in room.activeGroupCallEvents) { for (final groupCall in room.activeGroupCallEvents) {
// ignore: discarded_futures
createGroupCallFromRoomStateEvent(groupCall, createGroupCallFromRoomStateEvent(groupCall,
emitHandleNewGroupCall: false); emitHandleNewGroupCall: false);
} }
@ -589,7 +590,7 @@ class VoIP {
return null; return null;
} }
final groupId = genCallID(); final groupId = genCallID();
final groupCall = GroupCall( final groupCall = await GroupCall(
groupCallId: groupId, groupCallId: groupId,
client: client, client: client,
voip: this, voip: this,

View File

@ -1137,8 +1137,8 @@ void main() {
reason: '!5345234235:example.com not found as archived room'); reason: '!5345234235:example.com not found as archived room');
}); });
tearDown(() { tearDown(() async {
matrix.dispose(closeDatabase: true); await matrix.dispose(closeDatabase: true);
}); });
}); });
} }

View File

@ -27,7 +27,7 @@ import 'package:matrix/matrix.dart';
import 'fake_database.dart'; import 'fake_database.dart';
void main() { void main() {
group('HiveCollections Database Test', () { group('HiveCollections Database Test', () async {
testDatabase( testDatabase(
getHiveCollectionsDatabase(null), getHiveCollectionsDatabase(null),
); );

View File

@ -249,7 +249,7 @@ void main() {
test('start verification', () async { test('start verification', () async {
if (!olmEnabled) return; if (!olmEnabled) return;
var req = client var req = await client
.userDeviceKeys['@alice:example.com']?.deviceKeys['JLAFKJWSCS'] .userDeviceKeys['@alice:example.com']?.deviceKeys['JLAFKJWSCS']
?.startVerification(); ?.startVerification();
expect(req != null, true); expect(req != null, true);

View File

@ -260,6 +260,7 @@ class FakeMatrixApi extends BaseClient {
} }
} }
// and generate a fake sync // and generate a fake sync
// ignore: discarded_futures
_client!.handleSync(sdk.SyncUpdate(nextBatch: '')); _client!.handleSync(sdk.SyncUpdate(nextBatch: ''));
} }
return {}; return {};

View File

@ -50,7 +50,7 @@ void main() {
insertList.clear(); insertList.clear();
}); });
tearDown(() => client.dispose().onError((e, s) {})); tearDown(() async => client.dispose().onError((e, s) {}));
test('archive room not loaded', () async { test('archive room not loaded', () async {
final archiveRoom = final archiveRoom =

View File

@ -38,7 +38,7 @@ void main() {
var olmEnabled = true; var olmEnabled = true;
final countStream = StreamController<int>.broadcast(); final countStream = StreamController<int>.broadcast();
Future<int> waitForCount(int count) { Future<int> waitForCount(int count) async {
if (updateCount == count) { if (updateCount == count) {
return Future.value(updateCount); return Future.value(updateCount);
} }
@ -46,9 +46,9 @@ void main() {
final completer = Completer<int>(); final completer = Completer<int>();
StreamSubscription<int>? sub; StreamSubscription<int>? sub;
sub = countStream.stream.listen((newCount) { sub = countStream.stream.listen((newCount) async {
if (newCount == count) { if (newCount == count) {
sub?.cancel(); await sub?.cancel();
completer.complete(count); completer.complete(count);
} }
}); });
@ -100,7 +100,8 @@ void main() {
testTimeStamp = DateTime.now().millisecondsSinceEpoch; testTimeStamp = DateTime.now().millisecondsSinceEpoch;
}); });
tearDown(() => client.dispose(closeDatabase: true).onError((e, s) {})); tearDown(
() async => client.dispose(closeDatabase: true).onError((e, s) {}));
test('Request future', () async { test('Request future', () async {
timeline.events.clear(); timeline.events.clear();

View File

@ -39,7 +39,7 @@ void main() {
var currentPoison = 0; var currentPoison = 0;
final countStream = StreamController<int>.broadcast(); final countStream = StreamController<int>.broadcast();
Future<int> waitForCount(int count) { Future<int> waitForCount(int count) async {
if (updateCount == count) { if (updateCount == count) {
return Future.value(updateCount); return Future.value(updateCount);
} }
@ -47,9 +47,9 @@ void main() {
final completer = Completer<int>(); final completer = Completer<int>();
StreamSubscription<int>? sub; StreamSubscription<int>? sub;
sub = countStream.stream.listen((newCount) { sub = countStream.stream.listen((newCount) async {
if (newCount == count) { if (newCount == count) {
sub?.cancel(); await sub?.cancel();
completer.complete(count); completer.complete(count);
} }
}); });
@ -109,7 +109,8 @@ void main() {
testTimeStamp = DateTime.now().millisecondsSinceEpoch; testTimeStamp = DateTime.now().millisecondsSinceEpoch;
}); });
tearDown(() => client.dispose(closeDatabase: true).onError((e, s) {})); tearDown(
() async => client.dispose(closeDatabase: true).onError((e, s) {}));
test('Create', () async { test('Create', () async {
client.onEvent.add(EventUpdate( client.onEvent.add(EventUpdate(