Merge lp:~gz/bzr/unbreak_BZR_TEST_PDB_504070 into lp:bzr

Proposed by Martin Packman
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merged at revision: 5470
Proposed branch: lp:~gz/bzr/unbreak_BZR_TEST_PDB_504070
Merge into: lp:bzr
Diff against target: 161 lines (+92/-4)
2 files modified
bzrlib/tests/__init__.py (+21/-4)
bzrlib/tests/test_selftest.py (+71/-0)
To merge this branch: bzr merge lp:~gz/bzr/unbreak_BZR_TEST_PDB_504070
Reviewer Review Type Date Requested Status
John A Meinel Approve
Review via email: mp+37434@code.launchpad.net

Commit message

Fix BZR_TEST_PDB. (#504070)

Description of the change

Fix regression from introduction of testtools which made BZR_TEST_PDB useless as it didn't get you the actual traceback from the test.

As explained in the bug, because of the different way testtools runs test cases, we need to jump through some extra hoops to get the real traceback from the test, stash it somewhere, and then pass it on in addError and addFailure. The traceback is cleared in stopTest to break the cycle.

There's one case this branch doesn't handle, where the test passes but an exception is raised from a testtools cleanup. There the onException handler doesn't get given the traceback, so pdb will raise a ValueError as there's nothing for it to look at.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

seems good to me.

review: Approve
Revision history for this message
Andrew Bennetts (spiv) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2010-09-27 19:31:45 +0000
+++ bzrlib/tests/__init__.py 2010-10-04 07:45:58 +0000
@@ -195,6 +195,7 @@
195 self._strict = strict195 self._strict = strict
196 self._first_thread_leaker_id = None196 self._first_thread_leaker_id = None
197 self._tests_leaking_threads_count = 0197 self._tests_leaking_threads_count = 0
198 self._traceback_from_test = None
198199
199 def stopTestRun(self):200 def stopTestRun(self):
200 run = self.testsRun201 run = self.testsRun
@@ -280,6 +281,14 @@
280 what = re.sub(r'^bzrlib\.tests\.', '', what)281 what = re.sub(r'^bzrlib\.tests\.', '', what)
281 return what282 return what
282283
284 # GZ 2010-10-04: Cloned tests may end up harmlessly calling this method
285 # multiple times in a row, because the handler is added for
286 # each test but the container list is shared between cases.
287 # See lp:498869 lp:625574 and lp:637725 for background.
288 def _record_traceback_from_test(self, exc_info):
289 """Store the traceback from passed exc_info tuple till"""
290 self._traceback_from_test = exc_info[2]
291
283 def startTest(self, test):292 def startTest(self, test):
284 super(ExtendedTestResult, self).startTest(test)293 super(ExtendedTestResult, self).startTest(test)
285 if self.count == 0:294 if self.count == 0:
@@ -288,6 +297,10 @@
288 self.report_test_start(test)297 self.report_test_start(test)
289 test.number = self.count298 test.number = self.count
290 self._recordTestStartTime()299 self._recordTestStartTime()
300 # Make testtools cases give us the real traceback on failure
301 addOnException = getattr(test, "addOnException", None)
302 if addOnException is not None:
303 addOnException(self._record_traceback_from_test)
291 # Only check for thread leaks if the test case supports cleanups304 # Only check for thread leaks if the test case supports cleanups
292 addCleanup = getattr(test, "addCleanup", None)305 addCleanup = getattr(test, "addCleanup", None)
293 if addCleanup is not None:306 if addCleanup is not None:
@@ -297,6 +310,9 @@
297 self.report_tests_starting()310 self.report_tests_starting()
298 self._active_threads = threading.enumerate()311 self._active_threads = threading.enumerate()
299312
313 def stopTest(self, test):
314 self._traceback_from_test = None
315
300 def _check_leaked_threads(self, test):316 def _check_leaked_threads(self, test):
301 """See if any threads have leaked since last call317 """See if any threads have leaked since last call
302318
@@ -323,7 +339,7 @@
323 Called from the TestCase run() method when the test339 Called from the TestCase run() method when the test
324 fails with an unexpected error.340 fails with an unexpected error.
325 """341 """
326 self._post_mortem()342 self._post_mortem(self._traceback_from_test)
327 super(ExtendedTestResult, self).addError(test, err)343 super(ExtendedTestResult, self).addError(test, err)
328 self.error_count += 1344 self.error_count += 1
329 self.report_error(test, err)345 self.report_error(test, err)
@@ -336,7 +352,7 @@
336 Called from the TestCase run() method when the test352 Called from the TestCase run() method when the test
337 fails because e.g. an assert() method failed.353 fails because e.g. an assert() method failed.
338 """354 """
339 self._post_mortem()355 self._post_mortem(self._traceback_from_test)
340 super(ExtendedTestResult, self).addFailure(test, err)356 super(ExtendedTestResult, self).addFailure(test, err)
341 self.failure_count += 1357 self.failure_count += 1
342 self.report_failure(test, err)358 self.report_failure(test, err)
@@ -384,10 +400,11 @@
384 self.not_applicable_count += 1400 self.not_applicable_count += 1
385 self.report_not_applicable(test, reason)401 self.report_not_applicable(test, reason)
386402
387 def _post_mortem(self):403 def _post_mortem(self, tb=None):
388 """Start a PDB post mortem session."""404 """Start a PDB post mortem session."""
389 if os.environ.get('BZR_TEST_PDB', None):405 if os.environ.get('BZR_TEST_PDB', None):
390 import pdb;pdb.post_mortem()406 import pdb
407 pdb.post_mortem(tb)
391408
392 def progress(self, offset, whence):409 def progress(self, offset, whence):
393 """The test is adjusting the count of tests to run."""410 """The test is adjusting the count of tests to run."""
394411
=== modified file 'bzrlib/tests/test_selftest.py'
--- bzrlib/tests/test_selftest.py 2010-09-27 19:31:45 +0000
+++ bzrlib/tests/test_selftest.py 2010-10-04 07:45:58 +0000
@@ -3301,6 +3301,77 @@
3301 self.assertContainsString(result.stream.getvalue(), "leaking threads")3301 self.assertContainsString(result.stream.getvalue(), "leaking threads")
33023302
33033303
3304class TestPostMortemDebugging(tests.TestCase):
3305 """Check post mortem debugging works when tests fail or error"""
3306
3307 class TracebackRecordingResult(tests.ExtendedTestResult):
3308 def __init__(self):
3309 tests.ExtendedTestResult.__init__(self, StringIO(), 0, 1)
3310 self.postcode = None
3311 def _post_mortem(self, tb=None):
3312 """Record the code object at the end of the current traceback"""
3313 tb = tb or sys.exc_info()[2]
3314 if tb is not None:
3315 next = tb.tb_next
3316 while next is not None:
3317 tb = next
3318 next = next.tb_next
3319 self.postcode = tb.tb_frame.f_code
3320 def report_error(self, test, err):
3321 pass
3322 def report_failure(self, test, err):
3323 pass
3324
3325 def test_location_unittest_error(self):
3326 """Needs right post mortem traceback with erroring unittest case"""
3327 class Test(unittest.TestCase):
3328 def runTest(self):
3329 raise RuntimeError
3330 result = self.TracebackRecordingResult()
3331 Test().run(result)
3332 self.assertEqual(result.postcode, Test.runTest.func_code)
3333
3334 def test_location_unittest_failure(self):
3335 """Needs right post mortem traceback with failing unittest case"""
3336 class Test(unittest.TestCase):
3337 def runTest(self):
3338 raise self.failureException
3339 result = self.TracebackRecordingResult()
3340 Test().run(result)
3341 self.assertEqual(result.postcode, Test.runTest.func_code)
3342
3343 def test_location_bt_error(self):
3344 """Needs right post mortem traceback with erroring bzrlib.tests case"""
3345 class Test(tests.TestCase):
3346 def test_error(self):
3347 raise RuntimeError
3348 result = self.TracebackRecordingResult()
3349 Test("test_error").run(result)
3350 self.assertEqual(result.postcode, Test.test_error.func_code)
3351
3352 def test_location_bt_failure(self):
3353 """Needs right post mortem traceback with failing bzrlib.tests case"""
3354 class Test(tests.TestCase):
3355 def test_failure(self):
3356 raise self.failureException
3357 result = self.TracebackRecordingResult()
3358 Test("test_failure").run(result)
3359 self.assertEqual(result.postcode, Test.test_failure.func_code)
3360
3361 def test_env_var_triggers_post_mortem(self):
3362 """Check pdb.post_mortem is called iff BZR_TEST_PDB is set"""
3363 import pdb
3364 result = tests.ExtendedTestResult(StringIO(), 0, 1)
3365 post_mortem_calls = []
3366 self.overrideAttr(pdb, "post_mortem", post_mortem_calls.append)
3367 self.addCleanup(osutils.set_or_unset_env, "BZR_TEST_PDB",
3368 osutils.set_or_unset_env("BZR_TEST_PDB", None))
3369 result._post_mortem(1)
3370 os.environ["BZR_TEST_PDB"] = "on"
3371 result._post_mortem(2)
3372 self.assertEqual([2], post_mortem_calls)
3373
3374
3304class TestRunSuite(tests.TestCase):3375class TestRunSuite(tests.TestCase):
33053376
3306 def test_runner_class(self):3377 def test_runner_class(self):