~launchpad-pqm/launchpad/devel

13597.1.1 by Jeroen Vermeulen
Get rid of factory.make[Ubuntu]DistroRelease. And some lint.
1
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
8687.15.17 by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
3
4
"""In-memory doubles of core codehosting objects."""
5
6
__metaclass__ = type
7
__all__ = [
7055.5.4 by Jonathan Lange
Eschew 'fake' and 'real' for 'inmemory' and 'db'.
8
    'InMemoryFrontend',
10100.1.14 by Jonathan Lange
iter_split is in services.
9
    'XMLRPCWrapper',
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
10
    ]
11
8312.3.5 by Michael Hudson
mostly copy and pasted tests for the xml-rpc endpoint
12
import operator
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
13
from xmlrpclib import Fault
14
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
15
from bzrlib.urlutils import (
16
    escape,
17
    unescape,
18
    )
11583.3.15 by Jonathan Lange
Use the DeferredBlockingProxy in codehosting.
19
from twisted.internet import defer
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
20
from zope.component import (
21
    adapter,
22
    getSiteManager,
23
    )
8031.2.4 by Jonathan Lange
Use IBranchTarget() in the test, rather than directly getting the stacking
24
from zope.interface import implementer
25
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
26
from canonical.database.constants import UTC_NOW
14593.1.1 by Curtis Hovey
Moved xmlrpc to lp.
27
from lp.xmlrpc import faults
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
28
from lp.app.validators import LaunchpadValidationError
13662.5.3 by Aaron Bentley
Handle invalid source package names.
29
from lp.app.validators.name import valid_name
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
30
from lp.code.bzr import (
31
    BranchFormat,
32
    ControlFormat,
33
    RepositoryFormat,
34
    )
35
from lp.code.enums import BranchType
10379.2.3 by Michael Hudson
acquireBranchToPull changes
36
from lp.code.errors import UnknownBranchTypeError
8555.2.2 by Tim Penhey
Move enum -> enums.
37
from lp.code.interfaces.branch import IBranch
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
38
from lp.code.interfaces.branchtarget import IBranchTarget
39
from lp.code.interfaces.codehosting import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
40
    BRANCH_ALIAS_PREFIX,
12707.7.6 by Tim Penhey
Created a function to do the basic alias assembly.
41
    branch_id_alias,
12707.4.7 by Tim Penhey
Fix the inmemory xmlrpc mock.
42
    BRANCH_ID_ALIAS_PREFIX,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
43
    BRANCH_TRANSPORT,
44
    CONTROL_TRANSPORT,
45
    LAUNCHPAD_ANONYMOUS,
46
    LAUNCHPAD_SERVICES,
47
    )
11121.5.1 by Jonathan Lange
Begin supporting +branch over codehosting.
48
from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
49
from lp.code.model.branchnamespace import BranchNamespaceSet
50
from lp.code.model.branchtarget import (
51
    PackageBranchTarget,
52
    ProductBranchTarget,
53
    )
9590.3.3 by Michael Hudson
start addressing review comments
54
from lp.code.xmlrpc.codehosting import datetime_from_tuple
13662.5.3 by Aaron Bentley
Handle invalid source package names.
55
from lp.registry.errors import InvalidName
9113.7.4 by Jonathan Lange
Update many imports of pocket.
56
from lp.registry.interfaces.pocket import PackagePublishingPocket
10100.1.14 by Jonathan Lange
iter_split is in services.
57
from lp.services.utils import iter_split
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
58
from lp.services.xmlrpc import LaunchpadFault
8400.1.8 by Tim Penhey
Fix some imports.
59
from lp.testing.factory import ObjectFactory
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
60
61
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
62
class FakeStore:
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
63
    """Fake store that implements find well enough to pass tests.
64
65
    This is needed because some of the `test_codehosting` tests use
66
    assertSqlAttributeEqualsDate, which relies on ORM behaviour. Here, we fake
67
    enough of the ORM to pass the tests.
68
    """
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
69
70
    def __init__(self, object_set):
71
        self._object_set = object_set
72
73
    def find(self, cls, **kwargs):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
74
        """Implement Store.find that takes two attributes: id and one other.
75
76
        This is called by `assertSqlAttributeEqualsDate`, which relies on
77
        `find` returning either a single match or None. Returning a match
78
        implies that the given attribute has the expected value. Returning
79
        None implies the opposite.
80
        """
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
81
        branch_id = kwargs.pop('id')
82
        assert len(kwargs) == 1, (
83
            'Expected only id and one other. Got %r' % kwargs)
84
        attribute = kwargs.keys()[0]
85
        expected_value = kwargs[attribute]
86
        branch = self._object_set.get(branch_id)
87
        if branch is None:
8447.4.27 by Michael Hudson
fix remaining tests
88
            return FakeResult(None)
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
89
        if expected_value is getattr(branch, attribute):
8447.4.27 by Michael Hudson
fix remaining tests
90
            return FakeResult(branch)
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
91
        return None
92
93
8447.4.27 by Michael Hudson
fix remaining tests
94
class FakeResult:
95
    """As with FakeStore, just enough of a result to pass tests."""
96
97
    def __init__(self, branch):
98
        self._branch = branch
99
100
    def one(self):
101
        return self._branch
102
103
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
104
class FakeDatabaseObject:
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
105
    """Base class for fake database objects."""
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
106
107
    def _set_object_set(self, object_set):
108
        self.__storm_object_info__ = {'store': FakeStore(object_set)}
109
110
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
111
class ObjectSet:
112
    """Generic set of database objects."""
113
114
    def __init__(self):
7055.5.2 by Jonathan Lange
Make object sets use dicts internally so that ids are stable.
115
        self._objects = {}
116
        self._next_id = 1
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
117
118
    def _add(self, db_object):
7055.5.2 by Jonathan Lange
Make object sets use dicts internally so that ids are stable.
119
        self._objects[self._next_id] = db_object
120
        db_object.id = self._next_id
121
        self._next_id += 1
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
122
        db_object._set_object_set(self)
123
        return db_object
124
7055.5.2 by Jonathan Lange
Make object sets use dicts internally so that ids are stable.
125
    def _delete(self, db_object):
126
        del self._objects[db_object.id]
127
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
128
    def __iter__(self):
7055.5.2 by Jonathan Lange
Make object sets use dicts internally so that ids are stable.
129
        return self._objects.itervalues()
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
130
7055.10.11 by Jonathan Lange
Test write permissions thoroughly and simplify the inmemory version somewhat.
131
    def _find(self, **kwargs):
132
        [(key, value)] = kwargs.items()
133
        for obj in self:
134
            if getattr(obj, key) == value:
135
                return obj
136
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
137
    def get(self, id):
7055.5.2 by Jonathan Lange
Make object sets use dicts internally so that ids are stable.
138
        return self._objects.get(id, None)
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
139
140
    def getByName(self, name):
7055.10.11 by Jonathan Lange
Test write permissions thoroughly and simplify the inmemory version somewhat.
141
        return self._find(name=name)
7055.4.13 by Jonathan Lange
Shuffle things around a bit so that the logic is clearer.
142
143
11121.5.38 by Tim Penhey
Make sure that the branch doesn't exist after a link failure.
144
class BranchSet(ObjectSet):
145
    """Extra find methods for branches."""
146
147
    def getByUniqueName(self, unique_name):
148
        return self._find(unique_name=unique_name)
149
150
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
151
class FakeSourcePackage:
152
    """Fake ISourcePackage."""
153
154
    def __init__(self, sourcepackagename, distroseries):
155
        self.sourcepackagename = sourcepackagename
156
        self.distroseries = distroseries
157
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
158
    def __hash__(self):
159
        return hash((self.sourcepackagename.id, self.distroseries.id))
160
161
    def __eq__(self, other):
162
        return (self.sourcepackagename.id == other.sourcepackagename.id
163
                and self.distroseries.id == other.distroseries.id)
164
165
    def __ne__(self, other):
166
        return not (self == other)
167
7362.13.3 by Jonathan Lange
Use distribution property more effectively.
168
    @property
169
    def distribution(self):
7362.13.15 by Jonathan Lange
Guard against distroseries being None
170
        if self.distroseries is not None:
171
            return self.distroseries.distribution
172
        else:
173
            return None
7362.13.3 by Jonathan Lange
Use distribution property more effectively.
174
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
175
    @property
176
    def development_version(self):
177
        name = '%s-devel' % self.distribution.name
178
        dev_series = self._distroseries_set.getByName(name)
179
        if dev_series is None:
180
            dev_series = FakeDistroSeries(name, self.distribution)
181
            self._distroseries_set._add(dev_series)
182
        return self.__class__(self.sourcepackagename, dev_series)
183
184
    @property
185
    def path(self):
186
        return '%s/%s/%s' % (
187
            self.distribution.name,
188
            self.distroseries.name,
189
            self.sourcepackagename.name)
190
191
    def getBranch(self, pocket):
192
        return self.distroseries._linked_branches.get(
193
            (self, pocket), None)
194
195
    def setBranch(self, pocket, branch, registrant):
196
        self.distroseries._linked_branches[self, pocket] = branch
197
198
13662.5.1 by Aaron Bentley
Pushing to non-existant sourcepackagename creates.
199
class SourcePackageNameSet(ObjectSet):
200
201
    def new(self, name_string):
13662.5.3 by Aaron Bentley
Handle invalid source package names.
202
        if not valid_name(name_string):
203
            raise InvalidName(name_string)
13662.5.1 by Aaron Bentley
Pushing to non-existant sourcepackagename creates.
204
        return self._add(FakeSourcePackageName(name_string))
205
206
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
207
@adapter(FakeSourcePackage)
208
@implementer(IBranchTarget)
209
def fake_source_package_to_branch_target(fake_package):
210
    return PackageBranchTarget(fake_package)
211
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
212
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
213
class FakeBranch(FakeDatabaseObject):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
214
    """Fake branch object."""
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
215
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
216
    def __init__(self, branch_type, name, owner, url=None, product=None,
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
217
                 stacked_on=None, private=False, registrant=None,
218
                 distroseries=None, sourcepackagename=None):
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
219
        self.branch_type = branch_type
220
        self.last_mirror_attempt = None
221
        self.last_mirrored = None
222
        self.last_mirrored_id = None
223
        self.next_mirror_time = None
224
        self.url = url
225
        self.mirror_failures = 0
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
226
        self.name = name
227
        self.owner = owner
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
228
        self.stacked_on = None
229
        self.mirror_status_message = None
230
        self.stacked_on = stacked_on
231
        self.private = private
7055.4.15 by Jonathan Lange
Make the tests pass again.
232
        self.product = product
7055.4.18 by Jonathan Lange
Implement createBranch
233
        self.registrant = registrant
7362.9.16 by Jonathan Lange
Merge lower thread
234
        self._mirrored = False
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
235
        self.distroseries = distroseries
236
        self.sourcepackagename = sourcepackagename
7055.4.15 by Jonathan Lange
Make the tests pass again.
237
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
238
    @property
239
    def unique_name(self):
240
        if self.product is None:
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
241
            if self.distroseries is None:
242
                product = '+junk'
243
            else:
244
                product = '%s/%s/%s' % (
245
                    self.distroseries.distribution.name,
246
                    self.distroseries.name,
247
                    self.sourcepackagename.name)
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
248
        else:
249
            product = self.product.name
250
        return '~%s/%s/%s' % (self.owner.name, product, self.name)
251
7055.4.15 by Jonathan Lange
Make the tests pass again.
252
    def getPullURL(self):
8312.3.8 by Michael Hudson
return the branch type from the xml-rpc method too
253
        return 'lp-fake:///' + self.unique_name
254
255
    @property
256
    def target(self):
257
        if self.product is None:
258
            if self.distroseries is None:
259
                target = self.owner
260
            else:
261
                target = self.sourcepackage
262
        else:
263
            target = self.product
264
        return IBranchTarget(target)
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
265
266
    def requestMirror(self):
267
        self.next_mirror_time = UTC_NOW
268
269
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
270
class FakePerson(FakeDatabaseObject):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
271
    """Fake person object."""
14449.6.2 by Curtis Hovey
Updated templates to use is_team property.
272
    is_team = False
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
273
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
274
    def __init__(self, name):
275
        self.name = self.displayname = name
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
276
7055.5.3 by Jonathan Lange
Nail team ownership rules down.
277
    def inTeam(self, person_or_team):
278
        if self is person_or_team:
279
            return True
14449.6.1 by Curtis Hovey
Remove isTeam(). Replace calls with .is_team.
280
        if not person_or_team.is_team:
7055.5.3 by Jonathan Lange
Nail team ownership rules down.
281
            return False
282
        return self in person_or_team._members
283
284
285
class FakeTeam(FakePerson):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
286
    """Fake team."""
14449.6.2 by Curtis Hovey
Updated templates to use is_team property.
287
    is_team = True
7055.5.3 by Jonathan Lange
Nail team ownership rules down.
288
289
    def __init__(self, name, members=None):
290
        super(FakeTeam, self).__init__(name)
291
        if members is None:
292
            self._members = []
293
        else:
294
            self._members = list(members)
295
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
296
7055.4.15 by Jonathan Lange
Make the tests pass again.
297
class FakeProduct(FakeDatabaseObject):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
298
    """Fake product."""
7055.4.15 by Jonathan Lange
Make the tests pass again.
299
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
300
    def __init__(self, name, owner):
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
301
        self.name = name
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
302
        self.owner = owner
11121.5.1 by Jonathan Lange
Begin supporting +branch over codehosting.
303
        self.bzr_path = name
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
304
        self.development_focus = FakeProductSeries(self, 'trunk')
11121.5.30 by Tim Penhey
W00t, all tests pass.
305
        self.series = {
306
            'trunk': self.development_focus,
307
            }
308
309
    def getSeries(self, name):
310
        return self.series.get(name, None)
7055.4.15 by Jonathan Lange
Make the tests pass again.
311
312
8031.2.4 by Jonathan Lange
Use IBranchTarget() in the test, rather than directly getting the stacking
313
@adapter(FakeProduct)
314
@implementer(IBranchTarget)
315
def fake_product_to_branch_target(fake_product):
8031.2.11 by Jonathan Lange
Apply review comments
316
    """Adapt a `FakeProduct` to `IBranchTarget`."""
8031.2.4 by Jonathan Lange
Use IBranchTarget() in the test, rather than directly getting the stacking
317
    return ProductBranchTarget(fake_product)
318
319
11121.5.1 by Jonathan Lange
Begin supporting +branch over codehosting.
320
@adapter(FakeProduct)
321
@implementer(ICanHasLinkedBranch)
322
def fake_product_to_can_has_linked_branch(fake_product):
323
    """Adapt a `FakeProduct` to `ICanHasLinkedBranch`."""
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
324
    return fake_product.development_focus
11121.5.1 by Jonathan Lange
Begin supporting +branch over codehosting.
325
326
7055.4.15 by Jonathan Lange
Make the tests pass again.
327
class FakeProductSeries(FakeDatabaseObject):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
328
    """Fake product series."""
7055.4.15 by Jonathan Lange
Make the tests pass again.
329
7675.85.2 by Jonathan Lange
Undo revision generated by step 2 of process.
330
    branch = None
331
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
332
    def __init__(self, product, name):
333
        self.product = product
334
        self.name = name
335
11121.5.31 by Tim Penhey
More tests and tweaks to make the tests pass.
336
    @property
337
    def bzr_path(self):
338
        if self.product.development_focus is self:
339
            return self.product.name
340
        else:
341
            return "%s/%s" % (self.product.name, self.name)
342
7055.4.15 by Jonathan Lange
Make the tests pass again.
343
11121.5.30 by Tim Penhey
W00t, all tests pass.
344
@adapter(FakeProductSeries)
345
@implementer(ICanHasLinkedBranch)
346
def fake_productseries_to_can_has_linked_branch(fake_productseries):
347
    """Adapt a `FakeProductSeries` to `ICanHasLinkedBranch`."""
348
    return fake_productseries
349
350
7055.4.12 by Jonathan Lange
Start to get a real fake structure.
351
class FakeScriptActivity(FakeDatabaseObject):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
352
    """Fake script activity."""
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
353
354
    def __init__(self, name, hostname, date_started, date_completed):
7055.4.6 by Jonathan Lange
Factor out an object set -- I think this is a good idea.
355
        self.id = self.name = name
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
356
        self.hostname = hostname
357
        self.date_started = datetime_from_tuple(date_started)
358
        self.date_completed = datetime_from_tuple(date_completed)
359
360
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
361
class FakeDistribution(FakeDatabaseObject):
362
363
    def __init__(self, name):
364
        self.name = name
365
366
367
class FakeDistroSeries(FakeDatabaseObject):
368
    """Fake distroseries."""
369
370
    def __init__(self, name, distribution):
371
        self.name = name
372
        self.distribution = distribution
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
373
        self._linked_branches = {}
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
374
375
376
class FakeSourcePackageName(FakeDatabaseObject):
377
    """Fake SourcePackageName."""
378
379
    def __init__(self, name):
380
        self.name = name
381
382
7055.4.19 by Jonathan Lange
Make getBranchInformation pass all the tests. We now have a fully fake
383
DEFAULT_PRODUCT = object()
384
385
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
386
class FakeObjectFactory(ObjectFactory):
387
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
388
    def __init__(self, branch_set, person_set, product_set, distribution_set,
389
                 distroseries_set, sourcepackagename_set):
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
390
        super(FakeObjectFactory, self).__init__()
7055.4.11 by Jonathan Lange
Get rid of the last branch source references.
391
        self._branch_set = branch_set
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
392
        self._person_set = person_set
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
393
        self._product_set = product_set
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
394
        self._distribution_set = distribution_set
395
        self._distroseries_set = distroseries_set
396
        self._sourcepackagename_set = sourcepackagename_set
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
397
7055.4.15 by Jonathan Lange
Make the tests pass again.
398
    def makeBranch(self, branch_type=None, stacked_on=None, private=False,
7055.4.19 by Jonathan Lange
Make getBranchInformation pass all the tests. We now have a fully fake
399
                   product=DEFAULT_PRODUCT, owner=None, name=None,
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
400
                   registrant=None, sourcepackage=None):
8312.3.8 by Michael Hudson
return the branch type from the xml-rpc method too
401
        if branch_type is None:
402
            branch_type = BranchType.HOSTED
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
403
        if branch_type == BranchType.MIRRORED:
404
            url = self.getUniqueURL()
405
        else:
406
            url = None
7055.4.18 by Jonathan Lange
Implement createBranch
407
        if name is None:
408
            name = self.getUniqueString()
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
409
        if owner is None:
410
            owner = self.makePerson()
7055.4.19 by Jonathan Lange
Make getBranchInformation pass all the tests. We now have a fully fake
411
        if product is DEFAULT_PRODUCT:
412
            product = self.makeProduct()
7055.4.18 by Jonathan Lange
Implement createBranch
413
        if registrant is None:
414
            registrant = self.makePerson()
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
415
        if sourcepackage is None:
416
            sourcepackagename = None
417
            distroseries = None
418
        else:
419
            sourcepackagename = sourcepackage.sourcepackagename
420
            distroseries = sourcepackage.distroseries
7055.4.18 by Jonathan Lange
Implement createBranch
421
        IBranch['name'].validate(unicode(name))
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
422
        branch = FakeBranch(
7055.4.18 by Jonathan Lange
Implement createBranch
423
            branch_type, name=name, owner=owner, url=url,
424
            stacked_on=stacked_on, product=product, private=private,
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
425
            registrant=registrant, distroseries=distroseries,
426
            sourcepackagename=sourcepackagename)
7055.4.11 by Jonathan Lange
Get rid of the last branch source references.
427
        self._branch_set._add(branch)
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
428
        return branch
429
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
430
    def makeAnyBranch(self, **kwargs):
431
        return self.makeProductBranch(**kwargs)
432
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
433
    def makePackageBranch(self, sourcepackage=None, **kwargs):
434
        if sourcepackage is None:
435
            sourcepackage = self.makeSourcePackage()
436
        return self.makeBranch(
437
            product=None, sourcepackage=sourcepackage, **kwargs)
438
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
439
    def makePersonalBranch(self, owner=None, **kwargs):
440
        if owner is None:
441
            owner = self.makePerson()
442
        return self.makeBranch(
443
            owner=owner, product=None, sourcepackage=None, **kwargs)
444
445
    def makeProductBranch(self, product=None, **kwargs):
446
        if product is None:
447
            product = self.makeProduct()
448
        return self.makeBranch(product=product, sourcepackage=None, **kwargs)
449
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
450
    def makeDistribution(self):
451
        distro = FakeDistribution(self.getUniqueString())
452
        self._distribution_set._add(distro)
453
        return distro
454
13597.1.1 by Jeroen Vermeulen
Get rid of factory.make[Ubuntu]DistroRelease. And some lint.
455
    def makeDistroSeries(self):
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
456
        distro = self.makeDistribution()
457
        distroseries_name = self.getUniqueString()
458
        distroseries = FakeDistroSeries(distroseries_name, distro)
459
        self._distroseries_set._add(distroseries)
460
        return distroseries
461
462
    def makeSourcePackageName(self):
13662.5.1 by Aaron Bentley
Pushing to non-existant sourcepackagename creates.
463
        return self._sourcepackagename_set.new(self.getUniqueString())
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
464
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
465
    def makeSourcePackage(self, distroseries=None, sourcepackagename=None):
466
        if distroseries is None:
13597.1.1 by Jeroen Vermeulen
Get rid of factory.make[Ubuntu]DistroRelease. And some lint.
467
            distroseries = self.makeDistroSeries()
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
468
        if sourcepackagename is None:
469
            sourcepackagename = self.makeSourcePackageName()
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
470
        package = FakeSourcePackage(sourcepackagename, distroseries)
471
        package._distroseries_set = self._distroseries_set
472
        return package
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
473
7055.5.3 by Jonathan Lange
Nail team ownership rules down.
474
    def makeTeam(self, owner):
475
        team = FakeTeam(name=self.getUniqueString(), members=[owner])
476
        self._person_set._add(team)
477
        return team
478
11121.5.38 by Tim Penhey
Make sure that the branch doesn't exist after a link failure.
479
    def makePerson(self, name=None):
480
        if name is None:
481
            name = self.getUniqueString()
482
        person = FakePerson(name=name)
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
483
        self._person_set._add(person)
484
        return person
485
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
486
    def makeProduct(self, name=None, owner=None):
487
        if name is None:
488
            name = self.getUniqueString()
489
        if owner is None:
490
            owner = self.makePerson()
491
        product = FakeProduct(name, owner)
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
492
        self._product_set._add(product)
493
        return product
7055.4.15 by Jonathan Lange
Make the tests pass again.
494
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
495
    def makeProductSeries(self, product, name=None):
496
        if name is None:
497
            name = self.getUniqueString()
11121.5.30 by Tim Penhey
W00t, all tests pass.
498
        series = FakeProductSeries(product, name)
499
        product.series[name] = series
500
        return series
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
501
7362.9.16 by Jonathan Lange
Merge lower thread
502
    def enableDefaultStackingForProduct(self, product, branch=None):
503
        """Give 'product' a default stacked-on branch.
504
505
        :param product: The product to give a default stacked-on branch to.
506
        :param branch: The branch that should be the default stacked-on
507
            branch.  If not supplied, a fresh branch will be created.
508
        """
509
        if branch is None:
510
            branch = self.makeBranch(product=product)
8137.2.2 by Jonathan Lange
Fix last remaining use of user_branch
511
        product.development_focus.branch = branch
9590.13.5 by Michael Hudson
fix test_codehosting tests
512
        branch.last_mirrored_id = 'rev1'
7362.9.16 by Jonathan Lange
Merge lower thread
513
        return branch
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
514
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
515
    def enableDefaultStackingForPackage(self, package, branch):
516
        """Give 'package' a default stacked-on branch.
517
518
        :param package: The package to give a default stacked-on branch to.
519
        :param branch: The branch that should be the default stacked-on
520
            branch.
521
        """
522
        package.development_version.setBranch(
523
            PackagePublishingPocket.RELEASE, branch, branch.owner)
9590.13.5 by Michael Hudson
fix test_codehosting tests
524
        branch.last_mirrored_id = 'rev1'
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
525
        return branch
526
8031.2.7 by Jonathan Lange
Remove default_stacked_on_branch from product, make all the tests use
527
9590.1.49 by Michael Hudson
more combining the puller and filesystem endpoints
528
class FakeCodehosting:
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
529
9590.1.49 by Michael Hudson
more combining the puller and filesystem endpoints
530
    def __init__(self, branch_set, person_set, product_set, distribution_set,
531
                 distroseries_set, sourcepackagename_set, factory,
532
                 script_activity_set):
7055.4.7 by Jonathan Lange
Pass the branch set to the fake puller
533
        self._branch_set = branch_set
9590.1.49 by Michael Hudson
more combining the puller and filesystem endpoints
534
        self._person_set = person_set
535
        self._product_set = product_set
536
        self._distribution_set = distribution_set
537
        self._distroseries_set = distroseries_set
538
        self._sourcepackagename_set = sourcepackagename_set
539
        self._factory = factory
7055.4.7 by Jonathan Lange
Pass the branch set to the fake puller
540
        self._script_activity_set = script_activity_set
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
541
10379.2.5 by Michael Hudson
integration test and implied fixes
542
    def acquireBranchToPull(self, branch_type_names):
10379.2.3 by Michael Hudson
acquireBranchToPull changes
543
        if not branch_type_names:
544
            branch_type_names = 'HOSTED', 'MIRRORED', 'IMPORTED'
545
        branch_types = []
546
        for branch_type_name in branch_type_names:
547
            try:
548
                branch_types.append(BranchType.items[branch_type_name])
549
            except KeyError:
550
                raise UnknownBranchTypeError(
551
                    'Unknown branch type: %r' % (branch_type_name,))
8312.3.5 by Michael Hudson
mostly copy and pasted tests for the xml-rpc endpoint
552
        branches = sorted(
553
            [branch for branch in self._branch_set
9032.2.2 by Michael Hudson
test and fix
554
             if branch.next_mirror_time is not None
10379.2.3 by Michael Hudson
acquireBranchToPull changes
555
             and branch.branch_type in branch_types],
8312.3.5 by Michael Hudson
mostly copy and pasted tests for the xml-rpc endpoint
556
            key=operator.attrgetter('next_mirror_time'))
557
        if branches:
558
            branch = branches[-1]
9590.1.79 by Michael Hudson
readability++
559
            # Mark it as started mirroring.
560
            branch.last_mirror_attempt = UTC_NOW
561
            branch.next_mirror_time = None
8312.3.8 by Michael Hudson
return the branch type from the xml-rpc method too
562
            default_branch = branch.target.default_stacked_on_branch
8811.1.1 by Michael Hudson
assorted tests and tweaks
563
            if default_branch is None:
564
                default_branch_name = ''
565
            elif (branch.branch_type == BranchType.MIRRORED
566
                  and default_branch.private):
567
                default_branch_name = ''
8312.3.8 by Michael Hudson
return the branch type from the xml-rpc method too
568
            else:
8811.1.1 by Michael Hudson
assorted tests and tweaks
569
                default_branch_name = '/' + default_branch.unique_name
8312.3.8 by Michael Hudson
return the branch type from the xml-rpc method too
570
            return (branch.id, branch.getPullURL(), branch.unique_name,
571
                    default_branch_name, branch.branch_type.name)
8312.3.5 by Michael Hudson
mostly copy and pasted tests for the xml-rpc endpoint
572
        else:
573
            return ()
574
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
575
    def mirrorFailed(self, branch_id, reason):
7055.4.7 by Jonathan Lange
Pass the branch set to the fake puller
576
        branch = self._branch_set.get(branch_id)
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
577
        if branch is None:
578
            return faults.NoBranchWithID(branch_id)
579
        branch.mirror_failures += 1
580
        branch.mirror_status_message = reason
581
        return True
582
583
    def recordSuccess(self, name, hostname, date_started, date_completed):
7055.4.7 by Jonathan Lange
Pass the branch set to the fake puller
584
        self._script_activity_set._add(
7055.4.6 by Jonathan Lange
Factor out an object set -- I think this is a good idea.
585
            FakeScriptActivity(name, hostname, date_started, date_completed))
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
586
        return True
587
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
588
    def _parseUniqueName(self, branch_path):
589
        """Return a dict of the parsed information and the branch name."""
7213.4.4 by Jonathan Lange
createBranch now raises PermissionDenied when users try to create branches
590
        try:
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
591
            namespace_path, branch_name = branch_path.rsplit('/', 1)
7213.4.4 by Jonathan Lange
createBranch now raises PermissionDenied when users try to create branches
592
        except ValueError:
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
593
            raise faults.PermissionDenied(
11121.5.31 by Tim Penhey
More tests and tweaks to make the tests pass.
594
                "Cannot create branch at '/%s'" % branch_path)
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
595
        data = BranchNamespaceSet().parse(namespace_path)
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
596
        return data, branch_name
597
598
    def _createBranch(self, registrant, branch_path):
599
        """The guts of the create branch method.
600
601
        Raises exceptions on error conditions.
602
        """
603
        to_link = None
604
        if branch_path.startswith(BRANCH_ALIAS_PREFIX + '/'):
605
            branch_path = branch_path[len(BRANCH_ALIAS_PREFIX) + 1:]
606
            if branch_path.startswith('~'):
607
                data, branch_name = self._parseUniqueName(branch_path)
608
            else:
609
                tokens = branch_path.split('/')
610
                data = {
611
                    'person': registrant.name,
612
                    'product': tokens[0],
613
                    }
614
                branch_name = 'trunk'
615
                # check the series
616
                product = self._product_set.getByName(data['product'])
617
                if product is not None:
11121.5.30 by Tim Penhey
W00t, all tests pass.
618
                    if len(tokens) > 1:
619
                        series = product.getSeries(tokens[1])
620
                        if series is None:
621
                            raise faults.NotFound(
622
                                "No such product series: '%s'." % tokens[1])
623
                        else:
624
                            to_link = ICanHasLinkedBranch(series)
625
                    else:
626
                        to_link = ICanHasLinkedBranch(product)
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
627
                # don't forget the link.
628
        else:
629
            data, branch_name = self._parseUniqueName(branch_path)
630
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
631
        owner = self._person_set.getByName(data['person'])
7055.4.18 by Jonathan Lange
Implement createBranch
632
        if owner is None:
11121.5.30 by Tim Penhey
W00t, all tests pass.
633
            raise faults.NotFound(
9738.1.11 by Michael Hudson
fix test failures
634
                "User/team '%s' does not exist." % (data['person'],))
7055.5.9 by Jonathan Lange
Improve comments on authorization checks
635
        # The real code consults the branch creation policy of the product. We
636
        # don't need to do so here, since the tests above this layer never
637
        # encounter that behaviour. If they *do* change to rely on the branch
638
        # creation policy, the observed behaviour will be failure to raise
639
        # exceptions.
640
        if not registrant.inTeam(owner):
11121.5.30 by Tim Penhey
W00t, all tests pass.
641
            raise faults.PermissionDenied(
7055.4.18 by Jonathan Lange
Implement createBranch
642
                ('%s cannot create branches owned by %s'
643
                 % (registrant.displayname, owner.displayname)))
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
644
        product = sourcepackage = None
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
645
        if data['product'] == '+junk':
7055.4.18 by Jonathan Lange
Implement createBranch
646
            product = None
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
647
        elif data['product'] is not None:
648
            product = self._product_set.getByName(data['product'])
7055.4.18 by Jonathan Lange
Implement createBranch
649
            if product is None:
11121.5.30 by Tim Penhey
W00t, all tests pass.
650
                raise faults.NotFound(
9738.1.11 by Michael Hudson
fix test failures
651
                    "Project '%s' does not exist." % (data['product'],))
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
652
        elif data['distribution'] is not None:
653
            distro = self._distribution_set.getByName(data['distribution'])
7362.9.14 by Jonathan Lange
Tests for all sorts of not found errors.
654
            if distro is None:
11121.5.30 by Tim Penhey
W00t, all tests pass.
655
                raise faults.NotFound(
7362.9.27 by Jonathan Lange
Whole bunch of minor exception-related crap.
656
                    "No such distribution: '%s'." % (data['distribution'],))
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
657
            distroseries = self._distroseries_set.getByName(
658
                data['distroseries'])
7362.9.14 by Jonathan Lange
Tests for all sorts of not found errors.
659
            if distroseries is None:
11121.5.30 by Tim Penhey
W00t, all tests pass.
660
                raise faults.NotFound(
7362.9.27 by Jonathan Lange
Whole bunch of minor exception-related crap.
661
                    "No such distribution series: '%s'."
7362.9.14 by Jonathan Lange
Tests for all sorts of not found errors.
662
                    % (data['distroseries'],))
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
663
            sourcepackagename = self._sourcepackagename_set.getByName(
664
                data['sourcepackagename'])
7362.9.14 by Jonathan Lange
Tests for all sorts of not found errors.
665
            if sourcepackagename is None:
13662.5.3 by Aaron Bentley
Handle invalid source package names.
666
                try:
667
                    sourcepackagename = self._sourcepackagename_set.new(
668
                        data['sourcepackagename'])
669
                except InvalidName:
670
                    raise faults.InvalidSourcePackageName(
671
                        data['sourcepackagename'])
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
672
            sourcepackage = self._factory.makeSourcePackage(
673
                distroseries, sourcepackagename)
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
674
        else:
11121.5.30 by Tim Penhey
W00t, all tests pass.
675
            raise faults.PermissionDenied(
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
676
                "Cannot create branch at '%s'" % branch_path)
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
677
        branch = self._factory.makeBranch(
678
            owner=owner, name=branch_name, product=product,
679
            sourcepackage=sourcepackage, registrant=registrant,
680
            branch_type=BranchType.HOSTED)
681
        if to_link is not None:
682
            if registrant.inTeam(to_link.product.owner):
683
                to_link.branch = branch
684
            else:
11121.5.38 by Tim Penhey
Make sure that the branch doesn't exist after a link failure.
685
                self._branch_set._delete(branch)
11121.5.30 by Tim Penhey
W00t, all tests pass.
686
                raise faults.PermissionDenied(
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
687
                    "Cannot create linked branch at '%s'." % branch_path)
688
        return branch.id
689
690
    def createBranch(self, requester_id, branch_path):
691
        if not branch_path.startswith('/'):
692
            return faults.InvalidPath(branch_path)
693
        escaped_path = unescape(branch_path.strip('/'))
694
        registrant = self._person_set.get(requester_id)
7055.4.18 by Jonathan Lange
Implement createBranch
695
        try:
11121.5.29 by Tim Penhey
5 out of 7 failing inmemory tests now passing.
696
            return self._createBranch(registrant, escaped_path)
697
        except LaunchpadFault, e:
698
            return e
7055.4.18 by Jonathan Lange
Implement createBranch
699
        except LaunchpadValidationError, e:
9738.1.9 by Michael Hudson
tests for internal xml-rpc changes and fixes to in-memory double of same
700
            msg = e.args[0]
701
            if isinstance(msg, unicode):
702
                msg = msg.encode('utf-8')
703
            return faults.PermissionDenied(msg)
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
704
705
    def requestMirror(self, requester_id, branch_id):
706
        self._branch_set.get(branch_id).requestMirror()
707
9590.4.8 by Michael Hudson
don't use removeSecurityProxy to freely
708
    def branchChanged(self, login_id, branch_id, stacked_on_location,
709
                      last_revision_id, control_string, branch_string,
710
                      repository_string):
9590.2.12 by Michael Hudson
inmemory implementation of reporting bogus stacking
711
        branch = self._branch_set._find(id=branch_id)
712
        if branch is None:
713
            return faults.NoBranchWithID(branch_id)
9590.3.1 by Michael Hudson
clear the mirror_status_message if there is no error in branchChanged
714
        branch.mirror_status_message = None
9590.2.8 by Michael Hudson
in memory implementaion of branchChanged, one more test
715
        if stacked_on_location == '':
716
            stacked_on_branch = None
717
        else:
718
            # We could log or something if the branch is not found here, but
719
            # we just wait until the scanner fails and sets up an appropriate
720
            # message.
721
            stacked_on_branch = self._branch_set._find(
722
                unique_name=stacked_on_location.strip('/'))
9590.2.12 by Michael Hudson
inmemory implementation of reporting bogus stacking
723
            if stacked_on_branch is None:
724
                branch.mirror_status_message = (
725
                    'Invalid stacked on location: ' + stacked_on_location)
9590.2.8 by Michael Hudson
in memory implementaion of branchChanged, one more test
726
        branch.stacked_on = stacked_on_branch
9590.3.3 by Michael Hudson
start addressing review comments
727
        branch.last_mirrored = UTC_NOW
9590.2.8 by Michael Hudson
in memory implementaion of branchChanged, one more test
728
        if branch.last_mirrored_id != last_revision_id:
729
            branch.last_mirrored_id = last_revision_id
9590.2.23 by Michael Hudson
unit tests for branchChange recording the format
730
731
        def match_title(enum, title, default):
732
            for value in enum.items:
733
                if value.title == title:
734
                    return value
735
            else:
736
                return default
737
738
        branch.control_format = match_title(
739
            ControlFormat, control_string, ControlFormat.UNRECOGNIZED)
740
        branch.branch_format = match_title(
741
            BranchFormat, branch_string, BranchFormat.UNRECOGNIZED)
742
        branch.repository_format = match_title(
743
            RepositoryFormat, repository_string,
744
            RepositoryFormat.UNRECOGNIZED)
745
9590.2.8 by Michael Hudson
in memory implementaion of branchChanged, one more test
746
        return True
747
7055.5.10 by Jonathan Lange
Make canRead a little better.
748
    def _canRead(self, person_id, branch):
749
        """Can the person 'person_id' see 'branch'?"""
750
        # This is a substitute for an actual launchpad.View check on the
751
        # branch. It doesn't have to match the behaviour exactly, as long as
752
        # it's stricter than the real implementation (that way, mismatches in
753
        # behaviour should generate explicit errors.)
754
        if person_id == LAUNCHPAD_SERVICES:
755
            return True
7551.3.7 by Michael Hudson
use a new, custom value rather than ftests.ANONYMOUS to indicate the anonymous user to translatePath
756
        if person_id == LAUNCHPAD_ANONYMOUS:
7459.5.20 by Michael Hudson
let have us some tests for translatePath(ANONYMOUS, ...)
757
            return not branch.private
7055.5.10 by Jonathan Lange
Make canRead a little better.
758
        if not branch.private:
759
            return True
760
        person = self._person_set.get(person_id)
761
        return person.inTeam(branch.owner)
762
7055.5.3 by Jonathan Lange
Nail team ownership rules down.
763
    def _canWrite(self, person_id, branch):
764
        """Can the person 'person_id' write to 'branch'?"""
7551.3.7 by Michael Hudson
use a new, custom value rather than ftests.ANONYMOUS to indicate the anonymous user to translatePath
765
        if person_id in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]:
7055.5.3 by Jonathan Lange
Nail team ownership rules down.
766
            return False
767
        if branch.branch_type != BranchType.HOSTED:
768
            return False
769
        person = self._person_set.get(person_id)
770
        return person.inTeam(branch.owner)
771
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
772
    def _get_product_target(self, path):
773
        try:
774
            owner_name, product_name = path.split('/')
775
        except ValueError:
776
            # Wrong number of segments -- can't be a product.
777
            return
778
        product = self._product_set.getByName(product_name)
779
        return product
780
781
    def _get_package_target(self, path):
782
        try:
783
            owner_name, distro_name, series_name, package_name = (
784
                path.split('/'))
785
        except ValueError:
786
            # Wrong number of segments -- can't be a package.
787
            return
788
        distro = self._distribution_set.getByName(distro_name)
789
        distroseries = self._distroseries_set.getByName(series_name)
790
        sourcepackagename = self._sourcepackagename_set.getByName(
791
            package_name)
792
        if None in (distro, distroseries, sourcepackagename):
793
            return
794
        return self._factory.makeSourcePackage(
795
            distroseries, sourcepackagename)
796
7213.4.8 by Jonathan Lange
_getProduct -> _serializeControlDirectory.
797
    def _serializeControlDirectory(self, requester, product_path,
798
                                   trailing_path):
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
799
        if not ('.bzr' == trailing_path or trailing_path.startswith('.bzr/')):
800
            return
801
        target = self._get_product_target(product_path)
802
        if target is None:
803
            target = self._get_package_target(product_path)
804
        if target is None:
805
            return
806
        default_branch = IBranchTarget(target).default_stacked_on_branch
7055.10.22 by Jonathan Lange
Handle the case where there is no default stacked-on branch set.
807
        if default_branch is None:
808
            return
7055.10.24 by Jonathan Lange
Return a NotFound for product control directories where the stacked-on branch
809
        if not self._canRead(requester, default_branch):
810
            return
12707.7.6 by Tim Penhey
Created a function to do the basic alias assembly.
811
        path = branch_id_alias(default_branch)
7213.4.11 by Jonathan Lange
Return escaped URLs for the stacked-on branch.
812
        return (
813
            CONTROL_TRANSPORT,
12707.7.5 by Tim Penhey
Update the tests and the in-memory mock.
814
            {'default_stack_on': escape(path)},
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
815
            trailing_path)
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
816
12707.4.7 by Tim Penhey
Fix the inmemory xmlrpc mock.
817
    def _serializeBranch(self, requester_id, branch, trailing_path,
818
                         force_readonly=False):
7213.4.7 by Jonathan Lange
extract serialize branch from inmemory translatePath.
819
        if not self._canRead(requester_id, branch):
7459.6.5 by Michael Hudson
kill PERMISSION_DENIED_FAULT_CODE, replace with faults.PermissionDenied
820
            return faults.PermissionDenied()
7213.4.7 by Jonathan Lange
extract serialize branch from inmemory translatePath.
821
        elif branch.branch_type == BranchType.REMOTE:
822
            return None
12707.4.7 by Tim Penhey
Fix the inmemory xmlrpc mock.
823
        if force_readonly:
824
            writable = False
7213.4.7 by Jonathan Lange
extract serialize branch from inmemory translatePath.
825
        else:
12707.4.7 by Tim Penhey
Fix the inmemory xmlrpc mock.
826
            writable = self._canWrite(requester_id, branch)
827
        return (
828
            BRANCH_TRANSPORT,
829
            {'id': branch.id, 'writable': writable},
830
            trailing_path)
831
832
    def _translateBranchIdAlias(self, requester, path):
833
        # If the path isn't a branch id alias, nothing more to do.
834
        stripped_path = unescape(path.strip('/'))
835
        if not stripped_path.startswith(BRANCH_ID_ALIAS_PREFIX + '/'):
836
            return None
837
        try:
838
            parts = stripped_path.split('/', 2)
839
            branch_id = int(parts[1])
840
        except (ValueError, IndexError):
841
            return faults.PathTranslationError(path)
842
        branch = self._branch_set.get(branch_id)
843
        if branch is None:
844
            return faults.PathTranslationError(path)
845
        try:
846
            trailing = parts[2]
847
        except IndexError:
848
            trailing = ''
849
        return self._serializeBranch(requester, branch, trailing, True)
7213.4.7 by Jonathan Lange
extract serialize branch from inmemory translatePath.
850
7055.10.1 by Jonathan Lange
Start work on translatePath.
851
    def translatePath(self, requester_id, path):
7055.10.4 by Jonathan Lange
Make the preceding slash compulsory.
852
        if not path.startswith('/'):
853
            return faults.InvalidPath(path)
12707.4.7 by Tim Penhey
Fix the inmemory xmlrpc mock.
854
        branch = self._translateBranchIdAlias(requester_id, path)
855
        if branch is not None:
856
            return branch
7055.10.3 by Jonathan Lange
Start bringing in branch translation.
857
        stripped_path = path.strip('/')
7055.10.5 by Jonathan Lange
Handle child paths within a branch.
858
        for first, second in iter_split(stripped_path, '/'):
7055.10.6 by Jonathan Lange
translatePath should handle escaped paths.
859
            first = unescape(first).encode('utf-8')
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
860
            # Is it a branch?
11121.5.1 by Jonathan Lange
Begin supporting +branch over codehosting.
861
            if first.startswith('+branch/'):
11121.5.6 by Jonathan Lange
Fix the inmemory server
862
                component_name = first[len('+branch/'):]
863
                product = self._product_set.getByName(component_name)
864
                if product:
865
                    branch = product.development_focus.branch
866
                else:
13597.1.1 by Jeroen Vermeulen
Get rid of factory.make[Ubuntu]DistroRelease. And some lint.
867
                    branch = self._branch_set._find(
868
                        unique_name=component_name)
11121.5.1 by Jonathan Lange
Begin supporting +branch over codehosting.
869
            else:
870
                branch = self._branch_set._find(unique_name=first)
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
871
            if branch is not None:
7213.4.7 by Jonathan Lange
extract serialize branch from inmemory translatePath.
872
                branch = self._serializeBranch(requester_id, branch, second)
7459.6.2 by Michael Hudson
fix implementations
873
                if isinstance(branch, Fault):
874
                    return branch
875
                elif branch is None:
7213.4.7 by Jonathan Lange
extract serialize branch from inmemory translatePath.
876
                    break
877
                return branch
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
878
879
            # Is it a product?
7213.4.8 by Jonathan Lange
_getProduct -> _serializeControlDirectory.
880
            product = self._serializeControlDirectory(
881
                requester_id, first, second)
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
882
            if product:
883
                return product
7055.10.1 by Jonathan Lange
Start work on translatePath.
884
        return faults.PathTranslationError(path)
885
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
886
7055.5.4 by Jonathan Lange
Eschew 'fake' and 'real' for 'inmemory' and 'db'.
887
class InMemoryFrontend:
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
888
    """A in-memory 'frontend' to Launchpad's branch services.
889
890
    This is an in-memory version of `LaunchpadDatabaseFrontend`.
891
    """
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
892
893
    def __init__(self):
11121.5.38 by Tim Penhey
Make sure that the branch doesn't exist after a link failure.
894
        self._branch_set = BranchSet()
7055.4.8 by Jonathan Lange
Make the set concept a little stronger.
895
        self._script_activity_set = ObjectSet()
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
896
        self._person_set = ObjectSet()
7055.4.17 by Jonathan Lange
And now, getDefaultStackedOnBranch works.
897
        self._product_set = ObjectSet()
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
898
        self._distribution_set = ObjectSet()
899
        self._distroseries_set = ObjectSet()
13662.5.1 by Aaron Bentley
Pushing to non-existant sourcepackagename creates.
900
        self._sourcepackagename_set = SourcePackageNameSet()
7055.4.18 by Jonathan Lange
Implement createBranch
901
        self._factory = FakeObjectFactory(
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
902
            self._branch_set, self._person_set, self._product_set,
903
            self._distribution_set, self._distroseries_set,
904
            self._sourcepackagename_set)
9590.1.49 by Michael Hudson
more combining the puller and filesystem endpoints
905
        self._codehosting = FakeCodehosting(
7055.4.18 by Jonathan Lange
Implement createBranch
906
            self._branch_set, self._person_set, self._product_set,
7362.9.13 by Jonathan Lange
Add a test for creating a sourcepackage branch via createBranch.
907
            self._distribution_set, self._distroseries_set,
9590.1.49 by Michael Hudson
more combining the puller and filesystem endpoints
908
            self._sourcepackagename_set, self._factory,
909
            self._script_activity_set)
8031.2.7 by Jonathan Lange
Remove default_stacked_on_branch from product, make all the tests use
910
        sm = getSiteManager()
11121.5.1 by Jonathan Lange
Begin supporting +branch over codehosting.
911
        sm.registerAdapter(fake_product_to_can_has_linked_branch)
8031.2.7 by Jonathan Lange
Remove default_stacked_on_branch from product, make all the tests use
912
        sm.registerAdapter(fake_product_to_branch_target)
8031.4.9 by Jonathan Lange
Get the in memory versions passing.
913
        sm.registerAdapter(fake_source_package_to_branch_target)
11121.5.30 by Tim Penhey
W00t, all tests pass.
914
        sm.registerAdapter(fake_productseries_to_can_has_linked_branch)
7055.4.16 by Jonathan Lange
Start running the filesystem tests against a fake. They don't pass yet.
915
9590.1.49 by Michael Hudson
more combining the puller and filesystem endpoints
916
    def getCodehostingEndpoint(self):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
917
        """See `LaunchpadDatabaseFrontend`.
918
919
        Return an in-memory implementation of IBranchFileSystem that passes
920
        the tests in `test_codehosting`.
921
        """
9590.1.49 by Michael Hudson
more combining the puller and filesystem endpoints
922
        return self._codehosting
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
923
924
    def getLaunchpadObjectFactory(self):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
925
        """See `LaunchpadDatabaseFrontend`.
926
927
        Returns a partial, in-memory implementation of LaunchpadObjectFactory
928
        -- enough to pass the tests.
929
        """
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
930
        return self._factory
931
7940.2.10 by Jonathan Lange
Get the codehosting xmlrpc tests passing.
932
    def getBranchLookup(self):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
933
        """See `LaunchpadDatabaseFrontend`.
934
7940.2.10 by Jonathan Lange
Get the codehosting xmlrpc tests passing.
935
        Returns a partial implementation of `IBranchLookup` -- enough to pass
936
        the tests.
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
937
        """
7055.4.8 by Jonathan Lange
Make the set concept a little stronger.
938
        return self._branch_set
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
939
940
    def getLastActivity(self, activity_name):
7055.5.11 by Jonathan Lange
Add a bunch of docstrings.
941
        """Get the last script activity with 'activity_name'."""
7055.4.8 by Jonathan Lange
Make the set concept a little stronger.
942
        return self._script_activity_set.getByName(activity_name)
7055.4.29 by Jonathan Lange
Add an XMLRPCWrapper that deals with Zope's crazy fault handling.
943
944
945
class XMLRPCWrapper:
946
    """Wrapper around the endpoints that emulates an XMLRPC client."""
947
948
    def __init__(self, endpoint):
949
        self.endpoint = endpoint
950
11583.3.15 by Jonathan Lange
Use the DeferredBlockingProxy in codehosting.
951
    def _callRemote(self, method_name, *args):
7055.4.29 by Jonathan Lange
Add an XMLRPCWrapper that deals with Zope's crazy fault handling.
952
        result = getattr(self.endpoint, method_name)(*args)
953
        if isinstance(result, Fault):
954
            raise result
955
        return result
11583.3.15 by Jonathan Lange
Use the DeferredBlockingProxy in codehosting.
956
957
    def callRemote(self, method_name, *args):
958
        return defer.maybeDeferred(self._callRemote, method_name, *args)