| // Copyright (c) 2015, 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. |
| |
| // dart2js chain tests are separated out because dart2js stack traces are |
| // inconsistent due to inlining and browser differences. These tests don't |
| // assert anything about the content of the traces, just the number of traces in |
| // a chain. |
| @TestOn('js') |
| |
| import 'dart:async'; |
| |
| import 'package:stack_trace/stack_trace.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'utils.dart'; |
| |
| void main() { |
| group('capture() with onError catches exceptions', () { |
| test('thrown synchronously', () async { |
| var chain = await captureFuture(() => throw 'error'); |
| expect(chain.traces, hasLength(1)); |
| }); |
| |
| test('thrown in a microtask', () async { |
| var chain = await captureFuture(() => inMicrotask(() => throw 'error')); |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('thrown in a one-shot timer', () async { |
| var chain = |
| await captureFuture(() => inOneShotTimer(() => throw 'error')); |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('thrown in a periodic timer', () async { |
| var chain = |
| await captureFuture(() => inPeriodicTimer(() => throw 'error')); |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('thrown in a nested series of asynchronous operations', () async { |
| var chain = await captureFuture(() { |
| inPeriodicTimer(() { |
| inOneShotTimer(() => inMicrotask(() => throw 'error')); |
| }); |
| }); |
| |
| expect(chain.traces, hasLength(4)); |
| }); |
| |
| test('thrown in a long future chain', () async { |
| var chain = await captureFuture(() => inFutureChain(() => throw 'error')); |
| |
| // Despite many asynchronous operations, there's only one level of |
| // nested calls, so there should be only two traces in the chain. This |
| // is important; programmers expect stack trace memory consumption to be |
| // O(depth of program), not O(length of program). |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('thrown in new Future()', () async { |
| var chain = await captureFuture(() => inNewFuture(() => throw 'error')); |
| expect(chain.traces, hasLength(3)); |
| }); |
| |
| test('thrown in new Future.sync()', () async { |
| var chain = await captureFuture(() { |
| inMicrotask(() => inSyncFuture(() => throw 'error')); |
| }); |
| |
| expect(chain.traces, hasLength(3)); |
| }); |
| |
| test('multiple times', () { |
| var completer = Completer(); |
| var first = true; |
| |
| Chain.capture(() { |
| inMicrotask(() => throw 'first error'); |
| inPeriodicTimer(() => throw 'second error'); |
| }, onError: (error, chain) { |
| try { |
| if (first) { |
| expect(error, equals('first error')); |
| expect(chain.traces, hasLength(2)); |
| first = false; |
| } else { |
| expect(error, equals('second error')); |
| expect(chain.traces, hasLength(2)); |
| completer.complete(); |
| } |
| } catch (error, stackTrace) { |
| completer.completeError(error, stackTrace); |
| } |
| }); |
| |
| return completer.future; |
| }); |
| |
| test('passed to a completer', () async { |
| var trace = Trace.current(); |
| var chain = await captureFuture(() { |
| inMicrotask(() => completerErrorFuture(trace)); |
| }); |
| |
| expect(chain.traces, hasLength(3)); |
| |
| // The first trace is the trace that was manually reported for the |
| // error. |
| expect(chain.traces.first.toString(), equals(trace.toString())); |
| }); |
| |
| test('passed to a completer with no stack trace', () async { |
| var chain = await captureFuture(() { |
| inMicrotask(() => completerErrorFuture()); |
| }); |
| |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('passed to a stream controller', () async { |
| var trace = Trace.current(); |
| var chain = await captureFuture(() { |
| inMicrotask(() => controllerErrorStream(trace).listen(null)); |
| }); |
| |
| expect(chain.traces, hasLength(3)); |
| expect(chain.traces.first.toString(), equals(trace.toString())); |
| }); |
| |
| test('passed to a stream controller with no stack trace', () async { |
| var chain = await captureFuture(() { |
| inMicrotask(() => controllerErrorStream().listen(null)); |
| }); |
| |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('and relays them to the parent zone', () { |
| var completer = Completer(); |
| |
| runZoned(() { |
| Chain.capture(() { |
| inMicrotask(() => throw 'error'); |
| }, onError: (error, chain) { |
| expect(error, equals('error')); |
| expect(chain.traces, hasLength(2)); |
| throw error; |
| }); |
| }, onError: (error, chain) { |
| try { |
| expect(error, equals('error')); |
| expect(chain, TypeMatcher<Chain>()); |
| expect(chain.traces, hasLength(2)); |
| completer.complete(); |
| } catch (error, stackTrace) { |
| completer.completeError(error, stackTrace); |
| } |
| }); |
| |
| return completer.future; |
| }); |
| }); |
| |
| test('capture() without onError passes exceptions to parent zone', () { |
| var completer = Completer(); |
| |
| runZoned(() { |
| Chain.capture(() => inMicrotask(() => throw 'error')); |
| }, onError: (error, chain) { |
| try { |
| expect(error, equals('error')); |
| expect(chain, TypeMatcher<Chain>()); |
| expect(chain.traces, hasLength(2)); |
| completer.complete(); |
| } catch (error, stackTrace) { |
| completer.completeError(error, stackTrace); |
| } |
| }); |
| |
| return completer.future; |
| }); |
| |
| group('current() within capture()', () { |
| test('called in a microtask', () async { |
| var completer = Completer(); |
| Chain.capture(() { |
| inMicrotask(() => completer.complete(Chain.current())); |
| }); |
| |
| var chain = await completer.future; |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('called in a one-shot timer', () async { |
| var completer = Completer(); |
| Chain.capture(() { |
| inOneShotTimer(() => completer.complete(Chain.current())); |
| }); |
| |
| var chain = await completer.future; |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('called in a periodic timer', () async { |
| var completer = Completer(); |
| Chain.capture(() { |
| inPeriodicTimer(() => completer.complete(Chain.current())); |
| }); |
| |
| var chain = await completer.future; |
| expect(chain.traces, hasLength(2)); |
| }); |
| |
| test('called in a nested series of asynchronous operations', () async { |
| var completer = Completer(); |
| Chain.capture(() { |
| inPeriodicTimer(() { |
| inOneShotTimer(() { |
| inMicrotask(() => completer.complete(Chain.current())); |
| }); |
| }); |
| }); |
| |
| var chain = await completer.future; |
| expect(chain.traces, hasLength(4)); |
| }); |
| |
| test('called in a long future chain', () async { |
| var completer = Completer(); |
| Chain.capture(() { |
| inFutureChain(() => completer.complete(Chain.current())); |
| }); |
| |
| var chain = await completer.future; |
| expect(chain.traces, hasLength(2)); |
| }); |
| }); |
| |
| test( |
| 'current() outside of capture() returns a chain wrapping the current ' |
| 'trace', () { |
| // The test runner runs all tests with chains enabled. |
| return Chain.disable(() async { |
| var completer = Completer(); |
| inMicrotask(() => completer.complete(Chain.current())); |
| |
| var chain = await completer.future; |
| // Since the chain wasn't loaded within [Chain.capture], the full stack |
| // chain isn't available and it just returns the current stack when |
| // called. |
| expect(chain.traces, hasLength(1)); |
| }); |
| }); |
| |
| group('forTrace() within capture()', () { |
| test('called for a stack trace from a microtask', () async { |
| var chain = await Chain.capture(() { |
| return chainForTrace(inMicrotask, () => throw 'error'); |
| }); |
| |
| // Because [chainForTrace] has to set up a future chain to capture the |
| // stack trace while still showing it to the zone specification, it adds |
| // an additional level of async nesting and so an additional trace. |
| expect(chain.traces, hasLength(3)); |
| }); |
| |
| test('called for a stack trace from a one-shot timer', () async { |
| var chain = await Chain.capture(() { |
| return chainForTrace(inOneShotTimer, () => throw 'error'); |
| }); |
| |
| expect(chain.traces, hasLength(3)); |
| }); |
| |
| test('called for a stack trace from a periodic timer', () async { |
| var chain = await Chain.capture(() { |
| return chainForTrace(inPeriodicTimer, () => throw 'error'); |
| }); |
| |
| expect(chain.traces, hasLength(3)); |
| }); |
| |
| test( |
| 'called for a stack trace from a nested series of asynchronous ' |
| 'operations', () async { |
| var chain = await Chain.capture(() { |
| return chainForTrace((callback) { |
| inPeriodicTimer(() => inOneShotTimer(() => inMicrotask(callback))); |
| }, () => throw 'error'); |
| }); |
| |
| expect(chain.traces, hasLength(5)); |
| }); |
| |
| test('called for a stack trace from a long future chain', () async { |
| var chain = await Chain.capture(() { |
| return chainForTrace(inFutureChain, () => throw 'error'); |
| }); |
| |
| expect(chain.traces, hasLength(3)); |
| }); |
| |
| test( |
| 'called for an unregistered stack trace returns a chain wrapping that ' |
| 'trace', () { |
| StackTrace trace; |
| var chain = Chain.capture(() { |
| try { |
| throw 'error'; |
| } catch (_, stackTrace) { |
| trace = stackTrace; |
| return Chain.forTrace(stackTrace); |
| } |
| }); |
| |
| expect(chain.traces, hasLength(1)); |
| expect( |
| chain.traces.first.toString(), equals(Trace.from(trace).toString())); |
| }); |
| }); |
| |
| test( |
| 'forTrace() outside of capture() returns a chain wrapping the given ' |
| 'trace', () { |
| StackTrace trace; |
| var chain = Chain.capture(() { |
| try { |
| throw 'error'; |
| } catch (_, stackTrace) { |
| trace = stackTrace; |
| return Chain.forTrace(stackTrace); |
| } |
| }); |
| |
| expect(chain.traces, hasLength(1)); |
| expect(chain.traces.first.toString(), equals(Trace.from(trace).toString())); |
| }); |
| } |