| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library matcher.core_matchers; | 5 library matcher.core_matchers; |
| 6 | 6 |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'description.dart'; | 7 import 'description.dart'; |
| 10 import 'expect.dart'; | |
| 11 import 'interfaces.dart'; | 8 import 'interfaces.dart'; |
| 9 import 'util.dart'; |
| 12 | 10 |
| 13 /// Returns a matcher that matches empty strings, maps or iterables | 11 /// Returns a matcher that matches empty strings, maps or iterables |
| 14 /// (including collections). | 12 /// (including collections). |
| 15 const Matcher isEmpty = const _Empty(); | 13 const Matcher isEmpty = const _Empty(); |
| 16 | 14 |
| 17 class _Empty extends Matcher { | 15 class _Empty extends Matcher { |
| 18 const _Empty(); | 16 const _Empty(); |
| 19 bool matches(item, Map matchState) { | 17 bool matches(item, Map matchState) { |
| 20 if (item is Map || item is Iterable) { | 18 if (item is Map || item is Iterable) { |
| 21 return item.isEmpty; | 19 return item.isEmpty; |
| (...skipping 361 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 383 /// parameters. | 381 /// parameters. |
| 384 class isInstanceOf<T> extends Matcher { | 382 class isInstanceOf<T> extends Matcher { |
| 385 final String _name; | 383 final String _name; |
| 386 const isInstanceOf([name = 'specified type']) : this._name = name; | 384 const isInstanceOf([name = 'specified type']) : this._name = name; |
| 387 bool matches(obj, Map matchState) => obj is T; | 385 bool matches(obj, Map matchState) => obj is T; |
| 388 // The description here is lame :-( | 386 // The description here is lame :-( |
| 389 Description describe(Description description) => | 387 Description describe(Description description) => |
| 390 description.add('an instance of ${_name}'); | 388 description.add('an instance of ${_name}'); |
| 391 } | 389 } |
| 392 | 390 |
| 393 /// This can be used to match two kinds of objects: | |
| 394 /// | |
| 395 /// * A [Function] that throws an exception when called. The function cannot | |
| 396 /// take any arguments. If you want to test that a function expecting | |
| 397 /// arguments throws, wrap it in another zero-argument function that calls | |
| 398 /// the one you want to test. | |
| 399 /// | |
| 400 /// * A [Future] that completes with an exception. Note that this creates an | |
| 401 /// asynchronous expectation. The call to `expect()` that includes this will | |
| 402 /// return immediately and execution will continue. Later, when the future | |
| 403 /// completes, the actual expectation will run. | |
| 404 const Matcher throws = const Throws(); | |
| 405 | |
| 406 /// This can be used to match two kinds of objects: | |
| 407 /// | |
| 408 /// * A [Function] that throws an exception when called. The function cannot | |
| 409 /// take any arguments. If you want to test that a function expecting | |
| 410 /// arguments throws, wrap it in another zero-argument function that calls | |
| 411 /// the one you want to test. | |
| 412 /// | |
| 413 /// * A [Future] that completes with an exception. Note that this creates an | |
| 414 /// asynchronous expectation. The call to `expect()` that includes this will | |
| 415 /// return immediately and execution will continue. Later, when the future | |
| 416 /// completes, the actual expectation will run. | |
| 417 /// | |
| 418 /// In both cases, when an exception is thrown, this will test that the exceptio
n | |
| 419 /// object matches [matcher]. If [matcher] is not an instance of [Matcher], it | |
| 420 /// will implicitly be treated as `equals(matcher)`. | |
| 421 Matcher throwsA(matcher) => new Throws(wrapMatcher(matcher)); | |
| 422 | |
| 423 /// A matcher that matches a function call against no exception. | 391 /// A matcher that matches a function call against no exception. |
| 424 /// The function will be called once. Any exceptions will be silently swallowed. | 392 /// The function will be called once. Any exceptions will be silently swallowed. |
| 425 /// The value passed to expect() should be a reference to the function. | 393 /// The value passed to expect() should be a reference to the function. |
| 426 /// Note that the function cannot take arguments; to handle this | 394 /// Note that the function cannot take arguments; to handle this |
| 427 /// a wrapper will have to be created. | 395 /// a wrapper will have to be created. |
| 428 const Matcher returnsNormally = const _ReturnsNormally(); | 396 const Matcher returnsNormally = const _ReturnsNormally(); |
| 429 | 397 |
| 430 class Throws extends Matcher { | |
| 431 final Matcher _matcher; | |
| 432 | |
| 433 const Throws([Matcher matcher]) : this._matcher = matcher; | |
| 434 | |
| 435 bool matches(item, Map matchState) { | |
| 436 if (item is! Function && item is! Future) return false; | |
| 437 if (item is Future) { | |
| 438 var done = wrapAsync((fn) => fn()); | |
| 439 | |
| 440 // Queue up an asynchronous expectation that validates when the future | |
| 441 // completes. | |
| 442 item.then((value) { | |
| 443 done(() { | |
| 444 fail("Expected future to fail, but succeeded with '$value'."); | |
| 445 }); | |
| 446 }, onError: (error, trace) { | |
| 447 done(() { | |
| 448 if (_matcher == null) return; | |
| 449 var reason; | |
| 450 if (trace != null) { | |
| 451 var stackTrace = trace.toString(); | |
| 452 stackTrace = " ${stackTrace.replaceAll("\n", "\n ")}"; | |
| 453 reason = "Actual exception trace:\n$stackTrace"; | |
| 454 } | |
| 455 expect(error, _matcher, reason: reason); | |
| 456 }); | |
| 457 }); | |
| 458 // It hasn't failed yet. | |
| 459 return true; | |
| 460 } | |
| 461 | |
| 462 try { | |
| 463 item(); | |
| 464 return false; | |
| 465 } catch (e, s) { | |
| 466 if (_matcher == null || _matcher.matches(e, matchState)) { | |
| 467 return true; | |
| 468 } else { | |
| 469 addStateInfo(matchState, {'exception': e, 'stack': s}); | |
| 470 return false; | |
| 471 } | |
| 472 } | |
| 473 } | |
| 474 | |
| 475 Description describe(Description description) { | |
| 476 if (_matcher == null) { | |
| 477 return description.add("throws"); | |
| 478 } else { | |
| 479 return description.add('throws ').addDescriptionOf(_matcher); | |
| 480 } | |
| 481 } | |
| 482 | |
| 483 Description describeMismatch(item, Description mismatchDescription, | |
| 484 Map matchState, | |
| 485 bool verbose) { | |
| 486 if (item is! Function && item is! Future) { | |
| 487 return mismatchDescription.add('is not a Function or Future'); | |
| 488 } else if (_matcher == null || matchState['exception'] == null) { | |
| 489 return mismatchDescription.add('did not throw'); | |
| 490 } else { | |
| 491 mismatchDescription. add('threw '). | |
| 492 addDescriptionOf(matchState['exception']); | |
| 493 if (verbose) { | |
| 494 mismatchDescription.add(' at ').add(matchState['stack'].toString()); | |
| 495 } | |
| 496 return mismatchDescription; | |
| 497 } | |
| 498 } | |
| 499 } | |
| 500 | |
| 501 class _ReturnsNormally extends Matcher { | 398 class _ReturnsNormally extends Matcher { |
| 502 const _ReturnsNormally(); | 399 const _ReturnsNormally(); |
| 503 | 400 |
| 504 bool matches(f, Map matchState) { | 401 bool matches(f, Map matchState) { |
| 505 try { | 402 try { |
| 506 f(); | 403 f(); |
| 507 return true; | 404 return true; |
| 508 } catch (e, s) { | 405 } catch (e, s) { |
| 509 addStateInfo(matchState, {'exception': e, 'stack': s}); | 406 addStateInfo(matchState, {'exception': e, 'stack': s}); |
| 510 return false; | 407 return false; |
| (...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 738 addDescriptionOf(matchState['feature']); | 635 addDescriptionOf(matchState['feature']); |
| 739 var innerDescription = new StringDescription(); | 636 var innerDescription = new StringDescription(); |
| 740 _matcher.describeMismatch(matchState['feature'], innerDescription, | 637 _matcher.describeMismatch(matchState['feature'], innerDescription, |
| 741 matchState['state'], verbose); | 638 matchState['state'], verbose); |
| 742 if (innerDescription.length > 0) { | 639 if (innerDescription.length > 0) { |
| 743 mismatchDescription.add(' which ').add(innerDescription.toString()); | 640 mismatchDescription.add(' which ').add(innerDescription.toString()); |
| 744 } | 641 } |
| 745 return mismatchDescription; | 642 return mismatchDescription; |
| 746 } | 643 } |
| 747 } | 644 } |
| OLD | NEW |