779fe017ba
There are many failing Dart2JS language/library tests due to differences in backend implementations. We want to adopt a strategy that allows us to make explicit the different expectations we have of each backend. This adds logic for the 2 most common causes of these backend related failures (numbers and implicit checks) but we can add more of these as we discover more use cases. This is an iteration on https://dart-review.googlesource.com/c/sdk/+/293463 attempting to integrate feedback from that change. Change-Id: Ie3a1954066199695d92881497e940385467c9a12 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/311780 Commit-Queue: Nate Biggs <natebiggs@google.com> Reviewed-by: Stephen Adams <sra@google.com> Reviewed-by: Lasse Nielsen <lrn@google.com>
384 lines
10 KiB
Dart
384 lines
10 KiB
Dart
// Copyright (c) 2013, 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.
|
|
//
|
|
// VMOptions=
|
|
|
|
// Tests of hash set behavior, with focus in iteration and concurrent
|
|
// modification errors.
|
|
|
|
library hash_map2_test;
|
|
|
|
import "package:expect/expect.dart";
|
|
import 'dart:collection';
|
|
import 'dart:math' as math;
|
|
|
|
testSet(Set newSet(), Set newSetFrom(Iterable from)) {
|
|
Set gen(int from, int to) =>
|
|
new Set.from(new Iterable.generate(to - from, (n) => n + from));
|
|
|
|
bool odd(n) => (n & 1) == 1;
|
|
bool even(n) => (n & 1) == 0;
|
|
|
|
{
|
|
// Test growing to largish capacity.
|
|
Set set = newSet();
|
|
|
|
for (int i = 0; i < 256; i++) {
|
|
set.add(i);
|
|
}
|
|
|
|
set.addAll(gen(256, 512));
|
|
set.addAll(newSetFrom(gen(512, 1000)));
|
|
Expect.equals(1000, set.length);
|
|
|
|
// Remove half.
|
|
for (int i = 0; i < 1000; i += 2) set.remove(i);
|
|
Expect.equals(500, set.length);
|
|
Expect.isFalse(set.any(even));
|
|
Expect.isTrue(set.every(odd));
|
|
|
|
// Re-add all.
|
|
set.addAll(gen(0, 1000));
|
|
Expect.equals(1000, set.length);
|
|
}
|
|
|
|
{
|
|
// Test having many deleted elements.
|
|
Set set = newSet();
|
|
set.add(0);
|
|
for (int i = 0; i < 1000; i++) {
|
|
set.add(i + 1);
|
|
set.remove(i);
|
|
Expect.equals(1, set.length);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Test having many elements with same hashCode
|
|
Set set = newSet();
|
|
for (int i = 0; i < 1000; i++) {
|
|
set.add(new BadHashCode());
|
|
}
|
|
Expect.equals(1000, set.length);
|
|
}
|
|
|
|
{
|
|
// Check concurrent modification
|
|
Set set = newSet()
|
|
..add(0)
|
|
..add(1);
|
|
|
|
{
|
|
// Test adding before a moveNext.
|
|
Iterator iter = set.iterator;
|
|
iter.moveNext();
|
|
set.add(1); // Updating existing key isn't a modification.
|
|
iter.moveNext();
|
|
set.add(2);
|
|
Expect.throws(iter.moveNext, (e) => e is Error);
|
|
}
|
|
|
|
{
|
|
// Test adding after last element.
|
|
Iterator iter = set.iterator;
|
|
Expect.equals(3, set.length);
|
|
iter.moveNext();
|
|
iter.moveNext();
|
|
iter.moveNext();
|
|
set.add(3);
|
|
Expect.throws(iter.moveNext, (e) => e is Error);
|
|
}
|
|
|
|
{
|
|
// Test removing during iteration.
|
|
Iterator iter = set.iterator;
|
|
iter.moveNext();
|
|
set.remove(1000); // Not a modification if it's not there.
|
|
iter.moveNext();
|
|
int n = iter.current;
|
|
set.remove(n);
|
|
// Removing doesn't change current.
|
|
Expect.equals(n, iter.current);
|
|
Expect.throws(iter.moveNext, (e) => e is Error);
|
|
}
|
|
|
|
{
|
|
// Test removing after last element.
|
|
Iterator iter = set.iterator;
|
|
Expect.equals(3, set.length);
|
|
iter.moveNext();
|
|
iter.moveNext();
|
|
iter.moveNext();
|
|
int n = iter.current;
|
|
set.remove(n);
|
|
// Removing doesn't change current.
|
|
Expect.equals(n, iter.current);
|
|
Expect.throws(iter.moveNext, (e) => e is Error);
|
|
}
|
|
|
|
{
|
|
// Test that updating value doesn't cause error.
|
|
Iterator iter = set.iterator;
|
|
Expect.equals(2, set.length);
|
|
iter.moveNext();
|
|
int n = iter.current;
|
|
set.add(n);
|
|
iter.moveNext();
|
|
Expect.isTrue(set.contains(iter.current));
|
|
}
|
|
|
|
{
|
|
// Check adding many existing values isn't considered modification.
|
|
Set set2 = newSet();
|
|
for (var value in set) {
|
|
set2.add(value);
|
|
}
|
|
Iterator iter = set.iterator;
|
|
set.addAll(set2);
|
|
// Shouldn't throw.
|
|
iter.moveNext();
|
|
}
|
|
}
|
|
|
|
{
|
|
// Check that updating existing elements is not a modification.
|
|
// This must be the case even if the underlying data structure is
|
|
// nearly full.
|
|
for (int i = 1; i < 128; i++) {
|
|
// Create maps of different sizes, some of which should be
|
|
// at a limit of the underlying data structure.
|
|
Set set = newSetFrom(gen(0, i));
|
|
Iterator iter = set.iterator;
|
|
for (int j = 0; j < i; j++) {
|
|
set.add(j);
|
|
}
|
|
iter.moveNext(); // Should not throw.
|
|
|
|
for (int j = 1; j < i; j++) {
|
|
set.remove(j);
|
|
}
|
|
iter = set.iterator;
|
|
set.add(0);
|
|
iter.moveNext(); // Should not throw.
|
|
}
|
|
}
|
|
|
|
{
|
|
// Check that null can be in the set.
|
|
Set set = newSet();
|
|
set.add(null);
|
|
Expect.equals(1, set.length);
|
|
Expect.isTrue(set.contains(null));
|
|
Expect.isNull(set.first);
|
|
Expect.isNull(set.last);
|
|
set.add(null);
|
|
Expect.equals(1, set.length);
|
|
Expect.isTrue(set.contains(null));
|
|
set.remove(null);
|
|
Expect.isTrue(set.isEmpty);
|
|
Expect.isFalse(set.contains(null));
|
|
|
|
// Created using Set.from.
|
|
set = newSetFrom([null]);
|
|
Expect.equals(1, set.length);
|
|
Expect.isTrue(set.contains(null));
|
|
Expect.isNull(set.first);
|
|
Expect.isNull(set.last);
|
|
set.add(null);
|
|
Expect.equals(1, set.length);
|
|
Expect.isTrue(set.contains(null));
|
|
set.remove(null);
|
|
Expect.isTrue(set.isEmpty);
|
|
Expect.isFalse(set.contains(null));
|
|
|
|
// Set that grows with null in it.
|
|
set = newSetFrom([1, 2, 3, null, 4, 5, 6]);
|
|
Expect.equals(7, set.length);
|
|
for (int i = 7; i < 128; i++) {
|
|
set.add(i);
|
|
}
|
|
Expect.equals(128, set.length);
|
|
Expect.isTrue(set.contains(null));
|
|
set.add(null);
|
|
Expect.equals(128, set.length);
|
|
Expect.isTrue(set.contains(null));
|
|
set.remove(null);
|
|
Expect.equals(127, set.length);
|
|
Expect.isFalse(set.contains(null));
|
|
}
|
|
|
|
{
|
|
// Check that addAll and clear works.
|
|
Set set = newSet();
|
|
set.addAll([]);
|
|
Expect.isTrue(set.isEmpty);
|
|
set.addAll([1, 3, 2]);
|
|
Expect.equals(3, set.length);
|
|
Expect.isTrue(set.contains(1));
|
|
Expect.isTrue(set.contains(3));
|
|
Expect.isTrue(set.contains(2));
|
|
Expect.isFalse(set.contains(4));
|
|
set.clear();
|
|
Expect.isTrue(set.isEmpty);
|
|
}
|
|
|
|
{
|
|
// Check that removeWhere and retainWhere work.
|
|
Set set = newSetFrom([1, 2, 3]);
|
|
set.removeWhere((each) => each == 2);
|
|
Expect.equals(2, set.length);
|
|
Expect.isTrue(set.contains(1));
|
|
Expect.isFalse(set.contains(2));
|
|
Expect.isTrue(set.contains(3));
|
|
set.retainWhere((each) => each == 3);
|
|
Expect.equals(1, set.length);
|
|
Expect.isFalse(set.contains(1));
|
|
Expect.isFalse(set.contains(2));
|
|
Expect.isTrue(set.contains(3));
|
|
}
|
|
|
|
{
|
|
// Test lookup
|
|
Set set = newSet();
|
|
var m1a = new Mutable(1);
|
|
var m1b = new Mutable(1);
|
|
var m2a = new Mutable(2);
|
|
var m2b = new Mutable(2);
|
|
Expect.isNull(set.lookup(m1a));
|
|
Expect.isNull(set.lookup(m1b));
|
|
set.add(m1a);
|
|
Expect.identical(m1a, set.lookup(m1a));
|
|
Expect.identical(m1a, set.lookup(m1b));
|
|
|
|
Expect.isNull(set.lookup(m2a));
|
|
Expect.isNull(set.lookup(m2b));
|
|
set.add(m2a);
|
|
Expect.identical(m2a, set.lookup(m2a));
|
|
Expect.identical(m2a, set.lookup(m2b));
|
|
|
|
set.add(m2b); // Adding doesn't change element.
|
|
Expect.identical(m2a, set.lookup(m2a));
|
|
Expect.identical(m2a, set.lookup(m2b));
|
|
|
|
set.remove(m1a);
|
|
set.add(m1b);
|
|
Expect.identical(m1b, set.lookup(m1a));
|
|
Expect.identical(m1b, set.lookup(m1b));
|
|
|
|
set.add(1);
|
|
Expect.identical(1, set.lookup(1.0));
|
|
set.add(-0.0);
|
|
Expect.identical(-0.0, set.lookup(0.0));
|
|
}
|
|
|
|
{
|
|
// Test special hash codes
|
|
Set set = newSet();
|
|
List keys = [];
|
|
// Powers of two
|
|
for (int i = 63; i >= 2; --i) {
|
|
keys.add(new Mutable(math.pow(2, i) as int));
|
|
}
|
|
for (var key in keys) {
|
|
Expect.isTrue(set.add(key));
|
|
}
|
|
for (var key in keys) {
|
|
Expect.isTrue(set.contains(key));
|
|
}
|
|
}
|
|
}
|
|
|
|
void testIdentitySet(Set create()) {
|
|
Set set = create();
|
|
set.add(1);
|
|
set.add(2);
|
|
set.add(1); // Integers are identical if equal.
|
|
Expect.equals(2, set.length);
|
|
var complex = 4;
|
|
complex = set.length == 2 ? complex ~/ 4 : 87; // Avoid compile-time constant.
|
|
Expect.isTrue(set.contains(complex)); // 1 is in set, even if computed.
|
|
set.clear();
|
|
|
|
// All compile time constants are identical to themselves.
|
|
var constants = [
|
|
double.infinity,
|
|
double.nan,
|
|
-0.0,
|
|
0.0,
|
|
42,
|
|
"",
|
|
null,
|
|
false,
|
|
true,
|
|
#bif,
|
|
testIdentitySet
|
|
];
|
|
set.addAll(constants);
|
|
if (webNumbers) {
|
|
// 0.0 and -0.0 are identical in JS.
|
|
Expect.equals(constants.length - 1, set.length);
|
|
Expect.isTrue(
|
|
set.containsAll(constants.where((e) => !(e is double && e.isNaN))),
|
|
"constants: $set");
|
|
} else {
|
|
Expect.equals(constants.length, set.length);
|
|
Expect.isTrue(set.containsAll(constants), "constants: $set");
|
|
}
|
|
for (var c in constants) {
|
|
// identical(double.nan, double.nan) == false in JS.
|
|
if (webNumbers && c is double && c.isNaN) continue;
|
|
Expect.isTrue(set.contains(c), "constant: $c");
|
|
}
|
|
set.clear();
|
|
|
|
var m1 = new Mutable(1);
|
|
var m2 = new Mutable(2);
|
|
var m3 = new Mutable(3);
|
|
var m4 = new Mutable(2); // Equal to m2, but not identical.
|
|
set.addAll([m1, m2, m3, m4]);
|
|
Expect.equals(4, set.length);
|
|
Expect.equals(3, m3.hashCode);
|
|
m3.id = 1;
|
|
Expect.equals(1, m3.hashCode);
|
|
// Changing hashCode doesn't affect lookup.
|
|
Expect.isTrue(set.contains(m3));
|
|
Expect.isTrue(set.contains(m1));
|
|
set.remove(m3);
|
|
Expect.isFalse(set.contains(m3));
|
|
Expect.isTrue(set.contains(m1));
|
|
|
|
Expect.identical(m1, set.lookup(m1));
|
|
Expect.identical(null, set.lookup(m3));
|
|
}
|
|
|
|
void main() {
|
|
testSet(() => new Set(), (m) => new Set.from(m));
|
|
testSet(() => new HashSet(), (m) => new HashSet.from(m));
|
|
testSet(() => new LinkedHashSet(), (m) => new LinkedHashSet.from(m));
|
|
testIdentitySet(() => new Set.identity());
|
|
testIdentitySet(() => new HashSet.identity());
|
|
testIdentitySet(() => new LinkedHashSet.identity());
|
|
testIdentitySet(() => new HashSet(
|
|
equals: (x, y) => identical(x, y), hashCode: (x) => identityHashCode(x)));
|
|
testIdentitySet(() => new LinkedHashSet(
|
|
equals: (x, y) => identical(x, y), hashCode: (x) => identityHashCode(x)));
|
|
}
|
|
|
|
class BadHashCode {
|
|
static int idCounter = 0;
|
|
final int id;
|
|
BadHashCode() : id = idCounter++;
|
|
int get hashCode => 42;
|
|
// operator == is identity.
|
|
// Can't make a bad compareTo that isn't invalid.
|
|
int compareTo(BadHashCode other) => id - other.id;
|
|
}
|
|
|
|
class Mutable {
|
|
int id;
|
|
Mutable(this.id);
|
|
int get hashCode => id;
|
|
bool operator ==(other) => other is Mutable && id == other.id;
|
|
}
|