subunit/testtools: Include newer version.
authorJelmer Vernooij <jelmer@samba.org>
Sat, 16 Jan 2010 06:56:21 +0000 (19:56 +1300)
committerJelmer Vernooij <jelmer@samba.org>
Sat, 16 Jan 2010 06:56:21 +0000 (19:56 +1300)
lib/subunit/python/subunit/__init__.py
lib/subunit/python/subunit/run.py
lib/subunit/python/subunit/tests/test_test_protocol.py
lib/subunit/python/testtools/content.py
lib/subunit/python/testtools/content_type.py
lib/subunit/python/testtools/matchers.py
lib/subunit/python/testtools/testcase.py
lib/subunit/python/testtools/tests/__init__.py
lib/subunit/python/testtools/tests/test_matchers.py
lib/subunit/python/testtools/tests/test_testtools.py
lib/subunit/python/testtools/utils.py

index 6e8df90317444eb29a957094ea4e94307871c8d3..6b65ae42dce06bd5c2bfcabbe60a462520ee972f 100644 (file)
@@ -213,10 +213,10 @@ class _ParserState(object):
     def lineReceived(self, line):
         """a line has been received."""
         parts = line.split(None, 1)
-        if len(parts) == 2:
+        if len(parts) == 2 and line.startswith(parts[0]):
             cmd, rest = parts
             offset = len(cmd) + 1
-            cmd = cmd.strip(':')
+            cmd = cmd.rstrip(':')
             if cmd in ('test', 'testing'):
                 self.startTest(offset, line)
             elif cmd == 'error':
@@ -1111,3 +1111,16 @@ class TestResultStats(unittest.TestResult):
     def wasSuccessful(self):
         """Tells whether or not this result was a success"""
         return self.failed_tests == 0
+
+
+def get_default_formatter():
+    """Obtain the default formatter to write to.
+    
+    :return: A file-like object.
+    """
+    formatter = os.getenv("SUBUNIT_FORMATTER")
+    if formatter:
+        return os.popen(formatter, "w")
+    else:
+        return sys.stdout
+
index 2b90791d69cc4a5a1a10bc878925978790985f03..01c0b0e9e6b35911b06db3e3a534769a1cbe41de 100755 (executable)
@@ -22,7 +22,7 @@
 
 import sys
 
-from subunit import TestProtocolClient
+from subunit import TestProtocolClient, get_default_formatter
 
 
 class SubunitTestRunner(object):
@@ -41,6 +41,7 @@ if __name__ == '__main__':
     from unittest import TestProgram
     parser = optparse.OptionParser(__doc__)
     args = parser.parse_args()[1]
-    runner = SubunitTestRunner()
+    stream = get_default_formatter()
+    runner = SubunitTestRunner(stream)
     program = TestProgram(module=None, argv=[sys.argv[0]] + args,
                           testRunner=runner)
index 9e9db18163460f5883c8db834db9cc2c996c4e3b..f10380b09b69323c850a9ae6daa9e267a137d0f9 100644 (file)
@@ -124,6 +124,10 @@ class TestTestProtocolServerStartTest(unittest.TestCase):
         self.assertEqual(self.client._events,
             [('startTest', subunit.RemotedTestCase("old mcdonald"))])
 
+    def test_indented_test_colon_ignored(self):
+        self.protocol.lineReceived(" test: old mcdonald\n")
+        self.assertEqual([], self.client._events)
+
     def test_start_testing_colon(self):
         self.protocol.lineReceived("testing: old mcdonald\n")
         self.assertEqual(self.client._events,
index 00c782347ba2ef1f09e93f0ec96f601edab4e2d2..353e3f0f46441943de9ef1dea667aae109e2acaa 100644 (file)
@@ -44,7 +44,7 @@ class Content(object):
         no charset parameter is present in the MIME type. (This is somewhat
         arbitrary, but consistent with RFC2617 3.7.1).
 
-        :raises: ValueError If the content type is not text/*.
+        :raises ValueError: If the content type is not text/\*.
         """
         if self.content_type.type != "text":
             raise ValueError("Not a text type %r" % self.content_type)
index e70fa76ec8d8b028381ec474dfb37fcf3c31c060..aded81b7322a5136a1443bb0434750f3206873c0 100644 (file)
@@ -9,7 +9,7 @@ class ContentType(object):
     :ivar type: The primary type, e.g. "text" or "application"
     :ivar subtype: The subtype, e.g. "plain" or "octet-stream"
     :ivar parameters: A dict of additional parameters specific to the
-    content type.
+        content type.
     """
 
     def __init__(self, primary_type, sub_type, parameters=None):
index 947ef601b3b6f2985f789c468ab79c8a4f77004e..244daceb7f4bd05c9d5cbb4f7dc04fd51ce88809 100644 (file)
@@ -14,7 +14,10 @@ __metaclass__ = type
 __all__ = [
     'DocTestMatches',
     'Equals',
+    'MatchesAll',
     'MatchesAny',
+    'NotEquals',
+    'Not',
     ]
 
 import doctest
@@ -135,6 +138,36 @@ class EqualsMismatch:
         return "%r != %r" % (self.expected, self.other)
 
 
+class NotEquals:
+    """Matches if the items are not equal.
+
+    In most cases, this is equivalent to `Not(Equals(foo))`. The difference
+    only matters when testing `__ne__` implementations.
+    """
+
+    def __init__(self, expected):
+        self.expected = expected
+
+    def __str__(self):
+        return 'NotEquals(%r)' % (self.expected,)
+
+    def match(self, other):
+        if self.expected != other:
+            return None
+        return NotEqualsMismatch(self.expected, other)
+
+
+class NotEqualsMismatch:
+    """Two things are the same."""
+
+    def __init__(self, expected, other):
+        self.expected = expected
+        self.other = other
+
+    def describe(self):
+        return '%r == %r' % (self.expected, self.other)
+
+
 class MatchesAny:
     """Matches if any of the matchers it is created with match."""
 
@@ -155,6 +188,27 @@ class MatchesAny:
             str(matcher) for matcher in self.matchers])
 
 
+class MatchesAll:
+    """Matches if all of the matchers it is created with match."""
+
+    def __init__(self, *matchers):
+        self.matchers = matchers
+
+    def __str__(self):
+        return 'MatchesAll(%s)' % ', '.join(map(str, self.matchers))
+
+    def match(self, matchee):
+        results = []
+        for matcher in self.matchers:
+            mismatch = matcher.match(matchee)
+            if mismatch is not None:
+                results.append(mismatch)
+        if results:
+            return MismatchesAll(results)
+        else:
+            return None
+
+
 class MismatchesAll:
     """A mismatch with many child mismatches."""
 
@@ -167,3 +221,31 @@ class MismatchesAll:
             descriptions.append(mismatch.describe())
         descriptions.append("]\n")
         return '\n'.join(descriptions)
+
+
+class Not:
+    """Inverts a matcher."""
+
+    def __init__(self, matcher):
+        self.matcher = matcher
+
+    def __str__(self):
+        return 'Not(%s)' % (self.matcher,)
+
+    def match(self, other):
+        mismatch = self.matcher.match(other)
+        if mismatch is None:
+            return MatchedUnexpectedly(self.matcher, other)
+        else:
+            return None
+
+
+class MatchedUnexpectedly:
+    """A thing matched when it wasn't supposed to."""
+
+    def __init__(self, matcher, other):
+        self.matcher = matcher
+        self.other = other
+
+    def describe(self):
+        return "%r matches %s" % (self.other, self.matcher)
index a1d822ed472e97fe2fbb13662b4487cea3959bb7..fd70141e6d44a50c18de150316241d7e9985ac49 100644 (file)
@@ -203,15 +203,26 @@ class TestCase(unittest.TestCase):
         self.assertTrue(
             needle in haystack, '%r not in %r' % (needle, haystack))
 
-    def assertIs(self, expected, observed):
-        """Assert that `expected` is `observed`."""
+    def assertIs(self, expected, observed, message=''):
+        """Assert that 'expected' is 'observed'.
+
+        :param expected: The expected value.
+        :param observed: The observed value.
+        :param message: An optional message describing the error.
+        """
+        if message:
+            message = ': ' + message
         self.assertTrue(
-            expected is observed, '%r is not %r' % (expected, observed))
+            expected is observed,
+            '%r is not %r%s' % (expected, observed, message))
 
-    def assertIsNot(self, expected, observed):
-        """Assert that `expected` is not `observed`."""
+    def assertIsNot(self, expected, observed, message=''):
+        """Assert that 'expected' is not 'observed'."""
+        if message:
+            message = ': ' + message
         self.assertTrue(
-            expected is not observed, '%r is %r' % (expected, observed))
+            expected is not observed,
+            '%r is %r%s' % (expected, observed, message))
 
     def assertNotIn(self, needle, haystack):
         """Assert that needle is not in haystack."""
@@ -358,7 +369,11 @@ class TestCase(unittest.TestCase):
         """
         self.setUp()
         if not self.__setup_called:
-            raise ValueError("setUp was not called")
+            raise ValueError(
+                "TestCase.setUp was not called. Have you upcalled all the "
+                "way up the hierarchy from your setUp? e.g. Call "
+                "super(%s, self).setUp() from your setUp()."
+                % self.__class__.__name__)
 
     def _run_teardown(self, result):
         """Run the tearDown function for this test.
@@ -369,7 +384,11 @@ class TestCase(unittest.TestCase):
         """
         self.tearDown()
         if not self.__teardown_called:
-            raise ValueError("teardown was not called")
+            raise ValueError(
+                "TestCase.tearDown was not called. Have you upcalled all the "
+                "way up the hierarchy from your tearDown? e.g. Call "
+                "super(%s, self).tearDown() from your tearDown()."
+                % self.__class__.__name__)
 
     def _run_test_method(self, result):
         """Run the test method for this test.
@@ -395,14 +414,19 @@ class TestCase(unittest.TestCase):
         self.__teardown_called = True
 
 
-# Python 2.4 did not know how to deep copy functions.
-if types.FunctionType not in copy._deepcopy_dispatch:
-    copy._deepcopy_dispatch[types.FunctionType] = copy._deepcopy_atomic
+# Python 2.4 did not know how to copy functions.
+if types.FunctionType not in copy._copy_dispatch:
+    copy._copy_dispatch[types.FunctionType] = copy._copy_immutable
+
 
 
 def clone_test_with_new_id(test, new_id):
-    """Copy a TestCase, and give the copied test a new id."""
-    newTest = copy.deepcopy(test)
+    """Copy a TestCase, and give the copied test a new id.
+    
+    This is only expected to be used on tests that have been constructed but
+    not executed.
+    """
+    newTest = copy.copy(test)
     newTest.id = lambda: new_id
     return newTest
 
index e1d1148d5c0638ab4c067eae819b0608b6d8a233..2cceba91e298a9045a54501db39e3da8956af4e4 100644 (file)
@@ -1,3 +1,5 @@
+"""Tests for testtools itself."""
+
 # See README for copyright and licensing details.
 
 import unittest
index a9f4b245eb5241fb1ef1977167e4bb664a6b8217..d5fd8bab3b5843a604faabbcfb169e4cbd3489ff 100644 (file)
@@ -12,6 +12,9 @@ from testtools.matchers import (
     Equals,
     DocTestMatches,
     MatchesAny,
+    MatchesAll,
+    Not,
+    NotEquals,
     )
 
 
@@ -81,6 +84,31 @@ class TestEqualsInterface(TestCase, TestMatchersInterface):
     describe_examples = [("1 != 2", 2, Equals(1))]
 
 
+class TestNotEqualsInterface(TestCase, TestMatchersInterface):
+
+    matches_matcher = NotEquals(1)
+    matches_matches = [2]
+    matches_mismatches = [1]
+
+    str_examples = [
+        ("NotEquals(1)", NotEquals(1)), ("NotEquals('1')", NotEquals('1'))]
+
+    describe_examples = [("1 == 1", 1, NotEquals(1))]
+
+
+class TestNotInterface(TestCase, TestMatchersInterface):
+
+    matches_matcher = Not(Equals(1))
+    matches_matches = [2]
+    matches_mismatches = [1]
+
+    str_examples = [
+        ("Not(Equals(1))", Not(Equals(1))),
+        ("Not(Equals('1'))", Not(Equals('1')))]
+
+    describe_examples = [('1 matches Equals(1)', 1, Not(Equals(1)))]
+
+
 class TestMatchersAnyInterface(TestCase, TestMatchersInterface):
 
     matches_matcher = MatchesAny(DocTestMatches("1"), DocTestMatches("2"))
@@ -108,6 +136,23 @@ Got:
         "3", MatchesAny(DocTestMatches("1"), DocTestMatches("2")))]
 
 
+class TestMatchesAllInterface(TestCase, TestMatchersInterface):
+
+    matches_matcher = MatchesAll(NotEquals(1), NotEquals(2))
+    matches_matches = [3, 4]
+    matches_mismatches = [1, 2]
+
+    str_examples = [
+        ("MatchesAll(NotEquals(1), NotEquals(2))",
+         MatchesAll(NotEquals(1), NotEquals(2)))]
+
+    describe_examples = [("""Differences: [
+1 == 1
+]
+""",
+                          1, MatchesAll(NotEquals(1), NotEquals(2)))]
+
+
 def test_suite():
     from unittest import TestLoader
     return TestLoader().loadTestsFromName(__name__)
index 8cd90de6fe84de6fc92789bfe6b141534d7fe8b1..af1fd794c36c9a69715e64a4ca8a3d1271219a1f 100644 (file)
@@ -223,6 +223,12 @@ class TestAssertions(TestCase):
         self.assertFails('None is not 42', self.assertIs, None, 42)
         self.assertFails('[42] is not [42]', self.assertIs, [42], [42])
 
+    def test_assertIs_fails_with_message(self):
+        # assertIs raises assertion errors if one object is not identical to
+        # another, and includes a user-supplied message, if it's provided.
+        self.assertFails(
+            'None is not 42: foo bar', self.assertIs, None, 42, 'foo bar')
+
     def test_assertIsNot(self):
         # assertIsNot asserts that an object is not identical to another
         # object.
@@ -238,6 +244,12 @@ class TestAssertions(TestCase):
         self.assertFails(
             '[42] is [42]', self.assertIsNot, some_list, some_list)
 
+    def test_assertIsNot_fails_with_message(self):
+        # assertIsNot raises assertion errors if one object is identical to
+        # another, and includes a user-supplied message if it's provided.
+        self.assertFails(
+            'None is None: foo bar', self.assertIsNot, None, None, "foo bar")
+
     def test_assertThat_matches_clean(self):
         class Matcher:
             def match(self, foo):
@@ -303,12 +315,12 @@ class TestAddCleanup(TestCase):
         self.assertEqual(messages, [call[0] for call in self._result_calls])
 
     def assertTestLogEqual(self, messages):
-        """Assert that the call log equals `messages`."""
+        """Assert that the call log equals 'messages'."""
         case = self._result_calls[0][1]
         self.assertEqual(messages, case._calls)
 
     def logAppender(self, message):
-        """A cleanup that appends `message` to the tests log.
+        """A cleanup that appends 'message' to the tests log.
 
         Cleanups are callables that are added to a test by addCleanup. To
         verify that our cleanups run in the right order, we add strings to a
index 325572297b4cee9039c7d51e10e422f46d741a81..c0845b610c6123b2a4b3c961cca188d982f1b68d 100644 (file)
@@ -28,7 +28,7 @@ else:
 
 
 def iterate_tests(test_suite_or_case):
-    """Iterate through all of the test cases in `test_suite_or_case`."""
+    """Iterate through all of the test cases in 'test_suite_or_case'."""
     try:
         suite = iter(test_suite_or_case)
     except TypeError: