Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(97)

Side by Side Diff: pkg/unittest/lib/src/core_matchers.dart

Issue 16408019: Improved error messages from unittest. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/unittest/lib/src/basematcher.dart ('k') | pkg/unittest/lib/src/description.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of matcher; 5 part of matcher;
6 6
7 /** 7 /**
8 * Returns a matcher that matches empty strings, maps or iterables 8 * Returns a matcher that matches empty strings, maps or iterables
9 * (including collections). 9 * (including collections).
10 */ 10 */
11 const Matcher isEmpty = const _Empty(); 11 const Matcher isEmpty = const _Empty();
12 12
13 class _Empty extends BaseMatcher { 13 class _Empty extends BaseMatcher {
14 const _Empty(); 14 const _Empty();
15 bool matches(item, MatchState matchState) { 15 bool matches(item, Map matchState) {
16 if (item is Map || item is Iterable) { 16 if (item is Map || item is Iterable) {
17 return item.isEmpty; 17 return item.isEmpty;
18 } else if (item is String) { 18 } else if (item is String) {
19 return item.length == 0; 19 return item.length == 0;
20 } else { 20 } else {
21 return false; 21 return false;
22 } 22 }
23 } 23 }
24 Description describe(Description description) => 24 Description describe(Description description) =>
25 description.add('empty'); 25 description.add('empty');
26 } 26 }
27 27
28 /** A matcher that matches any null value. */ 28 /** A matcher that matches any null value. */
29 const Matcher isNull = const _IsNull(); 29 const Matcher isNull = const _IsNull();
30 30
31 /** A matcher that matches any non-null value. */ 31 /** A matcher that matches any non-null value. */
32 const Matcher isNotNull = const _IsNotNull(); 32 const Matcher isNotNull = const _IsNotNull();
33 33
34 class _IsNull extends BaseMatcher { 34 class _IsNull extends BaseMatcher {
35 const _IsNull(); 35 const _IsNull();
36 bool matches(item, MatchState matchState) => item == null; 36 bool matches(item, Map matchState) => item == null;
37 Description describe(Description description) => 37 Description describe(Description description) =>
38 description.add('null'); 38 description.add('null');
39 } 39 }
40 40
41 class _IsNotNull extends BaseMatcher { 41 class _IsNotNull extends BaseMatcher {
42 const _IsNotNull(); 42 const _IsNotNull();
43 bool matches(item, MatchState matchState) => item != null; 43 bool matches(item, Map matchState) => item != null;
44 Description describe(Description description) => 44 Description describe(Description description) =>
45 description.add('not null'); 45 description.add('not null');
46 } 46 }
47 47
48 /** A matcher that matches the Boolean value true. */ 48 /** A matcher that matches the Boolean value true. */
49 const Matcher isTrue = const _IsTrue(); 49 const Matcher isTrue = const _IsTrue();
50 50
51 /** A matcher that matches anything except the Boolean value true. */ 51 /** A matcher that matches anything except the Boolean value true. */
52 const Matcher isFalse = const _IsFalse(); 52 const Matcher isFalse = const _IsFalse();
53 53
54 class _IsTrue extends BaseMatcher { 54 class _IsTrue extends BaseMatcher {
55 const _IsTrue(); 55 const _IsTrue();
56 bool matches(item, MatchState matchState) => item == true; 56 bool matches(item, Map matchState) => item == true;
57 Description describe(Description description) => 57 Description describe(Description description) =>
58 description.add('true'); 58 description.add('true');
59 } 59 }
60 60
61 class _IsFalse extends BaseMatcher { 61 class _IsFalse extends BaseMatcher {
62 const _IsFalse(); 62 const _IsFalse();
63 bool matches(item, MatchState matchState) => item == false; 63 bool matches(item, Map matchState) => item == false;
64 Description describe(Description description) => 64 Description describe(Description description) =>
65 description.add('false'); 65 description.add('false');
66 } 66 }
67 67
68 /** 68 /**
69 * Returns a matches that matches if the value is the same instance 69 * Returns a matches that matches if the value is the same instance
70 * as [object] (`===`). 70 * as [object] (`===`).
71 */ 71 */
72 Matcher same(expected) => new _IsSameAs(expected); 72 Matcher same(expected) => new _IsSameAs(expected);
73 73
74 class _IsSameAs extends BaseMatcher { 74 class _IsSameAs extends BaseMatcher {
75 final _expected; 75 final _expected;
76 const _IsSameAs(this._expected); 76 const _IsSameAs(this._expected);
77 bool matches(item, MatchState matchState) => identical(item, _expected); 77 bool matches(item, Map matchState) => identical(item, _expected);
78 // If all types were hashable we could show a hash here. 78 // If all types were hashable we could show a hash here.
79 Description describe(Description description) => 79 Description describe(Description description) =>
80 description.add('same instance as ').addDescriptionOf(_expected); 80 description.add('same instance as ').addDescriptionOf(_expected);
81 } 81 }
82 82
83 /** 83 /**
84 * Returns a matcher that does a deep recursive match. This only works 84 * Returns a matcher that does a deep recursive match. This only works
85 * with scalars, Maps and Iterables. To handle cyclic structures a 85 * with scalars, Maps and Iterables. To handle cyclic structures a
86 * recursion depth [limit] can be provided. The default limit is 100. 86 * recursion depth [limit] can be provided. The default limit is 100.
87 */ 87 */
88 Matcher equals(expected, [limit=100]) => 88 Matcher equals(expected, [limit=100]) =>
89 expected is String 89 expected is String
90 ? new _StringEqualsMatcher(expected) 90 ? new _StringEqualsMatcher(expected)
91 : new _DeepMatcher(expected, limit); 91 : new _DeepMatcher(expected, limit);
92 92
93 class _DeepMatcher extends BaseMatcher { 93 class _DeepMatcher extends BaseMatcher {
94 final _expected; 94 final _expected;
95 final int _limit; 95 final int _limit;
96 var count; 96 var count;
97 97
98 _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit; 98 _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit;
99 99
100 String _compareIterables(expected, actual, matcher, depth) { 100 // Returns a pair (reason, location)
101 List _compareIterables(expected, actual, matcher, depth, location) {
101 if (actual is !Iterable) { 102 if (actual is !Iterable) {
102 return 'is not Iterable'; 103 return ['is not Iterable', location];
103 } 104 }
104 var expectedIterator = expected.iterator; 105 var expectedIterator = expected.iterator;
105 var actualIterator = actual.iterator; 106 var actualIterator = actual.iterator;
106 var position = 0; 107 var index = 0;
107 String reason = null; 108 while (true) {
108 while (reason == null) {
109 if (expectedIterator.moveNext()) { 109 if (expectedIterator.moveNext()) {
110 var newLocation = '${location}[${index}]';
110 if (actualIterator.moveNext()) { 111 if (actualIterator.moveNext()) {
111 Description r = matcher(expectedIterator.current, 112 var rp = matcher(expectedIterator.current,
112 actualIterator.current, 113 actualIterator.current, newLocation,
113 'mismatch at position ${position}',
114 depth); 114 depth);
115 if (r != null) reason = r.toString(); 115 if (rp != null) return rp;
116 ++position; 116 ++index;
117 } else { 117 } else {
118 reason = 'shorter than expected'; 118 return ['shorter than expected', newLocation];
119 } 119 }
120 } else if (actualIterator.moveNext()) { 120 } else if (actualIterator.moveNext()) {
121 reason = 'longer than expected'; 121 return ['longer than expected', newLocation];
122 } else { 122 } else {
123 return null; 123 return null;
124 } 124 }
125 } 125 }
126 return reason; 126 return null;
127 } 127 }
128 128
129 Description _recursiveMatch(expected, actual, String location, int depth) { 129 List _recursiveMatch(expected, actual, String location, int depth) {
130 Description reason = null; 130 String reason = null;
131 // If _limit is 1 we can only recurse one level into object. 131 // If _limit is 1 we can only recurse one level into object.
132 bool canRecurse = depth == 0 || _limit > 1; 132 bool canRecurse = depth == 0 || _limit > 1;
133 if (expected == actual) { 133 if (expected == actual) {
134 // Do nothing. 134 // Do nothing.
135 } else if (depth > _limit) { 135 } else if (depth > _limit) {
136 reason = new StringDescription('recursion depth limit exceeded'); 136 reason = 'recursion depth limit exceeded';
137 } else { 137 } else {
138 if (expected is Iterable && canRecurse) { 138 if (expected is Iterable && canRecurse) {
139 String r = _compareIterables(expected, actual, 139 List result = _compareIterables(expected, actual,
140 _recursiveMatch, depth+1); 140 _recursiveMatch, depth + 1, location);
141 if (r != null) reason = new StringDescription(r); 141 if (result != null) {
142 reason = result[0];
143 location = result[1];
144 }
142 } else if (expected is Map && canRecurse) { 145 } else if (expected is Map && canRecurse) {
143 if (actual is !Map) { 146 if (actual is !Map) {
144 reason = new StringDescription('expected a map'); 147 reason = 'expected a map';
145 } else { 148 } else {
146 var err = (expected.length == actual.length) ? '' : 149 var err = (expected.length == actual.length) ? '' :
147 'different map lengths; '; 150 'has different length and ';
148 for (var key in expected.keys) { 151 for (var key in expected.keys) {
149 if (!actual.containsKey(key)) { 152 if (!actual.containsKey(key)) {
150 reason = new StringDescription(err); 153 reason = '${err}is missing map key \'$key\'';
151 reason.add('missing map key ');
152 reason.addDescriptionOf(key);
153 break; 154 break;
154 } 155 }
155 } 156 }
156 if (reason == null) { 157 if (reason == null) {
157 for (var key in actual.keys) { 158 for (var key in actual.keys) {
158 if (!expected.containsKey(key)) { 159 if (!expected.containsKey(key)) {
159 reason = new StringDescription(err); 160 reason = '${err}has extra map key \'$key\'';
160 reason.add('extra map key ');
161 reason.addDescriptionOf(key);
162 break; 161 break;
163 } 162 }
164 } 163 }
165 if (reason == null) { 164 if (reason == null) {
166 for (var key in expected.keys) { 165 for (var key in expected.keys) {
167 reason = _recursiveMatch(expected[key], actual[key], 166 var rp = _recursiveMatch(expected[key], actual[key],
168 'with key <${key}> ${location}', depth+1); 167 "${location}['${key}']", depth+1);
169 if (reason != null) { 168 if (rp != null) {
169 reason = rp[0];
170 location = rp[1];
170 break; 171 break;
171 } 172 }
172 } 173 }
173 } 174 }
174 } 175 }
175 } 176 }
176 } else { 177 } else {
177 reason = new StringDescription(); 178 var description = new StringDescription();
178 // If we have recursed, show the expected value too; if not, 179 // If we have recursed, show the expected value too; if not,
179 // expect() will show it for us. 180 // expect() will show it for us.
180 if (depth > 0) { 181 if (depth > 0) {
181 reason.add('expected '); 182 description.add('was ').
182 reason.addDescriptionOf(expected).add(' but '); 183 addDescriptionOf(actual).
184 add(' instead of ').
185 addDescriptionOf(expected);
186 reason = description.toString();
187 } else {
188 reason = ''; // We're not adding any value to the actual value.
183 } 189 }
184 reason.add('was ');
185 reason.addDescriptionOf(actual);
186 } 190 }
187 } 191 }
188 if (reason != null && location.length > 0) { 192 if (reason == null) return null;
189 reason.add(' ').add(location); 193 return [reason, location];
194 }
195
196 String _match(expected, actual, Map matchState) {
197 var rp = _recursiveMatch(expected, actual, '', 0);
198 if (rp == null) return null;
199 var reason;
200 if (rp[0].length > 0) {
201 if (rp[1].length > 0) {
202 reason = "${rp[0]} at location ${rp[1]}";
203 } else {
204 reason = rp[0];
205 }
206 } else {
207 reason = '';
190 } 208 }
209 // Cache the failure reason in the matchState.
210 addStateInfo(matchState, {'reason': reason});
191 return reason; 211 return reason;
192 } 212 }
193 213
194 String _match(expected, actual) { 214 bool matches(item, Map matchState) =>
195 Description reason = _recursiveMatch(expected, actual, '', 0); 215 _match(_expected, item, matchState) == null;
196 return reason == null ? null : reason.toString();
197 }
198
199 // TODO(gram) - see if we can make use of matchState here to avoid
200 // recursing again in describeMismatch.
201 bool matches(item, MatchState matchState) => _match(_expected, item) == null;
202 216
203 Description describe(Description description) => 217 Description describe(Description description) =>
204 description.addDescriptionOf(_expected); 218 description.addDescriptionOf(_expected);
205 219
206 Description describeMismatch(item, Description mismatchDescription, 220 Description describeMismatch(item, Description mismatchDescription,
207 MatchState matchState, bool verbose) => 221 Map matchState, bool verbose) {
208 mismatchDescription.add(_match(_expected, item)); 222 var reason = matchState['reason'];
223 // If we didn't get a good reason, that would normally be a
224 // simple 'is <value>' message. We only add that if the mismatch
225 // description is non empty (so we are supplementing the mismatch
226 // description).
227 if (reason.length == 0 && mismatchDescription.length > 0) {
228 mismatchDescription.add('is ').addDescriptionOf(item);
229 } else {
230 mismatchDescription.add(reason);
231 }
232 return mismatchDescription;
233 }
209 } 234 }
210 235
211 /** A special equality matcher for strings. */ 236 /** A special equality matcher for strings. */
212 class _StringEqualsMatcher extends BaseMatcher { 237 class _StringEqualsMatcher extends BaseMatcher {
213 final String _value; 238 final String _value;
214 239
215 _StringEqualsMatcher(this._value); 240 _StringEqualsMatcher(this._value);
216 241
217 bool get showActualValue => true; 242 bool get showActualValue => true;
218 243
219 bool matches(item, MatchState mismatchState) => _value == item; 244 bool matches(item, Map matchState) => _value == item;
220 245
221 Description describe(Description description) => 246 Description describe(Description description) =>
222 description.addDescriptionOf(_value); 247 description.addDescriptionOf(_value);
223 248
224 Description describeMismatch(item, Description mismatchDescription, 249 Description describeMismatch(item, Description mismatchDescription,
225 MatchState matchState, bool verbose) { 250 Map matchState, bool verbose) {
226 if (item is! String) { 251 if (item is! String) {
227 return mismatchDescription.addDescriptionOf(item).add(' not a string'); 252 return mismatchDescription.addDescriptionOf(item).add('is not a string');
228 } else { 253 } else {
229 var buff = new StringBuffer(); 254 var buff = new StringBuffer();
230 buff.write('Strings are not equal.'); 255 buff.write('is different.');
231 var escapedItem = _escape(item); 256 var escapedItem = _escape(item);
232 var escapedValue = _escape(_value); 257 var escapedValue = _escape(_value);
233 int minLength = escapedItem.length < escapedValue.length ? 258 int minLength = escapedItem.length < escapedValue.length ?
234 escapedItem.length : escapedValue.length; 259 escapedItem.length : escapedValue.length;
235 int start; 260 int start;
236 for (start = 0; start < minLength; start++) { 261 for (start = 0; start < minLength; start++) {
237 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) { 262 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
238 break; 263 break;
239 } 264 }
240 } 265 }
241 if (start == minLength) { 266 if (start == minLength) {
242 if (escapedValue.length < escapedItem.length) { 267 if (escapedValue.length < escapedItem.length) {
243 buff.write(' Both strings start the same, but the given value also' 268 buff.write(' Both strings start the same, but the given value also'
244 ' has the following trailing characters: '); 269 ' has the following trailing characters: ');
245 _writeTrailing(buff, escapedItem, escapedValue.length); 270 _writeTrailing(buff, escapedItem, escapedValue.length);
246 } else { 271 } else {
247 buff.write(' Both strings start the same, but the given value is' 272 buff.write(' Both strings start the same, but the given value is'
248 ' missing the following trailing characters: '); 273 ' missing the following trailing characters: ');
249 _writeTrailing(buff, escapedValue, escapedItem.length); 274 _writeTrailing(buff, escapedValue, escapedItem.length);
250 } 275 }
251 } else { 276 } else {
252 buff.write('\nExpected: '); 277 buff.write('\nExpected: ');
253 _writeLeading(buff, escapedValue, start); 278 _writeLeading(buff, escapedValue, start);
254 _writeTrailing(buff, escapedValue, start); 279 _writeTrailing(buff, escapedValue, start);
255 buff.write('\n Actual: '); 280 buff.write('\n Actual: ');
256 _writeLeading(buff, escapedItem, start); 281 _writeLeading(buff, escapedItem, start);
257 _writeTrailing(buff, escapedItem, start); 282 _writeTrailing(buff, escapedItem, start);
258 buff.write('\n '); 283 buff.write('\n ');
259 for (int i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' '); 284 for (int i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' ');
260 buff.write('^\n Differ at position $start'); 285 buff.write('^\n Differ at offset $start');
261 } 286 }
262 287
263 return mismatchDescription.replace(buff.toString()); 288 return mismatchDescription.replace(buff.toString());
264 } 289 }
265 } 290 }
266 291
267 static String _escape(String s) => 292 static String _escape(String s) =>
268 s.replaceAll('\n', '\\n').replaceAll('\r', '\\r').replaceAll('\t', '\\t'); 293 s.replaceAll('\n', '\\n').replaceAll('\r', '\\r').replaceAll('\t', '\\t');
269 294
270 static String _writeLeading(StringBuffer buff, String s, int start) { 295 static String _writeLeading(StringBuffer buff, String s, int start) {
(...skipping 13 matching lines...) Expand all
284 buff.write(' ...'); 309 buff.write(' ...');
285 } 310 }
286 } 311 }
287 } 312 }
288 313
289 /** A matcher that matches any value. */ 314 /** A matcher that matches any value. */
290 const Matcher anything = const _IsAnything(); 315 const Matcher anything = const _IsAnything();
291 316
292 class _IsAnything extends BaseMatcher { 317 class _IsAnything extends BaseMatcher {
293 const _IsAnything(); 318 const _IsAnything();
294 bool matches(item, MatchState matchState) => true; 319 bool matches(item, Map matchState) => true;
295 Description describe(Description description) => 320 Description describe(Description description) =>
296 description.add('anything'); 321 description.add('anything');
297 } 322 }
298 323
299 /** 324 /**
300 * Returns a matcher that matches if an object is an instance 325 * Returns a matcher that matches if an object is an instance
301 * of [type] (or a subtype). 326 * of [type] (or a subtype).
302 * 327 *
303 * As types are not first class objects in Dart we can only 328 * As types are not first class objects in Dart we can only
304 * approximate this test by using a generic wrapper class. 329 * approximate this test by using a generic wrapper class.
305 * 330 *
306 * For example, to test whether 'bar' is an instance of type 331 * For example, to test whether 'bar' is an instance of type
307 * 'Foo', we would write: 332 * 'Foo', we would write:
308 * 333 *
309 * expect(bar, new isInstanceOf<Foo>()); 334 * expect(bar, new isInstanceOf<Foo>());
310 * 335 *
311 * To get better error message, supply a name when creating the 336 * To get better error message, supply a name when creating the
312 * Type wrapper; e.g.: 337 * Type wrapper; e.g.:
313 * 338 *
314 * expect(bar, new isInstanceOf<Foo>('Foo')); 339 * expect(bar, new isInstanceOf<Foo>('Foo'));
315 * 340 *
316 * Note that this does not currently work in dart2js; it will 341 * Note that this does not currently work in dart2js; it will
317 * match any type, and isNot(new isInstanceof<T>()) will always 342 * match any type, and isNot(new isInstanceof<T>()) will always
318 * fail. This is because dart2js currently ignores template type 343 * fail. This is because dart2js currently ignores template type
319 * parameters. 344 * parameters.
320 */ 345 */
321 class isInstanceOf<T> extends BaseMatcher { 346 class isInstanceOf<T> extends BaseMatcher {
322 final String _name; 347 final String _name;
323 const isInstanceOf([name = 'specified type']) : this._name = name; 348 const isInstanceOf([name = 'specified type']) : this._name = name;
324 bool matches(obj, MatchState matchState) => obj is T; 349 bool matches(obj, Map matchState) => obj is T;
325 // The description here is lame :-( 350 // The description here is lame :-(
326 Description describe(Description description) => 351 Description describe(Description description) =>
327 description.add('an instance of ${_name}'); 352 description.add('an instance of ${_name}');
328 } 353 }
329 354
330 /** 355 /**
331 * This can be used to match two kinds of objects: 356 * This can be used to match two kinds of objects:
332 * 357 *
333 * * A [Function] that throws an exception when called. The function cannot 358 * * A [Function] that throws an exception when called. The function cannot
334 * take any arguments. If you want to test that a function expecting 359 * take any arguments. If you want to test that a function expecting
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
369 * a wrapper will have to be created. 394 * a wrapper will have to be created.
370 */ 395 */
371 const Matcher returnsNormally = const _ReturnsNormally(); 396 const Matcher returnsNormally = const _ReturnsNormally();
372 397
373 class Throws extends BaseMatcher { 398 class Throws extends BaseMatcher {
374 final Matcher _matcher; 399 final Matcher _matcher;
375 400
376 const Throws([Matcher matcher]) : 401 const Throws([Matcher matcher]) :
377 this._matcher = matcher; 402 this._matcher = matcher;
378 403
379 bool matches(item, MatchState matchState) { 404 bool matches(item, Map matchState) {
380 if (item is! Function && item is! Future) return false; 405 if (item is! Function && item is! Future) return false;
381 if (item is Future) { 406 if (item is Future) {
382 var done = wrapAsync((fn) => fn()); 407 var done = wrapAsync((fn) => fn());
383 408
384 // Queue up an asynchronous expectation that validates when the future 409 // Queue up an asynchronous expectation that validates when the future
385 // completes. 410 // completes.
386 item.then((value) { 411 item.then((value) {
387 done(() => fail("Expected future to fail, but succeeded with '$value'.") ); 412 done(() => fail("Expected future to fail, but succeeded with '$value'.") );
388 }, onError: (error) { 413 }, onError: (error) {
389 done(() { 414 done(() {
(...skipping 12 matching lines...) Expand all
402 return true; 427 return true;
403 } 428 }
404 429
405 try { 430 try {
406 item(); 431 item();
407 return false; 432 return false;
408 } catch (e, s) { 433 } catch (e, s) {
409 if (_matcher == null ||_matcher.matches(e, matchState)) { 434 if (_matcher == null ||_matcher.matches(e, matchState)) {
410 return true; 435 return true;
411 } else { 436 } else {
412 matchState.state = { 437 addStateInfo(matchState, {'exception': e, 'stack': s});
413 'exception' :e,
414 'stack': s
415 };
416 return false; 438 return false;
417 } 439 }
418 } 440 }
419 } 441 }
420 442
421 Description describe(Description description) { 443 Description describe(Description description) {
422 if (_matcher == null) { 444 if (_matcher == null) {
423 return description.add("throws an exception"); 445 return description.add("throws");
424 } else { 446 } else {
425 return description.add('throws an exception which matches '). 447 return description.add('throws ').addDescriptionOf(_matcher);
426 addDescriptionOf(_matcher);
427 } 448 }
428 } 449 }
429 450
430 Description describeMismatch(item, Description mismatchDescription, 451 Description describeMismatch(item, Description mismatchDescription,
431 MatchState matchState, 452 Map matchState,
432 bool verbose) { 453 bool verbose) {
433 if (item is! Function && item is! Future) { 454 if (item is! Function && item is! Future) {
434 return mismatchDescription.add(' not a Function or Future'); 455 return mismatchDescription.add('is not a Function or Future');
435 } else if (_matcher == null || matchState.state == null) { 456 } else if (_matcher == null || matchState['exception'] == null) {
436 return mismatchDescription.add(' no exception'); 457 return mismatchDescription.add('did not throw');
437 } else { 458 } else {
438 mismatchDescription. 459 mismatchDescription. add('threw ').
439 add(' exception ').addDescriptionOf(matchState.state['exception']); 460 addDescriptionOf(matchState['exception']);
440 if (verbose) { 461 if (verbose) {
441 mismatchDescription.add(' at '). 462 mismatchDescription.add(' at ').add(matchState['stack'].toString());
442 add(matchState.state['stack'].toString());
443 } 463 }
444 mismatchDescription.add(' does not match ').addDescriptionOf(_matcher); 464 return mismatchDescription;
445 return mismatchDescription;
446 } 465 }
447 } 466 }
448 } 467 }
449 468
450 class _ReturnsNormally extends BaseMatcher { 469 class _ReturnsNormally extends BaseMatcher {
451 const _ReturnsNormally(); 470 const _ReturnsNormally();
452 471
453 bool matches(f, MatchState matchState) { 472 bool matches(f, Map matchState) {
454 try { 473 try {
455 f(); 474 f();
456 return true; 475 return true;
457 } catch (e, s) { 476 } catch (e, s) {
458 matchState.state = { 477 addStateInfo(matchState, {'exception': e, 'stack': s});
459 'exception' : e,
460 'stack': s
461 };
462 return false; 478 return false;
463 } 479 }
464 } 480 }
465 481
466 Description describe(Description description) => 482 Description describe(Description description) =>
467 description.add("return normally"); 483 description.add("return normally");
468 484
469 Description describeMismatch(item, Description mismatchDescription, 485 Description describeMismatch(item, Description mismatchDescription,
470 MatchState matchState, 486 Map matchState,
471 bool verbose) { 487 bool verbose) {
472 mismatchDescription.add(' threw '). 488 mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']);
473 addDescriptionOf(matchState.state['exception']); 489 if (verbose) {
474 if (verbose) { 490 mismatchDescription.add(' at ').add(matchState['stack'].toString());
475 mismatchDescription.add(' at '). 491 }
476 add(matchState.state['stack'].toString()); 492 return mismatchDescription;
477 }
478 return mismatchDescription;
479 } 493 }
480 } 494 }
481 495
482 /* 496 /*
483 * Matchers for different exception types. Ideally we should just be able to 497 * Matchers for different exception types. Ideally we should just be able to
484 * use something like: 498 * use something like:
485 * 499 *
486 * final Matcher throwsException = 500 * final Matcher throwsException =
487 * const _Throws(const isInstanceOf<Exception>()); 501 * const _Throws(const isInstanceOf<Exception>());
488 * 502 *
(...skipping 20 matching lines...) Expand all
509 523
510 /** A matcher for FormatExceptions. */ 524 /** A matcher for FormatExceptions. */
511 const isFormatException = const _FormatException(); 525 const isFormatException = const _FormatException();
512 526
513 /** A matcher for functions that throw FormatException. */ 527 /** A matcher for functions that throw FormatException. */
514 const Matcher throwsFormatException = 528 const Matcher throwsFormatException =
515 const Throws(isFormatException); 529 const Throws(isFormatException);
516 530
517 class _FormatException extends TypeMatcher { 531 class _FormatException extends TypeMatcher {
518 const _FormatException() : super("FormatException"); 532 const _FormatException() : super("FormatException");
519 bool matches(item, MatchState matchState) => item is FormatException; 533 bool matches(item, Map matchState) => item is FormatException;
520 } 534 }
521 535
522 /** A matcher for Exceptions. */ 536 /** A matcher for Exceptions. */
523 const isException = const _Exception(); 537 const isException = const _Exception();
524 538
525 /** A matcher for functions that throw Exception. */ 539 /** A matcher for functions that throw Exception. */
526 const Matcher throwsException = const Throws(isException); 540 const Matcher throwsException = const Throws(isException);
527 541
528 class _Exception extends TypeMatcher { 542 class _Exception extends TypeMatcher {
529 const _Exception() : super("Exception"); 543 const _Exception() : super("Exception");
530 bool matches(item, MatchState matchState) => item is Exception; 544 bool matches(item, Map matchState) => item is Exception;
531 } 545 }
532 546
533 /** A matcher for ArgumentErrors. */ 547 /** A matcher for ArgumentErrors. */
534 const isArgumentError = const _ArgumentError(); 548 const isArgumentError = const _ArgumentError();
535 549
536 /** A matcher for functions that throw ArgumentError. */ 550 /** A matcher for functions that throw ArgumentError. */
537 const Matcher throwsArgumentError = 551 const Matcher throwsArgumentError =
538 const Throws(isArgumentError); 552 const Throws(isArgumentError);
539 553
540 class _ArgumentError extends TypeMatcher { 554 class _ArgumentError extends TypeMatcher {
541 const _ArgumentError() : super("ArgumentError"); 555 const _ArgumentError() : super("ArgumentError");
542 bool matches(item, MatchState matchState) => item is ArgumentError; 556 bool matches(item, Map matchState) => item is ArgumentError;
543 } 557 }
544 558
545 /** A matcher for RangeErrors. */ 559 /** A matcher for RangeErrors. */
546 const isRangeError = const _RangeError(); 560 const isRangeError = const _RangeError();
547 561
548 /** A matcher for functions that throw RangeError. */ 562 /** A matcher for functions that throw RangeError. */
549 const Matcher throwsRangeError = 563 const Matcher throwsRangeError =
550 const Throws(isRangeError); 564 const Throws(isRangeError);
551 565
552 class _RangeError extends TypeMatcher { 566 class _RangeError extends TypeMatcher {
553 const _RangeError() : super("RangeError"); 567 const _RangeError() : super("RangeError");
554 bool matches(item, MatchState matchState) => item is RangeError; 568 bool matches(item, Map matchState) => item is RangeError;
555 } 569 }
556 570
557 /** A matcher for NoSuchMethodErrors. */ 571 /** A matcher for NoSuchMethodErrors. */
558 const isNoSuchMethodError = const _NoSuchMethodError(); 572 const isNoSuchMethodError = const _NoSuchMethodError();
559 573
560 /** A matcher for functions that throw NoSuchMethodError. */ 574 /** A matcher for functions that throw NoSuchMethodError. */
561 const Matcher throwsNoSuchMethodError = 575 const Matcher throwsNoSuchMethodError =
562 const Throws(isNoSuchMethodError); 576 const Throws(isNoSuchMethodError);
563 577
564 class _NoSuchMethodError extends TypeMatcher { 578 class _NoSuchMethodError extends TypeMatcher {
565 const _NoSuchMethodError() : super("NoSuchMethodError"); 579 const _NoSuchMethodError() : super("NoSuchMethodError");
566 bool matches(item, MatchState matchState) => item is NoSuchMethodError; 580 bool matches(item, Map matchState) => item is NoSuchMethodError;
567 } 581 }
568 582
569 /** A matcher for UnimplementedErrors. */ 583 /** A matcher for UnimplementedErrors. */
570 const isUnimplementedError = const _UnimplementedError(); 584 const isUnimplementedError = const _UnimplementedError();
571 585
572 /** A matcher for functions that throw Exception. */ 586 /** A matcher for functions that throw Exception. */
573 const Matcher throwsUnimplementedError = 587 const Matcher throwsUnimplementedError =
574 const Throws(isUnimplementedError); 588 const Throws(isUnimplementedError);
575 589
576 class _UnimplementedError extends TypeMatcher { 590 class _UnimplementedError extends TypeMatcher {
577 const _UnimplementedError() : super("UnimplementedError"); 591 const _UnimplementedError() : super("UnimplementedError");
578 bool matches(item, MatchState matchState) => item is UnimplementedError; 592 bool matches(item, Map matchState) => item is UnimplementedError;
579 } 593 }
580 594
581 /** A matcher for UnsupportedError. */ 595 /** A matcher for UnsupportedError. */
582 const isUnsupportedError = const _UnsupportedError(); 596 const isUnsupportedError = const _UnsupportedError();
583 597
584 /** A matcher for functions that throw UnsupportedError. */ 598 /** A matcher for functions that throw UnsupportedError. */
585 const Matcher throwsUnsupportedError = const Throws(isUnsupportedError); 599 const Matcher throwsUnsupportedError = const Throws(isUnsupportedError);
586 600
587 class _UnsupportedError extends TypeMatcher { 601 class _UnsupportedError extends TypeMatcher {
588 const _UnsupportedError() : 602 const _UnsupportedError() :
589 super("UnsupportedError"); 603 super("UnsupportedError");
590 bool matches(item, MatchState matchState) => item is UnsupportedError; 604 bool matches(item, Map matchState) => item is UnsupportedError;
591 } 605 }
592 606
593 /** A matcher for StateErrors. */ 607 /** A matcher for StateErrors. */
594 const isStateError = const _StateError(); 608 const isStateError = const _StateError();
595 609
596 /** A matcher for functions that throw StateError. */ 610 /** A matcher for functions that throw StateError. */
597 const Matcher throwsStateError = 611 const Matcher throwsStateError =
598 const Throws(isStateError); 612 const Throws(isStateError);
599 613
600 class _StateError extends TypeMatcher { 614 class _StateError extends TypeMatcher {
601 const _StateError() : super("StateError"); 615 const _StateError() : super("StateError");
602 bool matches(item, MatchState matchState) => item is StateError; 616 bool matches(item, Map matchState) => item is StateError;
603 } 617 }
604 618
605 619
606 /** A matcher for Map types. */ 620 /** A matcher for Map types. */
607 const isMap = const _IsMap(); 621 const isMap = const _IsMap();
608 622
609 class _IsMap extends TypeMatcher { 623 class _IsMap extends TypeMatcher {
610 const _IsMap() : super("Map"); 624 const _IsMap() : super("Map");
611 bool matches(item, MatchState matchState) => item is Map; 625 bool matches(item, Map matchState) => item is Map;
612 } 626 }
613 627
614 /** A matcher for List types. */ 628 /** A matcher for List types. */
615 const isList = const _IsList(); 629 const isList = const _IsList();
616 630
617 class _IsList extends TypeMatcher { 631 class _IsList extends TypeMatcher {
618 const _IsList() : super("List"); 632 const _IsList() : super("List");
619 bool matches(item, MatchState matchState) => item is List; 633 bool matches(item, Map matchState) => item is List;
620 } 634 }
621 635
622 /** 636 /**
623 * Returns a matcher that matches if an object has a length property 637 * Returns a matcher that matches if an object has a length property
624 * that matches [matcher]. 638 * that matches [matcher].
625 */ 639 */
626 Matcher hasLength(matcher) => 640 Matcher hasLength(matcher) =>
627 new _HasLength(wrapMatcher(matcher)); 641 new _HasLength(wrapMatcher(matcher));
628 642
629 class _HasLength extends BaseMatcher { 643 class _HasLength extends BaseMatcher {
630 final Matcher _matcher; 644 final Matcher _matcher;
631 const _HasLength([Matcher matcher = null]) : this._matcher = matcher; 645 const _HasLength([Matcher matcher = null]) : this._matcher = matcher;
632 646
633 bool matches(item, MatchState matchState) { 647 bool matches(item, Map matchState) {
634 return _matcher.matches(item.length, matchState); 648 try {
649 // This is harmless code that will throw if no length property
650 // but subtle enough that an optimizer shouldn't strip it out.
651 if (item.length * item.length >= 0) {
652 return _matcher.matches(item.length, matchState);
653 }
654 } catch (e) {
655 return false;
656 }
635 } 657 }
636 658
637 Description describe(Description description) => 659 Description describe(Description description) =>
638 description.add('an object with length of '). 660 description.add('an object with length of ').
639 addDescriptionOf(_matcher); 661 addDescriptionOf(_matcher);
640 662
641 Description describeMismatch(item, Description mismatchDescription, 663 Description describeMismatch(item, Description mismatchDescription,
642 MatchState matchState, bool verbose) { 664 Map matchState, bool verbose) {
643 try { 665 try {
644 // We want to generate a different description if there is no length 666 // We want to generate a different description if there is no length
645 // property. This is harmless code that will throw if no length property 667 // property; we use the same trick as in matches().
646 // but subtle enough that an optimizer shouldn't strip it out.
647 if (item.length * item.length >= 0) { 668 if (item.length * item.length >= 0) {
648 return mismatchDescription.add('had length of '). 669 return mismatchDescription.add('has length of ').
649 addDescriptionOf(item.length); 670 addDescriptionOf(item.length);
650 } 671 }
651 } catch (e) { 672 } catch (e) {
652 return mismatchDescription.add('had no length property'); 673 return mismatchDescription.add('has no length property');
653 } 674 }
654 } 675 }
655 } 676 }
656 677
657 /** 678 /**
658 * Returns a matcher that matches if the match argument contains 679 * Returns a matcher that matches if the match argument contains
659 * the expected value. For [String]s this means substring matching; 680 * the expected value. For [String]s this means substring matching;
660 * for [Map]s it means the map has the key, and for [Iterable]s 681 * for [Map]s it means the map has the key, and for [Iterable]s
661 * (including [Iterable]s) it means the iterable has a matching 682 * (including [Iterable]s) it means the iterable has a matching
662 * element. In the case of iterables, [expected] can itself be a 683 * element. In the case of iterables, [expected] can itself be a
663 * matcher. 684 * matcher.
664 */ 685 */
665 Matcher contains(expected) => new _Contains(expected); 686 Matcher contains(expected) => new _Contains(expected);
666 687
667 class _Contains extends BaseMatcher { 688 class _Contains extends BaseMatcher {
668 689
669 final _expected; 690 final _expected;
670 691
671 const _Contains(this._expected); 692 const _Contains(this._expected);
672 693
673 bool matches(item, MatchState matchState) { 694 bool matches(item, Map matchState) {
674 if (item is String) { 695 if (item is String) {
675 return item.indexOf(_expected) >= 0; 696 return item.indexOf(_expected) >= 0;
676 } else if (item is Iterable) { 697 } else if (item is Iterable) {
677 if (_expected is Matcher) { 698 if (_expected is Matcher) {
678 return item.any((e) => _expected.matches(e, matchState)); 699 return item.any((e) => _expected.matches(e, matchState));
679 } else { 700 } else {
680 return item.contains(_expected); 701 return item.contains(_expected);
681 } 702 }
682 } else if (item is Map) { 703 } else if (item is Map) {
683 return item.containsKey(_expected); 704 return item.containsKey(_expected);
684 } 705 }
685 return false; 706 return false;
686 } 707 }
687 708
688 Description describe(Description description) => 709 Description describe(Description description) =>
689 description.add('contains ').addDescriptionOf(_expected); 710 description.add('contains ').addDescriptionOf(_expected);
711
712 Description describeMismatch(item, Description mismatchDescription,
713 Map matchState, bool verbose) {
714 if (item is String || item is Iterable || item is Map) {
715 return super.describeMismatch(item, mismatchDescription, matchState,
716 verbose);
717 } else {
718 return mismatchDescription.add('is not a string, map or iterable');
719 }
720 }
690 } 721 }
691 722
692 /** 723 /**
693 * Returns a matcher that matches if the match argument is in 724 * Returns a matcher that matches if the match argument is in
694 * the expected value. This is the converse of [contains]. 725 * the expected value. This is the converse of [contains].
695 */ 726 */
696 Matcher isIn(expected) => new _In(expected); 727 Matcher isIn(expected) => new _In(expected);
697 728
698 class _In extends BaseMatcher { 729 class _In extends BaseMatcher {
699 730
700 final _expected; 731 final _expected;
701 732
702 const _In(this._expected); 733 const _In(this._expected);
703 734
704 bool matches(item, MatchState matchState) { 735 bool matches(item, Map matchState) {
705 if (_expected is String) { 736 if (_expected is String) {
706 return _expected.indexOf(item) >= 0; 737 return _expected.indexOf(item) >= 0;
707 } else if (_expected is Iterable) { 738 } else if (_expected is Iterable) {
708 return _expected.any((e) => e == item); 739 return _expected.any((e) => e == item);
709 } else if (_expected is Map) { 740 } else if (_expected is Map) {
710 return _expected.containsKey(item); 741 return _expected.containsKey(item);
711 } 742 }
712 return false; 743 return false;
713 } 744 }
714 745
(...skipping 10 matching lines...) Expand all
725 Matcher predicate(Function f, [description ='satisfies function']) => 756 Matcher predicate(Function f, [description ='satisfies function']) =>
726 new _Predicate(f, description); 757 new _Predicate(f, description);
727 758
728 class _Predicate extends BaseMatcher { 759 class _Predicate extends BaseMatcher {
729 760
730 final Function _matcher; 761 final Function _matcher;
731 final String _description; 762 final String _description;
732 763
733 const _Predicate(this._matcher, this._description); 764 const _Predicate(this._matcher, this._description);
734 765
735 bool matches(item, MatchState matchState) => _matcher(item); 766 bool matches(item, Map matchState) => _matcher(item);
736 767
737 Description describe(Description description) => 768 Description describe(Description description) =>
738 description.add(_description); 769 description.add(_description);
739 } 770 }
740 771
741 /** 772 /**
742 * A useful utility class for implementing other matchers through inheritance. 773 * A useful utility class for implementing other matchers through inheritance.
743 * Derived classes should call the base constructor with a feature name and 774 * Derived classes should call the base constructor with a feature name and
744 * description, and an instance matcher, and should implement the 775 * description, and an instance matcher, and should implement the
745 * [featureValueOf] abstract method. 776 * [featureValueOf] abstract method.
(...skipping 17 matching lines...) Expand all
763 final String _featureDescription; 794 final String _featureDescription;
764 final String _featureName; 795 final String _featureName;
765 final Matcher _matcher; 796 final Matcher _matcher;
766 797
767 CustomMatcher(this._featureDescription, this._featureName, matcher) 798 CustomMatcher(this._featureDescription, this._featureName, matcher)
768 : this._matcher = wrapMatcher(matcher); 799 : this._matcher = wrapMatcher(matcher);
769 800
770 /** Override this to extract the interesting feature.*/ 801 /** Override this to extract the interesting feature.*/
771 featureValueOf(actual) => actual; 802 featureValueOf(actual) => actual;
772 803
773 bool matches(item, MatchState matchState) { 804 bool matches(item, Map matchState) {
774 var f = featureValueOf(item); 805 var f = featureValueOf(item);
775 if (_matcher.matches(f, matchState)) return true; 806 if (_matcher.matches(f, matchState)) return true;
776 matchState.state = { 'innerState': matchState.state, 'feature': f }; 807 addStateInfo(matchState, {'feature': f});
777 return false; 808 return false;
778 } 809 }
779 810
780 Description describe(Description description) => 811 Description describe(Description description) =>
781 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); 812 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
782 813
783 Description describeMismatch(item, Description mismatchDescription, 814 Description describeMismatch(item, Description mismatchDescription,
784 MatchState matchState, bool verbose) { 815 Map matchState, bool verbose) {
785 mismatchDescription.add(_featureName).add(' '); 816 mismatchDescription.add('has ').add(_featureName).add(' with value ').
786 _matcher.describeMismatch(matchState.state['feature'], mismatchDescription, 817 addDescriptionOf(matchState['feature']);
787 matchState.state['innerState'], verbose); 818 var innerDescription = new StringDescription();
819 _matcher.describeMismatch(matchState['feature'], innerDescription,
820 matchState['state'], verbose);
821 if (innerDescription.length > 0) {
822 mismatchDescription.add(' which ').add(innerDescription.toString());
823 }
788 return mismatchDescription; 824 return mismatchDescription;
789 } 825 }
790 } 826 }
827
OLDNEW
« no previous file with comments | « pkg/unittest/lib/src/basematcher.dart ('k') | pkg/unittest/lib/src/description.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698