blob: 356c3bb51ab273f7ce78818de904aee2bca9331a [file] [log] [blame]
// 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);
}