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

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

Issue 10441104: New expectation functions plus convert old tests to use these. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 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
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) {
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) => 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) => 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) => 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) => 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) => 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 /** Returns a matcher that matches if two objects are equal (==). */
82 Matcher equals(expected) => new _IsEqual(expected);
83
84 class _IsEqual extends BaseMatcher {
85 final _expected;
86 const _IsEqual(this._expected);
87 bool matches(item) => item == _expected;
88 Description describe(Description description) =>
89 description.addDescriptionOf(_expected);
90 }
91
92 /** A matcher that matches any value. */
93 final Matcher anything = const _IsAnything();
94
95 class _IsAnything extends BaseMatcher {
96 const _IsAnything();
97 bool matches(item) => true;
98 Description describe(Description description) =>
99 description.add('anything');
100 }
101
102 /**
103 * A matcher that matches functions that throw exceptions when called.
104 * The value passed to expect() should be a reference to the function.
105 * Note that the function cannot take arguments; to handle this
106 * a wrapper will have to be created.
107 * The function will be called once upon success, or twice upon failure
108 * (the second time to get the failure description).
109 */
110 final Matcher throws = const _Throws();
111
112 /**
113 * Returns a matcher that matches a function call against an exception,
114 * which is in turn constrained by a [matcher].
115 * The value passed to expect() should be a reference to the function.
116 * Note that the function cannot take arguments; to handle this
117 * a wrapper will have to be created.
118 * The function will be called once upon success, or twice upon failure
119 * (the second time to get the failure description).
120 */
121 Matcher throwsA(Matcher matcher) => new _Throws(matcher);
122
123 /**
124 * A matcher that matches a function call against no exception.
125 * The function will be called once. Any exceptions will be silently swallowed.
126 * The value passed to expect() should be a reference to the function.
127 * Note that the function cannot take arguments; to handle this
128 * a wrapper will have to be created.
129 */
130 final Matcher returnsNormally = const _ReturnsNormally();
131
132 class _Throws extends BaseMatcher {
133 final Matcher _matcher;
134
135 const _Throws([Matcher this._matcher = null]);
136
137 bool matches(item) {
138 try {
139 item();
140 return false;
141 } catch (final e) {
142 return _matcher == null || _matcher.matches(e);
143 }
144 }
145
146 Description describe(Description description) {
147 if (_matcher == null) {
148 return description.add("throws an exception");
149 } else {
150 return description.add('throws an exception which matches ').
151 addDescriptionOf(_matcher);
152 }
153 }
154
155 Description describeMismatch(item, Description mismatchDescription) {
156 try {
157 item();
158 return mismatchDescription.add(' no exception');
159 } catch (final e) {
160 return mismatchDescription.add(' exception does not match ').
161 addDescriptionOf(_matcher);
162 }
163 }
164 }
165
166 class _ReturnsNormally extends BaseMatcher {
167
168 const _ReturnsNormally();
169
170 bool matches(f) {
171 try {
172 f();
173 return true;
174 } catch (final e) {
175 return false;
176 }
177 }
178
179 Description describe(Description description) =>
180 description.add("return normally");
181
182 Description describeMismatch(item, Description mismatchDescription) {
183 return mismatchDescription.add(' threw exception');
184 }
185 }
186
187 /**
188 * Returns a matcher that matches if an object is an instance
189 * of [type] (or a subtype).
190 *
191 * As types are not first class objects in Dart we can only
192 * approximate this test by using a generic wrapper class.
193 *
194 * For example, to test whether 'bar' is an instance of type
195 * 'Foo', we would write:
196 *
197 * expect(bar, new isInstanceOf<Foo>());
198 *
199 * To get better error message, supply a name when creating the
200 * Type wrapper; e.g.:
201 *
202 * expect(bar, new isInstanceOf<Foo>('Foo'));
203 */
204 class isInstanceOf<T> extends BaseMatcher {
205 final String _name;
206 const isInstanceOf([this._name = 'specified type']);
207 bool matches(obj) => obj is T;
208 // The description here is lame :-(
209 Description describe(Description description) =>
210 description.add('an instance of ${_name}');
211 }
212
213 /**
214 * Returns a matcher that matches if an object has a length property
215 * that matches [matcher].
216 */
217 Matcher hasLength(Matcher matcher) =>
218 new _HasLength(wrapMatcher(matcher));
219
220 class _HasLength extends BaseMatcher {
221 final Matcher _matcher;
222 const _HasLength([Matcher this._matcher = null]);
223
224 bool matches(item) {
225 return _matcher.matches(item.length);
226 }
227
228 Description describe(Description description) =>
229 description.add('an object with length of ').
230 addDescriptionOf(_matcher);
231
232 Description describeMismatch(item, Description mismatchDescription) {
233 super.describeMismatch(item, mismatchDescription);
234 try {
235 if (item.length * item.length >= 0) { // hack to will throw if no length
Bob Nystrom 2012/06/04 20:04:09 This comment no grammar. Why is this hack needed?
gram 2012/06/05 16:25:46 Basically we want to generate a different descript
Bob Nystrom 2012/06/05 16:50:37 OK. Can you move that comment to the preceding lin
gram 2012/06/05 16:59:53 Was done already :-) On 2012/06/05 16:50:37, Bob N
236 return mismatchDescription.add(' with length of ').
237 addDescriptionOf(item.length);
238 }
239 } catch (var e) {
240 return mismatchDescription.add(' has no length property');
241 }
242 }
243 }
244
245 /**
246 * Returns a matcher that does a deep recursive match. This only works
247 * with scalars, Maps and Iterables. To handle cyclic structures an
248 * item [limit] can be provided; if after [limit] items have been
249 * compared and the process is not complete this will be treated as
250 * a mismatch. The default limit is 1000.
251 */
252 Matcher recursivelyMatches(expected, [limit=1000]) =>
253 new _DeepMatcher(expected, limit);
254
255 // A utility function for comparing iterators
256
257 String _compareIterables(expected, actual, matcher) {
258 if (actual is !Iterable) {
259 return 'is not Iterable';
260 }
261 var expectedIterator = expected.iterator();
262 var actualIterator = actual.iterator();
263 var position = 0;
264 String reason = null;
265 while (reason == null) {
266 if (expectedIterator.hasNext()) {
267 if (actualIterator.hasNext()) {
268 reason = matcher(expectedIterator.next(),
269 actualIterator.next(),
270 'mismatch at position ${position}');
271 ++position;
272 } else {
273 reason = 'shorter than expected';
274 }
275 } else if (actualIterator.hasNext()) {
276 reason = 'longer than expected';
277 } else {
278 return null;
279 }
280 }
281 return reason;
282 }
283
284 class _DeepMatcher extends BaseMatcher {
285 final _expected;
286 final int _limit;
287 var count;
288
289 _DeepMatcher(this._expected, [this._limit = 1000]);
290
291 String _recursiveMatch(expected, actual, String location) {
292 String reason = null;
293 if (++count >= _limit) {
294 reason = 'item comparison limit exceeded';
295 } else if (expected is Iterable) {
296 reason = _compareIterables(expected, actual, _recursiveMatch);
297 } else if (expected is Map) {
298 if (actual is !Map) {
299 reason = 'expected a map';
300 } else if (expected.length != actual.length) {
301 reason = 'different map lengths';
302 } else {
303 for (var key in expected.getKeys()) {
304 if (!actual.containsKey(key)) {
305 reason = 'missing map key ${key}';
306 break;
307 }
308 reason = _recursiveMatch(expected[key], actual[key],
309 'with key ${key} ${location}');
310 if (reason != null) {
311 break;
312 }
313 }
314 }
315 } else if (expected != actual) {
316 reason = 'expected ${expected} but got ${actual}';
317 }
318 if (reason == null) {
319 return null;
320 } else {
321 return '${reason} ${location}';
322 }
323 }
324
325 String _match(expected, actual) {
326 count = 0;
327 return _recursiveMatch(expected, actual, '');
328 }
329
330 bool matches(item) => _match(_expected, item) == null;
331
332 Description describe(Description description) =>
333 description.add('recursively matches ').addDescriptionOf(_expected);
334
335 Description describeMismatch(item, Description mismatchDescription) =>
336 mismatchDescription.add(_match(_expected, item));
337 }
338
339 /**
340 * Returns a matcher that matches if the match argument contains
341 * the expected value. For [String]s this means substring matching;
342 * for [Map]s is means the map has the key, and for [Collection]s it
343 * means the collection has a matching element. In the case of collections,
344 * [expected] can itself be a matcher.
345 */
346 Matcher contains(expected) => new _Contains(expected);
347
348 class _Contains extends BaseMatcher {
349
350 final _expected;
351
352 const _Contains(this._expected);
353
354 bool matches(item) {
355 if (item is String) {
356 return item.indexOf(_expected) >= 0;
357 } else if (item is Collection) {
358 if (_expected is Matcher) {
359 return item.some((e) => _expected.matches(e));
360 } else {
361 return item.some((e) => e == _expected);
362 }
363 } else if (item is Map) {
364 return item.containsKey(_expected);
365 }
366 return false;
367 }
368
369 Description describe(Description description) =>
370 description.add('contains ').addDescriptionOf(_expected);
371 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698