Merge lp:~michael.nelson/launchpad/587113-buildbase-handleStatus into lp:launchpad
- 587113-buildbase-handleStatus
- Merge into devel
Proposed by
Michael Nelson
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Graham Binns | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 10970 | ||||
Proposed branch: | lp:~michael.nelson/launchpad/587113-buildbase-handleStatus | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
531 lines (+198/-69) 10 files modified
lib/canonical/launchpad/webapp/configure.zcml (+1/-1) lib/canonical/launchpad/webapp/tales.py (+2/-2) lib/lp/buildmaster/interfaces/buildbase.py (+27/-12) lib/lp/buildmaster/model/buildbase.py (+24/-13) lib/lp/buildmaster/model/packagebuild.py (+2/-2) lib/lp/buildmaster/tests/test_buildbase.py (+76/-35) lib/lp/code/model/sourcepackagerecipebuild.py (+7/-1) lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+31/-1) lib/lp/soyuz/tests/test_binarypackagebuild.py (+23/-0) lib/lp/testing/factory.py (+5/-2) |
||||
To merge this branch: | bzr merge lp:~michael.nelson/launchpad/587113-buildbase-handleStatus | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | code | Approve | |
Review via email: mp+27022@code.launchpad.net |
Commit message
Description of the change
This branch fixes the core issue of bug 587113.
It refactors the tests so that handleStatus() is called for both BinaryPackageBuilds and SourcePackageRe
It also adds other tests to ensure log files and other attributes are correctly added for both build types.
To enable the shared implementation to work for both classes (until SourcePackageRe
To test:
bin/test -vv -m test_sourcepack
To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/webapp/configure.zcml' | |||
2 | --- lib/canonical/launchpad/webapp/configure.zcml 2010-06-05 05:05:43 +0000 | |||
3 | +++ lib/canonical/launchpad/webapp/configure.zcml 2010-06-08 16:04:27 +0000 | |||
4 | @@ -421,7 +421,7 @@ | |||
5 | 421 | <adapter | 421 | <adapter |
6 | 422 | for="lp.buildmaster.interfaces.packagebuild.IPackageBuild" | 422 | for="lp.buildmaster.interfaces.packagebuild.IPackageBuild" |
7 | 423 | provides="zope.traversing.interfaces.IPathAdapter" | 423 | provides="zope.traversing.interfaces.IPathAdapter" |
9 | 424 | factory="canonical.launchpad.webapp.tales.BuildBaseFormatterAPI" | 424 | factory="canonical.launchpad.webapp.tales.PackageBuildFormatterAPI" |
10 | 425 | name="fmt" | 425 | name="fmt" |
11 | 426 | /> | 426 | /> |
12 | 427 | 427 | ||
13 | 428 | 428 | ||
14 | === modified file 'lib/canonical/launchpad/webapp/tales.py' | |||
15 | --- lib/canonical/launchpad/webapp/tales.py 2010-06-05 05:05:43 +0000 | |||
16 | +++ lib/canonical/launchpad/webapp/tales.py 2010-06-08 16:04:27 +0000 | |||
17 | @@ -1515,8 +1515,8 @@ | |||
18 | 1515 | return url | 1515 | return url |
19 | 1516 | 1516 | ||
20 | 1517 | 1517 | ||
23 | 1518 | class BuildBaseFormatterAPI(ObjectFormatterAPI): | 1518 | class PackageBuildFormatterAPI(ObjectFormatterAPI): |
24 | 1519 | """Adapter providing fmt support for `IBuildBase` objects.""" | 1519 | """Adapter providing fmt support for `IPackageBuild` objects.""" |
25 | 1520 | def _composeArchiveReference(self, archive): | 1520 | def _composeArchiveReference(self, archive): |
26 | 1521 | if archive.is_ppa: | 1521 | if archive.is_ppa: |
27 | 1522 | return " [%s/%s]" % ( | 1522 | return " [%s/%s]" % ( |
28 | 1523 | 1523 | ||
29 | === modified file 'lib/lp/buildmaster/interfaces/buildbase.py' | |||
30 | --- lib/lp/buildmaster/interfaces/buildbase.py 2010-05-19 15:39:52 +0000 | |||
31 | +++ lib/lp/buildmaster/interfaces/buildbase.py 2010-06-08 16:04:27 +0000 | |||
32 | @@ -116,7 +116,22 @@ | |||
33 | 116 | """Common interface shared by farm jobs that build a package.""" | 116 | """Common interface shared by farm jobs that build a package.""" |
34 | 117 | # XXX 2010-04-21 michael.nelson bug=567922. This interface | 117 | # XXX 2010-04-21 michael.nelson bug=567922. This interface |
35 | 118 | # can be removed once all *Build classes inherit from | 118 | # can be removed once all *Build classes inherit from |
37 | 119 | # IBuildFarmJob/IPackageBuild. | 119 | # IBuildFarmJob/IPackageBuild. Until that time, to allow the shared |
38 | 120 | # implementation of handling build status, IBuildBase needs to | ||
39 | 121 | # provide aliases for buildstate, buildlog and datebuilt as follows: | ||
40 | 122 | # status => buildstate | ||
41 | 123 | # log => buildlog | ||
42 | 124 | # date_finished => datebuilt | ||
43 | 125 | status = Choice( | ||
44 | 126 | title=_('State'), required=True, vocabulary=BuildStatus, | ||
45 | 127 | description=_("The current build state.")) | ||
46 | 128 | log = Object( | ||
47 | 129 | schema=ILibraryFileAlias, required=False, | ||
48 | 130 | title=_("The LibraryFileAlias containing the entire buildlog.")) | ||
49 | 131 | date_finished = Datetime( | ||
50 | 132 | title=_('Date built'), required=False, | ||
51 | 133 | description=_("The time when the build result got collected.")) | ||
52 | 134 | |||
53 | 120 | 135 | ||
54 | 121 | build_farm_job_type = Choice( | 136 | build_farm_job_type = Choice( |
55 | 122 | title=_("Job type"), required=True, readonly=True, | 137 | title=_("Job type"), required=True, readonly=True, |
56 | @@ -130,10 +145,7 @@ | |||
57 | 130 | title=_('Date created'), required=True, readonly=True, | 145 | title=_('Date created'), required=True, readonly=True, |
58 | 131 | description=_("The time when the build request was created."))) | 146 | description=_("The time when the build request was created."))) |
59 | 132 | 147 | ||
64 | 133 | buildstate = exported( | 148 | buildstate = exported(status) |
61 | 134 | Choice( | ||
62 | 135 | title=_('State'), required=True, vocabulary=BuildStatus, | ||
63 | 136 | description=_("The current build state."))) | ||
65 | 137 | 149 | ||
66 | 138 | date_first_dispatched = exported( | 150 | date_first_dispatched = exported( |
67 | 139 | Datetime( | 151 | Datetime( |
68 | @@ -146,19 +158,14 @@ | |||
69 | 146 | title=_("Builder"), schema=IBuilder, required=False, | 158 | title=_("Builder"), schema=IBuilder, required=False, |
70 | 147 | description=_("The Builder which address this build request.")) | 159 | description=_("The Builder which address this build request.")) |
71 | 148 | 160 | ||
76 | 149 | datebuilt = exported( | 161 | datebuilt = exported(date_finished) |
73 | 150 | Datetime( | ||
74 | 151 | title=_('Date built'), required=False, | ||
75 | 152 | description=_("The time when the build result got collected."))) | ||
77 | 153 | 162 | ||
78 | 154 | buildduration = Timedelta( | 163 | buildduration = Timedelta( |
79 | 155 | title=_("Build Duration"), required=False, | 164 | title=_("Build Duration"), required=False, |
80 | 156 | description=_("Build duration interval, calculated when the " | 165 | description=_("Build duration interval, calculated when the " |
81 | 157 | "build result gets collected.")) | 166 | "build result gets collected.")) |
82 | 158 | 167 | ||
86 | 159 | buildlog = Object( | 168 | buildlog = log |
84 | 160 | schema=ILibraryFileAlias, required=False, | ||
85 | 161 | title=_("The LibraryFileAlias containing the entire buildlog.")) | ||
87 | 162 | 169 | ||
88 | 163 | build_log_url = exported( | 170 | build_log_url = exported( |
89 | 164 | TextLine( | 171 | TextLine( |
90 | @@ -225,6 +232,14 @@ | |||
91 | 225 | executable. | 232 | executable. |
92 | 226 | """ | 233 | """ |
93 | 227 | 234 | ||
94 | 235 | def getUploadLogContent(root, leaf): | ||
95 | 236 | """Retrieve the upload log contents. | ||
96 | 237 | |||
97 | 238 | :param root: Root directory for the uploads | ||
98 | 239 | :param leaf: Leaf for this particular upload | ||
99 | 240 | :return: Contents of log file or message saying no log file was found. | ||
100 | 241 | """ | ||
101 | 242 | |||
102 | 228 | def handleStatus(build, status, librarian, slave_status): | 243 | def handleStatus(build, status, librarian, slave_status): |
103 | 229 | """Handle a finished build status from a slave. | 244 | """Handle a finished build status from a slave. |
104 | 230 | 245 | ||
105 | 231 | 246 | ||
106 | === modified file 'lib/lp/buildmaster/model/buildbase.py' | |||
107 | --- lib/lp/buildmaster/model/buildbase.py 2010-05-21 09:42:21 +0000 | |||
108 | +++ lib/lp/buildmaster/model/buildbase.py 2010-06-08 16:04:27 +0000 | |||
109 | @@ -9,7 +9,10 @@ | |||
110 | 9 | 9 | ||
111 | 10 | __metaclass__ = type | 10 | __metaclass__ = type |
112 | 11 | 11 | ||
114 | 12 | __all__ = ['BuildBase'] | 12 | __all__ = [ |
115 | 13 | 'handle_status_for_build', | ||
116 | 14 | 'BuildBase', | ||
117 | 15 | ] | ||
118 | 13 | 16 | ||
119 | 14 | import datetime | 17 | import datetime |
120 | 15 | import logging | 18 | import logging |
121 | @@ -37,6 +40,24 @@ | |||
122 | 37 | UPLOAD_LOG_FILENAME = 'uploader.log' | 40 | UPLOAD_LOG_FILENAME = 'uploader.log' |
123 | 38 | 41 | ||
124 | 39 | 42 | ||
125 | 43 | def handle_status_for_build(build, status, librarian, slave_status): | ||
126 | 44 | """Find and call the correct method for handling the build status. | ||
127 | 45 | |||
128 | 46 | This is extracted from build base so that the implementation | ||
129 | 47 | can be shared by the newer IPackageBuild class. | ||
130 | 48 | """ | ||
131 | 49 | logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME) | ||
132 | 50 | |||
133 | 51 | method = getattr(BuildBase, '_handleStatus_' + status, None) | ||
134 | 52 | |||
135 | 53 | if method is None: | ||
136 | 54 | logger.critical("Unknown BuildStatus '%s' for builder '%s'" | ||
137 | 55 | % (status, build.buildqueue_record.builder.url)) | ||
138 | 56 | return | ||
139 | 57 | |||
140 | 58 | method(build, librarian, slave_status, logger) | ||
141 | 59 | |||
142 | 60 | |||
143 | 40 | class BuildBase: | 61 | class BuildBase: |
144 | 41 | """A mixin class providing functionality for farm jobs that build a | 62 | """A mixin class providing functionality for farm jobs that build a |
145 | 42 | package. | 63 | package. |
146 | @@ -127,19 +148,9 @@ | |||
147 | 127 | return None | 148 | return None |
148 | 128 | return self._getProxiedFileURL(self.upload_log) | 149 | return self._getProxiedFileURL(self.upload_log) |
149 | 129 | 150 | ||
152 | 130 | @staticmethod | 151 | def handleStatus(self, status, librarian, slave_status): |
151 | 131 | def handleStatus(build, status, librarian, slave_status): | ||
153 | 132 | """See `IBuildBase`.""" | 152 | """See `IBuildBase`.""" |
164 | 133 | logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME) | 153 | return handle_status_for_build(self, status, librarian, slave_status) |
155 | 134 | |||
156 | 135 | method = getattr(build, '_handleStatus_' + status, None) | ||
157 | 136 | |||
158 | 137 | if method is None: | ||
159 | 138 | logger.critical("Unknown BuildStatus '%s' for builder '%s'" | ||
160 | 139 | % (status, build.buildqueue_record.builder.url)) | ||
161 | 140 | return | ||
162 | 141 | |||
163 | 142 | method(librarian, slave_status, logger) | ||
165 | 143 | 154 | ||
166 | 144 | @staticmethod | 155 | @staticmethod |
167 | 145 | def _handleStatus_OK(build, librarian, slave_status, logger): | 156 | def _handleStatus_OK(build, librarian, slave_status, logger): |
168 | 146 | 157 | ||
169 | === modified file 'lib/lp/buildmaster/model/packagebuild.py' | |||
170 | --- lib/lp/buildmaster/model/packagebuild.py 2010-05-21 09:42:21 +0000 | |||
171 | +++ lib/lp/buildmaster/model/packagebuild.py 2010-06-08 16:04:27 +0000 | |||
172 | @@ -24,7 +24,7 @@ | |||
173 | 24 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource | 24 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource |
174 | 25 | from lp.buildmaster.interfaces.packagebuild import ( | 25 | from lp.buildmaster.interfaces.packagebuild import ( |
175 | 26 | IPackageBuild, IPackageBuildSource) | 26 | IPackageBuild, IPackageBuildSource) |
177 | 27 | from lp.buildmaster.model.buildbase import BuildBase | 27 | from lp.buildmaster.model.buildbase import handle_status_for_build, BuildBase |
178 | 28 | from lp.buildmaster.model.buildfarmjob import BuildFarmJobDerived | 28 | from lp.buildmaster.model.buildfarmjob import BuildFarmJobDerived |
179 | 29 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 29 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
180 | 30 | from lp.soyuz.adapters.archivedependencies import ( | 30 | from lp.soyuz.adapters.archivedependencies import ( |
181 | @@ -185,7 +185,7 @@ | |||
182 | 185 | 185 | ||
183 | 186 | def handleStatus(self, status, librarian, slave_status): | 186 | def handleStatus(self, status, librarian, slave_status): |
184 | 187 | """See `IPackageBuild`.""" | 187 | """See `IPackageBuild`.""" |
186 | 188 | return BuildBase.handleStatus(self, status, librarian, slave_status) | 188 | return handle_status_for_build(self, status, librarian, slave_status) |
187 | 189 | 189 | ||
188 | 190 | # The following private handlers currently re-use the BuildBase | 190 | # The following private handlers currently re-use the BuildBase |
189 | 191 | # implementation until it is no longer in use. If we find in the | 191 | # implementation until it is no longer in use. If we find in the |
190 | 192 | 192 | ||
191 | === modified file 'lib/lp/buildmaster/tests/test_buildbase.py' | |||
192 | --- lib/lp/buildmaster/tests/test_buildbase.py 2010-05-21 09:42:21 +0000 | |||
193 | +++ lib/lp/buildmaster/tests/test_buildbase.py 2010-06-08 16:04:27 +0000 | |||
194 | @@ -15,17 +15,16 @@ | |||
195 | 15 | from datetime import datetime | 15 | from datetime import datetime |
196 | 16 | import os | 16 | import os |
197 | 17 | import unittest | 17 | import unittest |
198 | 18 | from zope.security.proxy import removeSecurityProxy | ||
199 | 18 | 19 | ||
200 | 19 | from canonical.config import config | 20 | from canonical.config import config |
201 | 20 | from canonical.database.constants import UTC_NOW | 21 | from canonical.database.constants import UTC_NOW |
204 | 21 | from canonical.testing.layers import ( | 22 | from canonical.testing.layers import LaunchpadZopelessLayer |
203 | 22 | DatabaseFunctionalLayer, LaunchpadZopelessLayer) | ||
205 | 23 | from lp.buildmaster.interfaces.buildbase import BuildStatus | 23 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
206 | 24 | from lp.buildmaster.model.buildbase import BuildBase | 24 | from lp.buildmaster.model.buildbase import BuildBase |
207 | 25 | from lp.registry.interfaces.pocket import pocketsuffix | 25 | from lp.registry.interfaces.pocket import pocketsuffix |
208 | 26 | from lp.soyuz.tests.soyuzbuilddhelpers import WaitingSlave | 26 | from lp.soyuz.tests.soyuzbuilddhelpers import WaitingSlave |
211 | 27 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher | 27 | from lp.testing import TestCase |
210 | 28 | from lp.testing import TestCase, TestCaseWithFactory | ||
212 | 29 | from lp.testing.fakemethod import FakeMethod | 28 | from lp.testing.fakemethod import FakeMethod |
213 | 30 | 29 | ||
214 | 31 | 30 | ||
215 | @@ -62,27 +61,41 @@ | |||
216 | 62 | self.package_build = BuildBase() | 61 | self.package_build = BuildBase() |
217 | 63 | 62 | ||
218 | 64 | 63 | ||
220 | 65 | class TestBuildBaseWithDatabase(TestCaseWithFactory): | 64 | class TestGetUploadMethodsMixin: |
221 | 66 | """Tests for `IBuildBase` that need objects from the rest of Launchpad.""" | 65 | """Tests for `IBuildBase` that need objects from the rest of Launchpad.""" |
222 | 67 | 66 | ||
224 | 68 | layer = DatabaseFunctionalLayer | 67 | layer = LaunchpadZopelessLayer |
225 | 68 | |||
226 | 69 | def makeBuild(self): | ||
227 | 70 | """Allow classes to override the build with which the test runs. | ||
228 | 71 | |||
229 | 72 | XXX michaeln 2010-06-03 bug=567922 | ||
230 | 73 | Until buildbase is removed, we need to ensure these tests | ||
231 | 74 | run against new IPackageBuild builds (BinaryPackageBuild) | ||
232 | 75 | and the IBuildBase builds (SPRecipeBuild). They assume the build | ||
233 | 76 | is successfully built and check that incorrect upload paths will | ||
234 | 77 | set the status to FAILEDTOUPLOAD. | ||
235 | 78 | """ | ||
236 | 79 | raise NotImplemented | ||
237 | 80 | |||
238 | 81 | def setUp(self): | ||
239 | 82 | super(TestGetUploadMethodsMixin, self).setUp() | ||
240 | 83 | self.build = self.makeBuild() | ||
241 | 69 | 84 | ||
242 | 70 | def test_getUploadLogContent_nolog(self): | 85 | def test_getUploadLogContent_nolog(self): |
243 | 71 | """If there is no log file there, a string explanation is returned. | 86 | """If there is no log file there, a string explanation is returned. |
244 | 72 | """ | 87 | """ |
245 | 73 | self.useTempDir() | 88 | self.useTempDir() |
246 | 74 | build_base = BuildBase() | ||
247 | 75 | self.assertEquals('Could not find upload log file', | 89 | self.assertEquals('Could not find upload log file', |
249 | 76 | build_base.getUploadLogContent(os.getcwd(), "myleaf")) | 90 | self.build.getUploadLogContent(os.getcwd(), "myleaf")) |
250 | 77 | 91 | ||
251 | 78 | def test_getUploadLogContent_only_dir(self): | 92 | def test_getUploadLogContent_only_dir(self): |
252 | 79 | """If there is a directory but no log file, expect the error string, | 93 | """If there is a directory but no log file, expect the error string, |
253 | 80 | not an exception.""" | 94 | not an exception.""" |
254 | 81 | self.useTempDir() | 95 | self.useTempDir() |
255 | 82 | os.makedirs("accepted/myleaf") | 96 | os.makedirs("accepted/myleaf") |
256 | 83 | build_base = BuildBase() | ||
257 | 84 | self.assertEquals('Could not find upload log file', | 97 | self.assertEquals('Could not find upload log file', |
259 | 85 | build_base.getUploadLogContent(os.getcwd(), "myleaf")) | 98 | self.build.getUploadLogContent(os.getcwd(), "myleaf")) |
260 | 86 | 99 | ||
261 | 87 | def test_getUploadLogContent_readsfile(self): | 100 | def test_getUploadLogContent_readsfile(self): |
262 | 88 | """If there is a log file, return its contents.""" | 101 | """If there is a log file, return its contents.""" |
263 | @@ -90,47 +103,53 @@ | |||
264 | 90 | os.makedirs("accepted/myleaf") | 103 | os.makedirs("accepted/myleaf") |
265 | 91 | with open('accepted/myleaf/uploader.log', 'w') as f: | 104 | with open('accepted/myleaf/uploader.log', 'w') as f: |
266 | 92 | f.write('foo') | 105 | f.write('foo') |
267 | 93 | build_base = BuildBase() | ||
268 | 94 | self.assertEquals('foo', | 106 | self.assertEquals('foo', |
270 | 95 | build_base.getUploadLogContent(os.getcwd(), "myleaf")) | 107 | self.build.getUploadLogContent(os.getcwd(), "myleaf")) |
271 | 96 | 108 | ||
272 | 97 | def test_getUploaderCommand(self): | 109 | def test_getUploaderCommand(self): |
273 | 98 | build_base = BuildBase() | ||
274 | 99 | upload_leaf = self.factory.getUniqueString('upload-leaf') | 110 | upload_leaf = self.factory.getUniqueString('upload-leaf') |
275 | 100 | build_base.distro_series = self.factory.makeDistroSeries() | ||
276 | 101 | build_base.distribution = build_base.distro_series.distribution | ||
277 | 102 | build_base.pocket = self.factory.getAnyPocket() | ||
278 | 103 | build_base.id = self.factory.getUniqueInteger() | ||
279 | 104 | build_base.policy_name = self.factory.getUniqueString('policy-name') | ||
280 | 105 | config_args = list(config.builddmaster.uploader.split()) | 111 | config_args = list(config.builddmaster.uploader.split()) |
281 | 106 | log_file = self.factory.getUniqueString('logfile') | 112 | log_file = self.factory.getUniqueString('logfile') |
282 | 107 | config_args.extend( | 113 | config_args.extend( |
283 | 108 | ['--log-file', log_file, | 114 | ['--log-file', log_file, |
288 | 109 | '-d', build_base.distribution.name, | 115 | '-d', self.build.distribution.name, |
289 | 110 | '-s', (build_base.distro_series.name | 116 | '-s', (self.build.distro_series.name |
290 | 111 | + pocketsuffix[build_base.pocket]), | 117 | + pocketsuffix[self.build.pocket]), |
291 | 112 | '-b', str(build_base.id), | 118 | '-b', str(self.build.id), |
292 | 113 | '-J', upload_leaf, | 119 | '-J', upload_leaf, |
294 | 114 | '--context=%s' % build_base.policy_name, | 120 | '--context=%s' % self.build.policy_name, |
295 | 115 | os.path.abspath(config.builddmaster.root), | 121 | os.path.abspath(config.builddmaster.root), |
296 | 116 | ]) | 122 | ]) |
299 | 117 | uploader_command = build_base.getUploaderCommand( | 123 | uploader_command = self.build.getUploaderCommand( |
300 | 118 | build_base, upload_leaf, log_file) | 124 | self.build, upload_leaf, log_file) |
301 | 119 | self.assertEqual(config_args, uploader_command) | 125 | self.assertEqual(config_args, uploader_command) |
302 | 120 | 126 | ||
303 | 121 | 127 | ||
306 | 122 | class TestBuildBaseHandleStatus(TestCaseWithFactory): | 128 | class TestHandleStatusMixin: |
307 | 123 | """Tests for `IBuildBase`s handleStatus method.""" | 129 | """Tests for `IBuildBase`s handleStatus method. |
308 | 130 | |||
309 | 131 | Note: these tests do *not* test the updating of the build | ||
310 | 132 | status to FULLYBUILT as this happens during the upload which | ||
311 | 133 | is stubbed out by a mock function. | ||
312 | 134 | """ | ||
313 | 124 | 135 | ||
314 | 125 | layer = LaunchpadZopelessLayer | 136 | layer = LaunchpadZopelessLayer |
315 | 126 | 137 | ||
316 | 138 | def makeBuild(self): | ||
317 | 139 | """Allow classes to override the build with which the test runs. | ||
318 | 140 | |||
319 | 141 | XXX michaeln 2010-06-03 bug=567922 | ||
320 | 142 | Until buildbase is removed, we need to ensure these tests | ||
321 | 143 | run against new IPackageBuild builds (BinaryPackageBuild) | ||
322 | 144 | and the IBuildBase builds (SPRecipeBuild). They assume the build | ||
323 | 145 | is successfully built and check that incorrect upload paths will | ||
324 | 146 | set the status to FAILEDTOUPLOAD. | ||
325 | 147 | """ | ||
326 | 148 | raise NotImplementedError | ||
327 | 149 | |||
328 | 127 | def setUp(self): | 150 | def setUp(self): |
335 | 128 | super(TestBuildBaseHandleStatus, self).setUp() | 151 | super(TestHandleStatusMixin, self).setUp() |
336 | 129 | test_publisher = SoyuzTestPublisher() | 152 | self.build = self.makeBuild() |
331 | 130 | test_publisher.prepareBreezyAutotest() | ||
332 | 131 | binaries = test_publisher.getPubBinaries() | ||
333 | 132 | self.build = binaries[0].binarypackagerelease.build | ||
334 | 133 | |||
337 | 134 | # For the moment, we require a builder for the build so that | 153 | # For the moment, we require a builder for the build so that |
338 | 135 | # handleStatus_OK can get a reference to the slave. | 154 | # handleStatus_OK can get a reference to the slave. |
339 | 136 | builder = self.factory.makeBuilder() | 155 | builder = self.factory.makeBuilder() |
340 | @@ -149,10 +168,14 @@ | |||
341 | 149 | config.push('tmp_builddmaster_root', tmp_builddmaster_root) | 168 | config.push('tmp_builddmaster_root', tmp_builddmaster_root) |
342 | 150 | 169 | ||
343 | 151 | # We stub out our builds getUploaderCommand() method so | 170 | # We stub out our builds getUploaderCommand() method so |
345 | 152 | # we can check whether it was called. | 171 | # we can check whether it was called as well as |
346 | 172 | # verifySuccessfulUpload(). | ||
347 | 153 | self.fake_getUploaderCommand = FakeMethod( | 173 | self.fake_getUploaderCommand = FakeMethod( |
348 | 154 | result=['echo', 'noop']) | 174 | result=['echo', 'noop']) |
350 | 155 | self.build.getUploaderCommand = self.fake_getUploaderCommand | 175 | removeSecurityProxy(self.build).getUploaderCommand = ( |
351 | 176 | self.fake_getUploaderCommand) | ||
352 | 177 | removeSecurityProxy(self.build).verifySuccessfulUpload = FakeMethod( | ||
353 | 178 | result=True) | ||
354 | 156 | 179 | ||
355 | 157 | def test_handleStatus_OK_normal_file(self): | 180 | def test_handleStatus_OK_normal_file(self): |
356 | 158 | # A filemap with plain filenames should not cause a problem. | 181 | # A filemap with plain filenames should not cause a problem. |
357 | @@ -183,6 +206,24 @@ | |||
358 | 183 | self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status) | 206 | self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status) |
359 | 184 | self.assertEqual(0, self.fake_getUploaderCommand.call_count) | 207 | self.assertEqual(0, self.fake_getUploaderCommand.call_count) |
360 | 185 | 208 | ||
361 | 209 | def test_handleStatus_OK_sets_build_log(self): | ||
362 | 210 | # The build log is set during handleStatus. | ||
363 | 211 | removeSecurityProxy(self.build).log = None | ||
364 | 212 | self.assertEqual(None, self.build.log) | ||
365 | 213 | self.build.handleStatus('OK', None, { | ||
366 | 214 | 'filemap': { 'myfile.py': 'test_file_hash'}, | ||
367 | 215 | }) | ||
368 | 216 | self.assertNotEqual(None, self.build.log) | ||
369 | 217 | |||
370 | 218 | def test_date_finished_set(self): | ||
371 | 219 | # The date finished is updated during handleStatus_OK. | ||
372 | 220 | removeSecurityProxy(self.build).date_finished = None | ||
373 | 221 | self.assertEqual(None, self.build.date_finished) | ||
374 | 222 | self.build.handleStatus('OK', None, { | ||
375 | 223 | 'filemap': { 'myfile.py': 'test_file_hash'}, | ||
376 | 224 | }) | ||
377 | 225 | self.assertNotEqual(None, self.build.date_finished) | ||
378 | 226 | |||
379 | 186 | 227 | ||
380 | 187 | def test_suite(): | 228 | def test_suite(): |
381 | 188 | return unittest.TestLoader().loadTestsFromName(__name__) | 229 | return unittest.TestLoader().loadTestsFromName(__name__) |
382 | 189 | 230 | ||
383 | === modified file 'lib/lp/code/model/sourcepackagerecipebuild.py' | |||
384 | --- lib/lp/code/model/sourcepackagerecipebuild.py 2010-05-27 22:18:16 +0000 | |||
385 | +++ lib/lp/code/model/sourcepackagerecipebuild.py 2010-06-08 16:04:27 +0000 | |||
386 | @@ -75,7 +75,6 @@ | |||
387 | 75 | buildlog = Reference(buildlog_id, 'LibraryFileAlias.id') | 75 | buildlog = Reference(buildlog_id, 'LibraryFileAlias.id') |
388 | 76 | 76 | ||
389 | 77 | buildstate = DBEnum(enum=BuildStatus, name='build_state') | 77 | buildstate = DBEnum(enum=BuildStatus, name='build_state') |
390 | 78 | |||
391 | 79 | dependencies = Unicode(allow_none=True) | 78 | dependencies = Unicode(allow_none=True) |
392 | 80 | 79 | ||
393 | 81 | upload_log_id = Int(name='upload_log', allow_none=True) | 80 | upload_log_id = Int(name='upload_log', allow_none=True) |
394 | @@ -88,6 +87,13 @@ | |||
395 | 88 | datecreated = UtcDateTimeCol(notNull=True, dbName='date_created') | 87 | datecreated = UtcDateTimeCol(notNull=True, dbName='date_created') |
396 | 89 | datebuilt = UtcDateTimeCol(notNull=False, dbName='date_built') | 88 | datebuilt = UtcDateTimeCol(notNull=False, dbName='date_built') |
397 | 90 | 89 | ||
398 | 90 | # See `IBuildBase` - the following attributes are aliased | ||
399 | 91 | # to allow a shared implementation of the handleStatus methods | ||
400 | 92 | # until IBuildBase is removed. | ||
401 | 93 | status = buildstate | ||
402 | 94 | date_finished = datebuilt | ||
403 | 95 | log = buildlog | ||
404 | 96 | |||
405 | 91 | @property | 97 | @property |
406 | 92 | def datestarted(self): | 98 | def datestarted(self): |
407 | 93 | """See `IBuild`.""" | 99 | """See `IBuild`.""" |
408 | 94 | 100 | ||
409 | === modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py' | |||
410 | --- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-05-27 22:18:16 +0000 | |||
411 | +++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-06-08 16:04:27 +0000 | |||
412 | @@ -20,11 +20,14 @@ | |||
413 | 20 | 20 | ||
414 | 21 | from canonical.launchpad.interfaces.launchpad import NotFoundError | 21 | from canonical.launchpad.interfaces.launchpad import NotFoundError |
415 | 22 | from canonical.launchpad.webapp.authorization import check_permission | 22 | from canonical.launchpad.webapp.authorization import check_permission |
417 | 23 | from lp.buildmaster.interfaces.buildbase import IBuildBase | 23 | from lp.buildmaster.interfaces.buildbase import BuildStatus, IBuildBase |
418 | 24 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue | 24 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue |
419 | 25 | from lp.buildmaster.tests.test_buildbase import ( | ||
420 | 26 | TestGetUploadMethodsMixin, TestHandleStatusMixin) | ||
421 | 25 | from lp.code.interfaces.sourcepackagerecipebuild import ( | 27 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
422 | 26 | ISourcePackageRecipeBuildJob, ISourcePackageRecipeBuild, | 28 | ISourcePackageRecipeBuildJob, ISourcePackageRecipeBuild, |
423 | 27 | ISourcePackageRecipeBuildSource) | 29 | ISourcePackageRecipeBuildSource) |
424 | 30 | from lp.soyuz.interfaces.processor import IProcessorFamilySet | ||
425 | 28 | from lp.soyuz.model.processor import ProcessorFamily | 31 | from lp.soyuz.model.processor import ProcessorFamily |
426 | 29 | from lp.testing import ANONYMOUS, login, person_logged_in, TestCaseWithFactory | 32 | from lp.testing import ANONYMOUS, login, person_logged_in, TestCaseWithFactory |
427 | 30 | 33 | ||
428 | @@ -193,6 +196,33 @@ | |||
429 | 193 | self.assertEqual([binary], list(spb.binary_builds)) | 196 | self.assertEqual([binary], list(spb.binary_builds)) |
430 | 194 | 197 | ||
431 | 195 | 198 | ||
432 | 199 | class MakeSPRecipeBuildMixin: | ||
433 | 200 | """Provide the common makeBuild method returning a queued build.""" | ||
434 | 201 | |||
435 | 202 | def makeBuild(self): | ||
436 | 203 | person = self.factory.makePerson() | ||
437 | 204 | distroseries = self.factory.makeDistroSeries() | ||
438 | 205 | processor_fam = getUtility(IProcessorFamilySet).getByName('x86') | ||
439 | 206 | distroseries_i386 = distroseries.newArch( | ||
440 | 207 | 'i386', processor_fam, False, person, | ||
441 | 208 | supports_virtualized=True) | ||
442 | 209 | distroseries.nominatedarchindep = distroseries_i386 | ||
443 | 210 | build = self.factory.makeSourcePackageRecipeBuild( | ||
444 | 211 | distroseries=distroseries, | ||
445 | 212 | status=BuildStatus.FULLYBUILT) | ||
446 | 213 | build.queueBuild(build) | ||
447 | 214 | return build | ||
448 | 215 | |||
449 | 216 | |||
450 | 217 | class TestGetUploadMethodsForSPRecipeBuild( | ||
451 | 218 | MakeSPRecipeBuildMixin, TestGetUploadMethodsMixin, TestCaseWithFactory): | ||
452 | 219 | """IBuildBase.getUpload-related methods work with SPRecipe builds.""" | ||
453 | 220 | |||
454 | 221 | |||
455 | 222 | class TestHandleStatusForSPRBuild( | ||
456 | 223 | MakeSPRecipeBuildMixin, TestHandleStatusMixin, TestCaseWithFactory): | ||
457 | 224 | """IBuildBase.handleStatus works with SPRecipe builds.""" | ||
458 | 225 | |||
459 | 196 | 226 | ||
460 | 197 | def test_suite(): | 227 | def test_suite(): |
461 | 198 | return unittest.TestLoader().loadTestsFromName(__name__) | 228 | return unittest.TestLoader().loadTestsFromName(__name__) |
462 | 199 | 229 | ||
463 | === modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py' | |||
464 | --- lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-07 20:21:02 +0000 | |||
465 | +++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-08 16:04:27 +0000 | |||
466 | @@ -17,6 +17,8 @@ | |||
467 | 17 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue | 17 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue |
468 | 18 | from lp.buildmaster.interfaces.packagebuild import IPackageBuild | 18 | from lp.buildmaster.interfaces.packagebuild import IPackageBuild |
469 | 19 | from lp.buildmaster.model.buildqueue import BuildQueue | 19 | from lp.buildmaster.model.buildqueue import BuildQueue |
470 | 20 | from lp.buildmaster.tests.test_buildbase import ( | ||
471 | 21 | TestGetUploadMethodsMixin, TestHandleStatusMixin) | ||
472 | 20 | from lp.soyuz.interfaces.binarypackagebuild import ( | 22 | from lp.soyuz.interfaces.binarypackagebuild import ( |
473 | 21 | IBinaryPackageBuild, IBinaryPackageBuildSet) | 23 | IBinaryPackageBuild, IBinaryPackageBuildSet) |
474 | 22 | from lp.soyuz.interfaces.buildpackagejob import IBuildPackageJob | 24 | from lp.soyuz.interfaces.buildpackagejob import IBuildPackageJob |
475 | @@ -345,5 +347,26 @@ | |||
476 | 345 | self.assertIsNot(None, self.build.date_finished) | 347 | self.assertIsNot(None, self.build.date_finished) |
477 | 346 | 348 | ||
478 | 347 | 349 | ||
479 | 350 | class MakeBinaryPackageBuildMixin: | ||
480 | 351 | """Provide the makeBuild method returning a queud build.""" | ||
481 | 352 | |||
482 | 353 | def makeBuild(self): | ||
483 | 354 | test_publisher = SoyuzTestPublisher() | ||
484 | 355 | test_publisher.prepareBreezyAutotest() | ||
485 | 356 | binaries = test_publisher.getPubBinaries() | ||
486 | 357 | return binaries[0].binarypackagerelease.build | ||
487 | 358 | |||
488 | 359 | |||
489 | 360 | class TestGetUploadMethodsForBinaryPackageBuild( | ||
490 | 361 | MakeBinaryPackageBuildMixin, TestGetUploadMethodsMixin, | ||
491 | 362 | TestCaseWithFactory): | ||
492 | 363 | """IBuildBase.getUpload-related methods work with binary builds.""" | ||
493 | 364 | |||
494 | 365 | |||
495 | 366 | class TestHandleStatusForBinaryPackageBuild( | ||
496 | 367 | MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TestCaseWithFactory): | ||
497 | 368 | """IBuildBase.handleStatus works with binary builds.""" | ||
498 | 369 | |||
499 | 370 | |||
500 | 348 | def test_suite(): | 371 | def test_suite(): |
501 | 349 | return unittest.TestLoader().loadTestsFromName(__name__) | 372 | return unittest.TestLoader().loadTestsFromName(__name__) |
502 | 350 | 373 | ||
503 | === modified file 'lib/lp/testing/factory.py' | |||
504 | --- lib/lp/testing/factory.py 2010-05-30 04:06:48 +0000 | |||
505 | +++ lib/lp/testing/factory.py 2010-06-08 16:04:27 +0000 | |||
506 | @@ -1789,7 +1789,8 @@ | |||
507 | 1789 | def makeSourcePackageRecipeBuild(self, sourcepackage=None, recipe=None, | 1789 | def makeSourcePackageRecipeBuild(self, sourcepackage=None, recipe=None, |
508 | 1790 | requester=None, archive=None, | 1790 | requester=None, archive=None, |
509 | 1791 | sourcename=None, distroseries=None, | 1791 | sourcename=None, distroseries=None, |
511 | 1792 | pocket=None): | 1792 | pocket=None, |
512 | 1793 | status=BuildStatus.NEEDSBUILD): | ||
513 | 1793 | """Make a new SourcePackageRecipeBuild.""" | 1794 | """Make a new SourcePackageRecipeBuild.""" |
514 | 1794 | if recipe is None: | 1795 | if recipe is None: |
515 | 1795 | recipe = self.makeSourcePackageRecipe(name=sourcename) | 1796 | recipe = self.makeSourcePackageRecipe(name=sourcename) |
516 | @@ -1800,12 +1801,14 @@ | |||
517 | 1800 | distribution=archive.distribution) | 1801 | distribution=archive.distribution) |
518 | 1801 | if requester is None: | 1802 | if requester is None: |
519 | 1802 | requester = self.makePerson() | 1803 | requester = self.makePerson() |
521 | 1803 | return getUtility(ISourcePackageRecipeBuildSource).new( | 1804 | spr_build = getUtility(ISourcePackageRecipeBuildSource).new( |
522 | 1804 | distroseries=distroseries, | 1805 | distroseries=distroseries, |
523 | 1805 | recipe=recipe, | 1806 | recipe=recipe, |
524 | 1806 | archive=archive, | 1807 | archive=archive, |
525 | 1807 | requester=requester, | 1808 | requester=requester, |
526 | 1808 | pocket=pocket) | 1809 | pocket=pocket) |
527 | 1810 | removeSecurityProxy(spr_build).buildstate = status | ||
528 | 1811 | return spr_build | ||
529 | 1809 | 1812 | ||
530 | 1810 | def makeSourcePackageRecipeBuildJob( | 1813 | def makeSourcePackageRecipeBuildJob( |
531 | 1811 | self, score=9876, virtualized=True, estimated_duration=64, | 1814 | self, score=9876, virtualized=True, estimated_duration=64, |