Index: packages/stack_trace/test/chain/dart2js_test.dart |
diff --git a/packages/stack_trace/test/chain/dart2js_test.dart b/packages/stack_trace/test/chain/dart2js_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..afb27fa046405523df4758a26bc3be4b81ad2996 |
--- /dev/null |
+++ b/packages/stack_trace/test/chain/dart2js_test.dart |
@@ -0,0 +1,338 @@ |
+// 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, so to test without we |
+ // have to do some zone munging. |
+ return runZoned(() 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)); |
+ }, zoneValues: {#stack_trace.stack_zone.spec: null}); |
+ }); |
+ |
+ 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())); |
+ }); |
+} |