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

Proposed by Martin Packman
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merged at revision: 6229
Proposed branch: lp:~gz/bzr/parallel_fork_broken_child_804130
Merge into: lp:bzr
Prerequisite: lp:~gz/bzr/trivial_cleanup_testcases_release_notes
Diff against target: 198 lines (+79/-43)
3 files modified
bzrlib/tests/__init__.py (+13/-9)
bzrlib/tests/test_selftest.py (+63/-34)
doc/en/release-notes/bzr-2.5.txt (+3/-0)
To merge this branch: bzr merge lp:~gz/bzr/parallel_fork_broken_child_804130
Reviewer Review Type Date Requested Status
Jelmer Vernooij (community) Approve
Review via email: mp+79674@code.launchpad.net

Commit message

Report child process exceptions from selftest --parallel=fork

Description of the change

Makes breakage in a selftest --parallel=fork child visible by printing the traceback to the child-to-parent stream if possible, rather than silently exiting with a success code. This is mostly useful when working on selftest code, as otherwise when something breaks it reports "0 tests OK" which takes some remembering to know it actually means the world broke. Now at least, it should be preceded by two plus tracebacks from the child processes, so will look different from the usual case of having misspelt a test subset.

I pulled some of the hacky code out of the TestUncollectedWarnings* classes so it could be reused for the test here, any feedback on how I've done that is welcome.

Finally, it would be nice to actually do something in the parent process if the child finishes with a non-zero status. It's no good raising an exception, because it just gets caught in the subthread, so short of creating another fake test case just to fail and appear in the output I'm not sure what.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

A fake test in the output seems sane to me. Or - you know all the
tests that were intended to run, you could make them all fail ('not
executed by <...>')

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

The refactoring seems reasonable to me.

review: Approve
Revision history for this message
Martin Packman (gz) wrote :

sent to pqm by email

Revision history for this message
Martin Packman (gz) wrote :

Decided to merge this branch as it stands and leave thinking about better error reporting for subunit for another day.

Revision history for this message
Martin Packman (gz) 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 2011-10-17 15:27:38 +0000
+++ bzrlib/tests/__init__.py 2011-10-21 16:05:50 +0000
@@ -3488,7 +3488,9 @@
3488 try:3488 try:
3489 ProtocolTestCase.run(self, result)3489 ProtocolTestCase.run(self, result)
3490 finally:3490 finally:
3491 os.waitpid(self.pid, 0)3491 pid, status = os.waitpid(self.pid, 0)
3492 # GZ 2011-10-18: If status is nonzero, should report to the result
3493 # that something went wrong.
34923494
3493 test_blocks = partition_tests(suite, concurrency)3495 test_blocks = partition_tests(suite, concurrency)
3494 # Clear the tests from the original suite so it doesn't keep them alive3496 # Clear the tests from the original suite so it doesn't keep them alive
@@ -3500,24 +3502,26 @@
3500 c2pread, c2pwrite = os.pipe()3502 c2pread, c2pwrite = os.pipe()
3501 pid = os.fork()3503 pid = os.fork()
3502 if pid == 0:3504 if pid == 0:
3503 workaround_zealous_crypto_random()
3504 try:3505 try:
3506 stream = os.fdopen(c2pwrite, 'wb', 1)
3507 workaround_zealous_crypto_random()
3505 os.close(c2pread)3508 os.close(c2pread)
3506 # Leave stderr and stdout open so we can see test noise3509 # Leave stderr and stdout open so we can see test noise
3507 # Close stdin so that the child goes away if it decides to3510 # Close stdin so that the child goes away if it decides to
3508 # read from stdin (otherwise its a roulette to see what3511 # read from stdin (otherwise its a roulette to see what
3509 # child actually gets keystrokes for pdb etc).3512 # child actually gets keystrokes for pdb etc).
3510 sys.stdin.close()3513 sys.stdin.close()
3511 # GZ 2011-06-16: Why set stdin to None? Breaks multi fork.
3512 #sys.stdin = None
3513 stream = os.fdopen(c2pwrite, 'wb', 1)
3514 subunit_result = AutoTimingTestResultDecorator(3514 subunit_result = AutoTimingTestResultDecorator(
3515 SubUnitBzrProtocolClient(stream))3515 SubUnitBzrProtocolClient(stream))
3516 process_suite.run(subunit_result)3516 process_suite.run(subunit_result)
3517 finally:3517 except:
3518 # GZ 2011-06-16: Is always exiting with silent success3518 # Try and report traceback on stream, but exit with error even
3519 # really the right thing? Hurts debugging.3519 # if stream couldn't be created or something else goes wrong
3520 os._exit(0)3520 try:
3521 traceback.print_exc(file=stream)
3522 finally:
3523 os._exit(1)
3524 os._exit(0)
3521 else:3525 else:
3522 os.close(c2pwrite)3526 os.close(c2pwrite)
3523 stream = os.fdopen(c2pread, 'rb', 1)3527 stream = os.fdopen(c2pread, 'rb', 1)
35243528
=== modified file 'bzrlib/tests/test_selftest.py'
--- bzrlib/tests/test_selftest.py 2011-10-05 14:46:09 +0000
+++ bzrlib/tests/test_selftest.py 2011-10-21 16:05:50 +0000
@@ -3315,7 +3315,66 @@
3315 self.assertLength(1, calls)3315 self.assertLength(1, calls)
33163316
33173317
3318class TestUncollectedWarnings(tests.TestCase):3318class _Selftest(object):
3319 """Mixin for tests needing full selftest output"""
3320
3321 def _inject_stream_into_subunit(self, stream):
3322 """To be overridden by subclasses that run tests out of process"""
3323
3324 def _run_selftest(self, **kwargs):
3325 sio = StringIO()
3326 self._inject_stream_into_subunit(sio)
3327 tests.selftest(stream=sio, stop_on_failure=False, **kwargs)
3328 return sio.getvalue()
3329
3330
3331class _ForkedSelftest(_Selftest):
3332 """Mixin for tests needing full selftest output with forked children"""
3333
3334 _test_needs_features = [features.subunit]
3335
3336 def _inject_stream_into_subunit(self, stream):
3337 """Monkey-patch subunit so the extra output goes to stream not stdout
3338
3339 Some APIs need rewriting so this kind of bogus hackery can be replaced
3340 by passing the stream param from run_tests down into ProtocolTestCase.
3341 """
3342 from subunit import ProtocolTestCase
3343 _original_init = ProtocolTestCase.__init__
3344 def _init_with_passthrough(self, *args, **kwargs):
3345 _original_init(self, *args, **kwargs)
3346 self._passthrough = stream
3347 self.overrideAttr(ProtocolTestCase, "__init__", _init_with_passthrough)
3348
3349 def _run_selftest(self, **kwargs):
3350 # GZ 2011-05-26: Add a PosixSystem feature so this check can go away
3351 if getattr(os, "fork", None) is None:
3352 raise tests.TestNotApplicable("Platform doesn't support forking")
3353 # Make sure the fork code is actually invoked by claiming two cores
3354 self.overrideAttr(osutils, "local_concurrency", lambda: 2)
3355 kwargs.setdefault("suite_decorators", []).append(tests.fork_decorator)
3356 return super(_ForkedSelftest, self)._run_selftest(**kwargs)
3357
3358
3359class TestParallelFork(_ForkedSelftest, tests.TestCase):
3360 """Check operation of --parallel=fork selftest option"""
3361
3362 def test_error_in_child_during_fork(self):
3363 """Error in a forked child during test setup should get reported"""
3364 class Test(tests.TestCase):
3365 def testMethod(self):
3366 pass
3367 # We don't care what, just break something that a child will run
3368 self.overrideAttr(tests, "workaround_zealous_crypto_random", None)
3369 out = self._run_selftest(test_suite_factory=Test)
3370 self.assertContainsRe(out,
3371 "Traceback.*:\n"
3372 ".+ in fork_for_tests\n"
3373 "\s*workaround_zealous_crypto_random\(\)\n"
3374 "TypeError:")
3375
3376
3377class TestUncollectedWarnings(_Selftest, tests.TestCase):
3319 """Check a test case still alive after being run emits a warning"""3378 """Check a test case still alive after being run emits a warning"""
33203379
3321 class Test(tests.TestCase):3380 class Test(tests.TestCase):
@@ -3333,25 +3392,19 @@
3333 self.Test("test_skip"),3392 self.Test("test_skip"),
3334 ])3393 ])
33353394
3336 def _inject_stream_into_subunit(self, stream):
3337 """To be overridden by subclasses that run tests out of process"""
3338
3339 def _run_selftest_with_suite(self, **kwargs):3395 def _run_selftest_with_suite(self, **kwargs):
3340 sio = StringIO()
3341 self._inject_stream_into_subunit(sio)
3342 old_flags = tests.selftest_debug_flags3396 old_flags = tests.selftest_debug_flags
3343 tests.selftest_debug_flags = old_flags.union(["uncollected_cases"])3397 tests.selftest_debug_flags = old_flags.union(["uncollected_cases"])
3344 gc_on = gc.isenabled()3398 gc_on = gc.isenabled()
3345 if gc_on:3399 if gc_on:
3346 gc.disable()3400 gc.disable()
3347 try:3401 try:
3348 tests.selftest(test_suite_factory=self._get_suite, stream=sio,3402 output = self._run_selftest(test_suite_factory=self._get_suite,
3349 stop_on_failure=False, **kwargs)3403 **kwargs)
3350 finally:3404 finally:
3351 if gc_on:3405 if gc_on:
3352 gc.enable()3406 gc.enable()
3353 tests.selftest_debug_flags = old_flags3407 tests.selftest_debug_flags = old_flags
3354 output = sio.getvalue()
3355 self.assertNotContainsRe(output, "Uncollected test case.*test_pass")3408 self.assertNotContainsRe(output, "Uncollected test case.*test_pass")
3356 self.assertContainsRe(output, "Uncollected test case.*test_self_ref")3409 self.assertContainsRe(output, "Uncollected test case.*test_self_ref")
3357 return output3410 return output
@@ -3394,33 +3447,9 @@
3394 runner_class=tests.SubUnitBzrRunner, **kwargs)3447 runner_class=tests.SubUnitBzrRunner, **kwargs)
33953448
33963449
3397class TestUncollectedWarningsForking(TestUncollectedWarnings):3450class TestUncollectedWarningsForked(_ForkedSelftest, TestUncollectedWarnings):
3398 """Check warnings from tests staying alive are emitted when forking"""3451 """Check warnings from tests staying alive are emitted when forking"""
33993452
3400 _test_needs_features = [features.subunit]
3401
3402 def _inject_stream_into_subunit(self, stream):
3403 """Monkey-patch subunit so the extra output goes to stream not stdout
3404
3405 Some APIs need rewriting so this kind of bogus hackery can be replaced
3406 by passing the stream param from run_tests down into ProtocolTestCase.
3407 """
3408 from subunit import ProtocolTestCase
3409 _original_init = ProtocolTestCase.__init__
3410 def _init_with_passthrough(self, *args, **kwargs):
3411 _original_init(self, *args, **kwargs)
3412 self._passthrough = stream
3413 self.overrideAttr(ProtocolTestCase, "__init__", _init_with_passthrough)
3414
3415 def _run_selftest_with_suite(self, **kwargs):
3416 # GZ 2011-05-26: Add a PosixSystem feature so this check can go away
3417 if getattr(os, "fork", None) is None:
3418 raise tests.TestNotApplicable("Platform doesn't support forking")
3419 # Make sure the fork code is actually invoked by claiming two cores
3420 self.overrideAttr(osutils, "local_concurrency", lambda: 2)
3421 kwargs.setdefault("suite_decorators", []).append(tests.fork_decorator)
3422 return TestUncollectedWarnings._run_selftest_with_suite(self, **kwargs)
3423
34243453
3425class TestEnvironHandling(tests.TestCase):3454class TestEnvironHandling(tests.TestCase):
34263455
34273456
=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- doc/en/release-notes/bzr-2.5.txt 2011-10-19 12:46:41 +0000
+++ doc/en/release-notes/bzr-2.5.txt 2011-10-21 16:05:50 +0000
@@ -107,6 +107,9 @@
107 run. The new ``-Euncollected_cases`` selftest flag will add failures if any107 run. The new ``-Euncollected_cases`` selftest flag will add failures if any
108 case which persists pasts its expected lifetime. (Martin Packman, #613247)108 case which persists pasts its expected lifetime. (Martin Packman, #613247)
109109
110* Report exceptions from child processes during fork instead of swallowing the
111 error and reporting that everything went okay. (Martin Packman, #804130)
112
110113
111bzr 2.5b2114bzr 2.5b2
112#########115#########