diff options
author | Benjamin Peterson <benjamin@python.org> | 2009-05-08 20:42:26 (GMT) |
---|---|---|
committer | Benjamin Peterson <benjamin@python.org> | 2009-05-08 20:42:26 (GMT) |
commit | 25c95f12987c08f446f355db6b9a807f3dea76a0 (patch) | |
tree | 9d70b6e22b45cec0e40cc6d5d174d03012af7f50 /Lib | |
parent | 8a282d175b5a9f1cd48c5de506fb02d60b20d6d5 (diff) | |
download | cpython-25c95f12987c08f446f355db6b9a807f3dea76a0.zip cpython-25c95f12987c08f446f355db6b9a807f3dea76a0.tar.gz cpython-25c95f12987c08f446f355db6b9a807f3dea76a0.tar.bz2 |
Merged revisions 70768,71657,71721,71729,71794,71976,72036-72037,72079,72085,72131-72134,72191,72197-72198,72219,72221,72225,72303,72434,72467,72476 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk
........
r70768 | andrew.kuchling | 2009-03-30 17:29:15 -0500 (Mon, 30 Mar 2009) | 1 line
Typo fixes
........
r71657 | vinay.sajip | 2009-04-16 14:07:37 -0500 (Thu, 16 Apr 2009) | 1 line
Issue #5768: Change to Unicode output logic and test case for same.
........
r71721 | benjamin.peterson | 2009-04-18 14:26:19 -0500 (Sat, 18 Apr 2009) | 1 line
fix a few nits in unittest.py #5771
........
r71729 | benjamin.peterson | 2009-04-18 16:03:10 -0500 (Sat, 18 Apr 2009) | 1 line
move test to a more appropiate one
........
r71794 | vinay.sajip | 2009-04-22 07:10:47 -0500 (Wed, 22 Apr 2009) | 2 lines
Issue #5170: Fixed regression caused when fixing #5768.
........
r71976 | mark.dickinson | 2009-04-26 14:54:55 -0500 (Sun, 26 Apr 2009) | 2 lines
Fix typo in function name
........
r72036 | georg.brandl | 2009-04-27 12:04:23 -0500 (Mon, 27 Apr 2009) | 1 line
#5848: small unittest doc patch.
........
r72037 | georg.brandl | 2009-04-27 12:09:53 -0500 (Mon, 27 Apr 2009) | 1 line
#5840: dont claim we dont support TLS.
........
r72079 | r.david.murray | 2009-04-28 14:02:55 -0500 (Tue, 28 Apr 2009) | 2 lines
Remove spurious 'u'.
........
r72085 | georg.brandl | 2009-04-28 16:48:35 -0500 (Tue, 28 Apr 2009) | 1 line
Make the doctests in the docs pass, except for those in the turtle module.
........
r72131 | benjamin.peterson | 2009-04-29 17:43:35 -0500 (Wed, 29 Apr 2009) | 1 line
fix test_shutil on ZFS #5676
........
r72132 | georg.brandl | 2009-04-29 17:44:07 -0500 (Wed, 29 Apr 2009) | 1 line
#5878: fix repr of re object.
........
r72133 | benjamin.peterson | 2009-04-29 17:44:15 -0500 (Wed, 29 Apr 2009) | 1 line
make sure mode is removable while cleaning up test droppings
........
r72134 | benjamin.peterson | 2009-04-29 19:06:33 -0500 (Wed, 29 Apr 2009) | 1 line
make sure to close file
........
r72191 | michael.foord | 2009-05-02 06:43:06 -0500 (Sat, 02 May 2009) | 9 lines
Adds an exit parameter to unittest.main(). If False main no longer
calls sys.exit.
Closes issue 3379.
Michael Foord
........
r72197 | benjamin.peterson | 2009-05-02 11:24:37 -0500 (Sat, 02 May 2009) | 1 line
don't let sys.argv be used in the tests
........
r72198 | andrew.kuchling | 2009-05-02 12:12:15 -0500 (Sat, 02 May 2009) | 1 line
Add items
........
r72219 | michael.foord | 2009-05-02 15:15:05 -0500 (Sat, 02 May 2009) | 8 lines
Add addCleanup and doCleanups to unittest.TestCase.
Closes issue 5679.
Michael Foord
........
r72221 | benjamin.peterson | 2009-05-02 15:26:53 -0500 (Sat, 02 May 2009) | 1 line
add myself
........
r72225 | michael.foord | 2009-05-02 17:43:34 -0500 (Sat, 02 May 2009) | 1 line
........
r72303 | benjamin.peterson | 2009-05-04 19:55:24 -0500 (Mon, 04 May 2009) | 1 line
using sys._getframe(x), where x > 0 doesnt' work on IronPython
........
r72434 | r.david.murray | 2009-05-07 13:09:58 -0500 (Thu, 07 May 2009) | 2 lines
Pre-opened test file needs to be opened in binary mode.
........
r72467 | georg.brandl | 2009-05-08 07:17:34 -0500 (Fri, 08 May 2009) | 1 line
Fix name.
........
r72476 | thomas.heller | 2009-05-08 15:09:40 -0500 (Fri, 08 May 2009) | 4 lines
Add a file that contains diffs between offical libffi files and the
files in this repository. Should make it easier to merge new libffi
versions.
........
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/collections.py | 7 | ||||
-rw-r--r-- | Lib/logging/__init__.py | 20 | ||||
-rwxr-xr-x | Lib/test/regrtest.py | 5 | ||||
-rw-r--r-- | Lib/test/test_aifc.py | 2 | ||||
-rw-r--r-- | Lib/test/test_descr.py | 55 | ||||
-rw-r--r-- | Lib/test/test_logging.py | 1 | ||||
-rw-r--r-- | Lib/test/test_shutil.py | 18 | ||||
-rw-r--r-- | Lib/test/test_unittest.py | 413 | ||||
-rw-r--r-- | Lib/unittest.py | 117 |
9 files changed, 534 insertions, 104 deletions
diff --git a/Lib/collections.py b/Lib/collections.py index a90915a..6e2fe29 100644 --- a/Lib/collections.py +++ b/Lib/collections.py @@ -271,9 +271,12 @@ def namedtuple(typename, field_names, verbose=False, rename=False): # For pickling to work, the __module__ variable needs to be set to the frame # where the named tuple is created. Bypass this step in enviroments where - # sys._getframe is not defined (Jython for example). - if hasattr(_sys, '_getframe'): + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython). + try: result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass return result diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index c95cea0..517a7a9 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -756,7 +756,7 @@ class StreamHandler(Handler): The record is then written to the stream with a trailing newline. If exception information is present, it is formatted using traceback.print_exception and appended to the stream. If the stream - has an 'encoding' attribute, it is used to encode the message before + has an 'encoding' attribute, it is used to determine how to do the output to the stream. """ try: @@ -767,11 +767,21 @@ class StreamHandler(Handler): stream.write(fs % msg) else: try: - if (isinstance(msg, unicode) or - getattr(stream, 'encoding', None) is None): - stream.write(fs % msg) + if (isinstance(msg, unicode) and + getattr(stream, 'encoding', None)): + fs = fs.decode(stream.encoding) + try: + stream.write(fs % msg) + except UnicodeEncodeError: + #Printing to terminals sometimes fails. For example, + #with an encoding of 'cp1251', the above write will + #work if written to a stream opened or wrapped by + #the codecs module, but fail when writing to a + #terminal even when the codepage is set to cp1251. + #An extra encoding step seems to be needed. + stream.write((fs % msg).encode(stream.encoding)) else: - stream.write(fs % msg.encode(stream.encoding)) + stream.write(fs % msg) except UnicodeError: stream.write(fs % msg.encode("UTF-8")) self.flush() diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py index 0408a02..673f11f 100755 --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -663,6 +663,7 @@ def runtest_inner(test, generate, verbose, quiet, test_times, def cleanup_test_droppings(testname, verbose): import shutil + import stat # Try to clean up junk commonly left behind. While tests shouldn't leave # any files or directories behind, when a test fails that can be tedious @@ -687,6 +688,10 @@ def cleanup_test_droppings(testname, verbose): if verbose: print("%r left behind %s %r" % (testname, kind, name)) try: + # if we have chmod, fix possible permissions problems + # that might prevent cleanup + if (hasattr(os, 'chmod')): + os.chmod(name, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) nuker(name) except Exception as msg: print(("%r left behind %s %r and it couldn't be " diff --git a/Lib/test/test_aifc.py b/Lib/test/test_aifc.py index 0248615..4869bf3 100644 --- a/Lib/test/test_aifc.py +++ b/Lib/test/test_aifc.py @@ -27,7 +27,7 @@ class AIFCTest(unittest.TestCase): def test_skipunknown(self): #Issue 2245 #This file contains chunk types aifc doesn't recognize. - f = aifc.open(self.sndfilepath) + self.f = aifc.open(self.sndfilepath) def test_params(self): f = self.f = aifc.open(self.sndfilepath) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index ba0e0b8..37cab01 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3585,31 +3585,6 @@ order (MRO) for bases """ self.assertEqual(e.a, 2) self.assertEqual(C2.__subclasses__(), [D]) - # stuff that shouldn't: - class L(list): - pass - - try: - L.__bases__ = (dict,) - except TypeError: - pass - else: - self.fail("shouldn't turn list subclass into dict subclass") - - try: - list.__bases__ = (dict,) - except TypeError: - pass - else: - self.fail("shouldn't be able to assign to list.__bases__") - - try: - D.__bases__ = (C2, list) - except TypeError: - pass - else: - assert 0, "best_base calculation found wanting" - try: del D.__bases__ except (TypeError, AttributeError): @@ -3657,6 +3632,36 @@ order (MRO) for bases """ if tp is not object: self.assertEqual(len(tp.__bases__), 1, tp) + class L(list): + pass + + class C(object): + pass + + class D(C): + pass + + try: + L.__bases__ = (dict,) + except TypeError: + pass + else: + self.fail("shouldn't turn list subclass into dict subclass") + + try: + list.__bases__ = (dict,) + except TypeError: + pass + else: + self.fail("shouldn't be able to assign to list.__bases__") + + try: + D.__bases__ = (C, list) + except TypeError: + pass + else: + assert 0, "best_base calculation found wanting" + def test_mutable_bases_with_failing_mro(self): # Testing mutable bases with failing mro... diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 1463cf7..69ec491 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -894,6 +894,7 @@ class EncodingTest(BaseTest): message = '\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f' #Ensure it's written in a Cyrillic encoding writer_class = codecs.getwriter('cp1251') + writer_class.encoding = 'cp1251' stream = io.BytesIO() writer = writer_class(stream, 'strict') handler = logging.StreamHandler(writer) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index cdb039d..5a546ec 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -46,9 +46,23 @@ class TestShutil(unittest.TestCase): shutil.rmtree(TESTFN) def check_args_to_onerror(self, func, arg, exc): + # test_rmtree_errors deliberately runs rmtree + # on a directory that is chmod 400, which will fail. + # This function is run when shutil.rmtree fails. + # 99.9% of the time it initially fails to remove + # a file in the directory, so the first time through + # func is os.remove. + # However, some Linux machines running ZFS on + # FUSE experienced a failure earlier in the process + # at os.listdir. The first failure may legally + # be either. if self.errorState == 0: - self.assertEqual(func, os.remove) - self.assertEqual(arg, self.childpath) + if func is os.remove: + self.assertEqual(arg, self.childpath) + else: + self.assertIs(func, os.listdir, + "func must be either os.remove or os.listdir") + self.assertEqual(arg, TESTFN) self.failUnless(issubclass(exc[0], OSError)) self.errorState = 1 else: diff --git a/Lib/test/test_unittest.py b/Lib/test/test_unittest.py index d0d2049..e7097cc 100644 --- a/Lib/test/test_unittest.py +++ b/Lib/test/test_unittest.py @@ -9,9 +9,10 @@ Still need testing: import re from test import support import unittest -from unittest import TestCase +from unittest import TestCase, TestProgram import types from copy import deepcopy +import io ### Support code ################################################################ @@ -25,10 +26,18 @@ class LoggingResult(unittest.TestResult): self._events.append('startTest') super().startTest(test) + def startTestRun(self): + self._events.append('startTestRun') + super(LoggingResult, self).startTestRun() + def stopTest(self, test): self._events.append('stopTest') super().stopTest(test) + def stopTestRun(self): + self._events.append('stopTestRun') + super(LoggingResult, self).stopTestRun() + def addFailure(self, *args): self._events.append('addFailure') super().addFailure(*args) @@ -1826,6 +1835,12 @@ class Test_TestResult(TestCase): self.assertEqual(result.testsRun, 1) self.assertEqual(result.shouldStop, False) + # "Called before and after tests are run. The default implementation does nothing." + def test_startTestRun_stopTestRun(self): + result = unittest.TestResult() + result.startTestRun() + result.stopTestRun() + # "addSuccess(test)" # ... # "Called when the test case test succeeds" @@ -1973,6 +1988,53 @@ class Foo(unittest.TestCase): class Bar(Foo): def test2(self): pass +class LoggingTestCase(unittest.TestCase): + """A test case which logs its calls.""" + + def __init__(self, events): + super(LoggingTestCase, self).__init__('test') + self.events = events + + def setUp(self): + self.events.append('setUp') + + def test(self): + self.events.append('test') + + def tearDown(self): + self.events.append('tearDown') + +class ResultWithNoStartTestRunStopTestRun(object): + """An object honouring TestResult before startTestRun/stopTestRun.""" + + def __init__(self): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.skipped = [] + self.expectedFailures = [] + self.unexpectedSuccesses = [] + self.shouldStop = False + + def startTest(self, test): + pass + + def stopTest(self, test): + pass + + def addError(self, test): + pass + + def addFailure(self, test): + pass + + def addSuccess(self, test): + pass + + def wasSuccessful(self): + return True + + ################################################################ ### /Support code for Test_TestCase @@ -2067,19 +2129,30 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): events = [] result = LoggingResult(events) - class Foo(unittest.TestCase): + class Foo(LoggingTestCase): def setUp(self): - events.append('setUp') + super(Foo, self).setUp() raise RuntimeError('raised by Foo.setUp') - def test(self): - events.append('test') + Foo(events).run(result) + expected = ['startTest', 'setUp', 'addError', 'stopTest'] + self.assertEqual(events, expected) - def tearDown(self): - events.append('tearDown') + # "With a temporary result stopTestRun is called when setUp errors. + def test_run_call_order__error_in_setUp_default_result(self): + events = [] - Foo('test').run(result) - expected = ['startTest', 'setUp', 'addError', 'stopTest'] + class Foo(LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + + def setUp(self): + super(Foo, self).setUp() + raise RuntimeError('raised by Foo.setUp') + + Foo(events).run() + expected = ['startTestRun', 'startTest', 'setUp', 'addError', + 'stopTest', 'stopTestRun'] self.assertEqual(events, expected) # "When a setUp() method is defined, the test runner will run that method @@ -2093,20 +2166,32 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): events = [] result = LoggingResult(events) - class Foo(unittest.TestCase): - def setUp(self): - events.append('setUp') - + class Foo(LoggingTestCase): def test(self): - events.append('test') + super(Foo, self).test() raise RuntimeError('raised by Foo.test') - def tearDown(self): - events.append('tearDown') - expected = ['startTest', 'setUp', 'test', 'addError', 'tearDown', 'stopTest'] - Foo('test').run(result) + Foo(events).run(result) + self.assertEqual(events, expected) + + # "With a default result, an error in the test still results in stopTestRun + # being called." + def test_run_call_order__error_in_test_default_result(self): + events = [] + + class Foo(LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + + def test(self): + super(Foo, self).test() + raise RuntimeError('raised by Foo.test') + + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addError', + 'tearDown', 'stopTest', 'stopTestRun'] + Foo(events).run() self.assertEqual(events, expected) # "When a setUp() method is defined, the test runner will run that method @@ -2120,20 +2205,30 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): events = [] result = LoggingResult(events) - class Foo(unittest.TestCase): - def setUp(self): - events.append('setUp') - + class Foo(LoggingTestCase): def test(self): - events.append('test') + super(Foo, self).test() self.fail('raised by Foo.test') - def tearDown(self): - events.append('tearDown') - expected = ['startTest', 'setUp', 'test', 'addFailure', 'tearDown', 'stopTest'] - Foo('test').run(result) + Foo(events).run(result) + self.assertEqual(events, expected) + + # "When a test fails with a default result stopTestRun is still called." + def test_run_call_order__failure_in_test_default_result(self): + + class Foo(LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + def test(self): + super(Foo, self).test() + self.fail('raised by Foo.test') + + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addFailure', + 'tearDown', 'stopTest', 'stopTestRun'] + events = [] + Foo(events).run() self.assertEqual(events, expected) # "When a setUp() method is defined, the test runner will run that method @@ -2147,22 +2242,44 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): events = [] result = LoggingResult(events) - class Foo(unittest.TestCase): - def setUp(self): - events.append('setUp') - - def test(self): - events.append('test') - + class Foo(LoggingTestCase): def tearDown(self): - events.append('tearDown') + super(Foo, self).tearDown() raise RuntimeError('raised by Foo.tearDown') - Foo('test').run(result) + Foo(events).run(result) expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', 'stopTest'] self.assertEqual(events, expected) + # "When tearDown errors with a default result stopTestRun is still called." + def test_run_call_order__error_in_tearDown_default_result(self): + + class Foo(LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + def tearDown(self): + super(Foo, self).tearDown() + raise RuntimeError('raised by Foo.tearDown') + + events = [] + Foo(events).run() + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'tearDown', + 'addError', 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + # "TestCase.run() still works when the defaultTestResult is a TestResult + # that does not support startTestRun and stopTestRun. + def test_run_call_order_default_result(self): + + class Foo(unittest.TestCase): + def defaultTestResult(self): + return ResultWithNoStartTestRunStopTestRun() + def test(self): + pass + + Foo('test').run() + # "This class attribute gives the exception raised by the test() method. # If a test framework needs to use a specialized exception, possibly to # carry additional information, it must subclass this exception in @@ -2253,7 +2370,9 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): self.failUnless(isinstance(Foo().id(), str)) # "If result is omitted or None, a temporary result object is created - # and used, but is not made available to the caller" + # and used, but is not made available to the caller. As TestCase owns the + # temporary result startTestRun and stopTestRun are called. + def test_run__uses_defaultTestResult(self): events = [] @@ -2267,7 +2386,8 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): # Make run() find a result object on its own Foo('test').run() - expected = ['startTest', 'test', 'addSuccess', 'stopTest'] + expected = ['startTestRun', 'startTest', 'test', 'addSuccess', + 'stopTest', 'stopTestRun'] self.assertEqual(events, expected) def testShortDescriptionWithoutDocstring(self): @@ -3012,6 +3132,220 @@ class TestLongMessage(TestCase): "^unexpectedly identical: None : oops$"]) +class TestCleanUp(TestCase): + + def testCleanUp(self): + class TestableTest(TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + self.assertEqual(test._cleanups, []) + + cleanups = [] + + def cleanup1(*args, **kwargs): + cleanups.append((1, args, kwargs)) + + def cleanup2(*args, **kwargs): + cleanups.append((2, args, kwargs)) + + test.addCleanup(cleanup1, 1, 2, 3, four='hello', five='goodbye') + test.addCleanup(cleanup2) + + self.assertEqual(test._cleanups, + [(cleanup1, (1, 2, 3), dict(four='hello', five='goodbye')), + (cleanup2, (), {})]) + + result = test.doCleanups() + self.assertTrue(result) + + self.assertEqual(cleanups, [(2, (), {}), (1, (1, 2, 3), dict(four='hello', five='goodbye'))]) + + def testCleanUpWithErrors(self): + class TestableTest(TestCase): + def testNothing(self): + pass + + class MockResult(object): + errors = [] + def addError(self, test, exc_info): + self.errors.append((test, exc_info)) + + result = MockResult() + test = TestableTest('testNothing') + test._result = result + + exc1 = Exception('foo') + exc2 = Exception('bar') + def cleanup1(): + raise exc1 + + def cleanup2(): + raise exc2 + + test.addCleanup(cleanup1) + test.addCleanup(cleanup2) + + self.assertFalse(test.doCleanups()) + + (test1, (Type1, instance1, _)), (test2, (Type2, instance2, _)) = reversed(MockResult.errors) + self.assertEqual((test1, Type1, instance1), (test, Exception, exc1)) + self.assertEqual((test2, Type2, instance2), (test, Exception, exc2)) + + def testCleanupInRun(self): + blowUp = False + ordering = [] + + class TestableTest(TestCase): + def setUp(self): + ordering.append('setUp') + if blowUp: + raise Exception('foo') + + def testNothing(self): + ordering.append('test') + + def tearDown(self): + ordering.append('tearDown') + + test = TestableTest('testNothing') + + def cleanup1(): + ordering.append('cleanup1') + def cleanup2(): + ordering.append('cleanup2') + test.addCleanup(cleanup1) + test.addCleanup(cleanup2) + + def success(some_test): + self.assertEqual(some_test, test) + ordering.append('success') + + result = unittest.TestResult() + result.addSuccess = success + + test.run(result) + self.assertEqual(ordering, ['setUp', 'test', 'tearDown', + 'cleanup2', 'cleanup1', 'success']) + + blowUp = True + ordering = [] + test = TestableTest('testNothing') + test.addCleanup(cleanup1) + test.run(result) + self.assertEqual(ordering, ['setUp', 'cleanup1']) + + +class Test_TestProgram(TestCase): + + # Horrible white box test + def testNoExit(self): + result = object() + test = object() + + class FakeRunner(object): + def run(self, test): + self.test = test + return result + + runner = FakeRunner() + + try: + oldParseArgs = TestProgram.parseArgs + TestProgram.parseArgs = lambda *args: None + TestProgram.test = test + + program = TestProgram(testRunner=runner, exit=False) + + self.assertEqual(program.result, result) + self.assertEqual(runner.test, test) + + finally: + TestProgram.parseArgs = oldParseArgs + del TestProgram.test + + + class FooBar(unittest.TestCase): + def testPass(self): + assert True + def testFail(self): + assert False + + class FooBarLoader(unittest.TestLoader): + """Test loader that returns a suite containing FooBar.""" + def loadTestsFromModule(self, module): + return self.suiteClass( + [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) + + + def test_NonExit(self): + program = unittest.main(exit=False, + argv=["foobar"], + testRunner=unittest.TextTestRunner(stream=io.StringIO()), + testLoader=self.FooBarLoader()) + self.assertTrue(hasattr(program, 'result')) + + + def test_Exit(self): + self.assertRaises( + SystemExit, + unittest.main, + argv=["foobar"], + testRunner=unittest.TextTestRunner(stream=io.StringIO()), + exit=True, + testLoader=self.FooBarLoader()) + + + def test_ExitAsDefault(self): + self.assertRaises( + SystemExit, + unittest.main, + argv=["foobar"], + testRunner=unittest.TextTestRunner(stream=io.StringIO()), + testLoader=self.FooBarLoader()) + + +class Test_TextTestRunner(TestCase): + """Tests for TextTestRunner.""" + + def test_works_with_result_without_startTestRun_stopTestRun(self): + class OldTextResult(ResultWithNoStartTestRunStopTestRun): + separator2 = '' + def printErrors(self): + pass + + class Runner(unittest.TextTestRunner): + def __init__(self): + super(Runner, self).__init__(io.StringIO()) + + def _makeResult(self): + return OldTextResult() + + runner = Runner() + runner.run(unittest.TestSuite()) + + def test_startTestRun_stopTestRun_called(self): + class LoggingTextResult(LoggingResult): + separator2 = '' + def printErrors(self): + pass + + class LoggingRunner(unittest.TextTestRunner): + def __init__(self, events): + super(LoggingRunner, self).__init__(io.StringIO()) + self._events = events + + def _makeResult(self): + return LoggingTextResult(self._events) + + events = [] + runner = LoggingRunner(events) + runner.run(unittest.TestSuite()) + expected = ['startTestRun', 'stopTestRun'] + self.assertEqual(events, expected) + + ###################################################################### ## Main ###################################################################### @@ -3019,7 +3353,8 @@ class TestLongMessage(TestCase): def test_main(): support.run_unittest(Test_TestCase, Test_TestLoader, Test_TestSuite, Test_TestResult, Test_FunctionTestCase, - Test_TestSkipping, Test_Assertions, TestLongMessage) + Test_TestSkipping, Test_Assertions, TestLongMessage, + Test_TestProgram, TestCleanUp) if __name__ == "__main__": test_main() diff --git a/Lib/unittest.py b/Lib/unittest.py index 0b7cea4..c6d893e 100644 --- a/Lib/unittest.py +++ b/Lib/unittest.py @@ -173,10 +173,22 @@ class TestResult(object): "Called when the given test is about to be run" self.testsRun = self.testsRun + 1 + def startTestRun(self): + """Called once before any tests are executed. + + See startTest for a method called before each test. + """ + def stopTest(self, test): "Called when the given test has been run" pass + def stopTestRun(self): + """Called once after all tests are executed. + + See stopTest for a method called after each test. + """ + def addError(self, test, err): """Called when an error has occurred. 'err' is a tuple of values as returned by sys.exc_info(). @@ -262,7 +274,7 @@ class _AssertRaisesContext(object): def __enter__(self): pass - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type, exc_value, tb): if exc_type is None: try: exc_name = self.expected.__name__ @@ -341,12 +353,14 @@ class TestCase(object): not have a method with the specified name. """ self._testMethodName = methodName + self._result = None try: testMethod = getattr(self, methodName) except AttributeError: raise ValueError("no such test method in %s: %s" % \ (self.__class__, methodName)) self._testMethodDoc = testMethod.__doc__ + self._cleanups = [] # Map types to custom assertEqual functions that will compare # instances of said type in more detail to generate a more useful @@ -373,6 +387,14 @@ class TestCase(object): """ self._type_equality_funcs[typeobj] = _AssertWrapper(function) + def addCleanup(self, function, *args, **kwargs): + """Add a function, with arguments, to be called when the test is + completed. Functions added are called on a LIFO basis and are + called after tearDown on test failure or success. + + Cleanup items are called even if setUp fails (unlike tearDown).""" + self._cleanups.append((function, args, kwargs)) + def setUp(self): "Hook method for setting up the test fixture before exercising it." pass @@ -428,45 +450,70 @@ class TestCase(object): (_strclass(self.__class__), self._testMethodName) def run(self, result=None): + orig_result = result if result is None: result = self.defaultTestResult() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + + self._result = result result.startTest(self) testMethod = getattr(self, self._testMethodName) try: - try: - self.setUp() - except SkipTest as e: - result.addSkip(self, str(e)) - return - except Exception: - result.addError(self, sys.exc_info()) - return - success = False try: - testMethod() - except self.failureException: - result.addFailure(self, sys.exc_info()) - except _ExpectedFailure as e: - result.addExpectedFailure(self, e.exc_info) - except _UnexpectedSuccess: - result.addUnexpectedSuccess(self) + self.setUp() except SkipTest as e: result.addSkip(self, str(e)) except Exception: result.addError(self, sys.exc_info()) else: - success = True + try: + testMethod() + except self.failureException: + result.addFailure(self, sys.exc_info()) + except _ExpectedFailure as e: + result.addExpectedFailure(self, e.exc_info) + except _UnexpectedSuccess: + result.addUnexpectedSuccess(self) + except SkipTest as e: + result.addSkip(self, str(e)) + except Exception: + result.addError(self, sys.exc_info()) + else: + success = True - try: - self.tearDown() - except Exception: - result.addError(self, sys.exc_info()) - success = False + try: + self.tearDown() + except Exception: + result.addError(self, sys.exc_info()) + success = False + + cleanUpSuccess = self.doCleanups() + success = success and cleanUpSuccess if success: result.addSuccess(self) finally: result.stopTest(self) + if orig_result is None: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + + def doCleanups(self): + """Execute all cleanup functions. Normally called for you after + tearDown.""" + result = self._result + ok = True + while self._cleanups: + function, args, kwargs = self._cleanups.pop(-1) + try: + function(*args, **kwargs) + except Exception: + ok = False + result.addError(self, sys.exc_info()) + return ok def __call__(self, *args, **kwds): return self.run(*args, **kwds) @@ -1037,7 +1084,7 @@ class TestSuite(object): def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented - return self._tests == other._tests + return list(self) == list(other) def __ne__(self, other): return not self == other @@ -1160,8 +1207,7 @@ class FunctionTestCase(TestCase): self._testFunc, self._description)) def __str__(self): - return "%s (%s)" % (_strclass(self.__class__), - self.__testFunc.__name__) + return "%s (%s)" % (_strclass(self.__class__), self._testFunc.__name__) def __repr__(self): return "<%s testFunc=%s>" % (_strclass(self.__class__), self._testFunc) @@ -1449,7 +1495,15 @@ class TextTestRunner(object): "Run the given test case or test suite." result = self._makeResult() startTime = time.time() - test(result) + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + try: + test(result) + finally: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() stopTime = time.time() timeTaken = stopTime - startTime result.printErrors() @@ -1511,7 +1565,7 @@ Examples: """ def __init__(self, module='__main__', defaultTest=None, argv=None, testRunner=TextTestRunner, - testLoader=defaultTestLoader): + testLoader=defaultTestLoader, exit=True): if isinstance(module, str): self.module = __import__(module) for part in module.split('.')[1:]: @@ -1520,6 +1574,8 @@ Examples: self.module = module if argv is None: argv = sys.argv + + self.exit = exit self.verbosity = 1 self.defaultTest = defaultTest self.testRunner = testRunner @@ -1571,8 +1627,9 @@ Examples: else: # it is assumed to be a TestRunner instance testRunner = self.testRunner - result = testRunner.run(self.test) - sys.exit(not result.wasSuccessful()) + self.result = testRunner.run(self.test) + if self.exit: + sys.exit(not self.result.wasSuccessful()) main = TestProgram |