dart-sdk/tests/lib/async/future_future_test.dart
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

187 lines
5.6 KiB
Dart

// Copyright (c) 2020, 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.
// Checks that `Future<Future<int>>` is a valid type
// and that futures can contain and complete other futures.
// This essentially checks that `FutureOr<X>` is treated correctly depending
// on what `X` is.
import 'dart:async';
import 'package:async_helper/async_helper.dart';
import "package:expect/expect.dart";
main() {
asyncStart();
// Helper values and factories.
Future<Null> nullFuture = Future<Null>.value(null);
var stack = StackTrace.current;
var error = ArgumentError("yep");
Future<Null> errorFuture = Future<Null>.error(error, stack)
..catchError((_) => null);
Future<int> fi(int n) => Future<int>.value(n);
Future<Future<int>> ffi(n) => Future<Future<int>>.value(fi(n));
// Tests that `Future<Future<int>>` can be created.
asyncTest(() {
return expectFutureFutureInt(ffi(0), 0);
});
// Check `Future.then`'s callback.
asyncTest(() {
Future<int> future = nullFuture.then<int>((_) => fi(1));
return expectFutureInt(future, 1);
});
asyncTest(() {
Future<Future<int>> future = nullFuture.then<Future<int>>((_) => fi(2));
return expectFutureFutureInt(future, 2);
});
asyncTest(() {
Future<Future<int>> future = nullFuture.then<Future<int>>((_) => ffi(3));
return expectFutureFutureInt(future, 3);
});
// Check `Future.then`'s `onError`.
asyncTest(() {
Future<int> future =
errorFuture.then<int>((_) => -1, onError: (_) => fi(4));
return expectFutureInt(future, 4);
});
asyncTest(() {
Future<Future<int>> future =
errorFuture.then<Future<int>>((_) => fi(-1), onError: (_) => fi(5));
return expectFutureFutureInt(future, 5);
});
asyncTest(() {
Future<Future<int>> future =
errorFuture.then<Future<int>>((_) => fi(-1), onError: (_) => ffi(6));
return expectFutureFutureInt(future, 6);
});
// Check `Future.catchError`.
// Its implied `FutureOr` return type is based on the original future's type.
asyncTest(() {
Future<int> errorFuture = Future<int>.error(error, stack);
Future<int> future = errorFuture.catchError((_) => fi(7));
return expectFutureInt(future, 7);
});
asyncTest(() {
Future<Future<int>> errorFuture = Future<Future<int>>.error(error, stack);
Future<Future<int>> future = errorFuture.catchError((_) => fi(8));
return expectFutureFutureInt(future, 8);
});
asyncTest(() {
Future<Future<int>> errorFuture = Future<Future<int>>.error(error, stack);
Future<Future<int>> future = errorFuture.catchError((_) => ffi(9));
return expectFutureFutureInt(future, 9);
});
// Check `Completer.complete`.
asyncTest(() {
var completer = Completer<int>()..complete(fi(10));
return expectFutureInt(completer.future, 10);
});
asyncTest(() {
var completer = Completer<Future<int>>()..complete(fi(11));
return expectFutureFutureInt(completer.future, 11);
});
asyncTest(() {
var completer = Completer<Future<int>>()..complete(ffi(12));
return expectFutureFutureInt(completer.future, 12);
});
// Future<Object> works correctly when Object is another Future.
asyncTest(() {
Future<Object> future = nullFuture.then<Object>((_) => fi(13));
Expect.type<Future<Object>>(future);
Expect.notType<Future<int>>(future);
return future.then<void>((o) {
Expect.equals(13, o);
});
});
asyncTest(() {
Future<Object> future = nullFuture.then<Object>((_) => ffi(14));
Expect.type<Future<Object>>(future);
Expect.notType<Future<int>>(future);
Expect.notType<Future<Future<int>>>(future);
return future.then<void>((v) => expectFutureInt(v, 14));
});
asyncTest(() {
Future<Object> future =
errorFuture.then<Object>((_) => -1, onError: (_) => fi(15));
Expect.type<Future<Object>>(future);
Expect.notType<Future<int>>(future);
return future.then<void>((o) {
Expect.equals(15, o);
});
});
asyncTest(() {
Future<Object> future =
errorFuture.then<Object>((_) => -1, onError: (_) => ffi(16));
Expect.type<Future<Object>>(future);
Expect.notType<Future<int>>(future);
Expect.notType<Future<Future<int>>>(future);
return future.then<void>((v) => expectFutureInt(v, 16));
});
asyncTest(() {
Future<Object> errorFuture = Future<Object>.error(error, stack);
Future<Object> future = errorFuture.catchError((_) => fi(17));
Expect.type<Future<Object>>(future);
Expect.notType<Future<int>>(future);
return future.then<void>((o) {
Expect.equals(17, o);
});
});
asyncTest(() {
Future<Object> errorFuture = Future<Object>.error(error, stack);
Future<Object> future = errorFuture.catchError((_) => ffi(18));
Expect.type<Future<Object>>(future);
Expect.notType<Future<int>>(future);
Expect.notType<Future<Future<int>>>(future);
return future.then<void>((v) => expectFutureInt(v, 18));
});
asyncEnd();
}
// Checks that future is a Future<Future<int>> containing the value Future<int>
// which then contains the value 42.
Future<void> expectFutureFutureInt(dynamic future, int n) {
Expect.type<Future<Future<int>>>(future);
asyncStart();
return future.then<void>((dynamic v) {
Expect.type<Future<int>>(v, "$n");
return expectFutureInt(v, n).then(asyncSuccess);
});
}
Future<void> expectFutureInt(dynamic future, int n) {
Expect.type<Future<int>>(future);
asyncStart();
return future.then<void>((dynamic v) {
Expect.type<int>(v, "$n");
Expect.equals(n, v);
asyncEnd();
});
}