async_guard_test.dart 8.19 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

7
import 'package:fake_async/fake_async.dart';
8 9 10 11 12 13 14
import 'package:flutter_tools/src/base/async_guard.dart';

import '../../src/common.dart';

Future<void> asyncError() {
  final Completer<void> completer = Completer<void>();
  final Completer<void> errorCompleter = Completer<void>();
15
  errorCompleter.completeError(_CustomException('Async Doom'), StackTrace.current);
16 17 18
  return completer.future;
}

19 20 21 22 23 24 25 26 27 28 29
/// Specialized exception to be caught.
class _CustomException implements Exception {
  _CustomException(this.message);

  final String message;

  @override
  String toString() => message;
}


30
Future<void> syncError() {
31
  throw _CustomException('Sync Doom');
32 33 34 35
}

Future<void> syncAndAsyncError() {
  final Completer<void> errorCompleter = Completer<void>();
36 37
  errorCompleter.completeError(_CustomException('Async Doom'), StackTrace.current);
  throw _CustomException('Sync Doom');
38 39
}

40 41
Future<void> delayedThrow(FakeAsync time) {
  final Future<void> result =
42
    Future<void>.delayed(const Duration(milliseconds: 10))
43
      .then((_) async {
44
        throw _CustomException('Delayed Doom');
45
      });
46 47 48 49 50
  time.elapse(const Duration(seconds: 1));
  time.flushMicrotasks();
  return result;
}

51
void main() {
52
  late Completer<void> caughtInZone;
53 54
  bool caughtByZone = false;
  bool caughtByHandler = false;
55
  late Zone zone;
56 57 58 59 60 61 62

  setUp(() {
    caughtInZone = Completer<void>();
    caughtByZone = false;
    caughtByHandler = false;
    zone = Zone.current.fork(specification: ZoneSpecification(
      handleUncaughtError: (
63 64 65 66 67 68
        Zone self,
        ZoneDelegate parent,
        Zone zone,
        Object error,
        StackTrace stackTrace,
      ) {
69 70 71 72 73 74 75 76 77 78 79 80 81
        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]);
82
      } on _CustomException {
83 84 85 86 87 88 89 90 91 92 93 94 95
        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]);
96
      } on _CustomException {
97 98 99 100 101 102 103 104 105 106 107 108
        caughtByHandler = true;
      }
    });

    expect(caughtByZone, true);
    expect(caughtByHandler, true);
  });

  test('syncError percolates through zone', () async {
    await zone.run(() async {
      try {
        await syncError();
109
      } on _CustomException {
110 111 112 113 114 115 116 117 118 119 120 121
        caughtByHandler = true;
      }
    });

    expect(caughtByZone, false);
    expect(caughtByHandler, true);
  });

  test('syncError is caught by asyncGuard', () async {
    await zone.run(() async {
      try {
        await asyncGuard(syncError);
122
      } on _CustomException {
123 124 125 126 127 128 129 130 131 132 133 134 135
        caughtByHandler = true;
      }
    });

    expect(caughtByZone, false);
    expect(caughtByHandler, true);
  });


  test('asyncError is caught by asyncGuard', () async {
    await zone.run(() async {
      try {
        await asyncGuard(asyncError);
136
      } on _CustomException {
137 138 139 140 141 142 143 144 145 146 147 148
        caughtByHandler = true;
      }
    });

    expect(caughtByZone, false);
    expect(caughtByHandler, true);
  });

  test('asyncAndSyncError is caught by asyncGuard', () async {
    await zone.run(() async {
      try {
        await asyncGuard(syncAndAsyncError);
149
      } on _CustomException {
150 151 152 153 154 155 156
        caughtByHandler = true;
      }
    });

    expect(caughtByZone, false);
    expect(caughtByHandler, true);
  });
157 158 159 160 161 162 163 164

  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) {
165
      unawaited(runZonedGuarded(() async {
166
        final Future<void> f = asyncGuard<void>(() => delayedThrow(time))
167 168 169 170 171 172
          .then(
            (Object? obj) => obj,
            onError: (Object e, StackTrace s) {
              caughtByCatchError = true;
            },
          );
173 174
        try {
          await f;
175
        } on _CustomException {
176 177 178
          caughtByHandler = true;
        }
        if (!completer.isCompleted) {
179
          completer.complete();
180
        }
181
      }, (Object e, StackTrace s) {
182 183
        caughtByZone = true;
        if (!completer.isCompleted) {
184
          completer.complete();
185 186 187 188 189 190 191 192 193 194 195 196
        }
      }));
      time.elapse(const Duration(seconds: 1));
      time.flushMicrotasks();
      return completer.future;
    });

    expect(caughtByZone, true);
    expect(caughtByHandler, false);
    expect(caughtByCatchError, true);
  });

197
  test('asyncError is propagated with binary onError', () async {
198 199 200 201 202 203
    bool caughtByZone = false;
    bool caughtByHandler = false;
    bool caughtByOnError = false;

    final Completer<void> completer = Completer<void>();
    await FakeAsync().run((FakeAsync time) {
204
      unawaited(runZonedGuarded(() async {
205 206 207 208 209 210
        final Future<void> f = asyncGuard<void>(
          () => delayedThrow(time),
          onError: (Object e, StackTrace s) {
            caughtByOnError = true;
          },
        );
211 212
        try {
          await f;
213
        } on _CustomException {
214 215 216
          caughtByHandler = true;
        }
        if (!completer.isCompleted) {
217
          completer.complete();
218
        }
219
      }, (Object e, StackTrace s) {
220 221
        caughtByZone = true;
        if (!completer.isCompleted) {
222
          completer.complete();
223 224 225 226 227 228 229 230 231 232 233
        }
      }));
      time.elapse(const Duration(seconds: 1));
      time.flushMicrotasks();
      return completer.future;
    });

    expect(caughtByZone, false);
    expect(caughtByHandler, false);
    expect(caughtByOnError, true);
  });
234 235 236 237 238 239 240 241

  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) {
242
      unawaited(runZonedGuarded(() async {
243 244 245 246 247 248 249 250
        final Future<void> f = asyncGuard<void>(
          () => delayedThrow(time),
          onError: (Object e) {
            caughtByOnError = true;
          },
        );
        try {
          await f;
251
        } on _CustomException {
252 253 254
          caughtByHandler = true;
        }
        if (!completer.isCompleted) {
255
          completer.complete();
256
        }
257
      }, (Object e, StackTrace s) {
258 259
        caughtByZone = true;
        if (!completer.isCompleted) {
260
          completer.complete();
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
        }
      }));
      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) {
281
      unawaited(runZonedGuarded(() async {
282 283
        final Future<void> f = asyncGuard<void>(
          () => delayedThrow(time),
284
          onError: (Object e, [StackTrace? s]) {
285 286 287 288 289 290
            caughtByOnError = true;
            nonNullStackTrace = s != null;
          },
        );
        try {
          await f;
291
        } on _CustomException {
292 293 294
          caughtByHandler = true;
        }
        if (!completer.isCompleted) {
295
          completer.complete();
296
        }
297
      }, (Object e, StackTrace s) {
298 299
        caughtByZone = true;
        if (!completer.isCompleted) {
300
          completer.complete();
301 302 303 304 305 306 307 308 309 310 311 312
        }
      }));
      time.elapse(const Duration(seconds: 1));
      time.flushMicrotasks();
      return completer.future;
    });

    expect(caughtByZone, false);
    expect(caughtByHandler, false);
    expect(caughtByOnError, true);
    expect(nonNullStackTrace, true);
  });
313
}