async_guard_test.dart 8.16 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
          .catchError((Object e, StackTrace s) {
            caughtByCatchError = true;
          });
170 171
        try {
          await f;
172
        } on _CustomException {
173 174 175 176 177
          caughtByHandler = true;
        }
        if (!completer.isCompleted) {
          completer.complete(null);
        }
178
      }, (Object e, StackTrace s) {
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
        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);
  });

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

    final Completer<void> completer = Completer<void>();
    await FakeAsync().run((FakeAsync time) {
201
      unawaited(runZonedGuarded(() async {
202 203 204 205 206 207
        final Future<void> f = asyncGuard<void>(
          () => delayedThrow(time),
          onError: (Object e, StackTrace s) {
            caughtByOnError = true;
          },
        );
208 209
        try {
          await f;
210
        } on _CustomException {
211 212 213 214 215
          caughtByHandler = true;
        }
        if (!completer.isCompleted) {
          completer.complete(null);
        }
216
      }, (Object e, StackTrace s) {
217 218 219 220 221 222 223 224 225 226 227 228 229 230
        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);
  });
231 232 233 234 235 236 237 238

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