| // 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<void> asyncError() { |
| final Completer<void> completer = Completer<void>(); |
| final Completer<void> errorCompleter = Completer<void>(); |
| errorCompleter.completeError('Async Doom', StackTrace.current); |
| return completer.future; |
| } |
| |
| Future<void> syncError() { |
| throw 'Sync Doom'; |
| } |
| |
| Future<void> syncAndAsyncError() { |
| final Completer<void> errorCompleter = Completer<void>(); |
| errorCompleter.completeError('Async Doom', StackTrace.current); |
| throw 'Sync Doom'; |
| } |
| |
| Future<void> delayedThrow(FakeAsync time) { |
| final Future<void> result = |
| Future<void>.delayed(const Duration(milliseconds: 10)) |
| .then((_) { |
| throw 'Delayed Doom'; |
| }); |
| time.elapse(const Duration(seconds: 1)); |
| time.flushMicrotasks(); |
| return result; |
| } |
| |
| void main() { |
| Completer<void> caughtInZone; |
| bool caughtByZone = false; |
| bool caughtByHandler = false; |
| Zone zone; |
| |
| setUp(() { |
| caughtInZone = Completer<void>(); |
| 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(<Future<void>>[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(<Future<void>>[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<void> completer = Completer<void>(); |
| await FakeAsync().run((FakeAsync time) { |
| unawaited(runZoned(() async { |
| final Future<void> f = asyncGuard<void>(() => 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<void> completer = Completer<void>(); |
| await FakeAsync().run((FakeAsync time) { |
| unawaited(runZoned(() async { |
| final Future<void> f = asyncGuard<void>( |
| () => 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<void> completer = Completer<void>(); |
| await FakeAsync().run((FakeAsync time) { |
| unawaited(runZoned(() async { |
| final Future<void> f = asyncGuard<void>( |
| () => 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<void> completer = Completer<void>(); |
| await FakeAsync().run((FakeAsync time) { |
| unawaited(runZoned(() async { |
| final Future<void> f = asyncGuard<void>( |
| () => 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); |
| }); |
| } |