blob: fb7d2e4d17bc363d6613850e339b8d7c1ee2220c [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 async_star_test;
import "package:unittest/unittest.dart";
import "dart:async";
main() {
group("basic", () {
test("empty", () {
f() async* {}
return f().toList().then((v) {
expect(v, equals([]));
});
});
test("single", () {
f() async* { yield 42; }
return f().toList().then((v) {
expect(v, equals([42]));
});
});
test("call delays", () {
var list = [];
f() async* { list.add(1); yield 2; }
var res = f().forEach(list.add);
list.add(0);
return res.whenComplete(() {
expect(list, equals([0, 1, 2]));
});
});
test("throws", () {
f() async* { yield 1; throw 2; }
var completer = new Completer();
var list = [];
f().listen(list.add,
onError: (v) => list.add("$v"),
onDone: completer.complete);
return completer.future.whenComplete(() {
expect(list, equals([1, "2"]));
});
});
test("multiple", () {
f() async* {
for (int i = 0; i < 10; i++) {
yield i;
}
}
return expectList(f(), new List.generate(10, id));
});
test("allows await", () {
f() async* {
var x = await new Future.value(42);
yield x;
x = await new Future.value(42);
}
return expectList(f(), [42]);
});
test("allows await in loop", () {
f() async* {
for (int i = 0; i < 10; i++) {
yield await i;
}
}
return expectList(f(), new List.generate(10, id));
});
test("allows yield*", () {
f() async* {
yield* new Stream.fromIterable([1, 2, 3]);
}
return expectList(f(), [1, 2, 3]);
});
test("allows yield* of async*", () {
f(n) async* {
yield n;
if (n == 0) return;
yield* f(n - 1);
yield n;
}
return expectList(f(3), [3, 2, 1, 0, 1, 2, 3]);
});
test("Cannot yield* non-stream", () {
f(s) async* {
yield* s;
}
return f(42).transform(getErrors).single.then((v) {
// Not implementing Stream.
expect(v is Error, isTrue);
});
});
test("Cannot yield* non-stream", () {
f(s) async* {
yield* s;
}
return f(new NotAStream()).transform(getErrors).single.then((v) {
// Not implementing Stream.
expect(v is Error, isTrue);
});
});
});
group("yield statement context", () {
test("plain", () {
f() async* {
yield 0;
}
return expectList(f(), [0]);
});
test("if-then-else", () {
f(b) async* {
if (b) yield 0; else yield 1;
}
return expectList(f(true), [0]).whenComplete(() {
expectList(f(false), [1]);
});
});
test("block", () {
f() async* {
yield 0;
{ yield 1; }
yield 2;
}
return expectList(f(), [0, 1, 2]);
});
test("labeled", () {
f() async* {
label1: yield 0;
}
return expectList(f(), [0]);
});
test("for-loop", () {
f() async* {
for (int i = 0; i < 3; i++) yield i;
}
return expectList(f(), [0, 1, 2]);
});
test("for-in-loop", () {
f() async* {
for (var i in [0, 1, 2]) yield i;
}
return expectList(f(), [0, 1, 2]);
});
test("await for-in-loop", () {
f() async* {
await for (var i in new Stream.fromIterable([0, 1, 2])) yield i;
}
return expectList(f(), [0, 1, 2]);
});
test("while-loop", () {
f() async* {
int i = 0;
while (i < 3) yield i++;
}
return expectList(f(), [0, 1, 2]);
});
test("do-while-loop", () {
f() async* {
int i = 0;
do yield i++; while (i < 3);
}
return expectList(f(), [0, 1, 2]);
});
test("try-catch-finally", () {
f() async* {
try { yield 0; } catch (e) { yield 1; } finally { yield 2; }
}
return expectList(f(), [0, 2]);
});
test("try-catch-finally 2", () {
f() async* {
try { yield throw 0; } catch (e) { yield 1; } finally { yield 2; }
}
return expectList(f(), [1, 2]);
});
test("switch-case", () {
f(v) async* {
switch (v) {
case 0:
yield 0;
continue label1;
label1:
case 1:
yield 1;
break;
default:
yield 2;
}
}
return expectList(f(0), [0, 1]).whenComplete(() {
return expectList(f(1), [1]);
}).whenComplete(() {
return expectList(f(2), [2]);
});
});
test("dead-code return", () {
f() async* {
return;
yield 1;
}
return expectList(f(), []);
});
test("dead-code throw", () {
f() async* {
try {
throw 0;
yield 1;
} catch (_) {}
}
return expectList(f(), []);
});
test("dead-code break", () {
f() async* {
while (true) {
break;
yield 1;
}
}
return expectList(f(), []);
});
test("dead-code break 2", () {
f() async* {
label: {
break label;
yield 1;
}
}
return expectList(f(), []);
});
test("dead-code continue", () {
f() async* {
do {
continue;
yield 1;
} while (false);
}
return expectList(f(), []);
});
});
group("yield expressions", () {
test("local variable", () {
f() async* {
var x = 42;
yield x;
}
return expectList(f(), [42]);
});
test("constant variable", () {
f() async* {
const x = 42;
yield x;
}
return expectList(f(), [42]);
});
test("function call", () {
g() => 42;
f() async* {
yield g();
}
return expectList(f(), [42]);
});
test("unary operator", () {
f() async* {
var x = -42;
yield -x;
}
return expectList(f(), [42]);
});
test("binary operator", () {
f() async* {
var x = 21;
yield x + x;
}
return expectList(f(), [42]);
});
test("ternary operator", () {
f() async* {
var x = 21;
yield x == 21 ? x + x : x;
}
return expectList(f(), [42]);
});
test("suffix post-increment", () {
f() async* {
var x = 42;
yield x++;
}
return expectList(f(), [42]);
});
test("suffix pre-increment", () {
f() async* {
var x = 41;
yield ++x;
}
return expectList(f(), [42]);
});
test("assignment", () {
f() async* {
var x = 37;
yield x = 42;
}
return expectList(f(), [42]);
});
test("assignment op", () {
f() async* {
var x = 41;
yield x += 1;
}
return expectList(f(), [42]);
});
test("await", () {
f() async* {
yield await new Future.value(42);
}
return expectList(f(), [42]);
});
test("index operator", () {
f() async* {
var x = [42];
yield x[0];
}
return expectList(f(), [42]);
});
test("function expression block", () {
var o = new Object();
f() async* {
yield () { return o; };
}
return f().first.then((v) {
expect(v(), same(o));
});
});
test("function expression arrow", () {
var o = new Object();
f() async* {
yield () => o;
}
return f().first.then((v) {
expect(v(), same(o));
});
});
test("function expression block async", () {
var o = new Object();
f() async* {
yield () async { return o; };
}
return f().first.then((v) => v()).then((v) {
expect(v, same(o));
});
});
test("function expression arrow async", () {
var o = new Object();
f() async* {
yield () async => o;
}
return f().first.then((v) => v()).then((v) {
expect(v, same(o));
});
});
test("function expression block async*", () {
var o = new Object();
f() async* {
yield () async* { yield o; };
}
return f().first.then((v) => v().first).then((v) {
expect(v, same(o));
});
});
});
group("loops", () {
test("simple yield", () {
f() async* {
for (int i = 0; i < 3; i++) {
yield i;
}
}
return expectList(f(), [0, 1, 2]);
});
test("yield in double loop", () {
f() async* {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
yield i * 2 + j;
}
}
}
return expectList(f(), [0, 1, 2, 3, 4, 5]);
});
test("yield in try body", () {
var list = [];
f() async* {
for (int i = 0; i < 3; i++) {
try {
yield i;
} finally {
list.add("$i");
}
}
}
return expectList(f(), [0, 1, 2]).whenComplete(() {
expect(list, equals(["0", "1", "2"]));
});
});
test("yield in catch", () {
var list = [];
f() async* {
for (int i = 0; i < 3; i++) {
try {
throw i;
} catch (e) {
yield e;
} finally {
list.add("$i");
}
}
}
return expectList(f(), [0, 1, 2]).whenComplete(() {
expect(list, equals(["0", "1", "2"]));
});
});
test("yield in finally", () {
var list = [];
f() async* {
for (int i = 0; i < 3; i++) {
try {
throw i;
} finally {
yield i;
list.add("$i");
continue;
}
}
}
return expectList(f(), [0, 1, 2]).whenComplete(() {
expect(list, equals(["0", "1", "2"]));
});
});
test("keep yielding after cancel", () {
f() async* {
for (int i = 0; i < 10; i++) {
try {
yield i;
} finally {
continue;
}
}
}
return expectList(f().take(3), [0, 1, 2]);
});
});
group("canceling", () {
// Stream.take(n) automatically cancels after seeing the n'th value.
test("cancels at yield", () {
Completer exits = new Completer();
var list = [];
f() async* {
try {
list.add(0);
yield list.add(1);
list.add(2);
} finally {
exits.complete(3);
}
}
// No events must be fired synchronously in response to a listen.
var subscription = f().listen((v) { fail("Received event $v"); },
onDone: () { fail("Received done"); });
// No events must be delivered after a cancel.
subscription.cancel();
return exits.future.then((v) {
expect(v, equals(3));
expect(list, equals([0, 1]));
});
});
test("does cancel eventually", () {
var exits = new Completer();
var list = [];
f() async* {
int i = 0;
try {
while (true) yield i++;
} finally {
list.add("a");
exits.complete(i);
}
}
return expectList(f().take(5), [0, 1, 2, 3, 4])
.then((_) => exits.future)
.then((v) {
expect(v, greaterThan(4));
expect(list, ["a"]);
});
});
group("at index", () {
f() async* {
try {
yield await new Future.microtask(() => 1);
} finally {
try {
yield await new Future.microtask(() => 2);
} finally {
yield await new Future.microtask(() => 3);
}
}
}
test("- all, sanity check", () {
return expectList(f(), [1, 2, 3]);
});
test("after end", () {
return expectList(f().take(4), [1, 2, 3]);
});
test("at end", () {
return expectList(f().take(3), [1, 2, 3]);
});
test("before end", () {
return expectList(f().take(2), [1, 2]);
});
test("early", () {
return expectList(f().take(1), [1]);
});
test("at start", () {
return expectList(f().take(0), []);
});
});
});
group("pausing", () {
test("pauses execution at yield for at least a microtask", () {
var list = [];
f() async* {
list.add(1);
yield 2;
list.add(3);
yield 4;
list.add(5);
}
var done = new Completer();
var sub = f().listen((v) {
if (v == 2) {
expect(list, equals([1]));
} else if (v == 4) {
expect(list, equals([1, 3]));
} else {
fail("Unexpected value $v");
}
}, onDone: () {
expect(list, equals([1, 3, 5]));
done.complete();
});
return done.future;
});
test("pause stops execution at yield", () {
var list = [];
f() async* {
list.add(1);
yield 2;
list.add(3);
yield 4;
list.add(5);
}
var done = new Completer();
var sub;
sub = f().listen((v) {
if (v == 2) {
expect(list, equals([1]));
sub.pause();
new Timer(MS * 300, () {
expect(list.length, lessThan(3));
sub.resume();
});
} else if (v == 4) {
expect(list, equals([1, 3]));
} else {
fail("Unexpected value $v");
}
}, onDone: () {
expect(list, equals([1, 3, 5]));
done.complete();
});
return done.future;
});
test("pause stops execution at yield 2", () {
var list = [];
f() async* {
int i = 0;
while (true) {
yield i;
list.add(i);
i++;
}
}
int expected = 0;
var done = new Completer();
var sub;
sub = f().listen((v) {
expect(v, equals(expected++));
if (v % 5 == 0) {
sub.pause(new Future.delayed(MS * 300));
} else if (v == 17) {
sub.cancel();
done.complete();
}
}, onDone: () {
fail("Unexpected done!");
});
return done.future.whenComplete(() {
expect(list.length == 18 || list.length == 19, isTrue);
});
});
});
group("await for", () {
mkStream(int n) async* {
for (int i = 0; i < n; i++) yield i;
}
test("simple stream", () {
f(s) async {
var r = 0;
await for(var v in s) r += v;
return r;
}
return f(mkStream(5)).then((v) {
expect(v, equals(10));
});
});
test("simple stream, await", () {
f(s) async {
var r = 0;
await for(var v in s) r += await new Future.microtask(() => v);
return r;
}
return f(mkStream(5)).then((v) {
expect(v, equals(10));
});
});
test("simple stream reyield", () {
f(s) async* {
var r = 0;
await for(var v in s) yield r += v;
}
return expectList(f(mkStream(5)), [0, 1, 3, 6, 10]);
});
test("simple stream, await, reyield", () {
f(s) async* {
var r = 0;
await for(var v in s) yield r += await new Future.microtask(() => v);
}
return expectList(f(mkStream(5)), [0, 1, 3, 6, 10]);
});
test("nested", () {
f() async {
var r = 0;
await for (var i in mkStream(5)) {
await for (var j in mkStream(3)) {
r += i * j;
}
}
return r;
}
return f().then((v) {
expect(v, equals((1 + 2 + 3 + 4) * (1 + 2)));
});
});
test("nested, await", () {
f() async {
var r = 0;
await for (var i in mkStream(5)) {
await for (var j in mkStream(3)) {
r += await new Future.microtask(() => i * j);
}
}
return r;
}
return f().then((v) {
expect(v, equals((1 + 2 + 3 + 4) * (1 + 2)));
});
});
test("nested, await * 2", () {
f() async {
var r = 0;
await for (var i in mkStream(5)) {
var ai = await new Future.microtask(() => i);
await for (var j in mkStream(3)) {
r += await new Future.microtask(() => ai * j);
}
}
return r;
}
return f().then((v) {
expect(v, equals((1 + 2 + 3 + 4) * (1 + 2)));
});
});
});
}
// Obscuring identity function.
id(x) {
try {
if (x != null) throw x;
} catch (e) {
return e;
}
return null;
}
expectList(stream, list) {
return stream.toList().then((v) {
expect(v, equals(list));
});
}
const MS = const Duration(milliseconds: 1);
var getErrors = new StreamTransformer.fromHandlers(
handleData:(data, sink) { fail("Unexpected value"); },
handleError: (e, s, sink) { sink.add(e); },
handleDone: (sink) { sink.close(); });
class NotAStream {
listen(oData, {onError, onDone, cancelOnError}) {
fail("Not implementing Stream.");
}
}