Merge lp:~mwhudson/launchpad/recipe-model-code into lp:launchpad/db-devel

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Michael Hudson-Doyle
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~mwhudson/launchpad/recipe-model-code
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~mwhudson/launchpad/recipe-db-schema
Diff against target: 837 lines (+799/-0)
6 files modified
lib/lp/soyuz/configure.zcml (+30/-0)
lib/lp/soyuz/interfaces/sourcepackagerecipe.py (+86/-0)
lib/lp/soyuz/model/sourcepackagerecipe.py (+82/-0)
lib/lp/soyuz/model/sourcepackagerecipedata.py (+230/-0)
lib/lp/soyuz/tests/test_sourcepackagerecipe.py (+370/-0)
utilities/sourcedeps.conf (+1/-0)
To merge this branch: bzr merge lp:~mwhudson/launchpad/recipe-model-code
Reviewer Review Type Date Requested Status
Paul Hummer (community) code Approve
Jonathan Lange (community) Needs Resubmitting
Review via email: mp+16272@code.launchpad.net

Commit message

Model classes and code for storing bzr-builder recipes in the database

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Hi there,

This branch adds the first content class for the sourcepackagerecipe stuff -- the SourcePacakgeRecipe class.

It doesn't do anything fancy at all wrt storing the recipe -- that can come later.

This can't be landed until a LOSA creates the bzr-builder sourcecode branch -- "bzr co lp:bzr-builder sourcecode/" until then.

Cheers,
mwh

Revision history for this message
Jonathan Lange (jml) wrote :

This is going to have to change as a result of the schema changes, I think.

review: Needs Resubmitting
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

> This is going to have to change as a result of the schema changes, I think.

And now it has. The guts of this branch is converting bzr-builder's view of a recipe into and out of the database views. The tests are copy-paste-hacked from bzr-builder's own tests -- it's possible we could/should submit a patch to bzr-builder that would let us reuse bzr-builders tests. But first we'd like to land this :-)

Revision history for this message
Jonathan Lange (jml) wrote :

On Tue, Jan 12, 2010 at 11:46 AM, Michael Hudson
<email address hidden> wrote:
>> This is going to have to change as a result of the schema changes, I think.
>
> And now it has.  The guts of this branch is converting bzr-builder's view of a recipe into and out of the database views.  The tests are copy-paste-hacked from bzr-builder's own tests -- it's possible we could/should submit a patch to bzr-builder that would let us reuse bzr-builders tests.  But first we'd like to land this :-)

Does that mean you want this reviewed again?

jml

Revision history for this message
Paul Hummer (rockstar) wrote :

Hi Michael-

  This branch is quite big, and has a lot of really hairy things going on. I know all of this has been discussed on the mailing list, but that's a terrible place to document things. Has anyone put any effort into saying "This is how this works and how you work with it?" Just something to think about.

  This branch looks good. As we discussed, ForbiddenInstruction's docstring needs to be updated to swap "unsupported" for "forbidden." Also, there are a few tests in TestSourcePackageRecipe, and all the methods of TestRecipeBranchRoundTripping that are missing comments/docstrings/some sort of "this is what this test is doing."

Cheers,
Paul

review: Approve
Revision history for this message
Paul Hummer (rockstar) wrote :

_SourcePackageRecipeData has two definitions of sourcepackage_recipe_build, one of which is commented out. Other than that, still good.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added symlink 'bzrplugins/builder'
=== target is u'../sourcecode/bzr-builder'
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2010-01-13 04:41:19 +0000
+++ lib/lp/soyuz/configure.zcml 2010-01-13 20:46:22 +0000
@@ -915,4 +915,34 @@
915 factory="lp.soyuz.model.binarypackagebuildbehavior.BinaryPackageBuildBehavior"915 factory="lp.soyuz.model.binarypackagebuildbehavior.BinaryPackageBuildBehavior"
916 permission="zope.Public" />916 permission="zope.Public" />
917917
918 <!-- SourcePackageRecipe -->
919 <class
920 class="lp.soyuz.model.sourcepackagerecipe.SourcePackageRecipe">
921 <allow interface="lp.soyuz.interfaces.sourcepackagerecipe.ISourcePackageRecipe"/>
922 <require
923 permission="launchpad.Edit"
924 set_attributes="builder_recipe"/>
925 </class>
926 <class
927 class="bzrlib.plugins.builder.recipe.BaseRecipeBranch">
928 <allow attributes="name url revspec revid child_branches deb_version" />
929 </class>
930 <class
931 class="bzrlib.plugins.builder.recipe.RecipeBranch">
932 <allow attributes="name url revspec revid child_branches" />
933 </class>
934 <class
935 class="bzrlib.plugins.builder.recipe.MergeInstruction">
936 <allow attributes="as_tuple recipe_branch nest_path" />
937 </class>
938 <class
939 class="bzrlib.plugins.builder.recipe.NestInstruction">
940 <allow attributes="as_tuple recipe_branch nest_path" />
941 </class>
942 <securedutility
943 component="lp.soyuz.model.sourcepackagerecipe.SourcePackageRecipe"
944 provides="lp.soyuz.interfaces.sourcepackagerecipe.ISourcePackageRecipeSource">
945 <allow interface="lp.soyuz.interfaces.sourcepackagerecipe.ISourcePackageRecipeSource"/>
946 </securedutility>
947
918</configure>948</configure>
919949
=== added file 'lib/lp/soyuz/interfaces/sourcepackagerecipe.py'
--- lib/lp/soyuz/interfaces/sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/interfaces/sourcepackagerecipe.py 2010-01-13 20:46:22 +0000
@@ -0,0 +1,86 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Interface of the `SourcePackageRecipe` content type."""
5
6__metaclass__ = type
7__all__ = [
8 'ForbiddenInstruction',
9 'ISourcePackageRecipe',
10 'ISourcePackageRecipeSource',
11 'TooNewRecipeFormat',
12 ]
13
14from lazr.restful.fields import Reference
15
16from zope.interface import Attribute, Interface
17
18from zope.schema import Datetime, TextLine
19
20from canonical.launchpad import _
21from canonical.launchpad.validators.name import name_validator
22
23from lp.registry.interfaces.person import IPerson
24from lp.registry.interfaces.role import IHasOwner
25from lp.registry.interfaces.distroseries import IDistroSeries
26from lp.registry.interfaces.sourcepackagename import ISourcePackageName
27
28
29class ForbiddenInstruction(Exception):
30 """A forbidden instruction was found in the recipe."""
31
32 def __init__(self, instruction_name):
33 self.instruction_name = instruction_name
34
35
36class TooNewRecipeFormat(Exception):
37 """The format of the recipe supplied was too new."""
38
39 def __init__(self, supplied_format, newest_supported):
40 self.supplied_format = supplied_format
41 self.newest_supported = newest_supported
42
43
44class ISourcePackageRecipe(IHasOwner):
45 """An ISourcePackageRecipe describes how to build a source package.
46
47 More precisely, it describes how to combine a number of branches into a
48 debianized source tree.
49 """
50
51 date_created = Datetime(required=True, readonly=True)
52 date_last_modified = Datetime(required=True, readonly=True)
53
54 registrant = Reference(
55 IPerson, title=_("The person who created this recipe"), readonly=True)
56 owner = Reference(
57 IPerson, title=_("The person or team who can edit this recipe"),
58 readonly=False)
59 distroseries = Reference(
60 IDistroSeries, title=_("The distroseries this recipe will build a "
61 "source package for"),
62 readonly=True)
63 sourcepackagename = Reference(
64 ISourcePackageName, title=_("The name of the source package this "
65 "recipe will build a source package"),
66 readonly=True)
67
68 name = TextLine(
69 title=_("Name"), required=True,
70 constraint=name_validator,
71 description=_("The name of this recipe."))
72
73 builder_recipe = Attribute(
74 _("The bzr-builder data structure for the recipe."))
75
76 def getReferencedBranches():
77 """An iterator of the branches referenced by this recipe."""
78
79
80class ISourcePackageRecipeSource(Interface):
81 """A utility of this interface can be used to create and access recipes.
82 """
83
84 def new(registrant, owner, distroseries, sourcepackagename, name,
85 builder_recipe):
86 """Create an `ISourcePackageRecipe`."""
087
=== added file 'lib/lp/soyuz/model/sourcepackagerecipe.py'
--- lib/lp/soyuz/model/sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/model/sourcepackagerecipe.py 2010-01-13 20:46:22 +0000
@@ -0,0 +1,82 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Implementation of the `SourcePackageRecipe` content type."""
5
6__metaclass__ = type
7__all__ = ['SourcePackageRecipe']
8
9from storm.locals import Int, Reference, Store, Storm, Unicode
10
11from zope.interface import classProvides, implements
12
13from canonical.database.datetimecol import UtcDateTimeCol
14from canonical.launchpad.interfaces.lpstorm import IMasterStore
15
16from lp.soyuz.interfaces.sourcepackagerecipe import (
17 ISourcePackageRecipe, ISourcePackageRecipeSource)
18from lp.soyuz.model.sourcepackagerecipedata import _SourcePackageRecipeData
19
20
21class SourcePackageRecipe(Storm):
22 """See `ISourcePackageRecipe` and `ISourcePackageRecipeSource`."""
23
24 __storm_table__ = 'SourcePackageRecipe'
25
26 implements(ISourcePackageRecipe)
27 classProvides(ISourcePackageRecipeSource)
28
29 id = Int(primary=True)
30
31 date_created = UtcDateTimeCol(notNull=True)
32 date_last_modified = UtcDateTimeCol(notNull=True)
33
34 owner_id = Int(name='owner', allow_none=True)
35 owner = Reference(owner_id, 'Person.id')
36
37 registrant_id = Int(name='registrant', allow_none=True)
38 registrant = Reference(registrant_id, 'Person.id')
39
40 distroseries_id = Int(name='distroseries', allow_none=True)
41 distroseries = Reference(distroseries_id, 'DistroSeries.id')
42
43 sourcepackagename_id = Int(name='sourcepackagename', allow_none=True)
44 sourcepackagename = Reference(
45 sourcepackagename_id, 'SourcePackageName.id')
46
47 name = Unicode(allow_none=True)
48
49 @property
50 def _recipe_data(self):
51 return Store.of(self).find(
52 _SourcePackageRecipeData,
53 _SourcePackageRecipeData.sourcepackage_recipe == self).one()
54
55 def _get_builder_recipe(self):
56 """Accesses of the recipe go to the _SourcePackageRecipeData."""
57 return self._recipe_data.getRecipe()
58
59 def _set_builder_recipe(self, value):
60 """Setting of the recipe goes to the _SourcePackageRecipeData."""
61 self._recipe_data.setRecipe(value)
62
63 builder_recipe = property(_get_builder_recipe, _set_builder_recipe)
64
65 def getReferencedBranches(self):
66 """See `ISourcePackageRecipe.getReferencedBranches`."""
67 return self._recipe_data.getReferencedBranches()
68
69 @staticmethod
70 def new(registrant, owner, distroseries, sourcepackagename, name,
71 builder_recipe):
72 """See `ISourcePackageRecipeSource.new`."""
73 store = IMasterStore(SourcePackageRecipe)
74 sprecipe = SourcePackageRecipe()
75 _SourcePackageRecipeData(builder_recipe, sprecipe)
76 sprecipe.registrant = registrant
77 sprecipe.owner = owner
78 sprecipe.distroseries = distroseries
79 sprecipe.sourcepackagename = sourcepackagename
80 sprecipe.name = name
81 store.add(sprecipe)
82 return sprecipe
083
=== added file 'lib/lp/soyuz/model/sourcepackagerecipedata.py'
--- lib/lp/soyuz/model/sourcepackagerecipedata.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/model/sourcepackagerecipedata.py 2010-01-13 20:46:22 +0000
@@ -0,0 +1,230 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Implementation of the recipe storage.
5
6This is purely an implementation detail of SourcePackageRecipe.recipe_data and
7SourcePackageRecipeBuild.manifest, the classes in this file have no public
8interfaces.
9"""
10
11__metaclass__ = type
12__all__ = ['_SourcePackageRecipeData']
13
14from bzrlib.plugins.builder.recipe import (
15 BaseRecipeBranch, MergeInstruction, NestInstruction, RecipeBranch)
16
17from lazr.enum import DBEnumeratedType, DBItem
18
19from storm.locals import Int, Reference, ReferenceSet, Store, Storm, Unicode
20
21from zope.component import getUtility
22
23from canonical.database.enumcol import EnumCol
24from canonical.launchpad.interfaces.lpstorm import IStore
25
26from lp.code.model.branch import Branch
27from lp.code.interfaces.branchlookup import IBranchLookup
28from lp.soyuz.interfaces.sourcepackagerecipe import (
29 ForbiddenInstruction, TooNewRecipeFormat)
30
31
32class InstructionType(DBEnumeratedType):
33 """The instruction type, for _SourcePackageRecipeDataInstruction.type."""
34
35 MERGE = DBItem(1, """
36 Merge instruction
37
38 A merge instruction.""")
39
40 NEST = DBItem(2, """
41 Nest instruction
42
43 A nest instruction.""")
44
45
46class _SourcePackageRecipeDataInstruction(Storm):
47 """A single line from a recipe."""
48
49 __storm_table__ = "SourcePackageRecipeDataInstruction"
50
51 def __init__(self, name, type, comment, line_number, branch, revspec,
52 directory, recipe_data, parent_instruction):
53 self.name = unicode(name)
54 self.type = type
55 self.comment = comment
56 self.line_number = line_number
57 self.branch = branch
58 if revspec is not None:
59 revspec = unicode(revspec)
60 self.revspec = revspec
61 if directory is not None:
62 directory = unicode(directory)
63 self.directory = directory
64 self.recipe_data = recipe_data
65 self.parent_instruction = parent_instruction
66
67 id = Int(primary=True)
68
69 name = Unicode(allow_none=False)
70 type = EnumCol(notNull=True, schema=InstructionType)
71 comment = Unicode(allow_none=True)
72 line_number = Int(allow_none=False)
73
74 branch_id = Int(name='branch', allow_none=False)
75 branch = Reference(branch_id, 'Branch.id')
76
77 revspec = Unicode(allow_none=True)
78 directory = Unicode(allow_none=True)
79
80 recipe_data_id = Int(name='recipe_data', allow_none=False)
81 recipe_data = Reference(recipe_data_id, '_SourcePackageRecipeData.id')
82
83 parent_instruction_id = Int(name='parent_instruction', allow_none=True)
84 parent_instruction = Reference(
85 parent_instruction_id, '_SourcePackageRecipeDataInstruction.id')
86
87 def appendToRecipe(self, recipe_branch):
88 """Append a bzr-builder instruction to the recipe_branch object."""
89 branch = RecipeBranch(
90 self.name, self.branch.bzr_identity, self.revspec)
91 if self.type == InstructionType.MERGE:
92 recipe_branch.merge_branch(branch)
93 elif self.type == InstructionType.NEST:
94 recipe_branch.nest_branch(self.directory, branch)
95 else:
96 raise AssertionError("Unknown type %r" % self.type)
97 return branch
98
99
100class _SourcePackageRecipeData(Storm):
101 """The database representation of a BaseRecipeBranch from bzr-builder.
102
103 This is referenced from the SourcePackageRecipe table as the 'recipe_data'
104 column and from the SourcePackageRecipeBuild table as the 'manifest'
105 column.
106 """
107
108 __storm_table__ = "SourcePackageRecipeData"
109
110 id = Int(primary=True)
111
112 base_branch_id = Int(name='base_branch', allow_none=False)
113 base_branch = Reference(base_branch_id, 'Branch.id')
114
115 recipe_format = Unicode(allow_none=False)
116 deb_version_template = Unicode(allow_none=False)
117 revspec = Unicode(allow_none=True)
118
119 instructions = ReferenceSet(
120 id, _SourcePackageRecipeDataInstruction.recipe_data_id,
121 order_by=_SourcePackageRecipeDataInstruction.line_number)
122
123 sourcepackage_recipe_id = Int(
124 name='sourcepackage_recipe', allow_none=True)
125 sourcepackage_recipe = Reference(
126 sourcepackage_recipe_id, 'SourcePackageRecipe.id')
127
128 sourcepackage_recipe_build_id = Int(
129 name='sourcepackage_recipe_build', allow_none=True)
130 #sourcepackage_recipe_build = Reference(
131 # sourcepackage_recipe_build_id, 'SourcePackageRecipeBuild.id')
132
133 def getRecipe(self):
134 """The BaseRecipeBranch version of the recipe."""
135 base_branch = BaseRecipeBranch(
136 self.base_branch.bzr_identity, self.deb_version_template,
137 self.recipe_format, self.revspec)
138 insn_stack = []
139 for insn in self.instructions:
140 while insn_stack and \
141 insn_stack[-1]['insn'] != insn.parent_instruction:
142 insn_stack.pop()
143 if insn_stack:
144 target_branch = insn_stack[-1]['recipe_branch']
145 else:
146 target_branch = base_branch
147 recipe_branch = insn.appendToRecipe(target_branch)
148 insn_stack.append(
149 dict(insn=insn, recipe_branch=recipe_branch))
150 return base_branch
151
152 def _scanInstructions(self, recipe_branch):
153 """Check the recipe_branch doesn't use 'run' and look up the branches.
154
155 We do all the lookups before we start constructing database objects to
156 avoid flushing half-constructed objects to the database.
157
158 :return: A map ``{branch_url: db_branch}``.
159 """
160 r = {}
161 for instruction in recipe_branch.child_branches:
162 if not (isinstance(instruction, MergeInstruction) or
163 isinstance(instruction, NestInstruction)):
164 raise ForbiddenInstruction(str(instruction))
165 db_branch = getUtility(IBranchLookup).getByUrl(
166 instruction.recipe_branch.url)
167 r[instruction.recipe_branch.url] = db_branch
168 r.update(self._scanInstructions(instruction.recipe_branch))
169 return r
170
171 def _recordInstructions(self, recipe_branch, parent_insn, branch_map,
172 line_number=0):
173 """Build _SourcePackageRecipeDataInstructions for the recipe_branch.
174 """
175 for instruction in recipe_branch.child_branches:
176 if isinstance(instruction, MergeInstruction):
177 type = InstructionType.MERGE
178 elif isinstance(instruction, NestInstruction):
179 type = InstructionType.NEST
180 else:
181 # Unsupported instructions should have been filtered out by
182 # _scanInstructions; if we get surprised here, that's a bug.
183 raise AssertionError(
184 "Unsupported instruction %r" % instruction)
185 line_number += 1
186 comment = None
187 db_branch = branch_map[instruction.recipe_branch.url]
188 insn = _SourcePackageRecipeDataInstruction(
189 instruction.recipe_branch.name, type, comment,
190 line_number, db_branch, instruction.recipe_branch.revspec,
191 instruction.nest_path, self, parent_insn)
192 line_number = self._recordInstructions(
193 instruction.recipe_branch, insn, branch_map, line_number)
194 return line_number
195
196 def setRecipe(self, builder_recipe):
197 """Convert the BaseRecipeBranch `builder_recipe` to the db form."""
198 if builder_recipe.format > 0.2:
199 raise TooNewRecipeFormat(builder_recipe.format, 0.2)
200 branch_map = self._scanInstructions(builder_recipe)
201 # If this object hasn't been added to a store yet, there can't be any
202 # instructions linking to us yet.
203 if Store.of(self) is not None:
204 self.instructions.find().remove()
205 branch_lookup = getUtility(IBranchLookup)
206 base_branch = branch_lookup.getByUrl(builder_recipe.url)
207 if builder_recipe.revspec is not None:
208 self.revspec = unicode(builder_recipe.revspec)
209 self._recordInstructions(
210 builder_recipe, parent_insn=None, branch_map=branch_map)
211 self.base_branch = base_branch
212 self.deb_version_template = unicode(builder_recipe.deb_version)
213 self.recipe_format = unicode(builder_recipe.format)
214
215 def __init__(self, recipe, sourcepackage_recipe):
216 """Initialize from the bzr-builder recipe and link it to a db recipe.
217 """
218 self.setRecipe(recipe)
219 self.sourcepackage_recipe = sourcepackage_recipe
220
221 def getReferencedBranches(self):
222 """Return an iterator of the Branch objects referenced by this recipe.
223 """
224 yield self.base_branch
225 sub_branches = IStore(self).find(
226 Branch,
227 _SourcePackageRecipeDataInstruction.recipe_data == self,
228 Branch.id == _SourcePackageRecipeDataInstruction.branch_id)
229 for branch in sub_branches:
230 yield branch
0231
=== added file 'lib/lp/soyuz/tests/test_sourcepackagerecipe.py'
--- lib/lp/soyuz/tests/test_sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/tests/test_sourcepackagerecipe.py 2010-01-13 20:46:22 +0000
@@ -0,0 +1,370 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the SourcePackageRecipe content type."""
5
6__metaclass__ = type
7
8import textwrap
9import unittest
10
11from bzrlib.plugins.builder.recipe import RecipeParser
12
13from zope.component import getUtility
14from zope.security.interfaces import Unauthorized
15
16from canonical.testing.layers import DatabaseFunctionalLayer
17
18from lp.soyuz.interfaces.sourcepackagerecipe import (
19 ForbiddenInstruction, ISourcePackageRecipe, ISourcePackageRecipeSource,
20 TooNewRecipeFormat)
21from lp.testing import login_person, TestCaseWithFactory
22
23
24MINIMAL_RECIPE_TEXT = u'''\
25# bzr-builder format 0.2 deb-version 1.0
26%s
27'''
28
29class TestSourcePackageRecipe(TestCaseWithFactory):
30 """Tests for `SourcePackageRecipe` objects."""
31
32 layer = DatabaseFunctionalLayer
33
34 def makeBuilderRecipe(self, *branches):
35 """Make a builder recipe that references `branches`.
36
37 If no branches are passed, return a recipe text that references an
38 arbitrary branch.
39 """
40 if len(branches) == 0:
41 branches = (self.factory.makeAnyBranch(),)
42 base_branch = branches[0]
43 other_branches = branches[1:]
44 text = MINIMAL_RECIPE_TEXT % base_branch.bzr_identity
45 for i, branch in enumerate(other_branches):
46 text += 'merge dummy-%s %s\n' % (i, branch.bzr_identity)
47 parser = RecipeParser(text)
48 return parser.parse()
49
50 def makeSourcePackageRecipeFromBuilderRecipe(self, builder_recipe):
51 """Make a SourcePackageRecipe from a recipe with arbitrary other data.
52 """
53 registrant = self.factory.makePerson()
54 owner = self.factory.makeTeam(owner=registrant)
55 distroseries = self.factory.makeDistroSeries()
56 sourcepackagename = self.factory.makeSourcePackageName()
57 name = self.factory.getUniqueString(u'recipe-name')
58 return getUtility(ISourcePackageRecipeSource).new(
59 registrant=registrant, owner=owner, distroseries=distroseries,
60 sourcepackagename=sourcepackagename, name=name,
61 builder_recipe=builder_recipe)
62
63 def test_creation(self):
64 # The metadata supplied when a SourcePackageRecipe is created is
65 # present on the new object.
66 registrant = self.factory.makePerson()
67 owner = self.factory.makeTeam(owner=registrant)
68 distroseries = self.factory.makeDistroSeries()
69 sourcepackagename = self.factory.makeSourcePackageName()
70 name = self.factory.getUniqueString(u'recipe-name')
71 builder_recipe = self.makeBuilderRecipe()
72 recipe = getUtility(ISourcePackageRecipeSource).new(
73 registrant=registrant, owner=owner, distroseries=distroseries,
74 sourcepackagename=sourcepackagename, name=name,
75 builder_recipe=builder_recipe)
76 self.assertEquals(
77 (registrant, owner, distroseries, sourcepackagename, name),
78 (recipe.registrant, recipe.owner, recipe.distroseries,
79 recipe.sourcepackagename, recipe.name))
80
81 def test_source_implements_interface(self):
82 # The SourcePackageRecipe class implements ISourcePackageRecipeSource.
83 self.assertProvides(
84 getUtility(ISourcePackageRecipeSource),
85 ISourcePackageRecipeSource)
86
87 def test_recipe_implements_interface(self):
88 # SourcePackageRecipe objects implement ISourcePackageRecipe.
89 recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
90 self.makeBuilderRecipe())
91 self.assertProvides(recipe, ISourcePackageRecipe)
92
93 def test_branch_links_created(self):
94 # When a recipe is created, we can query it for links to the branch
95 # it references.
96 branch = self.factory.makeAnyBranch()
97 builder_recipe = self.makeBuilderRecipe(branch)
98 sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
99 builder_recipe)
100 self.assertEquals([branch], list(sp_recipe.getReferencedBranches()))
101
102 def test_multiple_branch_links_created(self):
103 # If a recipe links to more than one branch, getReferencedBranches()
104 # returns all of them.
105 branch1 = self.factory.makeAnyBranch()
106 branch2 = self.factory.makeAnyBranch()
107 builder_recipe = self.makeBuilderRecipe(branch1, branch2)
108 sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
109 builder_recipe)
110 self.assertEquals(
111 sorted([branch1, branch2]),
112 sorted(sp_recipe.getReferencedBranches()))
113
114 def test_random_user_cant_edit(self):
115 # An arbitrary user can't set attributes.
116 branch1 = self.factory.makeAnyBranch()
117 builder_recipe1 = self.makeBuilderRecipe(branch1)
118 sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
119 builder_recipe1)
120 branch2 = self.factory.makeAnyBranch()
121 builder_recipe2 = self.makeBuilderRecipe(branch2)
122 login_person(self.factory.makePerson())
123 self.assertRaises(
124 Unauthorized, setattr, sp_recipe, 'builder_recipe',
125 builder_recipe2)
126
127 def test_set_recipe_text_resets_branch_references(self):
128 # When the recipe_text is replaced, getReferencedBranches returns
129 # (only) the branches referenced by the new recipe.
130 branch1 = self.factory.makeAnyBranch()
131 builder_recipe1 = self.makeBuilderRecipe(branch1)
132 sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
133 builder_recipe1)
134 branch2 = self.factory.makeAnyBranch()
135 builder_recipe2 = self.makeBuilderRecipe(branch2)
136 login_person(sp_recipe.owner.teamowner)
137 #import pdb; pdb.set_trace()
138 sp_recipe.builder_recipe = builder_recipe2
139 self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
140
141 def test_rejects_run_command(self):
142 recipe_text = '''\
143 # bzr-builder format 0.2 deb-version 0.1-{revno}
144 %(base)s
145 run touch test
146 ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
147 parser = RecipeParser(textwrap.dedent(recipe_text))
148 builder_recipe = parser.parse()
149 self.assertRaises(
150 ForbiddenInstruction,
151 self.makeSourcePackageRecipeFromBuilderRecipe, builder_recipe)
152
153 def test_run_rejected_without_mangling_recipe(self):
154 branch1 = self.factory.makeAnyBranch()
155 builder_recipe1 = self.makeBuilderRecipe(branch1)
156 sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
157 builder_recipe1)
158 recipe_text = '''\
159 # bzr-builder format 0.2 deb-version 0.1-{revno}
160 %(base)s
161 run touch test
162 ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
163 parser = RecipeParser(textwrap.dedent(recipe_text))
164 builder_recipe2 = parser.parse()
165 login_person(sp_recipe.owner.teamowner)
166 self.assertRaises(
167 ForbiddenInstruction, setattr, sp_recipe, 'builder_recipe',
168 builder_recipe2)
169 self.assertEquals([branch1], list(sp_recipe.getReferencedBranches()))
170
171 def test_reject_newer_formats(self):
172 builder_recipe = self.makeBuilderRecipe()
173 builder_recipe.format = 0.3
174 self.assertRaises(
175 TooNewRecipeFormat,
176 self.makeSourcePackageRecipeFromBuilderRecipe, builder_recipe)
177
178class TestRecipeBranchRoundTripping(TestCaseWithFactory):
179
180 layer = DatabaseFunctionalLayer
181
182 def setUp(self):
183 super(TestRecipeBranchRoundTripping, self).setUp()
184 self.base_branch = self.factory.makeAnyBranch()
185 self.nested_branch = self.factory.makeAnyBranch()
186 self.merged_branch = self.factory.makeAnyBranch()
187 self.branch_identities = {
188 'base': self.base_branch.bzr_identity,
189 'nested': self.nested_branch.bzr_identity,
190 'merged': self.merged_branch.bzr_identity,
191 }
192
193 def get_recipe(self, recipe_text):
194 builder_recipe = RecipeParser(textwrap.dedent(recipe_text)).parse()
195 registrant = self.factory.makePerson()
196 owner = self.factory.makeTeam(owner=registrant)
197 distroseries = self.factory.makeDistroSeries()
198 sourcepackagename = self.factory.makeSourcePackageName()
199 name = self.factory.getUniqueString(u'recipe-name')
200 recipe = getUtility(ISourcePackageRecipeSource).new(
201 registrant=registrant, owner=owner, distroseries=distroseries,
202 sourcepackagename=sourcepackagename, name=name,
203 builder_recipe=builder_recipe)
204 return recipe.builder_recipe
205
206 def check_base_recipe_branch(self, branch, url, revspec=None,
207 num_child_branches=0, revid=None, deb_version=None):
208 self.check_recipe_branch(branch, None, url, revspec=revspec,
209 num_child_branches=num_child_branches, revid=revid)
210 self.assertEqual(deb_version, branch.deb_version)
211
212 def check_recipe_branch(self, branch, name, url, revspec=None,
213 num_child_branches=0, revid=None):
214 self.assertEqual(name, branch.name)
215 self.assertEqual(url, branch.url)
216 self.assertEqual(revspec, branch.revspec)
217 self.assertEqual(revid, branch.revid)
218 self.assertEqual(num_child_branches, len(branch.child_branches))
219
220 def test_builds_simplest_recipe(self):
221 recipe_text = '''\
222 # bzr-builder format 0.2 deb-version 0.1-{revno}
223 %(base)s
224 ''' % self.branch_identities
225 base_branch = self.get_recipe(recipe_text)
226 self.check_base_recipe_branch(
227 base_branch, self.base_branch.bzr_identity,
228 deb_version='0.1-{revno}')
229
230 def test_builds_recipe_with_merge(self):
231 recipe_text = '''\
232 # bzr-builder format 0.2 deb-version 0.1-{revno}
233 %(base)s
234 merge bar %(merged)s
235 ''' % self.branch_identities
236 base_branch = self.get_recipe(recipe_text)
237 self.check_base_recipe_branch(
238 base_branch, self.base_branch.bzr_identity, num_child_branches=1,
239 deb_version='0.1-{revno}')
240 child_branch, location = base_branch.child_branches[0].as_tuple()
241 self.assertEqual(None, location)
242 self.check_recipe_branch(
243 child_branch, "bar", self.merged_branch.bzr_identity)
244
245 def test_builds_recipe_with_nest(self):
246 recipe_text = '''\
247 # bzr-builder format 0.2 deb-version 0.1-{revno}
248 %(base)s
249 nest bar %(nested)s baz
250 ''' % self.branch_identities
251 base_branch = self.get_recipe(recipe_text)
252 self.check_base_recipe_branch(
253 base_branch, self.base_branch.bzr_identity, num_child_branches=1,
254 deb_version='0.1-{revno}')
255 child_branch, location = base_branch.child_branches[0].as_tuple()
256 self.assertEqual("baz", location)
257 self.check_recipe_branch(
258 child_branch, "bar", self.nested_branch.bzr_identity)
259
260 def test_builds_recipe_with_nest_then_merge(self):
261 recipe_text = '''\
262 # bzr-builder format 0.2 deb-version 0.1-{revno}
263 %(base)s
264 nest bar %(nested)s baz
265 merge zam %(merged)s
266 ''' % self.branch_identities
267 base_branch = self.get_recipe(recipe_text)
268 self.check_base_recipe_branch(
269 base_branch, self.base_branch.bzr_identity, num_child_branches=2,
270 deb_version='0.1-{revno}')
271 child_branch, location = base_branch.child_branches[0].as_tuple()
272 self.assertEqual("baz", location)
273 self.check_recipe_branch(
274 child_branch, "bar", self.nested_branch.bzr_identity)
275 child_branch, location = base_branch.child_branches[1].as_tuple()
276 self.assertEqual(None, location)
277 self.check_recipe_branch(
278 child_branch, "zam", self.merged_branch.bzr_identity)
279
280 def test_builds_recipe_with_merge_then_nest(self):
281 recipe_text = '''\
282 # bzr-builder format 0.2 deb-version 0.1-{revno}
283 %(base)s
284 merge zam %(merged)s
285 nest bar %(nested)s baz
286 ''' % self.branch_identities
287 base_branch = self.get_recipe(recipe_text)
288 self.check_base_recipe_branch(
289 base_branch, self.base_branch.bzr_identity, num_child_branches=2,
290 deb_version='0.1-{revno}')
291 child_branch, location = base_branch.child_branches[0].as_tuple()
292 self.assertEqual(None, location)
293 self.check_recipe_branch(
294 child_branch, "zam", self.merged_branch.bzr_identity)
295 child_branch, location = base_branch.child_branches[1].as_tuple()
296 self.assertEqual("baz", location)
297 self.check_recipe_branch(
298 child_branch, "bar", self.nested_branch.bzr_identity)
299
300 def test_builds_a_merge_in_to_a_nest(self):
301 recipe_text = '''\
302 # bzr-builder format 0.2 deb-version 0.1-{revno}
303 %(base)s
304 nest bar %(nested)s baz
305 merge zam %(merged)s
306 ''' % self.branch_identities
307 base_branch = self.get_recipe(recipe_text)
308 self.check_base_recipe_branch(
309 base_branch, self.base_branch.bzr_identity, num_child_branches=1,
310 deb_version='0.1-{revno}')
311 child_branch, location = base_branch.child_branches[0].as_tuple()
312 self.assertEqual("baz", location)
313 self.check_recipe_branch(
314 child_branch, "bar", self.nested_branch.bzr_identity,
315 num_child_branches=1)
316 child_branch, location = child_branch.child_branches[0].as_tuple()
317 self.assertEqual(None, location)
318 self.check_recipe_branch(
319 child_branch, "zam", self.merged_branch.bzr_identity)
320
321 def tests_builds_nest_into_a_nest(self):
322 nested2 = self.factory.makeAnyBranch()
323 self.branch_identities['nested2'] = nested2.bzr_identity
324 recipe_text = '''\
325 # bzr-builder format 0.2 deb-version 0.1-{revno}
326 %(base)s
327 nest bar %(nested)s baz
328 nest zam %(nested2)s zoo
329 ''' % self.branch_identities
330 base_branch = self.get_recipe(recipe_text)
331 self.check_base_recipe_branch(
332 base_branch, self.base_branch.bzr_identity, num_child_branches=1,
333 deb_version='0.1-{revno}')
334 child_branch, location = base_branch.child_branches[0].as_tuple()
335 self.assertEqual("baz", location)
336 self.check_recipe_branch(
337 child_branch, "bar", self.nested_branch.bzr_identity,
338 num_child_branches=1)
339 child_branch, location = child_branch.child_branches[0].as_tuple()
340 self.assertEqual("zoo", location)
341 self.check_recipe_branch(child_branch, "zam", nested2.bzr_identity)
342
343 def tests_builds_recipe_with_revspecs(self):
344 recipe_text = '''\
345 # bzr-builder format 0.2 deb-version 0.1-{revno}
346 %(base)s revid:a
347 nest bar %(nested)s baz tag:b
348 merge zam %(merged)s 2
349 ''' % self.branch_identities
350 base_branch = self.get_recipe(recipe_text)
351 self.check_base_recipe_branch(
352 base_branch, self.base_branch.bzr_identity, num_child_branches=2,
353 revspec="revid:a", deb_version='0.1-{revno}')
354 instruction = base_branch.child_branches[0]
355 child_branch = instruction.recipe_branch
356 location = instruction.nest_path
357 self.assertEqual("baz", location)
358 self.check_recipe_branch(
359 child_branch, "bar", self.nested_branch.bzr_identity,
360 revspec="tag:b")
361 child_branch, location = base_branch.child_branches[1].as_tuple()
362 self.assertEqual(None, location)
363 self.check_recipe_branch(
364 child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")
365
366
367
368def test_suite():
369 return unittest.TestLoader().loadTestsFromName(__name__)
370
0371
=== modified file 'utilities/sourcedeps.conf'
--- utilities/sourcedeps.conf 2010-01-13 05:18:26 +0000
+++ utilities/sourcedeps.conf 2010-01-13 20:46:22 +0000
@@ -1,3 +1,4 @@
1bzr-builder lp:~launchpad-pqm/bzr-builder/trunk;revno=62
1bzr-git lp:~launchpad-pqm/bzr-git/devel;revno=2482bzr-git lp:~launchpad-pqm/bzr-git/devel;revno=248
2bzr-hg lp:~launchpad-pqm/bzr-hg/devel;revno=2813bzr-hg lp:~launchpad-pqm/bzr-hg/devel;revno=281
3bzr-loom lp:~launchpad-pqm/bzr-loom/trunk;revno=474bzr-loom lp:~launchpad-pqm/bzr-loom/trunk;revno=47

Subscribers

People subscribed via source and target branches

to status/vote changes: