Merge lp:~cjwatson/launchpad/git-recipe-model into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 17893
Proposed branch: lp:~cjwatson/launchpad/git-recipe-model
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/minimal-recipe-text-bzr
Diff against target: 1518 lines (+472/-211)
8 files modified
lib/lp/code/errors.py (+14/-3)
lib/lp/code/interfaces/sourcepackagerecipe.py (+16/-1)
lib/lp/code/model/sourcepackagerecipe.py (+21/-1)
lib/lp/code/model/sourcepackagerecipebuild.py (+1/-1)
lib/lp/code/model/sourcepackagerecipedata.py (+133/-41)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+264/-157)
lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+4/-4)
lib/lp/testing/factory.py (+19/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/git-recipe-model
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+282248@code.launchpad.net

Commit message

Add basic model for Git recipes.

Description of the change

Add basic model for Git recipes.

This is long, but it's mostly tests, and I couldn't get it much shorter while still testing it properly.

While the database model has git_repository plus a revspec, and there are certainly cases where one might wish to pin a recipe to a particular tag or commit rather than a moving branch (so that should be possible), the common case is going to be to create a recipe based on a branch; this is therefore optimised for that workflow, so you can point the recipe construction code at an IGitRef and it will set the revspec to the branch name. The browser code will let you create and list recipes from either a GitRef or a GitRepository (and if you don't fill in a revspec manually in the latter case, git-build-recipe will use HEAD, which matches "git clone" and is often what people will want in practice). I think this approach is least likely to result in confused users unable to find the link.

Other bits like repository deletion handling, staleness handling, and browser code will follow in later branches.

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 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2015-06-13 01:45:19 +0000
+++ lib/lp/code/errors.py 2016-01-12 17:27:35 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2015 Canonical Ltd. This software is licensed under the1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Errors used in the lp/code modules."""4"""Errors used in the lp/code modules."""
@@ -46,6 +46,7 @@
46 'NoSuchGitReference',46 'NoSuchGitReference',
47 'NoSuchGitRepository',47 'NoSuchGitRepository',
48 'PrivateBranchRecipe',48 'PrivateBranchRecipe',
49 'PrivateGitRepositoryRecipe',
49 'ReviewNotPending',50 'ReviewNotPending',
50 'StaleLastMirrored',51 'StaleLastMirrored',
51 'TooNewRecipeFormat',52 'TooNewRecipeFormat',
@@ -298,12 +299,22 @@
298299
299 def __init__(self, branch):300 def __init__(self, branch):
300 message = (301 message = (
301 'Recipe may not refer to private branch: %s' %302 'Recipe may not refer to private branch: %s' % branch.identity)
302 branch.bzr_identity)
303 self.branch = branch303 self.branch = branch
304 Exception.__init__(self, message)304 Exception.__init__(self, message)
305305
306306
307@error_status(httplib.BAD_REQUEST)
308class PrivateGitRepositoryRecipe(Exception):
309
310 def __init__(self, repository):
311 message = (
312 'Recipe may not refer to private repository: %s' %
313 repository.identity)
314 self.repository = repository
315 Exception.__init__(self, message)
316
317
307class ReviewNotPending(Exception):318class ReviewNotPending(Exception):
308 """The requested review is not in a pending state."""319 """The requested review is not in a pending state."""
309320
310321
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2016-01-11 21:11:27 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2016-01-12 17:27:35 +0000
@@ -14,6 +14,7 @@
14 'ISourcePackageRecipeDataSource',14 'ISourcePackageRecipeDataSource',
15 'ISourcePackageRecipeSource',15 'ISourcePackageRecipeSource',
16 'MINIMAL_RECIPE_TEXT_BZR',16 'MINIMAL_RECIPE_TEXT_BZR',
17 'MINIMAL_RECIPE_TEXT_GIT',
17 ]18 ]
1819
1920
@@ -55,6 +56,7 @@
55from lp import _56from lp import _
56from lp.app.validators.name import name_validator57from lp.app.validators.name import name_validator
57from lp.code.interfaces.branch import IBranch58from lp.code.interfaces.branch import IBranch
59from lp.code.interfaces.gitrepository import IGitRepository
58from lp.registry.interfaces.distroseries import IDistroSeries60from lp.registry.interfaces.distroseries import IDistroSeries
59from lp.registry.interfaces.pocket import PackagePublishingPocket61from lp.registry.interfaces.pocket import PackagePublishingPocket
60from lp.registry.interfaces.role import IHasOwner62from lp.registry.interfaces.role import IHasOwner
@@ -72,13 +74,26 @@
72 ''')74 ''')
7375
7476
77MINIMAL_RECIPE_TEXT_GIT = dedent(u'''\
78 # git-build-recipe format 0.4 deb-version {debupstream}-0~{revtime}
79 %s %s
80 ''')
81
82
75class ISourcePackageRecipeData(Interface):83class ISourcePackageRecipeData(Interface):
76 """A recipe as database data, not text."""84 """A recipe as database data, not text."""
7785
78 base_branch = exported(86 base_branch = exported(
79 Reference(87 Reference(
80 IBranch, title=_("The base branch used by this recipe."),88 IBranch, title=_("The base branch used by this recipe."),
81 required=True, readonly=True))89 required=False, readonly=True))
90 base_git_repository = exported(
91 Reference(
92 IGitRepository,
93 title=_("The base Git repository used by this recipe."),
94 required=False, readonly=True))
95 base = Attribute(
96 "The base branch/repository used by this recipe (VCS-agnostic).")
8297
83 deb_version_template = exported(98 deb_version_template = exported(
84 TextLine(99 TextLine(
85100
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2016-01-11 19:16:04 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2016-01-12 17:27:35 +0000
@@ -13,6 +13,7 @@
13 timedelta,13 timedelta,
14 )14 )
15from operator import attrgetter15from operator import attrgetter
16import re
1617
17from lazr.delegates import delegate_to18from lazr.delegates import delegate_to
18from pytz import utc19from pytz import utc
@@ -151,6 +152,18 @@
151 def base_branch(self):152 def base_branch(self):
152 return self._recipe_data.base_branch153 return self._recipe_data.base_branch
153154
155 @property
156 def base_git_repository(self):
157 return self._recipe_data.base_git_repository
158
159 @property
160 def base(self):
161 if self.base_branch is not None:
162 return self.base_branch
163 else:
164 assert self.base_git_repository is not None
165 return self.base_git_repository
166
154 @staticmethod167 @staticmethod
155 def preLoadDataForSourcePackageRecipes(sourcepackagerecipes):168 def preLoadDataForSourcePackageRecipes(sourcepackagerecipes):
156 # Load the referencing SourcePackageRecipeData.169 # Load the referencing SourcePackageRecipeData.
@@ -173,7 +186,14 @@
173186
174 @property187 @property
175 def recipe_text(self):188 def recipe_text(self):
176 return self.builder_recipe.get_recipe_text()189 recipe_text = self.builder_recipe.get_recipe_text()
190 # For git-based recipes, mangle the header line to say
191 # "git-build-recipe" to reduce confusion; bzr-builder's recipe
192 # parser will always round-trip this to "bzr-builder".
193 if self.base_git_repository is not None:
194 recipe_text = re.sub(
195 r"^(#\s*)bzr-builder", r"\1git-build-recipe", recipe_text)
196 return recipe_text
177197
178 def updateSeries(self, distroseries):198 def updateSeries(self, distroseries):
179 if distroseries != self.distroseries:199 if distroseries != self.distroseries:
180200
=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
--- lib/lp/code/model/sourcepackagerecipebuild.py 2016-01-11 19:16:04 +0000
+++ lib/lp/code/model/sourcepackagerecipebuild.py 2016-01-12 17:27:35 +0000
@@ -183,7 +183,7 @@
183 if self.recipe is None:183 if self.recipe is None:
184 branch_name = 'deleted'184 branch_name = 'deleted'
185 else:185 else:
186 branch_name = self.recipe.base_branch.unique_name186 branch_name = self.recipe.base.unique_name
187 return '%s recipe build in %s %s' % (187 return '%s recipe build in %s %s' % (
188 branch_name, self.distribution.name, self.distroseries.name)188 branch_name, self.distribution.name, self.distroseries.name)
189189
190190
=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
--- lib/lp/code/model/sourcepackagerecipedata.py 2016-01-11 19:16:04 +0000
+++ lib/lp/code/model/sourcepackagerecipedata.py 2016-01-12 17:27:35 +0000
@@ -11,7 +11,7 @@
11__metaclass__ = type11__metaclass__ = type
12__all__ = ['SourcePackageRecipeData']12__all__ = ['SourcePackageRecipeData']
1313
14from itertools import groupby14import re
1515
16from bzrlib.plugins.builder.recipe import (16from bzrlib.plugins.builder.recipe import (
17 BaseRecipeBranch,17 BaseRecipeBranch,
@@ -45,16 +45,23 @@
4545
46from lp.code.errors import (46from lp.code.errors import (
47 NoSuchBranch,47 NoSuchBranch,
48 NoSuchGitRepository,
48 PrivateBranchRecipe,49 PrivateBranchRecipe,
50 PrivateGitRepositoryRecipe,
49 TooNewRecipeFormat,51 TooNewRecipeFormat,
50 )52 )
53from lp.code.interfaces.branch import IBranch
51from lp.code.interfaces.branchlookup import IBranchLookup54from lp.code.interfaces.branchlookup import IBranchLookup
55from lp.code.interfaces.gitlookup import IGitLookup
56from lp.code.interfaces.gitref import IGitRef
57from lp.code.interfaces.gitrepository import IGitRepository
52from lp.code.interfaces.sourcepackagerecipe import (58from lp.code.interfaces.sourcepackagerecipe import (
53 IRecipeBranchSource,59 IRecipeBranchSource,
54 ISourcePackageRecipeData,60 ISourcePackageRecipeData,
55 ISourcePackageRecipeDataSource,61 ISourcePackageRecipeDataSource,
56 )62 )
57from lp.code.model.branch import Branch63from lp.code.model.branch import Branch
64from lp.code.model.gitrepository import GitRepository
58from lp.services.database.bulk import (65from lp.services.database.bulk import (
59 load_referencing,66 load_referencing,
60 load_related,67 load_related,
@@ -92,15 +99,25 @@
9299
93 __storm_table__ = "SourcePackageRecipeDataInstruction"100 __storm_table__ = "SourcePackageRecipeDataInstruction"
94101
95 def __init__(self, name, type, comment, line_number, branch, revspec,102 def __init__(self, name, type, comment, line_number, branch_or_repository,
96 directory, recipe_data, parent_instruction,103 revspec, directory, recipe_data, parent_instruction,
97 source_directory):104 source_directory):
98 super(_SourcePackageRecipeDataInstruction, self).__init__()105 super(_SourcePackageRecipeDataInstruction, self).__init__()
99 self.name = unicode(name)106 self.name = unicode(name)
100 self.type = type107 self.type = type
101 self.comment = comment108 self.comment = comment
102 self.line_number = line_number109 self.line_number = line_number
103 self.branch = branch110 if IGitRepository.providedBy(branch_or_repository):
111 self.git_repository = branch_or_repository
112 elif IGitRef.providedBy(branch_or_repository):
113 self.git_repository = branch_or_repository
114 if revspec is None:
115 revspec = branch_or_repository.name
116 elif IBranch.providedBy(branch_or_repository):
117 self.branch = branch_or_repository
118 else:
119 raise AssertionError(
120 "Unsupported source: %r" % (branch_or_repository,))
104 if revspec is not None:121 if revspec is not None:
105 revspec = unicode(revspec)122 revspec = unicode(revspec)
106 self.revspec = revspec123 self.revspec = revspec
@@ -118,8 +135,10 @@
118 comment = Unicode(allow_none=True)135 comment = Unicode(allow_none=True)
119 line_number = Int(allow_none=False)136 line_number = Int(allow_none=False)
120137
121 branch_id = Int(name='branch', allow_none=False)138 branch_id = Int(name='branch', allow_none=True)
122 branch = Reference(branch_id, 'Branch.id')139 branch = Reference(branch_id, 'Branch.id')
140 git_repository_id = Int(name='git_repository', allow_none=True)
141 git_repository = Reference(git_repository_id, 'GitRepository.id')
123142
124 revspec = Unicode(allow_none=True)143 revspec = Unicode(allow_none=True)
125 directory = Unicode(allow_none=True)144 directory = Unicode(allow_none=True)
@@ -134,8 +153,12 @@
134153
135 def appendToRecipe(self, recipe_branch):154 def appendToRecipe(self, recipe_branch):
136 """Append a bzr-builder instruction to the recipe_branch object."""155 """Append a bzr-builder instruction to the recipe_branch object."""
137 branch = RecipeBranch(156 if self.branch is not None:
138 self.name, self.branch.bzr_identity, self.revspec)157 identity = self.branch.identity
158 else:
159 assert self.git_repository is not None
160 identity = self.git_repository.identity
161 branch = RecipeBranch(self.name, identity, self.revspec)
139 if self.type == InstructionType.MERGE:162 if self.type == InstructionType.MERGE:
140 recipe_branch.merge_branch(branch)163 recipe_branch.merge_branch(branch)
141 elif self.type == InstructionType.NEST:164 elif self.type == InstructionType.NEST:
@@ -165,8 +188,29 @@
165188
166 id = Int(primary=True)189 id = Int(primary=True)
167190
168 base_branch_id = Int(name='base_branch', allow_none=False)191 base_branch_id = Int(name='base_branch', allow_none=True)
169 base_branch = Reference(base_branch_id, 'Branch.id')192 base_branch = Reference(base_branch_id, 'Branch.id')
193 base_git_repository_id = Int(name='base_git_repository', allow_none=True)
194 base_git_repository = Reference(base_git_repository_id, 'GitRepository.id')
195
196 @property
197 def base(self):
198 if self.base_branch is not None:
199 return self.base_branch
200 else:
201 assert self.base_git_repository is not None
202 return self.base_git_repository
203
204 @base.setter
205 def base(self, value):
206 if IGitRepository.providedBy(value):
207 self.base_git_repository = value
208 self.base_branch = None
209 elif IBranch.providedBy(value):
210 self.base_branch = value
211 self.base_git_repository = None
212 else:
213 raise AssertionError("Unsupported base: %r" % (value,))
170214
171 recipe_format = Unicode(allow_none=False)215 recipe_format = Unicode(allow_none=False)
172 deb_version_template = Unicode(allow_none=True)216 deb_version_template = Unicode(allow_none=True)
@@ -189,6 +233,12 @@
189 @staticmethod233 @staticmethod
190 def getParsedRecipe(recipe_text):234 def getParsedRecipe(recipe_text):
191 """See `IRecipeBranchSource`."""235 """See `IRecipeBranchSource`."""
236 # We're using bzr-builder to parse the recipe text. While the
237 # formats are mostly compatible, the header line must say
238 # "bzr-builder" even though git-build-recipe also supports its own
239 # name there.
240 recipe_text = re.sub(
241 r"^(#\s*)git-build-recipe", r"\1bzr-builder", recipe_text)
192 parser = RecipeParser(recipe_text)242 parser = RecipeParser(recipe_text)
193 return parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)243 return parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)
194244
@@ -222,7 +272,7 @@
222 def getRecipe(self):272 def getRecipe(self):
223 """The BaseRecipeBranch version of the recipe."""273 """The BaseRecipeBranch version of the recipe."""
224 base_branch = BaseRecipeBranch(274 base_branch = BaseRecipeBranch(
225 self.base_branch.bzr_identity, self.deb_version_template,275 self.base.identity, self.deb_version_template,
226 self.recipe_format, self.revspec)276 self.recipe_format, self.revspec)
227 insn_stack = []277 insn_stack = []
228 for insn in self.instructions:278 for insn in self.instructions:
@@ -238,7 +288,7 @@
238 dict(insn=insn, recipe_branch=recipe_branch))288 dict(insn=insn, recipe_branch=recipe_branch))
239 return base_branch289 return base_branch
240290
241 def _scanInstructions(self, recipe_branch):291 def _scanInstructions(self, base, recipe_branch):
242 """Check the recipe_branch doesn't use 'run' and look up the branches.292 """Check the recipe_branch doesn't use 'run' and look up the branches.
243293
244 We do all the lookups before we start constructing database objects to294 We do all the lookups before we start constructing database objects to
@@ -247,15 +297,22 @@
247 :return: A map ``{branch_url: db_branch}``.297 :return: A map ``{branch_url: db_branch}``.
248 """298 """
249 r = {}299 r = {}
300 if IGitRepository.providedBy(base):
301 lookup = getUtility(IGitLookup)
302 missing_error = NoSuchGitRepository
303 private_error = PrivateGitRepositoryRecipe
304 else:
305 lookup = getUtility(IBranchLookup)
306 missing_error = NoSuchBranch
307 private_error = PrivateBranchRecipe
250 for instruction in recipe_branch.child_branches:308 for instruction in recipe_branch.child_branches:
251 db_branch = getUtility(IBranchLookup).getByUrl(309 db_branch = lookup.getByUrl(instruction.recipe_branch.url)
252 instruction.recipe_branch.url)
253 if db_branch is None:310 if db_branch is None:
254 raise NoSuchBranch(instruction.recipe_branch.url)311 raise missing_error(instruction.recipe_branch.url)
255 if db_branch.private:312 if db_branch.private:
256 raise PrivateBranchRecipe(db_branch)313 raise private_error(db_branch)
257 r[instruction.recipe_branch.url] = db_branch314 r[instruction.recipe_branch.url] = db_branch
258 r.update(self._scanInstructions(instruction.recipe_branch))315 r.update(self._scanInstructions(base, instruction.recipe_branch))
259 return r316 return r
260317
261 def _recordInstructions(self, recipe_branch, parent_insn, branch_map,318 def _recordInstructions(self, recipe_branch, parent_insn, branch_map,
@@ -280,10 +337,11 @@
280 "Unsupported instruction %r" % instruction)337 "Unsupported instruction %r" % instruction)
281 line_number += 1338 line_number += 1
282 comment = None339 comment = None
283 db_branch = branch_map[instruction.recipe_branch.url]340 db_branch_or_repository = branch_map[instruction.recipe_branch.url]
284 insn = _SourcePackageRecipeDataInstruction(341 insn = _SourcePackageRecipeDataInstruction(
285 instruction.recipe_branch.name, type, comment,342 instruction.recipe_branch.name, type, comment,
286 line_number, db_branch, instruction.recipe_branch.revspec,343 line_number, db_branch_or_repository,
344 instruction.recipe_branch.revspec,
287 nest_path, self, parent_insn, source_directory)345 nest_path, self, parent_insn, source_directory)
288 line_number = self._recordInstructions(346 line_number = self._recordInstructions(
289 instruction.recipe_branch, insn, branch_map, line_number)347 instruction.recipe_branch, insn, branch_map, line_number)
@@ -294,24 +352,33 @@
294 clear_property_cache(self)352 clear_property_cache(self)
295 if builder_recipe.format > MAX_RECIPE_FORMAT:353 if builder_recipe.format > MAX_RECIPE_FORMAT:
296 raise TooNewRecipeFormat(builder_recipe.format, MAX_RECIPE_FORMAT)354 raise TooNewRecipeFormat(builder_recipe.format, MAX_RECIPE_FORMAT)
297 branch_map = self._scanInstructions(builder_recipe)355 base = getUtility(IBranchLookup).getByUrl(builder_recipe.url)
356 if base is None:
357 base = getUtility(IGitLookup).getByUrl(builder_recipe.url)
358 if base is None:
359 # If possible, try to raise an exception consistent with
360 # whether the current recipe is Bazaar-based or Git-based,
361 # so that error messages make more sense.
362 if self.base_git_repository is not None:
363 raise NoSuchGitRepository(builder_recipe.url)
364 else:
365 raise NoSuchBranch(builder_recipe.url)
366 elif base.private:
367 raise PrivateGitRepositoryRecipe(base)
368 elif base.private:
369 raise PrivateBranchRecipe(base)
370 branch_map = self._scanInstructions(base, builder_recipe)
298 # If this object hasn't been added to a store yet, there can't be any371 # If this object hasn't been added to a store yet, there can't be any
299 # instructions linking to us yet.372 # instructions linking to us yet.
300 if Store.of(self) is not None:373 if Store.of(self) is not None:
301 self.instructions.find().remove()374 self.instructions.find().remove()
302 branch_lookup = getUtility(IBranchLookup)
303 base_branch = branch_lookup.getByUrl(builder_recipe.url)
304 if base_branch is None:
305 raise NoSuchBranch(builder_recipe.url)
306 if base_branch.private:
307 raise PrivateBranchRecipe(base_branch)
308 if builder_recipe.revspec is not None:375 if builder_recipe.revspec is not None:
309 self.revspec = unicode(builder_recipe.revspec)376 self.revspec = unicode(builder_recipe.revspec)
310 else:377 else:
311 self.revspec = None378 self.revspec = None
312 self._recordInstructions(379 self._recordInstructions(
313 builder_recipe, parent_insn=None, branch_map=branch_map)380 builder_recipe, parent_insn=None, branch_map=branch_map)
314 self.base_branch = base_branch381 self.base = base
315 if builder_recipe.deb_version is None:382 if builder_recipe.deb_version is None:
316 self.deb_version_template = None383 self.deb_version_template = None
317 else:384 else:
@@ -329,44 +396,69 @@
329396
330 @staticmethod397 @staticmethod
331 def preLoadReferencedBranches(sourcepackagerecipedatas):398 def preLoadReferencedBranches(sourcepackagerecipedatas):
332 # Circular import.399 # Circular imports.
333 from lp.code.model.branchcollection import GenericBranchCollection400 from lp.code.model.branchcollection import GenericBranchCollection
401 from lp.code.model.gitcollection import GenericGitCollection
334 # Load the related Branch, _SourcePackageRecipeDataInstruction.402 # Load the related Branch, _SourcePackageRecipeDataInstruction.
335 base_branches = load_related(403 base_branches = load_related(
336 Branch, sourcepackagerecipedatas, ['base_branch_id'])404 Branch, sourcepackagerecipedatas, ['base_branch_id'])
405 base_repositories = load_related(
406 GitRepository, sourcepackagerecipedatas,
407 ['base_git_repository_id'])
337 sprd_instructions = load_referencing(408 sprd_instructions = load_referencing(
338 _SourcePackageRecipeDataInstruction,409 _SourcePackageRecipeDataInstruction,
339 sourcepackagerecipedatas, ['recipe_data_id'])410 sourcepackagerecipedatas, ['recipe_data_id'])
340 sub_branches = load_related(411 sub_branches = load_related(
341 Branch, sprd_instructions, ['branch_id'])412 Branch, sprd_instructions, ['branch_id'])
413 sub_repositories = load_related(
414 GitRepository, sprd_instructions, ['git_repository_id'])
342 all_branches = base_branches + sub_branches415 all_branches = base_branches + sub_branches
343 # Pre-load branches' data.416 all_repositories = base_repositories + sub_repositories
344 GenericBranchCollection.preloadDataForBranches(all_branches)417 # Pre-load branches'/repositories' data.
418 if all_branches:
419 GenericBranchCollection.preloadDataForBranches(all_branches)
420 if all_repositories:
421 GenericGitCollection.preloadDataForRepositories(all_repositories)
345 # Store the pre-fetched objects on the sourcepackagerecipedatas422 # Store the pre-fetched objects on the sourcepackagerecipedatas
346 # objects.423 # objects.
347 branch_to_recipe_data = dict([424 branch_to_recipe_data = {
348 (instr.branch_id, instr.recipe_data_id)425 instr.branch_id: instr.recipe_data_id
349 for instr in sprd_instructions])426 for instr in sprd_instructions
350 caches = dict((sprd.id, [sprd, get_property_cache(sprd)])427 if instr.branch_id is not None}
351 for sprd in sourcepackagerecipedatas)428 repository_to_recipe_data = {
352 for unused, [sprd, cache] in caches.items():429 instr.git_repository_id: instr.recipe_data_id
353 cache._referenced_branches = [sprd.base_branch]430 for instr in sprd_instructions
354 for recipe_data_id, branches in groupby(431 if instr.git_repository_id is not None}
355 sub_branches, lambda branch: branch_to_recipe_data[branch.id]):432 caches = {
356 cache = caches[recipe_data_id][1]433 sprd.id: [sprd, get_property_cache(sprd)]
357 cache._referenced_branches.extend(list(branches))434 for sprd in sourcepackagerecipedatas}
435 for _, [sprd, cache] in caches.items():
436 cache._referenced_branches = [sprd.base]
437 for branch in sub_branches:
438 cache = caches[branch_to_recipe_data[branch.id]][1]
439 cache._referenced_branches.append(branch)
440 for repository in sub_repositories:
441 cache = caches[repository_to_recipe_data[repository.id]][1]
442 cache._referenced_branches.append(repository)
358443
359 def getReferencedBranches(self):444 def getReferencedBranches(self):
360 """Return an iterator of the Branch objects referenced by this recipe.445 """Return an iterator of the Branch/GitRepository objects referenced
446 by this recipe.
361 """447 """
362 return self._referenced_branches448 return self._referenced_branches
363449
364 @cachedproperty450 @cachedproperty
365 def _referenced_branches(self):451 def _referenced_branches(self):
366 referenced_branches = [self.base_branch]452 referenced_branches = [self.base]
367 sub_branches = IStore(self).find(453 sub_branches = IStore(self).find(
368 Branch,454 Branch,
369 _SourcePackageRecipeDataInstruction.recipe_data == self,455 _SourcePackageRecipeDataInstruction.recipe_data == self,
370 Branch.id == _SourcePackageRecipeDataInstruction.branch_id)456 Branch.id == _SourcePackageRecipeDataInstruction.branch_id)
371 referenced_branches.extend(sub_branches)457 referenced_branches.extend(sub_branches)
458 sub_repositories = IStore(self).find(
459 GitRepository,
460 _SourcePackageRecipeDataInstruction.recipe_data == self,
461 GitRepository.id ==
462 _SourcePackageRecipeDataInstruction.git_repository_id)
463 referenced_branches.extend(sub_repositories)
372 return referenced_branches464 return referenced_branches
373465
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2016-01-11 21:11:27 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2016-01-12 17:27:35 +0000
@@ -32,6 +32,7 @@
32from lp.code.errors import (32from lp.code.errors import (
33 BuildAlreadyPending,33 BuildAlreadyPending,
34 PrivateBranchRecipe,34 PrivateBranchRecipe,
35 PrivateGitRepositoryRecipe,
35 TooNewRecipeFormat,36 TooNewRecipeFormat,
36 )37 )
37from lp.code.interfaces.sourcepackagerecipe import (38from lp.code.interfaces.sourcepackagerecipe import (
@@ -39,6 +40,7 @@
39 ISourcePackageRecipeSource,40 ISourcePackageRecipeSource,
40 ISourcePackageRecipeView,41 ISourcePackageRecipeView,
41 MINIMAL_RECIPE_TEXT_BZR,42 MINIMAL_RECIPE_TEXT_BZR,
43 MINIMAL_RECIPE_TEXT_GIT,
42 )44 )
43from lp.code.interfaces.sourcepackagerecipebuild import (45from lp.code.interfaces.sourcepackagerecipebuild import (
44 ISourcePackageRecipeBuild,46 ISourcePackageRecipeBuild,
@@ -86,14 +88,76 @@
86from lp.testing.pages import webservice_for_person88from lp.testing.pages import webservice_for_person
8789
8890
89class TestSourcePackageRecipe(TestCaseWithFactory):91class BzrMixin:
92 """Mixin for Bazaar-based recipe tests."""
93
94 private_error = PrivateBranchRecipe
95 branch_type = "branch"
96 recipe_id = "bzr-builder"
97
98 def makeBranch(self, **kwargs):
99 return self.factory.makeAnyBranch(**kwargs)
100
101 @staticmethod
102 def getRepository(branch):
103 return branch
104
105 @staticmethod
106 def getBranchRecipeText(branch):
107 return branch.identity
108
109 @staticmethod
110 def setInformationType(branch, information_type):
111 removeSecurityProxy(branch).information_type = information_type
112
113 def makeRecipeText(self):
114 branch = self.makeBranch()
115 return MINIMAL_RECIPE_TEXT_BZR % branch.identity
116
117
118class GitMixin:
119 """Mixin for Git-based recipe tests."""
120
121 private_error = PrivateGitRepositoryRecipe
122 branch_type = "repository"
123 recipe_id = "git-build-recipe"
124
125 def makeBranch(self, **kwargs):
126 return self.factory.makeGitRefs(**kwargs)[0]
127
128 @staticmethod
129 def getRepository(branch):
130 return branch.repository
131
132 @staticmethod
133 def getBranchRecipeText(branch):
134 return branch.identity
135
136 @staticmethod
137 def setInformationType(branch, information_type):
138 removeSecurityProxy(branch.repository).information_type = (
139 information_type)
140
141 def makeRecipeText(self):
142 branch = self.makeBranch()
143 return MINIMAL_RECIPE_TEXT_GIT % (
144 branch.repository.identity, branch.name)
145
146
147class TestSourcePackageRecipeMixin:
90 """Tests for `SourcePackageRecipe` objects."""148 """Tests for `SourcePackageRecipe` objects."""
91149
92 layer = DatabaseFunctionalLayer150 layer = DatabaseFunctionalLayer
93151
152 def makeSourcePackageRecipe(self, branches=(), recipe=None, **kwargs):
153 if recipe is None and len(branches) == 0:
154 branches = [self.makeBranch()]
155 return self.factory.makeSourcePackageRecipe(
156 branches=branches, recipe=recipe, **kwargs)
157
94 def test_implements_interface(self):158 def test_implements_interface(self):
95 """SourcePackageRecipe implements ISourcePackageRecipe."""159 """SourcePackageRecipe implements ISourcePackageRecipe."""
96 recipe = self.factory.makeSourcePackageRecipe()160 recipe = self.makeSourcePackageRecipe()
97 verifyObject(ISourcePackageRecipe, recipe)161 verifyObject(ISourcePackageRecipe, recipe)
98162
99 def test_avoids_problematic_snapshots(self):163 def test_avoids_problematic_snapshots(self):
@@ -103,7 +167,7 @@
103 'pending_builds',167 'pending_builds',
104 ]168 ]
105 self.assertThat(169 self.assertThat(
106 self.factory.makeSourcePackageRecipe(),170 self.makeSourcePackageRecipe(),
107 DoesNotSnapshot(problematic_properties, ISourcePackageRecipeView))171 DoesNotSnapshot(problematic_properties, ISourcePackageRecipeView))
108172
109 def makeRecipeComponents(self, branches=()):173 def makeRecipeComponents(self, branches=()):
@@ -128,7 +192,7 @@
128 components = self.makeRecipeComponents()192 components = self.makeRecipeComponents()
129 recipe = getUtility(ISourcePackageRecipeSource).new(**components)193 recipe = getUtility(ISourcePackageRecipeSource).new(**components)
130 transaction.commit()194 transaction.commit()
131 self.assertEquals(195 self.assertEqual(
132 (components['registrant'], components['owner'],196 (components['registrant'], components['owner'],
133 set(components['distroseries']), components['name']),197 set(components['distroseries']), components['name']),
134 (recipe.registrant, recipe.owner, set(recipe.distroseries),198 (recipe.registrant, recipe.owner, set(recipe.distroseries),
@@ -139,35 +203,38 @@
139 """An exception should be raised if the base branch is private."""203 """An exception should be raised if the base branch is private."""
140 owner = self.factory.makePerson()204 owner = self.factory.makePerson()
141 with person_logged_in(owner):205 with person_logged_in(owner):
142 branch = self.factory.makeAnyBranch(206 branch = self.makeBranch(
143 owner=owner, information_type=InformationType.USERDATA)207 owner=owner, information_type=InformationType.USERDATA)
144 components = self.makeRecipeComponents(branches=[branch])208 components = self.makeRecipeComponents(branches=[branch])
145 recipe_source = getUtility(ISourcePackageRecipeSource)209 recipe_source = getUtility(ISourcePackageRecipeSource)
146 e = self.assertRaises(210 e = self.assertRaises(
147 PrivateBranchRecipe, recipe_source.new, **components)211 self.private_error, recipe_source.new, **components)
148 self.assertEqual(212 self.assertEqual(
149 'Recipe may not refer to private branch: %s' %213 'Recipe may not refer to private %s: %s' %
150 branch.bzr_identity, str(e))214 (self.branch_type, self.getRepository(branch).identity),
215 str(e))
151216
152 def test_creation_private_referenced_branch(self):217 def test_creation_private_referenced_branch(self):
153 """An exception should be raised if a referenced branch is private."""218 """An exception should be raised if a referenced branch is private."""
154 owner = self.factory.makePerson()219 owner = self.factory.makePerson()
155 with person_logged_in(owner):220 with person_logged_in(owner):
156 base_branch = self.factory.makeAnyBranch(owner=owner)221 base_branch = self.makeBranch(owner=owner)
157 referenced_branch = self.factory.makeAnyBranch(222 referenced_branch = self.makeBranch(
158 owner=owner, information_type=InformationType.USERDATA)223 owner=owner, information_type=InformationType.USERDATA)
159 branches = [base_branch, referenced_branch]224 branches = [base_branch, referenced_branch]
160 components = self.makeRecipeComponents(branches=branches)225 components = self.makeRecipeComponents(branches=branches)
161 recipe_source = getUtility(ISourcePackageRecipeSource)226 recipe_source = getUtility(ISourcePackageRecipeSource)
162 e = self.assertRaises(227 e = self.assertRaises(
163 PrivateBranchRecipe, recipe_source.new, **components)228 self.private_error, recipe_source.new, **components)
164 self.assertEqual(229 self.assertEqual(
165 'Recipe may not refer to private branch: %s' %230 'Recipe may not refer to private %s: %s' % (
166 referenced_branch.bzr_identity, str(e))231 self.branch_type,
232 self.getRepository(referenced_branch).identity),
233 str(e))
167234
168 def test_exists(self):235 def test_exists(self):
169 # Test ISourcePackageRecipeSource.exists236 # Test ISourcePackageRecipeSource.exists
170 recipe = self.factory.makeSourcePackageRecipe()237 recipe = self.makeSourcePackageRecipe()
171238
172 self.assertTrue(239 self.assertTrue(
173 getUtility(ISourcePackageRecipeSource).exists(240 getUtility(ISourcePackageRecipeSource).exists(
@@ -185,32 +252,33 @@
185252
186 def test_recipe_implements_interface(self):253 def test_recipe_implements_interface(self):
187 # SourcePackageRecipe objects implement ISourcePackageRecipe.254 # SourcePackageRecipe objects implement ISourcePackageRecipe.
188 recipe = self.factory.makeSourcePackageRecipe()255 recipe = self.makeSourcePackageRecipe()
189 transaction.commit()256 transaction.commit()
190 with person_logged_in(recipe.owner):257 with person_logged_in(recipe.owner):
191 self.assertProvides(recipe, ISourcePackageRecipe)258 self.assertProvides(recipe, ISourcePackageRecipe)
192259
193 def test_base_branch(self):260 def test_base_branch(self):
194 # When a recipe is created, we can access its base branch.261 # When a recipe is created, we can access its base branch.
195 branch = self.factory.makeAnyBranch()262 branch = self.makeBranch()
196 sp_recipe = self.factory.makeSourcePackageRecipe(branches=[branch])263 sp_recipe = self.makeSourcePackageRecipe(branches=[branch])
197 transaction.commit()264 transaction.commit()
198 self.assertEquals(branch, sp_recipe.base_branch)265 self.assertEqual(self.getRepository(branch), sp_recipe.base)
199266
200 def test_branch_links_created(self):267 def test_branch_links_created(self):
201 # When a recipe is created, we can query it for links to the branch268 # When a recipe is created, we can query it for links to the branch
202 # it references.269 # it references.
203 branch = self.factory.makeAnyBranch()270 branch = self.makeBranch()
204 sp_recipe = self.factory.makeSourcePackageRecipe(271 sp_recipe = self.makeSourcePackageRecipe(branches=[branch])
205 branches=[branch])
206 transaction.commit()272 transaction.commit()
207 self.assertEquals([branch], list(sp_recipe.getReferencedBranches()))273 self.assertEqual(
274 [self.getRepository(branch)],
275 list(sp_recipe.getReferencedBranches()))
208276
209 def createSourcePackageRecipe(self, number_of_branches=2):277 def createSourcePackageRecipe(self, number_of_branches=2):
210 branches = []278 branches = []
211 for i in range(number_of_branches):279 for i in range(number_of_branches):
212 branches.append(self.factory.makeAnyBranch())280 branches.append(self.makeBranch())
213 sp_recipe = self.factory.makeSourcePackageRecipe(branches=branches)281 sp_recipe = self.makeSourcePackageRecipe(branches=branches)
214 transaction.commit()282 transaction.commit()
215 return sp_recipe, branches283 return sp_recipe, branches
216284
@@ -218,8 +286,8 @@
218 # If a recipe links to more than one branch, getReferencedBranches()286 # If a recipe links to more than one branch, getReferencedBranches()
219 # returns all of them.287 # returns all of them.
220 sp_recipe, [branch1, branch2] = self.createSourcePackageRecipe()288 sp_recipe, [branch1, branch2] = self.createSourcePackageRecipe()
221 self.assertEquals(289 self.assertEqual(
222 sorted([branch1, branch2]),290 sorted([self.getRepository(branch1), self.getRepository(branch2)]),
223 sorted(sp_recipe.getReferencedBranches()))291 sorted(sp_recipe.getReferencedBranches()))
224292
225 def test_preLoadReferencedBranches(self):293 def test_preLoadReferencedBranches(self):
@@ -230,16 +298,15 @@
230 referenced_branches = sp_recipe.getReferencedBranches()298 referenced_branches = sp_recipe.getReferencedBranches()
231 clear_property_cache(recipe_data)299 clear_property_cache(recipe_data)
232 SourcePackageRecipeData.preLoadReferencedBranches([recipe_data])300 SourcePackageRecipeData.preLoadReferencedBranches([recipe_data])
233 self.assertEquals(301 self.assertEqual(
234 sorted(referenced_branches),302 sorted(referenced_branches),
235 sorted(sp_recipe.getReferencedBranches()))303 sorted(sp_recipe.getReferencedBranches()))
236304
237 def test_random_user_cant_edit(self):305 def test_random_user_cant_edit(self):
238 # An arbitrary user can't set attributes.306 # An arbitrary user can't set attributes.
239 branch1 = self.factory.makeAnyBranch()307 branch1 = self.makeBranch()
240 recipe_1 = self.factory.makeRecipeText(branch1)308 recipe_1 = self.factory.makeRecipeText(branch1)
241 sp_recipe = self.factory.makeSourcePackageRecipe(309 sp_recipe = self.makeSourcePackageRecipe(recipe=recipe_1)
242 recipe=recipe_1)
243 login_person(self.factory.makePerson())310 login_person(self.factory.makePerson())
244 self.assertRaises(311 self.assertRaises(
245 Unauthorized, getattr, sp_recipe, 'setRecipeText')312 Unauthorized, getattr, sp_recipe, 'setRecipeText')
@@ -247,71 +314,78 @@
247 def test_set_recipe_text_resets_branch_references(self):314 def test_set_recipe_text_resets_branch_references(self):
248 # When the recipe_text is replaced, getReferencedBranches returns315 # When the recipe_text is replaced, getReferencedBranches returns
249 # (only) the branches referenced by the new recipe.316 # (only) the branches referenced by the new recipe.
250 branch1 = self.factory.makeAnyBranch()317 branch1 = self.makeBranch()
251 sp_recipe = self.factory.makeSourcePackageRecipe(318 sp_recipe = self.makeSourcePackageRecipe(branches=[branch1])
252 branches=[branch1])319 branch2 = self.makeBranch()
253 branch2 = self.factory.makeAnyBranch()
254 new_recipe = self.factory.makeRecipeText(branch2)320 new_recipe = self.factory.makeRecipeText(branch2)
255 with person_logged_in(sp_recipe.owner):321 with person_logged_in(sp_recipe.owner):
256 sp_recipe.setRecipeText(new_recipe)322 sp_recipe.setRecipeText(new_recipe)
257 self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))323 self.assertEqual(
324 [self.getRepository(branch2)],
325 list(sp_recipe.getReferencedBranches()))
258326
259 def test_rejects_run_command(self):327 def test_rejects_run_command(self):
260 recipe_text = '''\328 recipe_text = '''\
261 # bzr-builder format 0.3 deb-version 0.1-{revno}329 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
262 %(base)s330 %(base)s
263 run touch test331 run touch test
264 ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)332 ''' % dict(recipe_id=self.recipe_id,
333 base=self.getBranchRecipeText(self.makeBranch()))
265 recipe_text = textwrap.dedent(recipe_text)334 recipe_text = textwrap.dedent(recipe_text)
266 self.assertRaises(335 self.assertRaises(
267 ForbiddenInstructionError, self.factory.makeSourcePackageRecipe,336 ForbiddenInstructionError, self.makeSourcePackageRecipe,
268 recipe=recipe_text)337 recipe=recipe_text)
269338
270 def test_run_rejected_without_mangling_recipe(self):339 def test_run_rejected_without_mangling_recipe(self):
271 sp_recipe = self.factory.makeSourcePackageRecipe()340 sp_recipe = self.makeSourcePackageRecipe()
272 old_branches = list(sp_recipe.getReferencedBranches())341 old_branches = list(sp_recipe.getReferencedBranches())
273 recipe_text = '''\342 recipe_text = '''\
274 # bzr-builder format 0.3 deb-version 0.1-{revno}343 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
275 %(base)s344 %(base)s
276 run touch test345 run touch test
277 ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)346 ''' % dict(recipe_id=self.recipe_id,
347 base=self.getBranchRecipeText(self.makeBranch()))
278 recipe_text = textwrap.dedent(recipe_text)348 recipe_text = textwrap.dedent(recipe_text)
279 with person_logged_in(sp_recipe.owner):349 with person_logged_in(sp_recipe.owner):
280 self.assertRaises(350 self.assertRaises(
281 ForbiddenInstructionError, sp_recipe.setRecipeText,351 ForbiddenInstructionError, sp_recipe.setRecipeText,
282 recipe_text)352 recipe_text)
283 self.assertEquals(353 self.assertEqual(
284 old_branches, list(sp_recipe.getReferencedBranches()))354 old_branches, list(sp_recipe.getReferencedBranches()))
285355
286 def test_nest_part(self):356 def test_nest_part(self):
287 """nest-part instruction can be round-tripped."""357 """nest-part instruction can be round-tripped."""
288 base = self.factory.makeBranch()358 base = self.makeBranch()
289 nested = self.factory.makeBranch()359 nested = self.makeBranch()
290 recipe_text = (360 recipe_text = (
291 "# bzr-builder format 0.3 deb-version 1\n"361 "# %s format 0.3 deb-version 1\n"
292 "%s revid:base_revid\n"362 "%s revid:base_revid\n"
293 "nest-part nested1 %s foo bar tag:foo\n" %363 "nest-part nested1 %s foo bar tag:foo\n" %
294 (base.bzr_identity, nested.bzr_identity))364 (self.recipe_id,
295 recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)365 self.getRepository(base).identity,
366 self.getRepository(nested).identity))
367 recipe = self.makeSourcePackageRecipe(recipe=recipe_text)
296 self.assertEqual(recipe_text, recipe.recipe_text)368 self.assertEqual(recipe_text, recipe.recipe_text)
297369
298 def test_nest_part_no_target(self):370 def test_nest_part_no_target(self):
299 """nest-part instruction with no target-dir can be round-tripped."""371 """nest-part instruction with no target-dir can be round-tripped."""
300 base = self.factory.makeBranch()372 base = self.makeBranch()
301 nested = self.factory.makeBranch()373 nested = self.makeBranch()
302 recipe_text = (374 recipe_text = (
303 "# bzr-builder format 0.3 deb-version 1\n"375 "# %s format 0.3 deb-version 1\n"
304 "%s revid:base_revid\n"376 "%s revid:base_revid\n"
305 "nest-part nested1 %s foo\n" %377 "nest-part nested1 %s foo\n" %
306 (base.bzr_identity, nested.bzr_identity))378 (self.recipe_id,
307 recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)379 self.getRepository(base).identity,
380 self.getRepository(nested).identity))
381 recipe = self.makeSourcePackageRecipe(recipe=recipe_text)
308 self.assertEqual(recipe_text, recipe.recipe_text)382 self.assertEqual(recipe_text, recipe.recipe_text)
309383
310 def test_accept_format_0_3(self):384 def test_accept_format_0_3(self):
311 """Recipe format 0.3 is accepted."""385 """Recipe format 0.3 is accepted."""
312 builder_recipe = self.factory.makeRecipe()386 builder_recipe = self.factory.makeRecipe()
313 builder_recipe.format = 0.3387 builder_recipe.format = 0.3
314 self.factory.makeSourcePackageRecipe(recipe=str(builder_recipe))388 self.makeSourcePackageRecipe(recipe=str(builder_recipe))
315389
316 def test_reject_newer_formats(self):390 def test_reject_newer_formats(self):
317 with recipe_parser_newest_version(145.115):391 with recipe_parser_newest_version(145.115):
@@ -319,11 +393,11 @@
319 builder_recipe.format = 145.115393 builder_recipe.format = 145.115
320 self.assertRaises(394 self.assertRaises(
321 TooNewRecipeFormat,395 TooNewRecipeFormat,
322 self.factory.makeSourcePackageRecipe,396 self.makeSourcePackageRecipe,
323 recipe=str(builder_recipe))397 recipe=str(builder_recipe))
324398
325 def test_requestBuild(self):399 def test_requestBuild(self):
326 recipe = self.factory.makeSourcePackageRecipe()400 recipe = self.makeSourcePackageRecipe()
327 (distroseries,) = list(recipe.distroseries)401 (distroseries,) = list(recipe.distroseries)
328 ppa = self.factory.makeArchive()402 ppa = self.factory.makeArchive()
329 build = recipe.requestBuild(ppa, ppa.owner, distroseries,403 build = recipe.requestBuild(ppa, ppa.owner, distroseries,
@@ -342,17 +416,17 @@
342 removeSecurityProxy(build).build_farm_job_id).one()416 removeSecurityProxy(build).build_farm_job_id).one()
343 self.assertProvides(build_queue, IBuildQueue)417 self.assertProvides(build_queue, IBuildQueue)
344 self.assertTrue(build_queue.virtualized)418 self.assertTrue(build_queue.virtualized)
345 self.assertEquals(build_queue.status, BuildQueueStatus.WAITING)419 self.assertEqual(build_queue.status, BuildQueueStatus.WAITING)
346420
347 def test_requestBuildRejectsNotPPA(self):421 def test_requestBuildRejectsNotPPA(self):
348 recipe = self.factory.makeSourcePackageRecipe()422 recipe = self.makeSourcePackageRecipe()
349 not_ppa = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)423 not_ppa = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
350 (distroseries,) = list(recipe.distroseries)424 (distroseries,) = list(recipe.distroseries)
351 self.assertRaises(NonPPABuildRequest, recipe.requestBuild, not_ppa,425 self.assertRaises(NonPPABuildRequest, recipe.requestBuild, not_ppa,
352 not_ppa.owner, distroseries, PackagePublishingPocket.RELEASE)426 not_ppa.owner, distroseries, PackagePublishingPocket.RELEASE)
353427
354 def test_requestBuildRejectsNoPermission(self):428 def test_requestBuildRejectsNoPermission(self):
355 recipe = self.factory.makeSourcePackageRecipe()429 recipe = self.makeSourcePackageRecipe()
356 ppa = self.factory.makeArchive()430 ppa = self.factory.makeArchive()
357 requester = self.factory.makePerson()431 requester = self.factory.makePerson()
358 (distroseries,) = list(recipe.distroseries)432 (distroseries,) = list(recipe.distroseries)
@@ -360,14 +434,14 @@
360 requester, distroseries, PackagePublishingPocket.RELEASE)434 requester, distroseries, PackagePublishingPocket.RELEASE)
361435
362 def test_requestBuildRejectsInvalidPocket(self):436 def test_requestBuildRejectsInvalidPocket(self):
363 recipe = self.factory.makeSourcePackageRecipe()437 recipe = self.makeSourcePackageRecipe()
364 ppa = self.factory.makeArchive()438 ppa = self.factory.makeArchive()
365 (distroseries,) = list(recipe.distroseries)439 (distroseries,) = list(recipe.distroseries)
366 self.assertRaises(InvalidPocketForPPA, recipe.requestBuild, ppa,440 self.assertRaises(InvalidPocketForPPA, recipe.requestBuild, ppa,
367 ppa.owner, distroseries, PackagePublishingPocket.BACKPORTS)441 ppa.owner, distroseries, PackagePublishingPocket.BACKPORTS)
368442
369 def test_requestBuildRejectsDisabledArchive(self):443 def test_requestBuildRejectsDisabledArchive(self):
370 recipe = self.factory.makeSourcePackageRecipe()444 recipe = self.makeSourcePackageRecipe()
371 ppa = self.factory.makeArchive()445 ppa = self.factory.makeArchive()
372 removeSecurityProxy(ppa).disable()446 removeSecurityProxy(ppa).disable()
373 (distroseries,) = list(recipe.distroseries)447 (distroseries,) = list(recipe.distroseries)
@@ -377,7 +451,7 @@
377451
378 def test_requestBuildScore(self):452 def test_requestBuildScore(self):
379 """Normal build requests have a relatively low queue score (2505)."""453 """Normal build requests have a relatively low queue score (2505)."""
380 recipe = self.factory.makeSourcePackageRecipe()454 recipe = self.makeSourcePackageRecipe()
381 build = recipe.requestBuild(recipe.daily_build_archive,455 build = recipe.requestBuild(recipe.daily_build_archive,
382 recipe.owner, list(recipe.distroseries)[0],456 recipe.owner, list(recipe.distroseries)[0],
383 PackagePublishingPocket.RELEASE)457 PackagePublishingPocket.RELEASE)
@@ -387,7 +461,7 @@
387461
388 def test_requestBuildManualScore(self):462 def test_requestBuildManualScore(self):
389 """Manual build requests have a score equivalent to binary builds."""463 """Manual build requests have a score equivalent to binary builds."""
390 recipe = self.factory.makeSourcePackageRecipe()464 recipe = self.makeSourcePackageRecipe()
391 build = recipe.requestBuild(recipe.daily_build_archive,465 build = recipe.requestBuild(recipe.daily_build_archive,
392 recipe.owner, list(recipe.distroseries)[0],466 recipe.owner, list(recipe.distroseries)[0],
393 PackagePublishingPocket.RELEASE, manual=True)467 PackagePublishingPocket.RELEASE, manual=True)
@@ -397,7 +471,7 @@
397471
398 def test_requestBuild_relative_build_score(self):472 def test_requestBuild_relative_build_score(self):
399 """Offsets for archives are respected."""473 """Offsets for archives are respected."""
400 recipe = self.factory.makeSourcePackageRecipe()474 recipe = self.makeSourcePackageRecipe()
401 archive = recipe.daily_build_archive475 archive = recipe.daily_build_archive
402 removeSecurityProxy(archive).relative_build_score = 100476 removeSecurityProxy(archive).relative_build_score = 100
403 build = recipe.requestBuild(477 build = recipe.requestBuild(
@@ -409,7 +483,7 @@
409483
410 def test_requestBuildRejectRepeats(self):484 def test_requestBuildRejectRepeats(self):
411 """Reject build requests that are identical to pending builds."""485 """Reject build requests that are identical to pending builds."""
412 recipe = self.factory.makeSourcePackageRecipe()486 recipe = self.makeSourcePackageRecipe()
413 series = list(recipe.distroseries)[0]487 series = list(recipe.distroseries)[0]
414 archive = self.factory.makeArchive(owner=recipe.owner)488 archive = self.factory.makeArchive(owner=recipe.owner)
415 old_build = recipe.requestBuild(archive, recipe.owner, series,489 old_build = recipe.requestBuild(archive, recipe.owner, series,
@@ -447,7 +521,7 @@
447 private=True)521 private=True)
448522
449 # Create a recipe with the team P3A as the build destination.523 # Create a recipe with the team P3A as the build destination.
450 recipe = self.factory.makeSourcePackageRecipe()524 recipe = self.makeSourcePackageRecipe()
451525
452 # Add upload component rights for the non-team person.526 # Add upload component rights for the non-team person.
453 with person_logged_in(team_owner):527 with person_logged_in(team_owner):
@@ -467,13 +541,13 @@
467 def test_sourcepackagerecipe_description(self):541 def test_sourcepackagerecipe_description(self):
468 """Ensure that the SourcePackageRecipe has a proper description."""542 """Ensure that the SourcePackageRecipe has a proper description."""
469 description = u'The whoozits and whatzits.'543 description = u'The whoozits and whatzits.'
470 source_package_recipe = self.factory.makeSourcePackageRecipe(544 source_package_recipe = self.makeSourcePackageRecipe(
471 description=description)545 description=description)
472 self.assertEqual(description, source_package_recipe.description)546 self.assertEqual(description, source_package_recipe.description)
473547
474 def test_distroseries(self):548 def test_distroseries(self):
475 """Test that the distroseries behaves as a set."""549 """Test that the distroseries behaves as a set."""
476 recipe = self.factory.makeSourcePackageRecipe()550 recipe = self.makeSourcePackageRecipe()
477 distroseries = self.factory.makeDistroSeries()551 distroseries = self.factory.makeDistroSeries()
478 (old_distroseries,) = recipe.distroseries552 (old_distroseries,) = recipe.distroseries
479 recipe.distroseries.add(distroseries)553 recipe.distroseries.add(distroseries)
@@ -486,7 +560,7 @@
486560
487 def test_build_daily(self):561 def test_build_daily(self):
488 """Test that build_daily behaves as a bool."""562 """Test that build_daily behaves as a bool."""
489 recipe = self.factory.makeSourcePackageRecipe()563 recipe = self.makeSourcePackageRecipe()
490 self.assertFalse(recipe.build_daily)564 self.assertFalse(recipe.build_daily)
491 login_person(recipe.owner)565 login_person(recipe.owner)
492 recipe.build_daily = True566 recipe.build_daily = True
@@ -495,9 +569,9 @@
495 def test_view_public(self):569 def test_view_public(self):
496 """Anyone can view a recipe with public branches."""570 """Anyone can view a recipe with public branches."""
497 owner = self.factory.makePerson()571 owner = self.factory.makePerson()
498 branch = self.factory.makeAnyBranch(owner=owner)572 branch = self.makeBranch(owner=owner)
499 with person_logged_in(owner):573 with person_logged_in(owner):
500 recipe = self.factory.makeSourcePackageRecipe(branches=[branch])574 recipe = self.makeSourcePackageRecipe(branches=[branch])
501 self.assertTrue(check_permission('launchpad.View', recipe))575 self.assertTrue(check_permission('launchpad.View', recipe))
502 with person_logged_in(self.factory.makePerson()):576 with person_logged_in(self.factory.makePerson()):
503 self.assertTrue(check_permission('launchpad.View', recipe))577 self.assertTrue(check_permission('launchpad.View', recipe))
@@ -506,19 +580,18 @@
506 def test_view_private(self):580 def test_view_private(self):
507 """Recipes with private branches are restricted."""581 """Recipes with private branches are restricted."""
508 owner = self.factory.makePerson()582 owner = self.factory.makePerson()
509 branch = self.factory.makeAnyBranch(owner=owner)583 branch = self.makeBranch(owner=owner)
510 with person_logged_in(owner):584 with person_logged_in(owner):
511 recipe = self.factory.makeSourcePackageRecipe(branches=[branch])585 recipe = self.makeSourcePackageRecipe(branches=[branch])
512 self.assertTrue(check_permission('launchpad.View', recipe))586 self.assertTrue(check_permission('launchpad.View', recipe))
513 removeSecurityProxy(branch).information_type = (587 self.setInformationType(branch, InformationType.USERDATA)
514 InformationType.USERDATA)
515 with person_logged_in(self.factory.makePerson()):588 with person_logged_in(self.factory.makePerson()):
516 self.assertFalse(check_permission('launchpad.View', recipe))589 self.assertFalse(check_permission('launchpad.View', recipe))
517 self.assertFalse(check_permission('launchpad.View', recipe))590 self.assertFalse(check_permission('launchpad.View', recipe))
518591
519 def test_edit(self):592 def test_edit(self):
520 """Only the owner can edit a sourcepackagerecipe."""593 """Only the owner can edit a sourcepackagerecipe."""
521 recipe = self.factory.makeSourcePackageRecipe()594 recipe = self.makeSourcePackageRecipe()
522 self.assertFalse(check_permission('launchpad.Edit', recipe))595 self.assertFalse(check_permission('launchpad.Edit', recipe))
523 with person_logged_in(self.factory.makePerson()):596 with person_logged_in(self.factory.makePerson()):
524 self.assertFalse(check_permission('launchpad.Edit', recipe))597 self.assertFalse(check_permission('launchpad.Edit', recipe))
@@ -528,8 +601,8 @@
528 def test_destroySelf(self):601 def test_destroySelf(self):
529 """Should destroy associated builds, distroseries, etc."""602 """Should destroy associated builds, distroseries, etc."""
530 # Recipe should have at least one datainstruction.603 # Recipe should have at least one datainstruction.
531 branches = [self.factory.makeBranch() for count in range(2)]604 branches = [self.makeBranch() for count in range(2)]
532 recipe = self.factory.makeSourcePackageRecipe(branches=branches)605 recipe = self.makeSourcePackageRecipe(branches=branches)
533 pending_build = self.factory.makeSourcePackageRecipeBuild(606 pending_build = self.factory.makeSourcePackageRecipeBuild(
534 recipe=recipe)607 recipe=recipe)
535 pending_build.queueBuild()608 pending_build.queueBuild()
@@ -545,7 +618,7 @@
545 def test_destroySelf_preserves_release(self):618 def test_destroySelf_preserves_release(self):
546 # Destroying a sourcepackagerecipe removes references to its builds619 # Destroying a sourcepackagerecipe removes references to its builds
547 # from their releases.620 # from their releases.
548 recipe = self.factory.makeSourcePackageRecipe()621 recipe = self.makeSourcePackageRecipe()
549 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)622 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
550 release = self.factory.makeSourcePackageRelease(623 release = self.factory.makeSourcePackageRelease(
551 source_package_recipe_build=build)624 source_package_recipe_build=build)
@@ -557,7 +630,7 @@
557 def test_destroySelf_retains_build(self):630 def test_destroySelf_retains_build(self):
558 # Destroying a sourcepackagerecipe removes references to its builds631 # Destroying a sourcepackagerecipe removes references to its builds
559 # from their releases.632 # from their releases.
560 recipe = self.factory.makeSourcePackageRecipe()633 recipe = self.makeSourcePackageRecipe()
561 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)634 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
562 store = Store.of(build)635 store = Store.of(build)
563 store.flush()636 store.flush()
@@ -578,12 +651,11 @@
578651
579 def test_findStaleDailyBuilds(self):652 def test_findStaleDailyBuilds(self):
580 # Stale recipe not built daily.653 # Stale recipe not built daily.
581 self.factory.makeSourcePackageRecipe()654 self.makeSourcePackageRecipe()
582 # Daily build recipe not stale.655 # Daily build recipe not stale.
583 self.factory.makeSourcePackageRecipe(656 self.makeSourcePackageRecipe(build_daily=True, is_stale=False)
584 build_daily=True, is_stale=False)
585 # Stale daily build.657 # Stale daily build.
586 stale_daily = self.factory.makeSourcePackageRecipe(658 stale_daily = self.makeSourcePackageRecipe(
587 build_daily=True, is_stale=True)659 build_daily=True, is_stale=True)
588 self.assertContentEqual([stale_daily],660 self.assertContentEqual([stale_daily],
589 SourcePackageRecipe.findStaleDailyBuilds())661 SourcePackageRecipe.findStaleDailyBuilds())
@@ -591,8 +663,7 @@
591 def test_findStaleDailyBuildsDistinct(self):663 def test_findStaleDailyBuildsDistinct(self):
592 # If a recipe has 2 builds due to 2 distroseries, it only returns664 # If a recipe has 2 builds due to 2 distroseries, it only returns
593 # one recipe.665 # one recipe.
594 recipe = self.factory.makeSourcePackageRecipe(666 recipe = self.makeSourcePackageRecipe(build_daily=True, is_stale=True)
595 build_daily=True, is_stale=True)
596 hoary = self.factory.makeSourcePackageRecipeDistroseries("hoary")667 hoary = self.factory.makeSourcePackageRecipeDistroseries("hoary")
597 recipe.distroseries.add(hoary)668 recipe.distroseries.add(hoary)
598 for series in recipe.distroseries:669 for series in recipe.distroseries:
@@ -613,7 +684,7 @@
613 build.updateStatus(684 build.updateStatus(
614 BuildStatus.FULLYBUILT,685 BuildStatus.FULLYBUILT,
615 date_finished=build.date_started + duration)686 date_finished=build.date_started + duration)
616 recipe = removeSecurityProxy(self.factory.makeSourcePackageRecipe())687 recipe = removeSecurityProxy(self.makeSourcePackageRecipe())
617 self.assertIs(None, recipe.getMedianBuildDuration())688 self.assertIs(None, recipe.getMedianBuildDuration())
618 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)689 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
619 set_duration(build, 10)690 set_duration(build, 10)
@@ -632,7 +703,7 @@
632703
633 def test_getBuilds(self):704 def test_getBuilds(self):
634 # Test the various getBuilds methods.705 # Test the various getBuilds methods.
635 recipe = self.factory.makeSourcePackageRecipe()706 recipe = self.makeSourcePackageRecipe()
636 builds = [707 builds = [
637 self.factory.makeSourcePackageRecipeBuild(recipe=recipe)708 self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
638 for x in range(3)]709 for x in range(3)]
@@ -654,7 +725,7 @@
654 person = self.factory.makePerson()725 person = self.factory.makePerson()
655 archives = [self.factory.makeArchive(owner=person) for x in range(4)]726 archives = [self.factory.makeArchive(owner=person) for x in range(4)]
656 distroseries = self.factory.makeSourcePackageRecipeDistroseries()727 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
657 recipe = self.factory.makeSourcePackageRecipe()728 recipe = self.makeSourcePackageRecipe()
658729
659 build_info = []730 build_info = []
660 for archive in archives:731 for archive in archives:
@@ -666,7 +737,7 @@
666737
667 def test_getBuilds_cancelled(self):738 def test_getBuilds_cancelled(self):
668 # Cancelled builds are not considered pending.739 # Cancelled builds are not considered pending.
669 recipe = self.factory.makeSourcePackageRecipe()740 recipe = self.makeSourcePackageRecipe()
670 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)741 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
671 with admin_logged_in():742 with admin_logged_in():
672 build.queueBuild()743 build.queueBuild()
@@ -676,40 +747,42 @@
676 self.assertEqual([], list(recipe.pending_builds))747 self.assertEqual([], list(recipe.pending_builds))
677748
678 def test_setRecipeText_private_base_branch(self):749 def test_setRecipeText_private_base_branch(self):
679 source_package_recipe = self.factory.makeSourcePackageRecipe()750 source_package_recipe = self.makeSourcePackageRecipe()
680 with person_logged_in(source_package_recipe.owner):751 with person_logged_in(source_package_recipe.owner):
681 branch = self.factory.makeAnyBranch(752 branch = self.makeBranch(
682 owner=source_package_recipe.owner,753 owner=source_package_recipe.owner,
683 information_type=InformationType.USERDATA)754 information_type=InformationType.USERDATA)
684 recipe_text = self.factory.makeRecipeText(branch)755 recipe_text = self.factory.makeRecipeText(branch)
685 e = self.assertRaises(756 e = self.assertRaises(
686 PrivateBranchRecipe, source_package_recipe.setRecipeText,757 self.private_error, source_package_recipe.setRecipeText,
687 recipe_text)758 recipe_text)
688 self.assertEqual(759 self.assertEqual(
689 'Recipe may not refer to private branch: %s' %760 'Recipe may not refer to private %s: %s' %
690 branch.bzr_identity, str(e))761 (self.branch_type, self.getRepository(branch).identity),
762 str(e))
691763
692 def test_setRecipeText_private_referenced_branch(self):764 def test_setRecipeText_private_referenced_branch(self):
693 source_package_recipe = self.factory.makeSourcePackageRecipe()765 source_package_recipe = self.makeSourcePackageRecipe()
694 with person_logged_in(source_package_recipe.owner):766 with person_logged_in(source_package_recipe.owner):
695 base_branch = self.factory.makeAnyBranch(767 base_branch = self.makeBranch(owner=source_package_recipe.owner)
696 owner=source_package_recipe.owner)768 referenced_branch = self.makeBranch(
697 referenced_branch = self.factory.makeAnyBranch(
698 owner=source_package_recipe.owner,769 owner=source_package_recipe.owner,
699 information_type=InformationType.USERDATA)770 information_type=InformationType.USERDATA)
700 recipe_text = self.factory.makeRecipeText(771 recipe_text = self.factory.makeRecipeText(
701 base_branch, referenced_branch)772 base_branch, referenced_branch)
702 e = self.assertRaises(773 e = self.assertRaises(
703 PrivateBranchRecipe, source_package_recipe.setRecipeText,774 self.private_error, source_package_recipe.setRecipeText,
704 recipe_text)775 recipe_text)
705 self.assertEqual(776 self.assertEqual(
706 'Recipe may not refer to private branch: %s' %777 'Recipe may not refer to private %s: %s' %
707 referenced_branch.bzr_identity, str(e))778 (self.branch_type,
779 self.getRepository(referenced_branch).identity),
780 str(e))
708781
709 def test_getBuilds_ignores_disabled_archive(self):782 def test_getBuilds_ignores_disabled_archive(self):
710 # Builds into a disabled archive aren't returned.783 # Builds into a disabled archive aren't returned.
711 archive = self.factory.makeArchive()784 archive = self.factory.makeArchive()
712 recipe = self.factory.makeSourcePackageRecipe()785 recipe = self.makeSourcePackageRecipe()
713 self.factory.makeSourcePackageRecipeBuild(786 self.factory.makeSourcePackageRecipeBuild(
714 recipe=recipe, archive=archive)787 recipe=recipe, archive=archive)
715 with person_logged_in(archive.owner):788 with person_logged_in(archive.owner):
@@ -719,19 +792,19 @@
719 self.assertEqual([], list(recipe.pending_builds))792 self.assertEqual([], list(recipe.pending_builds))
720793
721 def test_containsUnbuildableSeries(self):794 def test_containsUnbuildableSeries(self):
722 recipe = self.factory.makeSourcePackageRecipe()795 recipe = self.makeSourcePackageRecipe()
723 self.assertFalse(recipe.containsUnbuildableSeries(796 self.assertFalse(recipe.containsUnbuildableSeries(
724 recipe.daily_build_archive))797 recipe.daily_build_archive))
725798
726 def test_containsUnbuildableSeries_with_obsolete_series(self):799 def test_containsUnbuildableSeries_with_obsolete_series(self):
727 recipe = self.factory.makeSourcePackageRecipe()800 recipe = self.makeSourcePackageRecipe()
728 warty = self.factory.makeSourcePackageRecipeDistroseries()801 warty = self.factory.makeSourcePackageRecipeDistroseries()
729 removeSecurityProxy(warty).status = SeriesStatus.OBSOLETE802 removeSecurityProxy(warty).status = SeriesStatus.OBSOLETE
730 self.assertTrue(recipe.containsUnbuildableSeries(803 self.assertTrue(recipe.containsUnbuildableSeries(
731 recipe.daily_build_archive))804 recipe.daily_build_archive))
732805
733 def test_performDailyBuild_filters_obsolete_series(self):806 def test_performDailyBuild_filters_obsolete_series(self):
734 recipe = self.factory.makeSourcePackageRecipe()807 recipe = self.makeSourcePackageRecipe()
735 warty = self.factory.makeSourcePackageRecipeDistroseries()808 warty = self.factory.makeSourcePackageRecipeDistroseries()
736 hoary = self.factory.makeSourcePackageRecipeDistroseries(name='hoary')809 hoary = self.factory.makeSourcePackageRecipeDistroseries(name='hoary')
737 with person_logged_in(recipe.owner):810 with person_logged_in(recipe.owner):
@@ -741,19 +814,30 @@
741 self.assertEqual([build.recipe for build in builds], [recipe])814 self.assertEqual([build.recipe for build in builds], [recipe])
742815
743816
744class TestRecipeBranchRoundTripping(TestCaseWithFactory):817class TestSourcePackageRecipeBzr(
818 TestSourcePackageRecipeMixin, BzrMixin, TestCaseWithFactory):
819 """Test `SourcePackageRecipe` objects for Bazaar."""
820
821
822class TestSourcePackageRecipeGit(
823 TestSourcePackageRecipeMixin, GitMixin, TestCaseWithFactory):
824 """Test `SourcePackageRecipe` objects for Git."""
825
826
827class TestRecipeBranchRoundTrippingMixin:
745828
746 layer = DatabaseFunctionalLayer829 layer = DatabaseFunctionalLayer
747830
748 def setUp(self):831 def setUp(self):
749 super(TestRecipeBranchRoundTripping, self).setUp()832 super(TestRecipeBranchRoundTrippingMixin, self).setUp()
750 self.base_branch = self.factory.makeAnyBranch()833 self.base_branch = self.makeBranch()
751 self.nested_branch = self.factory.makeAnyBranch()834 self.nested_branch = self.makeBranch()
752 self.merged_branch = self.factory.makeAnyBranch()835 self.merged_branch = self.makeBranch()
753 self.branch_identities = {836 self.branch_identities = {
754 'base': self.base_branch.bzr_identity,837 'recipe_id': self.recipe_id,
755 'nested': self.nested_branch.bzr_identity,838 'base': self.getRepository(self.base_branch).identity,
756 'merged': self.merged_branch.bzr_identity,839 'nested': self.getRepository(self.nested_branch).identity,
840 'merged': self.getRepository(self.merged_branch).identity,
757 }841 }
758842
759 def get_recipe(self, recipe_text):843 def get_recipe(self, recipe_text):
@@ -785,161 +869,173 @@
785869
786 def test_builds_simplest_recipe(self):870 def test_builds_simplest_recipe(self):
787 recipe_text = '''\871 recipe_text = '''\
788 # bzr-builder format 0.3 deb-version 0.1-{revno}872 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
789 %(base)s873 %(base)s
790 ''' % self.branch_identities874 ''' % self.branch_identities
791 base_branch = self.get_recipe(recipe_text).builder_recipe875 base_branch = self.get_recipe(recipe_text).builder_recipe
792 self.check_base_recipe_branch(876 self.check_base_recipe_branch(
793 base_branch, self.base_branch.bzr_identity,877 base_branch, self.getRepository(self.base_branch).identity,
794 deb_version='0.1-{revno}')878 deb_version='0.1-{revno}')
795879
796 def test_builds_recipe_with_merge(self):880 def test_builds_recipe_with_merge(self):
797 recipe_text = '''\881 recipe_text = '''\
798 # bzr-builder format 0.3 deb-version 0.1-{revno}882 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
799 %(base)s883 %(base)s
800 merge bar %(merged)s884 merge bar %(merged)s
801 ''' % self.branch_identities885 ''' % self.branch_identities
802 base_branch = self.get_recipe(recipe_text).builder_recipe886 base_branch = self.get_recipe(recipe_text).builder_recipe
803 self.check_base_recipe_branch(887 self.check_base_recipe_branch(
804 base_branch, self.base_branch.bzr_identity, num_child_branches=1,888 base_branch, self.getRepository(self.base_branch).identity,
805 deb_version='0.1-{revno}')889 num_child_branches=1, deb_version='0.1-{revno}')
806 child_branch, location = base_branch.child_branches[0].as_tuple()890 child_branch, location = base_branch.child_branches[0].as_tuple()
807 self.assertEqual(None, location)891 self.assertEqual(None, location)
808 self.check_recipe_branch(892 self.check_recipe_branch(
809 child_branch, "bar", self.merged_branch.bzr_identity)893 child_branch, "bar",
894 self.getRepository(self.merged_branch).identity)
810895
811 def test_builds_recipe_with_nest(self):896 def test_builds_recipe_with_nest(self):
812 recipe_text = '''\897 recipe_text = '''\
813 # bzr-builder format 0.3 deb-version 0.1-{revno}898 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
814 %(base)s899 %(base)s
815 nest bar %(nested)s baz900 nest bar %(nested)s baz
816 ''' % self.branch_identities901 ''' % self.branch_identities
817 base_branch = self.get_recipe(recipe_text).builder_recipe902 base_branch = self.get_recipe(recipe_text).builder_recipe
818 self.check_base_recipe_branch(903 self.check_base_recipe_branch(
819 base_branch, self.base_branch.bzr_identity, num_child_branches=1,904 base_branch, self.getRepository(self.base_branch).identity,
820 deb_version='0.1-{revno}')905 num_child_branches=1, deb_version='0.1-{revno}')
821 child_branch, location = base_branch.child_branches[0].as_tuple()906 child_branch, location = base_branch.child_branches[0].as_tuple()
822 self.assertEqual("baz", location)907 self.assertEqual("baz", location)
823 self.check_recipe_branch(908 self.check_recipe_branch(
824 child_branch, "bar", self.nested_branch.bzr_identity)909 child_branch, "bar",
910 self.getRepository(self.nested_branch).identity)
825911
826 def test_builds_recipe_with_nest_then_merge(self):912 def test_builds_recipe_with_nest_then_merge(self):
827 recipe_text = '''\913 recipe_text = '''\
828 # bzr-builder format 0.3 deb-version 0.1-{revno}914 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
829 %(base)s915 %(base)s
830 nest bar %(nested)s baz916 nest bar %(nested)s baz
831 merge zam %(merged)s917 merge zam %(merged)s
832 ''' % self.branch_identities918 ''' % self.branch_identities
833 base_branch = self.get_recipe(recipe_text).builder_recipe919 base_branch = self.get_recipe(recipe_text).builder_recipe
834 self.check_base_recipe_branch(920 self.check_base_recipe_branch(
835 base_branch, self.base_branch.bzr_identity, num_child_branches=2,921 base_branch, self.getRepository(self.base_branch).identity,
836 deb_version='0.1-{revno}')922 num_child_branches=2, deb_version='0.1-{revno}')
837 child_branch, location = base_branch.child_branches[0].as_tuple()923 child_branch, location = base_branch.child_branches[0].as_tuple()
838 self.assertEqual("baz", location)924 self.assertEqual("baz", location)
839 self.check_recipe_branch(925 self.check_recipe_branch(
840 child_branch, "bar", self.nested_branch.bzr_identity)926 child_branch, "bar",
927 self.getRepository(self.nested_branch).identity)
841 child_branch, location = base_branch.child_branches[1].as_tuple()928 child_branch, location = base_branch.child_branches[1].as_tuple()
842 self.assertEqual(None, location)929 self.assertEqual(None, location)
843 self.check_recipe_branch(930 self.check_recipe_branch(
844 child_branch, "zam", self.merged_branch.bzr_identity)931 child_branch, "zam",
932 self.getRepository(self.merged_branch).identity)
845933
846 def test_builds_recipe_with_merge_then_nest(self):934 def test_builds_recipe_with_merge_then_nest(self):
847 recipe_text = '''\935 recipe_text = '''\
848 # bzr-builder format 0.3 deb-version 0.1-{revno}936 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
849 %(base)s937 %(base)s
850 merge zam %(merged)s938 merge zam %(merged)s
851 nest bar %(nested)s baz939 nest bar %(nested)s baz
852 ''' % self.branch_identities940 ''' % self.branch_identities
853 base_branch = self.get_recipe(recipe_text).builder_recipe941 base_branch = self.get_recipe(recipe_text).builder_recipe
854 self.check_base_recipe_branch(942 self.check_base_recipe_branch(
855 base_branch, self.base_branch.bzr_identity, num_child_branches=2,943 base_branch, self.getRepository(self.base_branch).identity,
856 deb_version='0.1-{revno}')944 num_child_branches=2, deb_version='0.1-{revno}')
857 child_branch, location = base_branch.child_branches[0].as_tuple()945 child_branch, location = base_branch.child_branches[0].as_tuple()
858 self.assertEqual(None, location)946 self.assertEqual(None, location)
859 self.check_recipe_branch(947 self.check_recipe_branch(
860 child_branch, "zam", self.merged_branch.bzr_identity)948 child_branch, "zam",
949 self.getRepository(self.merged_branch).identity)
861 child_branch, location = base_branch.child_branches[1].as_tuple()950 child_branch, location = base_branch.child_branches[1].as_tuple()
862 self.assertEqual("baz", location)951 self.assertEqual("baz", location)
863 self.check_recipe_branch(952 self.check_recipe_branch(
864 child_branch, "bar", self.nested_branch.bzr_identity)953 child_branch, "bar",
954 self.getRepository(self.nested_branch).identity)
865955
866 def test_builds_a_merge_in_to_a_nest(self):956 def test_builds_a_merge_in_to_a_nest(self):
867 recipe_text = '''\957 recipe_text = '''\
868 # bzr-builder format 0.3 deb-version 0.1-{revno}958 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
869 %(base)s959 %(base)s
870 nest bar %(nested)s baz960 nest bar %(nested)s baz
871 merge zam %(merged)s961 merge zam %(merged)s
872 ''' % self.branch_identities962 ''' % self.branch_identities
873 base_branch = self.get_recipe(recipe_text).builder_recipe963 base_branch = self.get_recipe(recipe_text).builder_recipe
874 self.check_base_recipe_branch(964 self.check_base_recipe_branch(
875 base_branch, self.base_branch.bzr_identity, num_child_branches=1,965 base_branch, self.getRepository(self.base_branch).identity,
876 deb_version='0.1-{revno}')966 num_child_branches=1, deb_version='0.1-{revno}')
877 child_branch, location = base_branch.child_branches[0].as_tuple()967 child_branch, location = base_branch.child_branches[0].as_tuple()
878 self.assertEqual("baz", location)968 self.assertEqual("baz", location)
879 self.check_recipe_branch(969 self.check_recipe_branch(
880 child_branch, "bar", self.nested_branch.bzr_identity,970 child_branch, "bar",
971 self.getRepository(self.nested_branch).identity,
881 num_child_branches=1)972 num_child_branches=1)
882 child_branch, location = child_branch.child_branches[0].as_tuple()973 child_branch, location = child_branch.child_branches[0].as_tuple()
883 self.assertEqual(None, location)974 self.assertEqual(None, location)
884 self.check_recipe_branch(975 self.check_recipe_branch(
885 child_branch, "zam", self.merged_branch.bzr_identity)976 child_branch, "zam",
977 self.getRepository(self.merged_branch).identity)
886978
887 def tests_builds_nest_into_a_nest(self):979 def tests_builds_nest_into_a_nest(self):
888 nested2 = self.factory.makeAnyBranch()980 nested2 = self.makeBranch()
889 self.branch_identities['nested2'] = nested2.bzr_identity981 self.branch_identities['nested2'] = (
982 self.getRepository(nested2).identity)
890 recipe_text = '''\983 recipe_text = '''\
891 # bzr-builder format 0.3 deb-version 0.1-{revno}984 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
892 %(base)s985 %(base)s
893 nest bar %(nested)s baz986 nest bar %(nested)s baz
894 nest zam %(nested2)s zoo987 nest zam %(nested2)s zoo
895 ''' % self.branch_identities988 ''' % self.branch_identities
896 base_branch = self.get_recipe(recipe_text).builder_recipe989 base_branch = self.get_recipe(recipe_text).builder_recipe
897 self.check_base_recipe_branch(990 self.check_base_recipe_branch(
898 base_branch, self.base_branch.bzr_identity, num_child_branches=1,991 base_branch, self.getRepository(self.base_branch).identity,
899 deb_version='0.1-{revno}')992 num_child_branches=1, deb_version='0.1-{revno}')
900 child_branch, location = base_branch.child_branches[0].as_tuple()993 child_branch, location = base_branch.child_branches[0].as_tuple()
901 self.assertEqual("baz", location)994 self.assertEqual("baz", location)
902 self.check_recipe_branch(995 self.check_recipe_branch(
903 child_branch, "bar", self.nested_branch.bzr_identity,996 child_branch, "bar",
997 self.getRepository(self.nested_branch).identity,
904 num_child_branches=1)998 num_child_branches=1)
905 child_branch, location = child_branch.child_branches[0].as_tuple()999 child_branch, location = child_branch.child_branches[0].as_tuple()
906 self.assertEqual("zoo", location)1000 self.assertEqual("zoo", location)
907 self.check_recipe_branch(child_branch, "zam", nested2.bzr_identity)1001 self.check_recipe_branch(
1002 child_branch, "zam", self.getRepository(nested2).identity)
9081003
909 def tests_builds_recipe_with_revspecs(self):1004 def tests_builds_recipe_with_revspecs(self):
910 recipe_text = '''\1005 recipe_text = '''\
911 # bzr-builder format 0.3 deb-version 0.1-{revno}1006 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
912 %(base)s revid:a1007 %(base)s revid:a
913 nest bar %(nested)s baz tag:b1008 nest bar %(nested)s baz tag:b
914 merge zam %(merged)s 21009 merge zam %(merged)s 2
915 ''' % self.branch_identities1010 ''' % self.branch_identities
916 base_branch = self.get_recipe(recipe_text).builder_recipe1011 base_branch = self.get_recipe(recipe_text).builder_recipe
917 self.check_base_recipe_branch(1012 self.check_base_recipe_branch(
918 base_branch, self.base_branch.bzr_identity, num_child_branches=2,1013 base_branch, self.getRepository(self.base_branch).identity,
919 revspec="revid:a", deb_version='0.1-{revno}')1014 num_child_branches=2, revspec="revid:a", deb_version='0.1-{revno}')
920 instruction = base_branch.child_branches[0]1015 instruction = base_branch.child_branches[0]
921 child_branch = instruction.recipe_branch1016 child_branch = instruction.recipe_branch
922 location = instruction.nest_path1017 location = instruction.nest_path
923 self.assertEqual("baz", location)1018 self.assertEqual("baz", location)
924 self.check_recipe_branch(1019 self.check_recipe_branch(
925 child_branch, "bar", self.nested_branch.bzr_identity,1020 child_branch, "bar",
926 revspec="tag:b")1021 self.getRepository(self.nested_branch).identity, revspec="tag:b")
927 child_branch, location = base_branch.child_branches[1].as_tuple()1022 child_branch, location = base_branch.child_branches[1].as_tuple()
928 self.assertEqual(None, location)1023 self.assertEqual(None, location)
929 self.check_recipe_branch(1024 self.check_recipe_branch(
930 child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")1025 child_branch, "zam",
1026 self.getRepository(self.merged_branch).identity, revspec="2")
9311027
932 def test_unsets_revspecs(self):1028 def test_unsets_revspecs(self):
933 # Changing a recipe's text to no longer include revspecs unsets1029 # Changing a recipe's text to no longer include revspecs unsets
934 # them from the stored copy.1030 # them from the stored copy.
935 revspec_text = '''\1031 revspec_text = '''\
936 # bzr-builder format 0.3 deb-version 0.1-{revno}1032 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
937 %(base)s revid:a1033 %(base)s revid:a
938 nest bar %(nested)s baz tag:b1034 nest bar %(nested)s baz tag:b
939 merge zam %(merged)s 21035 merge zam %(merged)s 2
940 ''' % self.branch_identities1036 ''' % self.branch_identities
941 no_revspec_text = '''\1037 no_revspec_text = '''\
942 # bzr-builder format 0.3 deb-version 0.1-{revno}1038 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
943 %(base)s1039 %(base)s
944 nest bar %(nested)s baz1040 nest bar %(nested)s baz
945 merge zam %(merged)s1041 merge zam %(merged)s
@@ -952,18 +1048,29 @@
9521048
953 def test_builds_recipe_without_debversion(self):1049 def test_builds_recipe_without_debversion(self):
954 recipe_text = '''\1050 recipe_text = '''\
955 # bzr-builder format 0.41051 # %(recipe_id)s format 0.4
956 %(base)s1052 %(base)s
957 nest bar %(nested)s baz1053 nest bar %(nested)s baz
958 ''' % self.branch_identities1054 ''' % self.branch_identities
959 base_branch = self.get_recipe(recipe_text).builder_recipe1055 base_branch = self.get_recipe(recipe_text).builder_recipe
960 self.check_base_recipe_branch(1056 self.check_base_recipe_branch(
961 base_branch, self.base_branch.bzr_identity, num_child_branches=1,1057 base_branch, self.getRepository(self.base_branch).identity,
962 deb_version=None)1058 num_child_branches=1, deb_version=None)
963 child_branch, location = base_branch.child_branches[0].as_tuple()1059 child_branch, location = base_branch.child_branches[0].as_tuple()
964 self.assertEqual("baz", location)1060 self.assertEqual("baz", location)
965 self.check_recipe_branch(1061 self.check_recipe_branch(
966 child_branch, "bar", self.nested_branch.bzr_identity)1062 child_branch, "bar",
1063 self.getRepository(self.nested_branch).identity)
1064
1065
1066class TestRecipeBranchRoundTrippingBzr(
1067 TestRecipeBranchRoundTrippingMixin, BzrMixin, TestCaseWithFactory):
1068 pass
1069
1070
1071class TestRecipeBranchRoundTrippingGit(
1072 TestRecipeBranchRoundTrippingMixin, GitMixin, TestCaseWithFactory):
1073 pass
9671074
9681075
969class RecipeDateLastModified(TestCaseWithFactory):1076class RecipeDateLastModified(TestCaseWithFactory):
9701077
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2015-10-19 10:56:16 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2016-01-12 17:27:35 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010-2013 Canonical Ltd. This software is licensed under the1# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for source package builds."""4"""Tests for source package builds."""
@@ -106,11 +106,11 @@
106 self.assertEqual(bq, spb.buildqueue_record)106 self.assertEqual(bq, spb.buildqueue_record)
107107
108 def test_title(self):108 def test_title(self):
109 # A recipe build's title currently consists of the base109 # A recipe build's title currently consists of the base source
110 # branch's unique name.110 # location's unique name.
111 spb = self.makeSourcePackageRecipeBuild()111 spb = self.makeSourcePackageRecipeBuild()
112 title = "%s recipe build in %s %s" % (112 title = "%s recipe build in %s %s" % (
113 spb.recipe.base_branch.unique_name, spb.distribution.name,113 spb.recipe.base.unique_name, spb.distribution.name,
114 spb.distroseries.name)114 spb.distroseries.name)
115 self.assertEqual(spb.title, title)115 self.assertEqual(spb.title, title)
116116
117117
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2016-01-11 21:11:27 +0000
+++ lib/lp/testing/factory.py 2016-01-12 17:27:35 +0000
@@ -2,7 +2,7 @@
2# NOTE: The first line above must stay first; do not move the copyright2# NOTE: The first line above must stay first; do not move the copyright
3# notice to the top. See http://www.python.org/dev/peps/pep-0263/.3# notice to the top. See http://www.python.org/dev/peps/pep-0263/.
4#4#
5# Copyright 2009-2015 Canonical Ltd. This software is licensed under the5# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
6# GNU Affero General Public License version 3 (see the file LICENSE).6# GNU Affero General Public License version 3 (see the file LICENSE).
77
8"""Testing infrastructure for the Launchpad application.8"""Testing infrastructure for the Launchpad application.
@@ -112,6 +112,7 @@
112 RevisionControlSystems,112 RevisionControlSystems,
113 )113 )
114from lp.code.errors import UnknownBranchTypeError114from lp.code.errors import UnknownBranchTypeError
115from lp.code.interfaces.branch import IBranch
115from lp.code.interfaces.branchnamespace import get_branch_namespace116from lp.code.interfaces.branchnamespace import get_branch_namespace
116from lp.code.interfaces.branchtarget import IBranchTarget117from lp.code.interfaces.branchtarget import IBranchTarget
117from lp.code.interfaces.codeimport import ICodeImportSet118from lp.code.interfaces.codeimport import ICodeImportSet
@@ -119,11 +120,13 @@
119from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet120from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
120from lp.code.interfaces.codeimportresult import ICodeImportResultSet121from lp.code.interfaces.codeimportresult import ICodeImportResultSet
121from lp.code.interfaces.gitnamespace import get_git_namespace122from lp.code.interfaces.gitnamespace import get_git_namespace
123from lp.code.interfaces.gitref import IGitRef
122from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch124from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
123from lp.code.interfaces.revision import IRevisionSet125from lp.code.interfaces.revision import IRevisionSet
124from lp.code.interfaces.sourcepackagerecipe import (126from lp.code.interfaces.sourcepackagerecipe import (
125 ISourcePackageRecipeSource,127 ISourcePackageRecipeSource,
126 MINIMAL_RECIPE_TEXT_BZR,128 MINIMAL_RECIPE_TEXT_BZR,
129 MINIMAL_RECIPE_TEXT_GIT,
127 )130 )
128from lp.code.interfaces.sourcepackagerecipebuild import (131from lp.code.interfaces.sourcepackagerecipebuild import (
129 ISourcePackageRecipeBuildSource,132 ISourcePackageRecipeBuildSource,
@@ -2897,9 +2900,22 @@
2897 branches = (self.makeAnyBranch(), )2900 branches = (self.makeAnyBranch(), )
2898 base_branch = branches[0]2901 base_branch = branches[0]
2899 other_branches = branches[1:]2902 other_branches = branches[1:]
2900 text = MINIMAL_RECIPE_TEXT_BZR % base_branch.bzr_identity2903 if IBranch.providedBy(base_branch):
2904 text = MINIMAL_RECIPE_TEXT_BZR % base_branch.identity
2905 elif IGitRef.providedBy(base_branch):
2906 text = MINIMAL_RECIPE_TEXT_GIT % (
2907 base_branch.repository.identity, base_branch.name)
2908 else:
2909 raise AssertionError(
2910 "Unsupported base_branch: %r" % (base_branch,))
2901 for i, branch in enumerate(other_branches):2911 for i, branch in enumerate(other_branches):
2902 text += 'merge dummy-%s %s\n' % (i, branch.bzr_identity)2912 if IBranch.providedBy(branch):
2913 text += 'merge dummy-%s %s\n' % (i, branch.identity)
2914 elif IGitRef.providedBy(branch):
2915 text += 'merge dummy-%s %s %s\n' % (
2916 i, branch.repository.identity, branch.name)
2917 else:
2918 raise AssertionError("Unsupported branch: %r" % (branch,))
2903 return text2919 return text
29042920
2905 def makeRecipe(self, *branches):2921 def makeRecipe(self, *branches):