ExpectedException now more useful.
This commit is contained in:
4
NEWS
4
NEWS
@@ -39,6 +39,10 @@ Improvements
|
|||||||
* Correctly display non-ASCII unicode output on terminals that claim to have a
|
* Correctly display non-ASCII unicode output on terminals that claim to have a
|
||||||
unicode encoding. (Martin [gz], #804122)
|
unicode encoding. (Martin [gz], #804122)
|
||||||
|
|
||||||
|
* ``ExpectedException`` now matches any exception of the given type by
|
||||||
|
default, and also allows specifying a ``Matcher`` rather than a mere regular
|
||||||
|
expression. (Jonathan Lange, #791889)
|
||||||
|
|
||||||
* ``FixtureSuite`` added, allows test suites to run with a given fixture.
|
* ``FixtureSuite`` added, allows test suites to run with a given fixture.
|
||||||
(Jonathan Lange)
|
(Jonathan Lange)
|
||||||
|
|
||||||
|
|||||||
@@ -160,9 +160,10 @@ particular errors. ``ExpectedException`` does just that. For example::
|
|||||||
silly.square('orange')
|
silly.square('orange')
|
||||||
|
|
||||||
The first argument to ``ExpectedException`` is the type of exception you
|
The first argument to ``ExpectedException`` is the type of exception you
|
||||||
expect to see raised. The second argument is an optional regular expression,
|
expect to see raised. The second argument is optional, and can be either a
|
||||||
if provided, the ``str()`` of the raised exception must match the regular
|
regular expression or a matcher. If it is a regular expression, the ``str()``
|
||||||
expression.
|
of the raised exception must match the regular expression. If it is a matcher,
|
||||||
|
then the raised exception object must match it.
|
||||||
|
|
||||||
|
|
||||||
assertIn, assertNotIn
|
assertIn, assertNotIn
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ class MatchesException(Matcher):
|
|||||||
Matcher.__init__(self)
|
Matcher.__init__(self)
|
||||||
self.expected = exception
|
self.expected = exception
|
||||||
if istext(value_re):
|
if istext(value_re):
|
||||||
value_re = AfterPreproccessing(str, MatchesRegex(value_re))
|
value_re = AfterPreproccessing(str, MatchesRegex(value_re), False)
|
||||||
self.value_re = value_re
|
self.value_re = value_re
|
||||||
self._is_instance = type(self.expected) not in classtypes()
|
self._is_instance = type(self.expected) not in classtypes()
|
||||||
|
|
||||||
@@ -824,9 +824,19 @@ class AfterPreprocessing(object):
|
|||||||
return AfterPreprocessing(_read, Equals(content))
|
return AfterPreprocessing(_read, Equals(content))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, preprocessor, matcher):
|
def __init__(self, preprocessor, matcher, annotate=True):
|
||||||
|
"""Create an AfterPreprocessing matcher.
|
||||||
|
|
||||||
|
:param preprocessor: A function called with the matchee before
|
||||||
|
matching.
|
||||||
|
:param matcher: What to match the preprocessed matchee against.
|
||||||
|
:param annotate: Whether or not to annotate the matcher with
|
||||||
|
something explaining how we transformed the matchee. Defaults
|
||||||
|
to True.
|
||||||
|
"""
|
||||||
self.preprocessor = preprocessor
|
self.preprocessor = preprocessor
|
||||||
self.matcher = matcher
|
self.matcher = matcher
|
||||||
|
self.annotate = annotate
|
||||||
|
|
||||||
def _str_preprocessor(self):
|
def _str_preprocessor(self):
|
||||||
if isinstance(self.preprocessor, types.FunctionType):
|
if isinstance(self.preprocessor, types.FunctionType):
|
||||||
@@ -839,9 +849,13 @@ class AfterPreprocessing(object):
|
|||||||
|
|
||||||
def match(self, value):
|
def match(self, value):
|
||||||
after = self.preprocessor(value)
|
after = self.preprocessor(value)
|
||||||
return Annotate(
|
if self.annotate:
|
||||||
"after %s on %r" % (self._str_preprocessor(), value),
|
matcher = Annotate(
|
||||||
self.matcher).match(after)
|
"after %s on %r" % (self._str_preprocessor(), value),
|
||||||
|
self.matcher)
|
||||||
|
else:
|
||||||
|
matcher = self.matcher
|
||||||
|
return matcher.match(after)
|
||||||
|
|
||||||
# This is the old, deprecated. spelling of the name, kept for backwards
|
# This is the old, deprecated. spelling of the name, kept for backwards
|
||||||
# compatibility.
|
# compatibility.
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ __all__ = [
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import itertools
|
import itertools
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
@@ -29,6 +28,7 @@ from testtools.compat import advance_iterator
|
|||||||
from testtools.matchers import (
|
from testtools.matchers import (
|
||||||
Annotate,
|
Annotate,
|
||||||
Equals,
|
Equals,
|
||||||
|
MatchesException,
|
||||||
Is,
|
Is,
|
||||||
Not,
|
Not,
|
||||||
)
|
)
|
||||||
@@ -754,7 +754,7 @@ class ExpectedException:
|
|||||||
exception is raised, an AssertionError will be raised.
|
exception is raised, an AssertionError will be raised.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, exc_type, value_re):
|
def __init__(self, exc_type, value_re=None):
|
||||||
"""Construct an `ExpectedException`.
|
"""Construct an `ExpectedException`.
|
||||||
|
|
||||||
:param exc_type: The type of exception to expect.
|
:param exc_type: The type of exception to expect.
|
||||||
@@ -772,9 +772,11 @@ class ExpectedException:
|
|||||||
raise AssertionError('%s not raised.' % self.exc_type.__name__)
|
raise AssertionError('%s not raised.' % self.exc_type.__name__)
|
||||||
if exc_type != self.exc_type:
|
if exc_type != self.exc_type:
|
||||||
return False
|
return False
|
||||||
if not re.match(self.value_re, str(exc_value)):
|
if self.value_re:
|
||||||
raise AssertionError('"%s" does not match "%s".' %
|
matcher = MatchesException(self.exc_type, self.value_re)
|
||||||
(str(exc_value), self.value_re))
|
mismatch = matcher.match((exc_type, exc_value, traceback))
|
||||||
|
if mismatch:
|
||||||
|
raise AssertionError(mismatch.describe())
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -258,10 +258,8 @@ class TestMatchesExceptionTypeReInterface(TestCase, TestMatchersInterface):
|
|||||||
MatchesException(Exception, 'fo.'))
|
MatchesException(Exception, 'fo.'))
|
||||||
]
|
]
|
||||||
describe_examples = [
|
describe_examples = [
|
||||||
# XXX: This is kind of a crappy message. Need to change
|
("'bar' does not match 'fo.'",
|
||||||
# AfterPreproccessing.
|
error_bar, MatchesException(ValueError, "fo.")),
|
||||||
("'bar' does not match 'fo.': after %r on %r"
|
|
||||||
% (str, error_bar[1]), error_bar, MatchesException(ValueError, "fo.")),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -736,9 +734,10 @@ class TestAfterPreprocessing(TestCase, TestMatchersInterface):
|
|||||||
]
|
]
|
||||||
|
|
||||||
describe_examples = [
|
describe_examples = [
|
||||||
("1 != 0: after <function parity> on 2",
|
("1 != 0: after <function parity> on 2", 2,
|
||||||
2,
|
|
||||||
AfterPreprocessing(parity, Equals(1))),
|
AfterPreprocessing(parity, Equals(1))),
|
||||||
|
("1 != 0", 2,
|
||||||
|
AfterPreprocessing(parity, Equals(1), annotate=False)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ from testtools import (
|
|||||||
ExpectedException,
|
ExpectedException,
|
||||||
TestCase,
|
TestCase,
|
||||||
)
|
)
|
||||||
|
from testtools.matchers import (
|
||||||
|
AfterPreprocessing,
|
||||||
|
Equals,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestExpectedException(TestCase):
|
class TestExpectedException(TestCase):
|
||||||
"""Test the ExpectedException context manager."""
|
"""Test the ExpectedException context manager."""
|
||||||
@@ -16,13 +21,30 @@ class TestExpectedException(TestCase):
|
|||||||
with ExpectedException(ValueError, 'tes.'):
|
with ExpectedException(ValueError, 'tes.'):
|
||||||
raise ValueError('test')
|
raise ValueError('test')
|
||||||
|
|
||||||
|
def test_pass_on_raise_matcher(self):
|
||||||
|
with ExpectedException(
|
||||||
|
ValueError, AfterPreprocessing(str, Equals('test'))):
|
||||||
|
raise ValueError('test')
|
||||||
|
|
||||||
def test_raise_on_text_mismatch(self):
|
def test_raise_on_text_mismatch(self):
|
||||||
try:
|
try:
|
||||||
with ExpectedException(ValueError, 'tes.'):
|
with ExpectedException(ValueError, 'tes.'):
|
||||||
raise ValueError('mismatch')
|
raise ValueError('mismatch')
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
self.assertEqual('"mismatch" does not match "tes.".', str(e))
|
self.assertEqual("'mismatch' does not match 'tes.'", str(e))
|
||||||
|
else:
|
||||||
|
self.fail('AssertionError not raised.')
|
||||||
|
|
||||||
|
def test_raise_on_general_mismatch(self):
|
||||||
|
matcher = AfterPreprocessing(str, Equals('test'))
|
||||||
|
value_error = ValueError('mismatch')
|
||||||
|
try:
|
||||||
|
with ExpectedException(ValueError, matcher):
|
||||||
|
raise value_error
|
||||||
|
except AssertionError:
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
self.assertEqual(matcher.match(value_error).describe(), str(e))
|
||||||
else:
|
else:
|
||||||
self.fail('AssertionError not raised.')
|
self.fail('AssertionError not raised.')
|
||||||
|
|
||||||
@@ -45,3 +67,7 @@ class TestExpectedException(TestCase):
|
|||||||
self.assertEqual('TypeError not raised.', str(e))
|
self.assertEqual('TypeError not raised.', str(e))
|
||||||
else:
|
else:
|
||||||
self.fail('AssertionError not raised.')
|
self.fail('AssertionError not raised.')
|
||||||
|
|
||||||
|
def test_pass_on_raise_any_message(self):
|
||||||
|
with ExpectedException(ValueError):
|
||||||
|
raise ValueError('whatever')
|
||||||
|
|||||||
Reference in New Issue
Block a user