Merge ~cjwatson/launchpad:charm-recipe-build-behaviour into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: b7d4b47cd8bbafb401d41d534bd219a0bccf60be
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:charm-recipe-build-behaviour
Merge into: launchpad:master
Prerequisite: ~cjwatson/launchpad:charm-recipe-build-mailer
Diff against target: 564 lines (+541/-0)
3 files modified
lib/lp/charms/configure.zcml (+7/-0)
lib/lp/charms/model/charmrecipebuildbehaviour.py (+111/-0)
lib/lp/charms/tests/test_charmrecipebuildbehaviour.py (+423/-0)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Cristian Gonzalez (community) Approve
Review via email: mp+403732@code.launchpad.net

Commit message

Add a build behaviour for charm recipes

To post a comment you must log in.
Revision history for this message
Cristian Gonzalez (cristiangsp) wrote :

Looks good!

review: Approve
Revision history for this message
Ioana Lasc (ilasc) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/charms/configure.zcml b/lib/lp/charms/configure.zcml
index f1deb96..68acb5e 100644
--- a/lib/lp/charms/configure.zcml
+++ b/lib/lp/charms/configure.zcml
@@ -77,6 +77,13 @@
77 <allow interface="lp.charms.interfaces.charmrecipebuild.ICharmFile" />77 <allow interface="lp.charms.interfaces.charmrecipebuild.ICharmFile" />
78 </class>78 </class>
7979
80 <!-- CharmRecipeBuildBehaviour -->
81 <adapter
82 for="lp.charms.interfaces.charmrecipebuild.ICharmRecipeBuild"
83 provides="lp.buildmaster.interfaces.buildfarmjobbehaviour.IBuildFarmJobBehaviour"
84 factory="lp.charms.model.charmrecipebuildbehaviour.CharmRecipeBuildBehaviour"
85 permission="zope.Public" />
86
80 <!-- Charm-related jobs -->87 <!-- Charm-related jobs -->
81 <class class="lp.charms.model.charmrecipejob.CharmRecipeJob">88 <class class="lp.charms.model.charmrecipejob.CharmRecipeJob">
82 <allow interface="lp.charms.interfaces.charmrecipejob.ICharmRecipeJob" />89 <allow interface="lp.charms.interfaces.charmrecipejob.ICharmRecipeJob" />
diff --git a/lib/lp/charms/model/charmrecipebuildbehaviour.py b/lib/lp/charms/model/charmrecipebuildbehaviour.py
83new file mode 10064490new file mode 100644
index 0000000..31ef483
--- /dev/null
+++ b/lib/lp/charms/model/charmrecipebuildbehaviour.py
@@ -0,0 +1,111 @@
1# Copyright 2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""An `IBuildFarmJobBehaviour` for `CharmRecipeBuild`.
5
6Dispatches charm recipe build jobs to build-farm slaves.
7"""
8
9from __future__ import absolute_import, print_function, unicode_literals
10
11__metaclass__ = type
12__all__ = [
13 "CharmRecipeBuildBehaviour",
14 ]
15
16from twisted.internet import defer
17from zope.component import adapter
18from zope.interface import implementer
19from zope.security.proxy import removeSecurityProxy
20
21from lp.buildmaster.enums import BuildBaseImageType
22from lp.buildmaster.interfaces.builder import CannotBuild
23from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
24 IBuildFarmJobBehaviour,
25 )
26from lp.buildmaster.model.buildfarmjobbehaviour import (
27 BuildFarmJobBehaviourBase,
28 )
29from lp.charms.interfaces.charmrecipebuild import ICharmRecipeBuild
30from lp.registry.interfaces.series import SeriesStatus
31from lp.soyuz.adapters.archivedependencies import (
32 get_sources_list_for_building,
33 )
34
35
36@adapter(ICharmRecipeBuild)
37@implementer(IBuildFarmJobBehaviour)
38class CharmRecipeBuildBehaviour(BuildFarmJobBehaviourBase):
39 """Dispatches `CharmRecipeBuild` jobs to slaves."""
40
41 builder_type = "charm"
42 image_types = [BuildBaseImageType.LXD, BuildBaseImageType.CHROOT]
43
44 def getLogFileName(self):
45 das = self.build.distro_arch_series
46
47 # Examples:
48 # buildlog_charm_ubuntu_wily_amd64_name_FULLYBUILT.txt
49 return "buildlog_charm_%s_%s_%s_%s_%s.txt" % (
50 das.distroseries.distribution.name, das.distroseries.name,
51 das.architecturetag, self.build.recipe.name,
52 self.build.status.name)
53
54 def verifyBuildRequest(self, logger):
55 """Assert some pre-build checks.
56
57 The build request is checked:
58 * Virtualized builds can't build on a non-virtual builder
59 * Ensure that we have a chroot
60 """
61 build = self.build
62 if build.virtualized and not self._builder.virtualized:
63 raise AssertionError(
64 "Attempt to build virtual item on a non-virtual builder.")
65
66 chroot = build.distro_arch_series.getChroot()
67 if chroot is None:
68 raise CannotBuild(
69 "Missing chroot for %s" % build.distro_arch_series.displayname)
70
71 @defer.inlineCallbacks
72 def extraBuildArgs(self, logger=None):
73 """
74 Return the extra arguments required by the slave for the given build.
75 """
76 build = self.build
77 args = yield super(CharmRecipeBuildBehaviour, self).extraBuildArgs(
78 logger=logger)
79 args["name"] = build.recipe.store_name or build.recipe.name
80 channels = build.channels or {}
81 # We have to remove the security proxy that Zope applies to this
82 # dict, since otherwise we'll be unable to serialise it to XML-RPC.
83 args["channels"] = removeSecurityProxy(channels)
84 args["archives"], args["trusted_keys"] = (
85 yield get_sources_list_for_building(
86 self, build.distro_arch_series, None, logger=logger))
87 if build.recipe.build_path is not None:
88 args["build_path"] = build.recipe.build_path
89 if build.recipe.git_ref is not None:
90 args["git_repository"] = build.recipe.git_repository.git_https_url
91 # "git clone -b" doesn't accept full ref names. If this becomes
92 # a problem then we could change launchpad-buildd to do "git
93 # clone" followed by "git checkout" instead.
94 if build.recipe.git_path != "HEAD":
95 args["git_path"] = build.recipe.git_ref.name
96 else:
97 raise CannotBuild(
98 "Source repository for ~%s/%s/+charm/%s has been deleted." % (
99 build.recipe.owner.name, build.recipe.project.name,
100 build.recipe.name))
101 args["private"] = build.is_private
102 defer.returnValue(args)
103
104 def verifySuccessfulBuild(self):
105 """See `IBuildFarmJobBehaviour`."""
106 # The implementation in BuildFarmJobBehaviourBase checks whether the
107 # target suite is modifiable in the target archive. However, a
108 # `CharmRecipeBuild`'s archive is a source rather than a target, so
109 # that check does not make sense. We do, however, refuse to build
110 # for obsolete series.
111 assert self.build.distro_series.status != SeriesStatus.OBSOLETE
diff --git a/lib/lp/charms/tests/test_charmrecipebuildbehaviour.py b/lib/lp/charms/tests/test_charmrecipebuildbehaviour.py
0new file mode 100644112new file mode 100644
index 0000000..cafabd1
--- /dev/null
+++ b/lib/lp/charms/tests/test_charmrecipebuildbehaviour.py
@@ -0,0 +1,423 @@
1# Copyright 2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test charm recipe build behaviour."""
5
6from __future__ import absolute_import, print_function, unicode_literals
7
8__metaclass__ = type
9
10import os.path
11
12from pymacaroons import Macaroon
13from testtools import ExpectedException
14from testtools.matchers import (
15 Equals,
16 Is,
17 IsInstance,
18 MatchesDict,
19 MatchesListwise,
20 )
21from testtools.twistedsupport import (
22 AsynchronousDeferredRunTestForBrokenTwisted,
23 )
24import transaction
25from twisted.internet import defer
26from zope.component import getUtility
27from zope.proxy import isProxy
28from zope.security.proxy import removeSecurityProxy
29
30from lp.app.enums import InformationType
31from lp.archivepublisher.interfaces.archivegpgsigningkey import (
32 IArchiveGPGSigningKey,
33 )
34from lp.buildmaster.enums import (
35 BuildBaseImageType,
36 BuildStatus,
37 )
38from lp.buildmaster.interfaces.builder import CannotBuild
39from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
40 IBuildFarmJobBehaviour,
41 )
42from lp.buildmaster.interfaces.processor import IProcessorSet
43from lp.buildmaster.tests.mock_slaves import (
44 MockBuilder,
45 OkSlave,
46 )
47from lp.buildmaster.tests.test_buildfarmjobbehaviour import (
48 TestGetUploadMethodsMixin,
49 TestHandleStatusMixin,
50 TestVerifySuccessfulBuildMixin,
51 )
52from lp.charms.interfaces.charmrecipe import (
53 CHARM_RECIPE_ALLOW_CREATE,
54 CHARM_RECIPE_PRIVATE_FEATURE_FLAG,
55 )
56from lp.charms.model.charmrecipebuildbehaviour import (
57 CharmRecipeBuildBehaviour,
58 )
59from lp.registry.interfaces.series import SeriesStatus
60from lp.services.config import config
61from lp.services.features.testing import FeatureFixture
62from lp.services.log.logger import (
63 BufferLogger,
64 DevNullLogger,
65 )
66from lp.services.statsd.tests import StatsMixin
67from lp.services.webapp import canonical_url
68from lp.soyuz.adapters.archivedependencies import (
69 get_sources_list_for_building,
70 )
71from lp.soyuz.enums import PackagePublishingStatus
72from lp.soyuz.tests.soyuz import Base64KeyMatches
73from lp.testing import TestCaseWithFactory
74from lp.testing.dbuser import dbuser
75from lp.testing.gpgkeys import gpgkeysdir
76from lp.testing.keyserver import InProcessKeyServerFixture
77from lp.testing.layers import LaunchpadZopelessLayer
78
79
80class TestCharmRecipeBuildBehaviourBase(TestCaseWithFactory):
81 layer = LaunchpadZopelessLayer
82
83 def setUp(self):
84 self.useFixture(FeatureFixture({CHARM_RECIPE_ALLOW_CREATE: "on"}))
85 super(TestCharmRecipeBuildBehaviourBase, self).setUp()
86
87 def makeJob(self, distribution=None, with_builder=False, **kwargs):
88 """Create a sample `ICharmRecipeBuildBehaviour`."""
89 if distribution is None:
90 distribution = self.factory.makeDistribution(name="distro")
91 distroseries = self.factory.makeDistroSeries(
92 distribution=distribution, name="unstable")
93 processor = getUtility(IProcessorSet).getByName("386")
94 distroarchseries = self.factory.makeDistroArchSeries(
95 distroseries=distroseries, architecturetag="i386",
96 processor=processor)
97
98 # Taken from test_archivedependencies.py
99 for component_name in ("main", "universe"):
100 self.factory.makeComponentSelection(distroseries, component_name)
101
102 build = self.factory.makeCharmRecipeBuild(
103 distro_arch_series=distroarchseries, name="test-charm", **kwargs)
104 job = IBuildFarmJobBehaviour(build)
105 if with_builder:
106 builder = MockBuilder()
107 builder.processor = processor
108 job.setBuilder(builder, None)
109 return job
110
111
112class TestCharmRecipeBuildBehaviour(TestCharmRecipeBuildBehaviourBase):
113 layer = LaunchpadZopelessLayer
114
115 def test_provides_interface(self):
116 # CharmRecipeBuildBehaviour provides IBuildFarmJobBehaviour.
117 job = CharmRecipeBuildBehaviour(None)
118 self.assertProvides(job, IBuildFarmJobBehaviour)
119
120 def test_adapts_ICharmRecipeBuild(self):
121 # IBuildFarmJobBehaviour adapts an ICharmRecipeBuild.
122 build = self.factory.makeCharmRecipeBuild()
123 job = IBuildFarmJobBehaviour(build)
124 self.assertProvides(job, IBuildFarmJobBehaviour)
125
126 def test_verifyBuildRequest_valid(self):
127 # verifyBuildRequest doesn't raise any exceptions when called with a
128 # valid builder set.
129 job = self.makeJob()
130 lfa = self.factory.makeLibraryFileAlias()
131 transaction.commit()
132 job.build.distro_arch_series.addOrUpdateChroot(lfa)
133 builder = MockBuilder()
134 job.setBuilder(builder, OkSlave())
135 logger = BufferLogger()
136 job.verifyBuildRequest(logger)
137 self.assertEqual("", logger.getLogBuffer())
138
139 def test_verifyBuildRequest_virtual_mismatch(self):
140 # verifyBuildRequest raises on an attempt to build a virtualized
141 # build on a non-virtual builder.
142 job = self.makeJob()
143 lfa = self.factory.makeLibraryFileAlias()
144 transaction.commit()
145 job.build.distro_arch_series.addOrUpdateChroot(lfa)
146 builder = MockBuilder(virtualized=False)
147 job.setBuilder(builder, OkSlave())
148 logger = BufferLogger()
149 e = self.assertRaises(AssertionError, job.verifyBuildRequest, logger)
150 self.assertEqual(
151 "Attempt to build virtual item on a non-virtual builder.", str(e))
152
153 def test_verifyBuildRequest_no_chroot(self):
154 # verifyBuildRequest raises when the DAS has no chroot.
155 job = self.makeJob()
156 builder = MockBuilder()
157 job.setBuilder(builder, OkSlave())
158 logger = BufferLogger()
159 e = self.assertRaises(CannotBuild, job.verifyBuildRequest, logger)
160 self.assertIn("Missing chroot", str(e))
161
162
163class TestAsyncCharmRecipeBuildBehaviour(
164 StatsMixin, TestCharmRecipeBuildBehaviourBase):
165
166 run_tests_with = AsynchronousDeferredRunTestForBrokenTwisted.make_factory(
167 timeout=30)
168
169 def setUp(self):
170 super(TestAsyncCharmRecipeBuildBehaviour, self).setUp()
171 self.setUpStats()
172
173 @defer.inlineCallbacks
174 def test_composeBuildRequest(self):
175 job = self.makeJob(with_builder=True)
176 lfa = self.factory.makeLibraryFileAlias(db_only=True)
177 job.build.distro_arch_series.addOrUpdateChroot(lfa)
178 build_request = yield job.composeBuildRequest(None)
179 self.assertThat(build_request, MatchesListwise([
180 Equals("charm"),
181 Equals(job.build.distro_arch_series),
182 Equals(job.build.pocket),
183 Equals({}),
184 IsInstance(dict),
185 ]))
186
187 @defer.inlineCallbacks
188 def test_extraBuildArgs_git(self):
189 # extraBuildArgs returns appropriate arguments if asked to build a
190 # job for a Git branch.
191 [ref] = self.factory.makeGitRefs()
192 job = self.makeJob(git_ref=ref, with_builder=True)
193 expected_archives, expected_trusted_keys = (
194 yield get_sources_list_for_building(
195 job, job.build.distro_arch_series, None))
196 for archive_line in expected_archives:
197 self.assertIn("universe", archive_line)
198 with dbuser(config.builddmaster.dbuser):
199 args = yield job.extraBuildArgs()
200 self.assertThat(args, MatchesDict({
201 "archive_private": Is(False),
202 "archives": Equals(expected_archives),
203 "arch_tag": Equals("i386"),
204 "build_url": Equals(canonical_url(job.build)),
205 "channels": Equals({}),
206 "fast_cleanup": Is(True),
207 "git_repository": Equals(ref.repository.git_https_url),
208 "git_path": Equals(ref.name),
209 "name": Equals("test-charm"),
210 "private": Is(False),
211 "series": Equals("unstable"),
212 "trusted_keys": Equals(expected_trusted_keys),
213 }))
214
215 @defer.inlineCallbacks
216 def test_extraBuildArgs_git_HEAD(self):
217 # extraBuildArgs returns appropriate arguments if asked to build a
218 # job for the default branch in a Launchpad-hosted Git repository.
219 [ref] = self.factory.makeGitRefs()
220 removeSecurityProxy(ref.repository)._default_branch = ref.path
221 job = self.makeJob(
222 git_ref=ref.repository.getRefByPath("HEAD"), with_builder=True)
223 expected_archives, expected_trusted_keys = (
224 yield get_sources_list_for_building(
225 job, job.build.distro_arch_series, None))
226 for archive_line in expected_archives:
227 self.assertIn("universe", archive_line)
228 with dbuser(config.builddmaster.dbuser):
229 args = yield job.extraBuildArgs()
230 self.assertThat(args, MatchesDict({
231 "archive_private": Is(False),
232 "archives": Equals(expected_archives),
233 "arch_tag": Equals("i386"),
234 "build_url": Equals(canonical_url(job.build)),
235 "channels": Equals({}),
236 "fast_cleanup": Is(True),
237 "git_repository": Equals(ref.repository.git_https_url),
238 "name": Equals("test-charm"),
239 "private": Is(False),
240 "series": Equals("unstable"),
241 "trusted_keys": Equals(expected_trusted_keys),
242 }))
243
244 @defer.inlineCallbacks
245 def test_extraBuildArgs_prefers_store_name(self):
246 # For the "name" argument, extraBuildArgs prefers
247 # CharmRecipe.store_name over CharmRecipe.name if the former is set.
248 job = self.makeJob(store_name="something-else", with_builder=True)
249 with dbuser(config.builddmaster.dbuser):
250 args = yield job.extraBuildArgs()
251 self.assertEqual("something-else", args["name"])
252
253 @defer.inlineCallbacks
254 def test_extraBuildArgs_archive_trusted_keys(self):
255 # If the archive has a signing key, extraBuildArgs sends it.
256 yield self.useFixture(InProcessKeyServerFixture()).start()
257 distribution = self.factory.makeDistribution()
258 key_path = os.path.join(gpgkeysdir, "ppa-sample@canonical.com.sec")
259 yield IArchiveGPGSigningKey(distribution.main_archive).setSigningKey(
260 key_path, async_keyserver=True)
261 job = self.makeJob(distribution=distribution, with_builder=True)
262 self.factory.makeBinaryPackagePublishingHistory(
263 distroarchseries=job.build.distro_arch_series,
264 pocket=job.build.pocket, archive=distribution.main_archive,
265 status=PackagePublishingStatus.PUBLISHED)
266 with dbuser(config.builddmaster.dbuser):
267 args = yield job.extraBuildArgs()
268 self.assertThat(args["trusted_keys"], MatchesListwise([
269 Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
270 ]))
271
272 @defer.inlineCallbacks
273 def test_extraBuildArgs_channels(self):
274 # If the build needs particular channels, extraBuildArgs sends them.
275 job = self.makeJob(channels={"charmcraft": "edge"}, with_builder=True)
276 expected_archives, expected_trusted_keys = (
277 yield get_sources_list_for_building(
278 job, job.build.distro_arch_series, None))
279 with dbuser(config.builddmaster.dbuser):
280 args = yield job.extraBuildArgs()
281 self.assertFalse(isProxy(args["channels"]))
282 self.assertEqual({"charmcraft": "edge"}, args["channels"])
283
284 @defer.inlineCallbacks
285 def test_extraBuildArgs_archives_primary(self):
286 # The build uses the release, security, and updates pockets from the
287 # primary archive.
288 job = self.makeJob(with_builder=True)
289 expected_archives = [
290 "deb %s %s main universe" % (
291 job.archive.archive_url, job.build.distro_series.name),
292 "deb %s %s-security main universe" % (
293 job.archive.archive_url, job.build.distro_series.name),
294 "deb %s %s-updates main universe" % (
295 job.archive.archive_url, job.build.distro_series.name),
296 ]
297 with dbuser(config.builddmaster.dbuser):
298 extra_args = yield job.extraBuildArgs()
299 self.assertEqual(expected_archives, extra_args["archives"])
300
301 @defer.inlineCallbacks
302 def test_extraBuildArgs_build_path(self):
303 # If the recipe specifies a build path, extraBuildArgs sends it.
304 job = self.makeJob(build_path="src", with_builder=True)
305 expected_archives, expected_trusted_keys = (
306 yield get_sources_list_for_building(
307 job, job.build.distro_arch_series, None))
308 with dbuser(config.builddmaster.dbuser):
309 args = yield job.extraBuildArgs()
310 self.assertEqual("src", args["build_path"])
311
312 @defer.inlineCallbacks
313 def test_extraBuildArgs_private(self):
314 # If the recipe is private, extraBuildArgs sends the appropriate
315 # arguments.
316 self.useFixture(FeatureFixture({
317 CHARM_RECIPE_ALLOW_CREATE: "on",
318 CHARM_RECIPE_PRIVATE_FEATURE_FLAG: "on",
319 }))
320 job = self.makeJob(
321 information_type=InformationType.PROPRIETARY, with_builder=True)
322 with dbuser(config.builddmaster.dbuser):
323 args = yield job.extraBuildArgs()
324 self.assertTrue(args["private"])
325
326 @defer.inlineCallbacks
327 def test_composeBuildRequest_git_ref_deleted(self):
328 # If the source Git reference has been deleted, composeBuildRequest
329 # raises CannotBuild.
330 repository = self.factory.makeGitRepository()
331 [ref] = self.factory.makeGitRefs(repository=repository)
332 owner = self.factory.makePerson(name="charm-owner")
333 project = self.factory.makeProduct(name="charm-project")
334 job = self.makeJob(
335 registrant=owner, owner=owner, project=project, git_ref=ref,
336 with_builder=True)
337 repository.removeRefs([ref.path])
338 self.assertIsNone(job.build.recipe.git_ref)
339 expected_exception_msg = (
340 r"Source repository for "
341 r"~charm-owner/charm-project/\+charm/test-charm has been deleted.")
342 with ExpectedException(CannotBuild, expected_exception_msg):
343 yield job.composeBuildRequest(None)
344
345 @defer.inlineCallbacks
346 def test_dispatchBuildToSlave_prefers_lxd(self):
347 job = self.makeJob()
348 builder = MockBuilder()
349 builder.processor = job.build.processor
350 slave = OkSlave()
351 job.setBuilder(builder, slave)
352 chroot_lfa = self.factory.makeLibraryFileAlias(db_only=True)
353 job.build.distro_arch_series.addOrUpdateChroot(
354 chroot_lfa, image_type=BuildBaseImageType.CHROOT)
355 lxd_lfa = self.factory.makeLibraryFileAlias(db_only=True)
356 job.build.distro_arch_series.addOrUpdateChroot(
357 lxd_lfa, image_type=BuildBaseImageType.LXD)
358 yield job.dispatchBuildToSlave(DevNullLogger())
359 self.assertEqual(
360 ("ensurepresent", lxd_lfa.http_url, "", ""), slave.call_log[0])
361 self.assertEqual(1, self.stats_client.incr.call_count)
362 self.assertEqual(
363 self.stats_client.incr.call_args_list[0][0],
364 ("build.count,builder_name={},env=test,"
365 "job_type=CHARMRECIPEBUILD".format(builder.name),))
366
367 @defer.inlineCallbacks
368 def test_dispatchBuildToSlave_falls_back_to_chroot(self):
369 job = self.makeJob()
370 builder = MockBuilder()
371 builder.processor = job.build.processor
372 slave = OkSlave()
373 job.setBuilder(builder, slave)
374 chroot_lfa = self.factory.makeLibraryFileAlias(db_only=True)
375 job.build.distro_arch_series.addOrUpdateChroot(
376 chroot_lfa, image_type=BuildBaseImageType.CHROOT)
377 yield job.dispatchBuildToSlave(DevNullLogger())
378 self.assertEqual(
379 ("ensurepresent", chroot_lfa.http_url, "", ""), slave.call_log[0])
380
381
382class MakeCharmRecipeBuildMixin:
383 """Provide the common makeBuild method returning a queued build."""
384
385 def makeCharmRecipe(self):
386 self.useFixture(FeatureFixture({CHARM_RECIPE_ALLOW_CREATE: "on"}))
387 return self.factory.makeCharmRecipe(
388 store_upload=True, store_name=self.factory.getUniqueUnicode(),
389 store_secrets={"root": Macaroon().serialize()})
390
391 def makeBuild(self):
392 recipe = self.makeCharmRecipe()
393 build = self.factory.makeCharmRecipeBuild(
394 requester=recipe.registrant, recipe=recipe,
395 status=BuildStatus.BUILDING)
396 build.queueBuild()
397 return build
398
399 def makeUnmodifiableBuild(self):
400 recipe = self.makeCharmRecipe()
401 build = self.factory.makeCharmRecipeBuild(
402 requester=recipe.registrant, recipe=recipe,
403 status=BuildStatus.BUILDING)
404 build.distro_series.status = SeriesStatus.OBSOLETE
405 build.queueBuild()
406 return build
407
408
409class TestGetUploadMethodsForCharmRecipeBuild(
410 MakeCharmRecipeBuildMixin, TestGetUploadMethodsMixin,
411 TestCaseWithFactory):
412 """IPackageBuild.getUpload* methods work with charm recipe builds."""
413
414
415class TestVerifySuccessfulBuildForCharmRecipeBuild(
416 MakeCharmRecipeBuildMixin, TestVerifySuccessfulBuildMixin,
417 TestCaseWithFactory):
418 """IBuildFarmJobBehaviour.verifySuccessfulBuild works."""
419
420
421class TestHandleStatusForCharmRecipeBuild(
422 MakeCharmRecipeBuildMixin, TestHandleStatusMixin, TestCaseWithFactory):
423 """IPackageBuild.handleStatus works with charm recipe builds."""

Subscribers

People subscribed via source and target branches

to status/vote changes: