dart-sdk/tests/lib/collection/queue_test.dart
Lasse R.H. Nielsen ed3824a220 Tweaks, refactoring and updates on DoubleLinkedQueue.
It's a badly designed class, but this change makes it slightly better.
(We should consider moving it to package:collection and removing it from
the platform libraries. Eventually.)

The changes decouples the public `DoubleLinkedQueueEntry` class from
the actual queue implementation classes, avoiding the latter having
to share a generic `_Link<DoubleLinkedQueueEntry>` interface
which would force them to do otherwise unnecessary casts.

The public class should have been abstract, but isn't.
It's mainly used as the API of the entries exposed by DoubleLinkedQueue,
but it can also be used by itself to build double-linked lists that are
not part of a Queue, and the class can be extended.
If anyone does either, it should keep working, so it needs to be a concrete,
self-contained class with a generative constructor.

That class is moved to dart:_internal to avoid its private
implementation fields from interfering with the queue implementation classes,
which have similar fields, but can now have them at different types.
That's a hack, but it works.

The internal implementation of the DoubleLinkedQueue and its entries
are updated to better follow current programming idioms, and avoids
having fields on classes which don't need them.

Also fixes #27920
(`clear`ing a list now detaches each entry from the list so later `.contains`
won't give the wrong answer).

And moves queue tests from tests/corelib to tests/lib/collection,
since Queue isn't exposed by `dart:core`.

BUG= https://github.com/dart-lang/sdk/issues/27920

Change-Id: Ia3bb340a75886de160cc0e449947e8e7ee587061
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/211265
Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
Reviewed-by: Nate Bosch <nbosch@google.com>
2021-08-31 12:44:39 +00:00

484 lines
12 KiB
Dart

// 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 queue.test;
import "package:expect/expect.dart";
import 'dart:collection';
abstract class QueueTest {
Queue newQueue();
Queue newQueueFrom(Iterable iterable);
void testMain() {
Queue queue = newQueue();
checkQueue(queue, 0, 0);
queue.addFirst(1);
checkQueue(queue, 1, 1);
queue.addLast(10);
checkQueue(queue, 2, 11);
Expect.equals(10, queue.removeLast());
checkQueue(queue, 1, 1);
queue.addLast(10);
Expect.equals(1, queue.removeFirst());
checkQueue(queue, 1, 10);
queue.addFirst(1);
queue.addLast(100);
queue.addLast(1000);
Expect.equals(1000, queue.removeLast());
queue.addLast(1000);
checkQueue(queue, 4, 1111);
queue.removeFirst();
checkQueue(queue, 3, 1110);
dynamic mapTest(dynamic value) {
return (value as int) ~/ 10;
}
bool is10(dynamic value) {
return (value == 10);
}
Queue mapped = newQueueFrom(queue.map(mapTest));
checkQueue(mapped, 3, 111);
checkQueue(queue, 3, 1110);
Expect.equals(1, mapped.removeFirst());
Expect.equals(100, mapped.removeLast());
Expect.equals(10, mapped.removeFirst());
Queue other = newQueueFrom(queue.where(is10));
checkQueue(other, 1, 10);
Expect.isTrue(queue.any(is10));
bool isInstanceOfInt(dynamic value) {
return (value is int);
}
Expect.isTrue(queue.every(isInstanceOfInt));
Expect.isFalse(queue.every(is10));
bool is1(dynamic value) {
return (value == 1);
}
Expect.isFalse(queue.any(is1));
queue.clear();
Expect.equals(0, queue.length);
Expect.throws<StateError>(queue.removeFirst);
Expect.equals(0, queue.length);
Expect.throws<StateError>(queue.removeLast);
Expect.equals(0, queue.length);
queue.addFirst(1);
queue.addFirst(2);
Expect.equals(2, queue.first);
Expect.equals(1, queue.last);
queue.addLast(3);
Expect.equals(3, queue.last);
bool isGreaterThanOne(dynamic value) {
return ((value as int) > 1);
}
other = newQueueFrom(queue.where(isGreaterThanOne));
checkQueue(other, 2, 5);
// Cycle through values without ever having large element count.
queue = newQueue();
queue.add(0);
for (int i = 0; i < 255; i++) {
queue.add(i + 1);
Expect.equals(i, queue.removeFirst());
}
Expect.equals(255, queue.removeFirst());
Expect.isTrue(queue.isEmpty);
testAddAll();
testLengthChanges();
testLarge();
testFromListToList();
}
void checkQueue(Queue queue, int expectedSize, int expectedSum) {
testLength(expectedSize, queue);
int sum = 0;
void sumElements(dynamic value) {
sum += value as int;
}
queue.forEach(sumElements);
Expect.equals(expectedSum, sum);
}
void testLength(int length, Queue queue) {
Expect.equals(length, queue.length);
((length == 0) ? Expect.isTrue : Expect.isFalse)(queue.isEmpty);
((length != 0) ? Expect.isTrue : Expect.isFalse)(queue.isNotEmpty);
}
void testAddAll() {
Set<int> set = new Set<int>.from([1, 2, 4]);
Expect.equals(3, set.length);
Queue queue1 = newQueueFrom(set);
Queue queue2 = newQueue();
Queue queue3 = newQueue();
testLength(3, queue1);
testLength(0, queue2);
testLength(0, queue3);
queue2.addAll(set);
testLength(3, queue2);
queue3.addAll(queue1);
testLength(3, queue3);
int sum = 0;
void f(dynamic e) {
sum += (e as int);
}
set.forEach(f);
Expect.equals(7, sum);
sum = 0;
queue1.forEach(f);
Expect.equals(7, sum);
sum = 0;
queue2.forEach(f);
Expect.equals(7, sum);
sum = 0;
queue3.forEach(f);
Expect.equals(7, sum);
sum = 0;
set = new Set<int>.from([]);
queue1 = newQueueFrom(set);
queue2 = newQueue();
queue3 = newQueue();
queue2.addAll(set);
queue3.addAll(queue1);
Expect.equals(0, set.length);
Expect.equals(0, queue1.length);
Expect.equals(0, queue2.length);
Expect.equals(0, queue3.length);
}
void testLengthChanges() {
// Test that the length property is updated properly by
// modifications;
Queue queue = newQueue();
testLength(0, queue);
for (int i = 1; i <= 10; i++) {
queue.add(i);
testLength(i, queue);
}
for (int i = 1; i <= 10; i++) {
queue.addFirst(11 - i);
testLength(10 + i, queue);
}
for (int i = 1; i <= 10; i++) {
queue.addLast(i);
testLength(20 + i, queue);
}
queue.addAll([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
testLength(40, queue);
for (int i = 1; i <= 5; i++) {
Expect.equals(i, queue.removeFirst());
testLength(40 - i, queue);
}
for (int i = 1; i <= 5; i++) {
Expect.equals(11 - i, queue.removeLast());
testLength(35 - i, queue);
}
Expect.isTrue(queue.remove(10));
testLength(29, queue);
Expect.isFalse(queue.remove(999));
testLength(29, queue);
queue.removeWhere((x) => x == 7);
testLength(26, queue);
queue.retainWhere((x) => x != 3);
testLength(23, queue);
Expect.listEquals(
[6, 8, 9, 1, 2, 4, 5, 6, 8, 9, 10, 1, 2, 4, 5, 6, 8, 9, 10, 1, 2, 4, 5],
queue.toList());
// Regression test: http://dartbug.com/16270
// These should all do nothing, and should not throw.
Queue emptyQueue = newQueue();
emptyQueue.remove(0);
emptyQueue.removeWhere((x) => throw "unreachable");
emptyQueue.retainWhere((x) => throw "unreachable");
Expect.equals(0, emptyQueue.length);
}
void testLarge() {
int N = 10000;
Set set = new Set();
Queue queue = newQueue();
Expect.isTrue(queue.isEmpty);
for (int i = 0; i < N; i++) {
queue.add(i);
set.add(i);
}
Expect.equals(N, queue.length);
Expect.isFalse(queue.isEmpty);
Expect.equals(0, queue.elementAt(0));
Expect.equals(N - 1, queue.elementAt(N - 1));
Expect.throws(() {
queue.elementAt(-1);
});
Expect.throws(() {
queue.elementAt(N);
});
Iterable skip1 = queue.skip(1);
Iterable take1 = queue.take(1);
Iterable mapped = queue.map((e) => -e);
for (int i = 0; i < 500; i++) {
Expect.equals(i, take1.first);
Expect.equals(i, queue.first);
Expect.equals(-i, mapped.first);
Expect.equals(i + 1, skip1.first);
Expect.equals(i, queue.removeFirst());
Expect.equals(i + 1, take1.first);
Expect.equals(-i - 1, mapped.first);
Expect.equals(N - 1 - i, queue.last);
Expect.equals(N - 1 - i, queue.removeLast());
}
Expect.equals(N - 1000, queue.length);
Expect.isTrue(queue.remove(N >> 1));
Expect.equals(N - 1001, queue.length);
queue.clear();
Expect.equals(0, queue.length);
Expect.isTrue(queue.isEmpty);
queue.addAll(set);
Expect.equals(N, queue.length);
Expect.isFalse(queue.isEmpty);
// Iterate.
for (var element in queue) {
Expect.isTrue(set.contains(element));
}
queue.forEach((element) {
Expect.isTrue(set.contains(element));
});
queue.addAll(set);
Expect.equals(N * 2, queue.length);
Expect.isFalse(queue.isEmpty);
queue.clear();
Expect.equals(0, queue.length);
Expect.isTrue(queue.isEmpty);
}
void testFromListToList() {
const int N = 256;
List list = [];
for (int i = 0; i < N; i++) {
Queue queue = newQueueFrom(list);
Expect.equals(list.length, queue.length);
List to = queue.toList();
Expect.listEquals(list, to);
queue.add(i);
list.add(i);
Expect.equals(list.length, queue.length);
to = queue.toList();
Expect.listEquals(list, to);
}
}
}
class ListQueueTest extends QueueTest {
Queue newQueue() => new ListQueue();
Queue newQueueFrom(Iterable elements) => new ListQueue.from(elements);
void testMain() {
super.testMain();
trickyTest();
}
void trickyTest() {
// Test behavior around the know growing capacities of a ListQueue.
Queue q = new ListQueue();
for (int i = 0; i < 255; i++) {
q.add(i);
}
for (int i = 0; i < 128; i++) {
Expect.equals(i, q.removeFirst());
}
q.add(255);
for (int i = 0; i < 127; i++) {
q.add(i);
}
Expect.equals(255, q.length);
// Remove element at end of internal buffer.
q.removeWhere((v) => v == 255);
// Remove element at beginning of internal buffer.
q.removeWhere((v) => v == 0);
// Remove element at both ends of internal buffer.
q.removeWhere((v) => v == 254 || v == 1);
Expect.equals(251, q.length);
Iterable i255 = new Iterable.generate(255, (x) => x);
q = new ListQueue();
q.addAll(i255);
Expect.listEquals(i255.toList(), q.toList());
q = new ListQueue();
q.addAll(i255.toList());
Expect.listEquals(i255.toList(), q.toList());
q = new ListQueue.from(i255);
for (int i = 0; i < 128; i++) q.removeFirst();
q.add(256);
q.add(0);
q.addAll(i255.toList());
Expect.equals(129 + 255, q.length);
// Test addAll that requires the queue to grow.
q = new ListQueue();
q.addAll(i255.take(35));
q.addAll(i255.skip(35).take(96));
q.addAll(i255.skip(35 + 96));
Expect.listEquals(i255.toList(), q.toList());
}
}
class DoubleLinkedQueueTest extends QueueTest {
Queue newQueue() => new DoubleLinkedQueue();
Queue newQueueFrom(Iterable elements) => new DoubleLinkedQueue.from(elements);
void testMain() {
super.testMain();
testQueueElements();
}
void testQueueElements() {
DoubleLinkedQueue<int> queue1 = new DoubleLinkedQueue<int>.from([1, 2, 3]);
DoubleLinkedQueue<int> queue2 = new DoubleLinkedQueue<int>();
queue2.addAll(queue1);
Expect.equals(queue1.length, queue2.length);
DoubleLinkedQueueEntry<int>? entry1 = queue1.firstEntry();
DoubleLinkedQueueEntry<int>? entry2 = queue2.firstEntry();
while (entry1 != null) {
Expect.notIdentical(entry1, entry2);
entry1 = entry1.nextEntry();
entry2 = entry2!.nextEntry();
}
Expect.isNull(entry2);
var firstEntry = queue1.firstEntry()!;
var secondEntry = queue1.firstEntry()!.nextEntry()!;
var thirdEntry = queue1.lastEntry()!;
firstEntry.prepend(4);
firstEntry.append(5);
secondEntry.prepend(6);
secondEntry.append(7);
thirdEntry.prepend(8);
thirdEntry.append(9);
Expect.equals(9, queue1.length);
Expect.listEquals([4, 1, 5, 6, 2, 7, 8, 3, 9], queue1.toList());
Expect.equals(1, firstEntry.remove());
Expect.equals(2, secondEntry.remove());
Expect.equals(3, thirdEntry.remove());
Expect.equals(6, queue1.length);
Expect.listEquals([4, 5, 6, 7, 8, 9], queue1.toList());
var elements = [];
queue1.forEachEntry((entry) {
elements.add(entry.element);
entry.remove(); // Can remove while iterating.
});
Expect.listEquals([4, 5, 6, 7, 8, 9], elements);
Expect.isTrue(queue1.isEmpty);
}
}
void linkEntryTest() {
var entry = new DoubleLinkedQueueEntry(42);
Expect.isNull(entry.previousEntry());
Expect.isNull(entry.nextEntry());
entry.append(37);
entry.prepend(87);
var prev = entry.previousEntry()!;
var next = entry.nextEntry()!;
Expect.equals(42, entry.element);
Expect.equals(37, next.element);
Expect.equals(87, prev.element);
Expect.identical(entry, prev.nextEntry());
Expect.identical(entry, next.previousEntry());
Expect.isNull(next.nextEntry());
Expect.isNull(prev.previousEntry());
entry.element = 117;
Expect.equals(117, entry.element);
Expect.identical(next, entry.nextEntry());
Expect.identical(prev, entry.previousEntry());
Expect.equals(117, entry.remove());
Expect.identical(next, prev.nextEntry());
Expect.identical(prev, next.previousEntry());
Expect.isNull(next.nextEntry());
Expect.isNull(prev.previousEntry());
Expect.equals(37, next.element);
Expect.equals(87, prev.element);
Expect.equals(37, next.remove());
Expect.equals(87, prev.element);
Expect.isNull(prev.nextEntry());
Expect.isNull(prev.previousEntry());
Expect.equals(87, prev.remove());
}
main() {
new DoubleLinkedQueueTest().testMain();
new ListQueueTest().testMain();
linkEntryTest();
}