From c2f5b40f99822e6ca585964303be4065155210df Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Wed, 8 Feb 2017 10:28:03 -0800 Subject: [PATCH] Add deeplyEqual() method for use in replay (#25) Part of #11 --- lib/src/backends/record_replay/common.dart | 42 ++++++++++++++ test/recording_test.dart | 66 +++++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/lib/src/backends/record_replay/common.dart b/lib/src/backends/record_replay/common.dart index afd95e0c72538..d41abeb9f92c9 100644 --- a/lib/src/backends/record_replay/common.dart +++ b/lib/src/backends/record_replay/common.dart @@ -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'; @@ -83,3 +87,41 @@ class TypeMatcher { /// 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) { + return _areListsEqual(object1, object2); + } else if (object1 is Map) { + return _areMapsEqual(object1, object2); + } else { + return object1 == object2; + } +} + +bool _areListsEqual(List list1, List list2) { + int i = 0; + return list1.length == list2.length && + list1.every((T element) => deeplyEqual(element, list2[i++])); +} + +bool _areMapsEqual(Map map1, Map map2) { + return map1.length == map2.length && + map1.keys.every((K key) { + return map1.containsKey(key) == map2.containsKey(key) && + deeplyEqual(map1[key], map2[key]); + }); +} diff --git a/test/recording_test.dart b/test/recording_test.dart index c2472ab21eee7..919700c161021 100644 --- a/test/recording_test.dart +++ b/test/recording_test.dart @@ -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; @@ -185,7 +185,7 @@ void main() { }); }); - group('Encode', () { + group('encode', () { test('performsDeepEncoding', () async { rc.basicProperty = 'foo'; rc.basicProperty; @@ -245,6 +245,68 @@ void main() { }); }); }); + + group('deeplyEqual', () { + Map newMap({ + String stringValue: 'foo', + bool boolValue: true, + String lastListValue: 'c', + int lastMapValue: 2, + }) { + return { + 'string': stringValue, + 'bool': boolValue, + 'list': ['a', 'b', lastListValue], + 'map': { + #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([], []), isTrue); + expect(deeplyEqual([1, 2, 3], [1, 2, 3]), isTrue); + expect(deeplyEqual([1, 2, 3], [1, 3, 2]), isFalse); + expect(deeplyEqual([1, 2, 3], [1, 2]), isFalse); + expect(deeplyEqual([1, 2, 3], [1, 2, 3, 4]), isFalse); + expect(deeplyEqual(['a', 'b'], ['a', 'b']), isTrue); + expect(deeplyEqual(['a', 'b'], ['b', 'a']), isFalse); + expect(deeplyEqual(['a', 'b'], ['a']), isFalse); + expect(deeplyEqual([], []), isFalse); + expect(deeplyEqual([], null), isFalse); + }); + + test('mapOfPrimitives', () { + expect(deeplyEqual({}, {}), isTrue); + expect(deeplyEqual({1: 2}, {1: 2}), isTrue); + expect(deeplyEqual({1: 2}, {1: 3}), isFalse); + expect(deeplyEqual({1: 2}, {}), isFalse); + expect(deeplyEqual({}, {1: 2}), isFalse); + expect(deeplyEqual({}, {}), isFalse); + expect(deeplyEqual({}, {}), isFalse); + expect(deeplyEqual({}, 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', () {