Add an error callback to StackZoneSpecification.
This releases stack_trace 1.0.3.
R=rnystrom@google.com
Review URL: https://codereview.chromium.org//556363004
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@40383 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5d594a..56c614a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.0.3
+
+* Use `Zone.errorCallback` to attach stack chains to all errors without the need
+ for `Chain.track`, which is now deprecated.
+
## 1.0.2
* Remove a workaround for [issue 17083][].
diff --git a/README.md b/README.md
index 492a52a..38b768d 100644
--- a/README.md
+++ b/README.md
@@ -205,13 +205,4 @@
That's a lot easier to understand!
-### `Chain.track`
-
-For the most part `Chain.capture` will notice when an error is thrown and
-associate the correct stack chain with it. However, there are some cases where
-exceptions won't be automatically detected: any `Future` constructor,
-`Completer.completeError`, `Stream.addError`, and libraries that use these such
-as `dart:io` and `dart:async`. For these, all you need to do is wrap the Future
-or Stream in a call to `Chain.track` and the errors will be tracked correctly.
-
[Zone]: https://api.dartlang.org/apidocs/channels/stable/#dart-async.Zone
diff --git a/lib/src/chain.dart b/lib/src/chain.dart
index 78c6ae4..e4ac534 100644
--- a/lib/src/chain.dart
+++ b/lib/src/chain.dart
@@ -102,33 +102,13 @@
});
}
- /// Ensures that any errors emitted by [futureOrStream] have the correct stack
- /// chain information associated with them.
+ /// Returns [futureOrStream] unmodified.
///
- /// For the most part an error thrown within a [capture] zone will have the
- /// correct stack chain automatically associated with it. However, there are
- /// some cases where exceptions won't be automatically detected: any [Future]
- /// constructor, [Completer.completeError], [Stream.addError], and libraries
- /// that use these.
- ///
- /// This returns a [Future] or [Stream] that will emit the same values and
- /// errors as [futureOrStream]. The only exception is that if [futureOrStream]
- /// emits an error without a stack trace, one will be added in the return
- /// value.
- ///
- /// If this is called outside of a [capture] zone, it just returns
- /// [futureOrStream] as-is.
- ///
- /// As the name suggests, [futureOrStream] may be either a [Future] or a
- /// [Stream].
- static track(futureOrStream) {
- if (_currentSpec == null) return futureOrStream;
- if (futureOrStream is Future) {
- return _currentSpec.trackFuture(futureOrStream, 1);
- } else {
- return _currentSpec.trackStream(futureOrStream, 1);
- }
- }
+ /// Prior to Dart 1.7, this was necessary to ensure that stack traces for
+ /// exceptions reported with [Completer.completeError] and
+ /// [StreamController.addError] were tracked correctly.
+ @Deprecated("Chain.track is not necessary in Dart 1.7+.")
+ static track(futureOrStream) => futureOrStream;
/// Returns the current stack chain.
///
diff --git a/lib/src/stack_zone_specification.dart b/lib/src/stack_zone_specification.dart
index 9a4f7c0..292da65 100644
--- a/lib/src/stack_zone_specification.dart
+++ b/lib/src/stack_zone_specification.dart
@@ -56,7 +56,8 @@
handleUncaughtError: handleUncaughtError,
registerCallback: registerCallback,
registerUnaryCallback: registerUnaryCallback,
- registerBinaryCallback: registerBinaryCallback);
+ registerBinaryCallback: registerBinaryCallback,
+ errorCallback: errorCallback);
}
/// Returns the current stack chain.
@@ -88,7 +89,9 @@
var node = _createNode(level + 1);
future.then(completer.complete).catchError((e, stackTrace) {
if (stackTrace == null) stackTrace = new Trace.current();
- if (_chains[stackTrace] == null) _chains[stackTrace] = node;
+ if (stackTrace is! Chain && _chains[stackTrace] == null) {
+ _chains[stackTrace] = node;
+ }
completer.completeError(e, stackTrace);
});
return completer.future;
@@ -105,7 +108,9 @@
return stream.transform(new StreamTransformer.fromHandlers(
handleError: (error, stackTrace, sink) {
if (stackTrace == null) stackTrace = new Trace.current();
- if (_chains[stackTrace] == null) _chains[stackTrace] = node;
+ if (stackTrace is! Chain && _chains[stackTrace] == null) {
+ _chains[stackTrace] = node;
+ }
sink.addError(error, stackTrace);
}));
}
@@ -163,6 +168,26 @@
}
}
+ /// Attaches the current stack chain to [stackTrace], replacing it if
+ /// necessary.
+ AsyncError errorCallback(Zone self, ZoneDelegate parent, Zone zone,
+ Object error, StackTrace stackTrace) {
+ var asyncError = parent.errorCallback(zone, error, stackTrace);
+ if (asyncError != null) {
+ error = asyncError.error;
+ stackTrace = asyncError.stackTrace;
+ }
+
+ // Go up two levels to get through [_CustomZone.errorCallback].
+ if (stackTrace == null) {
+ stackTrace = _createNode(2).toChain();
+ } else {
+ if (_chains[stackTrace] == null) _chains[stackTrace] = _createNode(2);
+ }
+
+ return new AsyncError(error, stackTrace);
+ }
+
/// Creates a [_Node] with the current stack trace and linked to
/// [_currentNode].
///
diff --git a/pubspec.yaml b/pubspec.yaml
index 5226cf8..bf28770 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -7,7 +7,7 @@
#
# When the major version is upgraded, you *must* update that version constraint
# in pub to stay in sync with this.
-version: 1.0.2
+version: 1.0.3
author: "Dart Team <misc@dartlang.org>"
homepage: http://www.dartlang.org
description: >
@@ -19,4 +19,4 @@
dev_dependencies:
unittest: ">=0.9.0 <0.12.0"
environment:
- sdk: ">=1.0.0 <2.0.0"
+ sdk: ">=1.7.0-edge.40308 <2.0.0"
diff --git a/test/chain_test.dart b/test/chain_test.dart
index beb4721..6af3c6b 100644
--- a/test/chain_test.dart
+++ b/test/chain_test.dart
@@ -93,6 +93,37 @@
});
});
+ test('thrown in new Future()', () {
+ return captureFuture(() => inNewFuture(() => throw 'error'))
+ .then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+
+ // The second trace is the one captured by
+ // [StackZoneSpecification.errorCallback]. Because that runs
+ // asynchronously within [new Future], it doesn't actually refer to the
+ // source file at all.
+ expect(chain.traces[1].frames,
+ everyElement(frameLibrary(isNot(contains('chain_test')))));
+
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inNewFuture'))));
+ });
+ });
+
+ test('thrown in new Future.sync()', () {
+ return captureFuture(() {
+ inMicrotask(() => inSyncFuture(() => throw 'error'));
+ }).then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inSyncFuture'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
test('multiple times', () {
var completer = new Completer();
var first = true;
@@ -121,6 +152,73 @@
return completer.future;
});
+ test('passed to a completer', () {
+ var trace = new Trace.current();
+ return captureFuture(() {
+ inMicrotask(() => completerErrorFuture(trace));
+ }).then((chain) {
+ 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()));
+
+ // The second trace is the trace that was captured when
+ // [Completer.addError] was called.
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('completerErrorFuture'))));
+
+ // The third trace is the automatically-captured trace from when the
+ // microtask was scheduled.
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('passed to a completer with no stack trace', () {
+ return captureFuture(() {
+ inMicrotask(() => completerErrorFuture());
+ }).then((chain) {
+ expect(chain.traces, hasLength(2));
+
+ // The first trace is the one captured when [Completer.addError] was
+ // called.
+ expect(chain.traces[0].frames,
+ contains(frameMember(startsWith('completerErrorFuture'))));
+
+ // The second trace is the automatically-captured trace from when the
+ // microtask was scheduled.
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('passed to a stream controller', () {
+ var trace = new Trace.current();
+ return captureFuture(() {
+ inMicrotask(() => controllerErrorStream(trace).listen(null));
+ }).then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces.first.toString(), equals(trace.toString()));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('controllerErrorStream'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('passed to a stream controller with no stack trace', () {
+ return captureFuture(() {
+ inMicrotask(() => controllerErrorStream().listen(null));
+ }).then((chain) {
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames,
+ contains(frameMember(startsWith('controllerErrorStream'))));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
test('and relays them to the parent zone', () {
var completer = new Completer();
@@ -526,50 +624,6 @@
});
group('Chain.track(Future)', () {
- test('associates the current chain with a manually-reported exception with '
- 'a stack trace', () {
- var trace = new Trace.current();
- return captureFuture(() {
- inMicrotask(() => trackedErrorFuture(trace));
- }).then((chain) {
- 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()));
-
- // The second trace is the trace that was captured when [Chain.track]
- // was called.
- expect(chain.traces[1].frames.first,
- frameMember(startsWith('trackedErrorFuture')));
-
- // The third trace is the automatically-captured trace from when the
- // microtask was scheduled.
- expect(chain.traces[2].frames,
- contains(frameMember(startsWith('inMicrotask'))));
- });
- });
-
- test('associates the current chain with a manually-reported exception with '
- 'no stack trace', () {
- return captureFuture(() {
- inMicrotask(() => trackedErrorFuture());
- }).then((chain) {
- expect(chain.traces, hasLength(3));
-
- // The first trace is the one captured by
- // [StackZoneSpecification.trackFuture], which should contain only
- // stack_trace and dart: frames.
- expect(chain.traces.first.frames,
- everyElement(frameLibrary(isNot(contains('chain_test')))));
-
- expect(chain.traces[1].frames.first,
- frameMember(startsWith('trackedErrorFuture')));
- expect(chain.traces[2].frames,
- contains(frameMember(startsWith('inMicrotask'))));
- });
- });
-
test('forwards the future value within Chain.capture()', () {
Chain.capture(() {
expect(Chain.track(new Future.value('value')),
@@ -598,36 +652,6 @@
});
group('Chain.track(Stream)', () {
- test('associates the current chain with a manually-reported exception with '
- 'a stack trace', () {
- var trace = new Trace.current();
- return captureFuture(() {
- inMicrotask(() => trackedErrorStream(trace).listen(null));
- }).then((chain) {
- expect(chain.traces, hasLength(3));
- expect(chain.traces.first.toString(), equals(trace.toString()));
- expect(chain.traces[1].frames.first,
- frameMember(startsWith('trackedErrorStream')));
- expect(chain.traces[2].frames,
- contains(frameMember(startsWith('inMicrotask'))));
- });
- });
-
- test('associates the current chain with a manually-reported exception with '
- 'no stack trace', () {
- return captureFuture(() {
- inMicrotask(() => trackedErrorStream().listen(null));
- }).then((chain) {
- expect(chain.traces, hasLength(3));
- expect(chain.traces.first.frames,
- everyElement(frameLibrary(isNot(contains('chain_test')))));
- expect(chain.traces[1].frames.first,
- frameMember(startsWith('trackedErrorStream')));
- expect(chain.traces[2].frames,
- contains(frameMember(startsWith('inMicrotask'))));
- });
- });
-
test('forwards stream values within Chain.capture()', () {
Chain.capture(() {
var controller = new StreamController()
@@ -692,22 +716,30 @@
.then((_) => new Future(() {}));
}
-/// Returns a Future that completes to an error and is wrapped in [Chain.track].
-///
-/// If [trace] is passed, it's used as the stack trace for the error.
-Future trackedErrorFuture([StackTrace trace]) {
- var completer = new Completer();
- completer.completeError('error', trace);
- return Chain.track(completer.future);
+void inNewFuture(callback()) {
+ new Future(callback);
}
-/// Returns a Stream that emits an error and is wrapped in [Chain.track].
+void inSyncFuture(callback()) {
+ new Future.sync(callback);
+}
+
+/// Returns a Future that completes to an error using a completer.
///
/// If [trace] is passed, it's used as the stack trace for the error.
-Stream trackedErrorStream([StackTrace trace]) {
+Future completerErrorFuture([StackTrace trace]) {
+ var completer = new Completer();
+ completer.completeError('error', trace);
+ return completer.future;
+}
+
+/// Returns a Stream that emits an error using a controller.
+///
+/// If [trace] is passed, it's used as the stack trace for the error.
+Stream controllerErrorStream([StackTrace trace]) {
var controller = new StreamController();
controller.addError('error', trace);
- return Chain.track(controller.stream);
+ return controller.stream;
}
/// Runs [callback] within [asyncFn], then converts any errors raised into a