diff options
author | Benjamin Peterson <benjamin@python.org> | 2010-03-14 15:04:17 +0000 |
---|---|---|
committer | Benjamin Peterson <benjamin@python.org> | 2010-03-14 15:04:17 +0000 |
commit | ffee44ba935c4752b965ee51b5d61936b77258ac (patch) | |
tree | 70aa757b0916c7e1c8997c1fea0438bf8da545f0 | |
parent | 7ff86796e67b026b5ff0343b7f8830960ea0f111 (diff) | |
download | cpython-ffee44ba935c4752b965ee51b5d61936b77258ac.tar.gz |
Merged revisions 78227,78229,78288,78348,78377,78770,78774-78776,78810 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk
........
r78227 | michael.foord | 2010-02-18 14:30:09 -0600 (Thu, 18 Feb 2010) | 1 line
unittest.TestCase uses safe_repr for producing failure messages. Partial fix for issue 7956
........
r78229 | michael.foord | 2010-02-18 15:37:07 -0600 (Thu, 18 Feb 2010) | 1 line
Fix unittest.TestCase.assertDictContainsSubset so it can't die with unicode issues when constructing failure messages. Issue 7956
........
r78288 | michael.foord | 2010-02-21 08:48:59 -0600 (Sun, 21 Feb 2010) | 1 line
Silence UnicodeWarning in crazy unittest test.
........
r78348 | michael.foord | 2010-02-22 17:28:32 -0600 (Mon, 22 Feb 2010) | 1 line
Support for old TestResult object (unittest) with warnings when using unsupported features.
........
r78377 | michael.foord | 2010-02-23 11:00:53 -0600 (Tue, 23 Feb 2010) | 1 line
unittest.TestResult can now be used with the TextTestRunner. TextTestRunner compatible with old TestResult objects.
........
r78770 | michael.foord | 2010-03-07 14:22:12 -0600 (Sun, 07 Mar 2010) | 1 line
Fix for potentials errors in constructing unittest failure messages. Plus skipped test methods no longer run setUp and tearDown (Issue 8059)
........
r78774 | michael.foord | 2010-03-07 16:04:55 -0600 (Sun, 07 Mar 2010) | 1 line
Addition of setUpClass and setUpModule shared fixtures to unittest.
........
r78775 | michael.foord | 2010-03-07 17:10:36 -0600 (Sun, 07 Mar 2010) | 1 line
Fix accidental name rebinding in unittest py3k warning filtering.
........
r78776 | michael.foord | 2010-03-07 17:16:20 -0600 (Sun, 07 Mar 2010) | 1 line
Remove accidental print statement from last commit.
........
r78810 | raymond.hettinger | 2010-03-09 02:44:18 -0600 (Tue, 09 Mar 2010) | 5 lines
Improve the basic example.
* Show both the decorator and regular form for assertRaises()
* Use assertTrue() instead of assertIn() to teach useful minimal subset of the API
........
-rw-r--r-- | Doc/library/unittest.rst | 10 | ||||
-rw-r--r-- | Lib/test/test_unittest.py | 540 | ||||
-rw-r--r-- | Lib/unittest/__init__.py | 3 | ||||
-rw-r--r-- | Lib/unittest/case.py | 173 | ||||
-rw-r--r-- | Lib/unittest/result.py | 11 | ||||
-rw-r--r-- | Lib/unittest/runner.py | 17 | ||||
-rw-r--r-- | Lib/unittest/suite.py | 199 | ||||
-rw-r--r-- | Lib/unittest/util.py | 6 |
8 files changed, 865 insertions, 94 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index e2272f7146..4671f8a2d7 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -177,14 +177,18 @@ Here is a short script to test three functions from the :mod:`random` module:: self.seq.sort() self.assertEqual(self.seq, list(range(10))) + # should raise an exception for an immutable sequence + self.assertRaises(TypeError, random.shuffle, (1,2,3)) + def test_choice(self): element = random.choice(self.seq) - self.assertIn(element, self.seq) + self.assertTrue(element in self.seq) def test_sample(self): - self.assertRaises(ValueError, random.sample, self.seq, 20) + with self.assertRaises(ValueError): + random.sample(self.seq, 20) for element in random.sample(self.seq, 5): - self.assertIn(element, self.seq) + self.assertTrue(element in self.seq) if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_unittest.py b/Lib/test/test_unittest.py index 75b9d0c797..453c48d6e8 100644 --- a/Lib/test/test_unittest.py +++ b/Lib/test/test_unittest.py @@ -18,10 +18,15 @@ import types from copy import deepcopy import io import pickle +import warnings + ### Support code ################################################################ +def resultFactory(*_): + return unittest.TestResult() + class LoggingResult(unittest.TestResult): def __init__(self, log): self._events = log @@ -2076,6 +2081,70 @@ class Test_TestResult(TestCase): 'Tests getDescription() for a method with a longer ' 'docstring.')) +classDict = dict(unittest.TestResult.__dict__) +for m in ('addSkip', 'addExpectedFailure', 'addUnexpectedSuccess', + '__init__'): + del classDict[m] + +def __init__(self, stream=None, descriptions=None, verbosity=None): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.shouldStop = False +classDict['__init__'] = __init__ +OldResult = type('OldResult', (object,), classDict) + +class Test_OldTestResult(unittest.TestCase): + + def assertOldResultWarning(self, test, failures): + with warnings.catch_warnings(record=True) as log: + result = OldResult() + test.run(result) + self.assertEqual(len(result.failures), failures) + warning, = log + self.assertIs(warning.category, RuntimeWarning) + + def testOldTestResult(self): + class Test(unittest.TestCase): + def testSkip(self): + self.skipTest('foobar') + @unittest.expectedFailure + def testExpectedFail(self): + raise TypeError + @unittest.expectedFailure + def testUnexpectedSuccess(self): + pass + + for test_name, should_pass in (('testSkip', True), + ('testExpectedFail', True), + ('testUnexpectedSuccess', False)): + test = Test(test_name) + self.assertOldResultWarning(test, int(not should_pass)) + + def testOldTestTesultSetup(self): + class Test(unittest.TestCase): + def setUp(self): + self.skipTest('no reason') + def testFoo(self): + pass + self.assertOldResultWarning(Test('testFoo'), 0) + + def testOldTestResultClass(self): + @unittest.skip('no reason') + class Test(unittest.TestCase): + def testFoo(self): + pass + self.assertOldResultWarning(Test('testFoo'), 0) + + def testOldResultWithRunner(self): + class Test(unittest.TestCase): + def testFoo(self): + pass + runner = unittest.TextTestRunner(resultclass=OldResult, + stream=io.StringIO()) + # This will raise an exception if TextTestRunner can't handle old + # test result objects + runner.run(Test('testFoo')) ### Support code for Test_TestCase ################################################################ @@ -2578,21 +2647,27 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): 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:.*') + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({1: "one"}, {}) - self.assertRaises(unittest.TestCase.failureException, - self.assertDictContainsSubset, {'c': 1}, {'a': 1}, - '.*Missing:.*') + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'a': 2}, {'a': 1}) - self.assertRaises(unittest.TestCase.failureException, - self.assertDictContainsSubset, {'a': 1, 'c': 1}, - {'a': 1}, '.*Missing:.*') + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'c': 1}, {'a': 1}) - self.assertRaises(unittest.TestCase.failureException, - self.assertDictContainsSubset, {'a': 1, 'c': 1}, - {'a': 1}, '.*Missing:.*Mismatched values:.*') + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1}) + + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1}) + + with warnings.catch_warnings(record=True): + # silence the UnicodeWarning + one = ''.join(chr(i) for i in range(255)) + # this used to cause a UnicodeDecodeError constructing the failure msg + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'foo': one}, {'foo': '\uFFFD'}) def testAssertEqual(self): equal_pairs = [ @@ -3028,6 +3103,43 @@ class Test_TestSkipping(TestCase): self.assertEqual(result.unexpectedSuccesses, [test]) self.assertTrue(result.wasSuccessful()) + def test_skip_doesnt_run_setup(self): + class Foo(unittest.TestCase): + wasSetUp = False + wasTornDown = False + def setUp(self): + Foo.wasSetUp = True + def tornDown(self): + Foo.wasTornDown = True + @unittest.skip('testing') + def test_1(self): + pass + + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertFalse(Foo.wasSetUp) + self.assertFalse(Foo.wasTornDown) + + def test_decorated_skip(self): + def decorator(func): + def inner(*a): + return func(*a) + return inner + + class Foo(unittest.TestCase): + @decorator + @unittest.skip('testing') + def test_1(self): + pass + + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) class Test_Assertions(TestCase): @@ -3131,6 +3243,16 @@ class TestLongMessage(TestCase): self.assertEquals(self.testableTrue._formatMessage(None, "foo"), "foo") self.assertEquals(self.testableTrue._formatMessage("foo", "bar"), "bar : foo") + # This blows up if _formatMessage uses string concatenation + self.testableTrue._formatMessage(object(), 'foo') + + def test_formatMessage_unicode_error(self): + with warnings.catch_warnings(record=True): + # This causes a UnicodeWarning due to its craziness + one = ''.join(chr(i) for i in range(255)) + # this used to cause a UnicodeDecodeError constructing msg + self.testableTrue._formatMessage(one, '\uFFFD') + def assertMessages(self, methodName, args, errors): def getMethod(i): useTestableFalse = i < 2 @@ -3795,6 +3917,397 @@ class TestDiscovery(TestCase): self.assertEqual(program.verbosity, 2) +class TestSetups(unittest.TestCase): + + def getRunner(self): + return unittest.TextTestRunner(resultclass=resultFactory, + stream=io.StringIO()) + def runTests(self, *cases): + suite = unittest.TestSuite() + for case in cases: + tests = unittest.defaultTestLoader.loadTestsFromTestCase(case) + suite.addTests(tests) + + runner = self.getRunner() + + # creating a nested suite exposes some potential bugs + realSuite = unittest.TestSuite() + realSuite.addTest(suite) + # adding empty suites to the end exposes potential bugs + suite.addTest(unittest.TestSuite()) + realSuite.addTest(unittest.TestSuite()) + return runner.run(realSuite) + + def test_setup_class(self): + class Test(unittest.TestCase): + setUpCalled = 0 + @classmethod + def setUpClass(cls): + Test.setUpCalled += 1 + unittest.TestCase.setUpClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + + self.assertEqual(Test.setUpCalled, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_teardown_class(self): + class Test(unittest.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test.tearDownCalled += 1 + unittest.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + + self.assertEqual(Test.tearDownCalled, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_teardown_class_two_classes(self): + class Test(unittest.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test.tearDownCalled += 1 + unittest.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test2.tearDownCalled += 1 + unittest.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test, Test2) + + self.assertEqual(Test.tearDownCalled, 1) + self.assertEqual(Test2.tearDownCalled, 1) + self.assertEqual(result.testsRun, 4) + self.assertEqual(len(result.errors), 0) + + def test_error_in_setupclass(self): + class BrokenTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(BrokenTest) + + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), + 'classSetUp (%s.BrokenTest)' % __name__) + + def test_error_in_teardown_class(self): + class Test(unittest.TestCase): + tornDown = 0 + @classmethod + def tearDownClass(cls): + Test.tornDown += 1 + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest.TestCase): + tornDown = 0 + @classmethod + def tearDownClass(cls): + Test2.tornDown += 1 + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test, Test2) + self.assertEqual(result.testsRun, 4) + self.assertEqual(len(result.errors), 2) + self.assertEqual(Test.tornDown, 1) + self.assertEqual(Test2.tornDown, 1) + + error, _ = result.errors[0] + self.assertEqual(str(error), + 'classTearDown (%s.Test)' % __name__) + + def test_class_not_torndown_when_setup_fails(self): + class Test(unittest.TestCase): + tornDown = False + @classmethod + def setUpClass(cls): + raise TypeError + @classmethod + def tearDownClass(cls): + Test.tornDown = True + raise TypeError('foo') + def test_one(self): + pass + + self.runTests(Test) + self.assertFalse(Test.tornDown) + + def test_class_not_setup_or_torndown_when_skipped(self): + class Test(unittest.TestCase): + classSetUp = False + tornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.tornDown = True + def test_one(self): + pass + + Test = unittest.skip("hop")(Test) + self.runTests(Test) + self.assertFalse(Test.classSetUp) + self.assertFalse(Test.tornDown) + + def test_setup_teardown_order_with_pathological_suite(self): + results = [] + + class Module1(object): + @staticmethod + def setUpModule(): + results.append('Module1.setUpModule') + @staticmethod + def tearDownModule(): + results.append('Module1.tearDownModule') + + class Module2(object): + @staticmethod + def setUpModule(): + results.append('Module2.setUpModule') + @staticmethod + def tearDownModule(): + results.append('Module2.tearDownModule') + + class Test1(unittest.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 1') + @classmethod + def tearDownClass(cls): + results.append('teardown 1') + def testOne(self): + results.append('Test1.testOne') + def testTwo(self): + results.append('Test1.testTwo') + + class Test2(unittest.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 2') + @classmethod + def tearDownClass(cls): + results.append('teardown 2') + def testOne(self): + results.append('Test2.testOne') + def testTwo(self): + results.append('Test2.testTwo') + + class Test3(unittest.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 3') + @classmethod + def tearDownClass(cls): + results.append('teardown 3') + def testOne(self): + results.append('Test3.testOne') + def testTwo(self): + results.append('Test3.testTwo') + + Test1.__module__ = Test2.__module__ = 'Module' + Test3.__module__ = 'Module2' + sys.modules['Module'] = Module1 + sys.modules['Module2'] = Module2 + + first = unittest.TestSuite((Test1('testOne'),)) + second = unittest.TestSuite((Test1('testTwo'),)) + third = unittest.TestSuite((Test2('testOne'),)) + fourth = unittest.TestSuite((Test2('testTwo'),)) + fifth = unittest.TestSuite((Test3('testOne'),)) + sixth = unittest.TestSuite((Test3('testTwo'),)) + suite = unittest.TestSuite((first, second, third, fourth, fifth, sixth)) + + runner = self.getRunner() + result = runner.run(suite) + self.assertEqual(result.testsRun, 6) + self.assertEqual(len(result.errors), 0) + + self.assertEqual(results, + ['Module1.setUpModule', 'setup 1', + 'Test1.testOne', 'Test1.testTwo', 'teardown 1', + 'setup 2', 'Test2.testOne', 'Test2.testTwo', + 'teardown 2', 'Module1.tearDownModule', + 'Module2.setUpModule', 'setup 3', + 'Test3.testOne', 'Test3.testTwo', + 'teardown 3', 'Module2.tearDownModule']) + + def test_setup_module(self): + class Module(object): + moduleSetup = 0 + @staticmethod + def setUpModule(): + Module.moduleSetup += 1 + + class Test(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(Module.moduleSetup, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_error_in_setup_module(self): + class Module(object): + moduleSetup = 0 + moduleTornDown = 0 + @staticmethod + def setUpModule(): + Module.moduleSetup += 1 + raise TypeError('foo') + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + + class Test(unittest.TestCase): + classSetUp = False + classTornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.classTornDown = True + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + Test2.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test, Test2) + self.assertEqual(Module.moduleSetup, 1) + self.assertEqual(Module.moduleTornDown, 0) + self.assertEqual(result.testsRun, 0) + self.assertFalse(Test.classSetUp) + self.assertFalse(Test.classTornDown) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), 'setUpModule (Module)') + + def test_testcase_with_missing_module(self): + class Test(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules.pop('Module', None) + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 2) + + def test_teardown_module(self): + class Module(object): + moduleTornDown = 0 + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + + class Test(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(Module.moduleTornDown, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_error_in_teardown_module(self): + class Module(object): + moduleTornDown = 0 + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + raise TypeError('foo') + + class Test(unittest.TestCase): + classSetUp = False + classTornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.classTornDown = True + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + Test2.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test, Test2) + self.assertEqual(Module.moduleTornDown, 1) + self.assertEqual(result.testsRun, 4) + self.assertTrue(Test.classSetUp) + self.assertTrue(Test.classTornDown) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), 'tearDownModule (Module)') + ###################################################################### ## Main ###################################################################### @@ -3803,7 +4316,8 @@ def test_main(): support.run_unittest(Test_TestCase, Test_TestLoader, Test_TestSuite, Test_TestResult, Test_FunctionTestCase, Test_TestSkipping, Test_Assertions, TestLongMessage, - Test_TestProgram, TestCleanUp, TestDiscovery, Test_TextTestRunner) + Test_TestProgram, TestCleanUp, TestDiscovery, Test_TextTestRunner, + Test_OldTestResult, TestSetups) if __name__ == "__main__": test_main() diff --git a/Lib/unittest/__init__.py b/Lib/unittest/__init__.py index 4a308fa1c4..06fe55d08d 100644 --- a/Lib/unittest/__init__.py +++ b/Lib/unittest/__init__.py @@ -51,13 +51,12 @@ __all__ = ['TestResult', 'TestCase', 'TestSuite', # Expose obsolete functions for backwards compatibility __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) -__all__.append('_TextTestResult') from .result import TestResult from .case import (TestCase, FunctionTestCase, SkipTest, skip, skipIf, skipUnless, expectedFailure) -from .suite import TestSuite +from .suite import BaseTestSuite, TestSuite from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames, findTestCases) from .main import TestProgram, main diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index cc121e54a0..8900915979 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -7,7 +7,9 @@ import pprint import re import warnings -from . import result, util +from . import result +from .util import (strclass, safe_repr, sorted_list_difference, + unorderable_list_difference) class SkipTest(Exception): @@ -44,14 +46,15 @@ def skip(reason): Unconditionally skip a test. """ def decorator(test_item): - if isinstance(test_item, type) and issubclass(test_item, TestCase): - test_item.__unittest_skip__ = True - test_item.__unittest_skip_why__ = reason - return test_item - @functools.wraps(test_item) - def skip_wrapper(*args, **kwargs): - raise SkipTest(reason) - return skip_wrapper + if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): + @functools.wraps(test_item) + def skip_wrapper(*args, **kwargs): + raise SkipTest(reason) + test_item = skip_wrapper + + test_item.__unittest_skip__ = True + test_item.__unittest_skip_why__ = reason + return test_item return decorator def skipIf(condition, reason): @@ -164,6 +167,9 @@ class TestCase(object): longMessage = False + # Attribute used by TestSuite for classSetUp + + _classSetupFailed = False def __init__(self, methodName='runTest'): """Create an instance of the class that will use the named test @@ -175,7 +181,7 @@ class TestCase(object): try: testMethod = getattr(self, methodName) except AttributeError: - raise ValueError("no such test method in %s: %s" % \ + raise ValueError("no such test method in %s: %s" % (self.__class__, methodName)) self._testMethodDoc = testMethod.__doc__ self._cleanups = [] @@ -222,6 +228,14 @@ class TestCase(object): "Hook method for deconstructing the test fixture after testing it." pass + @classmethod + def setUpClass(cls): + "Hook method for setting up class fixture before running tests in the class." + + @classmethod + def tearDownClass(cls): + "Hook method for deconstructing the class fixture after running all tests in the class." + def countTestCases(self): return 1 @@ -240,7 +254,7 @@ class TestCase(object): def id(self): - return "%s.%s" % (util.strclass(self.__class__), self._testMethodName) + return "%s.%s" % (strclass(self.__class__), self._testMethodName) def __eq__(self, other): if type(self) is not type(other): @@ -255,11 +269,20 @@ class TestCase(object): return hash((type(self), self._testMethodName)) def __str__(self): - return "%s (%s)" % (self._testMethodName, util.strclass(self.__class__)) + return "%s (%s)" % (self._testMethodName, strclass(self.__class__)) def __repr__(self): return "<%s testMethod=%s>" % \ - (util.strclass(self.__class__), self._testMethodName) + (strclass(self.__class__), self._testMethodName) + + def _addSkip(self, result, reason): + addSkip = getattr(result, 'addSkip', None) + if addSkip is not None: + addSkip(self, reason) + else: + warnings.warn("TestResult has no addSkip method, skips not reported", + RuntimeWarning, 2) + result.addSuccess(self) def run(self, result=None): orig_result = result @@ -271,20 +294,24 @@ class TestCase(object): self._resultForDoCleanups = result result.startTest(self) - if getattr(self.__class__, "__unittest_skip__", False): - # If the whole class was skipped. + + testMethod = getattr(self, self._testMethodName) + if (getattr(self.__class__, "__unittest_skip__", False) or + getattr(testMethod, "__unittest_skip__", False)): + # If the class or method was skipped. try: - result.addSkip(self, self.__class__.__unittest_skip_why__) + skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') + or getattr(testMethod, '__unittest_skip_why__', '')) + self._addSkip(result, skip_why) finally: result.stopTest(self) return - testMethod = getattr(self, self._testMethodName) try: success = False try: self.setUp() except SkipTest as e: - result.addSkip(self, str(e)) + self._addSkip(result, str(e)) except Exception: result.addError(self, sys.exc_info()) else: @@ -293,11 +320,23 @@ class TestCase(object): except self.failureException: result.addFailure(self, sys.exc_info()) except _ExpectedFailure as e: - result.addExpectedFailure(self, e.exc_info) + addExpectedFailure = getattr(result, 'addExpectedFailure', None) + if addExpectedFailure is not None: + addExpectedFailure(self, e.exc_info) + else: + warnings.warn("TestResult has no addExpectedFailure method, reporting as passes", + RuntimeWarning) + result.addSuccess(self) except _UnexpectedSuccess: - result.addUnexpectedSuccess(self) + addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None) + if addUnexpectedSuccess is not None: + addUnexpectedSuccess(self) + else: + warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures", + RuntimeWarning) + result.addFailure(self, sys.exc_info()) except SkipTest as e: - result.addSkip(self, str(e)) + self._addSkip(result, str(e)) except Exception: result.addError(self, sys.exc_info()) else: @@ -354,13 +393,13 @@ class TestCase(object): 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) + msg = self._formatMessage(msg, "%s is not False" % safe_repr(expr)) raise self.failureException(msg) 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) + msg = self._formatMessage(msg, "%s is not True" % safe_repr(expr)) raise self.failureException(msg) def _formatMessage(self, msg, standardMsg): @@ -377,7 +416,12 @@ class TestCase(object): return msg or standardMsg if msg is None: return standardMsg - return standardMsg + ' : ' + msg + try: + # don't switch to '{}' formatting in Python 2.X + # it changes the way unicode input is handled + return '%s : %s' % (standardMsg, msg) + except UnicodeDecodeError: + return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg)) def assertRaises(self, excClass, callableObj=None, *args, **kwargs): @@ -436,7 +480,7 @@ class TestCase(object): def _baseAssertEqual(self, first, second, msg=None): """The default assertEqual implementation, not type specific.""" if not first == second: - standardMsg = '%r != %r' % (first, second) + standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second)) msg = self._formatMessage(msg, standardMsg) raise self.failureException(msg) @@ -452,7 +496,8 @@ class TestCase(object): operator. """ if not first != second: - msg = self._formatMessage(msg, '%r == %r' % (first, second)) + msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first), + safe_repr(second))) raise self.failureException(msg) def assertAlmostEqual(self, first, second, *, places=7, msg=None): @@ -467,10 +512,12 @@ class TestCase(object): compare almost equal. """ if first == second: - # shortcut for ite + # shortcut for inf return if round(abs(second-first), places) != 0: - standardMsg = '%r != %r within %r places' % (first, second, places) + standardMsg = '%s != %s within %r places' % (safe_repr(first), + safe_repr(second), + places) msg = self._formatMessage(msg, standardMsg) raise self.failureException(msg) @@ -485,7 +532,9 @@ class TestCase(object): Objects that are equal automatically fail. """ if (first == second) or round(abs(second-first), places) == 0: - standardMsg = '%r == %r within %r places' % (first, second, places) + standardMsg = '%s == %s within %r places' % (safe_repr(first), + safe_repr(second), + places) msg = self._formatMessage(msg, standardMsg) raise self.failureException(msg) @@ -535,11 +584,11 @@ class TestCase(object): 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)) + raise self.failureException('First sequence is not a %s: %s' + % (seq_type_name, safe_repr(seq1))) if not isinstance(seq2, seq_type): - raise self.failureException('Second sequence is not a %s: %r' - % (seq_type_name, seq2)) + raise self.failureException('Second sequence is not a %s: %s' + % (seq_type_name, safe_repr(seq2))) else: seq_type_name = "sequence" @@ -561,8 +610,8 @@ class TestCase(object): if seq1 == seq2: return - seq1_repr = repr(seq1) - seq2_repr = repr(seq2) + seq1_repr = safe_repr(seq1) + seq2_repr = safe_repr(seq2) if len(seq1_repr) > 30: seq1_repr = seq1_repr[:30] + '...' if len(seq2_repr) > 30: @@ -689,25 +738,28 @@ class TestCase(object): 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) + standardMsg = '%s not found in %s' % (safe_repr(member), + safe_repr(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) + standardMsg = '%s unexpectedly found in %s' % (safe_repr(member), + safe_repr(container)) self.fail(self._formatMessage(msg, standardMsg)) def assertIs(self, expr1, expr2, msg=None): """Just like self.assertTrue(a is b), but with a nicer default message.""" if expr1 is not expr2: - standardMsg = '%r is not %r' % (expr1, expr2) + standardMsg = '%s is not %s' % (safe_repr(expr1), + safe_repr(expr2)) self.fail(self._formatMessage(msg, standardMsg)) def assertIsNot(self, expr1, expr2, msg=None): """Just like self.assertTrue(a is not b), but with a nicer default message.""" if expr1 is expr2: - standardMsg = 'unexpectedly identical: %r' % (expr1,) + standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),) self.fail(self._formatMessage(msg, standardMsg)) def assertDictEqual(self, d1, d2, msg=None): @@ -729,14 +781,16 @@ class TestCase(object): missing.append(key) elif value != actual[key]: mismatched.append('%s, expected: %s, actual: %s' % - (key, value, actual[key])) + (safe_repr(key), safe_repr(value), + safe_repr(actual[key]))) if not (missing or mismatched): return standardMsg = '' if missing: - standardMsg = 'Missing: %r' % ','.join(missing) + standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in + missing) if mismatched: if standardMsg: standardMsg += '; ' @@ -758,10 +812,8 @@ class TestCase(object): try: expected = set(expected_seq) actual = set(actual_seq) - missing = list(expected.difference(actual)) - unexpected = list(actual.difference(expected)) - missing.sort() - unexpected.sort() + missing = sorted(expected.difference(actual)) + unexpected = sorted(actual.difference(expected)) except TypeError: # Fall back to slower list-compare if any of the objects are # not hashable. @@ -771,16 +823,17 @@ class TestCase(object): expected.sort() actual.sort() except TypeError: - missing, unexpected = util.unorderable_list_difference(expected, - actual) - else: - missing, unexpected = util.sorted_list_difference(expected, + missing, unexpected = unorderable_list_difference(expected, actual) + else: + missing, unexpected = sorted_list_difference(expected, actual) errors = [] if missing: - errors.append('Expected, but missing:\n %r' % missing) + errors.append('Expected, but missing:\n %s' % + safe_repr(missing)) if unexpected: - errors.append('Unexpected, but present:\n %r' % unexpected) + errors.append('Unexpected, but present:\n %s' % + safe_repr(unexpected)) if errors: standardMsg = '\n'.join(errors) self.fail(self._formatMessage(msg, standardMsg)) @@ -800,31 +853,31 @@ class TestCase(object): 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) + standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(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) + standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(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) + standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(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) + standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(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 + standardMsg = '%s is not None' % (safe_repr(obj),) self.fail(self._formatMessage(msg, standardMsg)) def assertIsNotNone(self, obj, msg=None): @@ -837,13 +890,13 @@ class TestCase(object): """Same as self.assertTrue(isinstance(obj, cls)), with a nicer default message.""" if not isinstance(obj, cls): - standardMsg = '%r is not an instance of %r' % (obj, cls) + standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls) self.fail(self._formatMessage(msg, standardMsg)) def assertNotIsInstance(self, obj, cls, msg=None): """Included for symmetry with assertIsInstance.""" if isinstance(obj, cls): - standardMsg = '%r is an instance of %r' % (obj, cls) + standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls) self.fail(self._formatMessage(msg, standardMsg)) def assertRaisesRegexp(self, expected_exception, expected_regexp, @@ -921,11 +974,11 @@ class FunctionTestCase(TestCase): self._testFunc, self._description)) def __str__(self): - return "%s (%s)" % (util.strclass(self.__class__), + return "%s (%s)" % (strclass(self.__class__), self._testFunc.__name__) def __repr__(self): - return "<%s testFunc=%s>" % (util.strclass(self.__class__), + return "<%s tec=%s>" % (strclass(self.__class__), self._testFunc) def shortDescription(self): diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py index 4538304908..746967ec28 100644 --- a/Lib/unittest/result.py +++ b/Lib/unittest/result.py @@ -16,7 +16,9 @@ class TestResult(object): contain tuples of (testcase, exceptioninfo), where exceptioninfo is the formatted traceback of the error that occurred. """ - def __init__(self): + _previousTestClass = None + _moduleSetUpFailed = False + def __init__(self, stream=None, descriptions=None, verbosity=None): self.failures = [] self.errors = [] self.testsRun = 0 @@ -25,6 +27,9 @@ class TestResult(object): self.unexpectedSuccesses = [] self.shouldStop = False + def printErrors(self): + "Called by TestRunner after test run" + def startTest(self, test): "Called when the given test is about to be run" self.testsRun += 1 @@ -107,6 +112,6 @@ class TestResult(object): return length def __repr__(self): - return "<%s run=%i errors=%i failures=%i>" % \ + return ("<%s run=%i errors=%i failures=%i>" % (util.strclass(self.__class__), self.testsRun, len(self.errors), - len(self.failures)) + len(self.failures))) diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py index 3f45f73638..8773d0c5fe 100644 --- a/Lib/unittest/runner.py +++ b/Lib/unittest/runner.py @@ -148,15 +148,22 @@ class TextTestRunner(object): stopTime = time.time() timeTaken = stopTime - startTime result.printErrors() - self.stream.writeln(result.separator2) + if hasattr(result, 'separator2'): + self.stream.writeln(result.separator2) run = result.testsRun self.stream.writeln("Ran %d test%s in %.3fs" % (run, run != 1 and "s" or "", timeTaken)) self.stream.writeln() - results = map(len, (result.expectedFailures, - result.unexpectedSuccesses, - result.skipped)) - expectedFails, unexpectedSuccesses, skipped = results + + expectedFails = unexpectedSuccesses = skipped = 0 + try: + results = map(len, (result.expectedFailures, + result.unexpectedSuccesses, + result.skipped)) + expectedFails, unexpectedSuccesses, skipped = results + except AttributeError: + pass + infos = [] if not result.wasSuccessful(): self.stream.write("FAILED") diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py index 35b7d91da3..ddd83ae4c6 100644 --- a/Lib/unittest/suite.py +++ b/Lib/unittest/suite.py @@ -1,17 +1,13 @@ """TestSuite""" +import sys + from . import case from . import util -class TestSuite(object): - """A test suite is a composite test consisting of a number of TestCases. - - For use, create an instance of TestSuite, then add test case instances. - When all tests have been added, the suite can be passed to a test - runner, such as TextTestRunner. It will run the individual test cases - in the order in which they were added, aggregating the results. When - subclassing, do not forget to call the base class constructor. +class BaseTestSuite(object): + """A simple test suite that doesn't provide class or module shared fixtures. """ def __init__(self, tests=()): self._tests = [] @@ -67,3 +63,190 @@ class TestSuite(object): """Run the tests without collecting errors in a TestResult""" for test in self: test.debug() + + +class TestSuite(BaseTestSuite): + """A test suite is a composite test consisting of a number of TestCases. + + For use, create an instance of TestSuite, then add test case instances. + When all tests have been added, the suite can be passed to a test + runner, such as TextTestRunner. It will run the individual test cases + in the order in which they were added, aggregating the results. When + subclassing, do not forget to call the base class constructor. + """ + + + def run(self, result): + self._wrapped_run(result) + self._tearDownPreviousClass(None, result) + self._handleModuleTearDown(result) + return result + + ################################ + # private methods + def _wrapped_run(self, result): + for test in self: + if result.shouldStop: + break + + if _isnotsuite(test): + self._tearDownPreviousClass(test, result) + self._handleModuleFixture(test, result) + self._handleClassSetUp(test, result) + result._previousTestClass = test.__class__ + + if (getattr(test.__class__, '_classSetupFailed', False) or + getattr(result, '_moduleSetUpFailed', False)): + continue + + if hasattr(test, '_wrapped_run'): + test._wrapped_run(result) + else: + test(result) + + def _handleClassSetUp(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if result._moduleSetUpFailed: + return + if getattr(currentClass, "__unittest_skip__", False): + return + + currentClass._classSetupFailed = False + + setUpClass = getattr(currentClass, 'setUpClass', None) + if setUpClass is not None: + try: + setUpClass() + except: + currentClass._classSetupFailed = True + self._addClassSetUpError(result, currentClass) + + def _get_previous_module(self, result): + previousModule = None + previousClass = getattr(result, '_previousTestClass', None) + if previousClass is not None: + previousModule = previousClass.__module__ + return previousModule + + + def _handleModuleFixture(self, test, result): + previousModule = self._get_previous_module(result) + currentModule = test.__class__.__module__ + if currentModule == previousModule: + return + + self._handleModuleTearDown(result) + + + result._moduleSetUpFailed = False + try: + module = sys.modules[currentModule] + except KeyError: + return + setUpModule = getattr(module, 'setUpModule', None) + if setUpModule is not None: + try: + setUpModule() + except: + result._moduleSetUpFailed = True + error = _ErrorHolder('setUpModule (%s)' % currentModule) + result.addError(error, sys.exc_info()) + + def _handleModuleTearDown(self, result): + previousModule = self._get_previous_module(result) + if previousModule is None: + return + if result._moduleSetUpFailed: + return + + try: + module = sys.modules[previousModule] + except KeyError: + return + + tearDownModule = getattr(module, 'tearDownModule', None) + if tearDownModule is not None: + try: + tearDownModule() + except: + error = _ErrorHolder('tearDownModule (%s)' % previousModule) + result.addError(error, sys.exc_info()) + + def _tearDownPreviousClass(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if getattr(previousClass, '_classSetupFailed', False): + return + if getattr(result, '_moduleSetUpFailed', False): + return + if getattr(previousClass, "__unittest_skip__", False): + return + + tearDownClass = getattr(previousClass, 'tearDownClass', None) + if tearDownClass is not None: + try: + tearDownClass() + except: + self._addClassTearDownError(result) + + def _addClassTearDownError(self, result): + className = util.strclass(result._previousTestClass) + error = _ErrorHolder('classTearDown (%s)' % className) + result.addError(error, sys.exc_info()) + + def _addClassSetUpError(self, result, klass): + className = util.strclass(klass) + error = _ErrorHolder('classSetUp (%s)' % className) + result.addError(error, sys.exc_info()) + + +class _ErrorHolder(object): + """ + Placeholder for a TestCase inside a result. As far as a TestResult + is concerned, this looks exactly like a unit test. Used to insert + arbitrary errors into a test suite run. + """ + # Inspired by the ErrorHolder from Twisted: + # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py + + # attribute used by TestResult._exc_info_to_string + failureException = None + + def __init__(self, description): + self.description = description + + def id(self): + return self.description + + def shortDescription(self): + return None + + def __repr__(self): + return "<ErrorHolder description=%r>" % (self.description,) + + def __str__(self): + return self.id() + + def run(self, result): + # could call result.addError(...) - but this test-like object + # shouldn't be run anyway + pass + + def __call__(self, result): + return self.run(result) + + def countTestCases(self): + return 0 + +def _isnotsuite(test): + "A crude way to tell apart testcases and suites with duck-typing" + try: + iter(test) + except TypeError: + return True + return False diff --git a/Lib/unittest/util.py b/Lib/unittest/util.py index 26805def31..1c1b600f7d 100644 --- a/Lib/unittest/util.py +++ b/Lib/unittest/util.py @@ -1,5 +1,11 @@ """Various utility functions.""" +def safe_repr(obj): + try: + return repr(obj) + except Exception: + return object.__repr__(obj) + def strclass(cls): return "%s.%s" % (cls.__module__, cls.__name__) |