blob: 323aa9043fe7912fbd70f80ff974947c0d6fde7f [file] [log] [blame] [edit]
// Copyright (c) 2024, 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.
import 'dart:async';
// Checks that an `Error` object that is "thrown" through async functionality
// gets a stack trace set.
import 'package:expect/async_helper.dart';
import 'package:expect/expect.dart';
main() async {
asyncStart();
for ((String, Object, StackTrace, Object, String, AsyncError?) Function()
setUp
in [
() {
// Without error callback. Inject and expect same fresh error.
var error = StateError("Quo!");
var stack = StackTrace.fromString("Secret stack");
return ("", error, stack, error, stack.toString(), null);
},
() {
// With error callback, same error, new stack.
var error = StateError("Quo");
var stack = StackTrace.fromString("Secret stack");
var stack2 = StackTrace.fromString("Other stack");
var errorCallback = AsyncError(error, stack2);
return (
"errorCallback: fresh both",
error,
stack,
error,
stack2.toString(),
errorCallback,
);
},
() {
// With error callback, new error, stack.
var error = StateError("Quo");
var stack = StackTrace.fromString("Secret stack");
var error2 = StateError("Quid");
var stack2 = StackTrace.fromString("Other stack");
var errorCallback = AsyncError(error2, stack2);
return (
"errorCallback: fresh stack",
error,
stack,
error2,
stack2.toString(),
errorCallback,
);
},
]) {
Future<void> test(
String name,
Future<(Object, StackTrace)> Function(Object, StackTrace) body,
) {
var (msg, error, stack, expectError, expectStackString, errorCallback) =
setUp();
print("Test: $name${msg.isNotEmpty ? ", $msg" : msg}");
return asyncTest(
() =>
runZoned(
() => body(error, stack),
zoneSpecification: ZoneSpecification(
errorCallback: (s, p, z, e, st) => errorCallback,
),
).then((es) {
var (e, s) = es;
Expect.identical(expectError, e, name);
Expect.equals(expectStackString, s.toString(), name);
Expect.equals(
expectStackString,
(e as Error).stackTrace.toString(),
name,
);
}),
);
}
// Sanity check: Plain throws.
() {
var (name, error, stack, error2, stack2, errorCallback) = setUp();
try {
Error.throwWithStackTrace(error, stack);
} catch (e, s) {
Expect.identical(error, e, "Error.throw");
Expect.equals(stack.toString(), s.toString(), "Error.throw");
Expect.equals(
stack.toString(),
(e as Error).stackTrace.toString(),
"Error.throw",
);
}
}();
// Futures.
// Immediate error.
await test(
"Future.error",
(error, stack) => futureError(Future.error(error, stack)),
);
// Through controller, async.
await test("Completer().completeError", (error, stack) {
var completer = Completer<void>();
completer.completeError(error, stack);
return futureError(completer.future);
});
// Through controller, sync.
await test("Completer.sync().completeError", (error, stack) {
var completer = Completer<void>.sync();
var future = completer.future..ignore();
completer.completeError(error, stack);
return futureError(future);
});
// Streams.
// Singleton error.
await test(
"Stream.error",
(error, stack) => streamError(Stream.error(error, stack)),
);
// Controller errors.
for (var broadcast in [false, true]) {
for (var sync in [false, true]) {
await test(
"Stream${broadcast ? ".broadcast" : ""}${sync ? "(sync)" : ""}",
(error, stack) {
var controller = broadcast
? StreamController<void>.broadcast(sync: sync)
: StreamController<void>(sync: sync);
var future = streamError(controller.stream)..ignore();
controller.addError(error, stack);
return future;
},
);
}
}
}
asyncEnd();
}
// --------------------------------------------------------------------
// Helper functions
(Object, StackTrace) captureError(Object e, StackTrace s) => (e, s);
(Object, StackTrace) fail(_) {
Expect.fail("No error from future") as Never;
}
/// Captures error of future.
Future<(Object, StackTrace)> futureError(Future<void> future) =>
future.then(fail, onError: captureError);
/// Captures first error of stream.
Future<(Object, StackTrace)> streamError(Stream<void> stream) {
var c = Completer<(Object, StackTrace)>();
var sub = stream.listen(
(_) {
// No values expected.
},
onError: (Object e, StackTrace s) {
c.complete((e, s));
},
onDone: () {
Expect.fail("No error from stream");
},
cancelOnError: true,
);
return c.future;
}