Add an option to Chain.capture() not to create an error zone (#36)
Some users (like test) manually convert traces to chains, so they
don't need the error zone behavior. In fact, it can cause problems,
such as those described in dart-lang/test#713.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0c972a..18ffda8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.9.0
+
+* Add an `errorZone` parameter to `Chain.capture()` that makes it avoid creating
+ an error zone.
+
## 1.8.3
* `Chain.forTrace()` now returns a full stack chain for *all* `StackTrace`s
diff --git a/lib/src/chain.dart b/lib/src/chain.dart
index 56c5333..8685a9e 100644
--- a/lib/src/chain.dart
+++ b/lib/src/chain.dart
@@ -65,13 +65,21 @@
/// parent Zone's `unhandledErrorHandler` will be called with the error and
/// its chain.
///
- /// Note that even if [onError] isn't passed, this zone will still be an error
- /// zone. This means that any errors that would cross the zone boundary are
- /// considered unhandled.
+ /// If [errorZone] is `true`, the zone this creates will be an error zone,
+ /// even if [onError] isn't passed. This means that any errors that would
+ /// cross the zone boundary are considered unhandled. If [errorZone] is
+ /// `false`, [onError] must be `null`.
///
/// If [callback] returns a value, it will be returned by [capture] as well.
static T capture<T>(T callback(),
- {void onError(error, Chain chain), bool when: true}) {
+ {void onError(error, Chain chain),
+ bool when: true,
+ bool errorZone: true}) {
+ if (!errorZone && onError != null) {
+ throw new ArgumentError.value(
+ onError, "onError", "must be null if errorZone is false");
+ }
+
if (!when) {
var newOnError;
if (onError != null) {
@@ -87,7 +95,7 @@
return runZoned(callback, onError: newOnError);
}
- var spec = new StackZoneSpecification(onError);
+ var spec = new StackZoneSpecification(onError, errorZone: errorZone);
return runZoned(() {
try {
return callback();
diff --git a/lib/src/stack_zone_specification.dart b/lib/src/stack_zone_specification.dart
index cd5d068..5be8dd3 100644
--- a/lib/src/stack_zone_specification.dart
+++ b/lib/src/stack_zone_specification.dart
@@ -61,12 +61,16 @@
/// The most recent node of the current stack chain.
_Node _currentNode;
- StackZoneSpecification([this._onError]);
+ /// Whether this is an error zone.
+ final bool _errorZone;
+
+ StackZoneSpecification(this._onError, {bool errorZone: true})
+ : _errorZone = errorZone;
/// Converts [this] to a real [ZoneSpecification].
ZoneSpecification toSpec() {
return new ZoneSpecification(
- handleUncaughtError: _handleUncaughtError,
+ handleUncaughtError: _errorZone ? _handleUncaughtError : null,
registerCallback: _registerCallback,
registerUnaryCallback: _registerUnaryCallback,
registerBinaryCallback: _registerBinaryCallback,
diff --git a/pubspec.yaml b/pubspec.yaml
index 65d3ef6..3218be1 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.8.3
+version: 1.9.0
author: "Dart Team <misc@dartlang.org>"
homepage: https://github.com/dart-lang/stack_trace
description: A package for manipulating stack traces and printing them readably.
diff --git a/test/chain/chain_test.dart b/test/chain/chain_test.dart
index 407cc2b..26b04a1 100644
--- a/test/chain/chain_test.dart
+++ b/test/chain/chain_test.dart
@@ -38,33 +38,65 @@
});
});
- group("Chain.capture() with when: false", () {
- test("with no onError doesn't block errors", () {
- expect(Chain.capture(() => new Future.error("oh no"), when: false),
- throwsA("oh no"));
- });
-
+ group("Chain.capture()", () {
test("with onError blocks errors", () {
Chain.capture(() {
return new Future.error("oh no");
}, onError: expectAsync2((error, chain) {
expect(error, equals("oh no"));
expect(chain, new isInstanceOf<Chain>());
- }), when: false);
+ }));
});
- test("doesn't enable chain-tracking", () {
- return Chain.disable(() {
- return Chain.capture(() {
- var completer = new Completer();
- inMicrotask(() {
- completer.complete(new Chain.current());
- });
+ test("with no onError blocks errors", () {
+ runZoned(() {
+ var future =
+ Chain.capture(() => new Future.error("oh no"), when: false);
+ future.then(expectAsync1((_) {}, count: 0));
+ }, onError: expectAsync2((error, chain) {
+ expect(error, equals("oh no"));
+ expect(chain, new isInstanceOf<Chain>());
+ }));
+ });
- return completer.future.then((chain) {
- expect(chain.traces, hasLength(1));
- });
- }, when: false);
+ test("with errorZone: false doesn't block errors", () {
+ expect(Chain.capture(() => new Future.error("oh no"), errorZone: false),
+ throwsA("oh no"));
+ });
+
+ test("doesn't allow onError and errorZone: false", () {
+ expect(() => Chain.capture(() {}, onError: (_, __) {}, errorZone: false),
+ throwsArgumentError);
+ });
+
+ group("with when: false", () {
+ test("with no onError doesn't block errors", () {
+ expect(Chain.capture(() => new Future.error("oh no"), when: false),
+ throwsA("oh no"));
+ });
+
+ test("with onError blocks errors", () {
+ Chain.capture(() {
+ return new Future.error("oh no");
+ }, onError: expectAsync2((error, chain) {
+ expect(error, equals("oh no"));
+ expect(chain, new isInstanceOf<Chain>());
+ }), when: false);
+ });
+
+ test("doesn't enable chain-tracking", () {
+ return Chain.disable(() {
+ return Chain.capture(() {
+ var completer = new Completer();
+ inMicrotask(() {
+ completer.complete(new Chain.current());
+ });
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(1));
+ });
+ }, when: false);
+ });
});
});
});