Handle CustomMatcher errors better (#50)
Fixes #25
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9527173..f36f40d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.12.1+1
+
+* Produce a better error message when a `CustomMatcher`'s feature throws.
+
## 0.12.1
* Add containsAllInOrder matcher for Iterables
diff --git a/lib/src/core_matchers.dart b/lib/src/core_matchers.dart
index c75739d..77782df 100644
--- a/lib/src/core_matchers.dart
+++ b/lib/src/core_matchers.dart
@@ -2,6 +2,8 @@
// 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.
+import 'package:stack_trace/stack_trace.dart';
+
import 'description.dart';
import 'interfaces.dart';
import 'util.dart';
@@ -261,7 +263,7 @@
Description describeMismatch(
item, Description mismatchDescription, Map matchState, bool verbose) {
- var reason = matchState['reason'];
+ var reason = matchState['reason'] ?? '';
// If we didn't get a good reason, that would normally be a
// simple 'is <value>' message. We only add that if the mismatch
// description is non empty (so we are supplementing the mismatch
@@ -614,9 +616,23 @@
featureValueOf(actual) => actual;
bool matches(item, Map matchState) {
- var f = featureValueOf(item);
- if (_matcher.matches(f, matchState)) return true;
- addStateInfo(matchState, {'feature': f});
+ try {
+ var f = featureValueOf(item);
+ if (_matcher.matches(f, matchState)) return true;
+ addStateInfo(matchState, {'custom.feature': f});
+ } catch (exception, stack) {
+ addStateInfo(matchState, {
+ 'custom.exception': exception.toString(),
+ 'custom.stack': new Chain.forTrace(stack)
+ .foldFrames(
+ (frame) =>
+ frame.package == 'test' ||
+ frame.package == 'stream_channel' ||
+ frame.package == 'matcher',
+ terse: true)
+ .toString()
+ });
+ }
return false;
}
@@ -625,14 +641,25 @@
Description describeMismatch(
item, Description mismatchDescription, Map matchState, bool verbose) {
+ if (matchState['custom.exception'] != null) {
+ mismatchDescription
+ .add('threw ')
+ .addDescriptionOf(matchState['custom.exception'])
+ .add('\n')
+ .add(matchState['custom.stack'].toString());
+ return mismatchDescription;
+ }
+
mismatchDescription
.add('has ')
.add(_featureName)
.add(' with value ')
- .addDescriptionOf(matchState['feature']);
+ .addDescriptionOf(matchState['custom.feature']);
var innerDescription = new StringDescription();
- _matcher.describeMismatch(
- matchState['feature'], innerDescription, matchState['state'], verbose);
+
+ _matcher.describeMismatch(matchState['custom.feature'], innerDescription,
+ matchState['state'], verbose);
+
if (innerDescription.length > 0) {
mismatchDescription.add(' which ').add(innerDescription.toString());
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 4bb9887..2a5ba6b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: matcher
-version: 0.12.1
+version: 0.12.1+1
author: Dart Team <misc@dartlang.org>
description: Support for specifying test expectations
homepage: https://github.com/dart-lang/matcher
diff --git a/test/core_matchers_test.dart b/test/core_matchers_test.dart
index a31391b..04cc111 100644
--- a/test/core_matchers_test.dart
+++ b/test/core_matchers_test.dart
@@ -7,6 +7,11 @@
import 'test_utils.dart';
+class BadCustomMatcher extends CustomMatcher {
+ BadCustomMatcher() : super("feature", "description", {1: "a"});
+ featureValueOf(actual) => throw new Exception("bang");
+}
+
void main() {
test('isTrue', () {
shouldPass(true, isTrue);
@@ -236,4 +241,17 @@
"Which: has price with value <10> which is not "
"a value greater than <10>");
});
+
+ test("Custom Matcher Exception", () {
+ shouldFail(
+ "a",
+ new BadCustomMatcher(),
+ allOf([
+ contains("Expected: feature {1: 'a'} "),
+ contains("Actual: 'a' "),
+ contains("Which: threw 'Exception: bang' "),
+ contains("test/core_matchers_test.dart "),
+ contains("package:test ")
+ ]));
+ });
}