| // 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; | |
| } |