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

Proposed by Colin Watson on 2016-01-12
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 2016-01-12 Approve on 2016-01-12
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.
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/errors.py'
2--- lib/lp/code/errors.py 2015-06-13 01:45:19 +0000
3+++ lib/lp/code/errors.py 2016-01-12 17:27:35 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Errors used in the lp/code modules."""
10@@ -46,6 +46,7 @@
11 'NoSuchGitReference',
12 'NoSuchGitRepository',
13 'PrivateBranchRecipe',
14+ 'PrivateGitRepositoryRecipe',
15 'ReviewNotPending',
16 'StaleLastMirrored',
17 'TooNewRecipeFormat',
18@@ -298,12 +299,22 @@
19
20 def __init__(self, branch):
21 message = (
22- 'Recipe may not refer to private branch: %s' %
23- branch.bzr_identity)
24+ 'Recipe may not refer to private branch: %s' % branch.identity)
25 self.branch = branch
26 Exception.__init__(self, message)
27
28
29+@error_status(httplib.BAD_REQUEST)
30+class PrivateGitRepositoryRecipe(Exception):
31+
32+ def __init__(self, repository):
33+ message = (
34+ 'Recipe may not refer to private repository: %s' %
35+ repository.identity)
36+ self.repository = repository
37+ Exception.__init__(self, message)
38+
39+
40 class ReviewNotPending(Exception):
41 """The requested review is not in a pending state."""
42
43
44=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
45--- lib/lp/code/interfaces/sourcepackagerecipe.py 2016-01-11 21:11:27 +0000
46+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2016-01-12 17:27:35 +0000
47@@ -14,6 +14,7 @@
48 'ISourcePackageRecipeDataSource',
49 'ISourcePackageRecipeSource',
50 'MINIMAL_RECIPE_TEXT_BZR',
51+ 'MINIMAL_RECIPE_TEXT_GIT',
52 ]
53
54
55@@ -55,6 +56,7 @@
56 from lp import _
57 from lp.app.validators.name import name_validator
58 from lp.code.interfaces.branch import IBranch
59+from lp.code.interfaces.gitrepository import IGitRepository
60 from lp.registry.interfaces.distroseries import IDistroSeries
61 from lp.registry.interfaces.pocket import PackagePublishingPocket
62 from lp.registry.interfaces.role import IHasOwner
63@@ -72,13 +74,26 @@
64 ''')
65
66
67+MINIMAL_RECIPE_TEXT_GIT = dedent(u'''\
68+ # git-build-recipe format 0.4 deb-version {debupstream}-0~{revtime}
69+ %s %s
70+ ''')
71+
72+
73 class ISourcePackageRecipeData(Interface):
74 """A recipe as database data, not text."""
75
76 base_branch = exported(
77 Reference(
78 IBranch, title=_("The base branch used by this recipe."),
79- required=True, readonly=True))
80+ required=False, readonly=True))
81+ base_git_repository = exported(
82+ Reference(
83+ IGitRepository,
84+ title=_("The base Git repository used by this recipe."),
85+ required=False, readonly=True))
86+ base = Attribute(
87+ "The base branch/repository used by this recipe (VCS-agnostic).")
88
89 deb_version_template = exported(
90 TextLine(
91
92=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
93--- lib/lp/code/model/sourcepackagerecipe.py 2016-01-11 19:16:04 +0000
94+++ lib/lp/code/model/sourcepackagerecipe.py 2016-01-12 17:27:35 +0000
95@@ -13,6 +13,7 @@
96 timedelta,
97 )
98 from operator import attrgetter
99+import re
100
101 from lazr.delegates import delegate_to
102 from pytz import utc
103@@ -151,6 +152,18 @@
104 def base_branch(self):
105 return self._recipe_data.base_branch
106
107+ @property
108+ def base_git_repository(self):
109+ return self._recipe_data.base_git_repository
110+
111+ @property
112+ def base(self):
113+ if self.base_branch is not None:
114+ return self.base_branch
115+ else:
116+ assert self.base_git_repository is not None
117+ return self.base_git_repository
118+
119 @staticmethod
120 def preLoadDataForSourcePackageRecipes(sourcepackagerecipes):
121 # Load the referencing SourcePackageRecipeData.
122@@ -173,7 +186,14 @@
123
124 @property
125 def recipe_text(self):
126- return self.builder_recipe.get_recipe_text()
127+ recipe_text = self.builder_recipe.get_recipe_text()
128+ # For git-based recipes, mangle the header line to say
129+ # "git-build-recipe" to reduce confusion; bzr-builder's recipe
130+ # parser will always round-trip this to "bzr-builder".
131+ if self.base_git_repository is not None:
132+ recipe_text = re.sub(
133+ r"^(#\s*)bzr-builder", r"\1git-build-recipe", recipe_text)
134+ return recipe_text
135
136 def updateSeries(self, distroseries):
137 if distroseries != self.distroseries:
138
139=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
140--- lib/lp/code/model/sourcepackagerecipebuild.py 2016-01-11 19:16:04 +0000
141+++ lib/lp/code/model/sourcepackagerecipebuild.py 2016-01-12 17:27:35 +0000
142@@ -183,7 +183,7 @@
143 if self.recipe is None:
144 branch_name = 'deleted'
145 else:
146- branch_name = self.recipe.base_branch.unique_name
147+ branch_name = self.recipe.base.unique_name
148 return '%s recipe build in %s %s' % (
149 branch_name, self.distribution.name, self.distroseries.name)
150
151
152=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
153--- lib/lp/code/model/sourcepackagerecipedata.py 2016-01-11 19:16:04 +0000
154+++ lib/lp/code/model/sourcepackagerecipedata.py 2016-01-12 17:27:35 +0000
155@@ -11,7 +11,7 @@
156 __metaclass__ = type
157 __all__ = ['SourcePackageRecipeData']
158
159-from itertools import groupby
160+import re
161
162 from bzrlib.plugins.builder.recipe import (
163 BaseRecipeBranch,
164@@ -45,16 +45,23 @@
165
166 from lp.code.errors import (
167 NoSuchBranch,
168+ NoSuchGitRepository,
169 PrivateBranchRecipe,
170+ PrivateGitRepositoryRecipe,
171 TooNewRecipeFormat,
172 )
173+from lp.code.interfaces.branch import IBranch
174 from lp.code.interfaces.branchlookup import IBranchLookup
175+from lp.code.interfaces.gitlookup import IGitLookup
176+from lp.code.interfaces.gitref import IGitRef
177+from lp.code.interfaces.gitrepository import IGitRepository
178 from lp.code.interfaces.sourcepackagerecipe import (
179 IRecipeBranchSource,
180 ISourcePackageRecipeData,
181 ISourcePackageRecipeDataSource,
182 )
183 from lp.code.model.branch import Branch
184+from lp.code.model.gitrepository import GitRepository
185 from lp.services.database.bulk import (
186 load_referencing,
187 load_related,
188@@ -92,15 +99,25 @@
189
190 __storm_table__ = "SourcePackageRecipeDataInstruction"
191
192- def __init__(self, name, type, comment, line_number, branch, revspec,
193- directory, recipe_data, parent_instruction,
194+ def __init__(self, name, type, comment, line_number, branch_or_repository,
195+ revspec, directory, recipe_data, parent_instruction,
196 source_directory):
197 super(_SourcePackageRecipeDataInstruction, self).__init__()
198 self.name = unicode(name)
199 self.type = type
200 self.comment = comment
201 self.line_number = line_number
202- self.branch = branch
203+ if IGitRepository.providedBy(branch_or_repository):
204+ self.git_repository = branch_or_repository
205+ elif IGitRef.providedBy(branch_or_repository):
206+ self.git_repository = branch_or_repository
207+ if revspec is None:
208+ revspec = branch_or_repository.name
209+ elif IBranch.providedBy(branch_or_repository):
210+ self.branch = branch_or_repository
211+ else:
212+ raise AssertionError(
213+ "Unsupported source: %r" % (branch_or_repository,))
214 if revspec is not None:
215 revspec = unicode(revspec)
216 self.revspec = revspec
217@@ -118,8 +135,10 @@
218 comment = Unicode(allow_none=True)
219 line_number = Int(allow_none=False)
220
221- branch_id = Int(name='branch', allow_none=False)
222+ branch_id = Int(name='branch', allow_none=True)
223 branch = Reference(branch_id, 'Branch.id')
224+ git_repository_id = Int(name='git_repository', allow_none=True)
225+ git_repository = Reference(git_repository_id, 'GitRepository.id')
226
227 revspec = Unicode(allow_none=True)
228 directory = Unicode(allow_none=True)
229@@ -134,8 +153,12 @@
230
231 def appendToRecipe(self, recipe_branch):
232 """Append a bzr-builder instruction to the recipe_branch object."""
233- branch = RecipeBranch(
234- self.name, self.branch.bzr_identity, self.revspec)
235+ if self.branch is not None:
236+ identity = self.branch.identity
237+ else:
238+ assert self.git_repository is not None
239+ identity = self.git_repository.identity
240+ branch = RecipeBranch(self.name, identity, self.revspec)
241 if self.type == InstructionType.MERGE:
242 recipe_branch.merge_branch(branch)
243 elif self.type == InstructionType.NEST:
244@@ -165,8 +188,29 @@
245
246 id = Int(primary=True)
247
248- base_branch_id = Int(name='base_branch', allow_none=False)
249+ base_branch_id = Int(name='base_branch', allow_none=True)
250 base_branch = Reference(base_branch_id, 'Branch.id')
251+ base_git_repository_id = Int(name='base_git_repository', allow_none=True)
252+ base_git_repository = Reference(base_git_repository_id, 'GitRepository.id')
253+
254+ @property
255+ def base(self):
256+ if self.base_branch is not None:
257+ return self.base_branch
258+ else:
259+ assert self.base_git_repository is not None
260+ return self.base_git_repository
261+
262+ @base.setter
263+ def base(self, value):
264+ if IGitRepository.providedBy(value):
265+ self.base_git_repository = value
266+ self.base_branch = None
267+ elif IBranch.providedBy(value):
268+ self.base_branch = value
269+ self.base_git_repository = None
270+ else:
271+ raise AssertionError("Unsupported base: %r" % (value,))
272
273 recipe_format = Unicode(allow_none=False)
274 deb_version_template = Unicode(allow_none=True)
275@@ -189,6 +233,12 @@
276 @staticmethod
277 def getParsedRecipe(recipe_text):
278 """See `IRecipeBranchSource`."""
279+ # We're using bzr-builder to parse the recipe text. While the
280+ # formats are mostly compatible, the header line must say
281+ # "bzr-builder" even though git-build-recipe also supports its own
282+ # name there.
283+ recipe_text = re.sub(
284+ r"^(#\s*)git-build-recipe", r"\1bzr-builder", recipe_text)
285 parser = RecipeParser(recipe_text)
286 return parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)
287
288@@ -222,7 +272,7 @@
289 def getRecipe(self):
290 """The BaseRecipeBranch version of the recipe."""
291 base_branch = BaseRecipeBranch(
292- self.base_branch.bzr_identity, self.deb_version_template,
293+ self.base.identity, self.deb_version_template,
294 self.recipe_format, self.revspec)
295 insn_stack = []
296 for insn in self.instructions:
297@@ -238,7 +288,7 @@
298 dict(insn=insn, recipe_branch=recipe_branch))
299 return base_branch
300
301- def _scanInstructions(self, recipe_branch):
302+ def _scanInstructions(self, base, recipe_branch):
303 """Check the recipe_branch doesn't use 'run' and look up the branches.
304
305 We do all the lookups before we start constructing database objects to
306@@ -247,15 +297,22 @@
307 :return: A map ``{branch_url: db_branch}``.
308 """
309 r = {}
310+ if IGitRepository.providedBy(base):
311+ lookup = getUtility(IGitLookup)
312+ missing_error = NoSuchGitRepository
313+ private_error = PrivateGitRepositoryRecipe
314+ else:
315+ lookup = getUtility(IBranchLookup)
316+ missing_error = NoSuchBranch
317+ private_error = PrivateBranchRecipe
318 for instruction in recipe_branch.child_branches:
319- db_branch = getUtility(IBranchLookup).getByUrl(
320- instruction.recipe_branch.url)
321+ db_branch = lookup.getByUrl(instruction.recipe_branch.url)
322 if db_branch is None:
323- raise NoSuchBranch(instruction.recipe_branch.url)
324+ raise missing_error(instruction.recipe_branch.url)
325 if db_branch.private:
326- raise PrivateBranchRecipe(db_branch)
327+ raise private_error(db_branch)
328 r[instruction.recipe_branch.url] = db_branch
329- r.update(self._scanInstructions(instruction.recipe_branch))
330+ r.update(self._scanInstructions(base, instruction.recipe_branch))
331 return r
332
333 def _recordInstructions(self, recipe_branch, parent_insn, branch_map,
334@@ -280,10 +337,11 @@
335 "Unsupported instruction %r" % instruction)
336 line_number += 1
337 comment = None
338- db_branch = branch_map[instruction.recipe_branch.url]
339+ db_branch_or_repository = branch_map[instruction.recipe_branch.url]
340 insn = _SourcePackageRecipeDataInstruction(
341 instruction.recipe_branch.name, type, comment,
342- line_number, db_branch, instruction.recipe_branch.revspec,
343+ line_number, db_branch_or_repository,
344+ instruction.recipe_branch.revspec,
345 nest_path, self, parent_insn, source_directory)
346 line_number = self._recordInstructions(
347 instruction.recipe_branch, insn, branch_map, line_number)
348@@ -294,24 +352,33 @@
349 clear_property_cache(self)
350 if builder_recipe.format > MAX_RECIPE_FORMAT:
351 raise TooNewRecipeFormat(builder_recipe.format, MAX_RECIPE_FORMAT)
352- branch_map = self._scanInstructions(builder_recipe)
353+ base = getUtility(IBranchLookup).getByUrl(builder_recipe.url)
354+ if base is None:
355+ base = getUtility(IGitLookup).getByUrl(builder_recipe.url)
356+ if base is None:
357+ # If possible, try to raise an exception consistent with
358+ # whether the current recipe is Bazaar-based or Git-based,
359+ # so that error messages make more sense.
360+ if self.base_git_repository is not None:
361+ raise NoSuchGitRepository(builder_recipe.url)
362+ else:
363+ raise NoSuchBranch(builder_recipe.url)
364+ elif base.private:
365+ raise PrivateGitRepositoryRecipe(base)
366+ elif base.private:
367+ raise PrivateBranchRecipe(base)
368+ branch_map = self._scanInstructions(base, builder_recipe)
369 # If this object hasn't been added to a store yet, there can't be any
370 # instructions linking to us yet.
371 if Store.of(self) is not None:
372 self.instructions.find().remove()
373- branch_lookup = getUtility(IBranchLookup)
374- base_branch = branch_lookup.getByUrl(builder_recipe.url)
375- if base_branch is None:
376- raise NoSuchBranch(builder_recipe.url)
377- if base_branch.private:
378- raise PrivateBranchRecipe(base_branch)
379 if builder_recipe.revspec is not None:
380 self.revspec = unicode(builder_recipe.revspec)
381 else:
382 self.revspec = None
383 self._recordInstructions(
384 builder_recipe, parent_insn=None, branch_map=branch_map)
385- self.base_branch = base_branch
386+ self.base = base
387 if builder_recipe.deb_version is None:
388 self.deb_version_template = None
389 else:
390@@ -329,44 +396,69 @@
391
392 @staticmethod
393 def preLoadReferencedBranches(sourcepackagerecipedatas):
394- # Circular import.
395+ # Circular imports.
396 from lp.code.model.branchcollection import GenericBranchCollection
397+ from lp.code.model.gitcollection import GenericGitCollection
398 # Load the related Branch, _SourcePackageRecipeDataInstruction.
399 base_branches = load_related(
400 Branch, sourcepackagerecipedatas, ['base_branch_id'])
401+ base_repositories = load_related(
402+ GitRepository, sourcepackagerecipedatas,
403+ ['base_git_repository_id'])
404 sprd_instructions = load_referencing(
405 _SourcePackageRecipeDataInstruction,
406 sourcepackagerecipedatas, ['recipe_data_id'])
407 sub_branches = load_related(
408 Branch, sprd_instructions, ['branch_id'])
409+ sub_repositories = load_related(
410+ GitRepository, sprd_instructions, ['git_repository_id'])
411 all_branches = base_branches + sub_branches
412- # Pre-load branches' data.
413- GenericBranchCollection.preloadDataForBranches(all_branches)
414+ all_repositories = base_repositories + sub_repositories
415+ # Pre-load branches'/repositories' data.
416+ if all_branches:
417+ GenericBranchCollection.preloadDataForBranches(all_branches)
418+ if all_repositories:
419+ GenericGitCollection.preloadDataForRepositories(all_repositories)
420 # Store the pre-fetched objects on the sourcepackagerecipedatas
421 # objects.
422- branch_to_recipe_data = dict([
423- (instr.branch_id, instr.recipe_data_id)
424- for instr in sprd_instructions])
425- caches = dict((sprd.id, [sprd, get_property_cache(sprd)])
426- for sprd in sourcepackagerecipedatas)
427- for unused, [sprd, cache] in caches.items():
428- cache._referenced_branches = [sprd.base_branch]
429- for recipe_data_id, branches in groupby(
430- sub_branches, lambda branch: branch_to_recipe_data[branch.id]):
431- cache = caches[recipe_data_id][1]
432- cache._referenced_branches.extend(list(branches))
433+ branch_to_recipe_data = {
434+ instr.branch_id: instr.recipe_data_id
435+ for instr in sprd_instructions
436+ if instr.branch_id is not None}
437+ repository_to_recipe_data = {
438+ instr.git_repository_id: instr.recipe_data_id
439+ for instr in sprd_instructions
440+ if instr.git_repository_id is not None}
441+ caches = {
442+ sprd.id: [sprd, get_property_cache(sprd)]
443+ for sprd in sourcepackagerecipedatas}
444+ for _, [sprd, cache] in caches.items():
445+ cache._referenced_branches = [sprd.base]
446+ for branch in sub_branches:
447+ cache = caches[branch_to_recipe_data[branch.id]][1]
448+ cache._referenced_branches.append(branch)
449+ for repository in sub_repositories:
450+ cache = caches[repository_to_recipe_data[repository.id]][1]
451+ cache._referenced_branches.append(repository)
452
453 def getReferencedBranches(self):
454- """Return an iterator of the Branch objects referenced by this recipe.
455+ """Return an iterator of the Branch/GitRepository objects referenced
456+ by this recipe.
457 """
458 return self._referenced_branches
459
460 @cachedproperty
461 def _referenced_branches(self):
462- referenced_branches = [self.base_branch]
463+ referenced_branches = [self.base]
464 sub_branches = IStore(self).find(
465 Branch,
466 _SourcePackageRecipeDataInstruction.recipe_data == self,
467 Branch.id == _SourcePackageRecipeDataInstruction.branch_id)
468 referenced_branches.extend(sub_branches)
469+ sub_repositories = IStore(self).find(
470+ GitRepository,
471+ _SourcePackageRecipeDataInstruction.recipe_data == self,
472+ GitRepository.id ==
473+ _SourcePackageRecipeDataInstruction.git_repository_id)
474+ referenced_branches.extend(sub_repositories)
475 return referenced_branches
476
477=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
478--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2016-01-11 21:11:27 +0000
479+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2016-01-12 17:27:35 +0000
480@@ -32,6 +32,7 @@
481 from lp.code.errors import (
482 BuildAlreadyPending,
483 PrivateBranchRecipe,
484+ PrivateGitRepositoryRecipe,
485 TooNewRecipeFormat,
486 )
487 from lp.code.interfaces.sourcepackagerecipe import (
488@@ -39,6 +40,7 @@
489 ISourcePackageRecipeSource,
490 ISourcePackageRecipeView,
491 MINIMAL_RECIPE_TEXT_BZR,
492+ MINIMAL_RECIPE_TEXT_GIT,
493 )
494 from lp.code.interfaces.sourcepackagerecipebuild import (
495 ISourcePackageRecipeBuild,
496@@ -86,14 +88,76 @@
497 from lp.testing.pages import webservice_for_person
498
499
500-class TestSourcePackageRecipe(TestCaseWithFactory):
501+class BzrMixin:
502+ """Mixin for Bazaar-based recipe tests."""
503+
504+ private_error = PrivateBranchRecipe
505+ branch_type = "branch"
506+ recipe_id = "bzr-builder"
507+
508+ def makeBranch(self, **kwargs):
509+ return self.factory.makeAnyBranch(**kwargs)
510+
511+ @staticmethod
512+ def getRepository(branch):
513+ return branch
514+
515+ @staticmethod
516+ def getBranchRecipeText(branch):
517+ return branch.identity
518+
519+ @staticmethod
520+ def setInformationType(branch, information_type):
521+ removeSecurityProxy(branch).information_type = information_type
522+
523+ def makeRecipeText(self):
524+ branch = self.makeBranch()
525+ return MINIMAL_RECIPE_TEXT_BZR % branch.identity
526+
527+
528+class GitMixin:
529+ """Mixin for Git-based recipe tests."""
530+
531+ private_error = PrivateGitRepositoryRecipe
532+ branch_type = "repository"
533+ recipe_id = "git-build-recipe"
534+
535+ def makeBranch(self, **kwargs):
536+ return self.factory.makeGitRefs(**kwargs)[0]
537+
538+ @staticmethod
539+ def getRepository(branch):
540+ return branch.repository
541+
542+ @staticmethod
543+ def getBranchRecipeText(branch):
544+ return branch.identity
545+
546+ @staticmethod
547+ def setInformationType(branch, information_type):
548+ removeSecurityProxy(branch.repository).information_type = (
549+ information_type)
550+
551+ def makeRecipeText(self):
552+ branch = self.makeBranch()
553+ return MINIMAL_RECIPE_TEXT_GIT % (
554+ branch.repository.identity, branch.name)
555+
556+
557+class TestSourcePackageRecipeMixin:
558 """Tests for `SourcePackageRecipe` objects."""
559
560 layer = DatabaseFunctionalLayer
561
562+ def makeSourcePackageRecipe(self, branches=(), recipe=None, **kwargs):
563+ if recipe is None and len(branches) == 0:
564+ branches = [self.makeBranch()]
565+ return self.factory.makeSourcePackageRecipe(
566+ branches=branches, recipe=recipe, **kwargs)
567+
568 def test_implements_interface(self):
569 """SourcePackageRecipe implements ISourcePackageRecipe."""
570- recipe = self.factory.makeSourcePackageRecipe()
571+ recipe = self.makeSourcePackageRecipe()
572 verifyObject(ISourcePackageRecipe, recipe)
573
574 def test_avoids_problematic_snapshots(self):
575@@ -103,7 +167,7 @@
576 'pending_builds',
577 ]
578 self.assertThat(
579- self.factory.makeSourcePackageRecipe(),
580+ self.makeSourcePackageRecipe(),
581 DoesNotSnapshot(problematic_properties, ISourcePackageRecipeView))
582
583 def makeRecipeComponents(self, branches=()):
584@@ -128,7 +192,7 @@
585 components = self.makeRecipeComponents()
586 recipe = getUtility(ISourcePackageRecipeSource).new(**components)
587 transaction.commit()
588- self.assertEquals(
589+ self.assertEqual(
590 (components['registrant'], components['owner'],
591 set(components['distroseries']), components['name']),
592 (recipe.registrant, recipe.owner, set(recipe.distroseries),
593@@ -139,35 +203,38 @@
594 """An exception should be raised if the base branch is private."""
595 owner = self.factory.makePerson()
596 with person_logged_in(owner):
597- branch = self.factory.makeAnyBranch(
598+ branch = self.makeBranch(
599 owner=owner, information_type=InformationType.USERDATA)
600 components = self.makeRecipeComponents(branches=[branch])
601 recipe_source = getUtility(ISourcePackageRecipeSource)
602 e = self.assertRaises(
603- PrivateBranchRecipe, recipe_source.new, **components)
604+ self.private_error, recipe_source.new, **components)
605 self.assertEqual(
606- 'Recipe may not refer to private branch: %s' %
607- branch.bzr_identity, str(e))
608+ 'Recipe may not refer to private %s: %s' %
609+ (self.branch_type, self.getRepository(branch).identity),
610+ str(e))
611
612 def test_creation_private_referenced_branch(self):
613 """An exception should be raised if a referenced branch is private."""
614 owner = self.factory.makePerson()
615 with person_logged_in(owner):
616- base_branch = self.factory.makeAnyBranch(owner=owner)
617- referenced_branch = self.factory.makeAnyBranch(
618+ base_branch = self.makeBranch(owner=owner)
619+ referenced_branch = self.makeBranch(
620 owner=owner, information_type=InformationType.USERDATA)
621 branches = [base_branch, referenced_branch]
622 components = self.makeRecipeComponents(branches=branches)
623 recipe_source = getUtility(ISourcePackageRecipeSource)
624 e = self.assertRaises(
625- PrivateBranchRecipe, recipe_source.new, **components)
626+ self.private_error, recipe_source.new, **components)
627 self.assertEqual(
628- 'Recipe may not refer to private branch: %s' %
629- referenced_branch.bzr_identity, str(e))
630+ 'Recipe may not refer to private %s: %s' % (
631+ self.branch_type,
632+ self.getRepository(referenced_branch).identity),
633+ str(e))
634
635 def test_exists(self):
636 # Test ISourcePackageRecipeSource.exists
637- recipe = self.factory.makeSourcePackageRecipe()
638+ recipe = self.makeSourcePackageRecipe()
639
640 self.assertTrue(
641 getUtility(ISourcePackageRecipeSource).exists(
642@@ -185,32 +252,33 @@
643
644 def test_recipe_implements_interface(self):
645 # SourcePackageRecipe objects implement ISourcePackageRecipe.
646- recipe = self.factory.makeSourcePackageRecipe()
647+ recipe = self.makeSourcePackageRecipe()
648 transaction.commit()
649 with person_logged_in(recipe.owner):
650 self.assertProvides(recipe, ISourcePackageRecipe)
651
652 def test_base_branch(self):
653 # When a recipe is created, we can access its base branch.
654- branch = self.factory.makeAnyBranch()
655- sp_recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
656+ branch = self.makeBranch()
657+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch])
658 transaction.commit()
659- self.assertEquals(branch, sp_recipe.base_branch)
660+ self.assertEqual(self.getRepository(branch), sp_recipe.base)
661
662 def test_branch_links_created(self):
663 # When a recipe is created, we can query it for links to the branch
664 # it references.
665- branch = self.factory.makeAnyBranch()
666- sp_recipe = self.factory.makeSourcePackageRecipe(
667- branches=[branch])
668+ branch = self.makeBranch()
669+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch])
670 transaction.commit()
671- self.assertEquals([branch], list(sp_recipe.getReferencedBranches()))
672+ self.assertEqual(
673+ [self.getRepository(branch)],
674+ list(sp_recipe.getReferencedBranches()))
675
676 def createSourcePackageRecipe(self, number_of_branches=2):
677 branches = []
678 for i in range(number_of_branches):
679- branches.append(self.factory.makeAnyBranch())
680- sp_recipe = self.factory.makeSourcePackageRecipe(branches=branches)
681+ branches.append(self.makeBranch())
682+ sp_recipe = self.makeSourcePackageRecipe(branches=branches)
683 transaction.commit()
684 return sp_recipe, branches
685
686@@ -218,8 +286,8 @@
687 # If a recipe links to more than one branch, getReferencedBranches()
688 # returns all of them.
689 sp_recipe, [branch1, branch2] = self.createSourcePackageRecipe()
690- self.assertEquals(
691- sorted([branch1, branch2]),
692+ self.assertEqual(
693+ sorted([self.getRepository(branch1), self.getRepository(branch2)]),
694 sorted(sp_recipe.getReferencedBranches()))
695
696 def test_preLoadReferencedBranches(self):
697@@ -230,16 +298,15 @@
698 referenced_branches = sp_recipe.getReferencedBranches()
699 clear_property_cache(recipe_data)
700 SourcePackageRecipeData.preLoadReferencedBranches([recipe_data])
701- self.assertEquals(
702+ self.assertEqual(
703 sorted(referenced_branches),
704 sorted(sp_recipe.getReferencedBranches()))
705
706 def test_random_user_cant_edit(self):
707 # An arbitrary user can't set attributes.
708- branch1 = self.factory.makeAnyBranch()
709+ branch1 = self.makeBranch()
710 recipe_1 = self.factory.makeRecipeText(branch1)
711- sp_recipe = self.factory.makeSourcePackageRecipe(
712- recipe=recipe_1)
713+ sp_recipe = self.makeSourcePackageRecipe(recipe=recipe_1)
714 login_person(self.factory.makePerson())
715 self.assertRaises(
716 Unauthorized, getattr, sp_recipe, 'setRecipeText')
717@@ -247,71 +314,78 @@
718 def test_set_recipe_text_resets_branch_references(self):
719 # When the recipe_text is replaced, getReferencedBranches returns
720 # (only) the branches referenced by the new recipe.
721- branch1 = self.factory.makeAnyBranch()
722- sp_recipe = self.factory.makeSourcePackageRecipe(
723- branches=[branch1])
724- branch2 = self.factory.makeAnyBranch()
725+ branch1 = self.makeBranch()
726+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch1])
727+ branch2 = self.makeBranch()
728 new_recipe = self.factory.makeRecipeText(branch2)
729 with person_logged_in(sp_recipe.owner):
730 sp_recipe.setRecipeText(new_recipe)
731- self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
732+ self.assertEqual(
733+ [self.getRepository(branch2)],
734+ list(sp_recipe.getReferencedBranches()))
735
736 def test_rejects_run_command(self):
737 recipe_text = '''\
738- # bzr-builder format 0.3 deb-version 0.1-{revno}
739+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
740 %(base)s
741 run touch test
742- ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
743+ ''' % dict(recipe_id=self.recipe_id,
744+ base=self.getBranchRecipeText(self.makeBranch()))
745 recipe_text = textwrap.dedent(recipe_text)
746 self.assertRaises(
747- ForbiddenInstructionError, self.factory.makeSourcePackageRecipe,
748+ ForbiddenInstructionError, self.makeSourcePackageRecipe,
749 recipe=recipe_text)
750
751 def test_run_rejected_without_mangling_recipe(self):
752- sp_recipe = self.factory.makeSourcePackageRecipe()
753+ sp_recipe = self.makeSourcePackageRecipe()
754 old_branches = list(sp_recipe.getReferencedBranches())
755 recipe_text = '''\
756- # bzr-builder format 0.3 deb-version 0.1-{revno}
757+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
758 %(base)s
759 run touch test
760- ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
761+ ''' % dict(recipe_id=self.recipe_id,
762+ base=self.getBranchRecipeText(self.makeBranch()))
763 recipe_text = textwrap.dedent(recipe_text)
764 with person_logged_in(sp_recipe.owner):
765 self.assertRaises(
766 ForbiddenInstructionError, sp_recipe.setRecipeText,
767 recipe_text)
768- self.assertEquals(
769+ self.assertEqual(
770 old_branches, list(sp_recipe.getReferencedBranches()))
771
772 def test_nest_part(self):
773 """nest-part instruction can be round-tripped."""
774- base = self.factory.makeBranch()
775- nested = self.factory.makeBranch()
776+ base = self.makeBranch()
777+ nested = self.makeBranch()
778 recipe_text = (
779- "# bzr-builder format 0.3 deb-version 1\n"
780+ "# %s format 0.3 deb-version 1\n"
781 "%s revid:base_revid\n"
782 "nest-part nested1 %s foo bar tag:foo\n" %
783- (base.bzr_identity, nested.bzr_identity))
784- recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)
785+ (self.recipe_id,
786+ self.getRepository(base).identity,
787+ self.getRepository(nested).identity))
788+ recipe = self.makeSourcePackageRecipe(recipe=recipe_text)
789 self.assertEqual(recipe_text, recipe.recipe_text)
790
791 def test_nest_part_no_target(self):
792 """nest-part instruction with no target-dir can be round-tripped."""
793- base = self.factory.makeBranch()
794- nested = self.factory.makeBranch()
795+ base = self.makeBranch()
796+ nested = self.makeBranch()
797 recipe_text = (
798- "# bzr-builder format 0.3 deb-version 1\n"
799+ "# %s format 0.3 deb-version 1\n"
800 "%s revid:base_revid\n"
801 "nest-part nested1 %s foo\n" %
802- (base.bzr_identity, nested.bzr_identity))
803- recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)
804+ (self.recipe_id,
805+ self.getRepository(base).identity,
806+ self.getRepository(nested).identity))
807+ recipe = self.makeSourcePackageRecipe(recipe=recipe_text)
808 self.assertEqual(recipe_text, recipe.recipe_text)
809
810 def test_accept_format_0_3(self):
811 """Recipe format 0.3 is accepted."""
812 builder_recipe = self.factory.makeRecipe()
813 builder_recipe.format = 0.3
814- self.factory.makeSourcePackageRecipe(recipe=str(builder_recipe))
815+ self.makeSourcePackageRecipe(recipe=str(builder_recipe))
816
817 def test_reject_newer_formats(self):
818 with recipe_parser_newest_version(145.115):
819@@ -319,11 +393,11 @@
820 builder_recipe.format = 145.115
821 self.assertRaises(
822 TooNewRecipeFormat,
823- self.factory.makeSourcePackageRecipe,
824+ self.makeSourcePackageRecipe,
825 recipe=str(builder_recipe))
826
827 def test_requestBuild(self):
828- recipe = self.factory.makeSourcePackageRecipe()
829+ recipe = self.makeSourcePackageRecipe()
830 (distroseries,) = list(recipe.distroseries)
831 ppa = self.factory.makeArchive()
832 build = recipe.requestBuild(ppa, ppa.owner, distroseries,
833@@ -342,17 +416,17 @@
834 removeSecurityProxy(build).build_farm_job_id).one()
835 self.assertProvides(build_queue, IBuildQueue)
836 self.assertTrue(build_queue.virtualized)
837- self.assertEquals(build_queue.status, BuildQueueStatus.WAITING)
838+ self.assertEqual(build_queue.status, BuildQueueStatus.WAITING)
839
840 def test_requestBuildRejectsNotPPA(self):
841- recipe = self.factory.makeSourcePackageRecipe()
842+ recipe = self.makeSourcePackageRecipe()
843 not_ppa = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
844 (distroseries,) = list(recipe.distroseries)
845 self.assertRaises(NonPPABuildRequest, recipe.requestBuild, not_ppa,
846 not_ppa.owner, distroseries, PackagePublishingPocket.RELEASE)
847
848 def test_requestBuildRejectsNoPermission(self):
849- recipe = self.factory.makeSourcePackageRecipe()
850+ recipe = self.makeSourcePackageRecipe()
851 ppa = self.factory.makeArchive()
852 requester = self.factory.makePerson()
853 (distroseries,) = list(recipe.distroseries)
854@@ -360,14 +434,14 @@
855 requester, distroseries, PackagePublishingPocket.RELEASE)
856
857 def test_requestBuildRejectsInvalidPocket(self):
858- recipe = self.factory.makeSourcePackageRecipe()
859+ recipe = self.makeSourcePackageRecipe()
860 ppa = self.factory.makeArchive()
861 (distroseries,) = list(recipe.distroseries)
862 self.assertRaises(InvalidPocketForPPA, recipe.requestBuild, ppa,
863 ppa.owner, distroseries, PackagePublishingPocket.BACKPORTS)
864
865 def test_requestBuildRejectsDisabledArchive(self):
866- recipe = self.factory.makeSourcePackageRecipe()
867+ recipe = self.makeSourcePackageRecipe()
868 ppa = self.factory.makeArchive()
869 removeSecurityProxy(ppa).disable()
870 (distroseries,) = list(recipe.distroseries)
871@@ -377,7 +451,7 @@
872
873 def test_requestBuildScore(self):
874 """Normal build requests have a relatively low queue score (2505)."""
875- recipe = self.factory.makeSourcePackageRecipe()
876+ recipe = self.makeSourcePackageRecipe()
877 build = recipe.requestBuild(recipe.daily_build_archive,
878 recipe.owner, list(recipe.distroseries)[0],
879 PackagePublishingPocket.RELEASE)
880@@ -387,7 +461,7 @@
881
882 def test_requestBuildManualScore(self):
883 """Manual build requests have a score equivalent to binary builds."""
884- recipe = self.factory.makeSourcePackageRecipe()
885+ recipe = self.makeSourcePackageRecipe()
886 build = recipe.requestBuild(recipe.daily_build_archive,
887 recipe.owner, list(recipe.distroseries)[0],
888 PackagePublishingPocket.RELEASE, manual=True)
889@@ -397,7 +471,7 @@
890
891 def test_requestBuild_relative_build_score(self):
892 """Offsets for archives are respected."""
893- recipe = self.factory.makeSourcePackageRecipe()
894+ recipe = self.makeSourcePackageRecipe()
895 archive = recipe.daily_build_archive
896 removeSecurityProxy(archive).relative_build_score = 100
897 build = recipe.requestBuild(
898@@ -409,7 +483,7 @@
899
900 def test_requestBuildRejectRepeats(self):
901 """Reject build requests that are identical to pending builds."""
902- recipe = self.factory.makeSourcePackageRecipe()
903+ recipe = self.makeSourcePackageRecipe()
904 series = list(recipe.distroseries)[0]
905 archive = self.factory.makeArchive(owner=recipe.owner)
906 old_build = recipe.requestBuild(archive, recipe.owner, series,
907@@ -447,7 +521,7 @@
908 private=True)
909
910 # Create a recipe with the team P3A as the build destination.
911- recipe = self.factory.makeSourcePackageRecipe()
912+ recipe = self.makeSourcePackageRecipe()
913
914 # Add upload component rights for the non-team person.
915 with person_logged_in(team_owner):
916@@ -467,13 +541,13 @@
917 def test_sourcepackagerecipe_description(self):
918 """Ensure that the SourcePackageRecipe has a proper description."""
919 description = u'The whoozits and whatzits.'
920- source_package_recipe = self.factory.makeSourcePackageRecipe(
921+ source_package_recipe = self.makeSourcePackageRecipe(
922 description=description)
923 self.assertEqual(description, source_package_recipe.description)
924
925 def test_distroseries(self):
926 """Test that the distroseries behaves as a set."""
927- recipe = self.factory.makeSourcePackageRecipe()
928+ recipe = self.makeSourcePackageRecipe()
929 distroseries = self.factory.makeDistroSeries()
930 (old_distroseries,) = recipe.distroseries
931 recipe.distroseries.add(distroseries)
932@@ -486,7 +560,7 @@
933
934 def test_build_daily(self):
935 """Test that build_daily behaves as a bool."""
936- recipe = self.factory.makeSourcePackageRecipe()
937+ recipe = self.makeSourcePackageRecipe()
938 self.assertFalse(recipe.build_daily)
939 login_person(recipe.owner)
940 recipe.build_daily = True
941@@ -495,9 +569,9 @@
942 def test_view_public(self):
943 """Anyone can view a recipe with public branches."""
944 owner = self.factory.makePerson()
945- branch = self.factory.makeAnyBranch(owner=owner)
946+ branch = self.makeBranch(owner=owner)
947 with person_logged_in(owner):
948- recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
949+ recipe = self.makeSourcePackageRecipe(branches=[branch])
950 self.assertTrue(check_permission('launchpad.View', recipe))
951 with person_logged_in(self.factory.makePerson()):
952 self.assertTrue(check_permission('launchpad.View', recipe))
953@@ -506,19 +580,18 @@
954 def test_view_private(self):
955 """Recipes with private branches are restricted."""
956 owner = self.factory.makePerson()
957- branch = self.factory.makeAnyBranch(owner=owner)
958+ branch = self.makeBranch(owner=owner)
959 with person_logged_in(owner):
960- recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
961+ recipe = self.makeSourcePackageRecipe(branches=[branch])
962 self.assertTrue(check_permission('launchpad.View', recipe))
963- removeSecurityProxy(branch).information_type = (
964- InformationType.USERDATA)
965+ self.setInformationType(branch, InformationType.USERDATA)
966 with person_logged_in(self.factory.makePerson()):
967 self.assertFalse(check_permission('launchpad.View', recipe))
968 self.assertFalse(check_permission('launchpad.View', recipe))
969
970 def test_edit(self):
971 """Only the owner can edit a sourcepackagerecipe."""
972- recipe = self.factory.makeSourcePackageRecipe()
973+ recipe = self.makeSourcePackageRecipe()
974 self.assertFalse(check_permission('launchpad.Edit', recipe))
975 with person_logged_in(self.factory.makePerson()):
976 self.assertFalse(check_permission('launchpad.Edit', recipe))
977@@ -528,8 +601,8 @@
978 def test_destroySelf(self):
979 """Should destroy associated builds, distroseries, etc."""
980 # Recipe should have at least one datainstruction.
981- branches = [self.factory.makeBranch() for count in range(2)]
982- recipe = self.factory.makeSourcePackageRecipe(branches=branches)
983+ branches = [self.makeBranch() for count in range(2)]
984+ recipe = self.makeSourcePackageRecipe(branches=branches)
985 pending_build = self.factory.makeSourcePackageRecipeBuild(
986 recipe=recipe)
987 pending_build.queueBuild()
988@@ -545,7 +618,7 @@
989 def test_destroySelf_preserves_release(self):
990 # Destroying a sourcepackagerecipe removes references to its builds
991 # from their releases.
992- recipe = self.factory.makeSourcePackageRecipe()
993+ recipe = self.makeSourcePackageRecipe()
994 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
995 release = self.factory.makeSourcePackageRelease(
996 source_package_recipe_build=build)
997@@ -557,7 +630,7 @@
998 def test_destroySelf_retains_build(self):
999 # Destroying a sourcepackagerecipe removes references to its builds
1000 # from their releases.
1001- recipe = self.factory.makeSourcePackageRecipe()
1002+ recipe = self.makeSourcePackageRecipe()
1003 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1004 store = Store.of(build)
1005 store.flush()
1006@@ -578,12 +651,11 @@
1007
1008 def test_findStaleDailyBuilds(self):
1009 # Stale recipe not built daily.
1010- self.factory.makeSourcePackageRecipe()
1011+ self.makeSourcePackageRecipe()
1012 # Daily build recipe not stale.
1013- self.factory.makeSourcePackageRecipe(
1014- build_daily=True, is_stale=False)
1015+ self.makeSourcePackageRecipe(build_daily=True, is_stale=False)
1016 # Stale daily build.
1017- stale_daily = self.factory.makeSourcePackageRecipe(
1018+ stale_daily = self.makeSourcePackageRecipe(
1019 build_daily=True, is_stale=True)
1020 self.assertContentEqual([stale_daily],
1021 SourcePackageRecipe.findStaleDailyBuilds())
1022@@ -591,8 +663,7 @@
1023 def test_findStaleDailyBuildsDistinct(self):
1024 # If a recipe has 2 builds due to 2 distroseries, it only returns
1025 # one recipe.
1026- recipe = self.factory.makeSourcePackageRecipe(
1027- build_daily=True, is_stale=True)
1028+ recipe = self.makeSourcePackageRecipe(build_daily=True, is_stale=True)
1029 hoary = self.factory.makeSourcePackageRecipeDistroseries("hoary")
1030 recipe.distroseries.add(hoary)
1031 for series in recipe.distroseries:
1032@@ -613,7 +684,7 @@
1033 build.updateStatus(
1034 BuildStatus.FULLYBUILT,
1035 date_finished=build.date_started + duration)
1036- recipe = removeSecurityProxy(self.factory.makeSourcePackageRecipe())
1037+ recipe = removeSecurityProxy(self.makeSourcePackageRecipe())
1038 self.assertIs(None, recipe.getMedianBuildDuration())
1039 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1040 set_duration(build, 10)
1041@@ -632,7 +703,7 @@
1042
1043 def test_getBuilds(self):
1044 # Test the various getBuilds methods.
1045- recipe = self.factory.makeSourcePackageRecipe()
1046+ recipe = self.makeSourcePackageRecipe()
1047 builds = [
1048 self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1049 for x in range(3)]
1050@@ -654,7 +725,7 @@
1051 person = self.factory.makePerson()
1052 archives = [self.factory.makeArchive(owner=person) for x in range(4)]
1053 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
1054- recipe = self.factory.makeSourcePackageRecipe()
1055+ recipe = self.makeSourcePackageRecipe()
1056
1057 build_info = []
1058 for archive in archives:
1059@@ -666,7 +737,7 @@
1060
1061 def test_getBuilds_cancelled(self):
1062 # Cancelled builds are not considered pending.
1063- recipe = self.factory.makeSourcePackageRecipe()
1064+ recipe = self.makeSourcePackageRecipe()
1065 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1066 with admin_logged_in():
1067 build.queueBuild()
1068@@ -676,40 +747,42 @@
1069 self.assertEqual([], list(recipe.pending_builds))
1070
1071 def test_setRecipeText_private_base_branch(self):
1072- source_package_recipe = self.factory.makeSourcePackageRecipe()
1073+ source_package_recipe = self.makeSourcePackageRecipe()
1074 with person_logged_in(source_package_recipe.owner):
1075- branch = self.factory.makeAnyBranch(
1076+ branch = self.makeBranch(
1077 owner=source_package_recipe.owner,
1078 information_type=InformationType.USERDATA)
1079 recipe_text = self.factory.makeRecipeText(branch)
1080 e = self.assertRaises(
1081- PrivateBranchRecipe, source_package_recipe.setRecipeText,
1082+ self.private_error, source_package_recipe.setRecipeText,
1083 recipe_text)
1084 self.assertEqual(
1085- 'Recipe may not refer to private branch: %s' %
1086- branch.bzr_identity, str(e))
1087+ 'Recipe may not refer to private %s: %s' %
1088+ (self.branch_type, self.getRepository(branch).identity),
1089+ str(e))
1090
1091 def test_setRecipeText_private_referenced_branch(self):
1092- source_package_recipe = self.factory.makeSourcePackageRecipe()
1093+ source_package_recipe = self.makeSourcePackageRecipe()
1094 with person_logged_in(source_package_recipe.owner):
1095- base_branch = self.factory.makeAnyBranch(
1096- owner=source_package_recipe.owner)
1097- referenced_branch = self.factory.makeAnyBranch(
1098+ base_branch = self.makeBranch(owner=source_package_recipe.owner)
1099+ referenced_branch = self.makeBranch(
1100 owner=source_package_recipe.owner,
1101 information_type=InformationType.USERDATA)
1102 recipe_text = self.factory.makeRecipeText(
1103 base_branch, referenced_branch)
1104 e = self.assertRaises(
1105- PrivateBranchRecipe, source_package_recipe.setRecipeText,
1106+ self.private_error, source_package_recipe.setRecipeText,
1107 recipe_text)
1108 self.assertEqual(
1109- 'Recipe may not refer to private branch: %s' %
1110- referenced_branch.bzr_identity, str(e))
1111+ 'Recipe may not refer to private %s: %s' %
1112+ (self.branch_type,
1113+ self.getRepository(referenced_branch).identity),
1114+ str(e))
1115
1116 def test_getBuilds_ignores_disabled_archive(self):
1117 # Builds into a disabled archive aren't returned.
1118 archive = self.factory.makeArchive()
1119- recipe = self.factory.makeSourcePackageRecipe()
1120+ recipe = self.makeSourcePackageRecipe()
1121 self.factory.makeSourcePackageRecipeBuild(
1122 recipe=recipe, archive=archive)
1123 with person_logged_in(archive.owner):
1124@@ -719,19 +792,19 @@
1125 self.assertEqual([], list(recipe.pending_builds))
1126
1127 def test_containsUnbuildableSeries(self):
1128- recipe = self.factory.makeSourcePackageRecipe()
1129+ recipe = self.makeSourcePackageRecipe()
1130 self.assertFalse(recipe.containsUnbuildableSeries(
1131 recipe.daily_build_archive))
1132
1133 def test_containsUnbuildableSeries_with_obsolete_series(self):
1134- recipe = self.factory.makeSourcePackageRecipe()
1135+ recipe = self.makeSourcePackageRecipe()
1136 warty = self.factory.makeSourcePackageRecipeDistroseries()
1137 removeSecurityProxy(warty).status = SeriesStatus.OBSOLETE
1138 self.assertTrue(recipe.containsUnbuildableSeries(
1139 recipe.daily_build_archive))
1140
1141 def test_performDailyBuild_filters_obsolete_series(self):
1142- recipe = self.factory.makeSourcePackageRecipe()
1143+ recipe = self.makeSourcePackageRecipe()
1144 warty = self.factory.makeSourcePackageRecipeDistroseries()
1145 hoary = self.factory.makeSourcePackageRecipeDistroseries(name='hoary')
1146 with person_logged_in(recipe.owner):
1147@@ -741,19 +814,30 @@
1148 self.assertEqual([build.recipe for build in builds], [recipe])
1149
1150
1151-class TestRecipeBranchRoundTripping(TestCaseWithFactory):
1152+class TestSourcePackageRecipeBzr(
1153+ TestSourcePackageRecipeMixin, BzrMixin, TestCaseWithFactory):
1154+ """Test `SourcePackageRecipe` objects for Bazaar."""
1155+
1156+
1157+class TestSourcePackageRecipeGit(
1158+ TestSourcePackageRecipeMixin, GitMixin, TestCaseWithFactory):
1159+ """Test `SourcePackageRecipe` objects for Git."""
1160+
1161+
1162+class TestRecipeBranchRoundTrippingMixin:
1163
1164 layer = DatabaseFunctionalLayer
1165
1166 def setUp(self):
1167- super(TestRecipeBranchRoundTripping, self).setUp()
1168- self.base_branch = self.factory.makeAnyBranch()
1169- self.nested_branch = self.factory.makeAnyBranch()
1170- self.merged_branch = self.factory.makeAnyBranch()
1171+ super(TestRecipeBranchRoundTrippingMixin, self).setUp()
1172+ self.base_branch = self.makeBranch()
1173+ self.nested_branch = self.makeBranch()
1174+ self.merged_branch = self.makeBranch()
1175 self.branch_identities = {
1176- 'base': self.base_branch.bzr_identity,
1177- 'nested': self.nested_branch.bzr_identity,
1178- 'merged': self.merged_branch.bzr_identity,
1179+ 'recipe_id': self.recipe_id,
1180+ 'base': self.getRepository(self.base_branch).identity,
1181+ 'nested': self.getRepository(self.nested_branch).identity,
1182+ 'merged': self.getRepository(self.merged_branch).identity,
1183 }
1184
1185 def get_recipe(self, recipe_text):
1186@@ -785,161 +869,173 @@
1187
1188 def test_builds_simplest_recipe(self):
1189 recipe_text = '''\
1190- # bzr-builder format 0.3 deb-version 0.1-{revno}
1191+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1192 %(base)s
1193 ''' % self.branch_identities
1194 base_branch = self.get_recipe(recipe_text).builder_recipe
1195 self.check_base_recipe_branch(
1196- base_branch, self.base_branch.bzr_identity,
1197+ base_branch, self.getRepository(self.base_branch).identity,
1198 deb_version='0.1-{revno}')
1199
1200 def test_builds_recipe_with_merge(self):
1201 recipe_text = '''\
1202- # bzr-builder format 0.3 deb-version 0.1-{revno}
1203+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1204 %(base)s
1205 merge bar %(merged)s
1206 ''' % self.branch_identities
1207 base_branch = self.get_recipe(recipe_text).builder_recipe
1208 self.check_base_recipe_branch(
1209- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1210- deb_version='0.1-{revno}')
1211+ base_branch, self.getRepository(self.base_branch).identity,
1212+ num_child_branches=1, deb_version='0.1-{revno}')
1213 child_branch, location = base_branch.child_branches[0].as_tuple()
1214 self.assertEqual(None, location)
1215 self.check_recipe_branch(
1216- child_branch, "bar", self.merged_branch.bzr_identity)
1217+ child_branch, "bar",
1218+ self.getRepository(self.merged_branch).identity)
1219
1220 def test_builds_recipe_with_nest(self):
1221 recipe_text = '''\
1222- # bzr-builder format 0.3 deb-version 0.1-{revno}
1223+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1224 %(base)s
1225 nest bar %(nested)s baz
1226 ''' % self.branch_identities
1227 base_branch = self.get_recipe(recipe_text).builder_recipe
1228 self.check_base_recipe_branch(
1229- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1230- deb_version='0.1-{revno}')
1231+ base_branch, self.getRepository(self.base_branch).identity,
1232+ num_child_branches=1, deb_version='0.1-{revno}')
1233 child_branch, location = base_branch.child_branches[0].as_tuple()
1234 self.assertEqual("baz", location)
1235 self.check_recipe_branch(
1236- child_branch, "bar", self.nested_branch.bzr_identity)
1237+ child_branch, "bar",
1238+ self.getRepository(self.nested_branch).identity)
1239
1240 def test_builds_recipe_with_nest_then_merge(self):
1241 recipe_text = '''\
1242- # bzr-builder format 0.3 deb-version 0.1-{revno}
1243+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1244 %(base)s
1245 nest bar %(nested)s baz
1246 merge zam %(merged)s
1247 ''' % self.branch_identities
1248 base_branch = self.get_recipe(recipe_text).builder_recipe
1249 self.check_base_recipe_branch(
1250- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
1251- deb_version='0.1-{revno}')
1252+ base_branch, self.getRepository(self.base_branch).identity,
1253+ num_child_branches=2, deb_version='0.1-{revno}')
1254 child_branch, location = base_branch.child_branches[0].as_tuple()
1255 self.assertEqual("baz", location)
1256 self.check_recipe_branch(
1257- child_branch, "bar", self.nested_branch.bzr_identity)
1258+ child_branch, "bar",
1259+ self.getRepository(self.nested_branch).identity)
1260 child_branch, location = base_branch.child_branches[1].as_tuple()
1261 self.assertEqual(None, location)
1262 self.check_recipe_branch(
1263- child_branch, "zam", self.merged_branch.bzr_identity)
1264+ child_branch, "zam",
1265+ self.getRepository(self.merged_branch).identity)
1266
1267 def test_builds_recipe_with_merge_then_nest(self):
1268 recipe_text = '''\
1269- # bzr-builder format 0.3 deb-version 0.1-{revno}
1270+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1271 %(base)s
1272 merge zam %(merged)s
1273 nest bar %(nested)s baz
1274 ''' % self.branch_identities
1275 base_branch = self.get_recipe(recipe_text).builder_recipe
1276 self.check_base_recipe_branch(
1277- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
1278- deb_version='0.1-{revno}')
1279+ base_branch, self.getRepository(self.base_branch).identity,
1280+ num_child_branches=2, deb_version='0.1-{revno}')
1281 child_branch, location = base_branch.child_branches[0].as_tuple()
1282 self.assertEqual(None, location)
1283 self.check_recipe_branch(
1284- child_branch, "zam", self.merged_branch.bzr_identity)
1285+ child_branch, "zam",
1286+ self.getRepository(self.merged_branch).identity)
1287 child_branch, location = base_branch.child_branches[1].as_tuple()
1288 self.assertEqual("baz", location)
1289 self.check_recipe_branch(
1290- child_branch, "bar", self.nested_branch.bzr_identity)
1291+ child_branch, "bar",
1292+ self.getRepository(self.nested_branch).identity)
1293
1294 def test_builds_a_merge_in_to_a_nest(self):
1295 recipe_text = '''\
1296- # bzr-builder format 0.3 deb-version 0.1-{revno}
1297+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1298 %(base)s
1299 nest bar %(nested)s baz
1300 merge zam %(merged)s
1301 ''' % self.branch_identities
1302 base_branch = self.get_recipe(recipe_text).builder_recipe
1303 self.check_base_recipe_branch(
1304- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1305- deb_version='0.1-{revno}')
1306+ base_branch, self.getRepository(self.base_branch).identity,
1307+ num_child_branches=1, deb_version='0.1-{revno}')
1308 child_branch, location = base_branch.child_branches[0].as_tuple()
1309 self.assertEqual("baz", location)
1310 self.check_recipe_branch(
1311- child_branch, "bar", self.nested_branch.bzr_identity,
1312+ child_branch, "bar",
1313+ self.getRepository(self.nested_branch).identity,
1314 num_child_branches=1)
1315 child_branch, location = child_branch.child_branches[0].as_tuple()
1316 self.assertEqual(None, location)
1317 self.check_recipe_branch(
1318- child_branch, "zam", self.merged_branch.bzr_identity)
1319+ child_branch, "zam",
1320+ self.getRepository(self.merged_branch).identity)
1321
1322 def tests_builds_nest_into_a_nest(self):
1323- nested2 = self.factory.makeAnyBranch()
1324- self.branch_identities['nested2'] = nested2.bzr_identity
1325+ nested2 = self.makeBranch()
1326+ self.branch_identities['nested2'] = (
1327+ self.getRepository(nested2).identity)
1328 recipe_text = '''\
1329- # bzr-builder format 0.3 deb-version 0.1-{revno}
1330+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1331 %(base)s
1332 nest bar %(nested)s baz
1333 nest zam %(nested2)s zoo
1334 ''' % self.branch_identities
1335 base_branch = self.get_recipe(recipe_text).builder_recipe
1336 self.check_base_recipe_branch(
1337- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1338- deb_version='0.1-{revno}')
1339+ base_branch, self.getRepository(self.base_branch).identity,
1340+ num_child_branches=1, deb_version='0.1-{revno}')
1341 child_branch, location = base_branch.child_branches[0].as_tuple()
1342 self.assertEqual("baz", location)
1343 self.check_recipe_branch(
1344- child_branch, "bar", self.nested_branch.bzr_identity,
1345+ child_branch, "bar",
1346+ self.getRepository(self.nested_branch).identity,
1347 num_child_branches=1)
1348 child_branch, location = child_branch.child_branches[0].as_tuple()
1349 self.assertEqual("zoo", location)
1350- self.check_recipe_branch(child_branch, "zam", nested2.bzr_identity)
1351+ self.check_recipe_branch(
1352+ child_branch, "zam", self.getRepository(nested2).identity)
1353
1354 def tests_builds_recipe_with_revspecs(self):
1355 recipe_text = '''\
1356- # bzr-builder format 0.3 deb-version 0.1-{revno}
1357+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1358 %(base)s revid:a
1359 nest bar %(nested)s baz tag:b
1360 merge zam %(merged)s 2
1361 ''' % self.branch_identities
1362 base_branch = self.get_recipe(recipe_text).builder_recipe
1363 self.check_base_recipe_branch(
1364- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
1365- revspec="revid:a", deb_version='0.1-{revno}')
1366+ base_branch, self.getRepository(self.base_branch).identity,
1367+ num_child_branches=2, revspec="revid:a", deb_version='0.1-{revno}')
1368 instruction = base_branch.child_branches[0]
1369 child_branch = instruction.recipe_branch
1370 location = instruction.nest_path
1371 self.assertEqual("baz", location)
1372 self.check_recipe_branch(
1373- child_branch, "bar", self.nested_branch.bzr_identity,
1374- revspec="tag:b")
1375+ child_branch, "bar",
1376+ self.getRepository(self.nested_branch).identity, revspec="tag:b")
1377 child_branch, location = base_branch.child_branches[1].as_tuple()
1378 self.assertEqual(None, location)
1379 self.check_recipe_branch(
1380- child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")
1381+ child_branch, "zam",
1382+ self.getRepository(self.merged_branch).identity, revspec="2")
1383
1384 def test_unsets_revspecs(self):
1385 # Changing a recipe's text to no longer include revspecs unsets
1386 # them from the stored copy.
1387 revspec_text = '''\
1388- # bzr-builder format 0.3 deb-version 0.1-{revno}
1389+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1390 %(base)s revid:a
1391 nest bar %(nested)s baz tag:b
1392 merge zam %(merged)s 2
1393 ''' % self.branch_identities
1394 no_revspec_text = '''\
1395- # bzr-builder format 0.3 deb-version 0.1-{revno}
1396+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1397 %(base)s
1398 nest bar %(nested)s baz
1399 merge zam %(merged)s
1400@@ -952,18 +1048,29 @@
1401
1402 def test_builds_recipe_without_debversion(self):
1403 recipe_text = '''\
1404- # bzr-builder format 0.4
1405+ # %(recipe_id)s format 0.4
1406 %(base)s
1407 nest bar %(nested)s baz
1408 ''' % self.branch_identities
1409 base_branch = self.get_recipe(recipe_text).builder_recipe
1410 self.check_base_recipe_branch(
1411- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1412- deb_version=None)
1413+ base_branch, self.getRepository(self.base_branch).identity,
1414+ num_child_branches=1, deb_version=None)
1415 child_branch, location = base_branch.child_branches[0].as_tuple()
1416 self.assertEqual("baz", location)
1417 self.check_recipe_branch(
1418- child_branch, "bar", self.nested_branch.bzr_identity)
1419+ child_branch, "bar",
1420+ self.getRepository(self.nested_branch).identity)
1421+
1422+
1423+class TestRecipeBranchRoundTrippingBzr(
1424+ TestRecipeBranchRoundTrippingMixin, BzrMixin, TestCaseWithFactory):
1425+ pass
1426+
1427+
1428+class TestRecipeBranchRoundTrippingGit(
1429+ TestRecipeBranchRoundTrippingMixin, GitMixin, TestCaseWithFactory):
1430+ pass
1431
1432
1433 class RecipeDateLastModified(TestCaseWithFactory):
1434
1435=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
1436--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2015-10-19 10:56:16 +0000
1437+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2016-01-12 17:27:35 +0000
1438@@ -1,4 +1,4 @@
1439-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
1440+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
1441 # GNU Affero General Public License version 3 (see the file LICENSE).
1442
1443 """Tests for source package builds."""
1444@@ -106,11 +106,11 @@
1445 self.assertEqual(bq, spb.buildqueue_record)
1446
1447 def test_title(self):
1448- # A recipe build's title currently consists of the base
1449- # branch's unique name.
1450+ # A recipe build's title currently consists of the base source
1451+ # location's unique name.
1452 spb = self.makeSourcePackageRecipeBuild()
1453 title = "%s recipe build in %s %s" % (
1454- spb.recipe.base_branch.unique_name, spb.distribution.name,
1455+ spb.recipe.base.unique_name, spb.distribution.name,
1456 spb.distroseries.name)
1457 self.assertEqual(spb.title, title)
1458
1459
1460=== modified file 'lib/lp/testing/factory.py'
1461--- lib/lp/testing/factory.py 2016-01-11 21:11:27 +0000
1462+++ lib/lp/testing/factory.py 2016-01-12 17:27:35 +0000
1463@@ -2,7 +2,7 @@
1464 # NOTE: The first line above must stay first; do not move the copyright
1465 # notice to the top. See http://www.python.org/dev/peps/pep-0263/.
1466 #
1467-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
1468+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
1469 # GNU Affero General Public License version 3 (see the file LICENSE).
1470
1471 """Testing infrastructure for the Launchpad application.
1472@@ -112,6 +112,7 @@
1473 RevisionControlSystems,
1474 )
1475 from lp.code.errors import UnknownBranchTypeError
1476+from lp.code.interfaces.branch import IBranch
1477 from lp.code.interfaces.branchnamespace import get_branch_namespace
1478 from lp.code.interfaces.branchtarget import IBranchTarget
1479 from lp.code.interfaces.codeimport import ICodeImportSet
1480@@ -119,11 +120,13 @@
1481 from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
1482 from lp.code.interfaces.codeimportresult import ICodeImportResultSet
1483 from lp.code.interfaces.gitnamespace import get_git_namespace
1484+from lp.code.interfaces.gitref import IGitRef
1485 from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
1486 from lp.code.interfaces.revision import IRevisionSet
1487 from lp.code.interfaces.sourcepackagerecipe import (
1488 ISourcePackageRecipeSource,
1489 MINIMAL_RECIPE_TEXT_BZR,
1490+ MINIMAL_RECIPE_TEXT_GIT,
1491 )
1492 from lp.code.interfaces.sourcepackagerecipebuild import (
1493 ISourcePackageRecipeBuildSource,
1494@@ -2897,9 +2900,22 @@
1495 branches = (self.makeAnyBranch(), )
1496 base_branch = branches[0]
1497 other_branches = branches[1:]
1498- text = MINIMAL_RECIPE_TEXT_BZR % base_branch.bzr_identity
1499+ if IBranch.providedBy(base_branch):
1500+ text = MINIMAL_RECIPE_TEXT_BZR % base_branch.identity
1501+ elif IGitRef.providedBy(base_branch):
1502+ text = MINIMAL_RECIPE_TEXT_GIT % (
1503+ base_branch.repository.identity, base_branch.name)
1504+ else:
1505+ raise AssertionError(
1506+ "Unsupported base_branch: %r" % (base_branch,))
1507 for i, branch in enumerate(other_branches):
1508- text += 'merge dummy-%s %s\n' % (i, branch.bzr_identity)
1509+ if IBranch.providedBy(branch):
1510+ text += 'merge dummy-%s %s\n' % (i, branch.identity)
1511+ elif IGitRef.providedBy(branch):
1512+ text += 'merge dummy-%s %s %s\n' % (
1513+ i, branch.repository.identity, branch.name)
1514+ else:
1515+ raise AssertionError("Unsupported branch: %r" % (branch,))
1516 return text
1517
1518 def makeRecipe(self, *branches):