8129c40c5e
Made it explicit in the documentation that elements of the `LinkedList` are compared using identity, not `operator==`, even if they choose to override that. The linked list entries are directly linked to their containing list, which is what defined their containing list. Merely being equal to an entry of a list does not make another entry belong to that list. The `contains` method now simply checks that the `list` property of the the provided entry is the list itself, which matches the existing behavior of `remove`. Fixes #44189. Bug: https://github.com/dart-lang/sdk/issues/44189 Change-Id: Ia6a7fd461ddf18f99121662f87e83415746e1ca4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/172161 Commit-Queue: Lasse R.H. Nielsen <lrn@google.com> Reviewed-by: Nate Bosch <nbosch@google.com>
314 lines
6.3 KiB
Dart
314 lines
6.3 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.
|
|
|
|
import 'dart:collection';
|
|
import "package:expect/expect.dart";
|
|
|
|
class MyEntry extends LinkedListEntry<MyEntry> {
|
|
final int value;
|
|
|
|
MyEntry(int this.value);
|
|
|
|
String toString() => value.toString();
|
|
|
|
int get hashCode => value.hashCode;
|
|
bool operator ==(Object o) => o is MyEntry && value == o.value;
|
|
}
|
|
|
|
void testPreviousNext() {
|
|
var list = LinkedList<MyEntry>();
|
|
Expect.throws(() => list.first);
|
|
Expect.throws(() => list.last);
|
|
Expect.equals(0, list.length);
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
list.add(MyEntry(i));
|
|
}
|
|
Expect.equals(3, list.length);
|
|
|
|
var entry = list.first;
|
|
Expect.isNull(entry.previous);
|
|
Expect.equals(0, entry.value);
|
|
entry = entry.next!;
|
|
Expect.equals(1, entry.value);
|
|
entry = entry.next!;
|
|
Expect.equals(2, entry.value);
|
|
Expect.isNull(entry.next);
|
|
entry = entry.previous!;
|
|
Expect.equals(1, entry.value);
|
|
entry = entry.previous!;
|
|
Expect.equals(0, entry.value);
|
|
Expect.isNull(entry.previous);
|
|
}
|
|
|
|
void testUnlinked() {
|
|
var unlinked = MyEntry(0);
|
|
Expect.isNull(unlinked.previous);
|
|
Expect.isNull(unlinked.next);
|
|
var list = LinkedList<MyEntry>();
|
|
list.add(unlinked);
|
|
Expect.isNull(unlinked.previous);
|
|
Expect.isNull(unlinked.next);
|
|
list.remove(unlinked);
|
|
Expect.isNull(unlinked.previous);
|
|
Expect.isNull(unlinked.next);
|
|
list.add(unlinked);
|
|
list.add(MyEntry(1));
|
|
Expect.isNull(unlinked.previous);
|
|
Expect.equals(1, unlinked.next!.value);
|
|
list.remove(unlinked);
|
|
Expect.isNull(unlinked.previous);
|
|
Expect.isNull(unlinked.next);
|
|
list.add(unlinked);
|
|
Expect.isNull(unlinked.next);
|
|
Expect.equals(1, unlinked.previous!.value);
|
|
}
|
|
|
|
void testInsert() {
|
|
// Insert last.
|
|
var list = LinkedList<MyEntry>();
|
|
for (int i = 0; i < 10; i++) {
|
|
list.add(MyEntry(i));
|
|
}
|
|
|
|
Expect.equals(10, list.length);
|
|
|
|
int i = 0;
|
|
for (var entry in list) {
|
|
Expect.equals(i, entry.value);
|
|
i++;
|
|
}
|
|
|
|
Expect.equals(10, i);
|
|
|
|
list.clear();
|
|
|
|
// Insert first.
|
|
for (int i = 0; i < 10; i++) {
|
|
list.addFirst(MyEntry(i));
|
|
}
|
|
|
|
Expect.equals(10, list.length);
|
|
|
|
i = 10;
|
|
for (var entry in list) {
|
|
Expect.equals(--i, entry.value);
|
|
}
|
|
Expect.equals(0, i);
|
|
|
|
list.clear();
|
|
|
|
// Insert after.
|
|
list.addFirst(MyEntry(0));
|
|
for (int i = 1; i < 10; i++) {
|
|
list.last.insertAfter(MyEntry(i));
|
|
}
|
|
|
|
Expect.equals(10, list.length);
|
|
|
|
i = 0;
|
|
for (var entry in list) {
|
|
Expect.equals(i, entry.value);
|
|
i++;
|
|
}
|
|
|
|
Expect.equals(10, i);
|
|
|
|
list.clear();
|
|
|
|
// Insert before.
|
|
list.addFirst(MyEntry(0));
|
|
for (int i = 1; i < 10; i++) {
|
|
list.first.insertBefore(MyEntry(i));
|
|
}
|
|
|
|
Expect.equals(10, list.length);
|
|
|
|
i = 10;
|
|
for (var entry in list) {
|
|
Expect.equals(--i, entry.value);
|
|
}
|
|
Expect.equals(0, i);
|
|
|
|
list.clear();
|
|
}
|
|
|
|
void testRemove() {
|
|
var list = LinkedList<MyEntry>();
|
|
for (int i = 0; i < 10; i++) {
|
|
list.add(MyEntry(i));
|
|
}
|
|
|
|
Expect.equals(10, list.length);
|
|
|
|
list.remove(list.skip(5).first);
|
|
|
|
Expect.equals(9, list.length);
|
|
|
|
int i = 0;
|
|
for (var entry in list) {
|
|
if (i == 5) i++;
|
|
Expect.equals(i, entry.value);
|
|
i++;
|
|
}
|
|
|
|
Expect.listEquals(
|
|
[0, 1, 2, 3, 4, 6, 7, 8, 9], list.map((e) => e.value).toList());
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
list.first.unlink();
|
|
}
|
|
|
|
Expect.throws(() => list.first);
|
|
|
|
Expect.equals(0, list.length);
|
|
}
|
|
|
|
void testContains() {
|
|
var list = LinkedList<MyEntry>();
|
|
var entry5 = MyEntry(5);
|
|
|
|
// Empty lists contains nothing.
|
|
Expect.isFalse(list.contains(null));
|
|
Expect.isFalse(list.contains(Object()));
|
|
Expect.isFalse(list.contains(entry5));
|
|
|
|
// Works for singleton lists.
|
|
list.add(MyEntry(0));
|
|
Expect.isTrue(list.contains(list.first));
|
|
Expect.isFalse(list.contains(null));
|
|
Expect.isFalse(list.contains(Object()));
|
|
Expect.isFalse(list.contains(entry5));
|
|
|
|
// Works for larger lists.
|
|
for (int i = 1; i < 10; i++) {
|
|
list.add(MyEntry(i));
|
|
}
|
|
for (var entry in list) {
|
|
Expect.isTrue(list.contains(entry));
|
|
}
|
|
Expect.isFalse(list.contains(Object()));
|
|
Expect.isFalse(list.contains(null));
|
|
Expect.isFalse(list.contains(entry5));
|
|
// Based on identity, not equality.
|
|
Expect.equals(entry5, list.elementAt(5));
|
|
}
|
|
|
|
void testBadAdd() {
|
|
var list1 = LinkedList<MyEntry>();
|
|
list1.addFirst(MyEntry(0));
|
|
|
|
var list2 = LinkedList<MyEntry>();
|
|
Expect.throws(() => list2.addFirst(list1.first));
|
|
|
|
Expect.throws(() => MyEntry(0).unlink());
|
|
}
|
|
|
|
void testConcurrentModificationError() {
|
|
test(function(LinkedList<MyEntry> ll)) {
|
|
var ll = LinkedList<MyEntry>();
|
|
for (int i = 0; i < 10; i++) {
|
|
ll.add(MyEntry(i));
|
|
}
|
|
Expect.throws(() => function(ll), (e) => e is ConcurrentModificationError);
|
|
}
|
|
|
|
test((ll) {
|
|
for (var x in ll) {
|
|
ll.remove(x);
|
|
}
|
|
});
|
|
test((ll) {
|
|
ll.forEach((x) {
|
|
ll.remove(x);
|
|
});
|
|
});
|
|
test((ll) {
|
|
ll.any((x) {
|
|
ll.remove(x);
|
|
return false;
|
|
});
|
|
});
|
|
test((ll) {
|
|
ll.every((x) {
|
|
ll.remove(x);
|
|
return true;
|
|
});
|
|
});
|
|
test((ll) {
|
|
ll.fold(0, (x, y) {
|
|
ll.remove(y);
|
|
return x;
|
|
});
|
|
});
|
|
test((ll) {
|
|
ll.reduce((x, y) {
|
|
ll.remove(y);
|
|
return x;
|
|
});
|
|
});
|
|
test((ll) {
|
|
ll.where((x) {
|
|
ll.remove(x);
|
|
return true;
|
|
}).forEach((_) {});
|
|
});
|
|
test((ll) {
|
|
ll.map((x) {
|
|
ll.remove(x);
|
|
return x;
|
|
}).forEach((_) {});
|
|
});
|
|
test((ll) {
|
|
ll.expand((x) {
|
|
ll.remove(x);
|
|
return [x];
|
|
}).forEach((_) {});
|
|
});
|
|
test((ll) {
|
|
ll.takeWhile((x) {
|
|
ll.remove(x);
|
|
return true;
|
|
}).forEach((_) {});
|
|
});
|
|
test((ll) {
|
|
ll.skipWhile((x) {
|
|
ll.remove(x);
|
|
return true;
|
|
}).forEach((_) {});
|
|
});
|
|
test((ll) {
|
|
bool first = true;
|
|
ll.firstWhere((x) {
|
|
ll.remove(x);
|
|
if (!first) return true;
|
|
return first = false;
|
|
});
|
|
});
|
|
test((ll) {
|
|
ll.lastWhere((x) {
|
|
ll.remove(x);
|
|
return true;
|
|
});
|
|
});
|
|
test((ll) {
|
|
bool first = true;
|
|
ll.singleWhere((x) {
|
|
ll.remove(x);
|
|
if (!first) return false;
|
|
return !(first = false);
|
|
});
|
|
});
|
|
}
|
|
|
|
void main() {
|
|
testPreviousNext();
|
|
testUnlinked();
|
|
testInsert();
|
|
testRemove();
|
|
testContains();
|
|
testBadAdd();
|
|
testConcurrentModificationError();
|
|
}
|