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