Files
sdk/tests/lib/async/future_test.dart
T
Lasse R.H. Nielsen 478602ad79 Fix errors in Future implementation around Future<Future<X>>.
Previously, when completing a future with another future,
we always chained to the other future. This effectively awaits
the latter future and then completes the former with its result.
This is incorrect behavior if the former future is, say,
a `Future<Future<int>>` and the latter is a `Future<int>`.
In that case we *must not* await the latter future
because we can't complete the former future with an `int`.
We should just complete the future directly with the latter future
as a value.

We now check first whether to chain a `Future<T>` to another
future, and only does so if the latter future is a `Future<T>`
(or it's not a `T`, which shouldn't happen,
but currently does in some places).

Add test for behavior.

Change-Id: I57e27111c2fc7b7792dcf4ae9b7c1d5d504d0c0f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/53602
Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
Reviewed-by: Nate Bosch <nbosch@google.com>
Reviewed-by: Erik Ernst <eernst@google.com>
2020-12-14 16:34:28 +00:00

1292 lines
31 KiB
Dart

// Copyright (c) 2012, 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 future_test;
import 'package:async_helper/async_helper.dart';
import "package:expect/expect.dart";
import 'dart:async';
const Duration MS = const Duration(milliseconds: 1);
void testValue() {
final future = new Future<String>.value("42");
asyncStart();
future.then((x) {
Expect.equals("42", x);
asyncEnd();
});
}
void testSync() {
compare(func) {
// Compare the results of the following two futures.
Future f1 = new Future.sync(func);
Future f2 = new Future.value().then((_) => func());
f2.catchError((_) {}); // I'll get the error later.
f1.then((v1) {
f2.then((v2) {
Expect.equals(v1, v2);
});
}, onError: (e1) {
f2.then((_) {
Expect.fail("Expected error");
}, onError: (e2) {
Expect.equals(e1, e2);
});
});
}
Future val = new Future.value(42);
Future err1 = new Future.error("Error")..catchError((_) {});
try {
throw [];
} catch (e, st) {
Future err2 = new Future.error(e, st)..catchError((_) {});
}
compare(() => 42);
compare(() => val);
compare(() {
throw "Flif";
});
compare(() => err1);
bool hasExecuted = false;
compare(() {
hasExecuted = true;
return 499;
});
Expect.isTrue(hasExecuted);
}
void testNeverComplete() {
final completer = new Completer<int>();
final future = completer.future;
future.then((v) => Expect.fail("Value not expected"));
future.catchError((e) => Expect.fail("Value not expected"));
}
void testComplete() {
final completer = new Completer<int>();
final future = completer.future;
Expect.isFalse(completer.isCompleted);
completer.complete(3);
Expect.isTrue(completer.isCompleted);
future.then((v) => Expect.equals(3, v));
}
// Tests for [then]
void testCompleteWithSuccessHandlerBeforeComplete() {
final completer = new Completer<int>();
final future = completer.future;
int? after;
asyncStart();
future.then((int v) {
after = v;
}).then((_) {
Expect.equals(3, after);
asyncEnd();
});
completer.complete(3);
Expect.isNull(after);
}
void testCompleteWithSuccessHandlerAfterComplete() {
final completer = new Completer<int>();
final future = completer.future;
int? after;
completer.complete(3);
Expect.isNull(after);
asyncStart();
future.then((int v) {
after = v;
}).then((_) {
Expect.equals(3, after);
asyncEnd();
});
}
void testCompleteManySuccessHandlers() {
final completer = new Completer<int>();
final future = completer.future;
late int before;
late int after1;
late int after2;
var futures = <Future<int>>[];
futures.add(future.then((int v) {
before = v;
return v;
}));
completer.complete(3);
futures.add(future.then((int v) {
after1 = v;
return v;
}));
futures.add(future.then((int v) {
after2 = v;
return v;
}));
asyncStart();
Future.wait(futures).then((_) {
Expect.equals(3, before);
Expect.equals(3, after1);
Expect.equals(3, after2);
asyncEnd();
});
// Regression test for fix to issue:
// https://github.com/dart-lang/sdk/issues/43445
asyncStart();
Future.wait<int>(<Future<int>>[]).then((list) {
Expect.equals(0, list.length);
Expect.type<List<int>>(list);
Expect.notType<List<Null>>(list);
Expect.notType<List<Never>>(list);
asyncEnd();
});
}
// Tests for [catchError]
void testException() {
final completer = new Completer<int?>();
final future = completer.future;
final ex = new Exception();
asyncStart();
future.then((v) {
throw "Value not expected";
return null;
}).catchError((error) {
Expect.equals(error, ex);
asyncEnd();
}, test: (e) => e == ex);
completer.completeError(ex);
}
void testExceptionHandler() {
final completer = new Completer<int?>();
final future = completer.future;
final ex = new Exception();
var ex2;
var done = future.catchError((error) {
ex2 = error;
});
Expect.isFalse(completer.isCompleted);
completer.completeError(ex);
Expect.isTrue(completer.isCompleted);
asyncStart();
done.then((_) {
Expect.equals(ex, ex2);
asyncEnd();
});
}
void testExceptionHandlerReturnsTrue() {
final completer = new Completer<int?>();
final future = completer.future;
final ex = new Exception();
bool reached = false;
future.catchError((e) {});
future.catchError((e) {
reached = true;
}, test: (e) => false).catchError((e) {});
Expect.isFalse(completer.isCompleted);
completer.completeError(ex);
Expect.isTrue(completer.isCompleted);
Expect.isFalse(reached);
}
void testExceptionHandlerReturnsTrue2() {
final completer = new Completer<int?>();
final future = completer.future;
final ex = new Exception();
bool reached = false;
var done = future.catchError((e) {}, test: (e) => false).catchError((e) {
reached = true;
});
completer.completeError(ex);
asyncStart();
done.then((_) {
Expect.isTrue(reached);
asyncEnd();
});
}
void testExceptionHandlerReturnsFalse() {
final completer = new Completer<int?>();
final future = completer.future;
final ex = new Exception();
bool reached = false;
future.catchError((e) {});
future.catchError((e) {
reached = true;
}, test: (e) => false).catchError((e) {});
completer.completeError(ex);
Expect.isFalse(reached);
}
void testFutureAsStreamCompleteAfter() {
var completer = new Completer();
bool gotValue = false;
asyncStart();
completer.future.asStream().listen((data) {
Expect.isFalse(gotValue);
gotValue = true;
Expect.equals("value", data);
}, onDone: () {
Expect.isTrue(gotValue);
asyncEnd();
});
completer.complete("value");
}
void testFutureAsStreamCompleteBefore() {
var completer = new Completer();
bool gotValue = false;
asyncStart();
completer.complete("value");
completer.future.asStream().listen((data) {
Expect.isFalse(gotValue);
gotValue = true;
Expect.equals("value", data);
}, onDone: () {
Expect.isTrue(gotValue);
asyncEnd();
});
}
void testFutureAsStreamCompleteImmediate() {
bool gotValue = false;
asyncStart();
new Future.value("value").asStream().listen((data) {
Expect.isFalse(gotValue);
gotValue = true;
Expect.equals("value", data);
}, onDone: () {
Expect.isTrue(gotValue);
asyncEnd();
});
}
void testFutureAsStreamCompleteErrorAfter() {
var completer = new Completer();
bool gotError = false;
asyncStart();
completer.future.asStream().listen((data) {
Expect.fail("Unexpected data");
}, onError: (error) {
Expect.isFalse(gotError);
gotError = true;
Expect.equals("error", error);
}, onDone: () {
Expect.isTrue(gotError);
asyncEnd();
});
completer.completeError("error");
}
void testFutureAsStreamWrapper() {
var completer = new Completer();
bool gotValue = false;
asyncStart();
completer.complete("value");
completer.future
.catchError((_) {
throw "not possible";
}) // Returns a future wrapper.
.asStream()
.listen((data) {
Expect.isFalse(gotValue);
gotValue = true;
Expect.equals("value", data);
}, onDone: () {
Expect.isTrue(gotValue);
asyncEnd();
});
}
void testFutureWhenCompleteValue() {
asyncStart();
int counter = 2;
countDown() {
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
Future future = completer.future;
Future later = future.whenComplete(countDown);
later.then((v) {
Expect.equals(42, v);
countDown();
});
completer.complete(42);
}
void testFutureWhenCompleteError() {
asyncStart();
int counter = 2;
countDown() {
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
Future future = completer.future;
Future later = future.whenComplete(countDown);
later.catchError((error) {
Expect.equals("error", error);
countDown();
});
completer.completeError("error");
}
void testFutureWhenCompleteValueNewError() {
asyncStart();
int counter = 2;
countDown() {
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
Future future = completer.future;
Future later = future.whenComplete(() {
countDown();
throw "new error";
});
later.catchError((error) {
Expect.equals("new error", error);
countDown();
});
completer.complete(42);
}
void testFutureWhenCompleteErrorNewError() {
asyncStart();
int counter = 2;
countDown() {
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
Future future = completer.future;
Future later = future.whenComplete(() {
countDown();
throw "new error";
});
later.catchError((error) {
Expect.equals("new error", error);
countDown();
});
completer.completeError("error");
}
void testFutureWhenCompletePreValue() {
asyncStart();
int counter = 2;
countDown() {
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
Future future = completer.future;
completer.complete(42);
Timer.run(() {
Future later = future.whenComplete(countDown);
later.then((v) {
Expect.equals(42, v);
countDown();
});
});
}
void testFutureWhenValueFutureValue() {
asyncStart();
int counter = 3;
countDown(int expect) {
Expect.equals(expect, counter);
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
completer.future.whenComplete(() {
countDown(3);
var completer2 = new Completer();
new Timer(MS * 10, () {
countDown(2);
completer2.complete(37);
});
return completer2.future;
}).then((v) {
Expect.equals(42, v);
countDown(1);
});
completer.complete(42);
}
void testFutureWhenValueFutureError() {
asyncStart();
int counter = 3;
countDown(int expect) {
Expect.equals(expect, counter);
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
completer.future.whenComplete(() {
countDown(3);
var completer2 = new Completer();
new Timer(MS * 10, () {
countDown(2);
completer2.completeError("Fail");
});
return completer2.future;
}).then((v) {
Expect.fail("should fail async");
}, onError: (error) {
Expect.equals("Fail", error);
countDown(1);
});
completer.complete(42);
}
void testFutureWhenErrorFutureValue() {
asyncStart();
int counter = 3;
countDown(int expect) {
Expect.equals(expect, counter);
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
completer.future.whenComplete(() {
countDown(3);
var completer2 = new Completer();
new Timer(MS * 10, () {
countDown(2);
completer2.complete(37);
});
return completer2.future;
}).then((v) {
Expect.fail("should fail async");
}, onError: (error) {
Expect.equals("Error", error);
countDown(1);
});
completer.completeError("Error");
}
void testFutureWhenErrorFutureError() {
asyncStart();
int counter = 3;
countDown(int expect) {
Expect.equals(expect, counter);
if (--counter == 0) asyncEnd();
}
var completer = new Completer();
completer.future.whenComplete(() {
countDown(3);
var completer2 = new Completer();
new Timer(MS * 10, () {
countDown(2);
completer2.completeError("Fail");
});
return completer2.future;
}).then((v) {
Expect.fail("should fail async");
}, onError: (error) {
Expect.equals("Fail", error);
countDown(1);
});
completer.completeError("Error");
}
void testFutureThenThrowsAsync() {
final completer = new Completer<int?>();
final future = completer.future;
int error = 42;
asyncStart();
future.then((v) {
throw error;
return null;
}).catchError((e) {
Expect.identical(error, e);
asyncEnd();
});
completer.complete(0);
}
void testFutureCatchThrowsAsync() {
final completer = new Completer<int?>();
final future = completer.future;
int error = 42;
asyncStart();
future.catchError((e) {
throw error;
}).catchError((e) {
Expect.identical(error, e);
asyncEnd();
});
completer.completeError(0);
}
void testFutureCatchRethrowsAsync() {
final completer = new Completer<int?>();
final future = completer.future;
var error;
asyncStart();
future.catchError((e) {
error = e;
throw e;
}).catchError((e) {
Expect.identical(error, e);
asyncEnd();
});
completer.completeError(0);
}
void testFutureWhenThrowsAsync() {
final completer = new Completer<int?>();
final future = completer.future;
var error = 42;
asyncStart();
future.whenComplete(() {
throw error;
}).catchError((e) {
Expect.identical(error, e);
asyncEnd();
});
completer.complete(0);
}
void testCompleteWithError() {
final completer = new Completer<int?>();
final future = completer.future;
var error = 42;
asyncStart();
future.catchError((e) {
Expect.identical(error, e);
asyncEnd();
});
completer.completeError(error);
}
void testCompleteWithFutureSuccess() {
asyncStart();
final completer = new Completer<int>();
final completer2 = new Completer<int>();
completer.complete(completer2.future);
completer.future.then((v) {
Expect.equals(42, v);
asyncEnd();
});
completer2.complete(42);
}
void testCompleteWithFutureSuccess2() {
asyncStart();
final completer = new Completer<int>();
final result = new Future<int>.value(42);
completer.complete(result);
completer.future.then((v) {
Expect.equals(42, v);
asyncEnd();
});
}
void testCompleteWithFutureError() {
asyncStart();
final completer = new Completer<int>();
final completer2 = new Completer<int>();
completer.complete(completer2.future);
completer.future.then((v) {
Expect.fail("Should not happen");
asyncEnd();
}, onError: (e) {
Expect.equals("ERROR-tcwfe", e);
asyncEnd();
});
completer2.completeError("ERROR-tcwfe");
}
void testCompleteWithFutureError2() {
asyncStart();
final completer = new Completer<int>();
var result = new Future<int>.error("ERROR-tcwfe2");
completer.complete(result);
completer.future.then((v) {
Expect.fail("Should not happen");
asyncEnd();
}, onError: (e) {
Expect.equals("ERROR-tcwfe2", e);
asyncEnd();
});
}
void testCompleteErrorWithFuture() {
asyncStart();
final completer = new Completer<int>();
completer.completeError(new Future.value(42));
completer.future.then((_) {
Expect.fail("Shouldn't happen");
}, onError: (e, s) {
Future f = e;
f.then((v) {
Expect.equals(42, v);
asyncEnd();
});
});
}
void testCompleteWithCustomFutureSuccess() {
asyncStart();
final completer = new Completer<int>();
final completer2 = new Completer<int>();
completer.complete(new CustomFuture<int>(completer2.future));
completer.future.then((v) {
Expect.equals(42, v);
asyncEnd();
});
completer2.complete(42);
}
void testCompleteWithCustomFutureError() {
asyncStart();
final completer = new Completer<int>();
final completer2 = new Completer<int>();
completer.complete(new CustomFuture<int>(completer2.future));
completer.future.then((v) {
Expect.fail("Should not happen");
asyncEnd();
}, onError: (e) {
Expect.equals("ERROR-tcwcfe", e);
asyncEnd();
});
completer2.completeError("ERROR-tcwcfe");
}
void testCompleteErrorWithCustomFuture() {
asyncStart();
final completer = new Completer<int>();
var future = new CustomFuture<int>(new Future.value(42));
completer.completeError(future);
completer.future.then((_) {
Expect.fail("Shouldn't happen");
}, onError: (e) {
Future f = e;
f.then((v) {
Expect.equals(42, v);
asyncEnd();
});
});
}
void testChainedFutureValue() {
final completer = new Completer();
final future = completer.future;
asyncStart();
future.then((v) => new Future.value(v * 2)).then((v) {
Expect.equals(42, v);
asyncEnd();
});
completer.complete(21);
}
void testChainedFutureValueDelay() {
final completer = new Completer();
final future = completer.future;
asyncStart();
future
.then((v) =>
new Future.delayed(const Duration(milliseconds: 10), () => v * 2))
.then((v) {
Expect.equals(42, v);
asyncEnd();
});
completer.complete(21);
}
void testChainedFutureValue2Delay() {
asyncStart();
new Future.delayed(const Duration(milliseconds: 10)).then((v) {
Expect.isNull(v);
asyncEnd();
});
}
void testChainedFutureError() {
final completer = new Completer();
final future = completer.future;
asyncStart();
future.then((v) => new Future.error("Fehler")).then((v) {
Expect.fail("unreachable!");
}, onError: (error) {
Expect.equals("Fehler", error);
asyncEnd();
});
completer.complete(21);
}
void testSyncFuture_i13368() {
asyncStart();
final future = new Future<int>.sync(() {
return new Future<int>.value(42);
});
future.then((int val) {
Expect.equals(val, 42);
asyncEnd();
});
}
void testWaitCleanUp() {
asyncStart();
// Creates three futures with different completion times, and where some fail.
// The `mask` specifies which futures fail (values 1-7),
// and `permute` defines the order of completion. values 0-5.
void doTest(int mask, int permute) {
asyncStart();
String stringId = "waitCleanup-$mask-$permute";
List<Future?> futures = <Future?>[null, null, null];
List cleanup = [null, null, null];
int permuteTmp = permute;
for (int i = 0; i < 3; i++) {
bool throws = (mask & (1 << i)) != 0;
var future = new Future.delayed(new Duration(milliseconds: 100 * (i + 1)),
() => (throws ? throw "Error $i($mask-$permute)" : i));
int mod = 3 - i;
int position = permuteTmp % mod;
permuteTmp = permuteTmp ~/ mod;
while (futures[position] != null) position++;
futures[position] = future;
cleanup[i] = throws;
}
void cleanUp(index) {
Expect.isFalse(cleanup[index]);
cleanup[index] = true;
}
Future.wait(futures.map((future) => future!), cleanUp: cleanUp).then((_) {
Expect.fail("No error: $stringId");
}, onError: (e, s) {
Expect.listEquals([true, true, true], cleanup);
asyncEnd();
});
}
for (int i = 1; i < 8; i++) {
for (int j = 0; j < 6; j++) {
doTest(i, j);
}
}
asyncEnd();
}
void testWaitCleanUpEager() {
asyncStart();
// Creates three futures with different completion times, and where some fail.
// The `mask` specifies which futures fail (values 1-7),
// and `permute` defines the order of completion. values 0-5.
void doTest(int mask, int permute) {
asyncStart();
asyncStart();
bool done = false;
String stringId = "waitCleanup-$mask-$permute";
List<Future?> futures = <Future?>[null, null, null];
List cleanup = [null, null, null];
int permuteTmp = permute;
for (int i = 0; i < 3; i++) {
bool throws = (mask & (1 << i)) != 0;
var future = new Future.delayed(new Duration(milliseconds: 100 * (i + 1)),
() => (throws ? throw "Error $i($mask-$permute)" : i));
int mod = 3 - i;
int position = permuteTmp % mod;
permuteTmp = permuteTmp ~/ mod;
while (futures[position] != null) position++;
futures[position] = future;
cleanup[i] = throws;
}
void checkDone() {
if (done) return;
if (cleanup.every((v) => v)) {
done = true;
asyncEnd();
}
}
void cleanUp(index) {
Expect.isFalse(cleanup[index]);
cleanup[index] = true;
// Cleanup might happen before and after the wait().then() callback.
checkDone();
}
Future.wait(futures.map((future) => future!),
eagerError: true, cleanUp: cleanUp)
.then((_) {
Expect.fail("No error: $stringId");
}, onError: (e, s) {
asyncEnd();
checkDone();
});
}
for (int i = 1; i < 8; i++) {
for (int j = 0; j < 6; j++) {
doTest(i, j);
}
}
asyncEnd();
}
void testWaitCleanUpError() {
var cms = const Duration(milliseconds: 100);
var cleanups = new List.filled(3, false);
var uncaughts = new List.filled(3, false);
asyncStart();
asyncStart();
asyncStart();
runZonedGuarded(() {
Future<List<int>?>.value(Future.wait([
new Future.delayed(cms, () => 0),
new Future.delayed(cms * 2, () => throw 1),
new Future.delayed(cms * 3, () => 2)
], cleanUp: (index) {
Expect.isTrue(index == 0 || index == 2, "$index");
Expect.isFalse(cleanups[index]);
cleanups[index] = true;
throw index;
})).catchError((e) {
Expect.equals(e, 1);
asyncEnd();
});
}, (e, s) {
int index = e as int;
Expect.isTrue(index == 0 || index == 2, "$index");
Expect.isFalse(uncaughts[index]);
uncaughts[index] = true;
asyncEnd();
});
}
void testWaitSyncError() {
var cms = const Duration(milliseconds: 100);
var cleanups = new List.filled(3, false);
asyncStart();
asyncStart();
runZoned(() {
Future.wait(
new Iterable.generate(5, (i) {
if (i != 3) return new Future.delayed(cms * (i + 1), () => i);
throw "throwing synchronously in iterable";
}), cleanUp: (dynamic index) {
Expect.isFalse(cleanups[index]);
cleanups[index] = true;
if (cleanups.every((x) => x)) asyncEnd();
});
}, onError: (e, s) {
asyncEnd();
});
}
// Creates an Iterable that throws when iterated. Used to validate how
// Future.wait() handles a synchronous error occurring inside its own code.
Iterable<Future> badIterable() sync* {
throw "!";
}
void testWaitSyncError2() {
asyncStart();
Future<List?>.value(Future.wait(badIterable())).catchError((e, st) {
// Makes sure that the `catchError` is invoked.
// Regression test: an earlier version of `Future.wait` would propagate
// the error too soon for the code to install an error handler.
// `testWaitSyncError` didn't show this problem, because the `runZoned`
// was already installed.
asyncEnd();
});
}
// Future.wait transforms synchronous errors into asynchronous ones.
// This function tests that zones can intercept them.
void testWaitSyncError3() {
var caughtError;
var count = 0;
AsyncError? errorCallback(Zone self, ZoneDelegate parent, Zone zone,
Object error, StackTrace? stackTrace) {
Expect.equals(0, count);
count++;
caughtError = error;
return parent.errorCallback(zone, error, stackTrace);
}
asyncStart();
runZoned(() {
Future<List?>.value(Future.wait(badIterable())).catchError((e, st) {
Expect.identical(e, caughtError);
Expect.equals(1, count);
asyncEnd();
});
}, zoneSpecification: new ZoneSpecification(errorCallback: errorCallback));
}
void testBadFuture() {
var bad = new BadFuture();
// Completing with bad future (then call throws) puts error in result.
asyncStart();
Completer completer = new Completer();
completer.complete(bad);
completer.future.then((_) {
Expect.fail("unreachable");
}, onError: (e, s) {
Expect.isTrue(completer.isCompleted);
asyncEnd();
});
asyncStart();
var f = new Future.value().then((_) => bad);
f.then((_) {
Expect.fail("unreachable");
}, onError: (e, s) {
asyncEnd();
});
}
void testTypes() {
// Test that future is a Future<int> and not something less precise.
testType(name, future, [int depth = 2]) {
var desc = "$name${".whenComplete" * (2 - depth)}";
Expect.isTrue(future is Future<int>, "$desc is Future<int>");
Expect.isFalse(future is Future<String>, "$desc is! Future<String>");
var stream = future.asStream();
Expect.isTrue(stream is Stream<int>, "$desc.asStream() is Stream<int>");
Expect.isFalse(
stream is Stream<String>, "$desc.asStream() is! Stream<String>");
if (depth > 0) {
testType(name, future.whenComplete(() {}), depth - 1);
}
}
for (var value in [42]) {
testType("Future($value)", new Future<int>(() => value));
testType("Future.delayed($value)",
new Future<int>.delayed(Duration.zero, () => value));
testType(
"Future.microtask($value)", new Future<int>.microtask(() => value));
testType("Future.sync($value)", new Future<int>.sync(() => value));
testType("Future.sync(future($value))",
new Future<int>.sync(() => new Future<int>.value(value)));
testType("Future.value($value)", new Future<int>.value(value));
testType(
"Future.error", new Future<int>.error("ERR")..catchError((_) => value));
}
testType("Completer.future", new Completer<int>().future);
}
void testAnyValue() {
asyncStart();
var cs = new List.generate(3, (_) => new Completer());
var result = Future.any(cs.map((x) => x.future));
result.then((v) {
Expect.equals(42, v);
asyncEnd();
}, onError: (e, s) {
Expect.fail("Unexpected error: $e");
});
cs[1].complete(42);
cs[2].complete(10);
cs[0].complete(20);
}
void testAnyError() {
asyncStart();
var cs = new List.generate(3, (_) => new Completer());
var result = Future.any(cs.map((x) => x.future));
result.then((v) {
Expect.fail("Unexpected value: $v");
}, onError: (e, s) {
Expect.equals(42, e);
asyncEnd();
});
cs[1].completeError(42);
cs[2].complete(10);
cs[0].complete(20);
}
void testAnyIgnoreIncomplete() {
asyncStart();
var cs = new List.generate(3, (_) => new Completer());
var result = Future.any(cs.map((x) => x.future));
result.then((v) {
Expect.equals(42, v);
asyncEnd();
}, onError: (e, s) {
Expect.fail("Unexpected error: $e");
});
cs[1].complete(42);
// The other two futures never complete.
}
void testAnyIgnoreError() {
asyncStart();
var cs = new List.generate(3, (_) => new Completer());
var result = Future.any(cs.map((x) => x.future));
result.then((v) {
Expect.equals(42, v);
asyncEnd();
}, onError: (e, s) {
Expect.fail("Unexpected error: $e");
});
cs[1].complete(42);
// The errors are ignored, not uncaught.
cs[2].completeError("BAD");
cs[0].completeError("BAD");
}
void testFutureResult() {
asyncStart();
() async {
var f = new UglyFuture(5);
// Sanity check that our future is as mis-behaved as we think.
f.then((v) {
Expect.equals(UglyFuture(4), v);
});
var v = await f;
// The static type of await is Flatten(static-type-of-expression), so it
// suggests that it flattens. In practice it currently doesn't.
// The specification doesn't say anything special, so v should be the
// completion value of the UglyFuture future which is a future.
Expect.equals(UglyFuture(4), v);
// We no longer flatten recursively when completing a future.
var w = new Future.value(42).then((_) => f);
Expect.equals(UglyFuture(4), await w);
asyncEnd();
}();
}
void testFutureOfFuture() async {
// Plain Future.
asyncStart();
var future = Future<Future<int>>.value(Future<int>.value(42));
Expect.type<Future<Future<int>>>(future);
future.then((innerFuture) {
Expect.type<Future<int>>(innerFuture);
innerFuture.then((number) {
Expect.equals(42, number);
asyncEnd();
});
});
// With completer.
asyncStart();
var completer = Completer<Future<int>>();
Expect.type<Future<Future<int>>>(completer.future);
completer.future.then((innerFuture) {
Expect.type<Future<int>>(innerFuture);
innerFuture.then((number) {
Expect.equals(42, number);
asyncEnd();
});
});
completer.complete(Future<int>.value(42));
}
main() {
asyncStart();
testValue();
testSync();
testNeverComplete();
testComplete();
testCompleteWithSuccessHandlerBeforeComplete();
testCompleteWithSuccessHandlerAfterComplete();
testCompleteManySuccessHandlers();
testCompleteWithError();
testCompleteWithFutureSuccess();
testCompleteWithFutureSuccess2();
testCompleteWithFutureError();
testCompleteWithFutureError2();
testCompleteErrorWithFuture();
testCompleteWithCustomFutureSuccess();
testCompleteWithCustomFutureError();
testCompleteErrorWithCustomFuture();
testException();
testExceptionHandler();
testExceptionHandlerReturnsTrue();
testExceptionHandlerReturnsTrue2();
testExceptionHandlerReturnsFalse();
testFutureAsStreamCompleteAfter();
testFutureAsStreamCompleteBefore();
testFutureAsStreamCompleteImmediate();
testFutureAsStreamCompleteErrorAfter();
testFutureAsStreamWrapper();
testFutureWhenCompleteValue();
testFutureWhenCompleteError();
testFutureWhenCompleteValueNewError();
testFutureWhenCompleteErrorNewError();
testFutureWhenValueFutureValue();
testFutureWhenErrorFutureValue();
testFutureWhenValueFutureError();
testFutureWhenErrorFutureError();
testFutureThenThrowsAsync();
testFutureCatchThrowsAsync();
testFutureWhenThrowsAsync();
testFutureCatchRethrowsAsync();
testChainedFutureValue();
testChainedFutureValueDelay();
testChainedFutureError();
testSyncFuture_i13368();
testWaitCleanUp();
testWaitCleanUpError();
testWaitSyncError();
testWaitSyncError2();
testWaitSyncError3();
testBadFuture();
testTypes();
testAnyValue();
testAnyError();
testAnyIgnoreIncomplete();
testAnyIgnoreError();
testFutureResult();
testFutureOfFuture();
asyncEnd();
}
/// A well-behaved Future that isn't recognizable as a _Future.
class CustomFuture<T> implements Future<T> {
Future<T> _realFuture;
CustomFuture(this._realFuture);
Future<S> then<S>(FutureOr<S> action(T result), {Function? onError}) =>
_realFuture.then(action, onError: onError);
Future<T> catchError(Function onError, {bool test(Object e)?}) =>
_realFuture.catchError(onError, test: test);
Future<T> whenComplete(action()) => _realFuture.whenComplete(action);
Future<T> timeout(Duration timeLimit, {FutureOr<T> onTimeout()?}) =>
_realFuture.timeout(timeLimit, onTimeout: onTimeout);
Stream<T> asStream() => _realFuture.asStream();
String toString() => "CustomFuture@${_realFuture.hashCode}";
int get hashCode => _realFuture.hashCode;
}
/// A bad future that throws on every method.
class BadFuture<T> implements Future<T> {
Future<S> then<S>(action(T result), {Function? onError}) {
throw "then GOTCHA!";
}
Future<T> catchError(Function onError, {bool test(Object e)?}) {
throw "catch GOTCHA!";
}
Future<T> whenComplete(action()) {
throw "finally GOTCHA!";
}
Stream<T> asStream() {
throw "asStream GOTCHA!";
}
Future<T> timeout(Duration duration, {onTimeout()?}) {
throw "timeout GOTCHA!";
}
}
// An evil future that completes with another future.
class UglyFuture implements Future<dynamic> {
final _result;
final int _badness;
UglyFuture(int badness)
: _badness = badness,
_result = (badness == 0) ? 42 : new UglyFuture(badness - 1);
Future<S> then<S>(action(value), {Function? onError}) {
var c = new Completer<S>();
c.complete(new Future<S>.microtask(() => action(_result)));
return c.future;
}
Future catchError(onError, {test}) => this; // Never an error.
Future whenComplete(action()) {
return new Future.microtask(action).then((_) => this);
}
Stream asStream() {
return (new StreamController()
..add(_result)
..close())
.stream;
}
Future timeout(Duration duration, {onTimeout()?}) {
return this;
}
int get hashCode => _badness;
bool operator ==(Object other) =>
other is UglyFuture && _badness == other._badness;
String toString() => "UglyFuture($_badness)";
}