blob: 76d04206319914cb194d13e3e60dfa98a7125b6b [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library isolate.test.registry_test;
import "dart:async";
import "dart:isolate";
import "package:isolate/isolate_runner.dart";
import "package:isolate/registry.dart";
import "package:test/test.dart";
const MS = const Duration(milliseconds: 1);
void main() {
group("lookup", testLookup);
group("AddLookup", testAddLookup);
group("AddRemoveTags", testAddRemoveTags);
group("Remove", testRemove);
group("CrossIsolate", testCrossIsolate);
group("Timeout", testTimeout);
group("MultiRegistry", testMultiRegistry);
group("ObjectsAndTags", testObjectsAndTags);
}
class Oddity {
static const int EVEN = 0;
static const int ODD = 1;
}
Future<List> waitAll(int n, Future action(int n)) {
return Future.wait(new Iterable.generate(n, action));
}
void testLookup() {
test("All", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
return waitAll(10, (i) {
var element = new Element(i);
var tag = i.isEven ? Oddity.EVEN : Oddity.ODD;
return registry.add(element, tags: [tag]);
}).then((_) {
return registry.lookup();
}).then((all) {
expect(all.length, 10);
expect(all.map((v) => v.id).toList()..sort(),
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}).whenComplete(regman.close);
});
test("Odd", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
return waitAll(10, (i) {
var element = new Element(i);
var tag = i.isEven ? Oddity.EVEN : Oddity.ODD;
return registry.add(element, tags: [tag]);
}).then((_) {
return registry.lookup(tags: [Oddity.ODD]);
}).then((all) {
expect(all.length, 5);
expect(all.map((v) => v.id).toList()..sort(), [1, 3, 5, 7, 9]);
}).whenComplete(regman.close);
});
test("Max", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
return waitAll(10, (i) {
var element = new Element(i);
var tag = i.isEven ? Oddity.EVEN : Oddity.ODD;
return registry.add(element, tags: [tag]);
}).then((_) {
return registry.lookup(max: 5);
}).then((all) {
expect(all.length, 5);
}).whenComplete(regman.close);
});
test("MultiTag", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
return waitAll(25, (i) {
var element = new Element(i);
// Collect all numbers dividing i.
var tags = [i];
for (int j = 2; j < 25; j++) {
if (i % j == 0) tags.add(j);
}
return registry.add(element, tags: tags);
}).then((_) {
return registry.lookup(tags: [2, 3]);
}).then((all) {
expect(all.length, 5);
expect(all.map((v) => v.id).toList()..sort(), [0, 6, 12, 18, 24]);
}).whenComplete(regman.close);
});
test("MultiTagMax", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
return waitAll(25, (i) {
var element = new Element(i);
// Collect all numbers dividing i.
var tags = [i];
for (int j = 2; j < 25; j++) {
if (i % j == 0) tags.add(j);
}
return registry.add(element, tags: tags);
}).then((_) {
return registry.lookup(tags: [2, 3], max: 3);
}).then((all) {
expect(all.length, 3);
expect(all.every((v) => (v.id % 6) == 0), isTrue);
}).whenComplete(regman.close);
});
}
void testAddLookup() {
test("Add-lookup-identical", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object = new Object();
return registry.add(object).then((_) {
return registry.lookup();
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
}).whenComplete(regman.close);
});
test("Add-multiple-identical", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object1 = new Object();
var object2 = new Object();
var object3 = new Object();
var objects = [object1, object2, object3];
return Future.wait(objects.map(registry.add)).then((_) {
return registry.lookup();
}).then((entries) {
expect(entries, hasLength(3));
for (var entry in entries) {
expect(entry, isIn(objects));
}
}).whenComplete(regman.close);
});
test("Add-twice", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object = new Object();
return registry.add(object).then((_) {
return registry.add(object);
}).then((_) {
fail("Unreachable");
}, onError: (e, s) {
expect(e, isStateError);
}).whenComplete(regman.close);
});
test("Add-lookup-add-lookup", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object = new Object();
var object2 = new Object();
return registry.add(object).then((_) {
return registry.lookup();
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
return registry.add(object2);
}).then((_) {
return registry.lookup();
}).then((entries) {
expect(entries, hasLength(2));
var entry1 = entries.first;
var entry2 = entries.last;
if (object == entry1) {
expect(entry2, same(object2));
} else {
expect(entry1, same(object));
}
}).whenComplete(regman.close);
});
test("lookup-add-lookup", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object = new Object();
return registry.lookup().then((entries) {
expect(entries, isEmpty);
return registry.add(object);
}).then((_) {
return registry.lookup();
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
}).whenComplete(regman.close);
});
test("Add-multiple-tags", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object1 = new Object();
var object2 = new Object();
var object3 = new Object();
return registry.add(object1, tags: [1, 3, 5, 7]).then((_) {
return registry.add(object2, tags: [2, 3, 6, 7]);
}).then((_) {
return registry.add(object3, tags: [4, 5, 6, 7]);
}).then((_) {
return registry.lookup(tags: [3]);
}).then((entries) {
expect(entries, hasLength(2));
expect(entries.first == object1 || entries.last == object1, isTrue);
expect(entries.first == object2 || entries.last == object2, isTrue);
}).then((_) {
return registry.lookup(tags: [2]);
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object2));
}).then((_) {
return registry.lookup(tags: [3, 6]);
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object2));
}).whenComplete(regman.close);
});
}
void testRemove() {
test("Add-remove", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object = new Object();
return registry.add(object).then((removeCapability) {
return registry.lookup().then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
return registry.remove(object, removeCapability);
});
}).then((removeSuccess) {
expect(removeSuccess, isTrue);
return registry.lookup();
}).then((entries) {
expect(entries, isEmpty);
}).whenComplete(regman.close);
});
test("Add-remove-fail", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object = new Object();
return registry.add(object).then((removeCapability) {
return registry.lookup().then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
return registry.remove(object, new Capability());
});
}).then((removeSuccess) {
expect(removeSuccess, isFalse);
}).whenComplete(regman.close);
});
}
void testAddRemoveTags() {
test("Add-remove-tag", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object = new Object();
return registry.add(object).then((removeCapability) {
return registry.lookup(tags: ["x"]);
}).then((entries) {
expect(entries, isEmpty);
return registry.addTags([object], ["x"]);
}).then((_) {
return registry.lookup(tags: ["x"]);
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
return registry.removeTags([object], ["x"]);
}).then((_) {
return registry.lookup(tags: ["x"]);
}).then((entries) {
expect(entries, isEmpty);
}).whenComplete(regman.close);
});
test("Tag-twice", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object = new Object();
return registry.add(object, tags: ["x"]).then((removeCapability) {
return registry.lookup(tags: ["x"]);
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
// Adding the same tag twice is allowed, but does nothing.
return registry.addTags([object], ["x"]);
}).then((_) {
return registry.lookup(tags: ["x"]);
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
// Removing the tag once is enough to remove it.
return registry.removeTags([object], ["x"]);
}).then((_) {
return registry.lookup(tags: ["x"]);
}).then((entries) {
expect(entries, isEmpty);
}).whenComplete(regman.close);
});
test("Add-remove-multiple", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
var object1 = new Object();
var object2 = new Object();
var object3 = new Object();
var objects = [object1, object2, object3];
return Future.wait(objects.map(registry.add)).then((_) {
return registry.addTags([object1, object2], ["x", "y"]);
}).then((_) {
return registry.addTags([object1, object3], ["z", "w"]);
}).then((_) {
return registry.lookup(tags: ["x", "z"]);
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object1));
return registry.removeTags([object1, object2], ["x", "z"]);
}).then((_) {
return registry.lookup(tags: ["z"]);
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object3));
}).whenComplete(regman.close);
});
test("Remove-wrong-object", () {
RegistryManager regman = new RegistryManager();
Registry registry = regman.registry;
expect(() => registry.removeTags([new Object()], ["x"]), throwsStateError);
regman.close();
});
}
var _regmen = {};
Registry createRegMan(id) {
var regman = new RegistryManager();
_regmen[id] = regman;
return regman.registry;
}
void closeRegMan(id) {
_regmen.remove(id).close();
}
void testCrossIsolate() {
var object = new Object();
test("regman-other-isolate", () {
// Add, lookup and remove object in other isolate.
return IsolateRunner.spawn().then((isolate) {
isolate.run(createRegMan, 1).then((registry) {
return registry.add(object, tags: ["a", "b"]).then((removeCapability) {
return registry.lookup(tags: ["a"]).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, same(object));
return registry.remove(entries.first, removeCapability);
}).then((removeSuccess) {
expect(removeSuccess, isTrue);
});
});
}).whenComplete(() {
return isolate.run(closeRegMan, 1);
}).whenComplete(() {
return isolate.close();
});
});
});
}
void testTimeout() {
test("Timeout-add", () {
RegistryManager regman = new RegistryManager(timeout: MS * 500);
Registry registry = regman.registry;
regman.close();
return registry.add(new Object()).then((_) {
fail("unreachable");
}, onError: (e, s) {
expect(e is TimeoutException, isTrue);
});
});
test("Timeout-remove", () {
RegistryManager regman = new RegistryManager(timeout: MS * 500);
Registry registry = regman.registry;
var object = new Object();
return registry.add(object).then((rc) {
regman.close();
return registry.remove(object, rc).then((_) {
fail("unreachable");
}, onError: (e, s) {
expect(e is TimeoutException, isTrue);
});
});
});
test("Timeout-addTags", () {
RegistryManager regman = new RegistryManager(timeout: MS * 500);
Registry registry = regman.registry;
var object = new Object();
return registry.add(object).then((rc) {
regman.close();
return registry.addTags([object], ["x"]).then((_) {
fail("unreachable");
}, onError: (e, s) {
expect(e is TimeoutException, isTrue);
});
});
});
test("Timeout-removeTags", () {
RegistryManager regman = new RegistryManager(timeout: MS * 500);
Registry registry = regman.registry;
var object = new Object();
return registry.add(object).then((rc) {
regman.close();
return registry.removeTags([object], ["x"]).then((_) {
fail("unreachable");
}, onError: (e, s) {
expect(e is TimeoutException, isTrue);
});
});
});
test("Timeout-lookup", () {
RegistryManager regman = new RegistryManager(timeout: MS * 500);
Registry registry = regman.registry;
regman.close();
registry.lookup().then((_) {
fail("unreachable");
}, onError: (e, s) {
expect(e is TimeoutException, isTrue);
});
});
}
void testMultiRegistry() {
test("dual-registry", () {
RegistryManager regman = new RegistryManager();
Registry registry1 = regman.registry;
Registry registry2 = regman.registry;
var l1 = ["x"];
var l2;
return registry1.add(l1, tags: ["y"]).then((removeCapability) {
return registry2.lookup().then((entries) {
expect(entries, hasLength(1));
l2 = entries.first;
expect(l2, equals(l1));
// The object for registry2 is not identical the one for registry1.
expect(!identical(l1, l2), isTrue);
// Removing the registry1 object through registry2 doesn't work.
return registry2.remove(l1, removeCapability);
}).then((removeSuccess) {
expect(removeSuccess, isFalse);
return registry2.remove(l2, removeCapability);
}).then((removeSuccess) {
expect(removeSuccess, isTrue);
return registry1.lookup();
}).then((entries) {
expect(entries, isEmpty);
});
}).whenComplete(regman.close);
});
}
void testObjectsAndTags() {
testObject(object) {
String name = "Transfer-${object.runtimeType}";
test(name, () {
RegistryManager regman = new RegistryManager();
Registry registry1 = regman.registry;
Registry registry2 = regman.registry;
return registry1.add(object, tags: [object]).then((removeCapability) {
return registry2.lookup().then((entries) {
expect(entries, hasLength(1));
expect(entries.first, equals(object));
return registry2.lookup(tags: [object]);
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, equals(object));
return registry2.removeTags([entries.first], [object]);
}).then((_) {
return registry2.lookup();
}).then((entries) {
expect(entries, hasLength(1));
expect(entries.first, equals(object));
return registry2.remove(entries.first, removeCapability);
}).then((removeSuccess) {
expect(removeSuccess, isTrue);
return registry2.lookup();
}).then((entries) {
expect(entries, isEmpty);
});
}).whenComplete(regman.close);
});
}
// Test objects that are sendable between equivalent isolates and
// that has an operator== that works after cloning (for use as tags).
testObject(42);
testObject(3.14);
testObject("string");
testObject(true);
testObject(null);
testObject(new Element(42));
testObject(#symbol);
testObject(#_privateSymbol);
testObject(new Capability());
testObject(topLevelFunction);
}
class Element {
final int id;
Element(this.id);
int get hashCode => id;
bool operator ==(Object other) => other is Element && id == other.id;
}
void topLevelFunction() {}