Merge lp:~frankban/launchpad/bug-974585-unclean-reactor-error into lp:launchpad

Proposed by Francesco Banconi on 2012-04-18
Status: Merged
Approved by: Benji York on 2012-04-18
Approved revision: no longer in the source branch.
Merged at revision: 15124
Proposed branch: lp:~frankban/launchpad/bug-974585-unclean-reactor-error
Merge into: lp:launchpad
Diff against target: 271 lines (+51/-11)
3 files modified
lib/lp/buildmaster/tests/test_builder.py (+40/-3)
lib/lp/registry/tests/test_distributionmirror_prober.py (+1/-8)
lib/lp/testing/__init__.py (+10/-0)
To merge this branch: bzr merge lp:~frankban/launchpad/bug-974585-unclean-reactor-error
Reviewer Review Type Date Requested Status
Benji York (community) code 2012-04-18 Approve on 2012-04-18
Review via email: mp+102473@code.launchpad.net

Description of the Change

= Summary =

Some tests using twisted leave delayed calls. If, later, another test uses
testtools an exception is raised if the reactor is not clean.

== Proposed fix ==

Use the clean_up_reactor function on the tearDown of tests leaving delayed
calls.

== Pre-implementation notes ==

I started a full test run temporary patching zope.testrunner to check and
notify not cancelled delayed calls after each test.
This way I spotted lp.buildmaster.tests.test_builder.TestSlaveConnectionTimeouts.test_connection_timeout as the only test leaving delayed calls. Hopefully, fixing that, we will not see the unclean reactor error again.

== Implementation details ==

- Move the `clean_up_reactor` function in a more generic place.
- And use it in
  lp.buildmaster.tests.test_builder.TestSlaveConnectionTimeouts.tearDown.
- Lint updates for file `lib/lp/buildmaster/tests/test_builder.py`.

== Demo and Q/A ==

no-qa

== lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/buildmaster/tests/test_builder.py
  lib/lp/registry/tests/test_distributionmirror_prober.py
  lib/lp/testing/__init__.py

To post a comment you must log in.
Benji York (benji) wrote :

Looks good. Your technique for finding offending tests was smart.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/buildmaster/tests/test_builder.py'
2--- lib/lp/buildmaster/tests/test_builder.py 2012-02-09 23:09:36 +0000
3+++ lib/lp/buildmaster/tests/test_builder.py 2012-04-19 08:39:19 +0000
4@@ -78,6 +78,7 @@
5 BinaryPackageBuildBehavior,
6 )
7 from lp.testing import (
8+ clean_up_reactor,
9 TestCase,
10 TestCaseWithFactory,
11 )
12@@ -150,11 +151,13 @@
13 lostbuilding_builder = MockBuilder(
14 'Lost Building Broken Slave', slave, behavior=CorruptBehavior())
15 d = lostbuilding_builder.updateStatus(BufferLogger())
16+
17 def check_slave_status(failure):
18 self.assertIn('abort', slave.call_log)
19 # 'Fault' comes from the LostBuildingBrokenSlave, this is
20 # just testing that the value is passed through.
21 self.assertIsInstance(failure.value, xmlrpclib.Fault)
22+
23 return d.addBoth(check_slave_status)
24
25 def test_resumeSlaveHost_nonvirtual(self):
26@@ -176,8 +179,10 @@
27
28 builder = self.factory.makeBuilder(virtualized=True, vm_host="pop")
29 d = builder.resumeSlaveHost()
30+
31 def got_resume(output):
32 self.assertEqual(('parp', ''), output)
33+
34 return d.addCallback(got_resume)
35
36 def test_resumeSlaveHost_command_failed(self):
37@@ -252,9 +257,11 @@
38 removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
39 result=candidate)
40 d = builder.findAndStartJob()
41+
42 def check_build_started(candidate):
43 self.assertEqual(candidate.builder, builder)
44 self.assertEqual(BuildStatus.BUILDING, build.status)
45+
46 return d.addCallback(check_build_started)
47
48 def test_virtual_job_dispatch_pings_before_building(self):
49@@ -265,9 +272,11 @@
50 removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
51 result=candidate)
52 d = builder.findAndStartJob()
53+
54 def check_build_started(candidate):
55 self.assertIn(
56 ('echo', 'ping'), removeSecurityProxy(builder.slave).call_log)
57+
58 return d.addCallback(check_build_started)
59
60 def test_slave(self):
61@@ -286,8 +295,10 @@
62 builder = MockBuilder("mock_builder", aborted_slave)
63 builder.currentjob = None
64 d = builder.rescueIfLost()
65+
66 def check_slave_calls(ignored):
67 self.assertIn('clean', aborted_slave.call_log)
68+
69 return d.addCallback(check_slave_calls)
70
71 def test_recovery_of_aborted_nonvirtual_slave(self):
72@@ -300,9 +311,11 @@
73 builder.virtualized = False
74 builder.builderok = True
75 d = builder.rescueIfLost()
76+
77 def check_failed(ignored):
78 self.assertFalse(builder.builderok)
79 self.assertNotIn('clean', aborted_slave.call_log)
80+
81 return d.addCallback(check_failed)
82
83 def test_recover_ok_slave(self):
84@@ -310,9 +323,11 @@
85 slave = OkSlave()
86 builder = MockBuilder("mock_builder", slave, TrivialBehavior())
87 d = builder.rescueIfLost()
88+
89 def check_slave_calls(ignored):
90 self.assertNotIn('abort', slave.call_log)
91 self.assertNotIn('clean', slave.call_log)
92+
93 return d.addCallback(check_slave_calls)
94
95 def test_recover_waiting_slave_with_good_id(self):
96@@ -321,9 +336,11 @@
97 waiting_slave = WaitingSlave()
98 builder = MockBuilder("mock_builder", waiting_slave, TrivialBehavior())
99 d = builder.rescueIfLost()
100+
101 def check_slave_calls(ignored):
102 self.assertNotIn('abort', waiting_slave.call_log)
103 self.assertNotIn('clean', waiting_slave.call_log)
104+
105 return d.addCallback(check_slave_calls)
106
107 def test_recover_waiting_slave_with_bad_id(self):
108@@ -335,31 +352,39 @@
109 waiting_slave = WaitingSlave()
110 builder = MockBuilder("mock_builder", waiting_slave, CorruptBehavior())
111 d = builder.rescueIfLost()
112+
113 def check_slave_calls(ignored):
114 self.assertNotIn('abort', waiting_slave.call_log)
115 self.assertIn('clean', waiting_slave.call_log)
116+
117 return d.addCallback(check_slave_calls)
118
119 def test_recover_building_slave_with_good_id(self):
120 # rescueIfLost does not attempt to abort or clean a builder that is
121 # BUILDING.
122 building_slave = BuildingSlave()
123- builder = MockBuilder("mock_builder", building_slave, TrivialBehavior())
124+ builder = MockBuilder(
125+ "mock_builder", building_slave, TrivialBehavior())
126 d = builder.rescueIfLost()
127+
128 def check_slave_calls(ignored):
129 self.assertNotIn('abort', building_slave.call_log)
130 self.assertNotIn('clean', building_slave.call_log)
131+
132 return d.addCallback(check_slave_calls)
133
134 def test_recover_building_slave_with_bad_id(self):
135 # If a slave is BUILDING with a build id we don't recognize, then we
136 # abort the build, thus stopping it in its tracks.
137 building_slave = BuildingSlave()
138- builder = MockBuilder("mock_builder", building_slave, CorruptBehavior())
139+ builder = MockBuilder(
140+ "mock_builder", building_slave, CorruptBehavior())
141 d = builder.rescueIfLost()
142+
143 def check_slave_calls(ignored):
144 self.assertIn('abort', building_slave.call_log)
145 self.assertNotIn('clean', building_slave.call_log)
146+
147 return d.addCallback(check_slave_calls)
148
149 def test_recover_building_slave_with_job_that_finished_elsewhere(self):
150@@ -384,10 +409,12 @@
151 self.layer.txn.commit()
152 builder = getUtility(IBuilderSet)[builder.name]
153 d = builder.rescueIfLost()
154+
155 def check_builder(ignored):
156 self.assertIsInstance(
157 removeSecurityProxy(builder.current_build_behavior),
158 IdleBuildBehavior)
159+
160 return d.addCallback(check_builder)
161
162
163@@ -930,10 +957,12 @@
164 build_id = 'status-build-id'
165 d = self.slave_helper.triggerGoodBuild(slave, build_id)
166 d.addCallback(lambda ignored: slave.status())
167+
168 def check_status(status):
169 self.assertEqual([BuilderStatus.BUILDING, build_id], status[:2])
170 [log_file] = status[2:]
171 self.assertIsInstance(log_file, xmlrpclib.Binary)
172+
173 return d.addCallback(check_status)
174
175 def test_ensurepresent_not_there(self):
176@@ -964,9 +993,11 @@
177 slave = self.slave_helper.getClientSlave()
178 self.slave_helper.makeCacheFile(tachandler, 'blahblah')
179 d = slave.sendFileToSlave('blahblah', None, None, None)
180+
181 def check_present(ignored):
182 d = slave.ensurepresent('blahblah', None, None, None)
183 return d.addCallback(self.assertEqual, [True, 'No URL'])
184+
185 d.addCallback(check_present)
186 return d
187
188@@ -1103,6 +1134,11 @@
189 self.slave = self.slave_helper.getClientSlave(
190 reactor=self.clock, proxy=self.proxy)
191
192+ def tearDown(self):
193+ # We need to remove any DelayedCalls that didn't actually get called.
194+ clean_up_reactor()
195+ super(TestSlaveConnectionTimeouts, self).tearDown()
196+
197 def test_connection_timeout(self):
198 # The default timeout of 30 seconds should not cause a timeout,
199 # only the config value should.
200@@ -1167,9 +1203,11 @@
201 slave = self.slave_helper.getClientSlave()
202 d = slave.ensurepresent(
203 lf.content.sha1, lf.http_url, "", "")
204+
205 def check_file(ignored):
206 d = getPage(expected_url.encode('utf8'))
207 return d.addCallback(self.assertEqual, content)
208+
209 return d.addCallback(check_file)
210
211 def test_getFiles(self):
212@@ -1186,7 +1224,6 @@
213 def got_files(ignored):
214 # Called back when getFiles finishes. Make sure all the
215 # content is as expected.
216- got_contents = []
217 for sha1 in filemap:
218 local_file = filemap[sha1]
219 file = open(local_file)
220
221=== modified file 'lib/lp/registry/tests/test_distributionmirror_prober.py'
222--- lib/lp/registry/tests/test_distributionmirror_prober.py 2012-04-02 20:56:02 +0000
223+++ lib/lp/registry/tests/test_distributionmirror_prober.py 2012-04-19 08:39:19 +0000
224@@ -60,6 +60,7 @@
225 from lp.services.config import config
226 from lp.services.daemons.tachandler import TacTestSetup
227 from lp.testing import (
228+ clean_up_reactor,
229 TestCase,
230 TestCaseWithFactory,
231 )
232@@ -69,14 +70,6 @@
233 )
234
235
236-def clean_up_reactor():
237- # XXX: JonathanLange 2010-11-22: These tests leave stacks of delayed
238- # calls around. They need to be updated to use Twisted correctly.
239- # For the meantime, just blat the reactor.
240- for delayed_call in reactor.getDelayedCalls():
241- delayed_call.cancel()
242-
243-
244 class HTTPServerTestSetup(TacTestSetup):
245
246 def setUpRoot(self):
247
248=== modified file 'lib/lp/testing/__init__.py'
249--- lib/lp/testing/__init__.py 2012-03-26 07:04:44 +0000
250+++ lib/lp/testing/__init__.py 2012-04-19 08:39:19 +0000
251@@ -15,6 +15,7 @@
252 'BrowserTestCase',
253 'build_yui_unittest_suite',
254 'celebrity_logged_in',
255+ 'clean_up_reactor',
256 'ExpectedException',
257 'extract_lp_cache',
258 'FakeAdapterMixin',
259@@ -1527,3 +1528,12 @@
260 self.addCleanup(
261 site_manager.registerUtility, current_commponent,
262 for_interface, name)
263+
264+
265+def clean_up_reactor():
266+ # XXX: JonathanLange 2010-11-22: These tests leave stacks of delayed
267+ # calls around. They need to be updated to use Twisted correctly.
268+ # For the meantime, just blat the reactor.
269+ from twisted.internet import reactor
270+ for delayed_call in reactor.getDelayedCalls():
271+ delayed_call.cancel()