Merge lp:~mwhudson/launchpad/branching-ubuntu into lp:launchpad

Proposed by Michael Hudson-Doyle
Status: Merged
Merged at revision: not available
Proposed branch: lp:~mwhudson/launchpad/branching-ubuntu
Merge into: lp:launchpad
Diff against target: 1129 lines
4 files modified
database/schema/security.cfg (+16/-0)
lib/lp/codehosting/branchdistro.py (+364/-0)
lib/lp/codehosting/tests/test_branchdistro.py (+685/-0)
scripts/branch-distro.py (+39/-0)
To merge this branch: bzr merge lp:~mwhudson/launchpad/branching-ubuntu
Reviewer Review Type Date Requested Status
Jonathan Lange (community) Approve
Review via email: mp+13269@code.launchpad.net

Commit message

A script to open a new distribution for branch-based development -- see https://dev.launchpad.net/BranchingUbuntu.

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

Hey Jono,

Finally I think this beast of a branch is ready (actually it's "only" 1123 lines, not too far over the limit :-).

It adds a script that carries out the procedure on https://dev.launchpad.net/BranchingUbuntu. It also adds a --check flag that verifies that a previous run of the script went ok. This was the only way I could convince myself to stop worrying about what would happen if the script crashed or was interrupted -- the idea is that if the script is interrupted we'll run the script again until it completes, then run branch-distro --check to find any damage that was left behind by the interrupted run.

I hope it makes sense, we've talked about this enough!

Cheers,
mwh

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

Oh, I guess I should say that we need to test this as much as we can on staging, I think this should be possible.

Revision history for this message
Jonathan Lange (jml) wrote :
Download full text (53.2 KiB)

Hey Michael,

Not much to say here, given that we've already talked about it. I look forward to seeing this QA'd on staging.

I have a few comments, one or two of which must be fixed before landing. Other than that, looks good. Thanks for getting this done.

jml

> === modified file 'database/schema/security.cfg'
> --- database/schema/security.cfg 2009-10-05 15:07:10 +0000
> +++ database/schema/security.cfg 2009-10-13 09:11:15 +0000
> @@ -641,6 +641,22 @@
> # Merge notifications
> public.codereviewvote = SELECT
>
> +[branch-distro]
> +type=user
> +public.branch = SELECT, INSERT
> +public.branchsubscription = SELECT, INSERT
> +public.distribution = SELECT
> +public.distroseries = SELECT
> +public.karma = SELECT, INSERT
> +public.karmaaction = SELECT
> +public.person = SELECT
> +public.product = SELECT
> +public.seriessourcepackagebranch = SELECT, INSERT, DELETE
> +public.sourcepackagename = SELECT
> +public.teamparticipation = SELECT
> +public.validpersoncache = SELECT
> +
> +

How did you determine these? Are you sure you've got everything?

> [targetnamecacheupdater]
> type=user
> groups=script
>

> === added file 'lib/lp/codehosting/branchdistro.py'
> --- lib/lp/codehosting/branchdistro.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/codehosting/branchdistro.py 2009-10-13 09:11:15 +0000
> @@ -0,0 +1,355 @@
> +# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +"""Opening a new DistroSeries for branch based development.
> +
> +Intended to be run just after a new distro series has been completed, this
> +script will create an official package branch in the new series for every one
> +in the old. The old branch will become stacked on the new, to avoid a using
> +too much disk space whilst retaining best performance for the new branch.
> +"""
> +
> +import os
> +
> +from bzrlib.branch import Branch
> +from bzrlib.bzrdir import BzrDir
> +from bzrlib.errors import NotBranchError, NotStacked
> +
> +from lazr.uri import URI
> +
> +import transaction
> +
> +from zope.component import getUtility
> +
> +from canonical.launchpad.interfaces import ILaunchpadCelebrities
> +from canonical.config import config
> +
> +from lp.code.interfaces.branchcollection import IAllBranches
> +from lp.code.interfaces.branch import BranchExists
> +from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
> +from lp.code.interfaces.seriessourcepackagebranch import (
> + IFindOfficialBranchLinks)
> +from lp.code.enums import BranchType
> +from lp.codehosting.vfs import branch_id_to_path
> +from lp.registry.interfaces.distribution import IDistributionSet
> +from lp.registry.interfaces.pocket import PackagePublishingPocket
> +
> +
> +__metaclass__ = type
> +__all__ = []
> +

These should be above the imports & below the docstring, IIRC.

> +
> +def switch_branches(prefix, scheme, old_db_branch, new_db_branch):
> + """Move bzr data from an old to a new branch, leaving old stacked on new.
> +
> + T...

review: Approve
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Download full text (56.0 KiB)

Jonathan Lange wrote:
> Review: Approve
> Hey Michael,
>
> Not much to say here, given that we've already talked about it. I look forward to seeing this QA'd on staging.
>
> I have a few comments, one or two of which must be fixed before landing. Other than that, looks good. Thanks for getting this done.
>
> jml
>
>> === modified file 'database/schema/security.cfg'
>> --- database/schema/security.cfg 2009-10-05 15:07:10 +0000
>> +++ database/schema/security.cfg 2009-10-13 09:11:15 +0000
>> @@ -641,6 +641,22 @@
>> # Merge notifications
>> public.codereviewvote = SELECT
>>
>> +[branch-distro]
>> +type=user
>> +public.branch = SELECT, INSERT
>> +public.branchsubscription = SELECT, INSERT
>> +public.distribution = SELECT
>> +public.distroseries = SELECT
>> +public.karma = SELECT, INSERT
>> +public.karmaaction = SELECT
>> +public.person = SELECT
>> +public.product = SELECT
>> +public.seriessourcepackagebranch = SELECT, INSERT, DELETE
>> +public.sourcepackagename = SELECT
>> +public.teamparticipation = SELECT
>> +public.validpersoncache = SELECT
>> +
>> +
>
> How did you determine these?

The tests run as this user, so I ran make schema over and over and over
again whilst gradually increasing the permissions...

> Are you sure you've got everything?

Reasonably. As you noticed, I spent a long time writing tests...

>> [targetnamecacheupdater]
>> type=user
>> groups=script
>>
>
>
>> === added file 'lib/lp/codehosting/branchdistro.py'
>> --- lib/lp/codehosting/branchdistro.py 1970-01-01 00:00:00 +0000
>> +++ lib/lp/codehosting/branchdistro.py 2009-10-13 09:11:15 +0000
>> @@ -0,0 +1,355 @@
>> +# Copyright 2009 Canonical Ltd. This software is licensed under the
>> +# GNU Affero General Public License version 3 (see the file LICENSE).
>> +
>> +"""Opening a new DistroSeries for branch based development.
>> +
>> +Intended to be run just after a new distro series has been completed, this
>> +script will create an official package branch in the new series for every one
>> +in the old. The old branch will become stacked on the new, to avoid a using
>> +too much disk space whilst retaining best performance for the new branch.
>> +"""
>> +
>> +import os
>> +
>> +from bzrlib.branch import Branch
>> +from bzrlib.bzrdir import BzrDir
>> +from bzrlib.errors import NotBranchError, NotStacked
>> +
>> +from lazr.uri import URI
>> +
>> +import transaction
>> +
>> +from zope.component import getUtility
>> +
>> +from canonical.launchpad.interfaces import ILaunchpadCelebrities
>> +from canonical.config import config
>> +
>> +from lp.code.interfaces.branchcollection import IAllBranches
>> +from lp.code.interfaces.branch import BranchExists
>> +from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
>> +from lp.code.interfaces.seriessourcepackagebranch import (
>> + IFindOfficialBranchLinks)
>> +from lp.code.enums import BranchType
>> +from lp.codehosting.vfs import branch_id_to_path
>> +from lp.registry.interfaces.distribution import IDistributionSet
>> +from lp.registry.interfa...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2009-10-05 15:07:10 +0000
+++ database/schema/security.cfg 2009-10-14 00:31:13 +0000
@@ -641,6 +641,22 @@
641# Merge notifications641# Merge notifications
642public.codereviewvote = SELECT642public.codereviewvote = SELECT
643643
644[branch-distro]
645type=user
646public.branch = SELECT, INSERT
647public.branchsubscription = SELECT, INSERT
648public.distribution = SELECT
649public.distroseries = SELECT
650public.karma = SELECT, INSERT
651public.karmaaction = SELECT
652public.person = SELECT
653public.product = SELECT
654public.seriessourcepackagebranch = SELECT, INSERT, DELETE
655public.sourcepackagename = SELECT
656public.teamparticipation = SELECT
657public.validpersoncache = SELECT
658
659
644[targetnamecacheupdater]660[targetnamecacheupdater]
645type=user661type=user
646groups=script662groups=script
647663
=== added file 'lib/lp/codehosting/branchdistro.py'
--- lib/lp/codehosting/branchdistro.py 1970-01-01 00:00:00 +0000
+++ lib/lp/codehosting/branchdistro.py 2009-10-14 00:31:13 +0000
@@ -0,0 +1,364 @@
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"""Opening a new DistroSeries for branch based development.
5
6Intended to be run just after a new distro series has been completed, this
7script will create an official package branch in the new series for every one
8in the old. The old branch will become stacked on the new, to avoid a using
9too much disk space whilst retaining best performance for the new branch.
10"""
11
12__metaclass__ = type
13__all__ = [
14 'DistroBrancher',
15 'switch_branches',
16 ]
17
18import os
19
20from bzrlib.branch import Branch
21from bzrlib.bzrdir import BzrDir
22from bzrlib.errors import NotBranchError, NotStacked
23
24from lazr.uri import URI
25
26import transaction
27
28from zope.component import getUtility
29
30from canonical.launchpad.interfaces import ILaunchpadCelebrities
31from canonical.config import config
32
33from lp.code.interfaces.branchcollection import IAllBranches
34from lp.code.interfaces.branch import BranchExists
35from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
36from lp.code.interfaces.seriessourcepackagebranch import (
37 IFindOfficialBranchLinks)
38from lp.code.enums import BranchType
39from lp.codehosting.vfs import branch_id_to_path
40from lp.registry.interfaces.distribution import IDistributionSet
41from lp.registry.interfaces.pocket import PackagePublishingPocket
42
43
44def switch_branches(prefix, scheme, old_db_branch, new_db_branch):
45 """Move bzr data from an old to a new branch, leaving old stacked on new.
46
47 This function is intended to be used just after Ubuntu is released to
48 create (at the bzr level) a new trunk branch for a source package for the
49 next release of the distribution. We move the bzr data to the location
50 for the new branch and replace the trunk branch for the just released
51 version with a stacked branch pointing at the new branch.
52
53 The procedure is to complicated to be carried out atomically, so if this
54 function is interrupted things may be a little inconsistent (e.g. there
55 might be a branch in the old location, but not stacked on the new location
56 yet). There should be no data loss though.
57
58 :param prefix: The non-branch id dependent part of the physical path to
59 the branches on disk.
60 :param scheme: The branches should be open-able at a URL of the form
61 ``scheme + :/// + unique_name``.
62 :param old_db_branch: The branch that currently has the trunk bzr data.
63 :param old_db_branch: The new trunk branch. This should not have any
64 presence on disk yet.
65 """
66 # Move .bzr directory from old to new location, crashing through the
67 # abstraction we usually hide our branch locations behind.
68 old_underlying_path = os.path.join(
69 prefix, branch_id_to_path(old_db_branch.id))
70 new_underlying_path = os.path.join(
71 prefix, branch_id_to_path(new_db_branch.id))
72 os.makedirs(new_underlying_path)
73 os.rename(
74 os.path.join(old_underlying_path, '.bzr'),
75 os.path.join(new_underlying_path, '.bzr'))
76
77 # Create branch at old location -- we use the "clone('null:')" trick to
78 # preserve the format. We have to open at the logical, unique_name-based,
79 # location so that it works to set the stacked on url to '/' + a
80 # unique_name.
81 new_location_bzrdir = BzrDir.open(
82 str(URI(
83 scheme=scheme, host='', path='/' + new_db_branch.unique_name)))
84 old_location_bzrdir = new_location_bzrdir.clone(
85 str(URI(
86 scheme=scheme, host='', path='/' + old_db_branch.unique_name)),
87 revision_id='null:')
88
89 # Set the stacked on url for old location.
90 old_location_branch = old_location_bzrdir.open_branch()
91 old_location_branch.set_stacked_on_url('/' + new_db_branch.unique_name)
92
93 # Pull from new location to old -- this won't actually transfer any
94 # revisions, just update the last revision pointer.
95 old_location_branch.pull(new_location_bzrdir.open_branch())
96
97
98class DistroBrancher:
99 """Open a new distroseries for branch based development.
100
101 `makeNewBranches` will create an official package branch in the new series
102 for every one in the old. `checkNewBranches` will check that a previous
103 run of this script completed successfully -- this is only likely to be
104 really useful if a script run died halfway through or had to be killed.
105 """
106
107 def __init__(self, logger, old_distroseries, new_distroseries):
108 """Construct a `DistroBrancher`.
109
110 The old and new distroseries must be from the same distribution, but
111 not the same distroseries.
112
113 :param logger: A Logger. Problems will be logged to this object at
114 the WARNING level or higher; progress reports will be logged at
115 the DEBUG level.
116 :param old_distroseries: The distroseries that will be examined to
117 find existing source package branches.
118 :param new_distroseries: The distroseries that will have new official
119 source branches made for it.
120 """
121 self.logger = logger
122 if old_distroseries.distribution != new_distroseries.distribution:
123 raise AssertionError(
124 "%s and %s are from different distributions!" %
125 (old_distroseries, new_distroseries))
126 if old_distroseries == new_distroseries:
127 raise AssertionError(
128 "New and old distributions must be different!")
129 self.old_distroseries = old_distroseries
130 self.new_distroseries = new_distroseries
131
132 @classmethod
133 def fromNames(cls, logger, distribution_name, old_distroseries_name,
134 new_distroseries_name):
135 """Make a `DistroBrancher` from the names of a distro and two series.
136 """
137 distribution = getUtility(IDistributionSet).getByName(
138 distribution_name)
139 new_distroseries = distribution.getSeries(new_distroseries_name)
140 old_distroseries = distribution.getSeries(old_distroseries_name)
141 return cls(logger, old_distroseries, new_distroseries)
142
143 def _existingOfficialBranches(self):
144 """Return the collection of official branches in the old distroseries.
145 """
146 branches = getUtility(IAllBranches)
147 distroseries_branches = branches.inDistroSeries(self.old_distroseries)
148 return distroseries_branches.officialBranches().getBranches()
149
150 def checkConsistentOfficialPackageBranch(self, db_branch):
151 """Check that `db_branch` is a consistent official package branch.
152
153 'Consistent official package branch' means:
154
155 * It's a package branch (rather than a personal or junk branch).
156 * It's official for its SourcePackage and no other.
157
158 This function simply returns True or False -- any problems will be
159 logged to ``self.logger``.
160
161 :param db_branch: The `IBranch` to check.
162 :return: ``True`` if the branch is a consistent official package
163 branch, ``False`` otherwise.
164 """
165 if db_branch.product:
166 self.logger.warning(
167 "Encountered unexpected product branch %r",
168 db_branch.unique_name)
169 return False
170 if not db_branch.distroseries:
171 self.logger.warning(
172 "Encountered unexpected personal branch %s",
173 db_branch.unique_name)
174 return False
175 find_branch_links = getUtility(IFindOfficialBranchLinks)
176 links = list(find_branch_links.findForBranch(db_branch))
177 if len(links) == 0:
178 self.logger.warning(
179 "%s is not an official branch", db_branch.unique_name)
180 return False
181 elif len(links) > 1:
182 series_text = ', '.join([
183 link.sourcepackage.path for link in links])
184 self.logger.warning(
185 "%s is official for multiple series: %s",
186 db_branch.unique_name, series_text)
187 return False
188 elif links[0].sourcepackage != db_branch.sourcepackage:
189 self.logger.warning(
190 "%s is the official branch for %s but not its "
191 "sourcepackage", db_branch.unique_name,
192 links[0].sourcepackage.path)
193 return False
194 return True
195
196 def makeNewBranches(self):
197 """Make official branches in the new distroseries."""
198 for db_branch in self._existingOfficialBranches():
199 self.logger.debug("Processing %s" % db_branch.unique_name)
200 try:
201 self.makeOneNewBranch(db_branch)
202 except BranchExists:
203 pass
204
205 def checkNewBranches(self):
206 """Check the branches in the new distroseries are present and correct.
207
208 This function checks that every official package branch in the old
209 distroseries has a matching branch in the new distroseries and that
210 stacking is set up as we expect in both the hosted and mirrored areas
211 on disk.
212
213 Every branch will be checked, even if some fail.
214
215 This function simply returns True or False -- any problems will be
216 logged to ``self.logger``.
217
218 :return: ``True`` if every branch passes the check, ``False``
219 otherwise.
220 """
221 ok = True
222 for db_branch in self._existingOfficialBranches():
223 self.logger.debug("Checking %s" % db_branch.unique_name)
224 try:
225 if not self.checkOneBranch(db_branch):
226 ok = False
227 except:
228 ok = False
229 self.logger.exception(
230 "Unexpected error checking %s!", db_branch)
231 return ok
232
233 def checkOneBranch(self, old_db_branch):
234 """Check a branch in the old distroseries has been copied to the new.
235
236 This function checks that `old_db_branch` has a matching branch in the
237 new distroseries and that stacking is set up as we expect in both the
238 hosted and mirrored areas on disk.
239
240 This function simply returns True or False -- any problems will be
241 logged to ``self.logger``.
242
243 :param old_db_branch: The branch to check.
244 :return: ``True`` if the branch passes the check, ``False`` otherwise.
245 """
246 ok = self.checkConsistentOfficialPackageBranch(old_db_branch)
247 if not ok:
248 return ok
249 new_sourcepackage = self.new_distroseries.getSourcePackage(
250 old_db_branch.sourcepackagename)
251 new_db_branch = new_sourcepackage.getBranch(
252 PackagePublishingPocket.RELEASE)
253 if new_db_branch is None:
254 self.logger.warning(
255 "No official branch found for %s",
256 new_sourcepackage.path)
257 return False
258 ok = self.checkConsistentOfficialPackageBranch(new_db_branch)
259 if not ok:
260 return ok
261 # for both mirrored and hosted areas:
262 for scheme in 'lp-mirrored', 'lp-hosted':
263 # the branch in the new distroseries is unstacked
264 new_location = str(URI(
265 scheme=scheme, host='', path='/' + new_db_branch.unique_name))
266 try:
267 new_bzr_branch = Branch.open(new_location)
268 except NotBranchError:
269 self.logger.warning(
270 "No bzr branch at new location %s", new_location)
271 ok = False
272 else:
273 try:
274 new_stacked_on_url = new_bzr_branch.get_stacked_on_url()
275 ok = False
276 self.logger.warning(
277 "New branch at %s is stacked on %s, should be "
278 "unstacked.", new_location, new_stacked_on_url)
279 except NotStacked:
280 pass
281 # The branch in the old distroseries is stacked on that in the
282 # new.
283 old_location = str(URI(
284 scheme=scheme, host='', path='/' + old_db_branch.unique_name))
285 try:
286 old_bzr_branch = Branch.open(old_location)
287 except NotBranchError:
288 self.logger.warning(
289 "No bzr branch at old location %s", old_location)
290 ok = False
291 else:
292 try:
293 old_stacked_on_url = old_bzr_branch.get_stacked_on_url()
294 if old_stacked_on_url != '/' + new_db_branch.unique_name:
295 self.logger.warning(
296 "Old branch at %s is stacked on %s, should be "
297 "stacked on %s", old_location, old_stacked_on_url,
298 '/' + new_db_branch.unique_name)
299 ok = False
300 except NotStacked:
301 self.logger.warning(
302 "Old branch at %s is not stacked, should be stacked "
303 "on %s", old_location,
304 '/' + new_db_branch.unique_name)
305 ok = False
306 # The branch in the old distroseries has no revisions in its
307 # repository. We open the repository independently of the
308 # branch because the branch's repository has had its fallback
309 # location activated. Note that this check might fail if new
310 # revisions get pushed to the branch in the old distroseries,
311 # which shouldn't happen but isn't totally impossible.
312 old_repo = BzrDir.open(old_location).open_repository()
313 if len(old_repo.all_revision_ids()) > 0:
314 self.logger.warning(
315 "Repository at %s has %s revisions.",
316 old_location, len(old_repo.all_revision_ids()))
317 ok = False
318 # The branch in the old distroseries has at least some
319 # history. (We can't check that the tips are the same because
320 # the branch in the new distroseries might have new revisons).
321 if old_bzr_branch.last_revision() == 'null:':
322 self.logger.warning(
323 "Old branch at %s has null tip revision.",
324 old_location)
325 ok = False
326 return ok
327
328 def makeOneNewBranch(self, old_db_branch):
329 """Copy a branch to the new distroseries.
330
331 This function makes a new database branch for the same source package
332 as old_db_branch but in the new distroseries and then uses
333 `switch_branches` to move the underlying bzr branch to the new series
334 and replace the old branch with a branch stacked on the new series'
335 branch.
336
337 :param old_db_branch: The branch to copy into the new distroseries.
338 :raises BranchExists: This will be raised if old_db_branch has already
339 been copied to the new distroseries (in the database, at least).
340 """
341 if not self.checkConsistentOfficialPackageBranch(old_db_branch):
342 self.logger.warning("Skipping branch")
343 return
344 new_namespace = getUtility(IBranchNamespaceSet).get(
345 person=old_db_branch.owner, product=None,
346 distroseries=self.new_distroseries,
347 sourcepackagename=old_db_branch.sourcepackagename)
348 new_db_branch = new_namespace.createBranch(
349 BranchType.HOSTED, old_db_branch.name, old_db_branch.registrant)
350 new_db_branch.sourcepackage.setBranch(
351 PackagePublishingPocket.RELEASE, new_db_branch,
352 getUtility(ILaunchpadCelebrities).ubuntu_branches.teamowner)
353 # switch_branches *moves* the data to locations dependent on the
354 # new_branch's id, so if the transaction was rolled back we wouldn't
355 # know the branch id and thus wouldn't be able to find the branch data
356 # again. So commit before doing that.
357 transaction.commit()
358 switch_branches(
359 config.codehosting.hosted_branches_root,
360 'lp-hosted', old_db_branch, new_db_branch)
361 switch_branches(
362 config.codehosting.mirrored_branches_root,
363 'lp-mirrored', old_db_branch, new_db_branch)
364 return new_db_branch
0365
=== added file 'lib/lp/codehosting/tests/test_branchdistro.py'
--- lib/lp/codehosting/tests/test_branchdistro.py 1970-01-01 00:00:00 +0000
+++ lib/lp/codehosting/tests/test_branchdistro.py 2009-10-14 00:31:13 +0000
@@ -0,0 +1,685 @@
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 making new source package branches just after a distro release.
5"""
6
7__metaclass__ = type
8
9import os
10import re
11from StringIO import StringIO
12from subprocess import PIPE, Popen, STDOUT
13import textwrap
14import unittest
15
16from bzrlib.branch import Branch
17from bzrlib.bzrdir import BzrDir
18from bzrlib.errors import NotStacked
19from bzrlib.tests import TestCaseWithTransport
20from bzrlib.transport import get_transport
21from bzrlib.transport.chroot import ChrootServer
22
23from lazr.uri import URI
24
25import transaction
26
27from canonical.config import config
28from canonical.testing.layers import ZopelessAppServerLayer
29from canonical.launchpad.scripts.logger import FakeLogger, QuietFakeLogger
30
31from lp.codehosting.branchdistro import DistroBrancher, switch_branches
32from lp.codehosting.vfs import branch_id_to_path
33from lp.registry.interfaces.pocket import PackagePublishingPocket
34from lp.testing import TestCaseWithFactory
35
36
37# We say "RELEASE" often enough to not want to say "PackagePublishingPocket."
38# each time.
39RELEASE = PackagePublishingPocket.RELEASE
40
41
42class FakeBranch:
43 """Just enough of a Branch to pass `test_switch_branches`."""
44
45 def __init__(self, id):
46 self.id = id
47
48 @property
49 def unique_name(self):
50 return branch_id_to_path(self.id)
51
52
53class TestSwitchBranches(TestCaseWithTransport):
54 """Tests for `switch_branches`."""
55
56 def test_switch_branches(self):
57 # switch_branches moves a branch to the new location and places a
58 # branch (with no revisions) stacked on the new branch in the old
59 # location.
60
61 chroot_server = ChrootServer(self.get_transport())
62 chroot_server.setUp()
63 self.addCleanup(chroot_server.tearDown)
64 scheme = chroot_server.get_url().rstrip('/:')
65
66 old_branch = FakeBranch(1)
67 self.get_transport(old_branch.unique_name).create_prefix()
68 tree = self.make_branch_and_tree(old_branch.unique_name)
69 tree.commit(message='.')
70
71 new_branch = FakeBranch(2)
72
73 switch_branches('.', scheme, old_branch, new_branch)
74
75 # Post conditions:
76 # 1. unstacked branch in new_branch's location
77 # 2. stacked branch with no revisions in repo at old_branch
78 # 3. last_revision() the same for two branches
79
80 old_location_bzrdir = BzrDir.open(str(URI(
81 scheme=scheme, host='', path='/' + old_branch.unique_name)))
82 new_location_bzrdir = BzrDir.open(str(URI(
83 scheme=scheme, host='', path='/' + new_branch.unique_name)))
84
85 old_location_branch = old_location_bzrdir.open_branch()
86 new_location_branch = new_location_bzrdir.open_branch()
87
88 # 1. unstacked branch in new_branch's location
89 self.assertRaises(NotStacked, new_location_branch.get_stacked_on_url)
90
91 # 2. stacked branch with no revisions in repo at old_branch
92 self.assertEqual(
93 '/' + new_branch.unique_name,
94 old_location_branch.get_stacked_on_url())
95 self.assertEqual(
96 [], old_location_bzrdir.open_repository().all_revision_ids())
97
98 # 3. last_revision() the same for two branches
99 self.assertEqual(
100 old_location_branch.last_revision(),
101 new_location_branch.last_revision())
102
103
104class TestDistroBrancher(TestCaseWithFactory):
105 """Tests for `DistroBrancher`."""
106
107 layer = ZopelessAppServerLayer
108
109 def setUp(self):
110 TestCaseWithFactory.setUp(self)
111 self.useBzrBranches(real_server=True)
112
113 def makeOfficialPackageBranch(self, distroseries=None):
114 """Make an official package branch with an underlying bzr branch."""
115 db_branch = self.factory.makePackageBranch(distroseries=distroseries)
116 db_branch.sourcepackage.setBranch(RELEASE, db_branch, db_branch.owner)
117
118 transaction.commit()
119
120 _, tree = self.create_branch_and_tree(
121 tree_location=self.factory.getUniqueString(), db_branch=db_branch,
122 hosted=True)
123 tree.commit('')
124 mirrored_branch = BzrDir.create_branch_convenience(
125 db_branch.warehouse_url)
126 mirrored_branch.pull(tree.branch)
127
128 return db_branch
129
130 def makeNewSeriesAndBrancher(self, distroseries=None):
131 """Make a DistroBrancher.
132
133 Any messages logged by this DistroBrancher can be checked by calling
134 `assertLogMessages` below.
135 """
136 if distroseries is None:
137 distroseries = self.factory.makeDistroRelease()
138 self._log_file = StringIO()
139 new_distroseries = self.factory.makeDistroRelease(
140 distribution=distroseries.distribution, name='new')
141 transaction.commit()
142 self.layer.switchDbUser('branch-distro')
143 return DistroBrancher(
144 FakeLogger(self._log_file), distroseries, new_distroseries)
145
146 def clearLogMessages(self):
147 """Forget about all logged messages seen so far."""
148 self._log_file.seek(0, 0)
149 self._log_file.truncate()
150
151 def assertLogMessages(self, patterns):
152 """Assert that the messages logged meet expectations.
153
154 :param patterns: A list of regular expressions. The length must match
155 the number of messages logged, and then each pattern must match
156 the messages logged in order.
157 """
158 log_messages = self._log_file.getvalue().splitlines()
159 if len(log_messages) > len(patterns):
160 self.fail(
161 "More log messages (%s) than expected (%s)" %
162 (log_messages, patterns))
163 elif len(log_messages) < len(patterns):
164 self.fail(
165 "Fewer log messages (%s) than expected (%s)" %
166 (log_messages, patterns))
167 for pattern, message in zip(patterns, log_messages):
168 if not re.match(pattern, message):
169 self.fail("%r does not match %r" % (pattern, message))
170
171 def test_DistroBrancher_same_distro_check(self):
172 # DistroBrancher.__init__ raises AssertionError if the two
173 # distroseries passed are not from the same distribution.
174 self.assertRaises(
175 AssertionError, DistroBrancher, None,
176 self.factory.makeDistroRelease(),
177 self.factory.makeDistroRelease())
178
179 def test_DistroBrancher_same_distroseries_check(self):
180 # DistroBrancher.__init__ raises AssertionError if passed the same
181 # distroseries twice.
182 distroseries = self.factory.makeDistroRelease()
183 self.assertRaises(
184 AssertionError, DistroBrancher, None, distroseries, distroseries)
185
186 def test_fromNames(self):
187 # DistroBrancher.fromNames constructs a DistroBrancher from the names
188 # of a distribution and two distroseries within it.
189 distribution = self.factory.makeDistribution()
190 distroseries1 = self.factory.makeDistroRelease(
191 distribution=distribution)
192 distroseries2 = self.factory.makeDistroRelease(
193 distribution=distribution)
194 brancher = DistroBrancher.fromNames(
195 None, distribution.name, distroseries1.name, distroseries2.name)
196 self.assertEqual(
197 [distroseries1, distroseries2],
198 [brancher.old_distroseries, brancher.new_distroseries])
199
200 # A word on testing strategy: we don't directly test the post conditions
201 # of makeOneNewBranch, but we do test that it satisfies checkOneBranch and
202 # the tests for checkOneBranch verify that this function rejects various
203 # ways in which makeOneNewBranch could conceivably fail.
204
205 def test_makeOneNewBranch(self):
206 # makeOneNewBranch creates an official package branch in the new
207 # distroseries.
208 db_branch = self.makeOfficialPackageBranch()
209
210 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
211 brancher.makeOneNewBranch(db_branch)
212
213 new_branch = brancher.new_distroseries.getSourcePackage(
214 db_branch.sourcepackage.name).getBranch(RELEASE)
215
216 self.assertIsNot(None, new_branch)
217 # The branch owner/name/target is the same, apart from the
218 # distroseries.
219 self.assertEqual(
220 [db_branch.owner, db_branch.distribution,
221 db_branch.sourcepackagename, db_branch.name],
222 [new_branch.owner, new_branch.distribution,
223 new_branch.sourcepackagename, new_branch.name])
224
225 def test_makeOneNewBranch_inconsistent_branch(self):
226 # makeOneNewBranch skips over an inconsistent official package branch
227 # (see `checkConsistentOfficialPackageBranch` for precisely what an
228 # "inconsistent official package branch" is).
229 unofficial_branch = self.factory.makePackageBranch()
230 brancher = self.makeNewSeriesAndBrancher(
231 unofficial_branch.distroseries)
232 brancher.makeOneNewBranch(unofficial_branch)
233
234 new_branch = brancher.new_distroseries.getSourcePackage(
235 unofficial_branch.sourcepackage.name).getBranch(RELEASE)
236 self.assertIs(None, new_branch)
237 self.assertLogMessages(
238 ['^WARNING .* is not an official branch$',
239 '^WARNING Skipping branch$'])
240
241 def test_makeNewBranches(self):
242 # makeNewBranches calls makeOneNewBranch for each official branch in
243 # the old distroseries.
244 db_branch = self.makeOfficialPackageBranch()
245 db_branch2 = self.makeOfficialPackageBranch(
246 distroseries=db_branch.distroseries)
247
248 new_distroseries = self.factory.makeDistroRelease(
249 distribution=db_branch.distribution)
250
251 brancher = DistroBrancher(
252 QuietFakeLogger(), db_branch.distroseries, new_distroseries)
253
254 brancher.makeNewBranches()
255
256 new_sourcepackage = new_distroseries.getSourcePackage(
257 db_branch.sourcepackage.name)
258 new_branch = new_sourcepackage.getBranch(RELEASE)
259 new_sourcepackage2 = new_distroseries.getSourcePackage(
260 db_branch2.sourcepackage.name)
261 new_branch2 = new_sourcepackage2.getBranch(RELEASE)
262
263 self.assertIsNot(None, new_branch)
264 self.assertIsNot(None, new_branch2)
265
266 def test_makeNewBranches_idempotent(self):
267 # makeNewBranches is idempotent in the sense that if a branch in the
268 # old distroseries already has a counterpart in the new distroseries,
269 # it is silently ignored.
270 db_branch = self.makeOfficialPackageBranch()
271
272 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
273 brancher.makeNewBranches()
274 brancher.makeNewBranches()
275
276 new_branch = brancher.new_distroseries.getSourcePackage(
277 db_branch.sourcepackage.name).getBranch(RELEASE)
278
279 self.assertIsNot(new_branch, None)
280
281 def test_makeOneNewBranch_checks_ok(self):
282 # After calling makeOneNewBranch for a branch, calling checkOneBranch
283 # returns True for that branch.
284 db_branch = self.makeOfficialPackageBranch()
285 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
286 brancher.makeOneNewBranch(db_branch)
287 self.clearLogMessages()
288 ok = brancher.checkOneBranch(db_branch)
289 self.assertLogMessages([])
290 self.assertTrue(ok)
291
292 def test_checkConsistentOfficialPackageBranch_product_branch(self):
293 # checkConsistentOfficialPackageBranch returns False when passed a
294 # product branch.
295 db_branch = self.factory.makeProductBranch()
296 brancher = self.makeNewSeriesAndBrancher()
297 ok = brancher.checkConsistentOfficialPackageBranch(db_branch)
298 self.assertLogMessages([
299 '^WARNING Encountered unexpected product branch .*/.*/.*$'])
300 self.assertFalse(ok)
301
302 def test_checkConsistentOfficialPackageBranch_personal_branch(self):
303 # checkConsistentOfficialPackageBranch returns False when passed a
304 # personal branch.
305 db_branch = self.factory.makePersonalBranch()
306 brancher = self.makeNewSeriesAndBrancher()
307 ok = brancher.checkConsistentOfficialPackageBranch(db_branch)
308 self.assertLogMessages([
309 '^WARNING Encountered unexpected personal branch .*/.*/.*$'])
310 self.assertFalse(ok)
311
312 def test_checkConsistentOfficialPackageBranch_no_official_branch(self):
313 # checkConsistentOfficialPackageBranch returns False when passed a
314 # branch which is not official for any package.
315 db_branch = self.factory.makePackageBranch()
316 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
317 ok = brancher.checkConsistentOfficialPackageBranch(db_branch)
318 self.assertLogMessages(
319 ['^WARNING .*/.*/.* is not an official branch$'])
320 self.assertFalse(ok)
321
322 def test_checkConsistentOfficialPackageBranch_official_elsewhere(self):
323 # checkConsistentOfficialPackageBranch returns False when passed a
324 # branch which is official for a sourcepackage that it is not a branch
325 # for.
326 db_branch = self.factory.makePackageBranch()
327 self.factory.makeSourcePackage().setBranch(
328 RELEASE, db_branch, db_branch.owner)
329 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
330 ok = brancher.checkConsistentOfficialPackageBranch(db_branch)
331 self.assertLogMessages(
332 ['^WARNING .*/.*/.* is the official branch for .*/.*/.* but not '
333 'its sourcepackage$'])
334 self.assertFalse(ok)
335
336 def test_checkConsistentOfficialPackageBranch_official_twice(self):
337 # checkConsistentOfficialPackageBranch returns False when passed a
338 # branch that is official for two sourcepackages.
339 db_branch = self.factory.makePackageBranch()
340 db_branch.sourcepackage.setBranch(RELEASE, db_branch, db_branch.owner)
341 self.factory.makeSourcePackage().setBranch(
342 RELEASE, db_branch, db_branch.owner)
343 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
344 ok = brancher.checkConsistentOfficialPackageBranch(db_branch)
345 self.assertLogMessages([
346 '^WARNING .*/.*/.* is official for multiple series: .*/.*/.*, '
347 '.*/.*/.*$'])
348 self.assertFalse(ok)
349
350 def test_checkConsistentOfficialPackageBranch_ok(self):
351 # checkConsistentOfficialPackageBranch returns True when passed a
352 # branch that is official for its sourcepackage and no other.
353 db_branch = self.factory.makePackageBranch()
354 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
355 db_branch.sourcepackage.setBranch(RELEASE, db_branch, db_branch.owner)
356 ok = brancher.checkConsistentOfficialPackageBranch(db_branch)
357 self.assertLogMessages([])
358 self.assertTrue(ok)
359
360 def test_checkOneBranch_inconsistent_old_package_branch(self):
361 # checkOneBranch returns False when passed a branch that is not a
362 # consistent official package branch.
363 db_branch = self.factory.makePackageBranch()
364 brancher = self.makeNewSeriesAndBrancher()
365 ok = brancher.checkOneBranch(db_branch)
366 self.assertFalse(ok)
367 self.assertLogMessages(
368 ['^WARNING .*/.*/.* is not an official branch$'])
369
370 def test_checkOneBranch_no_new_official_branch(self):
371 # checkOneBranch returns False when there is no corresponding official
372 # package branch in the new distroseries.
373 db_branch = self.makeOfficialPackageBranch()
374 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
375 ok = brancher.checkOneBranch(db_branch)
376 self.assertFalse(ok)
377 self.assertLogMessages(
378 ['^WARNING No official branch found for .*/.*/.*$'])
379
380 def test_checkOneBranch_inconsistent_new_package_branch(self):
381 # checkOneBranch returns False when the corresponding official package
382 # branch in the new distroseries is not consistent.
383 db_branch = self.makeOfficialPackageBranch()
384 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
385 new_db_branch = brancher.makeOneNewBranch(db_branch)
386 self.layer.switchDbUser('launchpad')
387 new_db_branch.setTarget(
388 new_db_branch.owner,
389 source_package=self.factory.makeSourcePackage())
390 transaction.commit()
391 self.layer.switchDbUser('branch-distro')
392 ok = brancher.checkOneBranch(new_db_branch)
393 self.assertFalse(ok)
394 self.assertLogMessages(
395 ['^WARNING .*/.*/.* is the official branch for .*/.*/.* but not '
396 'its sourcepackage$'])
397
398 def checkOneBranch_new_branch_missing(self, branch_type):
399 # checkOneBranch returns False when there is no bzr branch for the
400 # database branch in the new distroseries.
401 assert branch_type in ('hosted', 'mirrored')
402 db_branch = self.makeOfficialPackageBranch()
403 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
404 new_db_branch = brancher.makeOneNewBranch(db_branch)
405 if branch_type == 'hosted':
406 url = new_db_branch.getPullURL()
407 else:
408 url = new_db_branch.warehouse_url
409 get_transport(url).delete_tree('.bzr')
410 ok = brancher.checkOneBranch(db_branch)
411 self.assertFalse(ok)
412 # Deleting the new branch will break the old branch, as that's stacked
413 # on the new one.
414 self.assertLogMessages([
415 '^WARNING No bzr branch at new location lp-%s:///.*/.*/.*/.*$'
416 % branch_type,
417 '^WARNING No bzr branch at old location lp-%s:///.*/.*/.*/.*$'
418 % branch_type,
419 ])
420
421 def test_checkOneBranch_new_hosted_branch_missing(self):
422 # checkOneBranch returns False when there is no bzr branch in the
423 # hosted area for the database branch in the new distroseries.
424 self.checkOneBranch_new_branch_missing('hosted')
425
426 def test_checkOneBranch_new_mirrored_branch_missing(self):
427 # checkOneBranch returns False when there is no bzr branch in the
428 # mirrored area for the database branch in the new distroseries.
429 self.checkOneBranch_new_branch_missing('mirrored')
430
431 def checkOneBranch_old_branch_missing(self, branch_type):
432 # checkOneBranch returns False when there is no bzr branchfor the
433 # database branch in old distroseries.
434 assert branch_type in ('hosted', 'mirrored')
435 db_branch = self.makeOfficialPackageBranch()
436 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
437 brancher.makeOneNewBranch(db_branch)
438 if branch_type == 'hosted':
439 url = db_branch.getPullURL()
440 else:
441 url = db_branch.warehouse_url
442 get_transport(url).delete_tree('.bzr')
443 ok = brancher.checkOneBranch(db_branch)
444 self.assertFalse(ok)
445 self.assertLogMessages([
446 '^WARNING No bzr branch at old location lp-%s:///.*/.*/.*/.*$'
447 % branch_type,
448 ])
449
450 def test_checkOneBranch_old_hosted_branch_missing(self):
451 # checkOneBranch returns False when there is no bzr branch in the
452 # hosted area for the database branch in old distroseries.
453 self.checkOneBranch_old_branch_missing('hosted')
454
455 def test_checkOneBranch_old_mirrored_branch_missing(self):
456 # checkOneBranch returns False when there is no bzr branch in the
457 # mirrored area for the database branch in old distroseries.
458 self.checkOneBranch_old_branch_missing('mirrored')
459
460 def checkOneBranch_new_stacked(self, branch_type):
461 # checkOneBranch returns False when the bzr branch for the database
462 # branch in new distroseries is stacked.
463 assert branch_type in ('hosted', 'mirrored')
464 db_branch = self.makeOfficialPackageBranch()
465 b, _ = self.create_branch_and_tree(
466 self.factory.getUniqueString(), hosted=(branch_type == 'hosted'))
467 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
468 new_db_branch = brancher.makeOneNewBranch(db_branch)
469 if branch_type == 'hosted':
470 url = new_db_branch.getPullURL()
471 else:
472 url = new_db_branch.warehouse_url
473 Branch.open(url).set_stacked_on_url('/' + b.unique_name)
474 ok = brancher.checkOneBranch(db_branch)
475 self.assertFalse(ok)
476 self.assertLogMessages([
477 '^WARNING New branch at lp-%s:///.*/.*/.*/.* is stacked on '
478 '/.*/.*/.*, should be unstacked.$' % branch_type,
479 ])
480
481 def test_checkOneBranch_new_hosted_stacked(self):
482 # checkOneBranch returns False when the bzr branch in the hosted area
483 # for the database branch in new distroseries is stacked.
484 self.checkOneBranch_new_stacked('hosted')
485
486 def test_checkOneBranch_new_mirrored_stacked(self):
487 # checkOneBranch returns False when the bzr branch in the mirrored
488 # area for the database branch in new distroseries is stacked.
489 self.checkOneBranch_new_stacked('mirrored')
490
491 def checkOneBranch_old_unstacked(self, branch_type):
492 # checkOneBranch returns False when the bzr branch for the database
493 # branch in old distroseries is not stacked.
494 assert branch_type in ('hosted', 'mirrored')
495 db_branch = self.makeOfficialPackageBranch()
496 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
497 brancher.makeOneNewBranch(db_branch)
498 if branch_type == 'hosted':
499 url = db_branch.getPullURL()
500 else:
501 url = db_branch.warehouse_url
502 old_bzr_branch = Branch.open(url)
503 old_bzr_branch.set_stacked_on_url(None)
504 ok = brancher.checkOneBranch(db_branch)
505 self.assertLogMessages([
506 '^WARNING Old branch at lp-%s:///.*/.*/.*/.* is not stacked, '
507 'should be stacked on /.*/.*/.*.$' % branch_type,
508 '^.*has .* revisions.*$',
509 ])
510 self.assertFalse(ok)
511
512 def test_checkOneBranch_old_hosted_unstacked(self):
513 # checkOneBranch returns False when the bzr branch in the hosted area
514 # for the database branch in old distroseries is not stacked.
515 self.checkOneBranch_old_unstacked('hosted')
516
517 def test_checkOneBranch_old_mirrored_unstacked(self):
518 # checkOneBranch returns False when the bzr branch in the mirrored
519 # area for the database branch in old distroseries is not stacked.
520 self.checkOneBranch_old_unstacked('mirrored')
521
522 def checkOneBranch_old_misstacked(self, branch_type):
523 # checkOneBranch returns False when the bzr branch for the database
524 # branch in old distroseries stacked on some other branch than the
525 # branch in the new distroseries.
526 assert branch_type in ('hosted', 'mirrored')
527 db_branch = self.makeOfficialPackageBranch()
528 b, _ = self.create_branch_and_tree(
529 self.factory.getUniqueString(), hosted=(branch_type == 'hosted'))
530 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
531 brancher.makeOneNewBranch(db_branch)
532 if branch_type == 'hosted':
533 url = db_branch.getPullURL()
534 else:
535 url = db_branch.warehouse_url
536 Branch.open(url).set_stacked_on_url('/' + b.unique_name)
537 ok = brancher.checkOneBranch(db_branch)
538 self.assertLogMessages([
539 '^WARNING Old branch at lp-%s:///.*/.*/.*/.* is stacked on '
540 '/.*/.*/.*, should be stacked on /.*/.*/.*.$' % branch_type,
541 ])
542 self.assertFalse(ok)
543
544 def test_checkOneBranch_old_hosted_misstacked(self):
545 # checkOneBranch returns False when the bzr branch in the hosted area
546 # for the database branch in old distroseries stacked on some other
547 # branch than the branch in the new distroseries.
548 self.checkOneBranch_old_misstacked('hosted')
549
550 def test_checkOneBranch_old_mirrored_misstacked(self):
551 # checkOneBranch returns False when the bzr branch in the mirrored
552 # area for the database branch in old distroseries stacked on some
553 # other branch than the branch in the new distroseries.
554 self.checkOneBranch_old_misstacked('mirrored')
555
556 def checkOneBranch_old_has_revisions(self, branch_type):
557 # checkOneBranch returns False when the bzr branch for the database
558 # branch in old distroseries has a repository that contains revisions.
559 assert branch_type in ('hosted', 'mirrored')
560 db_branch = self.makeOfficialPackageBranch()
561 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
562 brancher.makeOneNewBranch(db_branch)
563 if branch_type == 'hosted':
564 url = db_branch.getPullURL()
565 else:
566 url = db_branch.warehouse_url
567 old_bzr_branch = Branch.open(url)
568 old_bzr_branch.create_checkout(
569 self.factory.getUniqueString()).commit('')
570 ok = brancher.checkOneBranch(db_branch)
571 self.assertLogMessages([
572 '^WARNING Repository at lp-%s:///.*/.*/.*/.* has 1 revisions.'
573 % branch_type
574 ])
575 self.assertFalse(ok)
576
577 def test_checkOneBranch_old_hosted_has_revisions(self):
578 # checkOneBranch returns False when the bzr branch in the hosted area
579 # for the database branch in old distroseries has a repository that
580 # contains revisions.
581 self.checkOneBranch_old_has_revisions('hosted')
582
583 def test_checkOneBranch_old_mirrored_has_revisions(self):
584 # checkOneBranch returns False when the bzr branch in the mirrored
585 # area for the database branch in old distroseries has a repository
586 # that contains revisions.
587 self.checkOneBranch_old_has_revisions('mirrored')
588
589 def checkOneBranch_old_has_null_tip(self, branch_type):
590 # checkOneBranch returns False when the bzr branch for the database
591 # branch in old distroseries has tip revision of 'null:'.
592 assert branch_type in ('hosted', 'mirrored')
593 db_branch = self.makeOfficialPackageBranch()
594 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
595 brancher.makeOneNewBranch(db_branch)
596 if branch_type == 'hosted':
597 url = db_branch.getPullURL()
598 else:
599 url = db_branch.warehouse_url
600 old_bzr_branch = Branch.open(url)
601 old_bzr_branch.set_last_revision_info(0, 'null:')
602 ok = brancher.checkOneBranch(db_branch)
603 self.assertLogMessages([
604 '^WARNING Old branch at lp-%s:///.*/.*/.*/.* has null tip '
605 'revision.' % branch_type
606 ])
607 self.assertFalse(ok)
608
609 def test_checkOneBranch_old_hosted_has_null_tip(self):
610 # checkOneBranch returns False when the bzr branch in the hosted area
611 # for the database branch in old distroseries has tip revision of
612 # 'null:'.
613 self.checkOneBranch_old_has_null_tip('hosted')
614
615 def test_checkOneBranch_old_mirrored_has_null_tip(self):
616 # checkOneBranch returns False when the bzr branch in the mirrored
617 # area for the database branch in old distroseries has tip revision of
618 # 'null:'.
619 self.checkOneBranch_old_has_null_tip('mirrored')
620
621 def runBranchDistroScript(self, args):
622 """Run the branch-distro.py script with the given arguments.
623
624 ;param args: The arguments to pass to the branch-distro.py script.
625 :return: A tuple (returncode, output). stderr and stdout are both
626 contained in the output.
627 """
628 script_path = os.path.join(config.root, 'scripts', 'branch-distro.py')
629 process = Popen([script_path] + args, stdout=PIPE, stderr=STDOUT)
630 output, error = process.communicate()
631 return process.returncode, output
632
633 def test_makeNewBranches_script(self):
634 # Running the script with the arguments 'distro old-series new-series'
635 # makes new branches in the new series.
636 db_branch = self.makeOfficialPackageBranch()
637 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
638 returncode, output = self.runBranchDistroScript(
639 ['-v', db_branch.distribution.name,
640 brancher.old_distroseries.name, brancher.new_distroseries.name])
641 self.assertEqual(0, returncode)
642 self.assertEqual(
643 'DEBUG Processing ' + db_branch.unique_name + '\n', output)
644 brancher.checkOneBranch(db_branch)
645
646 def test_checkNewBranches_script_success(self):
647 # Running the script with the arguments '--check distro old-series
648 # new-series' checks that the branches in the new series are as
649 # expected.
650 db_branch = self.makeOfficialPackageBranch()
651 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
652 brancher.makeNewBranches()
653 returncode, output = self.runBranchDistroScript(
654 ['-v', '--check', db_branch.distribution.name,
655 brancher.old_distroseries.name, brancher.new_distroseries.name])
656 self.assertEqual(0, returncode)
657 self.assertEqual(
658 'DEBUG Checking ' + db_branch.unique_name + '\n', output)
659 brancher.checkOneBranch(db_branch)
660
661 def test_checkNewBranches_script_failure(self):
662 # Running the script with the arguments '--check distro old-series
663 # new-series' checks that the branches in the new series are as
664 # expected and logs warnings and exits with code 1 is things are not
665 # as expected.
666 db_branch = self.makeOfficialPackageBranch()
667 brancher = self.makeNewSeriesAndBrancher(db_branch.distroseries)
668 returncode, output = self.runBranchDistroScript(
669 ['-v', '--check', db_branch.distribution.name,
670 brancher.old_distroseries.name, brancher.new_distroseries.name])
671 sp_path = brancher.new_distroseries.getSourcePackage(
672 db_branch.sourcepackagename).path
673 expected = '''\
674 DEBUG Checking %(branch_name)s
675 WARNING No official branch found for %(sp_path)s
676 ERROR Check failed
677 ''' % {'branch_name': db_branch.unique_name, 'sp_path': sp_path}
678 self.assertEqual(
679 textwrap.dedent(expected), output)
680 self.assertEqual(1, returncode)
681
682
683def test_suite():
684 return unittest.TestLoader().loadTestsFromName(__name__)
685
0686
=== added file 'scripts/branch-distro.py'
--- scripts/branch-distro.py 1970-01-01 00:00:00 +0000
+++ scripts/branch-distro.py 2009-10-14 00:31:13 +0000
@@ -0,0 +1,39 @@
1#!/usr/bin/python2.4
2#
3# Copyright 2009 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).
5
6import _pythonpath
7
8from lp.codehosting.branchdistro import DistroBrancher
9from lp.codehosting.vfs import get_multi_server
10from lp.services.scripts.base import LaunchpadScript, LaunchpadScriptFailure
11
12
13class BranchDistroScript(LaunchpadScript):
14
15 usage = "%prog distro old-series new-series"
16
17 def add_my_options(self):
18 self.parser.add_option(
19 '--check', dest="check", action="store_true", default=False,
20 help=("Check that the new distro series has its official "
21 "branches set up correctly."))
22
23 def main(self):
24 if len(self.args) != 3:
25 self.parser.error("Wrong number of arguments.")
26 brancher = DistroBrancher.fromNames(self.logger, *self.args)
27 server = get_multi_server(write_mirrored=True, write_hosted=True)
28 server.setUp()
29 try:
30 if self.options.check:
31 if not brancher.checkNewBranches():
32 raise LaunchpadScriptFailure("Check failed")
33 else:
34 brancher.makeNewBranches()
35 finally:
36 server.tearDown()
37
38if __name__ == '__main__':
39 BranchDistroScript("branch-distro", dbuser='branch-distro').run()