mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
e86b08e5bb
If a target `_Future` is chained to another source `_Future`, then it's because the target will complete with the same result as the source future. Instead of keeping both alive with a listener on the source which completes the target, instead the target moves all its listeners to be directly on the source, and keeps a link to the source in case more listeners are added later. The idea is that most futures are unreferenced after they have had their first listener, so the target future has a chance to be GC'ed. If the target future has `.ignore()` called, and has no listener, then the source completing with an error should not cause the error to be uncaught. Without the optimization, the source would have had a listener, and the target would ignore the error. To simulate that, the source now gets a copy of the target's `_ignoreUnhandledErrors` flag. This is still not precisely the same as it would be without the optimization. If *two* target futures are chained to the same source, and only one of targes has `.ignore()` called, then this implementation will make the uncaught error not be reported, where it technically should. (An alternative would be to *not* use chaining for futures with `ignore()` called on them. But those are precisely futures that are likely to be GC'able, because someone has already said that they don't care if the future complete with errors.) Fixes #54943 Bug: https://github.com/dart-lang/sdk/issues/54943 Change-Id: I0dbb4919ce2ea612d66539862fa0eb188aab8287 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352908 Reviewed-by: Slava Egorov <vegorov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com> Reviewed-by: Stephen Adams <sra@google.com>
1302 lines
31 KiB
Dart
1302 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();
|
|
runZonedGuarded(() {
|
|
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();
|
|
});
|
|
}, (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));
|
|
}
|
|
|
|
void testIgnoreWhenCompleteError() {
|
|
// Regression test for https://github.com/dart-lang/sdk/issues/54943
|
|
asyncStart();
|
|
Future.error("Should be overridden by whenComplete error.").whenComplete(() {
|
|
return Future.error("From whenComplete. Should be ignored.");
|
|
}).ignore();
|
|
Future.delayed(Duration.zero, asyncEnd);
|
|
}
|
|
|
|
void 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();
|
|
|
|
testIgnoreWhenCompleteError();
|
|
|
|
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)";
|
|
}
|