← Back to team overview

testtools-dev team mailing list archive

[Merge] lp:~jml/testtools/expected-exception-791889 into lp:testtools

 

Jonathan Lange has proposed merging lp:~jml/testtools/expected-exception-791889 into lp:testtools.

Requested reviews:
  testtools committers (testtools-committers)
Related bugs:
  Bug #791889 in testtools: "The value_re argument for ExpectedException only fits some situations"
  https://bugs.launchpad.net/testtools/+bug/791889

For more details, see:
https://code.launchpad.net/~jml/testtools/expected-exception-791889/+merge/69546

This patch *actually* fixes bug 791889. Really.

In https://code.launchpad.net/~jml/testtools/matches-exception-791889/+merge/68595, I had assumed that the bug was about MatchesException. Luckily, the changes in that branch were also worthwhile, and made this branch much easier.

What we do here is make ExpectedException delegate to MatchesException for its logic. Neat, huh?

Also took the opportunity to tweak AfterPreprocessing so we can use it "silently", such that it doesn't leave a trace of itself in the mismatch. For the cases here, it actually makes the errors clearer.



-- 
https://code.launchpad.net/~jml/testtools/expected-exception-791889/+merge/69546
Your team testtools developers is subscribed to branch lp:testtools.
=== modified file 'testtools/matchers.py'
--- testtools/matchers.py	2011-07-20 21:07:45 +0000
+++ testtools/matchers.py	2011-07-27 19:57:29 +0000
@@ -421,7 +421,7 @@
         Matcher.__init__(self)
         self.expected = exception
         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._is_instance = type(self.expected) not in classtypes()
 
@@ -824,9 +824,19 @@
           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.matcher = matcher
+        self.annotate = annotate
 
     def _str_preprocessor(self):
         if isinstance(self.preprocessor, types.FunctionType):
@@ -839,9 +849,13 @@
 
     def match(self, value):
         after = self.preprocessor(value)
-        return Annotate(
-            "after %s on %r" % (self._str_preprocessor(), value),
-            self.matcher).match(after)
+        if self.annotate:
+            matcher = Annotate(
+                "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
 # compatibility.

=== modified file 'testtools/testcase.py'
--- testtools/testcase.py	2011-07-21 09:39:52 +0000
+++ testtools/testcase.py	2011-07-27 19:57:29 +0000
@@ -16,7 +16,6 @@
 
 import copy
 import itertools
-import re
 import sys
 import types
 import unittest
@@ -29,6 +28,7 @@
 from testtools.matchers import (
     Annotate,
     Equals,
+    MatchesException,
     Is,
     Not,
     )
@@ -754,7 +754,7 @@
     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`.
 
         :param exc_type: The type of exception to expect.
@@ -772,9 +772,11 @@
             raise AssertionError('%s not raised.' % self.exc_type.__name__)
         if exc_type != self.exc_type:
             return False
-        if not re.match(self.value_re, str(exc_value)):
-            raise AssertionError('"%s" does not match "%s".' %
-                                 (str(exc_value), self.value_re))
+        if self.value_re:
+            matcher = MatchesException(self.exc_type, self.value_re)
+            mismatch = matcher.match((exc_type, exc_value, traceback))
+            if mismatch:
+                raise AssertionError(mismatch.describe())
         return True
 
 

=== modified file 'testtools/tests/test_matchers.py'
--- testtools/tests/test_matchers.py	2011-07-21 09:39:52 +0000
+++ testtools/tests/test_matchers.py	2011-07-27 19:57:29 +0000
@@ -258,9 +258,7 @@
          MatchesException(Exception, 'fo.'))
         ]
     describe_examples = [
-        # XXX: This is kind of a crappy message. Need to change
-        # AfterPreproccessing.
-        ("'bar' does not match 'fo.': after <type 'str'> on ValueError('bar',)",
+        ("'bar' does not match 'fo.'",
          error_bar, MatchesException(ValueError, "fo.")),
         ]
 
@@ -736,9 +734,10 @@
         ]
 
     describe_examples = [
-        ("1 != 0: after <function parity> on 2",
-         2,
+        ("1 != 0: after <function parity> on 2", 2,
          AfterPreprocessing(parity, Equals(1))),
+        ("1 != 0", 2,
+         AfterPreprocessing(parity, Equals(1), annotate=False)),
         ]
 
 

=== modified file 'testtools/tests/test_with_with.py'
--- testtools/tests/test_with_with.py	2011-04-20 23:45:52 +0000
+++ testtools/tests/test_with_with.py	2011-07-27 19:57:29 +0000
@@ -8,6 +8,11 @@
     ExpectedException,
     TestCase,
     )
+from testtools.matchers import (
+    AfterPreprocessing,
+    Equals,
+    )
+
 
 class TestExpectedException(TestCase):
     """Test the ExpectedException context manager."""
@@ -16,13 +21,30 @@
         with ExpectedException(ValueError, 'tes.'):
             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):
         try:
             with ExpectedException(ValueError, 'tes.'):
                 raise ValueError('mismatch')
         except AssertionError:
             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:
             self.fail('AssertionError not raised.')
 
@@ -45,3 +67,7 @@
             self.assertEqual('TypeError not raised.', str(e))
         else:
             self.fail('AssertionError not raised.')
+
+    def test_pass_on_raise_any_message(self):
+        with ExpectedException(ValueError):
+            raise ValueError('whatever')


Follow ups