Merge lp:~cjwatson/launchpad-buildd/gatherResults-in-thread into lp:launchpad-buildd

Proposed by Colin Watson
Status: Merged
Merged at revision: 350
Proposed branch: lp:~cjwatson/launchpad-buildd/gatherResults-in-thread
Merge into: lp:launchpad-buildd
Diff against target: 930 lines (+161/-75)
12 files modified
debian/changelog (+2/-0)
lpbuildd/binarypackage.py (+2/-9)
lpbuildd/debian.py (+37/-2)
lpbuildd/livefs.py (+2/-2)
lpbuildd/snap.py (+2/-2)
lpbuildd/sourcepackagerecipe.py (+4/-2)
lpbuildd/tests/test_binarypackage.py (+52/-28)
lpbuildd/tests/test_livefs.py (+14/-6)
lpbuildd/tests/test_snap.py (+12/-8)
lpbuildd/tests/test_sourcepackagerecipe.py (+19/-9)
lpbuildd/tests/test_translationtemplatesbuildmanager.py (+13/-5)
lpbuildd/translationtemplates.py (+2/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad-buildd/gatherResults-in-thread
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+356196@code.launchpad.net

Commit message

Call gatherResults in a different thread so that it doesn't block responses to XML-RPC requests.

Description of the change

The test changes here really just keep the existing tests working. I tried to get proper tests going for this, but I couldn't quite manage to hook up all the callbacks in a way that didn't crash or hang, and ran out of time for it. However, I've done a fair amount of local testing of these changes, including manually inserting time.sleep calls into gatherResults for various build types and testing that I can usefully call status() and abort() while gatherResults is in progress.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

Would it make sense to factor the gatherResults wrapper out into a deferGatherResults or similar? There's also no particular reason that the other four job types couldn't have the "Failed to gather results" handling too.

review: Approve (code)
349. By Colin Watson

Factor out common gatherResults thread handling.

350. By Colin Watson

Merge trunk.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2018-10-11 08:59:22 +0000
3+++ debian/changelog 2018-10-19 06:46:37 +0000
4@@ -8,6 +8,8 @@
5
6 [ Colin Watson ]
7 * Set SNAPCRAFT_BUILD_ENVIRONMENT=host when building snaps (LP: #1791201).
8+ * Call gatherResults in a different thread so that it doesn't block
9+ responses to XML-RPC requests (LP: #1795877).
10
11 -- Colin Watson <cjwatson@ubuntu.com> Tue, 09 Oct 2018 11:38:07 +0200
12
13
14=== modified file 'lpbuildd/binarypackage.py'
15--- lpbuildd/binarypackage.py 2017-08-03 13:13:08 +0000
16+++ lpbuildd/binarypackage.py 2018-10-19 06:46:37 +0000
17@@ -1,4 +1,4 @@
18-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
19+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
20 # GNU Affero General Public License version 3 (see the file LICENSE).
21
22 from __future__ import absolute_import, print_function
23@@ -343,14 +343,7 @@
24 """Finished the sbuild run."""
25 if success == SBuildExitCodes.OK:
26 print("Returning build status: OK")
27- try:
28- self.gatherResults()
29- except Exception as e:
30- self._slave.log("Failed to gather results: %s" % e)
31- self._slave.buildFail()
32- self.alreadyfailed = True
33- self.doReapProcesses(self._state)
34- return
35+ return self.deferGatherResults()
36
37 log_patterns = []
38 stop_patterns = [["^Toolchain package versions:", re.M]]
39
40=== modified file 'lpbuildd/debian.py'
41--- lpbuildd/debian.py 2018-05-02 09:00:10 +0000
42+++ lpbuildd/debian.py 2018-10-19 06:46:37 +0000
43@@ -1,4 +1,4 @@
44-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
45+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
46 # GNU Affero General Public License version 3 (see the file LICENSE).
47
48 # Authors: Daniel Silverstone <daniel.silverstone@canonical.com>
49@@ -13,6 +13,10 @@
50 import re
51 import signal
52
53+from twisted.internet import (
54+ defer,
55+ threads,
56+ )
57 from twisted.python import log
58
59 from lpbuildd.slave import (
60@@ -41,6 +45,7 @@
61 self._state = DebianBuildState.INIT
62 slave.emptyLog()
63 self.alreadyfailed = False
64+ self._iterator = None
65
66 @property
67 def initial_build_state(self):
68@@ -115,6 +120,27 @@
69 finally:
70 chfile.close()
71
72+ def deferGatherResults(self):
73+ """Gather the results of the build in a thread."""
74+ # XXX cjwatson 2018-10-04: Refactor using inlineCallbacks once we're
75+ # on Twisted >= 18.7.0 (https://twistedmatrix.com/trac/ticket/4632).
76+ def failed_to_gather(failure):
77+ if failure.check(defer.CancelledError):
78+ if not self.alreadyfailed:
79+ self._slave.log("Build cancelled unexpectedly!")
80+ self._slave.buildFail()
81+ else:
82+ self._slave.log("Failed to gather results: %s" % failure.value)
83+ self._slave.buildFail()
84+ self.alreadyfailed = True
85+
86+ def reap(ignored):
87+ self.doReapProcesses(self._state)
88+
89+ return threads.deferToThread(self.gatherResults).addErrback(
90+ failed_to_gather).addCallback(reap)
91+
92+ @defer.inlineCallbacks
93 def iterate(self, success, quiet=False):
94 # When a Twisted ProcessControl class is killed by SIGTERM,
95 # which we call 'build process aborted', 'None' is returned as
96@@ -129,7 +155,9 @@
97 func = getattr(self, "iterate_" + self._state, None)
98 if func is None:
99 raise ValueError("Unknown internal state " + self._state)
100- func(success)
101+ self._iterator = func(success)
102+ yield self._iterator
103+ self._iterator = None
104
105 def iterateReap(self, state, success):
106 log.msg("Iterating with success flag %s against stage %s after "
107@@ -305,6 +333,13 @@
108 """
109 self.doReapProcesses(self._state, notify=False)
110
111+ def abort(self):
112+ """See `BuildManager`."""
113+ super(DebianBuildManager, self).abort()
114+ if self._iterator is not None:
115+ self._iterator.cancel()
116+ self._iterator = None
117+
118
119 def get_build_path(home, build_id, *extra):
120 """Generate a path within the build directory.
121
122=== modified file 'lpbuildd/livefs.py'
123--- lpbuildd/livefs.py 2018-10-09 09:46:07 +0000
124+++ lpbuildd/livefs.py 2018-10-19 06:46:37 +0000
125@@ -1,4 +1,4 @@
126-# Copyright 2013-2017 Canonical Ltd. This software is licensed under the
127+# Copyright 2013-2018 Canonical Ltd. This software is licensed under the
128 # GNU Affero General Public License version 3 (see the file LICENSE).
129
130 __metaclass__ = type
131@@ -72,8 +72,8 @@
132 def iterate_BUILD_LIVEFS(self, retcode):
133 """Finished building the live filesystem."""
134 if retcode == RETCODE_SUCCESS:
135- self.gatherResults()
136 print("Returning build status: OK")
137+ return self.deferGatherResults()
138 elif (retcode >= RETCODE_FAILURE_INSTALL and
139 retcode <= RETCODE_FAILURE_BUILD):
140 if not self.alreadyfailed:
141
142=== modified file 'lpbuildd/snap.py'
143--- lpbuildd/snap.py 2018-06-07 20:08:26 +0000
144+++ lpbuildd/snap.py 2018-10-19 06:46:37 +0000
145@@ -1,4 +1,4 @@
146-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
147+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
148 # GNU Affero General Public License version 3 (see the file LICENSE).
149
150 from __future__ import print_function
151@@ -356,8 +356,8 @@
152 self.stopProxy()
153 self.revokeProxyToken()
154 if retcode == RETCODE_SUCCESS:
155- self.gatherResults()
156 print("Returning build status: OK")
157+ return self.deferGatherResults()
158 elif (retcode >= RETCODE_FAILURE_INSTALL and
159 retcode <= RETCODE_FAILURE_BUILD):
160 if not self.alreadyfailed:
161
162=== modified file 'lpbuildd/sourcepackagerecipe.py'
163--- lpbuildd/sourcepackagerecipe.py 2016-01-05 14:05:42 +0000
164+++ lpbuildd/sourcepackagerecipe.py 2018-10-19 06:46:37 +0000
165@@ -1,4 +1,4 @@
166-# Copyright 2010 Canonical Ltd. This software is licensed under the
167+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
168 # GNU Affero General Public License version 3 (see the file LICENSE).
169 # pylint: disable-msg=E1002
170
171@@ -12,6 +12,8 @@
172 DebianBuildState,
173 get_build_path,
174 )
175+
176+
177 RETCODE_SUCCESS = 0
178 RETCODE_FAILURE_INSTALL = 200
179 RETCODE_FAILURE_BUILD_TREE = 201
180@@ -98,8 +100,8 @@
181 def iterate_BUILD_RECIPE(self, retcode):
182 """Move from BUILD_RECIPE to the next logical state."""
183 if retcode == RETCODE_SUCCESS:
184- self.gatherResults()
185 print("Returning build status: OK")
186+ return self.deferGatherResults()
187 elif retcode == RETCODE_FAILURE_INSTALL_BUILD_DEPS:
188 if not self.alreadyfailed:
189 rx = (
190
191=== modified file 'lpbuildd/tests/test_binarypackage.py'
192--- lpbuildd/tests/test_binarypackage.py 2017-08-22 14:53:29 +0000
193+++ lpbuildd/tests/test_binarypackage.py 2018-10-19 06:46:37 +0000
194@@ -1,4 +1,4 @@
195-# Copyright 2013-2017 Canonical Ltd. This software is licensed under the
196+# Copyright 2013-2018 Canonical Ltd. This software is licensed under the
197 # GNU Affero General Public License version 3 (see the file LICENSE).
198
199 __metaclass__ = type
200@@ -13,6 +13,7 @@
201 from debian.deb822 import PkgRelation
202 from fixtures import MonkeyPatch
203 from testtools import TestCase
204+from testtools.deferredruntest import AsynchronousDeferredRunTest
205 from testtools.matchers import (
206 Contains,
207 ContainsDict,
208@@ -21,6 +22,7 @@
209 MatchesListwise,
210 Not,
211 )
212+from twisted.internet import defer
213 from twisted.internet.task import Clock
214
215 from lpbuildd.binarypackage import (
216@@ -85,6 +87,9 @@
217
218 class TestBinaryPackageBuildManagerIteration(TestCase):
219 """Run BinaryPackageBuildManager through its iteration steps."""
220+
221+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
222+
223 def setUp(self):
224 super(TestBinaryPackageBuildManagerIteration, self).setUp()
225 self.useFixture(DisableSudo())
226@@ -108,6 +113,7 @@
227 """Retrieve build manager's state."""
228 return self.buildmanager._state
229
230+ @defer.inlineCallbacks
231 def startBuild(self, dscname=''):
232 # The build manager's iterate() kicks off the consecutive states
233 # after INIT.
234@@ -123,7 +129,7 @@
235 self.buildmanager._state = BinaryPackageBuildState.UPDATE
236
237 # SBUILD: Build the package.
238- self.buildmanager.iterate(0)
239+ yield self.buildmanager.iterate(0)
240 self.assertState(
241 BinaryPackageBuildState.SBUILD,
242 [
243@@ -147,9 +153,10 @@
244 self.assertNotEqual(
245 self.buildmanager.iterate, self.buildmanager.iterators[-1])
246
247+ @defer.inlineCallbacks
248 def assertScansSanely(self, exit_code):
249 # After building the package, reap processes.
250- self.buildmanager.iterate(exit_code)
251+ yield self.buildmanager.iterate(exit_code)
252 self.assertState(
253 BinaryPackageBuildState.SBUILD,
254 ['sharepath/slavebin/in-target', 'in-target',
255@@ -166,9 +173,10 @@
256 '--backend=chroot', '--series=warty', '--arch=i386',
257 self.buildid], final=True)
258
259+ @defer.inlineCallbacks
260 def test_iterate(self):
261 # The build manager iterates a normal build from start to finish.
262- self.startBuild()
263+ yield self.startBuild()
264
265 write_file(
266 os.path.join(self.buildmanager._cachepath, 'buildlog'),
267@@ -179,7 +187,7 @@
268 write_file(changes_path, "I am a changes file.")
269
270 # After building the package, reap processes.
271- self.assertScansSanely(SBuildExitCodes.OK)
272+ yield self.assertScansSanely(SBuildExitCodes.OK)
273 self.assertFalse(self.slave.wasCalled('buildFail'))
274 self.assertThat(self.slave, HasWaitingFiles.byEquality({
275 'foo_1_i386.changes': b'I am a changes file.',
276@@ -189,6 +197,7 @@
277 self.assertUnmountsSanely()
278 self.assertFalse(self.slave.wasCalled('buildFail'))
279
280+ @defer.inlineCallbacks
281 def test_with_debug_symbols(self):
282 # A build with debug symbols sets up /CurrentlyBuilding
283 # appropriately, and does not pass DEB_BUILD_OPTIONS.
284@@ -199,7 +208,7 @@
285 'build_debug_symbols': True})
286 os.makedirs(self.chrootdir)
287 self.buildmanager._state = BinaryPackageBuildState.UPDATE
288- self.buildmanager.iterate(0)
289+ yield self.buildmanager.iterate(0)
290 self.assertState(
291 BinaryPackageBuildState.SBUILD,
292 ['sharepath/slavebin/sbuild-package', 'sbuild-package',
293@@ -218,6 +227,7 @@
294 Build-Debug-Symbols: yes
295 """), cb.read())
296
297+ @defer.inlineCallbacks
298 def test_without_debug_symbols(self):
299 # A build with debug symbols sets up /CurrentlyBuilding
300 # appropriately, and passes DEB_BUILD_OPTIONS=noautodbgsym.
301@@ -228,7 +238,7 @@
302 'build_debug_symbols': False})
303 os.makedirs(self.chrootdir)
304 self.buildmanager._state = BinaryPackageBuildState.UPDATE
305- self.buildmanager.iterate(0)
306+ yield self.buildmanager.iterate(0)
307 self.assertState(
308 BinaryPackageBuildState.SBUILD,
309 ['sharepath/slavebin/sbuild-package', 'sbuild-package',
310@@ -248,9 +258,10 @@
311 Purpose: PRIMARY
312 """), cb.read())
313
314+ @defer.inlineCallbacks
315 def test_abort_sbuild(self):
316 # Aborting sbuild kills processes in the chroot.
317- self.startBuild()
318+ yield self.startBuild()
319
320 # Send an abort command. The build manager reaps processes.
321 self.buildmanager.abort()
322@@ -267,10 +278,11 @@
323 self.assertUnmountsSanely()
324 self.assertFalse(self.slave.wasCalled('buildFail'))
325
326+ @defer.inlineCallbacks
327 def test_abort_sbuild_fail(self):
328 # If killing processes in the chroot hangs, the build manager does
329 # its best to clean up and fails the builder.
330- self.startBuild()
331+ yield self.startBuild()
332 sbuild_subprocess = self.buildmanager._subprocess
333
334 # Send an abort command. The build manager reaps processes.
335@@ -300,7 +312,7 @@
336
337 # When sbuild exits, it does not reap processes again, but proceeds
338 # directly to UMOUNT.
339- self.buildmanager.iterate(128 + 9) # SIGKILL
340+ yield self.buildmanager.iterate(128 + 9) # SIGKILL
341 self.assertState(
342 BinaryPackageBuildState.UMOUNT,
343 ['sharepath/slavebin/in-target', 'in-target',
344@@ -308,6 +320,7 @@
345 '--backend=chroot', '--series=warty', '--arch=i386',
346 self.buildid], final=True)
347
348+ @defer.inlineCallbacks
349 def test_abort_between_subprocesses(self):
350 # If a build is aborted between subprocesses, the build manager
351 # pretends that it was terminated by a signal.
352@@ -324,7 +337,7 @@
353 '--backend=chroot', '--series=warty', '--arch=i386',
354 self.buildid], final=False)
355
356- self.buildmanager.iterate(0)
357+ yield self.buildmanager.iterate(0)
358 self.assertState(
359 BinaryPackageBuildState.CLEANUP,
360 ['sharepath/slavebin/in-target', 'in-target',
361@@ -334,10 +347,11 @@
362 final=True)
363 self.assertFalse(self.slave.wasCalled('builderFail'))
364
365+ @defer.inlineCallbacks
366 def test_missing_changes(self):
367 # The build manager recovers if the expected .changes file does not
368 # exist, and considers it a package build failure.
369- self.startBuild()
370+ yield self.startBuild()
371 write_file(
372 os.path.join(self.buildmanager._cachepath, 'buildlog'),
373 "I am a build log.")
374@@ -348,7 +362,7 @@
375 "I am a changes file.")
376
377 # After building the package, reap processes.
378- self.assertScansSanely(SBuildExitCodes.OK)
379+ yield self.assertScansSanely(SBuildExitCodes.OK)
380 self.assertTrue(self.slave.wasCalled('buildFail'))
381 self.assertThat(self.slave, HasWaitingFiles({}))
382
383@@ -564,8 +578,9 @@
384 PkgRelation.parse_relations("foo <!nocheck>, bar <stage1>"),
385 {}))
386
387+ @defer.inlineCallbacks
388 def startDepFail(self, error, dscname=''):
389- self.startBuild(dscname=dscname)
390+ yield self.startBuild(dscname=dscname)
391 write_file(
392 os.path.join(self.buildmanager._cachepath, 'buildlog'),
393 "The following packages have unmet dependencies:\n"
394@@ -574,9 +589,10 @@
395 + ("a" * 4096) + "\n"
396 + "Fail-Stage: install-deps\n")
397
398+ @defer.inlineCallbacks
399 def assertMatchesDepfail(self, error, dep):
400- self.startDepFail(error)
401- self.assertScansSanely(SBuildExitCodes.GIVENBACK)
402+ yield self.startDepFail(error)
403+ yield self.assertScansSanely(SBuildExitCodes.GIVENBACK)
404 self.assertUnmountsSanely()
405 if dep is not None:
406 self.assertFalse(self.slave.wasCalled('buildFail'))
407@@ -585,27 +601,32 @@
408 self.assertFalse(self.slave.wasCalled('depFail'))
409 self.assertTrue(self.slave.wasCalled('buildFail'))
410
411+ @defer.inlineCallbacks
412 def test_detects_depfail(self):
413 # The build manager detects dependency installation failures.
414- self.assertMatchesDepfail(
415+ yield self.assertMatchesDepfail(
416 "enoent but it is not installable", "enoent")
417
418+ @defer.inlineCallbacks
419 def test_detects_versioned_depfail(self):
420 # The build manager detects dependency installation failures.
421- self.assertMatchesDepfail(
422+ yield self.assertMatchesDepfail(
423 "ebadver (< 2.0) but 3.0 is to be installed", "ebadver (< 2.0)")
424
425+ @defer.inlineCallbacks
426 def test_detects_versioned_current_depfail(self):
427 # The build manager detects dependency installation failures.
428- self.assertMatchesDepfail(
429+ yield self.assertMatchesDepfail(
430 "ebadver (< 2.0) but 3.0 is installed", "ebadver (< 2.0)")
431
432+ @defer.inlineCallbacks
433 def test_strips_depfail(self):
434 # The build manager strips qualifications and restrictions from
435 # dependency installation failures.
436- self.assertMatchesDepfail(
437+ yield self.assertMatchesDepfail(
438 "ebadver:any (>= 3.0) but 2.0 is installed", "ebadver (>= 3.0)")
439
440+ @defer.inlineCallbacks
441 def test_uninstallable_deps_analysis_failure(self):
442 # If there are uninstallable build-dependencies and analysis can't
443 # find any missing direct build-dependencies, the build manager
444@@ -618,7 +639,7 @@
445 Version: 1
446 Build-Depends: uninstallable (>= 1)
447 """))
448- self.startDepFail(
449+ yield self.startDepFail(
450 "uninstallable (>= 1) but it is not going to be installed",
451 dscname="123")
452 apt_lists = os.path.join(self.chrootdir, "var", "lib", "apt", "lists")
453@@ -629,11 +650,12 @@
454 Package: uninstallable
455 Version: 1
456 """))
457- self.assertScansSanely(SBuildExitCodes.GIVENBACK)
458+ yield self.assertScansSanely(SBuildExitCodes.GIVENBACK)
459 self.assertUnmountsSanely()
460 self.assertFalse(self.slave.wasCalled('depFail'))
461 self.assertTrue(self.slave.wasCalled('buildFail'))
462
463+ @defer.inlineCallbacks
464 def test_uninstallable_deps_analysis_depfail(self):
465 # If there are uninstallable build-dependencies and analysis reports
466 # some missing direct build-dependencies, the build manager marks
467@@ -645,7 +667,7 @@
468 Version: 1
469 Build-Depends: ebadver (>= 2)
470 """))
471- self.startDepFail(
472+ yield self.startDepFail(
473 "ebadver (>= 2) but it is not going to be installed",
474 dscname="123")
475 apt_lists = os.path.join(self.chrootdir, "var", "lib", "apt", "lists")
476@@ -656,11 +678,12 @@
477 Package: ebadver
478 Version: 1
479 """))
480- self.assertScansSanely(SBuildExitCodes.GIVENBACK)
481+ yield self.assertScansSanely(SBuildExitCodes.GIVENBACK)
482 self.assertUnmountsSanely()
483 self.assertFalse(self.slave.wasCalled('buildFail'))
484 self.assertEqual([(("ebadver (>= 2)",), {})], self.slave.depFail.calls)
485
486+ @defer.inlineCallbacks
487 def test_uninstallable_deps_analysis_mixed_depfail(self):
488 # If there is a mix of definite and dubious dep-wait output, then
489 # the build manager analyses the situation rather than trusting just
490@@ -672,7 +695,7 @@
491 Version: 1
492 Build-Depends: ebadver (>= 2), uninstallable
493 """))
494- self.startBuild(dscname="123")
495+ yield self.startBuild(dscname="123")
496 write_file(
497 os.path.join(self.buildmanager._cachepath, 'buildlog'),
498 "The following packages have unmet dependencies:\n"
499@@ -694,19 +717,20 @@
500 Package: uninstallable
501 Version: 1
502 """))
503- self.assertScansSanely(SBuildExitCodes.GIVENBACK)
504+ yield self.assertScansSanely(SBuildExitCodes.GIVENBACK)
505 self.assertUnmountsSanely()
506 self.assertFalse(self.slave.wasCalled('buildFail'))
507 self.assertEqual([(("ebadver (>= 2)",), {})], self.slave.depFail.calls)
508
509+ @defer.inlineCallbacks
510 def test_depfail_with_unknown_error_converted_to_packagefail(self):
511 # The build manager converts a DEPFAIL to a PACKAGEFAIL if the
512 # missing dependency can't be determined from the log.
513- self.startBuild()
514+ yield self.startBuild()
515 write_file(
516 os.path.join(self.buildmanager._cachepath, 'buildlog'),
517 "E: Everything is broken.\n")
518
519- self.assertScansSanely(SBuildExitCodes.GIVENBACK)
520+ yield self.assertScansSanely(SBuildExitCodes.GIVENBACK)
521 self.assertTrue(self.slave.wasCalled('buildFail'))
522 self.assertFalse(self.slave.wasCalled('depFail'))
523
524=== modified file 'lpbuildd/tests/test_livefs.py'
525--- lpbuildd/tests/test_livefs.py 2017-08-26 09:51:15 +0000
526+++ lpbuildd/tests/test_livefs.py 2018-10-19 06:46:37 +0000
527@@ -1,4 +1,4 @@
528-# Copyright 2013 Canonical Ltd. This software is licensed under the
529+# Copyright 2013-2018 Canonical Ltd. This software is licensed under the
530 # GNU Affero General Public License version 3 (see the file LICENSE).
531
532 __metaclass__ = type
533@@ -10,6 +10,8 @@
534 TempDir,
535 )
536 from testtools import TestCase
537+from testtools.deferredruntest import AsynchronousDeferredRunTest
538+from twisted.internet import defer
539
540 from lpbuildd.livefs import (
541 LiveFilesystemBuildManager,
542@@ -35,6 +37,9 @@
543
544 class TestLiveFilesystemBuildManagerIteration(TestCase):
545 """Run LiveFilesystemBuildManager through its iteration steps."""
546+
547+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
548+
549 def setUp(self):
550 super(TestLiveFilesystemBuildManagerIteration, self).setUp()
551 self.working_dir = self.useFixture(TempDir()).path
552@@ -52,6 +57,7 @@
553 """Retrieve build manager's state."""
554 return self.buildmanager._state
555
556+ @defer.inlineCallbacks
557 def startBuild(self):
558 # The build manager's iterate() kicks off the consecutive states
559 # after INIT.
560@@ -71,7 +77,7 @@
561 self.buildmanager._state = LiveFilesystemBuildState.UPDATE
562
563 # BUILD_LIVEFS: Run the slave's payload to build the live filesystem.
564- self.buildmanager.iterate(0)
565+ yield self.buildmanager.iterate(0)
566 self.assertEqual(
567 LiveFilesystemBuildState.BUILD_LIVEFS, self.getState())
568 expected_command = [
569@@ -85,9 +91,10 @@
570 self.buildmanager.iterate, self.buildmanager.iterators[-1])
571 self.assertFalse(self.slave.wasCalled("chrootFail"))
572
573+ @defer.inlineCallbacks
574 def test_iterate(self):
575 # The build manager iterates a normal build from start to finish.
576- self.startBuild()
577+ yield self.startBuild()
578
579 log_path = os.path.join(self.buildmanager._cachepath, "buildlog")
580 with open(log_path, "w") as log:
581@@ -97,7 +104,7 @@
582 "/build/livecd.ubuntu.manifest", b"I am a manifest file.")
583
584 # After building the package, reap processes.
585- self.buildmanager.iterate(0)
586+ yield self.buildmanager.iterate(0)
587 expected_command = [
588 "sharepath/slavebin/in-target", "in-target",
589 "scan-for-processes",
590@@ -126,9 +133,10 @@
591 self.buildmanager.iterate, self.buildmanager.iterators[-1])
592 self.assertFalse(self.slave.wasCalled("buildFail"))
593
594+ @defer.inlineCallbacks
595 def test_omits_symlinks(self):
596 # Symlinks in the build output are not included in gathered results.
597- self.startBuild()
598+ yield self.startBuild()
599
600 log_path = os.path.join(self.buildmanager._cachepath, "buildlog")
601 with open(log_path, "w") as log:
602@@ -139,7 +147,7 @@
603 self.buildmanager.backend.add_link(
604 "/build/livecd.ubuntu.kernel", "livefs.ubuntu.kernel-generic")
605
606- self.buildmanager.iterate(0)
607+ yield self.buildmanager.iterate(0)
608 self.assertThat(self.slave, HasWaitingFiles.byEquality({
609 "livecd.ubuntu.kernel-generic": b"I am a kernel.",
610 }))
611
612=== modified file 'lpbuildd/tests/test_snap.py'
613--- lpbuildd/tests/test_snap.py 2018-06-05 20:59:48 +0000
614+++ lpbuildd/tests/test_snap.py 2018-10-19 06:46:37 +0000
615@@ -1,4 +1,4 @@
616-# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
617+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
618 # GNU Affero General Public License version 3 (see the file LICENSE).
619
620 __metaclass__ = type
621@@ -70,6 +70,7 @@
622 """Retrieve build manager's state."""
623 return self.buildmanager._state
624
625+ @defer.inlineCallbacks
626 def startBuild(self, args=None, options=None):
627 # The build manager's iterate() kicks off the consecutive states
628 # after INIT.
629@@ -92,7 +93,7 @@
630 self.buildmanager._state = SnapBuildState.UPDATE
631
632 # BUILD_SNAP: Run the slave's payload to build the snap package.
633- self.buildmanager.iterate(0)
634+ yield self.buildmanager.iterate(0)
635 self.assertEqual(SnapBuildState.BUILD_SNAP, self.getState())
636 expected_command = [
637 "sharepath/slavebin/in-target", "in-target",
638@@ -119,9 +120,10 @@
639 status_file.write('{"revision_id": "dummy"}')
640 self.assertEqual({"revision_id": "dummy"}, self.buildmanager.status())
641
642+ @defer.inlineCallbacks
643 def test_iterate(self):
644 # The build manager iterates a normal build from start to finish.
645- self.startBuild()
646+ yield self.startBuild()
647
648 log_path = os.path.join(self.buildmanager._cachepath, "buildlog")
649 with open(log_path, "w") as log:
650@@ -131,7 +133,7 @@
651 "/build/test-snap/test-snap_0_all.snap", b"I am a snap package.")
652
653 # After building the package, reap processes.
654- self.buildmanager.iterate(0)
655+ yield self.buildmanager.iterate(0)
656 expected_command = [
657 "sharepath/slavebin/in-target", "in-target",
658 "scan-for-processes",
659@@ -159,10 +161,11 @@
660 self.buildmanager.iterate, self.buildmanager.iterators[-1])
661 self.assertFalse(self.slave.wasCalled("buildFail"))
662
663+ @defer.inlineCallbacks
664 def test_iterate_with_manifest(self):
665 # The build manager iterates a build that uploads a manifest from
666 # start to finish.
667- self.startBuild()
668+ yield self.startBuild()
669
670 log_path = os.path.join(self.buildmanager._cachepath, "buildlog")
671 with open(log_path, "w") as log:
672@@ -174,7 +177,7 @@
673 "/build/test-snap/test-snap_0_all.manifest", b"I am a manifest.")
674
675 # After building the package, reap processes.
676- self.buildmanager.iterate(0)
677+ yield self.buildmanager.iterate(0)
678 expected_command = [
679 "sharepath/slavebin/in-target", "in-target",
680 "scan-for-processes",
681@@ -203,10 +206,11 @@
682 self.buildmanager.iterate, self.buildmanager.iterators[-1])
683 self.assertFalse(self.slave.wasCalled("buildFail"))
684
685+ @defer.inlineCallbacks
686 def test_iterate_with_build_source_tarball(self):
687 # The build manager iterates a build that uploads a source tarball
688 # from start to finish.
689- self.startBuild(
690+ yield self.startBuild(
691 {"build_source_tarball": True}, ["--build-source-tarball"])
692
693 log_path = os.path.join(self.buildmanager._cachepath, "buildlog")
694@@ -219,7 +223,7 @@
695 "/build/test-snap.tar.gz", b"I am a source tarball.")
696
697 # After building the package, reap processes.
698- self.buildmanager.iterate(0)
699+ yield self.buildmanager.iterate(0)
700 expected_command = [
701 "sharepath/slavebin/in-target", "in-target",
702 "scan-for-processes",
703
704=== modified file 'lpbuildd/tests/test_sourcepackagerecipe.py'
705--- lpbuildd/tests/test_sourcepackagerecipe.py 2017-08-22 14:53:29 +0000
706+++ lpbuildd/tests/test_sourcepackagerecipe.py 2018-10-19 06:46:37 +0000
707@@ -1,4 +1,4 @@
708-# Copyright 2013-2017 Canonical Ltd. This software is licensed under the
709+# Copyright 2013-2018 Canonical Ltd. This software is licensed under the
710 # GNU Affero General Public License version 3 (see the file LICENSE).
711
712 __metaclass__ = type
713@@ -9,6 +9,8 @@
714 from textwrap import dedent
715
716 from testtools import TestCase
717+from testtools.deferredruntest import AsynchronousDeferredRunTest
718+from twisted.internet import defer
719
720 from lpbuildd.sourcepackagerecipe import (
721 RETCODE_FAILURE_INSTALL_BUILD_DEPS,
722@@ -35,6 +37,9 @@
723
724 class TestSourcePackageRecipeBuildManagerIteration(TestCase):
725 """Run SourcePackageRecipeBuildManager through its iteration steps."""
726+
727+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
728+
729 def setUp(self):
730 super(TestSourcePackageRecipeBuildManagerIteration, self).setUp()
731 self.working_dir = tempfile.mkdtemp()
732@@ -55,6 +60,7 @@
733 """Retrieve build manager's state."""
734 return self.buildmanager._state
735
736+ @defer.inlineCallbacks
737 def startBuild(self, git=False):
738 # The build manager's iterate() kicks off the consecutive states
739 # after INIT.
740@@ -84,7 +90,7 @@
741 self.buildmanager._state = SourcePackageRecipeBuildState.UPDATE
742
743 # BUILD_RECIPE: Run the slave's payload to build the source package.
744- self.buildmanager.iterate(0)
745+ yield self.buildmanager.iterate(0)
746 self.assertEqual(
747 SourcePackageRecipeBuildState.BUILD_RECIPE, self.getState())
748 expected_command = [
749@@ -101,9 +107,10 @@
750 self.buildmanager.iterate, self.buildmanager.iterators[-1])
751 self.assertFalse(self.slave.wasCalled('chrootFail'))
752
753+ @defer.inlineCallbacks
754 def test_iterate(self):
755 # The build manager iterates a normal build from start to finish.
756- self.startBuild()
757+ yield self.startBuild()
758
759 log_path = os.path.join(self.buildmanager._cachepath, 'buildlog')
760 with open(log_path, 'w') as log:
761@@ -121,7 +128,7 @@
762 manifest.write("I am a manifest file.")
763
764 # After building the package, reap processes.
765- self.buildmanager.iterate(0)
766+ yield self.buildmanager.iterate(0)
767 expected_command = [
768 'sharepath/slavebin/in-target', 'in-target',
769 'scan-for-processes',
770@@ -153,9 +160,10 @@
771 self.buildmanager.iterate, self.buildmanager.iterators[-1])
772 self.assertFalse(self.slave.wasCalled('buildFail'))
773
774+ @defer.inlineCallbacks
775 def test_iterate_BUILD_RECIPE_install_build_deps_depfail(self):
776 # The build manager can detect dependency wait states.
777- self.startBuild()
778+ yield self.startBuild()
779
780 log_path = os.path.join(self.buildmanager._cachepath, 'buildlog')
781 with open(log_path, 'w') as log:
782@@ -166,7 +174,7 @@
783 " but it is not going to be installed.\n")
784
785 # The buildmanager calls depFail correctly and reaps processes.
786- self.buildmanager.iterate(RETCODE_FAILURE_INSTALL_BUILD_DEPS)
787+ yield self.buildmanager.iterate(RETCODE_FAILURE_INSTALL_BUILD_DEPS)
788 expected_command = [
789 'sharepath/slavebin/in-target', 'in-target',
790 'scan-for-processes',
791@@ -196,17 +204,18 @@
792 self.buildmanager.iterate, self.buildmanager.iterators[-1])
793 self.assertFalse(self.slave.wasCalled('buildFail'))
794
795+ @defer.inlineCallbacks
796 def test_iterate_BUILD_RECIPE_install_build_deps_buildfail(self):
797 # If the build manager cannot detect a dependency wait from a
798 # build-dependency installation failure, it fails the build.
799- self.startBuild()
800+ yield self.startBuild()
801
802 log_path = os.path.join(self.buildmanager._cachepath, 'buildlog')
803 with open(log_path, 'w') as log:
804 log.write("I am a failing build log.")
805
806 # The buildmanager calls buildFail correctly and reaps processes.
807- self.buildmanager.iterate(RETCODE_FAILURE_INSTALL_BUILD_DEPS)
808+ yield self.buildmanager.iterate(RETCODE_FAILURE_INSTALL_BUILD_DEPS)
809 expected_command = [
810 'sharepath/slavebin/in-target', 'in-target',
811 'scan-for-processes',
812@@ -234,8 +243,9 @@
813 self.assertEqual(
814 self.buildmanager.iterate, self.buildmanager.iterators[-1])
815
816+ @defer.inlineCallbacks
817 def test_iterate_git(self):
818 # Starting a git-based recipe build passes the correct option. (The
819 # rest of the build is identical to bzr-based recipe builds from the
820 # build manager's point of view.)
821- self.startBuild(git=True)
822+ yield self.startBuild(git=True)
823
824=== modified file 'lpbuildd/tests/test_translationtemplatesbuildmanager.py'
825--- lpbuildd/tests/test_translationtemplatesbuildmanager.py 2017-09-08 15:57:18 +0000
826+++ lpbuildd/tests/test_translationtemplatesbuildmanager.py 2018-10-19 06:46:37 +0000
827@@ -1,4 +1,4 @@
828-# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
829+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
830 # GNU Affero General Public License version 3 (see the file LICENSE).
831
832 __metaclass__ = type
833@@ -10,6 +10,8 @@
834 TempDir,
835 )
836 from testtools import TestCase
837+from testtools.deferredruntest import AsynchronousDeferredRunTest
838+from twisted.internet import defer
839
840 from lpbuildd.target.generate_translation_templates import (
841 RETCODE_FAILURE_BUILD,
842@@ -39,6 +41,9 @@
843
844 class TestTranslationTemplatesBuildManagerIteration(TestCase):
845 """Run TranslationTemplatesBuildManager through its iteration steps."""
846+
847+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
848+
849 def setUp(self):
850 super(TestTranslationTemplatesBuildManagerIteration, self).setUp()
851 self.working_dir = self.useFixture(TempDir()).path
852@@ -57,6 +62,7 @@
853 """Retrieve build manager's state."""
854 return self.buildmanager._state
855
856+ @defer.inlineCallbacks
857 def test_iterate(self):
858 # Two iteration steps are specific to this build manager.
859 url = 'lp:~my/branch'
860@@ -74,7 +80,7 @@
861
862 # GENERATE: Run the slave's payload, the script that generates
863 # templates.
864- self.buildmanager.iterate(0)
865+ yield self.buildmanager.iterate(0)
866 self.assertEqual(
867 TranslationTemplatesBuildState.GENERATE, self.getState())
868 expected_command = [
869@@ -95,7 +101,7 @@
870 outfile_path, b"I am a template tarball. Seriously.")
871
872 # After generating templates, reap processes.
873- self.buildmanager.iterate(0)
874+ yield self.buildmanager.iterate(0)
875 expected_command = [
876 'sharepath/slavebin/in-target', 'in-target',
877 'scan-for-processes',
878@@ -128,6 +134,7 @@
879 self.buildmanager.iterate, self.buildmanager.iterators[-1])
880 self.assertFalse(self.slave.wasCalled('buildFail'))
881
882+ @defer.inlineCallbacks
883 def test_iterate_fail_GENERATE_install(self):
884 # See that a GENERATE that fails at the install step is handled
885 # properly.
886@@ -141,7 +148,7 @@
887 self.buildmanager._state = TranslationTemplatesBuildState.GENERATE
888
889 # The buildmanager fails and reaps processes.
890- self.buildmanager.iterate(RETCODE_FAILURE_INSTALL)
891+ yield self.buildmanager.iterate(RETCODE_FAILURE_INSTALL)
892 self.assertEqual(
893 TranslationTemplatesBuildState.GENERATE, self.getState())
894 expected_command = [
895@@ -169,6 +176,7 @@
896 self.assertEqual(
897 self.buildmanager.iterate, self.buildmanager.iterators[-1])
898
899+ @defer.inlineCallbacks
900 def test_iterate_fail_GENERATE_build(self):
901 # See that a GENERATE that fails at the build step is handled
902 # properly.
903@@ -182,7 +190,7 @@
904 self.buildmanager._state = TranslationTemplatesBuildState.GENERATE
905
906 # The buildmanager fails and reaps processes.
907- self.buildmanager.iterate(RETCODE_FAILURE_BUILD)
908+ yield self.buildmanager.iterate(RETCODE_FAILURE_BUILD)
909 expected_command = [
910 'sharepath/slavebin/in-target', 'in-target',
911 'scan-for-processes',
912
913=== modified file 'lpbuildd/translationtemplates.py'
914--- lpbuildd/translationtemplates.py 2017-09-08 15:57:18 +0000
915+++ lpbuildd/translationtemplates.py 2018-10-19 06:46:37 +0000
916@@ -1,4 +1,4 @@
917-# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
918+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
919 # GNU Affero General Public License version 3 (see the file LICENSE).
920
921 __metaclass__ = type
922@@ -63,7 +63,7 @@
923 """Template generation finished."""
924 if retcode == 0:
925 # It worked! Now let's bring in the harvest.
926- self.gatherResults()
927+ return self.deferGatherResults()
928 else:
929 if not self.alreadyfailed:
930 if retcode == RETCODE_FAILURE_INSTALL:

Subscribers

People subscribed via source and target branches

to all changes: