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

Subscribers

People subscribed via source and target branches

to all changes: