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
1=== modified file 'bzrlib/tests/__init__.py'
2--- bzrlib/tests/__init__.py 2011-10-17 15:27:38 +0000
3+++ bzrlib/tests/__init__.py 2011-10-21 16:05:50 +0000
4@@ -3488,7 +3488,9 @@
5 try:
6 ProtocolTestCase.run(self, result)
7 finally:
8- os.waitpid(self.pid, 0)
9+ pid, status = os.waitpid(self.pid, 0)
10+ # GZ 2011-10-18: If status is nonzero, should report to the result
11+ # that something went wrong.
12
13 test_blocks = partition_tests(suite, concurrency)
14 # Clear the tests from the original suite so it doesn't keep them alive
15@@ -3500,24 +3502,26 @@
16 c2pread, c2pwrite = os.pipe()
17 pid = os.fork()
18 if pid == 0:
19- workaround_zealous_crypto_random()
20 try:
21+ stream = os.fdopen(c2pwrite, 'wb', 1)
22+ workaround_zealous_crypto_random()
23 os.close(c2pread)
24 # Leave stderr and stdout open so we can see test noise
25 # Close stdin so that the child goes away if it decides to
26 # read from stdin (otherwise its a roulette to see what
27 # child actually gets keystrokes for pdb etc).
28 sys.stdin.close()
29- # GZ 2011-06-16: Why set stdin to None? Breaks multi fork.
30- #sys.stdin = None
31- stream = os.fdopen(c2pwrite, 'wb', 1)
32 subunit_result = AutoTimingTestResultDecorator(
33 SubUnitBzrProtocolClient(stream))
34 process_suite.run(subunit_result)
35- finally:
36- # GZ 2011-06-16: Is always exiting with silent success
37- # really the right thing? Hurts debugging.
38- os._exit(0)
39+ except:
40+ # Try and report traceback on stream, but exit with error even
41+ # if stream couldn't be created or something else goes wrong
42+ try:
43+ traceback.print_exc(file=stream)
44+ finally:
45+ os._exit(1)
46+ os._exit(0)
47 else:
48 os.close(c2pwrite)
49 stream = os.fdopen(c2pread, 'rb', 1)
50
51=== modified file 'bzrlib/tests/test_selftest.py'
52--- bzrlib/tests/test_selftest.py 2011-10-05 14:46:09 +0000
53+++ bzrlib/tests/test_selftest.py 2011-10-21 16:05:50 +0000
54@@ -3315,7 +3315,66 @@
55 self.assertLength(1, calls)
56
57
58-class TestUncollectedWarnings(tests.TestCase):
59+class _Selftest(object):
60+ """Mixin for tests needing full selftest output"""
61+
62+ def _inject_stream_into_subunit(self, stream):
63+ """To be overridden by subclasses that run tests out of process"""
64+
65+ def _run_selftest(self, **kwargs):
66+ sio = StringIO()
67+ self._inject_stream_into_subunit(sio)
68+ tests.selftest(stream=sio, stop_on_failure=False, **kwargs)
69+ return sio.getvalue()
70+
71+
72+class _ForkedSelftest(_Selftest):
73+ """Mixin for tests needing full selftest output with forked children"""
74+
75+ _test_needs_features = [features.subunit]
76+
77+ def _inject_stream_into_subunit(self, stream):
78+ """Monkey-patch subunit so the extra output goes to stream not stdout
79+
80+ Some APIs need rewriting so this kind of bogus hackery can be replaced
81+ by passing the stream param from run_tests down into ProtocolTestCase.
82+ """
83+ from subunit import ProtocolTestCase
84+ _original_init = ProtocolTestCase.__init__
85+ def _init_with_passthrough(self, *args, **kwargs):
86+ _original_init(self, *args, **kwargs)
87+ self._passthrough = stream
88+ self.overrideAttr(ProtocolTestCase, "__init__", _init_with_passthrough)
89+
90+ def _run_selftest(self, **kwargs):
91+ # GZ 2011-05-26: Add a PosixSystem feature so this check can go away
92+ if getattr(os, "fork", None) is None:
93+ raise tests.TestNotApplicable("Platform doesn't support forking")
94+ # Make sure the fork code is actually invoked by claiming two cores
95+ self.overrideAttr(osutils, "local_concurrency", lambda: 2)
96+ kwargs.setdefault("suite_decorators", []).append(tests.fork_decorator)
97+ return super(_ForkedSelftest, self)._run_selftest(**kwargs)
98+
99+
100+class TestParallelFork(_ForkedSelftest, tests.TestCase):
101+ """Check operation of --parallel=fork selftest option"""
102+
103+ def test_error_in_child_during_fork(self):
104+ """Error in a forked child during test setup should get reported"""
105+ class Test(tests.TestCase):
106+ def testMethod(self):
107+ pass
108+ # We don't care what, just break something that a child will run
109+ self.overrideAttr(tests, "workaround_zealous_crypto_random", None)
110+ out = self._run_selftest(test_suite_factory=Test)
111+ self.assertContainsRe(out,
112+ "Traceback.*:\n"
113+ ".+ in fork_for_tests\n"
114+ "\s*workaround_zealous_crypto_random\(\)\n"
115+ "TypeError:")
116+
117+
118+class TestUncollectedWarnings(_Selftest, tests.TestCase):
119 """Check a test case still alive after being run emits a warning"""
120
121 class Test(tests.TestCase):
122@@ -3333,25 +3392,19 @@
123 self.Test("test_skip"),
124 ])
125
126- def _inject_stream_into_subunit(self, stream):
127- """To be overridden by subclasses that run tests out of process"""
128-
129 def _run_selftest_with_suite(self, **kwargs):
130- sio = StringIO()
131- self._inject_stream_into_subunit(sio)
132 old_flags = tests.selftest_debug_flags
133 tests.selftest_debug_flags = old_flags.union(["uncollected_cases"])
134 gc_on = gc.isenabled()
135 if gc_on:
136 gc.disable()
137 try:
138- tests.selftest(test_suite_factory=self._get_suite, stream=sio,
139- stop_on_failure=False, **kwargs)
140+ output = self._run_selftest(test_suite_factory=self._get_suite,
141+ **kwargs)
142 finally:
143 if gc_on:
144 gc.enable()
145 tests.selftest_debug_flags = old_flags
146- output = sio.getvalue()
147 self.assertNotContainsRe(output, "Uncollected test case.*test_pass")
148 self.assertContainsRe(output, "Uncollected test case.*test_self_ref")
149 return output
150@@ -3394,33 +3447,9 @@
151 runner_class=tests.SubUnitBzrRunner, **kwargs)
152
153
154-class TestUncollectedWarningsForking(TestUncollectedWarnings):
155+class TestUncollectedWarningsForked(_ForkedSelftest, TestUncollectedWarnings):
156 """Check warnings from tests staying alive are emitted when forking"""
157
158- _test_needs_features = [features.subunit]
159-
160- def _inject_stream_into_subunit(self, stream):
161- """Monkey-patch subunit so the extra output goes to stream not stdout
162-
163- Some APIs need rewriting so this kind of bogus hackery can be replaced
164- by passing the stream param from run_tests down into ProtocolTestCase.
165- """
166- from subunit import ProtocolTestCase
167- _original_init = ProtocolTestCase.__init__
168- def _init_with_passthrough(self, *args, **kwargs):
169- _original_init(self, *args, **kwargs)
170- self._passthrough = stream
171- self.overrideAttr(ProtocolTestCase, "__init__", _init_with_passthrough)
172-
173- def _run_selftest_with_suite(self, **kwargs):
174- # GZ 2011-05-26: Add a PosixSystem feature so this check can go away
175- if getattr(os, "fork", None) is None:
176- raise tests.TestNotApplicable("Platform doesn't support forking")
177- # Make sure the fork code is actually invoked by claiming two cores
178- self.overrideAttr(osutils, "local_concurrency", lambda: 2)
179- kwargs.setdefault("suite_decorators", []).append(tests.fork_decorator)
180- return TestUncollectedWarnings._run_selftest_with_suite(self, **kwargs)
181-
182
183 class TestEnvironHandling(tests.TestCase):
184
185
186=== modified file 'doc/en/release-notes/bzr-2.5.txt'
187--- doc/en/release-notes/bzr-2.5.txt 2011-10-19 12:46:41 +0000
188+++ doc/en/release-notes/bzr-2.5.txt 2011-10-21 16:05:50 +0000
189@@ -107,6 +107,9 @@
190 run. The new ``-Euncollected_cases`` selftest flag will add failures if any
191 case which persists pasts its expected lifetime. (Martin Packman, #613247)
192
193+* Report exceptions from child processes during fork instead of swallowing the
194+ error and reporting that everything went okay. (Martin Packman, #804130)
195+
196
197 bzr 2.5b2
198 #########