| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // ignore_for_file: implementation_imports |
| |
| import 'dart:async'; |
| import 'dart:collection'; |
| import 'dart:math' show Random; |
| import 'dart:typed_data'; |
| |
| import 'package:lib.app.dart/logging.dart'; |
| import 'package:sledge/src/document/values/converted_change.dart'; |
| import 'package:sledge/src/document/values/ordered_list_value.dart'; |
| import 'package:test/test.dart'; |
| |
| import '../crdt_test_framework/crdt_test_framework.dart'; |
| import 'matchers.dart'; |
| |
| // Wraps construction of Fleet of OrderedListValues. |
| class OrderedListFleetFactory<T> { |
| const OrderedListFleetFactory(); |
| |
| // Returns Fleet of [count] OrderedListValues with pairwise different |
| // instanceIds. |
| Fleet<OrderedListValue<T>> newFleet(int count) { |
| return Fleet<OrderedListValue<T>>(count, |
| (index) => OrderedListValue<T>(Uint8List.fromList([index]))); |
| } |
| } |
| |
| const OrderedListFleetFactory<int> integerOrderedListFleetFactory = |
| OrderedListFleetFactory<int>(); |
| |
| // Checks that relative orders of elements do not change. |
| // Throws an error if some pair of elements [a] and [b] appear in both orders, |
| // e.g. ...[a]...[b]... and ...[b]...[a]... |
| // |
| // This checker works properly only if there are no insertions of equal |
| // elements. |
| class RelativeOrderChecker<T> extends Checker<OrderedListValue<T>> { |
| Map<T, Set<T>> graph = <T, Set<T>>{}; |
| |
| @override |
| void check(OrderedListValue<T> orderedList) { |
| var list = orderedList.toList(); |
| for (T a in list) { |
| graph.putIfAbsent(a, () => <T>{}); |
| } |
| // Iterate over all the pairs (i,j) contained in [0, list.length), with i<j. |
| for (int i = 0; i < list.length; i++) { |
| for (int j = i + 1; j < list.length; j++) { |
| T a = list[i], b = list[j]; |
| expect(graph[b].contains(a), isFalse); |
| graph[a].add(b); |
| } |
| } |
| } |
| } |
| |
| // Inserts [value] into [list] at position [pos]. And checks that this operation |
| // performed correctly. |
| void insertIntWithCheck(List<int> list, int pos, int value) { |
| final correctList = list.toList(); |
| list.insert(pos, value); |
| correctList.insert(pos, value); |
| expect(list, equals(correctList)); |
| } |
| |
| // Creates fleet of [countInstances] OrderedListValues. Runs test in |
| // [countEpochs] epochs. Between epochs all instances are synchronized. In one |
| // epoch on each instance performs [countInsertions] insertions at random |
| // positions. |
| // |
| // For each insertion checks that it was performed correctly. And |
| // checks that relative order of elements do not change. |
| Future randomRelativeOrderTest( |
| {final int countInstances, |
| final int countEpochs, |
| final int countInsertions, |
| final int seed}) async { |
| test( |
| 'Check relative order ' |
| '(i: $countInstances, e: $countEpochs, ins: $countInsertions, seed: $seed).', |
| () async { |
| final random = Random(seed); |
| int incValue = 0; |
| final fleet = integerOrderedListFleetFactory.newFleet(countInstances); |
| final instanceIdList = |
| List<int>.generate(countInstances, (index) => index); |
| |
| for (int epoch = 0; epoch < countEpochs; epoch++) { |
| for (int instance = 0; instance < countInstances; instance++) { |
| fleet.runInTransaction(instance, (OrderedListValue<int> l) async { |
| for (int it = 0; it < countInsertions; it++) { |
| int pos = random.nextInt(l.length + 1); |
| insertIntWithCheck(l, pos, incValue++); |
| } |
| }); |
| } |
| fleet.synchronize(instanceIdList); |
| } |
| fleet.addChecker(() => RelativeOrderChecker<int>()); |
| await fleet.testAllOrders(); |
| }); |
| } |
| |
| void main() async { |
| setupLogger(); |
| |
| test('OrderedList with framework', () async { |
| final fleet = integerOrderedListFleetFactory.newFleet(2) |
| ..runInTransaction(0, (OrderedListValue<int> l0) async { |
| l0.insert(0, 1); |
| }) |
| ..runInTransaction(1, (OrderedListValue<int> l1) async { |
| l1.insert(0, 2); |
| }) |
| ..synchronize([0, 1]) |
| ..runInTransaction(0, (OrderedListValue<int> l0) async { |
| expect(l0.toList(), anyOf(equals([1, 2]), equals([2, 1]))); |
| }); |
| await fleet.testAllOrders(); |
| }); |
| |
| test('OrderedList with framework. Check relative order.', () async { |
| final fleet = integerOrderedListFleetFactory.newFleet(3) |
| ..runInTransaction(0, (OrderedListValue<int> l0) async { |
| l0..insert(0, 1)..insert(1, 2); |
| }) |
| ..runInTransaction(1, (OrderedListValue<int> l1) async { |
| l1..insert(0, 3)..insert(1, 4); |
| }) |
| ..runInTransaction(2, (OrderedListValue<int> l2) async { |
| l2..insert(0, 5)..insert(1, 6); |
| }) |
| ..synchronize([0, 1, 2]) |
| ..addChecker(() => RelativeOrderChecker<int>()); |
| await fleet.testAllOrders(); |
| }); |
| |
| test('Ordered list. Test deletion.', () async { |
| final fleet = integerOrderedListFleetFactory.newFleet(2) |
| ..runInTransaction(0, (final cnt) async { |
| cnt.insert(0, 0); |
| }) |
| ..runInTransaction(0, (final cnt) async { |
| cnt.removeAt(0); |
| }) |
| ..synchronize([0, 1]) |
| ..runInTransaction(1, (final cnt) async { |
| expect(cnt.isEmpty, isTrue); |
| }); |
| await fleet.testSingleOrder(); |
| }); |
| |
| test('Ordered list. Stream.', () async { |
| final fleet = integerOrderedListFleetFactory.newFleet(3); |
| for (int id = 0; id < 3; id++) { |
| fleet.runInTransaction(id, (final cnt) async { |
| expect( |
| cnt.onChange, |
| emitsInOrder([ |
| OrderedListChangeMatcher(OrderedListChange<int>( |
| [], SplayTreeMap<int, int>.fromIterables([0], [1]))), |
| OrderedListChangeMatcher(OrderedListChange<int>([], |
| SplayTreeMap<int, int>.fromIterables([0, 2], [2, 3]))), |
| OrderedListChangeMatcher(OrderedListChange<int>( |
| [1, 2], SplayTreeMap<int, int>.fromIterables([], []))), |
| OrderedListChangeMatcher(OrderedListChange<int>( |
| [], SplayTreeMap<int, int>.fromIterables([0], [5]))), |
| ])); |
| }); |
| } |
| fleet |
| ..runInTransaction(0, (final cnt) async { |
| cnt.insert(0, 1); |
| }) |
| ..synchronize([0, 1, 2]) |
| ..runInTransaction(1, (final cnt) async { |
| cnt..insert(1, 3)..insert(0, 2); |
| }) |
| ..synchronize([0, 1, 2]) |
| ..runInTransaction(0, (final cnt) async { |
| cnt..removeAt(1)..removeAt(1); |
| }) |
| ..synchronize([0, 1, 2]) |
| ..runInTransaction(0, (final cnt) async { |
| cnt.insert(0, 5); |
| }) |
| ..synchronize([0, 1, 2]); |
| await fleet.testSingleOrder(); |
| }); |
| |
| await randomRelativeOrderTest( |
| countInstances: 3, countEpochs: 2, countInsertions: 2, seed: 1); |
| await randomRelativeOrderTest( |
| countInstances: 2, countEpochs: 3, countInsertions: 2, seed: 2); |
| await randomRelativeOrderTest( |
| countInstances: 3, countEpochs: 3, countInsertions: 1, seed: 3); |
| } |