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

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 IMatcher isEmpty = const _Empty();
10
11 class _Empty extends Matcher {
12 const _Empty();
13 bool matches(item) {
14 if (item is Map || item is Collection)
Bob Nystrom 2012/06/01 18:22:23 {} for multi-line ifs.
gram 2012/06/01 22:22:00 Done.
15 return item.isEmpty();
16 else if (item is String)
17 return item.length == 0;
18 else
19 return false;
20 }
21 IDescription describe(IDescription description) =>
22 description.add('empty');
23 }
24
25 /** A matcher that matches any null value. */
26 final IMatcher isNull = const _IsNull();
27
28 /** A matcher that matches any non-null value. */
29 final IMatcher isNotNull = const _IsNotNull();
30
31 class _IsNull extends Matcher {
32 const _IsNull();
33 bool matches(item) => item == null;
34 IDescription describe(IDescription description) =>
35 description.add('null');
36 }
37
38 class _IsNotNull extends Matcher {
39 const _IsNotNull();
40 bool matches(item) => item != null;
41 IDescription describe(IDescription description) =>
42 description.add('not null');
43 }
44
45 /** A matcher that matches the Boolean value true. */
46 final IMatcher isTrue = const _IsTrue();
47
48 /** A matcher that matches anything except the Boolean value true. */
49 final IMatcher isFalse = const _IsFalse();
50
51 class _IsTrue extends Matcher {
52 const _IsTrue();
53 bool matches(item) => item == true;
54 IDescription describe(IDescription description) =>
55 description.add('true');
56 }
57
58 class _IsFalse extends Matcher {
59 const _IsFalse();
60 bool matches(item) => item != true;
61 IDescription describe(IDescription description) =>
62 description.add('false');
63 }
64
65 /**
66 * Returns a matches that matches if the value is the same instance
67 * as [object] (`===`).
68 */
69 IMatcher same(expected) => new _IsSameAs(expected);
70
71 class _IsSameAs extends Matcher {
72 final _expected;
73 const _IsSameAs(this._expected);
74 bool matches(item) => item === _expected;
75 // If all types were hashable we could show a hash here.
76 IDescription describe(IDescription description) =>
77 description.add('same instance as ').addDescriptionOf(_expected);
78 }
79
80 /** Returns a matcher that matches if two objects are equal (==). */
81 IMatcher equals(expected) => new _IsEqual(expected);
82
83 class _IsEqual extends Matcher {
84 final _expected;
85 const _IsEqual(this._expected);
86 bool matches(item) => item == _expected;
87 IDescription describe(IDescription description) =>
88 description.addDescriptionOf(_expected);
89 }
90
91 /** A matcher that matches any value. */
92 final IMatcher anything = const _IsAnything();
93
94 class _IsAnything extends Matcher {
95 const _IsAnything();
96 bool matches(item) => true;
97 IDescription describe(IDescription description) =>
98 description.add('anything');
99 }
100
101 /**
102 * A matcher that matches functions that throw exceptions when called.
103 * The value passed to expect() should be a reference to the function.
104 * Note that the function cannot take arguments; to handle this
105 * a wrapper will have to be created.
106 * The function will be called once upon success, or twice upon failure
107 * (the second time to get the failure description).
108 */
109 final IMatcher throws = const _Throws();
110
111 /**
112 * Returns a matcher that matches a function call against an exception,
113 * which is in turn constrained by a [matcher].
114 * The value passed to expect() should be a reference to the function.
115 * Note that the function cannot take arguments; to handle this
116 * a wrapper will have to be created.
117 * The function will be called once upon success, or twice upon failure
118 * (the second time to get the failure description).
119 */
120 IMatcher throwsA(IMatcher matcher) => new _Throws(matcher);
Bob Nystrom 2012/06/01 18:22:23 Nice name. :)
gram 2012/06/01 22:22:00 Done.
121
122 /**
123 * A matcher that matches a function call against no exception.
124 * The function will be called once. Any exceptions will be silently swallowed.
125 * The value passed to expect() should be a reference to the function.
126 * Note that the function cannot take arguments; to handle this
127 * a wrapper will have to be created.
128 */
129 final IMatcher returnsNormally = const _ReturnsNormally();
130
131 class _Throws extends Matcher {
132 final IMatcher _matcher;
133
134 const _Throws([IMatcher this._matcher = null]);
135
136 bool matches(item) {
137 try {
138 item();
139 return false;
140 } catch (final e) {
141 return _matcher == null || _matcher.matches(e);
142 }
143 }
144
145 IDescription describe(IDescription description) {
146 if (_matcher == null) {
147 return description.add("throws an exception");
148 } else {
149 return description.add('throws an exception which matches ').
150 addDescriptionOf(_matcher);
151 }
152 }
153
154 IDescription describeMismatch(item, IDescription mismatchDescription) {
155 try {
156 item();
157 return mismatchDescription.add(' no exception');
158 } catch (final e) {
159 return mismatchDescription.add(' exception does not match ').
160 addDescriptionOf(_matcher);
161 }
162 }
163 }
164
165 class _ReturnsNormally extends Matcher {
166
167 const _ReturnsNormally();
168
169 bool matches(f) {
170 try {
171 f();
172 return true;
173 } catch (final e) {
174 return false;
175 }
176 }
177
178 IDescription describe(IDescription description) =>
179 description.add("return normally");
180
181 IDescription describeMismatch(item, IDescription mismatchDescription) {
182 return mismatchDescription.add(' threw exception');
183 }
184 }
185
186 /**
187 * Returns a matcher that matches if an object is an instance
188 * of [type] (or a subtype).
189 *
190 * As types are not first class objects in Dart we can only
191 * approximate this test by using a generic wrapper class.
192 *
193 * For example, to test whether 'bar' is an instance of type
194 * 'Foo', we would write:
195 *
196 * expect(bar, new isInstanceOf<Foo>());
197 *
198 * To get better error message, supply a name when creating the
199 * Type wrapper; e.g.:
200 *
201 * expect(bar, new isInstanceOf<Foo>('Foo'));
202 */
203 class isInstanceOf<T> extends Matcher {
204 final String _name;
205 const isInstanceOf([this._name = 'specified type']);
206 bool matches(obj) => obj is T;
207 // The description here is lame :-(
208 IDescription describe(IDescription description) =>
209 description.add('an instance of ${_name}');
210 }
211
212 /**
213 * Returns a matcher that matches if an object has a length property
214 * that matches [matcher].
215 */
216 IMatcher hasLength(IMatcher matcher) =>
217 new _HasLength(wrapMatcher(matcher));
218
219 // TODO(gram) - this is intended to fail if the value
220 // does not have a length property but it doesn't. See if this
221 // can be fixed.
222 class _HasLength extends Matcher {
223 final IMatcher _matcher;
224 const _HasLength([IMatcher this._matcher = null]);
225
226 bool matches(item) {
227 try {
228 // TODO(gram): this hack to throw if no length property is not
229 // working
230 if ((item.length * item.length) >= 0) {
231 return _matcher.matches(item.length);
232 }
233 } catch(var e) {
234 return false;
235 }
236 }
237
238 IDescription describe(IDescription description) =>
239 description.add('an object with length of ').
240 addDescriptionOf(_matcher);
241
242 IDescription describeMismatch(item, IDescription mismatchDescription) {
243 super.describeMismatch(item, mismatchDescription);
244 try {
245 // TODO(gram): this hack to throw if no length property is not
246 // working
247 if ((item.length * item.length) >= 0) {
248 return mismatchDescription.add(' with length of ').
249 addDescriptionOf(item.length);
250 }
251 } catch (var e) {
252 return mismatchDescription.add(' has no length property');
253 }
254 }
255 }
256
257 /**
258 * Returns a matcher that does a deep recursive match. This only works
259 * with scalars, Maps and Iterables. To handle cyclic structures an
260 * item [limit] can be provided; if after [limit] items have been
261 * compared and the process is not complete this will be treated as
262 * a mismatch. The default limit is 1000.
263 */
264 IMatcher recursivelyMatches(expected) => new _DeepMatcher(expected);
265
266 class _DeepMatcher extends Matcher {
267 final _expected;
268 final int _limit;
269 var count;
270
271 _DeepMatcher(this._expected, [this._limit = 1000]);
272
273 String _recursiveMatch(expected, actual, String location) {
274 String reason = null;
275 if (++count >= _limit) {
276 return 'item comparison limit exceeded ${location}';
277 } else if (expected is Iterable) {
278 if (actual is !Iterable) {
279 reason = 'expected an Iterable';
280 } else {
281 var expectedIterator = expected.iterator();
282 var actualIterator = actual.iterator();
283 var position = 0;
284 while (reason == null) {
285 if (expectedIterator.hasNext()) {
Bob Nystrom 2012/06/01 18:22:23 collection_matchers has a near identical chunk of
gram 2012/06/01 22:22:00 Done.
286 if (actualIterator.hasNext()) {
287 reason = _recursiveMatch(expectedIterator.next(),
288 actualIterator.next(),
289 'at position ${position} ${location}');
290 if (reason != null) {
291 break;
292 }
293 ++position;
294 } else {
295 reason = 'shorter than expected ${location}';
296 }
297 } else if (actualIterator.hasNext()) {
298 reason = 'longer than expected ${location}';
299 } else {
300 break;
301 }
302 }
303 }
304 } else if (expected is Map) {
305 if (actual is !Map) {
306 reason = 'expected a map';
307 } else if (expected.length != actual.length) {
308 reason = 'different map lengths';
309 } else {
310 for (var key in expected.getKeys()) {
311 if (!actual.containsKey(key)) {
312 reason = 'missing map key ${key}';
313 break;
314 }
315 reason = _recursiveMatch(expected[key], actual[key],
316 'with key ${key} ${location}');
317 if (reason != null) {
318 break;
319 }
320 }
321 }
322 } else if (expected != actual) {
323 reason = 'expected ${expected} but got ${actual}';
324 }
325 if (reason == null) {
326 return null;
327 } else {
328 return '${reason} ${location}';
329 }
330 }
331
332 String _match(expected, actual) {
333 count = 0;
334 return _recursiveMatch(expected, actual, '');
335 }
336
337 bool matches(item) => _match(_expected, item) == null;
338
339 IDescription describe(IDescription description) =>
340 description.add('recursively matches ').addDescriptionOf(_expected);
341
342 IDescription describeMismatch(item, IDescription mismatchDescription) =>
343 mismatchDescription.add(_match(_expected, item));
344 }
345
346 /**
347 * Returns a matcher that matches if the match argument contains
348 * the expected value. For [String]s this means substring matching;
349 * for [Map]s is means the map has the key, and for [Collection]s it
350 * means the collection has a matching element.
351 */
352 IMatcher contains(expected) => new _Contains(expected);
Bob Nystrom 2012/06/01 18:22:23 For the non-string case, can expected be a matcher
gram 2012/06/01 22:22:00 Good idea!
353
354 class _Contains extends Matcher {
355
356 final _expected;
357
358 const _Contains(this._expected);
359
360 bool matches(item) {
361 if (item is String) {
362 return item.indexOf(_expected) >= 0;
363 } else if (item is Collection) {
364 return item.some((e) => e == _expected);
365 } else if (item is Map) {
366 return item.containsKey(_expected);
367 }
368 return false;
369 }
370
371 IDescription describe(IDescription description) =>
372 description.add('contains ').addDescriptionOf(_expected);
373 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698