Skip to content

Commit

Permalink
Add deeplyEqual() method for use in replay (#25)
Browse files Browse the repository at this point in the history
Part of flutter#11
  • Loading branch information
tvolkert authored Feb 8, 2017
1 parent b2689ff commit c2f5b40
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 2 deletions.
42 changes: 42 additions & 0 deletions lib/src/backends/record_replay/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const String kManifestPositionalArgumentsKey = 'positionalArguments';
/// named arguments that were passed to the method.
const String kManifestNamedArgumentsKey = 'namedArguments';

/// The key in a serialized [InvocationEvent] map that is used to store whether
/// the invocation has been replayed already.
const String kManifestReplayedKey = 'replayed';

/// The serialized [kManifestTypeKey] for property retrievals.
const String kGetType = 'get';

Expand Down Expand Up @@ -83,3 +87,41 @@ class TypeMatcher<T> {
/// Returns `true` if the given object is of type `T`.
bool matches(dynamic object) => object is T;
}

/// Tells whether two objects are equal using deep equality checking.
///
/// Two lists are deeply equal if they have the same runtime type, the same
/// length, and every element in list A is pairwise deeply equal with the
/// corresponding element in list B.
///
/// Two maps are deeply equal if they have the same runtime type, the same
/// length, the same set of keys, and the value for every key in map A is
/// deeply equal to the corresponding value in map B.
///
/// All other types of objects are deeply equal if they have the same runtime
/// type and are logically equal (according to `operator==`).
bool deeplyEqual(dynamic object1, dynamic object2) {
if (object1.runtimeType != object2.runtimeType) {
return false;
} else if (object1 is List<dynamic>) {
return _areListsEqual(object1, object2);
} else if (object1 is Map<dynamic, dynamic>) {
return _areMapsEqual(object1, object2);
} else {
return object1 == object2;
}
}

bool _areListsEqual<T>(List<T> list1, List<T> list2) {
int i = 0;
return list1.length == list2.length &&
list1.every((T element) => deeplyEqual(element, list2[i++]));
}

bool _areMapsEqual<K, V>(Map<K, V> map1, Map<K, V> map2) {
return map1.length == map2.length &&
map1.keys.every((K key) {
return map1.containsKey(key) == map2.containsKey(key) &&
deeplyEqual(map1[key], map2[key]);
});
}
66 changes: 64 additions & 2 deletions test/recording_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import 'package:test/test.dart';
import 'common_tests.dart';

void main() {
group('SupportingClasses', () {
group('SupportingCode', () {
_BasicClass delegate;
_RecordingClass rc;
MutableRecording recording;
Expand Down Expand Up @@ -185,7 +185,7 @@ void main() {
});
});

group('Encode', () {
group('encode', () {
test('performsDeepEncoding', () async {
rc.basicProperty = 'foo';
rc.basicProperty;
Expand Down Expand Up @@ -245,6 +245,68 @@ void main() {
});
});
});

group('deeplyEqual', () {
Map<String, dynamic> newMap({
String stringValue: 'foo',
bool boolValue: true,
String lastListValue: 'c',
int lastMapValue: 2,
}) {
return <String, dynamic>{
'string': stringValue,
'bool': boolValue,
'list': <String>['a', 'b', lastListValue],
'map': <Symbol, int>{
#foo: 1,
#bar: lastMapValue,
},
};
}

test('primitives', () {
expect(deeplyEqual(1, 1), isTrue);
expect(deeplyEqual(1, 2), isFalse);
expect(deeplyEqual('1', '1'), isTrue);
expect(deeplyEqual('1', '2'), isFalse);
expect(deeplyEqual(true, true), isTrue);
expect(deeplyEqual(true, false), isFalse);
expect(deeplyEqual(null, null), isTrue);
expect(deeplyEqual(1, '1'), isFalse);
});

test('listOfPrimitives', () {
expect(deeplyEqual(<int>[], <int>[]), isTrue);
expect(deeplyEqual(<int>[1, 2, 3], <int>[1, 2, 3]), isTrue);
expect(deeplyEqual(<int>[1, 2, 3], <int>[1, 3, 2]), isFalse);
expect(deeplyEqual(<int>[1, 2, 3], <int>[1, 2]), isFalse);
expect(deeplyEqual(<int>[1, 2, 3], <int>[1, 2, 3, 4]), isFalse);
expect(deeplyEqual(<String>['a', 'b'], <String>['a', 'b']), isTrue);
expect(deeplyEqual(<String>['a', 'b'], <String>['b', 'a']), isFalse);
expect(deeplyEqual(<String>['a', 'b'], <String>['a']), isFalse);
expect(deeplyEqual(<int>[], <dynamic>[]), isFalse);
expect(deeplyEqual(<int>[], null), isFalse);
});

test('mapOfPrimitives', () {
expect(deeplyEqual(<String, int>{}, <String, int>{}), isTrue);
expect(deeplyEqual(<int, int>{1: 2}, <int, int>{1: 2}), isTrue);
expect(deeplyEqual(<int, int>{1: 2}, <int, int>{1: 3}), isFalse);
expect(deeplyEqual(<int, int>{1: 2}, <int, int>{}), isFalse);
expect(deeplyEqual(<int, int>{}, <int, int>{1: 2}), isFalse);
expect(deeplyEqual(<String, int>{}, <int, int>{}), isFalse);
expect(deeplyEqual(<String, int>{}, <dynamic, dynamic>{}), isFalse);
expect(deeplyEqual(<String, int>{}, null), isFalse);
});

test('listOfMaps', () {
expect(deeplyEqual(newMap(), newMap()), isTrue);
expect(deeplyEqual(newMap(), newMap(stringValue: 'bar')), isFalse);
expect(deeplyEqual(newMap(), newMap(boolValue: false)), isFalse);
expect(deeplyEqual(newMap(), newMap(lastListValue: 'd')), isFalse);
expect(deeplyEqual(newMap(), newMap(lastMapValue: 3)), isFalse);
});
});
});

group('RecordingFileSystem', () {
Expand Down

0 comments on commit c2f5b40

Please sign in to comment.