// Copyright 2014 The Flutter Authors. 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'; import 'package:flutter_tools/src/base/async_guard.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:fake_async/fake_async.dart'; import '../../src/common.dart'; Future asyncError() { final Completer completer = Completer(); final Completer errorCompleter = Completer(); errorCompleter.completeError('Async Doom', StackTrace.current); return completer.future; } Future syncError() { throw 'Sync Doom'; } Future syncAndAsyncError() { final Completer errorCompleter = Completer(); errorCompleter.completeError('Async Doom', StackTrace.current); throw 'Sync Doom'; } Future delayedThrow(FakeAsync time) { final Future result = Future.delayed(const Duration(milliseconds: 10)) .then((_) { throw 'Delayed Doom'; }); time.elapse(const Duration(seconds: 1)); time.flushMicrotasks(); return result; } void main() { Completer caughtInZone; bool caughtByZone = false; bool caughtByHandler = false; Zone zone; setUp(() { caughtInZone = Completer(); caughtByZone = false; caughtByHandler = false; zone = Zone.current.fork(specification: ZoneSpecification( handleUncaughtError: ( Zone self, ZoneDelegate parent, Zone zone, Object error, StackTrace stackTrace, ) { caughtByZone = true; if (!caughtInZone.isCompleted) { caughtInZone.complete(); } }, )); }); test('asyncError percolates through zone', () async { await zone.run(() async { try { // Completer is required or else we timeout. await Future.any(>[asyncError(), caughtInZone.future]); } on String { caughtByHandler = true; } }); expect(caughtByZone, true); expect(caughtByHandler, false); }); test('syncAndAsyncError percolates through zone', () async { await zone.run(() async { try { // Completer is required or else we timeout. await Future.any(>[syncAndAsyncError(), caughtInZone.future]); } on String { caughtByHandler = true; } }); expect(caughtByZone, true); expect(caughtByHandler, true); }); test('syncError percolates through zone', () async { await zone.run(() async { try { await syncError(); } on String { caughtByHandler = true; } }); expect(caughtByZone, false); expect(caughtByHandler, true); }); test('syncError is caught by asyncGuard', () async { await zone.run(() async { try { await asyncGuard(syncError); } on String { caughtByHandler = true; } }); expect(caughtByZone, false); expect(caughtByHandler, true); }); test('asyncError is caught by asyncGuard', () async { await zone.run(() async { try { await asyncGuard(asyncError); } on String { caughtByHandler = true; } }); expect(caughtByZone, false); expect(caughtByHandler, true); }); test('asyncAndSyncError is caught by asyncGuard', () async { await zone.run(() async { try { await asyncGuard(syncAndAsyncError); } on String { caughtByHandler = true; } }); expect(caughtByZone, false); expect(caughtByHandler, true); }); test('asyncError is missed when catchError is attached too late', () async { bool caughtByZone = false; bool caughtByHandler = false; bool caughtByCatchError = false; final Completer completer = Completer(); await FakeAsync().run((FakeAsync time) { unawaited(runZoned(() async { final Future f = asyncGuard(() => delayedThrow(time)) .catchError((Object e, StackTrace s) { caughtByCatchError = true; }); try { await f; } on String { caughtByHandler = true; } if (!completer.isCompleted) { completer.complete(null); } }, onError: (Object e, StackTrace s) { caughtByZone = true; if (!completer.isCompleted) { completer.complete(null); } })); time.elapse(const Duration(seconds: 1)); time.flushMicrotasks(); return completer.future; }); expect(caughtByZone, true); expect(caughtByHandler, false); expect(caughtByCatchError, true); }); test('asyncError is propagated with binary onError', () async { bool caughtByZone = false; bool caughtByHandler = false; bool caughtByOnError = false; final Completer completer = Completer(); await FakeAsync().run((FakeAsync time) { unawaited(runZoned(() async { final Future f = asyncGuard( () => delayedThrow(time), onError: (Object e, StackTrace s) { caughtByOnError = true; }, ); try { await f; } on String { caughtByHandler = true; } if (!completer.isCompleted) { completer.complete(null); } }, onError: (Object e, StackTrace s) { caughtByZone = true; if (!completer.isCompleted) { completer.complete(null); } })); time.elapse(const Duration(seconds: 1)); time.flushMicrotasks(); return completer.future; }); expect(caughtByZone, false); expect(caughtByHandler, false); expect(caughtByOnError, true); }); test('asyncError is propagated with unary onError', () async { bool caughtByZone = false; bool caughtByHandler = false; bool caughtByOnError = false; final Completer completer = Completer(); await FakeAsync().run((FakeAsync time) { unawaited(runZoned(() async { final Future f = asyncGuard( () => delayedThrow(time), onError: (Object e) { caughtByOnError = true; }, ); try { await f; } on String { caughtByHandler = true; } if (!completer.isCompleted) { completer.complete(null); } }, onError: (Object e, StackTrace s) { caughtByZone = true; if (!completer.isCompleted) { completer.complete(null); } })); time.elapse(const Duration(seconds: 1)); time.flushMicrotasks(); return completer.future; }); expect(caughtByZone, false); expect(caughtByHandler, false); expect(caughtByOnError, true); }); test('asyncError is propagated with optional stack trace', () async { bool caughtByZone = false; bool caughtByHandler = false; bool caughtByOnError = false; bool nonNullStackTrace = false; final Completer completer = Completer(); await FakeAsync().run((FakeAsync time) { unawaited(runZoned(() async { final Future f = asyncGuard( () => delayedThrow(time), onError: (Object e, [StackTrace s]) { caughtByOnError = true; nonNullStackTrace = s != null; }, ); try { await f; } on String { caughtByHandler = true; } if (!completer.isCompleted) { completer.complete(null); } }, onError: (Object e, StackTrace s) { caughtByZone = true; if (!completer.isCompleted) { completer.complete(null); } })); time.elapse(const Duration(seconds: 1)); time.flushMicrotasks(); return completer.future; }); expect(caughtByZone, false); expect(caughtByHandler, false); expect(caughtByOnError, true); expect(nonNullStackTrace, true); }); }