blob: b8d3a825d7d1cc68ab7cd60c6a2beb038e9c1562 [file] [log] [blame]
// 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 = new 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 = new 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 = new 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 = new 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, new isInstanceOf<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 = new Completer();
runZoned(() {
Chain.capture(() => inMicrotask(() => throw 'error'));
}, onError: (error, chain) {
try {
expect(error, equals('error'));
expect(chain, new isInstanceOf<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 = new Completer();
Chain.capture(() {
inMicrotask(() => completer.complete(new Chain.current()));
});
var chain = await completer.future;
expect(chain.traces, hasLength(2));
});
test('called in a one-shot timer', () async {
var completer = new Completer();
Chain.capture(() {
inOneShotTimer(() => completer.complete(new Chain.current()));
});
var chain = await completer.future;
expect(chain.traces, hasLength(2));
});
test('called in a periodic timer', () async {
var completer = new Completer();
Chain.capture(() {
inPeriodicTimer(() => completer.complete(new Chain.current()));
});
var chain = await completer.future;
expect(chain.traces, hasLength(2));
});
test('called in a nested series of asynchronous operations', () async {
var completer = new Completer();
Chain.capture(() {
inPeriodicTimer(() {
inOneShotTimer(() {
inMicrotask(() => completer.complete(new Chain.current()));
});
});
});
var chain = await completer.future;
expect(chain.traces, hasLength(4));
});
test('called in a long future chain', () async {
var completer = new Completer();
Chain.capture(() {
inFutureChain(() => completer.complete(new 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 = new Completer();
inMicrotask(() => completer.complete(new 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', () {
var trace;
var chain = Chain.capture(() {
try {
throw 'error';
} catch (_, stackTrace) {
trace = stackTrace;
return new Chain.forTrace(stackTrace);
}
});
expect(chain.traces, hasLength(1));
expect(chain.traces.first.toString(),
equals(new Trace.from(trace).toString()));
});
});
test('forTrace() outside of capture() returns a chain wrapping the given '
'trace', () {
var trace;
var chain = Chain.capture(() {
try {
throw 'error';
} catch (_, stackTrace) {
trace = stackTrace;
return new Chain.forTrace(stackTrace);
}
});
expect(chain.traces, hasLength(1));
expect(chain.traces.first.toString(),
equals(new Trace.from(trace).toString()));
});
}