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

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

Issue 10836241: Move unittest from lib to pkg. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 4 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 | « lib/unittest/config.dart ('k') | lib/unittest/coverage_controller.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5
6 /**
7 * Returns a matcher that matches empty strings, maps or collections.
8 */
9 final Matcher isEmpty = const _Empty();
10
11 class _Empty extends BaseMatcher {
12 const _Empty();
13 bool matches(item, MatchState matchState) {
14 if (item is Map || item is Collection) {
15 return item.isEmpty();
16 } else if (item is String) {
17 return item.length == 0;
18 } else {
19 return false;
20 }
21 }
22 Description describe(Description description) =>
23 description.add('empty');
24 }
25
26 /** A matcher that matches any null value. */
27 final Matcher isNull = const _IsNull();
28
29 /** A matcher that matches any non-null value. */
30 final Matcher isNotNull = const _IsNotNull();
31
32 class _IsNull extends BaseMatcher {
33 const _IsNull();
34 bool matches(item, MatchState matchState) => item == null;
35 Description describe(Description description) =>
36 description.add('null');
37 }
38
39 class _IsNotNull extends BaseMatcher {
40 const _IsNotNull();
41 bool matches(item, MatchState matchState) => item != null;
42 Description describe(Description description) =>
43 description.add('not null');
44 }
45
46 /** A matcher that matches the Boolean value true. */
47 final Matcher isTrue = const _IsTrue();
48
49 /** A matcher that matches anything except the Boolean value true. */
50 final Matcher isFalse = const _IsFalse();
51
52 class _IsTrue extends BaseMatcher {
53 const _IsTrue();
54 bool matches(item, MatchState matchState) => item == true;
55 Description describe(Description description) =>
56 description.add('true');
57 }
58
59 class _IsFalse extends BaseMatcher {
60 const _IsFalse();
61 bool matches(item, MatchState matchState) => item != true;
62 Description describe(Description description) =>
63 description.add('false');
64 }
65
66 /**
67 * Returns a matches that matches if the value is the same instance
68 * as [object] (`===`).
69 */
70 Matcher same(expected) => new _IsSameAs(expected);
71
72 class _IsSameAs extends BaseMatcher {
73 final _expected;
74 const _IsSameAs(this._expected);
75 bool matches(item, MatchState matchState) => item === _expected;
76 // If all types were hashable we could show a hash here.
77 Description describe(Description description) =>
78 description.add('same instance as ').addDescriptionOf(_expected);
79 }
80
81 /**
82 * Returns a matcher that does a deep recursive match. This only works
83 * with scalars, Maps and Iterables. To handle cyclic structures a
84 * recursion depth [limit] can be provided. The default limit is 100.
85 */
86 Matcher equals(expected, [limit=100]) =>
87 new _DeepMatcher(expected, limit);
88
89 class _DeepMatcher extends BaseMatcher {
90 final _expected;
91 final int _limit;
92 var count;
93
94 _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit;
95
96 String _compareIterables(expected, actual, matcher, depth) {
97 if (actual is !Iterable) {
98 return 'is not Iterable';
99 }
100 var expectedIterator = expected.iterator();
101 var actualIterator = actual.iterator();
102 var position = 0;
103 String reason = null;
104 while (reason == null) {
105 if (expectedIterator.hasNext()) {
106 if (actualIterator.hasNext()) {
107 Description r = matcher(expectedIterator.next(),
108 actualIterator.next(),
109 'mismatch at position ${position}',
110 depth);
111 if (r != null) reason = r.toString();
112 ++position;
113 } else {
114 reason = 'shorter than expected';
115 }
116 } else if (actualIterator.hasNext()) {
117 reason = 'longer than expected';
118 } else {
119 return null;
120 }
121 }
122 return reason;
123 }
124
125 Description _recursiveMatch(expected, actual, String location, int depth) {
126 Description reason = null;
127 // If _limit is 1 we can only recurse one level into object.
128 bool canRecurse = depth == 0 || _limit > 1;
129 if (expected == actual) {
130 // Do nothing.
131 } else if (depth > _limit) {
132 reason = new StringDescription('recursion depth limit exceeded');
133 } else {
134 if (expected is Iterable && canRecurse) {
135 String r = _compareIterables(expected, actual,
136 _recursiveMatch, depth+1);
137 if (r != null) reason = new StringDescription(r);
138 } else if (expected is Map && canRecurse) {
139 if (actual is !Map) {
140 reason = new StringDescription('expected a map');
141 } else if (expected.length != actual.length) {
142 reason = new StringDescription('different map lengths');
143 } else {
144 for (var key in expected.getKeys()) {
145 if (!actual.containsKey(key)) {
146 reason = new StringDescription('missing map key ');
147 reason.addDescriptionOf(key);
148 break;
149 }
150 reason = _recursiveMatch(expected[key], actual[key],
151 'with key <${key}> ${location}', depth+1);
152 if (reason != null) {
153 break;
154 }
155 }
156 }
157 } else {
158 // If we have recursed, show the expected value too; if not,
159 // expect() will show it for us.
160 reason = new StringDescription();
161 if (depth > 1) {
162 reason.add('expected ').addDescriptionOf(expected).add(' but was ').
163 addDescriptionOf(actual);
164 } else {
165 reason.add('was ').addDescriptionOf(actual);
166 }
167 }
168 }
169 if (reason != null && location.length > 0) {
170 reason.add(' ').add(location);
171 }
172 return reason;
173 }
174
175 String _match(expected, actual) {
176 Description reason = _recursiveMatch(expected, actual, '', 0);
177 return reason == null ? null : reason.toString();
178 }
179
180 // TODO(gram) - see if we can make use of matchState here to avoid
181 // recursing again in describeMismatch.
182 bool matches(item, MatchState matchState) => _match(_expected, item) == null;
183
184 Description describe(Description description) =>
185 description.addDescriptionOf(_expected);
186
187 Description describeMismatch(item, Description mismatchDescription,
188 MatchState matchState, bool verbose) =>
189 mismatchDescription.add(_match(_expected, item));
190 }
191
192 /** A matcher that matches any value. */
193 final Matcher anything = const _IsAnything();
194
195 class _IsAnything extends BaseMatcher {
196 const _IsAnything();
197 bool matches(item, MatchState matchState) => true;
198 Description describe(Description description) =>
199 description.add('anything');
200 }
201
202 /**
203 * Returns a matcher that matches if an object is an instance
204 * of [type] (or a subtype).
205 *
206 * As types are not first class objects in Dart we can only
207 * approximate this test by using a generic wrapper class.
208 *
209 * For example, to test whether 'bar' is an instance of type
210 * 'Foo', we would write:
211 *
212 * expect(bar, new isInstanceOf<Foo>());
213 *
214 * To get better error message, supply a name when creating the
215 * Type wrapper; e.g.:
216 *
217 * expect(bar, new isInstanceOf<Foo>('Foo'));
218 */
219 class isInstanceOf<T> extends BaseMatcher {
220 final String _name;
221 const isInstanceOf([name = 'specified type']) : this._name = name;
222 bool matches(obj, MatchState matchState) => obj is T;
223 // The description here is lame :-(
224 Description describe(Description description) =>
225 description.add('an instance of ${_name}');
226 }
227
228 /**
229 * This can be used to match two kinds of objects:
230 *
231 * * A [Function] that throws an exception when called. The function cannot
232 * take any arguments. If you want to test that a function expecting
233 * arguments throws, wrap it in another zero-argument function that calls
234 * the one you want to test.
235 *
236 * * A [Future] that completes with an exception. Note that this creates an
237 * asynchronous expectation. The call to `expect()` that includes this will
238 * return immediately and execution will continue. Later, when the future
239 * completes, the actual expectation will run.
240 */
241 final Matcher throws = const _Throws();
242
243 /**
244 * This can be used to match two kinds of objects:
245 *
246 * * A [Function] that throws an exception when called. The function cannot
247 * take any arguments. If you want to test that a function expecting
248 * arguments throws, wrap it in another zero-argument function that calls
249 * the one you want to test.
250 *
251 * * A [Future] that completes with an exception. Note that this creates an
252 * asynchronous expectation. The call to `expect()` that includes this will
253 * return immediately and execution will continue. Later, when the future
254 * completes, the actual expectation will run.
255 *
256 * In both cases, when an exception is thrown, this will test that the exception
257 * object matches [matcher]. If [matcher] is not an instance of [Matcher], it
258 * will implicitly be treated as `equals(matcher)`.
259 */
260 Matcher throwsA(matcher) => new _Throws(wrapMatcher(matcher));
261
262 /**
263 * A matcher that matches a function call against no exception.
264 * The function will be called once. Any exceptions will be silently swallowed.
265 * The value passed to expect() should be a reference to the function.
266 * Note that the function cannot take arguments; to handle this
267 * a wrapper will have to be created.
268 */
269 final Matcher returnsNormally = const _ReturnsNormally();
270
271 class _Throws extends BaseMatcher {
272 final Matcher _matcher;
273
274 const _Throws([Matcher matcher]) :
275 this._matcher = matcher;
276
277 bool matches(item, MatchState matchState) {
278 if (item is Future) {
279 // Queue up an asynchronous expectation that validates when the future
280 // completes.
281 item.onComplete(expectAsync1((future) {
282 if (future.hasValue) {
283 expect(false, reason:
284 "Expected future to fail, but succeeded with '${future.value}'.");
285 } else if (_matcher != null) {
286 var reason;
287 if (future.stackTrace != null) {
288 var stackTrace = future.stackTrace.toString();
289 stackTrace = " ${stackTrace.replaceAll("\n", "\n ")}";
290 reason = "Actual exception trace:\n$stackTrace";
291 }
292 expect(future.exception, _matcher, reason: reason);
293 }
294 }));
295
296 // It hasn't failed yet.
297 return true;
298 }
299
300 try {
301 item();
302 return false;
303 } catch (final e, final s) {
304 if (_matcher == null ||_matcher.matches(e, matchState)) {
305 return true;
306 } else {
307 matchState.state = {
308 'exception' :e,
309 'stack': s
310 };
311 return false;
312 }
313 }
314 }
315
316 Description describe(Description description) {
317 if (_matcher == null) {
318 return description.add("throws an exception");
319 } else {
320 return description.add('throws an exception which matches ').
321 addDescriptionOf(_matcher);
322 }
323 }
324
325 Description describeMismatch(item, Description mismatchDescription,
326 MatchState matchState,
327 bool verbose) {
328 if (_matcher == null || matchState.state == null) {
329 return mismatchDescription.add(' no exception');
330 } else {
331 mismatchDescription.
332 add(' exception ').addDescriptionOf(matchState.state['exception']);
333 if (verbose) {
334 mismatchDescription.add(' at ').
335 add(matchState.state['stack'].toString());
336 }
337 mismatchDescription.add(' does not match ').addDescriptionOf(_matcher);
338 return mismatchDescription;
339 }
340 }
341 }
342
343 class _ReturnsNormally extends BaseMatcher {
344 const _ReturnsNormally();
345
346 bool matches(f, MatchState matchState) {
347 try {
348 f();
349 return true;
350 } catch (final e, final s) {
351 matchState.state = {
352 'exception' : e,
353 'stack': s
354 };
355 return false;
356 }
357 }
358
359 Description describe(Description description) =>
360 description.add("return normally");
361
362 Description describeMismatch(item, Description mismatchDescription,
363 MatchState matchState,
364 bool verbose) {
365 mismatchDescription.add(' threw ').
366 addDescriptionOf(matchState.state['exception']);
367 if (verbose) {
368 mismatchDescription.add(' at ').
369 add(matchState.state['stack'].toString());
370 }
371 return mismatchDescription;
372 }
373 }
374
375 /*
376 * Matchers for different exception types. Ideally we should just be able to
377 * use something like:
378 *
379 * final Matcher throwsException =
380 * const _Throws(const isInstanceOf<Exception>());
381 *
382 * Unfortunately instanceOf is not working with dart2js.
383 *
384 * Alternatively, if static functions could be used in const expressions,
385 * we could use:
386 *
387 * bool _isException(x) => x is Exception;
388 * final Matcher isException = const _Predicate(_isException, "Exception");
389 * final Matcher throwsException = const _Throws(isException);
390 *
391 * But currently using static functions in const expressions is not supported.
392 * For now the only solution for all platforms seems to be separate classes
393 * for each exception type.
394 */
395
396 /* abstract */ class _ExceptionMatcher extends BaseMatcher {
397 final String _name;
398 const _ExceptionMatcher(this._name);
399 Description describe(Description description) =>
400 description.add(_name);
401 }
402
403 /** A matcher for FormatExceptions. */
404 final isFormatException = const _FormatException();
405
406 /** A matcher for functions that throw FormatException */
407 final Matcher throwsFormatException =
408 const _Throws(isFormatException);
409
410 class _FormatException extends _ExceptionMatcher {
411 const _FormatException() : super("FormatException");
412 bool matches(item, MatchState matchState) => item is FormatException;
413 }
414
415 /** A matcher for Exceptions. */
416 final isException = const _Exception();
417
418 /** A matcher for functions that throw Exception */
419 final Matcher throwsException = const _Throws(isException);
420
421 class _Exception extends _ExceptionMatcher {
422 const _Exception() : super("Exception");
423 bool matches(item, MatchState matchState) => item is Exception;
424 }
425
426 /** A matcher for IllegalArgumentExceptions. */
427 final isIllegalArgumentException = const _IllegalArgumentException();
428
429 /** A matcher for functions that throw IllegalArgumentException */
430 final Matcher throwsIllegalArgumentException =
431 const _Throws(isIllegalArgumentException);
432
433 class _IllegalArgumentException extends _ExceptionMatcher {
434 const _IllegalArgumentException() : super("IllegalArgumentException");
435 bool matches(item, MatchState matchState) => item is IllegalArgumentException;
436 }
437
438 /** A matcher for IllegalJSRegExpExceptions. */
439 final isIllegalJSRegExpException = const _IllegalJSRegExpException();
440
441 /** A matcher for functions that throw IllegalJSRegExpException */
442 final Matcher throwsIllegalJSRegExpException =
443 const _Throws(isIllegalJSRegExpException);
444
445 class _IllegalJSRegExpException extends _ExceptionMatcher {
446 const _IllegalJSRegExpException() : super("IllegalJSRegExpException");
447 bool matches(item, MatchState matchState) => item is IllegalJSRegExpException;
448 }
449
450 /** A matcher for IndexOutOfRangeExceptions. */
451 final isIndexOutOfRangeException = const _IndexOutOfRangeException();
452
453 /** A matcher for functions that throw IndexOutOfRangeException */
454 final Matcher throwsIndexOutOfRangeException =
455 const _Throws(isIndexOutOfRangeException);
456
457 class _IndexOutOfRangeException extends _ExceptionMatcher {
458 const _IndexOutOfRangeException() : super("IndexOutOfRangeException");
459 bool matches(item, MatchState matchState) => item is IndexOutOfRangeException;
460 }
461
462 /** A matcher for NoSuchMethodExceptions. */
463 final isNoSuchMethodException = const _NoSuchMethodException();
464
465 /** A matcher for functions that throw NoSuchMethodException */
466 final Matcher throwsNoSuchMethodException =
467 const _Throws(isNoSuchMethodException);
468
469 class _NoSuchMethodException extends _ExceptionMatcher {
470 const _NoSuchMethodException() : super("NoSuchMethodException");
471 bool matches(item, MatchState matchState) => item is NoSuchMethodException;
472 }
473
474 /** A matcher for NotImplementedExceptions. */
475 final isNotImplementedException = const _NotImplementedException();
476
477 /** A matcher for functions that throw Exception */
478 final Matcher throwsNotImplementedException =
479 const _Throws(isNotImplementedException);
480
481 class _NotImplementedException extends _ExceptionMatcher {
482 const _NotImplementedException() : super("NotImplementedException");
483 bool matches(item, MatchState matchState) => item is NotImplementedException;
484 }
485
486 /** A matcher for NullPointerExceptions. */
487 final isNullPointerException = const _NullPointerException();
488
489 /** A matcher for functions that throw NotNullPointerException */
490 final Matcher throwsNullPointerException =
491 const _Throws(isNullPointerException);
492
493 class _NullPointerException extends _ExceptionMatcher {
494 const _NullPointerException() : super("NullPointerException");
495 bool matches(item, MatchState matchState) => item is NullPointerException;
496 }
497
498 /** A matcher for UnsupportedOperationExceptions. */
499 final isUnsupportedOperationException = const _UnsupportedOperationException();
500
501 /** A matcher for functions that throw UnsupportedOperationException */
502 final Matcher throwsUnsupportedOperationException =
503 const _Throws(isUnsupportedOperationException);
504
505 class _UnsupportedOperationException extends _ExceptionMatcher {
506 const _UnsupportedOperationException() :
507 super("UnsupportedOperationException");
508 bool matches(item, MatchState matchState) => item is UnsupportedOperationExcep tion;
509 }
510
511 /**
512 * Returns a matcher that matches if an object has a length property
513 * that matches [matcher].
514 */
515 Matcher hasLength(matcher) =>
516 new _HasLength(wrapMatcher(matcher));
517
518 class _HasLength extends BaseMatcher {
519 final Matcher _matcher;
520 const _HasLength([Matcher matcher = null]) : this._matcher = matcher;
521
522 bool matches(item, MatchState matchState) {
523 return _matcher.matches(item.length, matchState);
524 }
525
526 Description describe(Description description) =>
527 description.add('an object with length of ').
528 addDescriptionOf(_matcher);
529
530 Description describeMismatch(item, Description mismatchDescription,
531 MatchState matchState, bool verbose) {
532 super.describeMismatch(item, mismatchDescription, matchState, verbose);
533 try {
534 // We want to generate a different description if there is no length
535 // property. This is harmless code that will throw if no length property
536 // but subtle enough that an optimizer shouldn't strip it out.
537 if (item.length * item.length >= 0) {
538 return mismatchDescription.add(' with length of ').
539 addDescriptionOf(item.length);
540 }
541 } catch (var e) {
542 return mismatchDescription.add(' has no length property');
543 }
544 }
545 }
546
547 /**
548 * Returns a matcher that matches if the match argument contains
549 * the expected value. For [String]s this means substring matching;
550 * for [Map]s is means the map has the key, and for [Collection]s it
551 * means the collection has a matching element. In the case of collections,
552 * [expected] can itself be a matcher.
553 */
554 Matcher contains(expected) => new _Contains(expected);
555
556 class _Contains extends BaseMatcher {
557
558 final _expected;
559
560 const _Contains(this._expected);
561
562 bool matches(item, MatchState matchState) {
563 if (item is String) {
564 return item.indexOf(_expected) >= 0;
565 } else if (item is Collection) {
566 if (_expected is Matcher) {
567 return item.some((e) => _expected.matches(e, matchState));
568 } else {
569 return item.some((e) => e == _expected);
570 }
571 } else if (item is Map) {
572 return item.containsKey(_expected);
573 }
574 return false;
575 }
576
577 Description describe(Description description) =>
578 description.add('contains ').addDescriptionOf(_expected);
579 }
580
581 /**
582 * Returns a matcher that matches if the match argument is in
583 * the expected value. This is the converse of [contains].
584 */
585 Matcher isIn(expected) => new _In(expected);
586
587 class _In extends BaseMatcher {
588
589 final _expected;
590
591 const _In(this._expected);
592
593 bool matches(item, MatchState matchState) {
594 if (_expected is String) {
595 return _expected.indexOf(item) >= 0;
596 } else if (_expected is Collection) {
597 return _expected.some((e) => e == item);
598 } else if (_expected is Map) {
599 return _expected.containsKey(item);
600 }
601 return false;
602 }
603
604 Description describe(Description description) =>
605 description.add('is in ').addDescriptionOf(_expected);
606 }
607
608 /**
609 * Returns a matcher that uses an arbitrary function that returns
610 * true or false for the actual value.
611 */
612 Matcher predicate(f, [description = 'satisfies function']) =>
613 new _Predicate(f, description);
614
615 class _Predicate extends BaseMatcher {
616
617 final _matcher;
618 final String _description;
619
620 const _Predicate(this._matcher, this._description);
621
622 bool matches(item, MatchState matchState) => _matcher(item);
623
624 Description describe(Description description) =>
625 description.add(_description);
626 }
OLDNEW
« no previous file with comments | « lib/unittest/config.dart ('k') | lib/unittest/coverage_controller.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698