diff options
-rw-r--r-- | Doc/library/unittest.rst | 267 | ||||
-rw-r--r-- | Lib/test/test_gc.py | 2 | ||||
-rw-r--r-- | Lib/test/test_struct.py | 1 | ||||
-rw-r--r-- | Lib/test/test_unittest.py | 633 | ||||
-rw-r--r-- | Lib/unittest.py | 607 |
5 files changed, 1376 insertions, 134 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index c9dc6b6..5034ed4 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1,4 +1,3 @@ - :mod:`unittest` --- Unit testing framework ========================================== @@ -207,8 +206,8 @@ The simplest :class:`TestCase` subclass will simply override the widget = Widget('The widget') self.assertEqual(widget.size(), (50, 50), 'incorrect default size') -Note that in order to test something, we use the one of the :meth:`assert\*` or -:meth:`fail\*` methods provided by the :class:`TestCase` base class. If the +Note that in order to test something, we use the one of the :meth:`assert\*` +methods provided by the :class:`TestCase` base class. If the test fails, an exception will be raised, and :mod:`unittest` will identify the test case as a :dfn:`failure`. Any other exceptions will be treated as :dfn:`errors`. This helps you identify where the problem is: :dfn:`failures` are @@ -238,13 +237,13 @@ us when we run the test:: class DefaultWidgetSizeTestCase(SimpleWidgetTestCase): def runTest(self): - self.failUnless(self.widget.size() == (50,50), + self.assertTrue(self.widget.size() == (50,50), 'incorrect default size') class WidgetResizeTestCase(SimpleWidgetTestCase): def runTest(self): self.widget.resize(100,150) - self.failUnless(self.widget.size() == (100,150), + self.assertTrue(self.widget.size() == (100,150), 'wrong size after resize') If the :meth:`~TestCase.setUp` method raises an exception while the test is @@ -286,12 +285,12 @@ mechanism:: self.widget = None def testDefaultSize(self): - self.failUnless(self.widget.size() == (50,50), + self.assertTrue(self.widget.size() == (50,50), 'incorrect default size') def testResize(self): self.widget.resize(100,150) - self.failUnless(self.widget.size() == (100,150), + self.assertTrue(self.widget.size() == (100,150), 'wrong size after resize') Here we have not provided a :meth:`~TestCase.runTest` method, but have instead @@ -605,23 +604,37 @@ Test cases failures. - .. method:: assert_(expr[, msg]) + .. method:: assertTrue(expr[, msg]) + assert_(expr[, msg]) failUnless(expr[, msg]) - assertTrue(expr[, msg]) Signal a test failure if *expr* is false; the explanation for the error will be *msg* if given, otherwise it will be :const:`None`. + .. deprecated:: 2.7 + :meth:`failUnless`. + .. method:: assertEqual(first, second[, msg]) failUnlessEqual(first, second[, msg]) Test that *first* and *second* are equal. If the values do not compare equal, the test will fail with the explanation given by *msg*, or - :const:`None`. Note that using :meth:`failUnlessEqual` improves upon - doing the comparison as the first parameter to :meth:`failUnless`: the - default value for *msg* can be computed to include representations of both - *first* and *second*. + :const:`None`. Note that using :meth:`assertEqual` improves upon + doing the comparison as the first parameter to :meth:`assertTrue`: the + default value for *msg* include representations of both *first* and + *second*. + + In addition, if *first* and *second* are the exact same type and one of + list, tuple, dict, set, or frozenset or any type that a subclass + registers :meth:`addTypeEqualityFunc` the type specific equality function + will be called in order to generate a more useful default error message. + + .. versionchanged:: 2.7 + Added the automatic calling of type specific equality function. + + .. deprecated:: 2.7 + :meth:`failUnlessEqual`. .. method:: assertNotEqual(first, second[, msg]) @@ -629,11 +642,14 @@ Test cases Test that *first* and *second* are not equal. If the values do compare equal, the test will fail with the explanation given by *msg*, or - :const:`None`. Note that using :meth:`failIfEqual` improves upon doing - the comparison as the first parameter to :meth:`failUnless` is that the + :const:`None`. Note that using :meth:`assertNotEqual` improves upon doing + the comparison as the first parameter to :meth:`assertTrue` is that the default value for *msg* can be computed to include representations of both *first* and *second*. + .. deprecated:: 2.7 + :meth:`failIfEqual`. + .. method:: assertAlmostEqual(first, second[, places[, msg]]) failUnlessAlmostEqual(first, second[, places[, msg]]) @@ -647,6 +663,9 @@ Test cases compare equal, the test will fail with the explanation given by *msg*, or :const:`None`. + .. deprecated:: 2.7 + :meth:`failUnlessAlmostEqual`. + .. method:: assertNotAlmostEqual(first, second[, places[, msg]]) failIfAlmostEqual(first, second[, places[, msg]]) @@ -660,6 +679,128 @@ Test cases compare equal, the test will fail with the explanation given by *msg*, or :const:`None`. + .. deprecated:: 2.7 + :meth:`failIfAlmostEqual`. + + + .. method:: assertGreater(first, second, msg=None) + assertGreaterEqual(first, second, msg=None) + assertLess(first, second, msg=None) + assertLessEqual(first, second, msg=None) + + Test that *first* is respectively >, >=, < or <= than *second* depending + on the method name. If not, the test will fail with the nice explanation + or with the explanation given by *msg*:: + + >>> self.assertGreaterEqual(3, 4) + AssertionError: "3" unexpectedly not greater than or equal to "4" + + .. versionadded:: 2.7 + + + .. method:: assertMultiLineEqual(self, first, second, msg=None) + + Test that the multiline string *first* is equal to the string *second*. + When not equal a diff of the two strings highlighting the differences + will be included in the error message. + + If specified *msg* will be used as the error message on failure. + + .. versionadded:: 2.7 + + + .. method:: assertRegexpMatches(text, regexp[, msg=None]): + + Verifies that a *regexp* search matches *text*. Fails with an error + message including the pattern and the *text*. *regexp* may be + a regular expression object or a string containing a regular expression + suitable for use by :func:`re.search`. + + .. versionadded:: 2.7 + + + .. method:: assertIn(first, second, msg=None) + assertNotIn(first, second, msg=None) + + Tests that *first* is or is not in *second* with a nice explanitory error + message as appropriate. + + If specified *msg* will be used as the error message on failure. + + .. versionadded:: 2.7 + + + .. method:: assertSameElements(expected, actual, msg=None) + + Test that sequence *expected* contains the same elements as *actual*. + When they don't an error message listing the differences between the + sequences will be generated. + + If specified *msg* will be used as the error message on failure. + + .. versionadded:: 2.7 + + + .. method:: assertSetEqual(set1, set2, msg=None) + + Tests that two sets are equal. If not, an error message is constructed + that lists the differences between the sets. + + Fails if either of *set1* or *set2* does not have a :meth:`set.difference` + method. + + If specified *msg* will be used as the error message on failure. + + .. versionadded:: 2.7 + + + .. method:: assertDictEqual(expected, actual, msg=None) + + Test that two dictionaries are equal. If not, an error message is + constructed that shows the differences in the dictionaries. + + If specified *msg* will be used as the error message on failure. + + .. versionadded:: 2.7 + + + .. method:: assertDictContainsSubset(expected, actual, msg=None) + + Tests whether the key value pairs in dictionary *actual* are a + superset of those in *expected*. If not, an error message listing + the missing keys and mismatched values is generated. + + If specified *msg* will be used as the error message on failure. + + .. versionadded:: 2.7 + + + .. method:: assertListEqual(list1, list2, msg=None) + assertTupleEqual(tuple1, tuple2, msg=None) + + Tests that two lists or tuples are equal. If not an error message is + constructed that shows only the differences between the two. An error + is also raised if either of the parameters are of the wrong type. + + If specified *msg* will be used as the error message on failure. + + .. versionadded:: 2.7 + + + .. method:: assertSequenceEqual(seq1, seq2, msg=None, seq_type=None) + + Tests that two sequences are equal. If a *seq_type* is supplied, both + *seq1* and *seq2* must be instances of *seq_type* or a failure will + be raised. If the sequences are different an error message is + constructed that shows the difference between the two. + + If specified *msg* will be used as the error message on failure. + + This method is used to implement :meth:`assertListEqual` and + :meth:`assertTupleEqual`. + + .. versionadded:: 2.7 + .. method:: assertRaises(exception[, callable, ...]) failUnlessRaises(exception[, callable, ...]) @@ -680,14 +821,53 @@ Test cases .. versionchanged:: 3.1 Added the ability to use :meth:`assertRaises` as a context manager. + .. deprecated:: 2.7 + :meth:`failUnlessRaises`. + + + .. method:: assertRaisesRegexp(exception, regexp[, callable, ...]) + + Like :meth:`assertRaises` but also tests that *regexp* matches + on the string representation of the raised exception. *regexp* may be + a regular expression object or a string containing a regular expression + suitable for use by :func:`re.search`. Examples:: + + self.assertRaisesRegexp(ValueError, 'invalid literal for.*XYZ$', + int, 'XYZ') + + or:: + + with self.assertRaisesRegexp(ValueError, 'literal'): + int('XYZ') + + .. versionadded:: 2.7 + + + .. method:: assertIsNone(expr[, msg]) - .. method:: failIf(expr[, msg]) - assertFalse(expr[, msg]) + This signals a test failure if *expr* is not None. - The inverse of the :meth:`failUnless` method is the :meth:`failIf` method. + .. versionadded:: 2.7 + + + .. method:: assertIsNotNone(expr[, msg]) + + The inverse of the :meth:`assertIsNone` method. + This signals a test failure if *expr* is None. + + .. versionadded:: 2.7 + + + .. method:: assertFalse(expr[, msg]) + failIf(expr[, msg]) + + The inverse of the :meth:`assertTrue` method is the :meth:`assertFalse` method. This signals a test failure if *expr* is true, with *msg* or :const:`None` for the error message. + .. deprecated:: 2.7 + :meth:`failIf`. + .. method:: fail([msg]) @@ -703,6 +883,25 @@ Test cases fair" with the framework. The initial value of this attribute is :exc:`AssertionError`. + + .. attribute:: longMessage + + If set to True then any explicit failure message you pass in to the + assert methods will be appended to the end of the normal failure message. + The normal messages contain useful information about the objects involved, + for example the message from assertEqual shows you the repr of the two + unequal objects. Setting this attribute to True allows you to have a + custom error message in addition to the normal one. + + This attribute defaults to False, meaning that a custom message passed + to an assert method will silence the normal message. + + The class setting can be overridden in individual tests by assigning an + instance attribute to True or False before calling the assert methods. + + .. versionadded:: 2.7 + + Testing frameworks can use the following methods to collect information on the test: @@ -732,10 +931,34 @@ Test cases .. method:: shortDescription() - Returns a one-line description of the test, or :const:`None` if no - description has been provided. The default implementation of this method - returns the first line of the test method's docstring, if available, or - :const:`None`. + Returns a description of the test, or :const:`None` if no description + has been provided. The default implementation of this method + returns the first line of the test method's docstring, if available, + along with the method name. + + .. versionchanged:: 2.7 + + In earlier versions this only returned the first line of the test + method's docstring, if available or the :const:`None`. That led to + undesirable behavior of not printing the test name when someone was + thoughtful enough to write a docstring. + + + .. method:: addTypeEqualityFunc(typeobj, function) + + Registers a type specific :meth:`assertEqual` equality checking + function to be called by :meth:`assertEqual` when both objects it has + been asked to compare are exactly *typeobj* (not subclasses). + *function* must take two positional arguments and a third msg=None + keyword argument just as :meth:`assertEqual` does. It must raise + self.failureException when inequality between the first two + parameters is detected. + + One good use of custom equality checking functions for a type + is to raise self.failureException with an error message useful + for debugging the by explaining the inequalities in detail. + + .. versionadded:: 2.7 .. class:: FunctionTestCase(testFunc[, setUp[, tearDown[, description]]]) diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 2262b36..3b7df99 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -244,7 +244,7 @@ class GCTests(unittest.TestCase): # - the call to assertEqual somehow avoids building its args tuple def test_get_count(self): # Avoid future allocation of method object - assertEqual = self.assertEqual + assertEqual = self._baseAssertEqual gc.collect() assertEqual(gc.get_count(), (0, 0, 0)) a = dict() diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index beeee8f..982047a 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -207,6 +207,7 @@ class StructTest(unittest.TestCase): class IntTester(unittest.TestCase): def __init__(self, formatpair, bytesize): + super(IntTester, self).__init__(methodName='test_one') self.assertEqual(len(formatpair), 2) self.formatpair = formatpair for direction in "<>!=": diff --git a/Lib/test/test_unittest.py b/Lib/test/test_unittest.py index b7272f1..0782119 100644 --- a/Lib/test/test_unittest.py +++ b/Lib/test/test_unittest.py @@ -6,10 +6,12 @@ Still need testing: TestCase.{assert,fail}* methods (some are tested implicitly) """ +import re from test import support import unittest from unittest import TestCase import types +from copy import deepcopy ### Support code ################################################################ @@ -53,6 +55,8 @@ class LoggingResult(unittest.TestResult): class TestEquality(object): + """Used as a mixin for TestCase""" + # Check for a valid __eq__ implementation def test_eq(self): for obj_1, obj_2 in self.eq_pairs: @@ -66,25 +70,26 @@ class TestEquality(object): self.failIfEqual(obj_2, obj_1) class TestHashing(object): + """Used as a mixin for TestCase""" + # Check for a valid __hash__ implementation def test_hash(self): for obj_1, obj_2 in self.eq_pairs: try: - assert hash(obj_1) == hash(obj_2) + if not hash(obj_1) == hash(obj_2): + self.fail("%r and %r do not hash equal" % (obj_1, obj_2)) except KeyboardInterrupt: raise - except AssertionError: - self.fail("%s and %s do not hash equal" % (obj_1, obj_2)) except Exception as e: - self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) + self.fail("Problem hashing %r and %r: %s" % (obj_1, obj_2, e)) for obj_1, obj_2 in self.ne_pairs: try: - assert hash(obj_1) != hash(obj_2) + if hash(obj_1) == hash(obj_2): + self.fail("%s and %s hash equal, but shouldn't" % + (obj_1, obj_2)) except KeyboardInterrupt: raise - except AssertionError: - self.fail("%s and %s hash equal, but shouldn't" % (obj_1, obj_2)) except Exception as e: self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) @@ -2247,39 +2252,6 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): self.failUnless(isinstance(Foo().id(), str)) - # "Returns a one-line description of the test, or None if no description - # has been provided. The default implementation of this method returns - # the first line of the test method's docstring, if available, or None." - def test_shortDescription__no_docstring(self): - class Foo(unittest.TestCase): - def runTest(self): - pass - - self.assertEqual(Foo().shortDescription(), None) - - # "Returns a one-line description of the test, or None if no description - # has been provided. The default implementation of this method returns - # the first line of the test method's docstring, if available, or None." - def test_shortDescription__singleline_docstring(self): - class Foo(unittest.TestCase): - def runTest(self): - "this tests foo" - pass - - self.assertEqual(Foo().shortDescription(), "this tests foo") - - # "Returns a one-line description of the test, or None if no description - # has been provided. The default implementation of this method returns - # the first line of the test method's docstring, if available, or None." - def test_shortDescription__multiline_docstring(self): - class Foo(unittest.TestCase): - def runTest(self): - """this tests foo - blah, bar and baz are also tested""" - pass - - self.assertEqual(Foo().shortDescription(), "this tests foo") - # "If result is omitted or None, a temporary result object is created # and used, but is not made available to the caller" def test_run__uses_defaultTestResult(self): @@ -2298,6 +2270,405 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): expected = ['startTest', 'test', 'addSuccess', 'stopTest'] self.assertEqual(events, expected) + def testShortDescriptionWithoutDocstring(self): + self.assertEqual( + self.shortDescription(), + 'testShortDescriptionWithoutDocstring (' + __name__ + + '.Test_TestCase)') + + def testShortDescriptionWithOneLineDocstring(self): + """Tests shortDescription() for a method with a docstring.""" + self.assertEqual( + self.shortDescription(), + ('testShortDescriptionWithOneLineDocstring ' + '(' + __name__ + '.Test_TestCase)\n' + 'Tests shortDescription() for a method with a docstring.')) + + def testShortDescriptionWithMultiLineDocstring(self): + """Tests shortDescription() for a method with a longer docstring. + + This method ensures that only the first line of a docstring is + returned used in the short description, no matter how long the + whole thing is. + """ + self.assertEqual( + self.shortDescription(), + ('testShortDescriptionWithMultiLineDocstring ' + '(' + __name__ + '.Test_TestCase)\n' + 'Tests shortDescription() for a method with a longer ' + 'docstring.')) + + def testAddTypeEqualityFunc(self): + class SadSnake(object): + """Dummy class for test_addTypeEqualityFunc.""" + s1, s2 = SadSnake(), SadSnake() + self.assertFalse(s1 == s2) + def AllSnakesCreatedEqual(a, b, msg=None): + return type(a) == type(b) == SadSnake + self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual) + self.assertEqual(s1, s2) + # No this doesn't clean up and remove the SadSnake equality func + # from this TestCase instance but since its a local nothing else + # will ever notice that. + + def testAssertIn(self): + animals = {'monkey': 'banana', 'cow': 'grass', 'seal': 'fish'} + + self.assertIn('a', 'abc') + self.assertIn(2, [1, 2, 3]) + self.assertIn('monkey', animals) + + self.assertNotIn('d', 'abc') + self.assertNotIn(0, [1, 2, 3]) + self.assertNotIn('otter', animals) + + self.assertRaises(self.failureException, self.assertIn, 'x', 'abc') + self.assertRaises(self.failureException, self.assertIn, 4, [1, 2, 3]) + self.assertRaises(self.failureException, self.assertIn, 'elephant', + animals) + + self.assertRaises(self.failureException, self.assertNotIn, 'c', 'abc') + self.assertRaises(self.failureException, self.assertNotIn, 1, [1, 2, 3]) + self.assertRaises(self.failureException, self.assertNotIn, 'cow', + animals) + + def testAssertDictContainsSubset(self): + self.assertDictContainsSubset({}, {}) + self.assertDictContainsSubset({}, {'a': 1}) + self.assertDictContainsSubset({'a': 1}, {'a': 1}) + self.assertDictContainsSubset({'a': 1}, {'a': 1, 'b': 2}) + self.assertDictContainsSubset({'a': 1, 'b': 2}, {'a': 1, 'b': 2}) + + self.assertRaises(unittest.TestCase.failureException, + self.assertDictContainsSubset, {'a': 2}, {'a': 1}, + '.*Mismatched values:.*') + + self.assertRaises(unittest.TestCase.failureException, + self.assertDictContainsSubset, {'c': 1}, {'a': 1}, + '.*Missing:.*') + + self.assertRaises(unittest.TestCase.failureException, + self.assertDictContainsSubset, {'a': 1, 'c': 1}, + {'a': 1}, '.*Missing:.*') + + self.assertRaises(unittest.TestCase.failureException, + self.assertDictContainsSubset, {'a': 1, 'c': 1}, + {'a': 1}, '.*Missing:.*Mismatched values:.*') + + def testAssertEqual(self): + equal_pairs = [ + ((), ()), + ({}, {}), + ([], []), + (set(), set()), + (frozenset(), frozenset())] + for a, b in equal_pairs: + # This mess of try excepts is to test the assertEqual behavior + # itself. + try: + self.assertEqual(a, b) + except self.failureException: + self.fail('assertEqual(%r, %r) failed' % (a, b)) + try: + self.assertEqual(a, b, msg='foo') + except self.failureException: + self.fail('assertEqual(%r, %r) with msg= failed' % (a, b)) + try: + self.assertEqual(a, b, 'foo') + except self.failureException: + self.fail('assertEqual(%r, %r) with third parameter failed' % + (a, b)) + + unequal_pairs = [ + ((), []), + ({}, set()), + (set([4,1]), frozenset([4,2])), + (frozenset([4,5]), set([2,3])), + (set([3,4]), set([5,4]))] + for a, b in unequal_pairs: + self.assertRaises(self.failureException, self.assertEqual, a, b) + self.assertRaises(self.failureException, self.assertEqual, a, b, + 'foo') + self.assertRaises(self.failureException, self.assertEqual, a, b, + msg='foo') + + def testEquality(self): + self.assertListEqual([], []) + self.assertTupleEqual((), ()) + self.assertSequenceEqual([], ()) + + a = [0, 'a', []] + b = [] + self.assertRaises(unittest.TestCase.failureException, + self.assertListEqual, a, b) + self.assertRaises(unittest.TestCase.failureException, + self.assertListEqual, tuple(a), tuple(b)) + self.assertRaises(unittest.TestCase.failureException, + self.assertSequenceEqual, a, tuple(b)) + + b.extend(a) + self.assertListEqual(a, b) + self.assertTupleEqual(tuple(a), tuple(b)) + self.assertSequenceEqual(a, tuple(b)) + self.assertSequenceEqual(tuple(a), b) + + self.assertRaises(self.failureException, self.assertListEqual, + a, tuple(b)) + self.assertRaises(self.failureException, self.assertTupleEqual, + tuple(a), b) + self.assertRaises(self.failureException, self.assertListEqual, None, b) + self.assertRaises(self.failureException, self.assertTupleEqual, None, + tuple(b)) + self.assertRaises(self.failureException, self.assertSequenceEqual, + None, tuple(b)) + self.assertRaises(self.failureException, self.assertListEqual, 1, 1) + self.assertRaises(self.failureException, self.assertTupleEqual, 1, 1) + self.assertRaises(self.failureException, self.assertSequenceEqual, + 1, 1) + + self.assertDictEqual({}, {}) + + c = { 'x': 1 } + d = {} + self.assertRaises(unittest.TestCase.failureException, + self.assertDictEqual, c, d) + + d.update(c) + self.assertDictEqual(c, d) + + d['x'] = 0 + self.assertRaises(unittest.TestCase.failureException, + self.assertDictEqual, c, d, 'These are unequal') + + self.assertRaises(self.failureException, self.assertDictEqual, None, d) + self.assertRaises(self.failureException, self.assertDictEqual, [], d) + self.assertRaises(self.failureException, self.assertDictEqual, 1, 1) + + self.assertSameElements([1, 2, 3], [3, 2, 1]) + self.assertSameElements([1, 2] + [3] * 100, [1] * 100 + [2, 3]) + self.assertSameElements(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) + self.assertRaises(self.failureException, self.assertSameElements, + [10], [10, 11]) + self.assertRaises(self.failureException, self.assertSameElements, + [10, 11], [10]) + + # Test that sequences of unhashable objects can be tested for sameness: + self.assertSameElements([[1, 2], [3, 4]], [[3, 4], [1, 2]]) + self.assertRaises(self.failureException, self.assertSameElements, + [[1]], [[2]]) + + def testAssertSetEqual(self): + set1 = set() + set2 = set() + self.assertSetEqual(set1, set2) + + self.assertRaises(self.failureException, self.assertSetEqual, None, set2) + self.assertRaises(self.failureException, self.assertSetEqual, [], set2) + self.assertRaises(self.failureException, self.assertSetEqual, set1, None) + self.assertRaises(self.failureException, self.assertSetEqual, set1, []) + + set1 = set(['a']) + set2 = set() + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a']) + set2 = set(['a']) + self.assertSetEqual(set1, set2) + + set1 = set(['a']) + set2 = set(['a', 'b']) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a']) + set2 = frozenset(['a', 'b']) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a', 'b']) + set2 = frozenset(['a', 'b']) + self.assertSetEqual(set1, set2) + + set1 = set() + set2 = "foo" + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + self.assertRaises(self.failureException, self.assertSetEqual, set2, set1) + + # make sure any string formatting is tuple-safe + set1 = set([(0, 1), (2, 3)]) + set2 = set([(4, 5)]) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + def testInequality(self): + # Try ints + self.assertGreater(2, 1) + self.assertGreaterEqual(2, 1) + self.assertGreaterEqual(1, 1) + self.assertLess(1, 2) + self.assertLessEqual(1, 2) + self.assertLessEqual(1, 1) + self.assertRaises(self.failureException, self.assertGreater, 1, 2) + self.assertRaises(self.failureException, self.assertGreater, 1, 1) + self.assertRaises(self.failureException, self.assertGreaterEqual, 1, 2) + self.assertRaises(self.failureException, self.assertLess, 2, 1) + self.assertRaises(self.failureException, self.assertLess, 1, 1) + self.assertRaises(self.failureException, self.assertLessEqual, 2, 1) + + # Try Floats + self.assertGreater(1.1, 1.0) + self.assertGreaterEqual(1.1, 1.0) + self.assertGreaterEqual(1.0, 1.0) + self.assertLess(1.0, 1.1) + self.assertLessEqual(1.0, 1.1) + self.assertLessEqual(1.0, 1.0) + self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.1) + self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.0) + self.assertRaises(self.failureException, self.assertGreaterEqual, 1.0, 1.1) + self.assertRaises(self.failureException, self.assertLess, 1.1, 1.0) + self.assertRaises(self.failureException, self.assertLess, 1.0, 1.0) + self.assertRaises(self.failureException, self.assertLessEqual, 1.1, 1.0) + + # Try Strings + self.assertGreater('bug', 'ant') + self.assertGreaterEqual('bug', 'ant') + self.assertGreaterEqual('ant', 'ant') + self.assertLess('ant', 'bug') + self.assertLessEqual('ant', 'bug') + self.assertLessEqual('ant', 'ant') + self.assertRaises(self.failureException, self.assertGreater, 'ant', 'bug') + self.assertRaises(self.failureException, self.assertGreater, 'ant', 'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, 'ant', 'bug') + self.assertRaises(self.failureException, self.assertLess, 'bug', 'ant') + self.assertRaises(self.failureException, self.assertLess, 'ant', 'ant') + self.assertRaises(self.failureException, self.assertLessEqual, 'bug', 'ant') + + # Try bytes + self.assertGreater(b'bug', b'ant') + self.assertGreaterEqual(b'bug', b'ant') + self.assertGreaterEqual(b'ant', b'ant') + self.assertLess(b'ant', b'bug') + self.assertLessEqual(b'ant', b'bug') + self.assertLessEqual(b'ant', b'ant') + self.assertRaises(self.failureException, self.assertGreater, b'ant', b'bug') + self.assertRaises(self.failureException, self.assertGreater, b'ant', b'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, b'ant', + b'bug') + self.assertRaises(self.failureException, self.assertLess, b'bug', b'ant') + self.assertRaises(self.failureException, self.assertLess, b'ant', b'ant') + self.assertRaises(self.failureException, self.assertLessEqual, b'bug', b'ant') + + def testAssertMultiLineEqual(self): + sample_text = """\ +http://www.python.org/doc/2.3/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] +""" + revised_sample_text = """\ +http://www.python.org/doc/2.4.1/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] You may provide your + own implementation that does not subclass from TestCase, of course. +""" + sample_text_error = """ +- http://www.python.org/doc/2.3/lib/module-unittest.html +? ^ ++ http://www.python.org/doc/2.4.1/lib/module-unittest.html +? ^^^ + test case +- A test case is the smallest unit of testing. [...] ++ A test case is the smallest unit of testing. [...] You may provide your +? +++++++++++++++++++++ ++ own implementation that does not subclass from TestCase, of course. +""" + + try: + self.assertMultiLineEqual(sample_text, revised_sample_text) + except self.failureException as e: + # no fair testing ourself with ourself, use assertEqual.. + self.assertEqual(sample_text_error, str(e)) + + def testAssertIsNone(self): + self.assertIsNone(None) + self.assertRaises(self.failureException, self.assertIsNone, False) + self.assertIsNotNone('DjZoPloGears on Rails') + self.assertRaises(self.failureException, self.assertIsNotNone, None) + + def testAssertRegexpMatches(self): + self.assertRegexpMatches('asdfabasdf', r'ab+') + self.assertRaises(self.failureException, self.assertRegexpMatches, + 'saaas', r'aaaa') + + def testAssertRaisesRegexp(self): + class ExceptionMock(Exception): + pass + + def Stub(): + raise ExceptionMock('We expect') + + self.assertRaisesRegexp(ExceptionMock, re.compile('expect$'), Stub) + self.assertRaisesRegexp(ExceptionMock, 'expect$', Stub) + + def testAssertNotRaisesRegexp(self): + self.assertRaisesRegexp( + self.failureException, '^Exception not raised$', + self.assertRaisesRegexp, Exception, re.compile('x'), + lambda: None) + self.assertRaisesRegexp( + self.failureException, '^Exception not raised$', + self.assertRaisesRegexp, Exception, 'x', + lambda: None) + + def testAssertRaisesRegexpMismatch(self): + def Stub(): + raise Exception('Unexpected') + + self.assertRaisesRegexp( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegexp, Exception, '^Expected$', + Stub) + self.assertRaisesRegexp( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegexp, Exception, + re.compile('^Expected$'), Stub) + + def testSynonymAssertMethodNames(self): + """Test undocumented method name synonyms. + + Please do not use these methods names in your own code. + + This test confirms their continued existence and functionality + in order to avoid breaking existing code. + """ + self.assertNotEquals(3, 5) + self.assertEquals(3, 3) + self.assertAlmostEquals(2.0, 2.0) + self.assertNotAlmostEquals(3.0, 5.0) + self.assert_(True) + + def testPendingDeprecationMethodNames(self): + """Test fail* methods pending deprecation, they will warn in 3.2. + + Do not use these methods. They will go away in 3.3. + """ + self.failIfEqual(3, 5) + self.failUnlessEqual(3, 3) + self.failUnlessAlmostEqual(2.0, 2.0) + self.failIfAlmostEqual(3.0, 5.0) + self.failUnless(True) + self.failUnlessRaises(TypeError, lambda _: 3.14 + 'spam') + self.failIf(False) + + def testDeepcopy(self): + # Issue: 5660 + class TestableTest(TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + + # This shouldn't blow up + deepcopy(test) + class Test_TestSkipping(TestCase): @@ -2396,20 +2767,20 @@ class Test_Assertions(TestCase): def test_AlmostEqual(self): self.failUnlessAlmostEqual(1.00000001, 1.0) self.failIfAlmostEqual(1.0000001, 1.0) - self.assertRaises(AssertionError, + self.assertRaises(self.failureException, self.failUnlessAlmostEqual, 1.0000001, 1.0) - self.assertRaises(AssertionError, + self.assertRaises(self.failureException, self.failIfAlmostEqual, 1.00000001, 1.0) self.failUnlessAlmostEqual(1.1, 1.0, places=0) - self.assertRaises(AssertionError, + self.assertRaises(self.failureException, self.failUnlessAlmostEqual, 1.1, 1.0, places=1) self.failUnlessAlmostEqual(0, .1+.1j, places=0) self.failIfAlmostEqual(0, .1+.1j, places=1) - self.assertRaises(AssertionError, + self.assertRaises(self.failureException, self.failUnlessAlmostEqual, 0, .1+.1j, places=1) - self.assertRaises(AssertionError, + self.assertRaises(self.failureException, self.failIfAlmostEqual, 0, .1+.1j, places=0) def test_assertRaises(self): @@ -2419,7 +2790,7 @@ class Test_Assertions(TestCase): self.assertRaises(KeyError, _raise, KeyError("key")) try: self.assertRaises(KeyError, lambda: None) - except AssertionError as e: + except self.failureException as e: self.assert_("KeyError not raised" in str(e), str(e)) else: self.fail("assertRaises() didn't fail") @@ -2436,7 +2807,7 @@ class Test_Assertions(TestCase): try: with self.assertRaises(KeyError): pass - except AssertionError as e: + except self.failureException as e: self.assert_("KeyError not raised" in str(e), str(e)) else: self.fail("assertRaises() didn't fail") @@ -2449,6 +2820,172 @@ class Test_Assertions(TestCase): self.fail("assertRaises() didn't let exception pass through") +class TestLongMessage(TestCase): + """Test that the individual asserts honour longMessage. + This actually tests all the message behaviour for + asserts that use longMessage.""" + + def setUp(self): + class TestableTestFalse(TestCase): + longMessage = False + failureException = self.failureException + + def testTest(self): + pass + + class TestableTestTrue(TestCase): + longMessage = True + failureException = self.failureException + + def testTest(self): + pass + + self.testableTrue = TestableTestTrue('testTest') + self.testableFalse = TestableTestFalse('testTest') + + def testDefault(self): + self.assertFalse(TestCase.longMessage) + + def test_formatMsg(self): + self.assertEquals(self.testableFalse._formatMessage(None, "foo"), "foo") + self.assertEquals(self.testableFalse._formatMessage("foo", "bar"), "foo") + + self.assertEquals(self.testableTrue._formatMessage(None, "foo"), "foo") + self.assertEquals(self.testableTrue._formatMessage("foo", "bar"), "bar : foo") + + def assertMessages(self, methodName, args, errors): + def getMethod(i): + useTestableFalse = i < 2 + if useTestableFalse: + test = self.testableFalse + else: + test = self.testableTrue + return getattr(test, methodName) + + for i, expected_regexp in enumerate(errors): + testMethod = getMethod(i) + kwargs = {} + withMsg = i % 2 + if withMsg: + kwargs = {"msg": "oops"} + + with self.assertRaisesRegexp(self.failureException, + expected_regexp=expected_regexp): + testMethod(*args, **kwargs) + + def testAssertTrue(self): + self.assertMessages('assertTrue', (False,), + ["^False is not True$", "^oops$", "^False is not True$", + "^False is not True : oops$"]) + + def testAssertFalse(self): + self.assertMessages('assertFalse', (True,), + ["^True is not False$", "^oops$", "^True is not False$", + "^True is not False : oops$"]) + + def testNotEqual(self): + self.assertMessages('assertNotEqual', (1, 1), + ["^1 == 1$", "^oops$", "^1 == 1$", + "^1 == 1 : oops$"]) + + def testAlmostEqual(self): + self.assertMessages('assertAlmostEqual', (1, 2), + ["^1 != 2 within 7 places$", "^oops$", + "^1 != 2 within 7 places$", "^1 != 2 within 7 places : oops$"]) + + def testNotAlmostEqual(self): + self.assertMessages('assertNotAlmostEqual', (1, 1), + ["^1 == 1 within 7 places$", "^oops$", + "^1 == 1 within 7 places$", "^1 == 1 within 7 places : oops$"]) + + def test_baseAssertEqual(self): + self.assertMessages('_baseAssertEqual', (1, 2), + ["^1 != 2$", "^oops$", "^1 != 2$", "^1 != 2 : oops$"]) + + def testAssertSequenceEqual(self): + # Error messages are multiline so not testing on full message + # assertTupleEqual and assertListEqual delegate to this method + self.assertMessages('assertSequenceEqual', ([], [None]), + ["\+ \[None\]$", "^oops$", r"\+ \[None\]$", + r"\+ \[None\] : oops$"]) + + def testAssertSetEqual(self): + self.assertMessages('assertSetEqual', (set(), set([None])), + ["None$", "^oops$", "None$", + "None : oops$"]) + + def testAssertIn(self): + self.assertMessages('assertIn', (None, []), + ['^None not found in \[\]$', "^oops$", + '^None not found in \[\]$', + '^None not found in \[\] : oops$']) + + def testAssertNotIn(self): + self.assertMessages('assertNotIn', (None, [None]), + ['^None unexpectedly found in \[None\]$', "^oops$", + '^None unexpectedly found in \[None\]$', + '^None unexpectedly found in \[None\] : oops$']) + + def testAssertDictEqual(self): + self.assertMessages('assertDictEqual', ({}, {'key': 'value'}), + [r"\+ \{'key': 'value'\}$", "^oops$", + "\+ \{'key': 'value'\}$", + "\+ \{'key': 'value'\} : oops$"]) + + def testAssertDictContainsSubset(self): + self.assertMessages('assertDictContainsSubset', ({'key': 'value'}, {}), + ["^Missing: 'key'$", "^oops$", + "^Missing: 'key'$", + "^Missing: 'key' : oops$"]) + + def testAssertSameElements(self): + self.assertMessages('assertSameElements', ([], [None]), + [r"\[None\]$", "^oops$", + r"\[None\]$", + r"\[None\] : oops$"]) + + def testAssertMultiLineEqual(self): + self.assertMessages('assertMultiLineEqual', ("", "foo"), + [r"\+ foo$", "^oops$", + r"\+ foo$", + r"\+ foo : oops$"]) + + def testAssertLess(self): + self.assertMessages('assertLess', (2, 1), + ["^2 not less than 1$", "^oops$", + "^2 not less than 1$", "^2 not less than 1 : oops$"]) + + def testAssertLessEqual(self): + self.assertMessages('assertLessEqual', (2, 1), + ["^2 not less than or equal to 1$", "^oops$", + "^2 not less than or equal to 1$", + "^2 not less than or equal to 1 : oops$"]) + + def testAssertGreater(self): + self.assertMessages('assertGreater', (1, 2), + ["^1 not greater than 2$", "^oops$", + "^1 not greater than 2$", + "^1 not greater than 2 : oops$"]) + + def testAssertGreaterEqual(self): + self.assertMessages('assertGreaterEqual', (1, 2), + ["^1 not greater than or equal to 2$", "^oops$", + "^1 not greater than or equal to 2$", + "^1 not greater than or equal to 2 : oops$"]) + + def testAssertIsNone(self): + self.assertMessages('assertIsNone', ('not None',), + ["^'not None' is not None$", "^oops$", + "^'not None' is not None$", + "^'not None' is not None : oops$"]) + + def testAssertIsNotNone(self): + self.assertMessages('assertIsNotNone', (None,), + ["^unexpectedly None$", "^oops$", + "^unexpectedly None$", + "^unexpectedly None : oops$"]) + + ###################################################################### ## Main ###################################################################### @@ -2456,7 +2993,7 @@ class Test_Assertions(TestCase): def test_main(): support.run_unittest(Test_TestCase, Test_TestLoader, Test_TestSuite, Test_TestResult, Test_FunctionTestCase, - Test_TestSkipping, Test_Assertions) + Test_TestSkipping, Test_Assertions, TestLongMessage) if __name__ == "__main__": test_main() diff --git a/Lib/unittest.py b/Lib/unittest.py index ade806c..16a8663 100644 --- a/Lib/unittest.py +++ b/Lib/unittest.py @@ -14,11 +14,11 @@ Simple usage: class IntegerArithmenticTestCase(unittest.TestCase): def testAdd(self): ## test method names begin 'test*' - self.assertEquals((1 + 2), 3) - self.assertEquals(0 + 1, 1) + self.assertEqual((1 + 2), 3) + self.assertEqual(0 + 1, 1) def testMultiply(self): - self.assertEquals((0 * 10), 0) - self.assertEquals((5 * 8), 40) + self.assertEqual((0 * 10), 0) + self.assertEqual((5 * 8), 40) if __name__ == '__main__': unittest.main() @@ -45,12 +45,16 @@ AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. ''' -import time +import difflib +import functools +import os +import pprint +import re import sys +import time import traceback -import os import types -import functools +import warnings ############################################################################## # Exported classes and functions @@ -143,7 +147,6 @@ def expectedFailure(func): raise _UnexpectedSuccess return wrapper - __unittest = 1 class TestResult(object): @@ -239,10 +242,12 @@ class TestResult(object): len(self.failures)) -class AssertRaisesContext(object): +class _AssertRaisesContext(object): + """A context manager used to implement TestCase.assertRaises* methods.""" - def __init__(self, expected, test_case, callable_obj=None): + def __init__(self, expected, test_case, callable_obj=None, + expected_regexp=None): self.expected = expected self.failureException = test_case.failureException if callable_obj is not None: @@ -252,6 +257,7 @@ class AssertRaisesContext(object): self.obj_name = str(callable_obj) else: self.obj_name = None + self.expected_regex = expected_regexp def __enter__(self): pass @@ -268,10 +274,30 @@ class AssertRaisesContext(object): else: raise self.failureException("{0} not raised" .format(exc_name)) - if issubclass(exc_type, self.expected): + if not issubclass(exc_type, self.expected): + # let unexpected exceptions pass through + return False + if self.expected_regex is None: return True - # Let unexpected exceptions skip through - return False + + expected_regexp = self.expected_regex + if isinstance(expected_regexp, (bytes, str)): + expected_regexp = re.compile(expected_regexp) + if not expected_regexp.search(str(exc_value)): + raise self.failureException('"%s" does not match "%s"' % + (expected_regexp.pattern, str(exc_value))) + return True + + +class _AssertWrapper(object): + """Wrap entries in the _type_equality_funcs registry to make them deep + copyable.""" + + def __init__(self, function): + self.function = function + + def __deepcopy__(self, memo): + memo[id(self)] = self class TestCase(object): @@ -302,6 +328,13 @@ class TestCase(object): failureException = AssertionError + # This attribute determines whether long messages (including repr of + # objects used in assert methods) will be printed on failure in *addition* + # to any explicit message passed. + + longMessage = False + + def __init__(self, methodName='runTest'): """Create an instance of the class that will use the named test method when executed. Raises a ValueError if the instance does @@ -315,6 +348,31 @@ class TestCase(object): (self.__class__, methodName)) self._testMethodDoc = testMethod.__doc__ + # Map types to custom assertEqual functions that will compare + # instances of said type in more detail to generate a more useful + # error message. + self._type_equality_funcs = {} + self.addTypeEqualityFunc(dict, self.assertDictEqual) + self.addTypeEqualityFunc(list, self.assertListEqual) + self.addTypeEqualityFunc(tuple, self.assertTupleEqual) + self.addTypeEqualityFunc(set, self.assertSetEqual) + self.addTypeEqualityFunc(frozenset, self.assertSetEqual) + + def addTypeEqualityFunc(self, typeobj, function): + """Add a type specific assertEqual style function to compare a type. + + This method is for use by TestCase subclasses that need to register + their own type equality functions to provide nicer error messages. + + Args: + typeobj: The data type to call this function on when both values + are of the same type in assertEqual(). + function: The callable taking two arguments and an optional + msg= argument that raises self.failureException with a + useful error message when the two arguments are not equal. + """ + self._type_equality_funcs[typeobj] = _AssertWrapper(function) + def setUp(self): "Hook method for setting up the test fixture before exercising it." pass @@ -330,14 +388,22 @@ class TestCase(object): return TestResult() def shortDescription(self): - """Returns a one-line description of the test, or None if no - description has been provided. + """Returns both the test method name and first line of its docstring. - The default implementation of this method returns the first line of - the specified test method's docstring. + If no docstring is given, only returns the method name. + + This method overrides unittest.TestCase.shortDescription(), which + only returns the first line of the docstring, obscuring the name + of the test upon failure. """ - doc = self._testMethodDoc - return doc and doc.split("\n")[0].strip() or None + desc = str(self) + doc_first_line = None + + if self._testMethodDoc: + doc_first_line = self._testMethodDoc.split("\n")[0].strip() + if doc_first_line: + desc = '\n'.join((desc, doc_first_line)) + return desc def id(self): return "%s.%s" % (_strclass(self.__class__), self._testMethodName) @@ -419,17 +485,36 @@ class TestCase(object): """Fail immediately, with the given message.""" raise self.failureException(msg) - def failIf(self, expr, msg=None): + def assertFalse(self, expr, msg=None): "Fail the test if the expression is true." if expr: + msg = self._formatMessage(msg, "%r is not False" % expr) raise self.failureException(msg) - def failUnless(self, expr, msg=None): + def assertTrue(self, expr, msg=None): """Fail the test unless the expression is true.""" if not expr: + msg = self._formatMessage(msg, "%r is not True" % expr) raise self.failureException(msg) - def failUnlessRaises(self, excClass, callableObj=None, *args, **kwargs): + def _formatMessage(self, msg, standardMsg): + """Honour the longMessage attribute when generating failure messages. + If longMessage is False this means: + * Use only an explicit message if it is provided + * Otherwise use the standard message for the assert + + If longMessage is True: + * Use the standard message + * If an explicit message is provided, plus ' : ' and the explicit message + """ + if not self.longMessage: + return msg or standardMsg + if msg is None: + return standardMsg + return standardMsg + ' : ' + msg + + + def assertRaises(self, excClass, callableObj=None, *args, **kwargs): """Fail unless an exception of class excClass is thrown by callableObj when invoked with arguments args and keyword arguments kwargs. If a different type of exception is @@ -440,30 +525,62 @@ class TestCase(object): If called with callableObj omitted or None, will return a context object used like this:: - with self.failUnlessRaises(some_error_class): + with self.assertRaises(some_error_class): do_something() """ - context = AssertRaisesContext(excClass, self, callableObj) + context = _AssertRaisesContext(excClass, self, callableObj) if callableObj is None: return context with context: callableObj(*args, **kwargs) - def failUnlessEqual(self, first, second, msg=None): + def _getAssertEqualityFunc(self, first, second): + """Get a detailed comparison function for the types of the two args. + + Returns: A callable accepting (first, second, msg=None) that will + raise a failure exception if first != second with a useful human + readable error message for those types. + """ + # + # NOTE(gregory.p.smith): I considered isinstance(first, type(second)) + # and vice versa. I opted for the conservative approach in case + # subclasses are not intended to be compared in detail to their super + # class instances using a type equality func. This means testing + # subtypes won't automagically use the detailed comparison. Callers + # should use their type specific assertSpamEqual method to compare + # subclasses if the detailed comparison is desired and appropriate. + # See the discussion in http://bugs.python.org/issue2578. + # + if type(first) is type(second): + asserter = self._type_equality_funcs.get(type(first)) + if asserter is not None: + return asserter.function + + return self._baseAssertEqual + + def _baseAssertEqual(self, first, second, msg=None): + """The default assertEqual implementation, not type specific.""" + if not first == second: + standardMsg = '%r != %r' % (first, second) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertEqual(self, first, second, msg=None): """Fail if the two objects are unequal as determined by the '==' operator. """ - if not first == second: - raise self.failureException(msg or '%r != %r' % (first, second)) + assertion_func = self._getAssertEqualityFunc(first, second) + assertion_func(first, second, msg=msg) - def failIfEqual(self, first, second, msg=None): + def assertNotEqual(self, first, second, msg=None): """Fail if the two objects are equal as determined by the '==' operator. """ - if first == second: - raise self.failureException(msg or '%r == %r' % (first, second)) + if not first != second: + msg = self._formatMessage(msg, '%r == %r' % (first, second)) + raise self.failureException(msg) - def failUnlessAlmostEqual(self, first, second, *, places=7, msg=None): + def assertAlmostEqual(self, first, second, *, places=7, msg=None): """Fail if the two objects are unequal as determined by their difference rounded to the given number of decimal places (default 7) and comparing to zero. @@ -472,10 +589,11 @@ class TestCase(object): as significant digits (measured from the most signficant digit). """ if round(abs(second-first), places) != 0: - raise self.failureException( - msg or '%r != %r within %r places' % (first, second, places)) + standardMsg = '%r != %r within %r places' % (first, second, places) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) - def failIfAlmostEqual(self, first, second, *, places=7, msg=None): + def assertNotAlmostEqual(self, first, second, *, places=7, msg=None): """Fail if the two objects are equal as determined by their difference rounded to the given number of decimal places (default 7) and comparing to zero. @@ -484,25 +602,388 @@ class TestCase(object): as significant digits (measured from the most signficant digit). """ if round(abs(second-first), places) == 0: - raise self.failureException( - msg or '%r == %r within %r places' % (first, second, places)) + standardMsg = '%r == %r within %r places' % (first, second, places) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) # Synonyms for assertion methods - assertEqual = assertEquals = failUnlessEqual + # The plurals are undocumented. Keep them that way to discourage use. + # Do not add more. Do not remove. + # Going through a deprecation cycle on these would annoy many people. + assertEquals = assertEqual + assertNotEquals = assertNotEqual + assertAlmostEquals = assertAlmostEqual + assertNotAlmostEquals = assertNotAlmostEqual + assert_ = assertTrue + + # These fail* assertion method names are pending deprecation and will + # be a DeprecationWarning in 3.2; http://bugs.python.org/issue2578 + def _deprecate(original_func): + def deprecated_func(*args, **kwargs): + warnings.warn( + 'Please use {0} instead.'.format(original_func.__name__), + PendingDeprecationWarning, 2) + return original_func(*args, **kwargs) + return deprecated_func + + failUnlessEqual = _deprecate(assertEqual) + failIfEqual = _deprecate(assertNotEqual) + failUnlessAlmostEqual = _deprecate(assertAlmostEqual) + failIfAlmostEqual = _deprecate(assertNotAlmostEqual) + failUnless = _deprecate(assertTrue) + failUnlessRaises = _deprecate(assertRaises) + failIf = _deprecate(assertFalse) + + def assertSequenceEqual(self, seq1, seq2, msg=None, seq_type=None): + """An equality assertion for ordered sequences (like lists and tuples). + + For the purposes of this function, a valid orderd sequence type is one + which can be indexed, has a length, and has an equality operator. + + Args: + seq1: The first sequence to compare. + seq2: The second sequence to compare. + seq_type: The expected datatype of the sequences, or None if no + datatype should be enforced. + msg: Optional message to use on failure instead of a list of + differences. + """ + if seq_type != None: + seq_type_name = seq_type.__name__ + if not isinstance(seq1, seq_type): + raise self.failureException('First sequence is not a %s: %r' + % (seq_type_name, seq1)) + if not isinstance(seq2, seq_type): + raise self.failureException('Second sequence is not a %s: %r' + % (seq_type_name, seq2)) + else: + seq_type_name = "sequence" + + differing = None + try: + len1 = len(seq1) + except (TypeError, NotImplementedError): + differing = 'First %s has no length. Non-sequence?' % ( + seq_type_name) + + if differing is None: + try: + len2 = len(seq2) + except (TypeError, NotImplementedError): + differing = 'Second %s has no length. Non-sequence?' % ( + seq_type_name) + + if differing is None: + if seq1 == seq2: + return + + for i in range(min(len1, len2)): + try: + item1 = seq1[i] + except (TypeError, IndexError, NotImplementedError): + differing = ('Unable to index element %d of first %s\n' % + (i, seq_type_name)) + break + + try: + item2 = seq2[i] + except (TypeError, IndexError, NotImplementedError): + differing = ('Unable to index element %d of second %s\n' % + (i, seq_type_name)) + break + + if item1 != item2: + differing = ('First differing element %d:\n%s\n%s\n' % + (i, item1, item2)) + break + else: + if (len1 == len2 and seq_type is None and + type(seq1) != type(seq2)): + # The sequences are the same, but have differing types. + return + # A catch-all message for handling arbitrary user-defined + # sequences. + differing = '%ss differ:\n' % seq_type_name.capitalize() + if len1 > len2: + differing = ('First %s contains %d additional ' + 'elements.\n' % (seq_type_name, len1 - len2)) + try: + differing += ('First extra element %d:\n%s\n' % + (len2, seq1[len2])) + except (TypeError, IndexError, NotImplementedError): + differing += ('Unable to index element %d ' + 'of first %s\n' % (len2, seq_type_name)) + elif len1 < len2: + differing = ('Second %s contains %d additional ' + 'elements.\n' % (seq_type_name, len2 - len1)) + try: + differing += ('First extra element %d:\n%s\n' % + (len1, seq2[len1])) + except (TypeError, IndexError, NotImplementedError): + differing += ('Unable to index element %d ' + 'of second %s\n' % (len1, seq_type_name)) + standardMsg = differing + '\n'.join(difflib.ndiff(pprint.pformat(seq1).splitlines(), + pprint.pformat(seq2).splitlines())) + msg = self._formatMessage(msg, standardMsg) + self.fail(msg) + + def assertListEqual(self, list1, list2, msg=None): + """A list-specific equality assertion. + + Args: + list1: The first list to compare. + list2: The second list to compare. + msg: Optional message to use on failure instead of a list of + differences. + + """ + self.assertSequenceEqual(list1, list2, msg, seq_type=list) - assertNotEqual = assertNotEquals = failIfEqual + def assertTupleEqual(self, tuple1, tuple2, msg=None): + """A tuple-specific equality assertion. - assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual + Args: + tuple1: The first tuple to compare. + tuple2: The second tuple to compare. + msg: Optional message to use on failure instead of a list of + differences. + """ + self.assertSequenceEqual(tuple1, tuple2, msg, seq_type=tuple) - assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual + def assertSetEqual(self, set1, set2, msg=None): + """A set-specific equality assertion. - assertRaises = failUnlessRaises + Args: + set1: The first set to compare. + set2: The second set to compare. + msg: Optional message to use on failure instead of a list of + differences. - assert_ = assertTrue = failUnless + For more general containership equality, assertSameElements will work + with things other than sets. This uses ducktyping to support + different types of sets, and is optimized for sets specifically + (parameters must support a difference method). + """ + try: + difference1 = set1.difference(set2) + except TypeError as e: + self.fail('invalid type when attempting set difference: %s' % e) + except AttributeError as e: + self.fail('first argument does not support set difference: %s' % e) - assertFalse = failIf + try: + difference2 = set2.difference(set1) + except TypeError as e: + self.fail('invalid type when attempting set difference: %s' % e) + except AttributeError as e: + self.fail('second argument does not support set difference: %s' % e) + + if not (difference1 or difference2): + return + + lines = [] + if difference1: + lines.append('Items in the first set but not the second:') + for item in difference1: + lines.append(repr(item)) + if difference2: + lines.append('Items in the second set but not the first:') + for item in difference2: + lines.append(repr(item)) + + standardMsg = '\n'.join(lines) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIn(self, member, container, msg=None): + """Just like self.assertTrue(a in b), but with a nicer default message.""" + if member not in container: + standardMsg = '%r not found in %r' % (member, container) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertNotIn(self, member, container, msg=None): + """Just like self.assertTrue(a not in b), but with a nicer default message.""" + if member in container: + standardMsg = '%r unexpectedly found in %r' % (member, container) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictEqual(self, d1, d2, msg=None): + self.assert_(isinstance(d1, dict), 'First argument is not a dictionary') + self.assert_(isinstance(d2, dict), 'Second argument is not a dictionary') + + if d1 != d2: + standardMsg = ('\n' + '\n'.join(difflib.ndiff( + pprint.pformat(d1).splitlines(), + pprint.pformat(d2).splitlines()))) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictContainsSubset(self, expected, actual, msg=None): + """Checks whether actual is a superset of expected.""" + missing = [] + mismatched = [] + for key, value in expected.items(): + if key not in actual: + missing.append(key) + elif value != actual[key]: + mismatched.append('%s, expected: %s, actual: %s' % (key, value, actual[key])) + + if not (missing or mismatched): + return + + standardMsg = '' + if missing: + standardMsg = 'Missing: %r' % ','.join(missing) + if mismatched: + if standardMsg: + standardMsg += '; ' + standardMsg += 'Mismatched values: %s' % ','.join(mismatched) + + self.fail(self._formatMessage(msg, standardMsg)) + + def assertSameElements(self, expected_seq, actual_seq, msg=None): + """An unordered sequence specific comparison. + + Raises with an error message listing which elements of expected_seq + are missing from actual_seq and vice versa if any. + """ + try: + expected = set(expected_seq) + actual = set(actual_seq) + missing = list(expected.difference(actual)) + unexpected = list(actual.difference(expected)) + missing.sort() + unexpected.sort() + except TypeError: + # Fall back to slower list-compare if any of the objects are + # not hashable. + expected = list(expected_seq) + actual = list(actual_seq) + expected.sort() + actual.sort() + missing, unexpected = _SortedListDifference(expected, actual) + errors = [] + if missing: + errors.append('Expected, but missing:\n %r' % missing) + if unexpected: + errors.append('Unexpected, but present:\n %r' % unexpected) + if errors: + standardMsg = '\n'.join(errors) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertMultiLineEqual(self, first, second, msg=None): + """Assert that two multi-line strings are equal.""" + self.assert_(isinstance(first, str), ( + 'First argument is not a string')) + self.assert_(isinstance(second, str), ( + 'Second argument is not a string')) + + if first != second: + standardMsg = '\n' + ''.join(difflib.ndiff(first.splitlines(True), second.splitlines(True))) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertLess(self, a, b, msg=None): + """Just like self.assertTrue(a < b), but with a nicer default message.""" + if not a < b: + standardMsg = '%r not less than %r' % (a, b) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertLessEqual(self, a, b, msg=None): + """Just like self.assertTrue(a <= b), but with a nicer default message.""" + if not a <= b: + standardMsg = '%r not less than or equal to %r' % (a, b) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertGreater(self, a, b, msg=None): + """Just like self.assertTrue(a > b), but with a nicer default message.""" + if not a > b: + standardMsg = '%r not greater than %r' % (a, b) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertGreaterEqual(self, a, b, msg=None): + """Just like self.assertTrue(a >= b), but with a nicer default message.""" + if not a >= b: + standardMsg = '%r not greater than or equal to %r' % (a, b) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNone(self, obj, msg=None): + """Same as self.assertTrue(obj is None), with a nicer default message.""" + if obj is not None: + standardMsg = '%r is not None' % obj + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNotNone(self, obj, msg=None): + """Included for symmetry with assertIsNone.""" + if obj is None: + standardMsg = 'unexpectedly None' + self.fail(self._formatMessage(msg, standardMsg)) + + def assertRaisesRegexp(self, expected_exception, expected_regexp, + callable_obj=None, *args, **kwargs): + """Asserts that the message in a raised exception matches a regexp. + + Args: + expected_exception: Exception class expected to be raised. + expected_regexp: Regexp (re pattern object or string) expected + to be found in error message. + callable_obj: Function to be called. + args: Extra args. + kwargs: Extra kwargs. + """ + context = _AssertRaisesContext(expected_exception, self, callable_obj, + expected_regexp) + if callable_obj is None: + return context + with context: + callable_obj(*args, **kwargs) + + def assertRegexpMatches(self, text, expected_regex, msg=None): + if isinstance(expected_regex, (str, bytes)): + expected_regex = re.compile(expected_regex) + if not expected_regex.search(text): + msg = msg or "Regexp didn't match" + msg = '%s: %r not found in %r' % (msg, expected_regex.pattern, text) + raise self.failureException(msg) + + +def _SortedListDifference(expected, actual): + """Finds elements in only one or the other of two, sorted input lists. + Returns a two-element tuple of lists. The first list contains those + elements in the "expected" list but not in the "actual" list, and the + second contains those elements in the "actual" list but not in the + "expected" list. Duplicate elements in either input list are ignored. + """ + i = j = 0 + missing = [] + unexpected = [] + while True: + try: + e = expected[i] + a = actual[j] + if e < a: + missing.append(e) + i += 1 + while expected[i] == e: + i += 1 + elif e > a: + unexpected.append(a) + j += 1 + while actual[j] == a: + j += 1 + else: + i += 1 + try: + while expected[i] == e: + i += 1 + finally: + j += 1 + while actual[j] == a: + j += 1 + except IndexError: + missing.extend(expected[i:]) + unexpected.extend(actual[j:]) + break + return missing, unexpected class TestSuite(object): @@ -611,52 +1092,52 @@ class FunctionTestCase(TestCase): def __init__(self, testFunc, setUp=None, tearDown=None, description=None): super(FunctionTestCase, self).__init__() - self.__setUpFunc = setUp - self.__tearDownFunc = tearDown - self.__testFunc = testFunc - self.__description = description + self._setUpFunc = setUp + self._tearDownFunc = tearDown + self._testFunc = testFunc + self._description = description def setUp(self): - if self.__setUpFunc is not None: - self.__setUpFunc() + if self._setUpFunc is not None: + self._setUpFunc() def tearDown(self): - if self.__tearDownFunc is not None: - self.__tearDownFunc() + if self._tearDownFunc is not None: + self._tearDownFunc() def runTest(self): - self.__testFunc() + self._testFunc() def id(self): - return self.__testFunc.__name__ + return self._testFunc.__name__ def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented - return self.__setUpFunc == other.__setUpFunc and \ - self.__tearDownFunc == other.__tearDownFunc and \ - self.__testFunc == other.__testFunc and \ - self.__description == other.__description + return self._setUpFunc == other._setUpFunc and \ + self._tearDownFunc == other._tearDownFunc and \ + self._testFunc == other._testFunc and \ + self._description == other._description def __ne__(self, other): return not self == other def __hash__(self): - return hash((type(self), self.__setUpFunc, self.__tearDownFunc, - self.__testFunc, self.__description)) + return hash((type(self), self._setUpFunc, self._tearDownFunc, + self._testFunc, self._description)) def __str__(self): return "%s (%s)" % (_strclass(self.__class__), self.__testFunc.__name__) def __repr__(self): - return "<%s testFunc=%s>" % (_strclass(self.__class__), - self.__testFunc) + return "<%s testFunc=%s>" % (_strclass(self.__class__), self._testFunc) def shortDescription(self): - if self.__description is not None: return self.__description - doc = self.__testFunc.__doc__ + if self._description is not None: + return self._description + doc = self._testFunc.__doc__ return doc and doc.split("\n")[0].strip() or None |