| // Copyright (c) 2013, 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 'package:expect/expect.dart'; |
| import 'package:async_helper/async_helper.dart'; |
| import 'dart:async'; |
| |
| /** |
| * We represent the current stack trace by an integer. From time to time we |
| * increment the variable. This corresponds to a new stack trace. |
| */ |
| int stackTrace = 0; |
| List restoredStackTrace = []; |
| |
| List events = []; |
| |
| ZoneCallback<R> debugZoneRegisterCallback<R>( |
| Zone self, ZoneDelegate parent, Zone origin, R f()) { |
| List savedTrace = [stackTrace]..addAll(restoredStackTrace); |
| return parent.registerCallback(origin, () { |
| restoredStackTrace = savedTrace; |
| return f(); |
| }); |
| } |
| |
| ZoneUnaryCallback<R, T> debugZoneRegisterUnaryCallback<R, T>( |
| Zone self, ZoneDelegate parent, Zone origin, R f(T arg)) { |
| List savedTrace = [stackTrace]..addAll(restoredStackTrace); |
| return parent.registerUnaryCallback(origin, (arg) { |
| restoredStackTrace = savedTrace; |
| return f(arg); |
| }); |
| } |
| |
| T debugZoneRun<T>(Zone self, ZoneDelegate parent, Zone origin, T f()) { |
| stackTrace++; |
| restoredStackTrace = []; |
| return parent.run(origin, f); |
| } |
| |
| R debugZoneRunUnary<R, T>( |
| Zone self, ZoneDelegate parent, Zone origin, R f(T arg), T arg) { |
| stackTrace++; |
| restoredStackTrace = []; |
| return parent.runUnary(origin, f, arg); |
| } |
| |
| late List expectedDebugTrace; |
| |
| void debugUncaughtHandler( |
| Zone self, |
| ZoneDelegate parent, |
| Zone origin, |
| error, |
| StackTrace? stackTrace) { |
| events.add("handling uncaught error $error"); |
| Expect.listEquals(expectedDebugTrace, restoredStackTrace); |
| // Suppress the error and don't propagate to parent. |
| } |
| |
| const DEBUG_SPECIFICATION = const ZoneSpecification( |
| registerCallback: debugZoneRegisterCallback, |
| registerUnaryCallback: debugZoneRegisterUnaryCallback, |
| run: debugZoneRun, |
| runUnary: debugZoneRunUnary, |
| handleUncaughtError: debugUncaughtHandler); |
| |
| main() { |
| Completer done = new Completer(); |
| |
| // runGuarded calls run, captures the synchronous error (if any) and |
| // gives that one to handleUncaughtError. |
| |
| Expect.identical(Zone.root, Zone.current); |
| Zone forked; |
| forked = Zone.current.fork(specification: DEBUG_SPECIFICATION); |
| |
| asyncStart(); |
| |
| int openTests = 0; |
| |
| openTests++; |
| forked.run(() { |
| int forkTrace = stackTrace; |
| scheduleMicrotask(() { |
| int scheduleMicrotaskTrace = stackTrace; |
| scheduleMicrotask(() { |
| expectedDebugTrace = [scheduleMicrotaskTrace, forkTrace]; |
| openTests--; |
| if (openTests == 0) { |
| done.complete(); |
| } |
| throw "foo"; |
| }); |
| expectedDebugTrace = [forkTrace]; |
| throw "bar"; |
| }); |
| }); |
| |
| Expect.listEquals([], restoredStackTrace); |
| Zone forked2 = forked.fork(); |
| Zone forked3 = forked2.fork(); |
| int fork2Trace; |
| int fork3Trace; |
| var f2; |
| var globalTrace = stackTrace; |
| var f = forked3.bindCallback(() { |
| Expect.identical(forked3, Zone.current); |
| fork2Trace = stackTrace; |
| f2 = forked2.bindCallback(() { |
| Expect.identical(forked2, Zone.current); |
| Expect.listEquals([fork2Trace, globalTrace], restoredStackTrace); |
| fork3Trace = stackTrace; |
| openTests--; |
| if (openTests == 0) { |
| done.complete(); |
| } |
| scheduleMicrotask(() { |
| expectedDebugTrace = [fork3Trace, fork2Trace, globalTrace]; |
| throw "gee"; |
| }); |
| }); |
| }); |
| openTests++; |
| f(); |
| f2(); |
| |
| done.future.whenComplete(() { |
| // We don't really care for the order. |
| events.sort(); |
| Expect.listEquals([ |
| "handling uncaught error bar", |
| "handling uncaught error foo", |
| "handling uncaught error gee" |
| ], events); |
| asyncEnd(); |
| }); |
| } |