~launchpad-pqm/launchpad/devel

14083.1.1 by Jeroen Vermeulen
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).
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
3
5743.2.2 by Tim Penhey
Listing now has revision number and codebrowse hyperlink.
4
"""Tests for Revisions."""
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
5
6
__metaclass__ = type
7
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
8
from datetime import (
9
    datetime,
10
    timedelta,
11
    )
6789.3.9 by Jonathan Lange
Move the revision from bzr revision method into RevisionSet.
12
import time
13677.3.2 by Steve Kowalik
Remove unneeded unittest imports.
13
from unittest import TestCase
5743.2.6 by Tim Penhey
Updates following review
14
5821.2.32 by James Henstridge
* s/psycopg/psycopg2/ in a few more places.
15
import psycopg2
6789.3.9 by Jonathan Lange
Move the revision from bzr revision method into RevisionSet.
16
import pytz
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
17
from storm.store import Store
5743.2.6 by Tim Penhey
Updates following review
18
from zope.component import getUtility
6789.3.9 by Jonathan Lange
Move the revision from bzr revision method into RevisionSet.
19
from zope.security.proxy import removeSecurityProxy
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
20
8555.2.2 by Tim Penhey
Move enum -> enums.
21
from lp.code.enums import BranchLifecycleStatus
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
22
from lp.code.interfaces.branchlookup import IBranchLookup
8225.3.2 by Tim Penhey
Sort the imports.
23
from lp.code.interfaces.revision import IRevisionSet
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
24
from lp.code.model.revision import (
25
    RevisionCache,
26
    RevisionSet,
27
    )
8225.3.2 by Tim Penhey
Sort the imports.
28
from lp.registry.model.karma import Karma
14083.1.1 by Jeroen Vermeulen
Lint.
29
from lp.scripts.garbo import RevisionAuthorEmailLinker
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
30
from lp.services.database.lpstorm import IMasterObject
31
from lp.services.database.sqlbase import cursor
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
32
from lp.services.identity.interfaces.account import AccountStatus
12070.1.17 by Tim Penhey
Replace MockLogger with DevNullLogger.
33
from lp.services.log.logger import DevNullLogger
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
34
from lp.services.webapp.interfaces import (
35
    DEFAULT_FLAVOR,
36
    IStoreSelector,
37
    MAIN_STORE,
38
    )
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
39
from lp.testing import (
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
40
    login,
41
    logout,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
42
    TestCaseWithFactory,
43
    time_counter,
44
    )
7675.181.1 by Jonathan Lange
Merge stable, resolving conflict in test_revision.py
45
from lp.testing.factory import LaunchpadObjectFactory
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
46
from lp.testing.layers import DatabaseFunctionalLayer
8225.3.2 by Tim Penhey
Sort the imports.
47
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
48
7925.4.1 by Tim Penhey
Don't create revisions with future revision_dates.
49
class TestRevisionCreationDate(TestCaseWithFactory):
50
    """Test that RevisionSet.new won't create revisions with future dates."""
51
52
    layer = DatabaseFunctionalLayer
53
54
    def test_new_past_revision_date(self):
55
        # A revision created with a revision date in the past works fine.
56
        past_date = datetime(2009, 1, 1, tzinfo=pytz.UTC)
57
        revision = RevisionSet().new(
58
            'rev_id', 'log body', past_date, 'author', [], {})
59
        self.assertEqual(past_date, revision.revision_date)
60
61
    def test_new_future_revision_date(self):
62
        # A revision with a future date gets the revision date set to
63
        # date_created.
64
        now = datetime.now(pytz.UTC)
65
        future_date = now + timedelta(days=1)
66
        revision = RevisionSet().new(
67
            'rev_id', 'log body', future_date, 'author', [], {})
68
        self.assertEqual(revision.date_created, revision.revision_date)
69
        self.assertTrue(revision.revision_date <= now)
70
71
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
72
class TestRevisionKarma(TestCaseWithFactory):
6623.4.25 by Tim Penhey
Make sure we don't try to allocate karma to junk branches.
73
    """Test the allocation of karma for revisions."""
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
74
6623.4.13 by Tim Penhey
Change the layers for the tests.
75
    layer = DatabaseFunctionalLayer
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
76
77
    def setUp(self):
78
        # Use an administrator to set branch privacy easily.
6623.4.25 by Tim Penhey
Make sure we don't try to allocate karma to junk branches.
79
        TestCaseWithFactory.setUp(self, "admin@canonical.com")
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
80
81
    def test_revisionWithUnknownEmail(self):
82
        # A revision when created does not have karma allocated.
83
        rev = self.factory.makeRevision()
6623.4.20 by Tim Penhey
Testing the guts of the cron script and creating a dedicated db user.
84
        self.failIf(rev.karma_allocated)
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
85
        # Even if the revision author is someone we know.
86
        author = self.factory.makePerson()
87
        rev = self.factory.makeRevision(
88
            author=author.preferredemail.email)
6623.4.20 by Tim Penhey
Testing the guts of the cron script and creating a dedicated db user.
89
        self.failIf(rev.karma_allocated)
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
90
91
    def test_noKarmaForUnknownAuthor(self):
6623.4.18 by Tim Penhey
More review updates.
92
        # If the revision author is unknown, karma isn't allocated.
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
93
        rev = self.factory.makeRevision()
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
94
        branch = self.factory.makeProductBranch()
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
95
        branch.createBranchRevision(1, rev)
6623.4.20 by Tim Penhey
Testing the guts of the cron script and creating a dedicated db user.
96
        self.failIf(rev.karma_allocated)
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
97
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
98
    def test_noRevisionsNeedingAllocation(self):
99
        # There are no outstanding revisions needing karma allocated.
100
        self.assertEqual(
101
            [], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
102
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
103
    def test_karmaAllocatedForKnownAuthor(self):
104
        # If the revision author is known, allocate karma.
105
        author = self.factory.makePerson()
106
        rev = self.factory.makeRevision(
6950.1.1 by Tim Penhey
Make sure that revision karma doesn't create a future karma event.
107
            author=author.preferredemail.email,
108
            revision_date=datetime.now(pytz.UTC) - timedelta(days=5))
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
109
        branch = self.factory.makeProductBranch()
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
110
        branch.createBranchRevision(1, rev)
6623.4.20 by Tim Penhey
Testing the guts of the cron script and creating a dedicated db user.
111
        self.failUnless(rev.karma_allocated)
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
112
        # Get the karma event.
113
        [karma] = list(Store.of(author).find(
114
            Karma,
115
            Karma.person == author,
116
            Karma.product == branch.product))
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
117
        self.assertEqual(karma.datecreated, rev.revision_date)
118
        self.assertEqual(karma.product, branch.product)
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
119
        # Since karma has been allocated, the revision isn't in our list.
120
        self.assertEqual(
121
            [], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
122
6623.4.23 by Tim Penhey
Fix a bug with attempting to allocate karma to an inactive person, and fix logging in the cron script.
123
    def test_karmaNotAllocatedForKnownAuthorWithInactiveAccount(self):
124
        # If the revision author is known, but the account is not active,
125
        # don't allocate karma.
126
        author = self.factory.makePerson()
127
        rev = self.factory.makeRevision(
128
            author=author.preferredemail.email)
7675.85.2 by Jonathan Lange
Undo revision generated by step 2 of process.
129
        IMasterObject(author.account).status = AccountStatus.SUSPENDED
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
130
        branch = self.factory.makeProductBranch()
6623.4.23 by Tim Penhey
Fix a bug with attempting to allocate karma to an inactive person, and fix logging in the cron script.
131
        branch.createBranchRevision(1, rev)
132
        self.failIf(rev.karma_allocated)
133
        # Even though the revision author is connected to the person, since
134
        # the account status is suspended, the person is not "valid", and so
135
        # the revisions are not returned as needing karma allocated.
136
        self.assertEqual(
137
            [], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
138
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
139
    def test_noKarmaForJunk(self):
140
        # Revisions only associated with junk branches don't get karma.
141
        author = self.factory.makePerson()
142
        rev = self.factory.makeRevision(
143
            author=author.preferredemail.email)
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
144
        branch = self.factory.makePersonalBranch()
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
145
        branch.createBranchRevision(1, rev)
6623.4.20 by Tim Penhey
Testing the guts of the cron script and creating a dedicated db user.
146
        self.failIf(rev.karma_allocated)
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
147
        # Nor is this revision identified as needing karma allocated.
148
        self.assertEqual(
149
            [], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
150
151
    def test_junkBranchMovedToProductNeedsKarma(self):
152
        # A junk branch that moves to a product needs karma allocated.
153
        author = self.factory.makePerson()
7675.439.10 by Tim Penhey
Fix the test.
154
        rev = self.factory.makeRevision(author=author)
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
155
        branch = self.factory.makePersonalBranch()
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
156
        branch.createBranchRevision(1, rev)
157
        # Once the branch is connected to the revision, we now specify
158
        # a product for the branch.
8971.24.19 by Tim Penhey
More tests that move branches updated.
159
        project = self.factory.makeProduct()
160
        branch.setTarget(user=branch.owner, project=project)
6623.4.18 by Tim Penhey
More review updates.
161
        # The revision is now identified as needing karma allocated.
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
162
        self.assertEqual(
163
            [rev], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
164
7675.439.10 by Tim Penhey
Fix the test.
165
    def test_junkBranchMovedToPackageNeedsKarma(self):
166
        # A junk branch that moves to a package needs karma allocated.
7675.439.8 by Tim Penhey
Test confirming that package revisions aren't yet being retrieved for karma allocation.
167
        author = self.factory.makePerson()
168
        rev = self.factory.makeRevision(author=author)
7675.439.10 by Tim Penhey
Fix the test.
169
        branch = self.factory.makePersonalBranch()
7675.439.8 by Tim Penhey
Test confirming that package revisions aren't yet being retrieved for karma allocation.
170
        branch.createBranchRevision(1, rev)
7675.439.10 by Tim Penhey
Fix the test.
171
        # Once the branch is connected to the revision, we now specify
172
        # a product for the branch.
173
        source_package = self.factory.makeSourcePackage()
174
        branch.setTarget(user=branch.owner, source_package=source_package)
175
        # The revision is now identified as needing karma allocated.
7675.439.8 by Tim Penhey
Test confirming that package revisions aren't yet being retrieved for karma allocation.
176
        self.assertEqual(
177
            [rev], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
178
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
179
    def test_newRevisionAuthorLinkNeedsKarma(self):
180
        # If Launchpad knows of revisions by a particular author, and later
181
        # that authoer registers with launchpad, the revisions need karma
182
        # allocated.
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
183
        email = self.factory.getUniqueEmailAddress()
184
        rev = self.factory.makeRevision(author=email)
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
185
        branch = self.factory.makeProductBranch()
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
186
        branch.createBranchRevision(1, rev)
6623.4.20 by Tim Penhey
Testing the guts of the cron script and creating a dedicated db user.
187
        self.failIf(rev.karma_allocated)
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
188
        # Since the revision author is not known, the revisions do not at this
189
        # stage need karma allocated.
190
        self.assertEqual(
191
            [], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
192
        # The person registers with Launchpad.
12070.1.17 by Tim Penhey
Replace MockLogger with DevNullLogger.
193
        self.factory.makePerson(email=email)
8303.10.5 by James Henstridge
Update tests that expect RevisionAuthors or HWSubmissions to be linked
194
        # Garbo runs the RevisionAuthorEmailLinker job.
12070.1.17 by Tim Penhey
Replace MockLogger with DevNullLogger.
195
        RevisionAuthorEmailLinker(log=DevNullLogger()).run()
6623.4.16 by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript.
196
        # Now the kama needs allocating.
197
        self.assertEqual(
198
            [rev], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
199
6950.1.1 by Tim Penhey
Make sure that revision karma doesn't create a future karma event.
200
    def test_karmaDateForFutureRevisions(self):
201
        # If the revision date is some time in the future, then the karma date
202
        # is set to be the time that the revision was created.
203
        author = self.factory.makePerson()
204
        rev = self.factory.makeRevision(
7675.439.5 by Tim Penhey
Make the test_karmaDateForFutureRevisions actually test what it says it is testing.
205
            author=author,
6950.1.1 by Tim Penhey
Make sure that revision karma doesn't create a future karma event.
206
            revision_date=datetime.now(pytz.UTC) + timedelta(days=5))
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
207
        branch = self.factory.makeProductBranch()
7675.439.5 by Tim Penhey
Make the test_karmaDateForFutureRevisions actually test what it says it is testing.
208
        karma = rev.allocateKarma(branch)
6950.1.1 by Tim Penhey
Make sure that revision karma doesn't create a future karma event.
209
        self.assertEqual(karma.datecreated, rev.date_created)
210
7675.439.6 by Tim Penhey
Tests to show karma allocation for products and packages.
211
    def test_allocateKarma_personal_branch(self):
212
        # A personal branch gets no karma event.
213
        author = self.factory.makePerson()
7675.439.7 by Tim Penhey
simplify the revision creation
214
        rev = self.factory.makeRevision(author=author)
7675.439.6 by Tim Penhey
Tests to show karma allocation for products and packages.
215
        branch = self.factory.makePersonalBranch()
216
        karma = rev.allocateKarma(branch)
217
        self.assertIs(None, karma)
218
219
    def test_allocateKarma_package_branch(self):
220
        # A revision on a package branch gets karma.
221
        author = self.factory.makePerson()
7675.439.7 by Tim Penhey
simplify the revision creation
222
        rev = self.factory.makeRevision(author=author)
7675.439.6 by Tim Penhey
Tests to show karma allocation for products and packages.
223
        branch = self.factory.makePackageBranch()
224
        karma = rev.allocateKarma(branch)
225
        self.assertEqual(author, karma.person)
226
        self.assertEqual(branch.distribution, karma.distribution)
227
        self.assertEqual(branch.sourcepackagename, karma.sourcepackagename)
228
229
    def test_allocateKarma_product_branch(self):
230
        # A revision on a product branch gets karma.
231
        author = self.factory.makePerson()
7675.439.7 by Tim Penhey
simplify the revision creation
232
        rev = self.factory.makeRevision(author=author)
7675.439.6 by Tim Penhey
Tests to show karma allocation for products and packages.
233
        branch = self.factory.makeProductBranch()
234
        karma = rev.allocateKarma(branch)
235
        self.assertEqual(author, karma.person)
236
        self.assertEqual(branch.product, karma.product)
237
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
238
11116.5.6 by Jonathan Lange
More unit test coverage to replace branch.txt
239
class TestRevisionSet(TestCaseWithFactory):
240
    """Tests for IRevisionSet."""
241
242
    layer = DatabaseFunctionalLayer
243
244
    def setUp(self):
245
        super(TestRevisionSet, self).setUp()
246
        self.revision_set = getUtility(IRevisionSet)
247
248
    def test_getRevisionById_existing(self):
249
        # IRevisionSet.getByRevisionId returns the revision with that id.
250
        revision = self.factory.makeRevision()
251
        found = self.revision_set.getByRevisionId(revision.revision_id)
252
        self.assertEquals(revision, found)
253
254
    def test_getRevisionById_nonexistent(self):
255
        # IRevisionSet.getByRevisionId returns None if there is no revision
256
        # with that id.
257
        found = self.revision_set.getByRevisionId('nonexistent')
258
        self.assertIs(None, found)
259
260
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
261
class TestRevisionGetBranch(TestCaseWithFactory):
262
    """Test the `getBranch` method of the revision."""
263
6736.3.22 by Tim Penhey
Changed the test layers.
264
    layer = DatabaseFunctionalLayer
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
265
266
    def setUp(self):
267
        # Use an administrator to set branch privacy easily.
6736.3.16 by Tim Penhey
Add the revision feed for products and projects.
268
        TestCaseWithFactory.setUp(self, "admin@canonical.com")
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
269
        self.author = self.factory.makePerson()
270
        self.revision = self.factory.makeRevision(
271
            author=self.author.preferredemail.email)
272
6623.4.25 by Tim Penhey
Make sure we don't try to allocate karma to junk branches.
273
    def makeBranchWithRevision(self, sequence, **kwargs):
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
274
        branch = self.factory.makeAnyBranch(**kwargs)
6736.3.12 by Tim Penhey
Updates following review.
275
        branch.createBranchRevision(sequence, self.revision)
276
        return branch
277
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
278
    def testPreferAuthorBranch(self):
279
        # If a revision is on the mainline history of two (or more) different
280
        # branches, then choose one owned by the revision author.
6736.3.12 by Tim Penhey
Updates following review.
281
        self.makeBranchWithRevision(1)
6736.3.13 by Tim Penhey
Another tweak to the tests.
282
        b = self.makeBranchWithRevision(1, owner=self.author)
6736.3.12 by Tim Penhey
Updates following review.
283
        self.assertEqual(b, self.revision.getBranch())
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
284
285
    def testPreferMainlineRevisionBranch(self):
286
        # Choose a branch where the revision is on the mainline history over a
287
        # branch where the revision is just in the ancestry.
6736.3.12 by Tim Penhey
Updates following review.
288
        self.makeBranchWithRevision(None)
289
        b = self.makeBranchWithRevision(1)
290
        self.assertEqual(b, self.revision.getBranch())
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
291
292
    def testOwnerTrunksMainline(self):
293
        # If the revision is mainline on a branch not owned by the revision
294
        # owner, but in the ancestry of a branch owned by the revision owner,
295
        # choose the branch owned by the revision author.
6736.3.12 by Tim Penhey
Updates following review.
296
        self.makeBranchWithRevision(1)
6736.3.13 by Tim Penhey
Another tweak to the tests.
297
        b = self.makeBranchWithRevision(None, owner=self.author)
6736.3.12 by Tim Penhey
Updates following review.
298
        self.assertEqual(b, self.revision.getBranch())
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
299
300
    def testPublicBranchTrumpsOwner(self):
301
        # Only public branches are returned.
6736.3.12 by Tim Penhey
Updates following review.
302
        b1 = self.makeBranchWithRevision(1)
6736.3.13 by Tim Penhey
Another tweak to the tests.
303
        b2 = self.makeBranchWithRevision(1, owner=self.author)
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
304
        removeSecurityProxy(b2).explicitly_private = True
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
305
        self.assertEqual(b1, self.revision.getBranch())
6623.4.18 by Tim Penhey
More review updates.
306
307
    def testAllowPrivateReturnsPrivateBranch(self):
308
        # If the allow_private flag is set, then private branches can be
309
        # returned if they are the best match.
14083.1.1 by Jeroen Vermeulen
Lint.
310
        self.makeBranchWithRevision(1)
6623.4.18 by Tim Penhey
More review updates.
311
        b2 = self.makeBranchWithRevision(1, owner=self.author)
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
312
        removeSecurityProxy(b2).explicitly_private = True
6623.4.18 by Tim Penhey
More review updates.
313
        self.assertEqual(b2, self.revision.getBranch(allow_private=True))
314
315
    def testAllowPrivateCanReturnPublic(self):
316
        # Allowing private branches does not change the priority ordering of
317
        # the branches.
318
        b1 = self.makeBranchWithRevision(1)
319
        b2 = self.makeBranchWithRevision(1, owner=self.author)
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
320
        removeSecurityProxy(b1).explicitly_private = True
6623.4.6 by Tim Penhey
Hook up the claiming of older revisions.
321
        self.assertEqual(b2, self.revision.getBranch(allow_private=True))
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
322
6623.4.25 by Tim Penhey
Make sure we don't try to allocate karma to junk branches.
323
    def testGetBranchNotJunk(self):
324
        # If allow_junk is set to False, then branches without products are
6623.4.27 by Tim Penhey
Slight fixes following review.
325
        # not returned.
7362.12.35 by Jonathan Lange
Watch as I fix more code to use correct semantics.
326
        b1 = self.factory.makeProductBranch()
327
        b1.createBranchRevision(1, self.revision)
328
        b2 = self.factory.makePersonalBranch(owner=self.author)
329
        b2.createBranchRevision(1, self.revision)
6623.4.25 by Tim Penhey
Make sure we don't try to allocate karma to junk branches.
330
        self.assertEqual(
331
            b1, self.revision.getBranch(allow_private=True, allow_junk=False))
332
9894.5.1 by Muharem Hrnjadovic
Bug fixed.
333
    def testGetBranchSourcePackage(self):
334
        # Branches targetting source packages are not junk.
335
        b1 = self.factory.makePackageBranch()
336
        b1.createBranchRevision(1, self.revision)
337
        b2 = self.factory.makePersonalBranch(owner=self.author)
338
        b2.createBranchRevision(1, self.revision)
339
        self.assertEqual(
340
            b1, self.revision.getBranch(allow_private=True, allow_junk=False))
341
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
342
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
343
class GetPublicRevisionsTestCase(TestCaseWithFactory):
344
    """A base class for the tests for people, products and projects."""
345
346
    layer = DatabaseFunctionalLayer
347
348
    def setUp(self):
349
        # Use an administrator to set branch privacy easily.
350
        TestCaseWithFactory.setUp(self, "admin@canonical.com")
351
        # Since the tests order by date, but also limit to the last 30
352
        # days, we want a time counter that starts 10 days ago.
353
        self.date_generator = time_counter(
354
            datetime.now(pytz.UTC) - timedelta(days=10),
355
            delta=timedelta(days=1))
356
357
    def _makeRevision(self, revision_date=None):
358
        """Make a revision using the date generator."""
359
        if revision_date is None:
360
            revision_date = self.date_generator.next()
361
        return self.factory.makeRevision(
362
            revision_date=revision_date)
363
364
    def _addRevisionsToBranch(self, branch, *revs):
8037.2.1 by Brad Crittenden
fixed annoying typos
365
        # Add the revisions to the branch.
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
366
        for sequence, rev in enumerate(revs):
367
            branch.createBranchRevision(sequence, rev)
368
369
    def _makeBranch(self, product=None):
370
        # Make a branch.
371
        if product is None:
372
            # If the test defines a product, use that, otherwise
373
            # have the factory generate one.
374
            product = getattr(self, 'product', None)
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
375
        return self.factory.makeProductBranch(product=product)
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
376
377
    def _makeRevisionInBranch(self, product=None):
378
        # Make a revision, and associate it with a branch.  The branch is made
379
        # with the product passed in, which means that if there was no product
380
        # passed in, the factory makes a new one.
7362.12.35 by Jonathan Lange
Watch as I fix more code to use correct semantics.
381
        branch = self.factory.makeProductBranch(product=product)
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
382
        rev = self._makeRevision()
383
        branch.createBranchRevision(1, rev)
384
        return rev
385
386
    def _getRevisions(self, day_limit=30):
387
        raise NotImplementedError('_getRevisions')
388
6950.2.4 by Tim Penhey
Fix test failures
389
390
class RevisionTestMixin:
391
    """Common tests for the different GetPublicRevision test cases."""
392
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
393
    def testNewestRevisionFirst(self):
394
        # The revisions are ordered with the newest first.
395
        rev1 = self._makeRevision()
396
        rev2 = self._makeRevision()
397
        rev3 = self._makeRevision()
398
        self._addRevisionsToBranch(self._makeBranch(), rev1, rev2, rev3)
399
        self.assertEqual([rev3, rev2, rev1], self._getRevisions())
400
401
    def testRevisionsOnlyReturnedOnce(self):
402
        # If the revisions appear in multiple branches, they are only returned
403
        # once.
404
        rev1 = self._makeRevision()
405
        rev2 = self._makeRevision()
406
        rev3 = self._makeRevision()
407
        self._addRevisionsToBranch(
408
            self._makeBranch(), rev1, rev2, rev3)
409
        self._addRevisionsToBranch(
410
            self._makeBranch(), rev1, rev2, rev3)
411
        self.assertEqual([rev3, rev2, rev1], self._getRevisions())
412
413
    def testRevisionsMustBeInABranch(self):
414
        # A revision authored by the person must be in a branch to be
415
        # returned.
416
        rev1 = self._makeRevision()
417
        self.assertEqual([], self._getRevisions())
418
        b = self._makeBranch()
419
        b.createBranchRevision(1, rev1)
420
        self.assertEqual([rev1], self._getRevisions())
421
422
    def testRevisionsMustBeInAPublicBranch(self):
423
        # A revision authored by the person must be in a branch to be
424
        # returned.
425
        rev1 = self._makeRevision()
426
        b = self._makeBranch()
427
        b.createBranchRevision(1, rev1)
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
428
        removeSecurityProxy(b).explicitly_private = True
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
429
        self.assertEqual([], self._getRevisions())
430
431
    def testRevisionDateRange(self):
7925.4.2 by Tim Penhey
Fix tests that relied on creating future revision dates.
432
        # Revisions where the revision_date is older than the day_limit are
433
        # not returned.
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
434
        now = datetime.now(pytz.UTC)
435
        day_limit = 5
436
        # Make the first revision earlier than our day limit.
437
        rev1 = self._makeRevision(
438
            revision_date=(now - timedelta(days=(day_limit + 2))))
439
        # The second one is just two days ago.
440
        rev2 = self._makeRevision(
441
            revision_date=(now - timedelta(days=2)))
7925.4.2 by Tim Penhey
Fix tests that relied on creating future revision dates.
442
        self._addRevisionsToBranch(self._makeBranch(), rev1, rev2)
12337.2.5 by Jeroen Vermeulen
Lint.
443
        self.assertEqual([rev2], self._getRevisions(day_limit))
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
444
445
6950.2.4 by Tim Penhey
Fix test failures
446
class TestGetPublicRevisionsForPerson(GetPublicRevisionsTestCase,
447
                                      RevisionTestMixin):
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
448
    """Test the `getPublicRevisionsForPerson` method of `RevisionSet`."""
449
450
    def setUp(self):
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
451
        GetPublicRevisionsTestCase.setUp(self)
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
452
        self.author = self.factory.makePerson()
453
        self.revision = self.factory.makeRevision(
454
            author=self.author.preferredemail.email)
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
455
456
    def _getRevisions(self, day_limit=30):
457
        # Returns the revisions for the person.
458
        return list(RevisionSet.getPublicRevisionsForPerson(
459
                self.author, day_limit))
460
461
    def _makeRevision(self, author=None, revision_date=None):
6736.3.12 by Tim Penhey
Updates following review.
462
        """Make a revision owned by `author`.
463
464
        `author` defaults to self.author if not set."""
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
465
        if revision_date is None:
466
            revision_date = self.date_generator.next()
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
467
        if author is None:
468
            author = self.author
469
        return self.factory.makeRevision(
470
            author=author.preferredemail.email,
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
471
            revision_date=revision_date)
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
472
473
    def testTeamRevisions(self):
6736.3.16 by Tim Penhey
Add the revision feed for products and projects.
474
        # Revisions owned by all members of a team are returned.
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
475
        team = self.factory.makeTeam(self.author)
476
        team_member = self.factory.makePerson()
477
        team.addMember(team_member, self.author)
478
        rev1 = self._makeRevision()
479
        rev2 = self._makeRevision(team_member)
480
        rev3 = self._makeRevision(self.factory.makePerson())
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
481
        branch = self.factory.makeAnyBranch()
6736.3.4 by Tim Penhey
Added tests for addition Revision and RevisionSet methods.
482
        self._addRevisionsToBranch(branch, rev1, rev2, rev3)
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
483
        self.assertEqual([rev2, rev1],
484
                         list(RevisionSet.getPublicRevisionsForPerson(team)))
485
486
6950.2.4 by Tim Penhey
Fix test failures
487
class TestGetPublicRevisionsForProduct(GetPublicRevisionsTestCase,
488
                                       RevisionTestMixin):
6736.3.20 by Tim Penhey
Separate out the product and project method calls.
489
    """Test the `getPublicRevisionsForProduct` method of `RevisionSet`."""
490
491
    def setUp(self):
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
492
        GetPublicRevisionsTestCase.setUp(self)
6736.3.20 by Tim Penhey
Separate out the product and project method calls.
493
        self.product = self.factory.makeProduct()
494
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
495
    def _getRevisions(self, day_limit=30):
496
        # Returns the revisions for the person.
497
        return list(RevisionSet.getPublicRevisionsForProduct(
498
                self.product, day_limit))
6736.3.20 by Tim Penhey
Separate out the product and project method calls.
499
500
    def testRevisionsMustBeInABranchOfProduct(self):
501
        # The revision must be in a branch for the product.
502
        # returned.
503
        rev1 = self._makeRevisionInBranch(product=self.product)
14083.1.1 by Jeroen Vermeulen
Lint.
504
        self._makeRevisionInBranch()
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
505
        self.assertEqual([rev1], self._getRevisions())
506
507
10724.1.1 by Henning Eggers
First batch of Project -> ProjectGrpoup renamings.
508
class TestGetPublicRevisionsForProjectGroup(GetPublicRevisionsTestCase,
509
                                            RevisionTestMixin):
510
    """Test the `getPublicRevisionsForProjectGroup` method of `RevisionSet`.
511
    """
6736.3.16 by Tim Penhey
Add the revision feed for products and projects.
512
513
    def setUp(self):
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
514
        GetPublicRevisionsTestCase.setUp(self)
6736.3.16 by Tim Penhey
Add the revision feed for products and projects.
515
        self.project = self.factory.makeProject()
516
        self.product = self.factory.makeProduct(project=self.project)
517
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
518
    def _getRevisions(self, day_limit=30):
519
        # Returns the revisions for the person.
10724.1.1 by Henning Eggers
First batch of Project -> ProjectGrpoup renamings.
520
        return list(RevisionSet.getPublicRevisionsForProjectGroup(
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
521
                self.project, day_limit))
6736.3.16 by Tim Penhey
Add the revision feed for products and projects.
522
523
    def testRevisionsMustBeInABranchOfProduct(self):
524
        # The revision must be in a branch for the product.
525
        # returned.
526
        rev1 = self._makeRevisionInBranch(product=self.product)
14083.1.1 by Jeroen Vermeulen
Lint.
527
        self._makeRevisionInBranch()
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
528
        self.assertEqual([rev1], self._getRevisions())
6736.3.16 by Tim Penhey
Add the revision feed for products and projects.
529
530
    def testProjectRevisions(self):
531
        # Revisions in all products that are part of the project are returned.
532
        another_product = self.factory.makeProduct(project=self.project)
533
        rev1 = self._makeRevisionInBranch(product=self.product)
534
        rev2 = self._makeRevisionInBranch(product=another_product)
14083.1.1 by Jeroen Vermeulen
Lint.
535
        self._makeRevisionInBranch()
6950.2.1 by Tim Penhey
Update the revision getting methods to be date bound to speed up queries.
536
        self.assertEqual([rev2, rev1], self._getRevisions())
6736.3.16 by Tim Penhey
Add the revision feed for products and projects.
537
538
7028.1.1 by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products.
539
class TestGetRecentRevisionsForProduct(GetPublicRevisionsTestCase):
540
    """Test the `getRecentRevisionsForProduct` method of `RevisionSet`."""
541
542
    def setUp(self):
543
        GetPublicRevisionsTestCase.setUp(self)
544
        self.product = self.factory.makeProduct()
545
546
    def _getRecentRevisions(self, day_limit=30):
547
        # Return a list of the recent revisions and revision authors.
548
        return list(RevisionSet.getRecentRevisionsForProduct(
549
                self.product, day_limit))
550
551
    def testRevisionAuthorMatchesRevision(self):
552
        # The revision author returned with the revision is the same as the
553
        # author for the revision.
14083.1.1 by Jeroen Vermeulen
Lint.
554
        self._makeRevisionInBranch(product=self.product)
7028.1.1 by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products.
555
        results = self._getRecentRevisions()
14083.1.1 by Jeroen Vermeulen
Lint.
556
        [(revision, revision_author)] = results
7028.1.1 by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products.
557
        self.assertEqual(revision.revision_author, revision_author)
558
559
    def testRevisionsMustBeInABranchOfProduct(self):
560
        # The revisions returned revision must be in a branch for the product.
561
        rev1 = self._makeRevisionInBranch(product=self.product)
14083.1.1 by Jeroen Vermeulen
Lint.
562
        self._makeRevisionInBranch()
563
        self.assertEqual(
564
            [(rev1, rev1.revision_author)], self._getRecentRevisions())
7028.1.1 by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products.
565
8079.2.1 by Tim Penhey
Write the failing test.
566
    def testRevisionsMustBeInActiveBranches(self):
567
        # The revisions returned revision must be in a branch for the product.
568
        rev1 = self._makeRevisionInBranch(product=self.product)
569
        branch = self.factory.makeProductBranch(
570
            product=self.product,
571
            lifecycle_status=BranchLifecycleStatus.MERGED)
572
        branch.createBranchRevision(1, self._makeRevision())
573
        self.assertEqual([(rev1, rev1.revision_author)],
574
                         self._getRecentRevisions())
575
7028.1.1 by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products.
576
    def testRevisionDateRange(self):
7925.4.2 by Tim Penhey
Fix tests that relied on creating future revision dates.
577
        # Revisions where the revision_date is older than the day_limit are
578
        # not returned.
7028.1.1 by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products.
579
        now = datetime.now(pytz.UTC)
580
        day_limit = 5
581
        # Make the first revision earlier than our day limit.
582
        rev1 = self._makeRevision(
583
            revision_date=(now - timedelta(days=(day_limit + 2))))
584
        # The second one is just two days ago.
585
        rev2 = self._makeRevision(revision_date=(now - timedelta(days=2)))
7925.4.2 by Tim Penhey
Fix tests that relied on creating future revision dates.
586
        self._addRevisionsToBranch(self._makeBranch(), rev1, rev2)
7028.1.1 by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products.
587
        self.assertEqual([(rev2, rev2.revision_author)],
588
                         self._getRecentRevisions(day_limit))
589
590
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
591
class TestTipRevisionsForBranches(TestCase):
592
    """Test that the tip revisions get returned properly."""
593
6736.3.22 by Tim Penhey
Changed the test layers.
594
    layer = DatabaseFunctionalLayer
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
595
596
    def setUp(self):
597
        login('test@canonical.com')
598
599
        factory = LaunchpadObjectFactory()
7362.12.24 by Jonathan Lange
Restore change that somehow got lost.
600
        branches = [factory.makeAnyBranch() for count in range(5)]
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
601
        branch_ids = [branch.id for branch in branches]
602
        for branch in branches:
603
            factory.makeRevisionsForBranch(branch)
604
        # Retrieve the updated branches (due to transaction boundaries).
7940.2.5 by Jonathan Lange
Move branch lookup methods to IBranchLookup
605
        branch_set = getUtility(IBranchLookup)
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
606
        self.branches = [branch_set.get(id) for id in branch_ids]
5743.2.2 by Tim Penhey
Listing now has revision number and codebrowse hyperlink.
607
        self.revision_set = getUtility(IRevisionSet)
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
608
609
    def tearDown(self):
610
        logout()
611
612
    def _breakTransaction(self):
613
        # make sure the current transaction can not be committed by
614
        # sending a broken SQL statement to the database
615
        try:
616
            cursor().execute('break this transaction')
5821.2.32 by James Henstridge
* s/psycopg/psycopg2/ in a few more places.
617
        except psycopg2.DatabaseError:
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
618
            pass
619
620
    def testNoBranches(self):
621
        """Assert that when given an empty list, an empty list is returned."""
5743.2.2 by Tim Penhey
Listing now has revision number and codebrowse hyperlink.
622
        bs = self.revision_set
623
        revisions = bs.getTipRevisionsForBranches([])
5743.2.6 by Tim Penhey
Updates following review
624
        self.assertTrue(revisions is None)
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
625
626
    def testOneBranches(self):
627
        """When given one branch, one branch revision is returned."""
5743.2.2 by Tim Penhey
Listing now has revision number and codebrowse hyperlink.
628
        revisions = list(
629
            self.revision_set.getTipRevisionsForBranches(
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
630
                self.branches[:1]))
5821.6.10 by James Henstridge
Fix up some of the branch/revision tests to account for some
631
        # XXX jamesh 2008-06-02: ensure that branch[0] is loaded
7940.2.5 by Jonathan Lange
Move branch lookup methods to IBranchLookup
632
        last_scanned_id = self.branches[0].last_scanned_id
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
633
        self._breakTransaction()
5743.2.2 by Tim Penhey
Listing now has revision number and codebrowse hyperlink.
634
        self.assertEqual(1, len(revisions))
635
        revision = revisions[0]
14083.1.1 by Jeroen Vermeulen
Lint.
636
        self.assertEqual(last_scanned_id, revision.revision_id)
5743.2.2 by Tim Penhey
Listing now has revision number and codebrowse hyperlink.
637
        # By accessing to the revision_author we can confirm that the
638
        # revision author has in fact been retrieved already.
639
        revision_author = revision.revision_author
640
        self.assertTrue(revision_author is not None)
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
641
642
    def testManyBranches(self):
643
        """Assert multiple branch revisions are returned correctly."""
5743.2.2 by Tim Penhey
Listing now has revision number and codebrowse hyperlink.
644
        revisions = list(
645
            self.revision_set.getTipRevisionsForBranches(
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
646
                self.branches))
647
        self._breakTransaction()
5743.2.2 by Tim Penhey
Listing now has revision number and codebrowse hyperlink.
648
        self.assertEqual(5, len(revisions))
649
        for revision in revisions:
650
            # By accessing to the revision_author we can confirm that the
651
            # revision author has in fact been retrieved already.
652
            revision_author = revision.revision_author
653
            self.assertTrue(revision_author is not None)
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
654
6789.3.9 by Jonathan Lange
Move the revision from bzr revision method into RevisionSet.
655
    def test_timestampToDatetime_with_negative_fractional(self):
656
        # timestampToDatetime should convert a negative, fractional timestamp
657
        # into a valid, sane datetime object.
658
        revision_set = removeSecurityProxy(getUtility(IRevisionSet))
659
        UTC = pytz.timezone('UTC')
660
        timestamp = -0.5
661
        date = revision_set._timestampToDatetime(timestamp)
662
        self.assertEqual(
6789.3.12 by Jonathan Lange
Merge trunk, resolving conflicts.
663
            date, datetime(1969, 12, 31, 23, 59, 59, 500000, UTC))
6789.3.9 by Jonathan Lange
Move the revision from bzr revision method into RevisionSet.
664
665
    def test_timestampToDatetime(self):
666
        # timestampTODatetime should convert a regular timestamp into a valid,
667
        # sane datetime object.
668
        revision_set = removeSecurityProxy(getUtility(IRevisionSet))
669
        UTC = pytz.timezone('UTC')
670
        timestamp = time.time()
6789.3.12 by Jonathan Lange
Merge trunk, resolving conflicts.
671
        date = datetime.fromtimestamp(timestamp, tz=UTC)
6789.3.9 by Jonathan Lange
Move the revision from bzr revision method into RevisionSet.
672
        self.assertEqual(date, revision_set._timestampToDatetime(timestamp))
673
5743.2.1 by Tim Penhey
Add method to get branch revisions for multiple branches at once.
674
7049.1.13 by Michael Hudson
tests for onlyPresent
675
class TestOnlyPresent(TestCaseWithFactory):
7049.1.16 by Michael Hudson
docstrings, comments
676
    """Tests for `RevisionSet.onlyPresent`.
677
678
    Note that although onlyPresent returns a set, it is a security proxied
679
    set, so we have to convert it to a real set before doing any comparisons.
680
    """
7049.1.13 by Michael Hudson
tests for onlyPresent
681
682
    layer = DatabaseFunctionalLayer
683
684
    def test_empty(self):
7049.1.16 by Michael Hudson
docstrings, comments
685
        # onlyPresent returns no results when passed no revids.
686
        self.assertEqual(
687
            set(),
688
            set(getUtility(IRevisionSet).onlyPresent([])))
7049.1.13 by Michael Hudson
tests for onlyPresent
689
690
    def test_none_present(self):
7049.1.16 by Michael Hudson
docstrings, comments
691
        # onlyPresent returns no results when passed a revid not present in
692
        # the database.
7049.1.13 by Michael Hudson
tests for onlyPresent
693
        not_present = self.factory.getUniqueString()
694
        self.assertEqual(
695
            set(),
696
            set(getUtility(IRevisionSet).onlyPresent([not_present])))
697
698
    def test_one_present(self):
7049.1.16 by Michael Hudson
docstrings, comments
699
        # onlyPresent returns a revid that is present in the database.
7049.1.13 by Michael Hudson
tests for onlyPresent
700
        present = self.factory.makeRevision().revision_id
701
        self.assertEqual(
702
            set([present]),
703
            set(getUtility(IRevisionSet).onlyPresent([present])))
704
705
    def test_some_present(self):
7049.1.16 by Michael Hudson
docstrings, comments
706
        # onlyPresent returns only the revid that is present in the database.
7049.1.13 by Michael Hudson
tests for onlyPresent
707
        not_present = self.factory.getUniqueString()
708
        present = self.factory.makeRevision().revision_id
709
        self.assertEqual(
710
            set([present]),
711
            set(getUtility(IRevisionSet).onlyPresent([present, not_present])))
712
7049.1.16 by Michael Hudson
docstrings, comments
713
    def test_call_twice_in_one_transaction(self):
714
        # onlyPresent creates temporary tables, but cleans after itself so
715
        # that it can safely be called twice in one transaction.
716
        not_present = self.factory.getUniqueString()
717
        getUtility(IRevisionSet).onlyPresent([not_present])
718
        # This is just "assertNotRaises"
719
        getUtility(IRevisionSet).onlyPresent([not_present])
720
7049.1.13 by Michael Hudson
tests for onlyPresent
721
7675.169.3 by Tim Penhey
Add methods to populate and prune the RevisionCache.
722
class RevisionCacheTestCase(TestCaseWithFactory):
723
    """Base class for RevisionCache tests."""
724
725
    layer = DatabaseFunctionalLayer
726
727
    def setUp(self):
728
        # Login as an admin as we don't care about permissions here.
729
        TestCaseWithFactory.setUp(self, 'admin@canonical.com')
730
        self.store = getUtility(IStoreSelector).get(
731
            MAIN_STORE, DEFAULT_FLAVOR)
7675.169.7 by Tim Penhey
Updates following review.
732
        # There should be no RevisionCache entries in the test data.
733
        assert self.store.find(RevisionCache).count() == 0
7675.169.3 by Tim Penhey
Add methods to populate and prune the RevisionCache.
734
735
    def _getRevisionCache(self):
736
        return list(self.store.find(RevisionCache)
737
                    .order_by(RevisionCache.revision_id))
738
739
740
class TestUpdateRevisionCacheForBranch(RevisionCacheTestCase):
741
    """Tests for RevisionSet.updateRevisionCacheForBranch."""
742
743
    def test_empty_branch(self):
744
        # A branch with no revisions should add no revisions to the cache.
745
        branch = self.factory.makeAnyBranch()
746
        RevisionSet.updateRevisionCacheForBranch(branch)
747
        self.assertEqual(0, len(self._getRevisionCache()))
748
749
    def test_adds_revisions(self):
750
        # A branch with revisions should add the new revisions.
751
        branch = self.factory.makeAnyBranch()
752
        revision = self.factory.makeRevision()
753
        branch.createBranchRevision(1, revision)
754
        RevisionSet.updateRevisionCacheForBranch(branch)
755
        [cached] = self._getRevisionCache()
756
        self.assertEqual(cached.revision, revision)
757
        self.assertEqual(cached.revision_author, revision.revision_author)
758
        self.assertEqual(cached.revision_date, revision.revision_date)
759
760
    def test_old_revisions_not_added(self):
761
        # Revisions older than the 30 day epoch are not added to the cache.
762
763
        # Start 33 days ago.
764
        epoch = datetime.now(pytz.UTC) - timedelta(days=30)
765
        date_generator = time_counter(
766
            epoch - timedelta(days=3), delta=timedelta(days=2))
767
        # And add 4 revisions at 33, 31, 29, and 27 days ago
768
        branch = self.factory.makeAnyBranch()
769
        self.factory.makeRevisionsForBranch(
770
            branch, count=4, date_generator=date_generator)
771
        RevisionSet.updateRevisionCacheForBranch(branch)
772
        cached = self._getRevisionCache()
773
        # Only two revisions are within the 30 day cutoff.
774
        self.assertEqual(2, len(cached))
775
        # And both the revisions stored are a date create after the epoch.
776
        for rev in cached:
777
            self.assertTrue(rev.revision_date > epoch)
778
779
    def test_new_revisions_added(self):
780
        # If there are already revisions in the cache for the branch, updating
781
        # the branch again will only add the new revisions.
782
        date_generator = time_counter(
783
            datetime.now(pytz.UTC) - timedelta(days=29),
784
            delta=timedelta(days=1))
785
        # Initially add in 4 revisions.
786
        branch = self.factory.makeAnyBranch()
787
        self.factory.makeRevisionsForBranch(
788
            branch, count=4, date_generator=date_generator)
789
        RevisionSet.updateRevisionCacheForBranch(branch)
790
        # Now add two more revisions.
791
        self.factory.makeRevisionsForBranch(
792
            branch, count=2, date_generator=date_generator)
793
        RevisionSet.updateRevisionCacheForBranch(branch)
794
        # There will be only six revisions cached.
795
        cached = self._getRevisionCache()
796
        self.assertEqual(6, len(cached))
797
798
    def test_revisions_for_public_branch_marked_public(self):
799
        # If the branch is public, then the revisions in the cache will be
800
        # marked public too.
801
        branch = self.factory.makeAnyBranch()
802
        revision = self.factory.makeRevision()
803
        branch.createBranchRevision(1, revision)
804
        RevisionSet.updateRevisionCacheForBranch(branch)
805
        [cached] = self._getRevisionCache()
806
        self.assertFalse(branch.private)
807
        self.assertFalse(cached.private)
808
809
    def test_revisions_for_private_branch_marked_private(self):
810
        # If the branch is private, then the revisions in the cache will be
811
        # marked private too.
812
        branch = self.factory.makeAnyBranch(private=True)
813
        revision = self.factory.makeRevision()
814
        branch.createBranchRevision(1, revision)
815
        RevisionSet.updateRevisionCacheForBranch(branch)
816
        [cached] = self._getRevisionCache()
817
        self.assertTrue(branch.private)
818
        self.assertTrue(cached.private)
819
13875.2.1 by Ian Booth
Reimplement code to query for private branches to account for transitive privacy
820
    def test_revisions_for_transitive_private_branch_marked_private(self):
821
        # If the branch is stacked on a private branch, then the revisions in
822
        # the cache will be marked private too.
823
        private_branch = self.factory.makeAnyBranch(private=True)
824
        branch = self.factory.makeAnyBranch(stacked_on=private_branch)
825
        revision = self.factory.makeRevision()
826
        branch.createBranchRevision(1, revision)
827
        RevisionSet.updateRevisionCacheForBranch(branch)
828
        [cached] = self._getRevisionCache()
829
        self.assertTrue(branch.private)
830
        self.assertTrue(cached.private)
831
7675.169.3 by Tim Penhey
Add methods to populate and prune the RevisionCache.
832
    def test_product_branch_revisions(self):
833
        # The revision cache knows the product for revisions in product
834
        # branches.
835
        branch = self.factory.makeProductBranch()
836
        revision = self.factory.makeRevision()
837
        branch.createBranchRevision(1, revision)
838
        RevisionSet.updateRevisionCacheForBranch(branch)
839
        [cached] = self._getRevisionCache()
840
        self.assertEqual(cached.product, branch.product)
841
        self.assertIs(None, cached.distroseries)
842
        self.assertIs(None, cached.sourcepackagename)
843
844
    def test_personal_branch_revisions(self):
845
        # If the branch is a personal branch, the revision cache stores NULL
846
        # for the product, distroseries and sourcepackagename.
847
        branch = self.factory.makePersonalBranch()
848
        revision = self.factory.makeRevision()
849
        branch.createBranchRevision(1, revision)
850
        RevisionSet.updateRevisionCacheForBranch(branch)
851
        [cached] = self._getRevisionCache()
852
        self.assertIs(None, cached.product)
853
        self.assertIs(None, cached.distroseries)
854
        self.assertIs(None, cached.sourcepackagename)
855
856
    def test_package_branch_revisions(self):
857
        # The revision cache stores the distroseries and sourcepackagename for
858
        # package branches.
859
        branch = self.factory.makePackageBranch()
860
        revision = self.factory.makeRevision()
861
        branch.createBranchRevision(1, revision)
862
        RevisionSet.updateRevisionCacheForBranch(branch)
863
        [cached] = self._getRevisionCache()
864
        self.assertIs(None, cached.product)
865
        self.assertEqual(branch.distroseries, cached.distroseries)
866
        self.assertEqual(branch.sourcepackagename, cached.sourcepackagename)
867
868
    def test_same_revision_multiple_targets(self):
869
        # If there are branches that have different targets that contain the
870
        # same revision, the revision appears in the revision cache once for
871
        # each different target.
872
        b1 = self.factory.makeProductBranch()
873
        b2 = self.factory.makePackageBranch()
874
        revision = self.factory.makeRevision()
875
        b1.createBranchRevision(1, revision)
876
        RevisionSet.updateRevisionCacheForBranch(b1)
877
        b2.createBranchRevision(1, revision)
878
        RevisionSet.updateRevisionCacheForBranch(b2)
879
        [rev1, rev2] = self._getRevisionCache()
880
        # Both cached revisions point to the same underlying revisions, but
881
        # for different targets.
882
        self.assertEqual(rev1.revision, revision)
883
        self.assertEqual(rev2.revision, revision)
884
        # Make rev1 be the cached revision for the product branch.
885
        if rev1.product is None:
886
            rev1, rev2 = rev2, rev1
887
        self.assertEqual(b1.product, rev1.product)
888
        self.assertEqual(b2.distroseries, rev2.distroseries)
889
        self.assertEqual(b2.sourcepackagename, rev2.sourcepackagename)
890
891
    def test_existing_private_revisions_with_public_branch(self):
892
        # If a revision is in both public and private branches, there is a
893
        # revision cache row for both public and private.
894
        private_branch = self.factory.makeAnyBranch(private=True)
895
        public_branch = self.factory.makeAnyBranch(private=False)
896
        revision = self.factory.makeRevision()
897
        private_branch.createBranchRevision(1, revision)
898
        RevisionSet.updateRevisionCacheForBranch(private_branch)
899
        public_branch.createBranchRevision(1, revision)
900
        RevisionSet.updateRevisionCacheForBranch(public_branch)
901
        [rev1, rev2] = self._getRevisionCache()
902
        # Both revisions point to the same underlying revision.
903
        self.assertEqual(rev1.revision, revision)
904
        self.assertEqual(rev2.revision, revision)
905
        # But the privacy flags are different.
906
        self.assertNotEqual(rev1.private, rev2.private)
907
13875.2.3 by Ian Booth
Lint
908
    def test_existing_transitive_private_revisions_with_public_branch(self):
13875.2.1 by Ian Booth
Reimplement code to query for private branches to account for transitive privacy
909
        # If a revision is in both public and private branches, there is a
910
        # revision cache row for both public and private. A branch is private
911
        # if it is stacked on a private branch.
912
        stacked_on_branch = self.factory.makeAnyBranch(private=True)
913
        private_branch = self.factory.makeAnyBranch(
914
            stacked_on=stacked_on_branch)
915
        public_branch = self.factory.makeAnyBranch(private=False)
916
        revision = self.factory.makeRevision()
917
        private_branch.createBranchRevision(1, revision)
918
        RevisionSet.updateRevisionCacheForBranch(private_branch)
919
        public_branch.createBranchRevision(1, revision)
920
        RevisionSet.updateRevisionCacheForBranch(public_branch)
921
        [rev1, rev2] = self._getRevisionCache()
922
        # Both revisions point to the same underlying revision.
923
        self.assertEqual(rev1.revision, revision)
924
        self.assertEqual(rev2.revision, revision)
925
        # But the privacy flags are different.
926
        self.assertNotEqual(rev1.private, rev2.private)
927
7675.169.3 by Tim Penhey
Add methods to populate and prune the RevisionCache.
928
929
class TestPruneRevisionCache(RevisionCacheTestCase):
930
    """Tests for RevisionSet.pruneRevisionCache."""
931
932
    def test_old_revisions_removed(self):
933
        # Revisions older than 30 days are removed.
934
        date_generator = time_counter(
7675.169.6 by Tim Penhey
Fix the pruning.
935
            datetime.now(pytz.UTC) - timedelta(days=33),
7675.169.3 by Tim Penhey
Add methods to populate and prune the RevisionCache.
936
            delta=timedelta(days=2))
937
        for i in range(4):
938
            revision = self.factory.makeRevision(
939
                revision_date=date_generator.next())
7675.169.6 by Tim Penhey
Fix the pruning.
940
            cache = RevisionCache(revision)
7675.169.3 by Tim Penhey
Add methods to populate and prune the RevisionCache.
941
            self.store.add(cache)
7675.169.6 by Tim Penhey
Fix the pruning.
942
        RevisionSet.pruneRevisionCache(5)
7675.169.3 by Tim Penhey
Add methods to populate and prune the RevisionCache.
943
        self.assertEqual(2, len(self._getRevisionCache()))
944
7675.169.6 by Tim Penhey
Fix the pruning.
945
    def test_pruning_limit(self):
946
        # The prune will only remove at most the parameter rows.
947
        date_generator = time_counter(
948
            datetime.now(pytz.UTC) - timedelta(days=33),
949
            delta=timedelta(days=2))
950
        for i in range(4):
951
            revision = self.factory.makeRevision(
952
                revision_date=date_generator.next())
953
            cache = RevisionCache(revision)
954
            self.store.add(cache)
955
        RevisionSet.pruneRevisionCache(1)
956
        self.assertEqual(3, len(self._getRevisionCache()))