Skip to content

Commit c2f5b40

Browse files
authored
Add deeplyEqual() method for use in replay (#25)
Part of flutter#11
1 parent b2689ff commit c2f5b40

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

lib/src/backends/record_replay/common.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ const String kManifestPositionalArgumentsKey = 'positionalArguments';
5151
/// named arguments that were passed to the method.
5252
const String kManifestNamedArgumentsKey = 'namedArguments';
5353

54+
/// The key in a serialized [InvocationEvent] map that is used to store whether
55+
/// the invocation has been replayed already.
56+
const String kManifestReplayedKey = 'replayed';
57+
5458
/// The serialized [kManifestTypeKey] for property retrievals.
5559
const String kGetType = 'get';
5660

@@ -83,3 +87,41 @@ class TypeMatcher<T> {
8387
/// Returns `true` if the given object is of type `T`.
8488
bool matches(dynamic object) => object is T;
8589
}
90+
91+
/// Tells whether two objects are equal using deep equality checking.
92+
///
93+
/// Two lists are deeply equal if they have the same runtime type, the same
94+
/// length, and every element in list A is pairwise deeply equal with the
95+
/// corresponding element in list B.
96+
///
97+
/// Two maps are deeply equal if they have the same runtime type, the same
98+
/// length, the same set of keys, and the value for every key in map A is
99+
/// deeply equal to the corresponding value in map B.
100+
///
101+
/// All other types of objects are deeply equal if they have the same runtime
102+
/// type and are logically equal (according to `operator==`).
103+
bool deeplyEqual(dynamic object1, dynamic object2) {
104+
if (object1.runtimeType != object2.runtimeType) {
105+
return false;
106+
} else if (object1 is List<dynamic>) {
107+
return _areListsEqual(object1, object2);
108+
} else if (object1 is Map<dynamic, dynamic>) {
109+
return _areMapsEqual(object1, object2);
110+
} else {
111+
return object1 == object2;
112+
}
113+
}
114+
115+
bool _areListsEqual<T>(List<T> list1, List<T> list2) {
116+
int i = 0;
117+
return list1.length == list2.length &&
118+
list1.every((T element) => deeplyEqual(element, list2[i++]));
119+
}
120+
121+
bool _areMapsEqual<K, V>(Map<K, V> map1, Map<K, V> map2) {
122+
return map1.length == map2.length &&
123+
map1.keys.every((K key) {
124+
return map1.containsKey(key) == map2.containsKey(key) &&
125+
deeplyEqual(map1[key], map2[key]);
126+
});
127+
}

test/recording_test.dart

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import 'package:test/test.dart';
2020
import 'common_tests.dart';
2121

2222
void main() {
23-
group('SupportingClasses', () {
23+
group('SupportingCode', () {
2424
_BasicClass delegate;
2525
_RecordingClass rc;
2626
MutableRecording recording;
@@ -185,7 +185,7 @@ void main() {
185185
});
186186
});
187187

188-
group('Encode', () {
188+
group('encode', () {
189189
test('performsDeepEncoding', () async {
190190
rc.basicProperty = 'foo';
191191
rc.basicProperty;
@@ -245,6 +245,68 @@ void main() {
245245
});
246246
});
247247
});
248+
249+
group('deeplyEqual', () {
250+
Map<String, dynamic> newMap({
251+
String stringValue: 'foo',
252+
bool boolValue: true,
253+
String lastListValue: 'c',
254+
int lastMapValue: 2,
255+
}) {
256+
return <String, dynamic>{
257+
'string': stringValue,
258+
'bool': boolValue,
259+
'list': <String>['a', 'b', lastListValue],
260+
'map': <Symbol, int>{
261+
#foo: 1,
262+
#bar: lastMapValue,
263+
},
264+
};
265+
}
266+
267+
test('primitives', () {
268+
expect(deeplyEqual(1, 1), isTrue);
269+
expect(deeplyEqual(1, 2), isFalse);
270+
expect(deeplyEqual('1', '1'), isTrue);
271+
expect(deeplyEqual('1', '2'), isFalse);
272+
expect(deeplyEqual(true, true), isTrue);
273+
expect(deeplyEqual(true, false), isFalse);
274+
expect(deeplyEqual(null, null), isTrue);
275+
expect(deeplyEqual(1, '1'), isFalse);
276+
});
277+
278+
test('listOfPrimitives', () {
279+
expect(deeplyEqual(<int>[], <int>[]), isTrue);
280+
expect(deeplyEqual(<int>[1, 2, 3], <int>[1, 2, 3]), isTrue);
281+
expect(deeplyEqual(<int>[1, 2, 3], <int>[1, 3, 2]), isFalse);
282+
expect(deeplyEqual(<int>[1, 2, 3], <int>[1, 2]), isFalse);
283+
expect(deeplyEqual(<int>[1, 2, 3], <int>[1, 2, 3, 4]), isFalse);
284+
expect(deeplyEqual(<String>['a', 'b'], <String>['a', 'b']), isTrue);
285+
expect(deeplyEqual(<String>['a', 'b'], <String>['b', 'a']), isFalse);
286+
expect(deeplyEqual(<String>['a', 'b'], <String>['a']), isFalse);
287+
expect(deeplyEqual(<int>[], <dynamic>[]), isFalse);
288+
expect(deeplyEqual(<int>[], null), isFalse);
289+
});
290+
291+
test('mapOfPrimitives', () {
292+
expect(deeplyEqual(<String, int>{}, <String, int>{}), isTrue);
293+
expect(deeplyEqual(<int, int>{1: 2}, <int, int>{1: 2}), isTrue);
294+
expect(deeplyEqual(<int, int>{1: 2}, <int, int>{1: 3}), isFalse);
295+
expect(deeplyEqual(<int, int>{1: 2}, <int, int>{}), isFalse);
296+
expect(deeplyEqual(<int, int>{}, <int, int>{1: 2}), isFalse);
297+
expect(deeplyEqual(<String, int>{}, <int, int>{}), isFalse);
298+
expect(deeplyEqual(<String, int>{}, <dynamic, dynamic>{}), isFalse);
299+
expect(deeplyEqual(<String, int>{}, null), isFalse);
300+
});
301+
302+
test('listOfMaps', () {
303+
expect(deeplyEqual(newMap(), newMap()), isTrue);
304+
expect(deeplyEqual(newMap(), newMap(stringValue: 'bar')), isFalse);
305+
expect(deeplyEqual(newMap(), newMap(boolValue: false)), isFalse);
306+
expect(deeplyEqual(newMap(), newMap(lastListValue: 'd')), isFalse);
307+
expect(deeplyEqual(newMap(), newMap(lastMapValue: 3)), isFalse);
308+
});
309+
});
248310
});
249311

250312
group('RecordingFileSystem', () {

0 commit comments

Comments
 (0)