mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 03:56:57 +00:00
a957c4351a
Change-Id: Icadb9edfe2ef40aff3808295685f961897d63c9a Reviewed-on: https://dart-review.googlesource.com/c/86181 Reviewed-by: Lasse R.H. Nielsen <lrn@google.com> Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
377 lines
12 KiB
Dart
377 lines
12 KiB
Dart
// Copyright (c) 2018, 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.
|
|
|
|
// Test that `await for` and `async*` interact correctly.
|
|
|
|
// An `await for` must pause its subscription immediately
|
|
// if the `await for` body does anything asynchronous
|
|
// (any `await`, `await for`, or pausing at a `yield`/`yield*`)
|
|
// A pause happening synchronously in an event delivery
|
|
// must pause the `sync*` method at the `yield` sending the event.
|
|
// A break happening synchronously in an event delivery,
|
|
// or while paused at a `yield`, must exit at that `yield`.
|
|
|
|
import "dart:async";
|
|
import "package:expect/expect.dart";
|
|
import "package:async_helper/async_helper.dart";
|
|
|
|
Stream<int> stream(List<String> log) async* {
|
|
log.add("^");
|
|
try {
|
|
log.add("?1");
|
|
yield 1;
|
|
log.add("?2");
|
|
yield 2;
|
|
log.add("?3");
|
|
yield 3;
|
|
} finally {
|
|
log.add(r"$");
|
|
}
|
|
}
|
|
|
|
Stream<int> consume(List<String> log,
|
|
{int breakAt = -1,
|
|
int yieldAt = -1,
|
|
int yieldStarAt = -1,
|
|
int pauseAt = -1}) async* {
|
|
// Create stream.
|
|
var s = stream(log);
|
|
log.add("(");
|
|
// The "consume loop".
|
|
await for (var event in s) {
|
|
// Should be acting synchronously wrt. the delivery of the event.
|
|
// The source stream should be at the yield now.
|
|
log.add("!$event");
|
|
if (event == pauseAt) {
|
|
log.add("p$event[");
|
|
// Async operation causes subscription to pause.
|
|
// Nothing should happen in the source stream
|
|
// until the end of the loop body where the subscription is resumed.
|
|
await Future.delayed(Duration(microseconds: 1));
|
|
log.add("]");
|
|
}
|
|
if (event == yieldAt) {
|
|
log.add("y$event[");
|
|
// Yield may cause subscription to pause or cancel.
|
|
// This loop should stay at the yield until the event has been delieverd.
|
|
// If the receiver pauses or cancels, we delay or break the loop here.
|
|
yield event;
|
|
log.add("]");
|
|
}
|
|
if (event == yieldStarAt) {
|
|
log.add("Y$event[");
|
|
// Yield* will always cause the subscription for this loop to pause.
|
|
// If the listener pauses, this stream is paused. If the listener cancels,
|
|
// this stream is cancelled, and the yield* acts like return, cancelling
|
|
// the loop subscription and waiting for the cancel future.
|
|
yield* Stream<int>.fromIterable([event]);
|
|
log.add("]");
|
|
}
|
|
if (event == breakAt) {
|
|
log.add("b$event");
|
|
// Breaks the loop. This cancels the loop subscription and waits for the
|
|
// cancel future.
|
|
break;
|
|
}
|
|
}
|
|
// Done event from stream or cancel future has completed.
|
|
log.add(")");
|
|
}
|
|
|
|
main() async {
|
|
asyncStart();
|
|
|
|
// Just run the loop over the stream. The consume stream emits no events.
|
|
{
|
|
var log = <String>[];
|
|
await for (var _ in consume(log)) {
|
|
throw "unreachable";
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 1));
|
|
var trace = log.join("");
|
|
Expects.equals(r"(^?1!1?2!2?3!3$)", trace, "straight through");
|
|
}
|
|
|
|
// Pause at 1, then resume.
|
|
// Consume loop forces a pause when it receives the 1 event.
|
|
// Nothing should happen until that pause is resumed.
|
|
{
|
|
var log = <String>[];
|
|
await for (var _ in consume(log, pauseAt: 1)) {
|
|
throw "unreachable";
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "pause at 1";
|
|
if (trace.contains("p1[?2")) {
|
|
message += " (did not pause in time)";
|
|
}
|
|
Expects.equals(r"(^?1!1p1[]?2!2?3!3$)", trace, message);
|
|
}
|
|
|
|
// Break at 1.
|
|
// Consume loop breaks after receiving the 1 event.
|
|
// The consume stream emits no events.
|
|
{
|
|
var log = <String>[];
|
|
await for (var _ in consume(log, breakAt: 1)) {
|
|
throw "unreachable";
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "break at 1";
|
|
if (trace.contains("b1?2")) {
|
|
message += " (did not cancel in time)";
|
|
}
|
|
Expects.equals(r"(^?1!1b1$)", trace, message);
|
|
}
|
|
|
|
// Pause then break at 1.
|
|
// Consume loop pauses after receiving the 1 event,
|
|
// then breaks before resuming. It should still be at the yield.
|
|
// The consume stream emits no events.
|
|
{
|
|
var log = <String>[];
|
|
await for (var _ in consume(log, pauseAt: 1, breakAt: 1)) {
|
|
throw "unreachable";
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "pause then break at 1";
|
|
if (trace.contains("p1[?2")) {
|
|
message += " (did not pause in time)";
|
|
}
|
|
if (trace.contains("b1?2")) {
|
|
message += " (did not cancel in time)";
|
|
}
|
|
Expects.equals(r"(^?1!1p1[]b1$)", trace, message);
|
|
}
|
|
|
|
// Yield at 1.
|
|
// The consume loop re-emits the 1 event.
|
|
// The test loop should receive that event while the consume loop is still
|
|
// at the yield statement.
|
|
// The consume loop may or may not pause, it should make no difference.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldAt: 1)) {
|
|
log.add("e$s");
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield at 1";
|
|
if (trace.contains("y1[?2")) {
|
|
message += " (did not wait for delivery)";
|
|
}
|
|
Expects.equals(r"(^?1!1y1[e1]?2!2?3!3$)", trace, message);
|
|
}
|
|
|
|
// Yield at 1, then pause at yield.
|
|
// The consume loop re-emits the 1 event.
|
|
// The test loop should receive that event while the consume loop is still
|
|
// at the yield statement.
|
|
// The test loop then pauses.
|
|
// Nothing should happen in either the original yield
|
|
// or the consume-function yield until the test loop ends.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldAt: 1)) {
|
|
log.add("e$s<");
|
|
// Force pause at yield.
|
|
await Future.delayed(Duration(milliseconds: 1));
|
|
log.add(">");
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield at 1, pause at yield";
|
|
if (trace.contains("y1[?2")) {
|
|
message += " (did not wait for delivery)";
|
|
}
|
|
if (trace.contains("e1<?2")) {
|
|
message += " (did not pause in time)";
|
|
}
|
|
Expects.equals(r"(^?1!1y1[e1<>]?2!2?3!3$)", trace, message);
|
|
}
|
|
|
|
// Yield at 1, then break at yield.
|
|
// The consume loop re-emits the 1 event.
|
|
// The test loop should receive that event while the consume loop is still
|
|
// at the yield statement.
|
|
// The test loop then breaks. That makes the consume loop yield return,
|
|
// breaking the consume loop, which makes the source yield return.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldAt: 1)) {
|
|
log.add("e${s}B$s");
|
|
break; // Force break at yield*.
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield at 1, break at yield";
|
|
if (trace.contains("y1[?2")) {
|
|
message += " (did not wait for delivery)";
|
|
}
|
|
if (trace.contains("B1?2")) {
|
|
message += " (did not break in time)";
|
|
}
|
|
Expects.equals(r"(^?1!1y1[e1B1$)", trace, message);
|
|
}
|
|
|
|
// Yield* at 1.
|
|
// The consume loop re-emits a stream containing the 1 event.
|
|
// The test loop should receive that event before the consume loop
|
|
// continues from the `yield*`, which again happens before the source
|
|
// stream continues from its `yield`.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldStarAt: 1)) {
|
|
log.add("e$s");
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield* at 1";
|
|
if (trace.contains("Y1[?2")) {
|
|
message += " (did not wait for delivery)";
|
|
}
|
|
Expects.equals(r"(^?1!1Y1[e1]?2!2?3!3$)", trace, message);
|
|
}
|
|
|
|
// Yield* at 1, pause at yield.
|
|
// The consume loop re-emits a stream containing the 1 event.
|
|
// The test loop should receive that event before the consume loop
|
|
// continues from the `yield*`. The test loop then force a pause.
|
|
// Nothing further should happen during that pause.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldStarAt: 1)) {
|
|
log.add("e$s<");
|
|
await Future.delayed(Duration(milliseconds: 1)); // force pause.
|
|
log.add(">");
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield* then pause at 1";
|
|
if (trace.contains("Y1[?2")) {
|
|
message += " (did not wait for delivery)";
|
|
}
|
|
if (trace.contains("e1<?2")) {
|
|
message += " (did not pause in time)";
|
|
}
|
|
Expects.equals(r"(^?1!1Y1[e1<>]?2!2?3!3$)", trace, message);
|
|
}
|
|
|
|
// Yield* at 1, then break at 1.
|
|
// The consume loop re-emits a stream containing the 1 event.
|
|
// The test loop should receive that event before the consume loop
|
|
// continues from the `yield*`.
|
|
// When the consume loop continues, it breaks,
|
|
// forcing the waiting source yield to return.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldStarAt: 1, breakAt: 1)) {
|
|
log.add("e$s");
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield* then pause at 1";
|
|
if (trace.contains("Y1[?2")) {
|
|
message += " (did not wait for delivery)";
|
|
}
|
|
Expects.equals(r"(^?1!1Y1[e1]b1$)", trace, message);
|
|
}
|
|
|
|
// Yield* at 1, pause at yield, then break at 1.
|
|
// The consume loop re-emits a stream containing the 1 event.
|
|
// The test loop should receive that event before the consume loop
|
|
// continues from the `yield*`. After the `yield*`, the consume loop breaks.
|
|
// This forces the waiting source yield to return.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldStarAt: 1, breakAt: 1)) {
|
|
log.add("e$s<");
|
|
await Future.delayed(Duration(milliseconds: 1)); // force pause.
|
|
log.add(">");
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield* then pause at 1";
|
|
Expects.equals(r"(^?1!1Y1[e1<>]b1$)", trace, message);
|
|
}
|
|
|
|
// Yield* at 1, break at yield.
|
|
// The consume loop re-emits a stream containing the 1 event.
|
|
// The test loop should receive that event before the consume loop
|
|
// continues from the `yield*`. The test loop then breaks,
|
|
// forcing the two waiting yields to return.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldStarAt: 1)) {
|
|
log.add("e${s}B$s");
|
|
break;
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield* then break at 1";
|
|
if (trace.contains("Y1[?2")) {
|
|
message += " (did not deliver event in time)";
|
|
}
|
|
if (trace.contains("e1?2")) {
|
|
message += " (did not cancel in time)";
|
|
}
|
|
Expects.equals(r"(^?1!1Y1[e1B1$)", trace, message);
|
|
}
|
|
|
|
// Yield* at 1, pause at yield, then break at yield.
|
|
// The consume loop re-emits a stream containing the 1 event.
|
|
// The test loop should receive that event before the consume loop
|
|
// continues from the `yield*`. The test loop then forces a pause,
|
|
// and then breaks before that pause is resumed.
|
|
// This forces the two waiting yields to return.
|
|
{
|
|
var log = <String>[];
|
|
await for (var s in consume(log, yieldStarAt: 1)) {
|
|
log.add("e$s<");
|
|
await Future.delayed(Duration(milliseconds: 1)); // force pause.
|
|
log.add(">B$s");
|
|
break; // And break.
|
|
}
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
var trace = log.join("");
|
|
String message = "yield* then pause then break at 1";
|
|
Expects.equals(r"(^?1!1Y1[e1<>B1$)", trace, message);
|
|
}
|
|
|
|
Expects.summarize();
|
|
asyncEnd();
|
|
}
|
|
|
|
class Expects {
|
|
static var _errors = [];
|
|
static int _tests = 0;
|
|
static void summarize() {
|
|
if (_errors.isNotEmpty) {
|
|
var buffer = StringBuffer();
|
|
for (var es in _errors) {
|
|
buffer.writeln("FAILURE:");
|
|
buffer.writeln(es[0]); // error
|
|
buffer.writeln(es[1]); // stack trace
|
|
}
|
|
;
|
|
buffer.writeln("Expectations failed: ${_errors.length}"
|
|
", succeeded: ${_tests - _errors.length}");
|
|
throw ExpectException(buffer.toString());
|
|
}
|
|
}
|
|
|
|
static void equals(o1, o2, String message) {
|
|
_tests++;
|
|
try {
|
|
Expect.equals(o1, o2, message);
|
|
} on ExpectException catch (e) {
|
|
var stack = StackTrace.current;
|
|
_errors.add([e, stack]);
|
|
}
|
|
}
|
|
}
|