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 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 * Returns a matcher that matches if an object is an instance |
| 104 * of [type] (or a subtype). |
| 105 * |
| 106 * As types are not first class objects in Dart we can only |
| 107 * approximate this test by using a generic wrapper class. |
| 108 * |
| 109 * For example, to test whether 'bar' is an instance of type |
| 110 * 'Foo', we would write: |
| 111 * |
| 112 * expect(bar, new isInstanceOf<Foo>()); |
| 113 * |
| 114 * To get better error message, supply a name when creating the |
| 115 * Type wrapper; e.g.: |
| 116 * |
| 117 * expect(bar, new isInstanceOf<Foo>('Foo')); |
| 118 */ |
| 119 class isInstanceOf<T> extends BaseMatcher { |
| 120 final String _name; |
| 121 const isInstanceOf([this._name = 'specified type']); |
| 122 bool matches(obj) => obj is T; |
| 123 // The description here is lame :-( |
| 124 Description describe(Description description) => |
| 125 description.add('an instance of ${_name}'); |
| 126 } |
| 127 |
| 128 /** |
| 129 * A matcher that matches functions that throw exceptions when called. |
| 130 * The value passed to expect() should be a reference to the function. |
| 131 * Note that the function cannot take arguments; to handle this |
| 132 * a wrapper will have to be created. |
| 133 * The function will be called once upon success, or twice upon failure |
| 134 * (the second time to get the failure description). |
| 135 */ |
| 136 final Matcher throws = const _Throws(); |
| 137 |
| 138 /** |
| 139 * Returns a matcher that matches a function call against an exception, |
| 140 * which is in turn constrained by a [matcher]. |
| 141 * The value passed to expect() should be a reference to the function. |
| 142 * Note that the function cannot take arguments; to handle this |
| 143 * a wrapper will have to be created. |
| 144 * The function will be called once upon success, or twice upon failure |
| 145 * (the second time to get the failure description). |
| 146 */ |
| 147 Matcher throwsA(Matcher matcher) => new _Throws(matcher); |
| 148 |
| 149 /** |
| 150 * A matcher that matches a function call against no exception. |
| 151 * The function will be called once. Any exceptions will be silently swallowed. |
| 152 * The value passed to expect() should be a reference to the function. |
| 153 * Note that the function cannot take arguments; to handle this |
| 154 * a wrapper will have to be created. |
| 155 */ |
| 156 final Matcher returnsNormally = const _ReturnsNormally(); |
| 157 |
| 158 class _Throws extends BaseMatcher { |
| 159 final Matcher _matcher; |
| 160 |
| 161 const _Throws([Matcher this._matcher = null]); |
| 162 |
| 163 bool matches(item) { |
| 164 try { |
| 165 item(); |
| 166 return false; |
| 167 } catch (final e) { |
| 168 return _matcher == null || _matcher.matches(e); |
| 169 } |
| 170 } |
| 171 |
| 172 Description describe(Description description) { |
| 173 if (_matcher == null) { |
| 174 return description.add("throws an exception"); |
| 175 } else { |
| 176 return description.add('throws an exception which matches '). |
| 177 addDescriptionOf(_matcher); |
| 178 } |
| 179 } |
| 180 |
| 181 Description describeMismatch(item, Description mismatchDescription) { |
| 182 try { |
| 183 item(); |
| 184 return mismatchDescription.add(' no exception'); |
| 185 } catch (final e) { |
| 186 return mismatchDescription.add(' exception does not match '). |
| 187 addDescriptionOf(_matcher); |
| 188 } |
| 189 } |
| 190 } |
| 191 |
| 192 class _ReturnsNormally extends BaseMatcher { |
| 193 |
| 194 const _ReturnsNormally(); |
| 195 |
| 196 bool matches(f) { |
| 197 try { |
| 198 f(); |
| 199 return true; |
| 200 } catch (final e) { |
| 201 return false; |
| 202 } |
| 203 } |
| 204 |
| 205 Description describe(Description description) => |
| 206 description.add("return normally"); |
| 207 |
| 208 Description describeMismatch(item, Description mismatchDescription) { |
| 209 return mismatchDescription.add(' threw exception'); |
| 210 } |
| 211 } |
| 212 |
| 213 /** A matcher for functions that throw BadNumberFormatException */ |
| 214 final Matcher throwsBadNumberFormatException = |
| 215 new _Throws(new isInstanceOf<BadNumberFormatException>()); |
| 216 |
| 217 /** A matcher for functions that throw an Exception */ |
| 218 final Matcher throwsException = |
| 219 new _Throws(new isInstanceOf<Exception>()); |
| 220 |
| 221 /** A matcher for functions that throw an IllegalArgumentException */ |
| 222 final Matcher throwsIllegalArgumentException = |
| 223 new _Throws(new isInstanceOf<IllegalArgumentException>()); |
| 224 |
| 225 /** A matcher for functions that throw an IllegalJSRegExpException */ |
| 226 final Matcher throwsIllegalJSRegExpException = |
| 227 new _Throws(new isInstanceOf<IllegalJSRegExpException>()); |
| 228 |
| 229 /** A matcher for functions that throw an IndexOutOfRangeException */ |
| 230 final Matcher throwsIndexOutOfRangeException = |
| 231 new _Throws(new isInstanceOf<IndexOutOfRangeException>()); |
| 232 |
| 233 /** A matcher for functions that throw a NoSuchMethodException */ |
| 234 final Matcher throwsNoSuchMethodException = |
| 235 new _Throws(new isInstanceOf<NoSuchMethodException>()); |
| 236 |
| 237 /** A matcher for functions that throw a NotImplementedException */ |
| 238 final Matcher throwsNotImplementedException = |
| 239 new _Throws(new isInstanceOf<NotImplementedException>()); |
| 240 |
| 241 /** A matcher for functions that throw a NullPointerException */ |
| 242 final Matcher throwsNullPointerException = |
| 243 new _Throws(new isInstanceOf<NullPointerException>()); |
| 244 |
| 245 /** A matcher for functions that throw an UnsupportedOperationException */ |
| 246 final Matcher throwsUnsupportedOperationException = |
| 247 new _Throws(new isInstanceOf<UnsupportedOperationException>()); |
| 248 |
| 249 /** |
| 250 * Returns a matcher that matches if an object has a length property |
| 251 * that matches [matcher]. |
| 252 */ |
| 253 Matcher hasLength(matcher) => |
| 254 new _HasLength(wrapMatcher(matcher)); |
| 255 |
| 256 class _HasLength extends BaseMatcher { |
| 257 final Matcher _matcher; |
| 258 const _HasLength([Matcher this._matcher = null]); |
| 259 |
| 260 bool matches(item) { |
| 261 return _matcher.matches(item.length); |
| 262 } |
| 263 |
| 264 Description describe(Description description) => |
| 265 description.add('an object with length of '). |
| 266 addDescriptionOf(_matcher); |
| 267 |
| 268 Description describeMismatch(item, Description mismatchDescription) { |
| 269 super.describeMismatch(item, mismatchDescription); |
| 270 try { |
| 271 // We want to generate a different description if there is no length |
| 272 // property. This is harmless code that will throw if no length property |
| 273 // but subtle enough that an optimizer shouldn't strip it out. |
| 274 if (item.length * item.length >= 0) { |
| 275 return mismatchDescription.add(' with length of '). |
| 276 addDescriptionOf(item.length); |
| 277 } |
| 278 } catch (var e) { |
| 279 return mismatchDescription.add(' has no length property'); |
| 280 } |
| 281 } |
| 282 } |
| 283 |
| 284 /** |
| 285 * Returns a matcher that does a deep recursive match. This only works |
| 286 * with scalars, Maps and Iterables. To handle cyclic structures an |
| 287 * item [limit] can be provided; if after [limit] items have been |
| 288 * compared and the process is not complete this will be treated as |
| 289 * a mismatch. The default limit is 1000. |
| 290 */ |
| 291 Matcher recursivelyMatches(expected, [limit=1000]) => |
| 292 new _DeepMatcher(expected, limit); |
| 293 |
| 294 // A utility function for comparing iterators |
| 295 |
| 296 String _compareIterables(expected, actual, matcher) { |
| 297 if (actual is !Iterable) { |
| 298 return 'is not Iterable'; |
| 299 } |
| 300 var expectedIterator = expected.iterator(); |
| 301 var actualIterator = actual.iterator(); |
| 302 var position = 0; |
| 303 String reason = null; |
| 304 while (reason == null) { |
| 305 if (expectedIterator.hasNext()) { |
| 306 if (actualIterator.hasNext()) { |
| 307 reason = matcher(expectedIterator.next(), |
| 308 actualIterator.next(), |
| 309 'mismatch at position ${position}'); |
| 310 ++position; |
| 311 } else { |
| 312 reason = 'shorter than expected'; |
| 313 } |
| 314 } else if (actualIterator.hasNext()) { |
| 315 reason = 'longer than expected'; |
| 316 } else { |
| 317 return null; |
| 318 } |
| 319 } |
| 320 return reason; |
| 321 } |
| 322 |
| 323 class _DeepMatcher extends BaseMatcher { |
| 324 final _expected; |
| 325 final int _limit; |
| 326 var count; |
| 327 |
| 328 _DeepMatcher(this._expected, [this._limit = 1000]); |
| 329 |
| 330 String _recursiveMatch(expected, actual, String location) { |
| 331 String reason = null; |
| 332 if (++count >= _limit) { |
| 333 reason = 'item comparison limit exceeded'; |
| 334 } else if (expected is Iterable) { |
| 335 reason = _compareIterables(expected, actual, _recursiveMatch); |
| 336 } else if (expected is Map) { |
| 337 if (actual is !Map) { |
| 338 reason = 'expected a map'; |
| 339 } else if (expected.length != actual.length) { |
| 340 reason = 'different map lengths'; |
| 341 } else { |
| 342 for (var key in expected.getKeys()) { |
| 343 if (!actual.containsKey(key)) { |
| 344 reason = 'missing map key ${key}'; |
| 345 break; |
| 346 } |
| 347 reason = _recursiveMatch(expected[key], actual[key], |
| 348 'with key ${key} ${location}'); |
| 349 if (reason != null) { |
| 350 break; |
| 351 } |
| 352 } |
| 353 } |
| 354 } else if (expected != actual) { |
| 355 reason = 'expected ${expected} but got ${actual}'; |
| 356 } |
| 357 if (reason == null) { |
| 358 return null; |
| 359 } else { |
| 360 return '${reason} ${location}'; |
| 361 } |
| 362 } |
| 363 |
| 364 String _match(expected, actual) { |
| 365 count = 0; |
| 366 return _recursiveMatch(expected, actual, ''); |
| 367 } |
| 368 |
| 369 bool matches(item) => _match(_expected, item) == null; |
| 370 |
| 371 Description describe(Description description) => |
| 372 description.add('recursively matches ').addDescriptionOf(_expected); |
| 373 |
| 374 Description describeMismatch(item, Description mismatchDescription) => |
| 375 mismatchDescription.add(_match(_expected, item)); |
| 376 } |
| 377 |
| 378 /** |
| 379 * Returns a matcher that matches if the match argument contains |
| 380 * the expected value. For [String]s this means substring matching; |
| 381 * for [Map]s is means the map has the key, and for [Collection]s it |
| 382 * means the collection has a matching element. In the case of collections, |
| 383 * [expected] can itself be a matcher. |
| 384 */ |
| 385 Matcher contains(expected) => new _Contains(expected); |
| 386 |
| 387 class _Contains extends BaseMatcher { |
| 388 |
| 389 final _expected; |
| 390 |
| 391 const _Contains(this._expected); |
| 392 |
| 393 bool matches(item) { |
| 394 if (item is String) { |
| 395 return item.indexOf(_expected) >= 0; |
| 396 } else if (item is Collection) { |
| 397 if (_expected is Matcher) { |
| 398 return item.some((e) => _expected.matches(e)); |
| 399 } else { |
| 400 return item.some((e) => e == _expected); |
| 401 } |
| 402 } else if (item is Map) { |
| 403 return item.containsKey(_expected); |
| 404 } |
| 405 return false; |
| 406 } |
| 407 |
| 408 Description describe(Description description) => |
| 409 description.add('contains ').addDescriptionOf(_expected); |
| 410 } |
OLD | NEW |