Add deeplyEqual() method for use in replay (#25)
Part of #11
diff --git a/lib/src/backends/record_replay/common.dart b/lib/src/backends/record_replay/common.dart
index afd95e0..d41abeb 100644
--- a/lib/src/backends/record_replay/common.dart
+++ b/lib/src/backends/record_replay/common.dart
@@ -51,6 +51,10 @@
/// 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 @@
/// 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]);
+ });
+}
diff --git a/test/recording_test.dart b/test/recording_test.dart
index c2472ab..919700c 100644
--- a/test/recording_test.dart
+++ b/test/recording_test.dart
@@ -20,7 +20,7 @@
import 'common_tests.dart';
void main() {
- group('SupportingClasses', () {
+ group('SupportingCode', () {
_BasicClass delegate;
_RecordingClass rc;
MutableRecording recording;
@@ -185,7 +185,7 @@
});
});
- group('Encode', () {
+ group('encode', () {
test('performsDeepEncoding', () async {
rc.basicProperty = 'foo';
rc.basicProperty;
@@ -245,6 +245,68 @@
});
});
});
+
+ 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', () {