Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |