blob: 3b8dd47b160ea92cbdd38214273e520afc4644bf [file] [log] [blame]
// Copyright (c) 2011, 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 set_test;
import 'package:expect/expect.dart';
import "dart:collection";
void testMain(Set create()) {
testInts(create);
testStrings(create);
testInts(() => create().toSet());
testStrings(() => create().toSet());
}
void testInts(Set create()) {
Set set = create();
testLength(0, set);
Expect.isTrue(set.add(1));
testLength(1, set);
Expect.isTrue(set.contains(1));
Expect.isFalse(set.add(1));
testLength(1, set);
Expect.isTrue(set.contains(1));
Expect.isTrue(set.remove(1));
testLength(0, set);
Expect.isFalse(set.contains(1));
Expect.isFalse(set.remove(1));
testLength(0, set);
Expect.isFalse(set.contains(1));
for (int i = 0; i < 10; i++) {
set.add(i);
}
testLength(10, set);
for (int i = 0; i < 10; i++) {
Expect.isTrue(set.contains(i));
}
testLength(10, set);
for (int i = 10; i < 20; i++) {
Expect.isFalse(set.contains(i));
}
// Test Set.forEach.
int sum = 0;
void testForEach(dynamic val) {
sum += ((val as int) + 1);
}
set.forEach(testForEach);
Expect.equals(10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1, sum);
Expect.isTrue(set.containsAll(set));
// Test Set.map.
int testMap(dynamic val) {
return (val as int) * val;
}
Set mapped = set.map(testMap).toSet();
Expect.equals(10, mapped.length);
Expect.isTrue(mapped.contains(0));
Expect.isTrue(mapped.contains(1));
Expect.isTrue(mapped.contains(4));
Expect.isTrue(mapped.contains(9));
Expect.isTrue(mapped.contains(16));
Expect.isTrue(mapped.contains(25));
Expect.isTrue(mapped.contains(36));
Expect.isTrue(mapped.contains(49));
Expect.isTrue(mapped.contains(64));
Expect.isTrue(mapped.contains(81));
sum = 0;
set.forEach(testForEach);
Expect.equals(10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1, sum);
sum = 0;
mapped.forEach(testForEach);
Expect.equals(1 + 2 + 5 + 10 + 17 + 26 + 37 + 50 + 65 + 82, sum);
// Test Set.filter.
bool testFilter(dynamic val) {
return (val as int).isEven;
}
Set filtered = set.where(testFilter).toSet();
Expect.equals(5, filtered.length);
Expect.isTrue(filtered.contains(0));
Expect.isTrue(filtered.contains(2));
Expect.isTrue(filtered.contains(4));
Expect.isTrue(filtered.contains(6));
Expect.isTrue(filtered.contains(8));
sum = 0;
filtered.forEach(testForEach);
Expect.equals(1 + 3 + 5 + 7 + 9, sum);
Expect.isTrue(set.containsAll(filtered));
// Test Set.every.
bool testEvery(dynamic val) {
return ((val as int) < 10);
}
Expect.isTrue(set.every(testEvery));
Expect.isTrue(filtered.every(testEvery));
filtered.add(10);
Expect.isFalse(filtered.every(testEvery));
// Test Set.some.
bool testSome(dynamic val) {
return (val == 4);
}
Expect.isTrue(set.any(testSome));
Expect.isTrue(filtered.any(testSome));
filtered.remove(4);
Expect.isFalse(filtered.any(testSome));
// Test Set.intersection.
Set intersection = set.intersection(filtered);
Expect.isTrue(set.contains(0));
Expect.isTrue(set.contains(2));
Expect.isTrue(set.contains(6));
Expect.isTrue(set.contains(8));
Expect.isFalse(intersection.contains(1));
Expect.isFalse(intersection.contains(3));
Expect.isFalse(intersection.contains(4));
Expect.isFalse(intersection.contains(5));
Expect.isFalse(intersection.contains(7));
Expect.isFalse(intersection.contains(9));
Expect.isFalse(intersection.contains(10));
Expect.equals(4, intersection.length);
Expect.isTrue(set.containsAll(intersection));
Expect.isTrue(filtered.containsAll(intersection));
// Test Set.union.
Set twice = create()..addAll([0, 2, 4, 6, 8, 10, 12, 14]);
Set thrice = create()..addAll([0, 3, 6, 9, 12, 15]);
Set union = twice.union(thrice);
Expect.equals(11, union.length);
for (int i = 0; i < 16; i++) {
Expect.equals(i.isEven || (i % 3) == 0, union.contains(i));
}
// Test Set.difference.
Set difference = twice.difference(thrice);
Expect.equals(5, difference.length);
for (int i = 0; i < 16; i++) {
Expect.equals(i.isEven && (i % 3) != 0, difference.contains(i));
}
Expect.isTrue(twice.difference(thrice).difference(twice).isEmpty);
// Test Set.difference with non-element type.
Set diffSet = create()..addAll([0, 1, 2, 499, 999]);
Set<Object> objectSet = new Set<Object>();
objectSet.add("foo");
objectSet.add(499);
Set diffResult = diffSet.difference(objectSet);
Expect.equals(4, diffResult.length);
for (int value in [0, 1, 2, 999]) {
Expect.isTrue(diffResult.contains(value));
}
// Test Set.addAll.
List list = new List.filled(10, null);
for (int i = 0; i < 10; i++) {
list[i] = i + 10;
}
set.addAll(list);
testLength(20, set);
for (int i = 0; i < 20; i++) {
Expect.isTrue(set.contains(i));
}
// Test Set.removeAll
set.removeAll(list);
testLength(10, set);
for (int i = 0; i < 10; i++) {
Expect.isTrue(set.contains(i));
}
for (int i = 10; i < 20; i++) {
Expect.isFalse(set.contains(i));
}
// Test Set.clear.
set.clear();
testLength(0, set);
Expect.isTrue(set.add(11));
testLength(1, set);
// Test Set.toSet.
set.add(1);
set.add(21);
testLength(3, set);
var set2 = set.toSet();
testLength(3, set2);
Expect.listEquals(set.toList(), set2.toList());
set.add(31);
testLength(4, set);
testLength(3, set2);
set2 = set.toSet()..clear();
testLength(0, set2);
Expect.isTrue(set2.add(11));
Expect.isTrue(set2.add(1));
Expect.isTrue(set2.add(21));
Expect.isTrue(set2.add(31));
testLength(4, set2);
Expect.listEquals(set.toList(), set2.toList());
set2 = (set.toSet()..clear()).toSet(); // Cloning empty set shouldn't fail.
testLength(0, set2);
}
void testLength(int length, Set set) {
Expect.equals(length, set.length);
(length == 0 ? Expect.isTrue : Expect.isFalse)(set.isEmpty);
(length != 0 ? Expect.isTrue : Expect.isFalse)(set.isNotEmpty);
if (length == 0) {
for (var e in set) {
Expect.fail("contains element when iterated: $e");
}
}
(length == 0 ? Expect.isFalse : Expect.isTrue)(set.iterator.moveNext());
}
void testStrings(Set create()) {
var set = create();
var strings = ["foo", "bar", "baz", "qux", "fisk", "hest", "svin", "pigvar"];
set.addAll(strings);
testLength(8, set);
set.removeAll(strings.where((x) => x.length == 3));
testLength(4, set);
Expect.isTrue(set.add("bar"));
Expect.isTrue(set.add("qux"));
testLength(6, set);
set.addAll(strings);
testLength(8, set);
set.removeWhere((x) => x.length != 3);
testLength(4, set);
set.retainWhere((x) => x[1] == "a");
testLength(2, set);
Expect.isTrue(set.containsAll(["baz", "bar"]));
set = set.union(strings.where((x) => x.length != 3).toSet());
testLength(6, set);
set = set.intersection(["qux", "baz", "fisk", "egern"].toSet());
testLength(2, set);
Expect.isTrue(set.containsAll(["baz", "fisk"]));
}
void testTypeAnnotations(Set<int> set) {
set.add(0);
set.add(999);
set.add(0x800000000);
set.add(0x20000000000000);
Expect.isFalse(set.contains("not an it"));
Expect.isFalse(set.remove("not an it"));
Expect.isFalse(set.containsAll(["Not an int", "Also no an int"]));
testLength(4, set);
set.removeAll(["Not an int", 999, "Also no an int"]);
testLength(3, set);
set.retainAll(["Not an int", 0, "Also no an int"]);
testLength(1, set);
}
void testRetainWhere(
Set<CE> create(
[CEEq? equals, CEHash? hashCode, ValidKey? validKey, CECompare? compare])) {
// The retainWhere method must not collapse the argument Iterable
// in a way that doesn't match the equality of the set.
// It must not throw away equal elements that are different in the
// equality of the set.
// It must not consider objects to be not there if they are equal
// in the equality of the set.
// If set equality is natural equality, using different but equal objects
// must work. Can't use an identity set internally (as was done at some point
// during development).
var set = create();
set.addAll([new CE(0), new CE(1), new CE(2)]);
Expect.equals(3, set.length); // All different.
set.retainAll([new CE(0), new CE(2)]);
Expect.equals(2, set.length);
Expect.isTrue(set.contains(new CE(0)));
Expect.isTrue(set.contains(new CE(2)));
// If equality of set is identity, we can't internally use a non-identity
// based set because it might throw away equal objects that are not identical.
var elems = [new CE(0), new CE(1), new CE(2), new CE(0)];
set = create(identical, null, null, identityCompare);
set.addAll(elems);
Expect.equals(4, set.length);
set.retainAll([elems[0], elems[2], elems[3]]);
Expect.equals(3, set.length);
Expect.isTrue(set.contains(elems[0]));
Expect.isTrue(set.contains(elems[2]));
Expect.isTrue(set.contains(elems[3]));
// If set equality is less precise than equality, we must not use equality
// internally to see if the element is there:
set = create(customEq(3), customHash(3), validKey, customCompare(3));
set.addAll([new CE(0), new CE(1), new CE(2)]);
Expect.equals(3, set.length);
set.retainAll([new CE(3), new CE(5)]);
Expect.equals(2, set.length);
Expect.isTrue(set.contains(new CE(6)));
Expect.isTrue(set.contains(new CE(8)));
// It shouldn't matter if the input is a set.
set.clear();
set.addAll([new CE(0), new CE(1), new CE(2)]);
Expect.equals(3, set.length);
set.retainAll(new Set.from([new CE(3), new CE(5)]));
Expect.equals(2, set.length);
Expect.isTrue(set.contains(new CE(6)));
Expect.isTrue(set.contains(new CE(8)));
}
void testDifferenceIntersection(create([equals, hashCode, validKey, compare])) {
// Test that elements of intersection comes from receiver set.
CE ce1a = new CE(1);
CE ce1b = new CE(1);
CE ce2 = new CE(2);
CE ce3 = new CE(3);
Expect.equals(ce1a, ce1b); // Sanity check.
var set1 = create();
var set2 = create();
set1.add(ce1a);
set1.add(ce2);
set2.add(ce1b);
set2.add(ce3);
var difference = set1.difference(set2);
testLength(1, difference);
Expect.identical(ce2, difference.lookup(ce2));
difference = set2.difference(set1);
testLength(1, difference);
Expect.identical(ce3, difference.lookup(ce3));
// Difference uses other.contains to check for equality.
var set3 = create(identical, identityHashCode, null, identityCompare);
set3.add(ce1b);
difference = set1.difference(set3);
testLength(2, difference); // ce1a is not identical to element in set3.
Expect.identical(ce1a, difference.lookup(ce1a));
Expect.identical(ce2, difference.lookup(ce2));
// Intersection always takes elements from receiver set.
var intersection = set1.intersection(set2);
testLength(1, intersection);
Expect.identical(ce1a, intersection.lookup(ce1a));
intersection = set1.intersection(set3);
testLength(0, intersection);
}
// Objects that are equal based on data.
class CE implements Comparable<CE> {
final int id;
const CE(this.id);
int get hashCode => id;
bool operator ==(Object other) => other is CE && id == other.id;
int compareTo(CE other) => id - other.id;
String toString() => "CE($id)";
}
typedef int CECompare(CE e1, CE e2);
typedef int CEHash(CE e1);
typedef bool CEEq(CE e1, CE e2);
typedef bool ValidKey(dynamic o);
// Equality of Id objects based on id modulo value.
CEEq customEq(int mod) => (CE e1, CE e2) => ((e1.id - e2.id) % mod) == 0;
CEHash customHash(int mod) => (CE e) => e.id % mod;
CECompare customCompare(int mod) =>
(CE e1, CE e2) => (e1.id % mod) - (e2.id % mod);
bool validKey(dynamic o) => o is CE;
final customId = new Map<dynamic, dynamic>.identity();
int counter = 0;
int identityCompare(e1, e2) {
if (identical(e1, e2)) return 0;
int i1 = customId.putIfAbsent(e1, () => ++counter);
int i2 = customId.putIfAbsent(e2, () => ++counter);
return i1 - i2;
}
void testIdentity(Set create()) {
Set set = create();
var e1 = new CE(0);
var e2 = new CE(0);
Expect.equals(e1, e2);
Expect.isFalse(identical(e1, e2));
testLength(0, set);
set.add(e1);
testLength(1, set);
Expect.isTrue(set.contains(e1));
Expect.isFalse(set.contains(e2));
set.add(e2);
testLength(2, set);
Expect.isTrue(set.contains(e1));
Expect.isTrue(set.contains(e2));
var set2 = set.toSet();
testLength(2, set2);
Expect.isTrue(set2.contains(e1));
Expect.isTrue(set2.contains(e2));
}
void testIntSetFrom(setFrom) {
List<num> numList = [2, 3, 5, 7, 11, 13];
Set<int> set1 = setFrom(numList);
Expect.listEquals(numList, set1.toList()..sort());
Set<num> numSet = numList.toSet();
Set<int> set2 = setFrom(numSet);
Expect.listEquals(numList, set2.toList()..sort());
Iterable<num> numIter = numList.where((x) => true);
Set<int> set3 = setFrom(numIter);
Expect.listEquals(numList, set3.toList()..sort());
Set<int> set4 = setFrom(new Iterable.generate(0));
Expect.isTrue(set4.isEmpty);
}
void testCESetFrom(setFrom) {
var ceList = [
new CE(2),
new CE(3),
new CE(5),
new CE(7),
new CE(11),
new CE(13)
];
Set<CE> set1 = setFrom(ceList);
Expect.listEquals(ceList, set1.toList()..sort());
Set<CE> ceSet = ceList.toSet();
Set<CE> set2 = setFrom(ceSet);
Expect.listEquals(ceList, set2.toList()..sort());
Iterable<CE> ceIter = ceList.where((x) => true);
Set<CE> set3 = setFrom(ceIter);
Expect.listEquals(ceList, set3.toList()..sort());
Set<CE> set4 = setFrom(new Iterable.generate(0));
Expect.isTrue(set4.isEmpty);
}
class A {}
class B {}
class C implements A, B {}
void testASetFrom(setFrom) {
List<B> bList = <B>[new C()];
// Set.from allows to cast elements.
Set<A> aSet = setFrom(bList);
Expect.isTrue(aSet.length == 1);
}
void testUnmodifiable(Set source) {
var unmodifiable = Set.unmodifiable(source);
Expect.throwsUnsupportedError(() {
unmodifiable.add(3);
}, "add");
Expect.throwsUnsupportedError(() {
unmodifiable.addAll({1, 2, 3});
}, "addAll");
Expect.throwsUnsupportedError(() {
unmodifiable.addAll(<int>{});
}, "addAll empty");
Expect.throwsUnsupportedError(() {
unmodifiable.remove(3);
}, "remove");
Expect.throwsUnsupportedError(() {
unmodifiable.removeAll({1, 2, 3});
}, "removeAll");
Expect.throwsUnsupportedError(() {
unmodifiable.removeAll(<int>{});
}, "removeAll empty");
Expect.throwsUnsupportedError(() {
unmodifiable.retainAll({1, 2, 3});
}, "retainAll");
Expect.throwsUnsupportedError(() {
unmodifiable.retainAll(<int>{});
}, "retainAll empty");
Expect.throwsUnsupportedError(() {
unmodifiable.removeWhere((_) => true);
}, "removeWhere");
Expect.throwsUnsupportedError(() {
unmodifiable.retainWhere((_) => false);
}, "retainWhere");
Expect.throwsUnsupportedError(() {
unmodifiable.clear();
}, "clear");
}
void testUnmodifiableSetIsNotUpdatedIfSourceSetIsUpdated() {
var modifiable = {1};
var unmodifiable = Set.unmodifiable(modifiable);
modifiable.add(2);
Expect.notEquals(modifiable.length, unmodifiable.length);
Expect.setEquals({2}, modifiable.difference(unmodifiable));
modifiable.removeAll({1, 2});
Expect.setEquals({1}, unmodifiable.difference(modifiable));
Expect.setEquals({1}, unmodifiable);
}
main() {
testMain(() => new HashSet());
testMain(() => new LinkedHashSet());
testMain(() => new HashSet.identity());
testMain(() => new LinkedHashSet.identity());
testMain(() => new HashSet(equals: identical));
testMain(() => new LinkedHashSet(equals: identical));
testMain(() => new HashSet(
equals: (a, b) => a == b,
hashCode: (a) => -a.hashCode,
isValidKey: (a) => true));
testMain(() => new LinkedHashSet(
equals: (a, b) => a == b,
hashCode: (a) => -a.hashCode,
isValidKey: (a) => true));
testMain(() => new SplayTreeSet());
testIdentity(() => new HashSet.identity());
testIdentity(() => new LinkedHashSet.identity());
testIdentity(() => new HashSet(equals: identical));
testIdentity(() => new LinkedHashSet(equals: identical));
testIdentity(() => new SplayTreeSet(identityCompare));
testTypeAnnotations(new HashSet<int>());
testTypeAnnotations(new LinkedHashSet<int>());
testTypeAnnotations(new HashSet<int>(equals: identical));
testTypeAnnotations(new LinkedHashSet<int>(equals: identical));
testTypeAnnotations(new HashSet<int>(
equals: (int a, int b) => a == b,
hashCode: (int a) => a.hashCode,
isValidKey: (a) => a is int));
testTypeAnnotations(new LinkedHashSet<int>(
equals: (int a, int b) => a == b,
hashCode: (int a) => a.hashCode,
isValidKey: (a) => a is int));
testTypeAnnotations(new SplayTreeSet<int>());
testRetainWhere(([equals, hashCode, validKey, comparator]) =>
new HashSet(equals: equals, hashCode: hashCode, isValidKey: validKey));
testRetainWhere(([equals, hashCode, validKey, comparator]) =>
new LinkedHashSet(
equals: equals, hashCode: hashCode, isValidKey: validKey));
testRetainWhere(([equals, hashCode, validKey, comparator]) =>
new SplayTreeSet(comparator, validKey));
testDifferenceIntersection(([equals, hashCode, validKey, comparator]) =>
new HashSet(equals: equals, hashCode: hashCode, isValidKey: validKey));
testDifferenceIntersection(([equals, hashCode, validKey, comparator]) =>
new LinkedHashSet(
equals: equals, hashCode: hashCode, isValidKey: validKey));
testDifferenceIntersection(([equals, hashCode, validKey, comparator]) =>
new SplayTreeSet(comparator, validKey));
testIntSetFrom((x) => new Set<int>.from(x));
testIntSetFrom((x) => new HashSet<int>.from(x));
testIntSetFrom((x) => new LinkedHashSet<int>.from(x));
testIntSetFrom((x) => new SplayTreeSet<int>.from(x));
testCESetFrom((x) => new Set<CE>.from(x));
testCESetFrom((x) => new HashSet<CE>.from(x));
testCESetFrom((x) => new LinkedHashSet<CE>.from(x));
testCESetFrom((x) => new SplayTreeSet<CE>.from(x));
testCESetFrom(
(x) => new SplayTreeSet<CE>.from(x, customCompare(20), validKey));
testCESetFrom((x) => new SplayTreeSet<CE>.from(x, identityCompare));
testASetFrom((x) => new Set<A>.from(x));
testASetFrom((x) => new HashSet<A>.from(x));
testASetFrom((x) => new LinkedHashSet<A>.from(x));
testASetFrom((x) => new SplayTreeSet<A>.from(x, identityCompare));
testUnmodifiable({1});
testUnmodifiableSetIsNotUpdatedIfSourceSetIsUpdated();
}