Skip to content

Commit

Permalink
Update matcher to handle async callbacks. (#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
polina-c authored Sep 18, 2023
1 parent ca43245 commit bdf1e6c
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 42 deletions.
4 changes: 4 additions & 0 deletions pkgs/leak_tracker_flutter_testing/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.0.4

* Update matcher for memory events to handle async callbacks.

# 1.0.3

* Define matcher to verify if a class is reporting memory allocations.
Expand Down
65 changes: 32 additions & 33 deletions pkgs/leak_tracker_flutter_testing/lib/src/matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,55 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

/// Checks if the object dispatches events to `MemoryAllocations.instance`.
///
/// The memory events are used by tools like leak_tracker for diagnostics.
///
/// The matcher checks that the object object is instrumented properly,
/// dispatches two events to `MemoryAllocations.instance`,
/// first `ObjectCreated` and then `ObjectDisposed`.
Matcher dispatchesMemoryEvents(Type type) {
return _DispatchesMemoryEvents(type);
/// Invokes [callback] and collects events dispatched to [MemoryAllocations.instance] for [type].
Future<List<ObjectEvent>> memoryEvents(
FutureOr<void> Function() callback,
Type type,
) async {
final events = <ObjectEvent>[];

void listener(ObjectEvent event) {
if (event.object.runtimeType == type) {
events.add(event);
}
}

MemoryAllocations.instance.addListener(listener);
await callback();
MemoryAllocations.instance.removeListener(listener);

return events;
}

class _DispatchesMemoryEvents extends Matcher {
const _DispatchesMemoryEvents(this.type);
/// Checks if Iterable<ObjectEvent> contains two events, first `ObjectCreated` and then `ObjectDisposed`.
Matcher areCreateAndDispose = const _AreCreateAndDispose();

class _AreCreateAndDispose extends Matcher {
const _AreCreateAndDispose();

static const _key = 'description';
final Type type;

@override
bool matches(Object? item, Map matchState) {
if (item is! Function()) {
matchState[_key] = 'The matcher applies to `Function()`.';
if (item is! Iterable<ObjectEvent>) {
matchState[_key] = 'The matcher applies to $Iterable<$ObjectEvent>.';
return false;
}

final events = <ObjectEvent>[];

void listener(ObjectEvent event) {
if (event.object.runtimeType == type) {
events.add(event);
}
}

MemoryAllocations.instance.addListener(listener);
item();
MemoryAllocations.instance.removeListener(listener);

if (events.length == 2 &&
events.first is ObjectCreated &&
events.last is ObjectDisposed) {
if (item.length == 2 &&
item.first is ObjectCreated &&
item.last is ObjectDisposed) {
return true;
}

matchState[_key] =
'createAndDispose is expected to dispatch two events to $MemoryAllocations.instance,'
' for the type $item,'
' first $ObjectCreated and then $ObjectDisposed.\n'
'Instead, it dispatched ${events.length} events:\n$events';
'The events are expected to be first $ObjectCreated and then $ObjectDisposed.\n'
'Instead, they are ${item.length} events:\n$item.';

return false;
}
Expand Down
2 changes: 1 addition & 1 deletion pkgs/leak_tracker_flutter_testing/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: leak_tracker_flutter_testing
version: 1.0.3
version: 1.0.4
description: Flutter specific helpers for dart memory leak tracking.
repository: https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_flutter_testing

Expand Down
38 changes: 30 additions & 8 deletions pkgs/leak_tracker_flutter_testing/test/tests/matchers_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,39 @@ class _TrackedClass {
}

void main() {
test('dispatchesMemoryEvents success', () {
expect(
() => _TrackedClass().dispose(),
dispatchesMemoryEvents(_TrackedClass),
test('dispatchesMemoryEvents success sync', () async {
await expectLater(
await memoryEvents(() => _TrackedClass().dispose(), _TrackedClass),
areCreateAndDispose,
);
});

test('dispatchesMemoryEvents failure', () {
expect(
() => expect(() {}, dispatchesMemoryEvents(_TrackedClass)),
throwsA(isA<TestFailure>()),
test('dispatchesMemoryEvents failure sync', () async {
try {
await expectLater(
await memoryEvents(() {}, _TrackedClass),
areCreateAndDispose,
);
} catch (e) {
expect(e, isA<TestFailure>());
}
});

test('dispatchesMemoryEvents success async', () async {
await expectLater(
await memoryEvents(() async => _TrackedClass().dispose(), _TrackedClass),
areCreateAndDispose,
);
});

test('dispatchesMemoryEvents failure async', () async {
try {
await expectLater(
await memoryEvents(() async {}, _TrackedClass),
areCreateAndDispose,
);
} catch (e) {
expect(e, isA<TestFailure>());
}
});
}

0 comments on commit bdf1e6c

Please sign in to comment.