// 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 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(); completer.completeError(error, stack); return futureError(completer.future); }); // Through controller, sync. await test("Completer.sync().completeError", (error, stack) { var completer = Completer.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.broadcast(sync: sync) : StreamController(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 future) => future.then(fail, onError: captureError); /// Captures first error of stream. Future<(Object, StackTrace)> streamError(Stream 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; }