Merge lp:~cjwatson/launchpad-buildd/fix-recipe-depfail into lp:launchpad-buildd

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 86
Merged at revision: 86
Proposed branch: lp:~cjwatson/launchpad-buildd/fix-recipe-depfail
Merge into: lp:launchpad-buildd
Diff against target: 255 lines (+222/-2)
4 files modified
Makefile (+2/-1)
debian/changelog (+7/-0)
lpbuildd/sourcepackagerecipe.py (+1/-1)
lpbuildd/tests/test_sourcepackagerecipe.py (+212/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad-buildd/fix-recipe-depfail
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+189041@code.launchpad.net

Commit message

Fix dep-wait detection when recipes fail to install build-dependencies.

Description of the change

Fix a crash in the recipe build manager, introduced in launchpad-buildd 116. This slipped through because the recipe build manager was untested, so I wrote a load of test code first.

QA: Try a recipe build with a deliberately unsatisfiable dependency, and make sure it lands in dep-wait.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2013-07-25 17:26:10 +0000
3+++ Makefile 2013-10-03 10:41:22 +0000
4@@ -31,4 +31,5 @@
5 lpbuildd.tests.test_buildd_slave \
6 lpbuildd.tests.test_check_implicit_pointer_functions \
7 lpbuildd.tests.test_harness \
8- lpbuildd.tests.test_translationtemplatesbuildmanager
9+ lpbuildd.tests.test_translationtemplatesbuildmanager \
10+ lpbuildd.tests.test_sourcepackagerecipe
11
12=== modified file 'debian/changelog'
13--- debian/changelog 2013-09-27 12:12:29 +0000
14+++ debian/changelog 2013-10-03 10:41:22 +0000
15@@ -1,3 +1,10 @@
16+launchpad-buildd (117) UNRELEASED; urgency=low
17+
18+ * Fix dep-wait detection when recipes fail to install build-dependencies
19+ (LP: #1234621).
20+
21+ -- Colin Watson <cjwatson@ubuntu.com> Thu, 03 Oct 2013 11:38:46 +0100
22+
23 launchpad-buildd (116) hardy; urgency=low
24
25 [ Colin Watson ]
26
27=== modified file 'lpbuildd/sourcepackagerecipe.py'
28--- lpbuildd/sourcepackagerecipe.py 2013-09-19 11:01:10 +0000
29+++ lpbuildd/sourcepackagerecipe.py 2013-10-03 10:41:22 +0000
30@@ -102,7 +102,7 @@
31 rx = (
32 'The following packages have unmet dependencies:\n'
33 '.*: Depends: ([^ ]*( \([^)]*\))?)')
34- _, mo = self.searchLogContents([rx, re.M])
35+ _, mo = self.searchLogContents([[rx, re.M]])
36 if mo:
37 self._slave.depFail(mo.group(1))
38 print("Returning build status: DEPFAIL")
39
40=== added file 'lpbuildd/tests/test_sourcepackagerecipe.py'
41--- lpbuildd/tests/test_sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
42+++ lpbuildd/tests/test_sourcepackagerecipe.py 2013-10-03 10:41:22 +0000
43@@ -0,0 +1,212 @@
44+# Copyright 2013 Canonical Ltd. This software is licensed under the
45+# GNU Affero General Public License version 3 (see the file LICENSE).
46+
47+__metaclass__ = type
48+
49+import os
50+import shutil
51+import tempfile
52+from textwrap import dedent
53+
54+from testtools import TestCase
55+
56+from lpbuildd.tests.fakeslave import FakeSlave
57+from lpbuildd.sourcepackagerecipe import (
58+ RETCODE_FAILURE_INSTALL_BUILD_DEPS,
59+ SourcePackageRecipeBuildManager,
60+ SourcePackageRecipeBuildState,
61+ )
62+
63+
64+class MockBuildManager(SourcePackageRecipeBuildManager):
65+ def __init__(self, *args, **kwargs):
66+ super(MockBuildManager, self).__init__(*args, **kwargs)
67+ self.commands = []
68+ self.iterators = []
69+
70+ def runSubProcess(self, path, command, iterate=None):
71+ self.commands.append([path]+command)
72+ if iterate is None:
73+ iterate = self.iterate
74+ self.iterators.append(iterate)
75+ return 0
76+
77+
78+class TestSourcePackageRecipeBuildManagerIteration(TestCase):
79+ """Run SourcePackageRecipeBuildManager through its iteration steps."""
80+ def setUp(self):
81+ super(TestSourcePackageRecipeBuildManagerIteration, self).setUp()
82+ self.working_dir = tempfile.mkdtemp()
83+ self.addCleanup(lambda: shutil.rmtree(self.working_dir))
84+ slave_dir = os.path.join(self.working_dir, 'slave')
85+ home_dir = os.path.join(self.working_dir, 'home')
86+ for dir in (slave_dir, home_dir):
87+ os.mkdir(dir)
88+ self.slave = FakeSlave(slave_dir)
89+ self.buildid = '123'
90+ self.buildmanager = MockBuildManager(self.slave, self.buildid)
91+ self.buildmanager.home = home_dir
92+ self.buildmanager._cachepath = self.slave._cachepath
93+ self.chrootdir = os.path.join(
94+ home_dir, 'build-%s' % self.buildid, 'chroot-autobuild')
95+
96+ def getState(self):
97+ """Retrieve build manager's state."""
98+ return self.buildmanager._state
99+
100+ def startBuild(self):
101+ # The build manager's iterate() kicks off the consecutive states
102+ # after INIT.
103+ extra_args = {
104+ 'recipe_text': dedent("""\
105+ # bzr-builder format 0.2 deb-version {debupstream}-0~{revno}
106+ http://bazaar.launchpad.dev/~ppa-user/+junk/wakeonlan"""),
107+ 'suite': 'maverick',
108+ 'ogrecomponent': 'universe',
109+ 'author_name': 'Steve\u1234',
110+ 'author_email': 'stevea@example.org',
111+ 'archive_purpose': 'puppies',
112+ 'distroseries_name': 'maverick',
113+ 'archives': [
114+ 'deb http://archive.ubuntu.com/ubuntu maverick main universe',
115+ 'deb http://ppa.launchpad.net/launchpad/bzr-builder-dev/'
116+ 'ubuntu main',
117+ ],
118+ }
119+ self.buildmanager.initiate({}, 'chroot.tar.gz', extra_args)
120+
121+ # Skip states that are done in DebianBuildManager to the state
122+ # directly before BUILD_RECIPE.
123+ self.buildmanager._state = SourcePackageRecipeBuildState.UPDATE
124+
125+ # BUILD_RECIPE: Run the slave's payload to build the source package.
126+ self.buildmanager.iterate(0)
127+ self.assertEqual(
128+ SourcePackageRecipeBuildState.BUILD_RECIPE, self.getState())
129+ expected_command = [
130+ 'buildrecipepath', 'buildrecipe', self.buildid,
131+ 'Steve\u1234'.encode('utf-8'), 'stevea@example.org',
132+ 'maverick', 'maverick', 'universe', 'puppies',
133+ ]
134+ self.assertEqual(expected_command, self.buildmanager.commands[-1])
135+ self.assertEqual(
136+ self.buildmanager.iterate, self.buildmanager.iterators[-1])
137+ self.assertFalse(self.slave.wasCalled('chrootFail'))
138+
139+ def test_iterate(self):
140+ # The build manager iterates a normal build from start to finish.
141+ self.startBuild()
142+
143+ log_path = os.path.join(self.buildmanager._cachepath, 'buildlog')
144+ log = open(log_path, 'w')
145+ log.write("I am a build log.")
146+ log.close()
147+
148+ changes_path = os.path.join(
149+ self.buildmanager.home, 'build-%s' % self.buildid,
150+ 'foo_1_source.changes')
151+ changes = open(changes_path, 'w')
152+ changes.write("I am a changes file.")
153+ changes.close()
154+
155+ manifest_path = os.path.join(
156+ self.buildmanager.home, 'build-%s' % self.buildid, 'manifest')
157+ manifest = open(manifest_path, 'w')
158+ manifest.write("I am a manifest file.")
159+ manifest.close()
160+
161+ # After building the package, reap processes.
162+ self.buildmanager.iterate(0)
163+ expected_command = [
164+ 'processscanpath', 'scan-for-processes', self.buildid,
165+ ]
166+ self.assertEqual(
167+ SourcePackageRecipeBuildState.BUILD_RECIPE, self.getState())
168+ self.assertEqual(expected_command, self.buildmanager.commands[-1])
169+ self.assertNotEqual(
170+ self.buildmanager.iterate, self.buildmanager.iterators[-1])
171+ self.assertFalse(self.slave.wasCalled('buildFail'))
172+ self.assertEqual(
173+ [((changes_path,), {}), ((manifest_path,), {})],
174+ self.slave.addWaitingFile.calls)
175+
176+ # Control returns to the DebianBuildManager in the UMOUNT state.
177+ self.buildmanager.iterateReap(self.getState(), 0)
178+ expected_command = [
179+ 'umountpath', 'umount-chroot', self.buildid
180+ ]
181+ self.assertEqual(SourcePackageRecipeBuildState.UMOUNT, self.getState())
182+ self.assertEqual(expected_command, self.buildmanager.commands[-1])
183+ self.assertEqual(
184+ self.buildmanager.iterate, self.buildmanager.iterators[-1])
185+ self.assertFalse(self.slave.wasCalled('buildFail'))
186+
187+ def test_iterate_BUILD_RECIPE_install_build_deps_depfail(self):
188+ # The build manager can detect dependency wait states.
189+ self.startBuild()
190+
191+ log_path = os.path.join(self.buildmanager._cachepath, 'buildlog')
192+ log = open(log_path, 'w')
193+ log.write(
194+ "The following packages have unmet dependencies:\n"
195+ " pbuilder-satisfydepends-dummy : Depends: base-files (>= 1000)"
196+ " but it is not going to be installed.\n")
197+ log.close()
198+
199+ # The buildmanager calls depFail correctly and reaps processes.
200+ self.buildmanager.iterate(RETCODE_FAILURE_INSTALL_BUILD_DEPS)
201+ expected_command = [
202+ 'processscanpath', 'scan-for-processes', self.buildid
203+ ]
204+ self.assertEqual(
205+ SourcePackageRecipeBuildState.BUILD_RECIPE, self.getState())
206+ self.assertEqual(expected_command, self.buildmanager.commands[-1])
207+ self.assertNotEqual(
208+ self.buildmanager.iterate, self.buildmanager.iterators[-1])
209+ self.assertFalse(self.slave.wasCalled('buildFail'))
210+ self.assertEqual(
211+ [(("base-files (>= 1000)",), {})], self.slave.depFail.calls)
212+
213+ # Control returns to the DebianBuildManager in the UMOUNT state.
214+ self.buildmanager.iterateReap(self.getState(), 0)
215+ expected_command = [
216+ 'umountpath', 'umount-chroot', self.buildid
217+ ]
218+ self.assertEqual(SourcePackageRecipeBuildState.UMOUNT, self.getState())
219+ self.assertEqual(expected_command, self.buildmanager.commands[-1])
220+ self.assertEqual(
221+ self.buildmanager.iterate, self.buildmanager.iterators[-1])
222+ self.assertFalse(self.slave.wasCalled('buildFail'))
223+
224+ def test_iterate_BUILD_RECIPE_install_build_deps_buildfail(self):
225+ # If the build manager cannot detect a dependency wait from a
226+ # build-dependency installation failure, it fails the build.
227+ self.startBuild()
228+
229+ log_path = os.path.join(self.buildmanager._cachepath, 'buildlog')
230+ log = open(log_path, 'w')
231+ log.write("I am a failing build log.")
232+ log.close()
233+
234+ # The buildmanager calls buildFail correctly and reaps processes.
235+ self.buildmanager.iterate(RETCODE_FAILURE_INSTALL_BUILD_DEPS)
236+ expected_command = [
237+ 'processscanpath', 'scan-for-processes', self.buildid
238+ ]
239+ self.assertEqual(
240+ SourcePackageRecipeBuildState.BUILD_RECIPE, self.getState())
241+ self.assertEqual(expected_command, self.buildmanager.commands[-1])
242+ self.assertNotEqual(
243+ self.buildmanager.iterate, self.buildmanager.iterators[-1])
244+ self.assertTrue(self.slave.wasCalled('buildFail'))
245+ self.assertFalse(self.slave.wasCalled('depFail'))
246+
247+ # Control returns to the DebianBuildManager in the UMOUNT state.
248+ self.buildmanager.iterateReap(self.getState(), 0)
249+ expected_command = [
250+ 'umountpath', 'umount-chroot', self.buildid
251+ ]
252+ self.assertEqual(SourcePackageRecipeBuildState.UMOUNT, self.getState())
253+ self.assertEqual(expected_command, self.buildmanager.commands[-1])
254+ self.assertEqual(
255+ self.buildmanager.iterate, self.buildmanager.iterators[-1])

Subscribers

People subscribed via source and target branches

to all changes: