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

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;
Siggi Cherem (dart-lang) 2012/06/06 00:26:08 FYI - I believe frog fails incorrectly here (retur
gram 2012/06/06 16:23:56 I'll test this. If it fails I'll file a bug agains
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 // We want to generate a different description if there is no length
236 // property. This is harmless code that will throw if no length property
237 // but subtle enough that an optimizer shouldn't strip it out.
238 if (item.length * item.length >= 0) {
239 return mismatchDescription.add(' with length of ').
240 addDescriptionOf(item.length);
241 }
242 } catch (var e) {
243 return mismatchDescription.add(' has no length property');
244 }
245 }
246 }
247
248 /**
249 * Returns a matcher that does a deep recursive match. This only works
250 * with scalars, Maps and Iterables. To handle cyclic structures an
251 * item [limit] can be provided; if after [limit] items have been
252 * compared and the process is not complete this will be treated as
253 * a mismatch. The default limit is 1000.
254 */
255 Matcher recursivelyMatches(expected, [limit=1000]) =>
256 new _DeepMatcher(expected, limit);
257
258 // A utility function for comparing iterators
259
260 String _compareIterables(expected, actual, matcher) {
261 if (actual is !Iterable) {
262 return 'is not Iterable';
263 }
264 var expectedIterator = expected.iterator();
265 var actualIterator = actual.iterator();
266 var position = 0;
267 String reason = null;
268 while (reason == null) {
269 if (expectedIterator.hasNext()) {
270 if (actualIterator.hasNext()) {
271 reason = matcher(expectedIterator.next(),
272 actualIterator.next(),
273 'mismatch at position ${position}');
274 ++position;
275 } else {
276 reason = 'shorter than expected';
277 }
278 } else if (actualIterator.hasNext()) {
279 reason = 'longer than expected';
280 } else {
281 return null;
282 }
283 }
284 return reason;
285 }
286
287 class _DeepMatcher extends BaseMatcher {
288 final _expected;
289 final int _limit;
290 var count;
291
292 _DeepMatcher(this._expected, [this._limit = 1000]);
293
294 String _recursiveMatch(expected, actual, String location) {
295 String reason = null;
296 if (++count >= _limit) {
297 reason = 'item comparison limit exceeded';
298 } else if (expected is Iterable) {
299 reason = _compareIterables(expected, actual, _recursiveMatch);
300 } else if (expected is Map) {
301 if (actual is !Map) {
302 reason = 'expected a map';
303 } else if (expected.length != actual.length) {
304 reason = 'different map lengths';
305 } else {
306 for (var key in expected.getKeys()) {
307 if (!actual.containsKey(key)) {
308 reason = 'missing map key ${key}';
309 break;
310 }
311 reason = _recursiveMatch(expected[key], actual[key],
312 'with key ${key} ${location}');
313 if (reason != null) {
314 break;
315 }
316 }
317 }
318 } else if (expected != actual) {
319 reason = 'expected ${expected} but got ${actual}';
320 }
321 if (reason == null) {
322 return null;
323 } else {
324 return '${reason} ${location}';
325 }
326 }
327
328 String _match(expected, actual) {
329 count = 0;
330 return _recursiveMatch(expected, actual, '');
331 }
332
333 bool matches(item) => _match(_expected, item) == null;
334
335 Description describe(Description description) =>
336 description.add('recursively matches ').addDescriptionOf(_expected);
337
338 Description describeMismatch(item, Description mismatchDescription) =>
339 mismatchDescription.add(_match(_expected, item));
340 }
341
342 /**
343 * Returns a matcher that matches if the match argument contains
344 * the expected value. For [String]s this means substring matching;
345 * for [Map]s is means the map has the key, and for [Collection]s it
346 * means the collection has a matching element. In the case of collections,
347 * [expected] can itself be a matcher.
348 */
349 Matcher contains(expected) => new _Contains(expected);
350
351 class _Contains extends BaseMatcher {
352
353 final _expected;
354
355 const _Contains(this._expected);
356
357 bool matches(item) {
358 if (item is String) {
359 return item.indexOf(_expected) >= 0;
360 } else if (item is Collection) {
361 if (_expected is Matcher) {
362 return item.some((e) => _expected.matches(e));
363 } else {
364 return item.some((e) => e == _expected);
365 }
366 } else if (item is Map) {
367 return item.containsKey(_expected);
368 }
369 return false;
370 }
371
372 Description describe(Description description) =>
373 description.add('contains ').addDescriptionOf(_expected);
374 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698