-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import re | ||
from hamcrest.core.base_matcher import BaseMatcher | ||
from hamcrest.core.helpers.wrap_matcher import wrap_matcher | ||
|
||
__author__ = "Per Fagrell" | ||
__copyright__ = "Copyright 2013 hamcrest.org" | ||
__license__ = "BSD, see License.txt" | ||
|
||
|
||
class Raises(BaseMatcher): | ||
def __init__(self, expected, pattern=None): | ||
self.pattern = pattern | ||
self.expected = expected | ||
self.actual = None | ||
|
||
def _matches(self, function): | ||
if not callable(function): | ||
return False | ||
|
||
try: | ||
function() | ||
except Exception as err: | ||
self.actual = err | ||
|
||
if isinstance(self.actual, self.expected): | ||
if self.pattern is not None: | ||
return re.search(self.pattern, str(self.actual)) is not None | ||
return True | ||
return False | ||
|
||
def describe_to(self, description): | ||
description.append_text('Expected %s' % self.expected) | ||
|
||
def describe_mismatch(self, item, description): | ||
if not callable(item): | ||
description.append_text('%s is not callable' % item) | ||
return | ||
|
||
if self.actual is None: | ||
description.append_text('No exception raised.') | ||
elif isinstance(self.actual, self.expected) and self.pattern is not None: | ||
description.append_text('Correct assertion type raised, but the expected pattern ("%s") not found.' % self.pattern) | ||
description.append_text('\n message was: "%s"' % str(self.actual)) | ||
else: | ||
description.append_text('%s was raised instead' % type(self.actual)) | ||
|
||
|
||
def raises(exception, pattern=None): | ||
"""Matches if the called function raised the expected exception. | ||
:param exception: The class of the expected exception | ||
:param pattern: Optional regular expression to match exception message. | ||
Expects the actual to be wrapped by using :py:func:`~hamcrest.core.core.raises.calling`, | ||
or a callable taking no arguments. | ||
Optional argument pattern should be a string containing a regular expression. If provided, | ||
the string representation of the actual exception - e.g. `str(actual)` - must match pattern. | ||
Examples:: | ||
assert_that(calling(int).with_('q'), raises(TypeError)) | ||
assert_that(calling(parse, broken_input), raises(ValueError)) | ||
""" | ||
return Raises(exception, pattern) | ||
|
||
|
||
class DelayedCall(object): | ||
def __init__(self, func, *args, **kwargs): | ||
self.func = func | ||
self.args = args | ||
self.kwargs = kwargs | ||
|
||
def __call__(self): | ||
self.func(*self.args, **self.kwargs) | ||
|
||
def with_(self, *args, **kwargs): | ||
self.args = args | ||
self.kwargs = kwargs | ||
return self | ||
|
||
|
||
def calling(func, *args, **kwargs): | ||
"""Wrapper for function call that delays the actual execution so that | ||
:py:func:`~hamcrest.core.core.raises.raises` matcher can catch any thrown exception. | ||
:param func: The function or method to be called | ||
:param \*args: The positional arguments to pass to the function or method | ||
:param \*\*kwargs: The keyword arguments to pass to the function or method | ||
The arguments can either be provided directly along with the function or you can call | ||
the `with_` function on the returned object as a for of syntactic sugar:: | ||
calling(my_method).with_(arguments, and_='keywords') | ||
""" | ||
return DelayedCall(func, *args, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
if __name__ == '__main__': | ||
import sys | ||
sys.path.insert(0, '..') | ||
sys.path.insert(0, '../..') | ||
|
||
from hamcrest.core.core.raises import * | ||
|
||
from hamcrest.core.core.isequal import equal_to | ||
from hamcrest_unit_test.matcher_test import MatcherTest | ||
import unittest | ||
|
||
__author__ = "Per Fagrell" | ||
__copyright__ = "Copyright 2013 hamcrest.org" | ||
__license__ = "BSD, see License.txt" | ||
|
||
|
||
def no_exception(*args, **kwargs): | ||
return | ||
|
||
|
||
def raise_exception(*args, **kwargs): | ||
raise AssertionError(str(args) + str(kwargs)) | ||
|
||
|
||
class RaisesTest(MatcherTest): | ||
def testMatchesIfFunctionRaisesTheExactExceptionExpected(self): | ||
self.assert_matches('Right exception', | ||
raises(AssertionError), | ||
calling(raise_exception)) | ||
|
||
def testDoesNotMatchTypeErrorIfActualIsNotCallable(self): | ||
self.assert_does_not_match('Not callable', | ||
raises(TypeError), | ||
23) | ||
|
||
def testMatchesIfFunctionRaisesASubclassOfTheExpectedException(self): | ||
self.assert_matches('Subclassed Exception', | ||
raises(Exception), | ||
calling(raise_exception)) | ||
|
||
def testDoesNotMatchIfFunctionDoesNotRaiseException(self): | ||
self.assert_does_not_match('No exception', | ||
raises(ValueError), | ||
calling(no_exception)) | ||
|
||
def testDoesNotMatchExceptionIfRegularExpressionDoesNotMatch(self): | ||
self.assert_does_not_match('Bad regex', | ||
raises(AssertionError, "Bananarama"), | ||
calling(raise_exception)) | ||
|
||
def testMatchesRegularExpressionToStringifiedException(self): | ||
self.assert_matches('Regex', | ||
raises(AssertionError, "(3, 1, 4)"), | ||
calling(raise_exception).with_(3,1,4)) | ||
|
||
self.assert_matches('Regex', | ||
raises(AssertionError, "([\d, ]+)"), | ||
calling(raise_exception).with_(3,1,4)) | ||
|
||
|
||
class CallingTest(unittest.TestCase): | ||
def testCallingDoesNotImmediatelyExecuteFunction(self): | ||
try: | ||
calling(raise_exception) | ||
except AssertionError: | ||
self.fail() | ||
else: | ||
pass | ||
|
||
def testCallingObjectCallsProvidedFunction(self): | ||
method = Callable() | ||
calling(method)() | ||
self.assertTrue(method.called) | ||
|
||
def testCallingObjectPassesArgsAndKwargs(self): | ||
method = Callable() | ||
calling(method, "Banana", 3, keyword1="happy")() | ||
|
||
self.assertEqual(method.args, ("Banana", 3)) | ||
self.assertEqual(method.kwargs, {'keyword1': 'happy'}) | ||
|
||
def testCallingWithFunctionReturnsObject(self): | ||
method = Callable() | ||
callable = calling(method) | ||
returned = callable.with_(3, 1, 4, keyword2="bronze") | ||
|
||
self.assertEqual(returned, callable) | ||
|
||
def testCallingWithFunctionSetsArgumentList(self): | ||
method = Callable() | ||
calling(method).with_(3, 1, 4, keyword2="bronze")() | ||
|
||
self.assertEqual(method.args, (3, 1, 4)) | ||
self.assertEqual(method.kwargs, {'keyword2': 'bronze'}) | ||
|
||
|
||
class Callable(object): | ||
def __init__(self): | ||
self.called = False | ||
|
||
def __call__(self, *args, **kwargs): | ||
self.called = True | ||
self.args = args | ||
self.kwargs = kwargs |
8e71212
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has this been merged into master? Is it going to make the next release?
I'd personally love to see this matcher. I've been hand rolling these (with tests!) every time I need them.
8e71212
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has not been merged yet. It needs to be rewritten slightly, to use weakref as mentioned in the merge request. I'll try to get some time to do that before/during this weekend, and hopefully it will be considered for the next release.
8e71212
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.