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

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

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

Powered by Google App Engine
This is Rietveld 408576698