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)
     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, rest = parts
             offset = len(cmd) + 1
-            cmd = cmd.strip(':')
+            cmd = cmd.rstrip(':')
             if cmd in ('test', 'testing'):
                 self.startTest(offset, line)
             elif cmd == 'error':
             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 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
 
 
 import sys
 
-from subunit import TestProtocolClient
+from subunit import TestProtocolClient, get_default_formatter
 
 
 class SubunitTestRunner(object):
 
 
 class SubunitTestRunner(object):
@@ -41,6 +41,7 @@ if __name__ == '__main__':
     from unittest import TestProgram
     parser = optparse.OptionParser(__doc__)
     args = parser.parse_args()[1]
     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)
     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"))])
 
         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,
     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).
 
         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)
         """
         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
     :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):
     """
 
     def __init__(self, primary_type, sub_type, parameters=None):
index 947ef601b3b6f2985f789c468ab79c8a4f77004e..244daceb7f4bd05c9d5cbb4f7dc04fd51ce88809 100644 (file)
@@ -14,7 +14,10 @@ __metaclass__ = type
 __all__ = [
     'DocTestMatches',
     'Equals',
 __all__ = [
     'DocTestMatches',
     'Equals',
+    'MatchesAll',
     'MatchesAny',
     'MatchesAny',
+    'NotEquals',
+    'Not',
     ]
 
 import doctest
     ]
 
 import doctest
@@ -135,6 +138,36 @@ class EqualsMismatch:
         return "%r != %r" % (self.expected, self.other)
 
 
         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."""
 
 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])
 
 
             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."""
 
 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)
             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))
 
         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(
         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(
         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."""
 
     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:
         """
         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.
 
     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:
         """
         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.
 
     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
 
 
         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):
 
 
 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
 
     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
 # 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,
     Equals,
     DocTestMatches,
     MatchesAny,
+    MatchesAll,
+    Not,
+    NotEquals,
     )
 
 
     )
 
 
@@ -81,6 +84,31 @@ class TestEqualsInterface(TestCase, TestMatchersInterface):
     describe_examples = [("1 != 2", 2, Equals(1))]
 
 
     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"))
 class TestMatchersAnyInterface(TestCase, TestMatchersInterface):
 
     matches_matcher = MatchesAny(DocTestMatches("1"), DocTestMatches("2"))
@@ -108,6 +136,23 @@ Got:
         "3", MatchesAny(DocTestMatches("1"), DocTestMatches("2")))]
 
 
         "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__)
 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])
 
         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.
     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)
 
         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):
     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):
         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):
         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
 
         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):
 
 
 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:
     try:
         suite = iter(test_suite_or_case)
     except TypeError: