~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# Copyright 2009 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Tests for creating BugBranch items based on Bazaar revisions."""

__metaclass__ = type

import unittest

from bzrlib.revision import Revision
from zope.component import getUtility
from zope.event import notify
from zope.security.proxy import removeSecurityProxy

from canonical.config import config
from canonical.testing.layers import LaunchpadZopelessLayer
from lp.app.errors import NotFoundError
from lp.bugs.interfaces.bug import IBugSet
from lp.bugs.interfaces.bugbranch import IBugBranchSet
from lp.code.interfaces.revision import IRevisionSet
from lp.codehosting.scanner import events
from lp.codehosting.scanner.buglinks import BugBranchLinker
from lp.codehosting.scanner.tests.test_bzrsync import BzrSyncTestCase
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.services.osutils import override_environ
from lp.testing import (
    TestCase,
    TestCaseWithFactory,
    )


class RevisionPropertyParsing(TestCase):
    """Tests for parsing the bugs revision property.

    The bugs revision property holds information about Launchpad bugs which
    are affected by a revision. A given revision may affect multiple bugs in
    different ways. A revision may indicate work has begin on a bug, or that
    it constitutes a fix for a bug.

    The bugs property is formatted as a newline-separated list of entries.
    Each entry is of the form '<bug_id> <status>', where '<bug_id>' is the URL
    for a page that describes the bug, and status is one of 'fixed' or
    'inprogress'.

    In general, the parser skips over any lines with errors.

    Blank lines and extraneous whitespace are ignored. URLs for non-Launchpad
    bugs are ignored. The '<status>' field is case-insensitive.

    If the same bug is mentioned more than once, the final mention is
    considered authoritative.
    """

    def extractBugInfo(self, bug_property):
        revision = Revision(
            self.factory.getUniqueString(),
            properties=dict(bugs=bug_property))
        bug_linker = BugBranchLinker(None)
        return bug_linker.extractBugInfo(revision)

    def test_single(self):
        # Parsing a single line should give a dict with a single entry,
        # mapping the bug_id to the status.
        bugs = self.extractBugInfo("https://launchpad.net/bugs/9999 fixed")
        self.assertEquals(bugs, {9999: 'fixed'})

    def test_multiple(self):
        # Information about more than one bug can be specified. Make sure that
        # all the information is processed.
        bugs = self.extractBugInfo(
            "https://launchpad.net/bugs/9999 fixed\n"
            "https://launchpad.net/bugs/8888 fixed")
        self.assertEquals(bugs, {9999: 'fixed',
                                 8888: 'fixed'})

    def test_empty(self):
        # If the property is empty, then return an empty dict.
        bugs = self.extractBugInfo('')
        self.assertEquals(bugs, {})

    def test_bad_bug(self):
        # If the given bug is not a valid integer, then skip it, generate an
        # OOPS and continue processing.
        bugs = self.extractBugInfo('https://launchpad.net/~jml fixed')
        self.assertEquals(bugs, {})

    def test_non_launchpad_bug(self):
        # References to bugs on sites other than launchpad are ignored.
        bugs = self.extractBugInfo('http://bugs.debian.org/1234 fixed')
        self.assertEquals(bugs, {})

    def test_duplicated_line(self):
        # If a particular line is duplicated, silently ignore the duplicates.
        bugs = self.extractBugInfo(
            'https://launchpad.net/bugs/9999 fixed\n'
            'https://launchpad.net/bugs/9999 fixed')
        self.assertEquals(bugs, {9999: 'fixed'})

    def test_strict_url_checking(self):
        # Ignore URLs that look like a Launchpad bug URL but aren't.
        bugs = self.extractBugInfo('https://launchpad.net/people/1234 fixed')
        self.assertEquals(bugs, {})
        bugs = self.extractBugInfo(
            'https://launchpad.net/bugs/foo/1234 fixed')
        self.assertEquals(bugs, {})


class TestBugLinking(BzrSyncTestCase):
    """Tests for creating BugBranch items on scanning branches.

    We create a BugBranch item if we find a good 'bugs' property in a new
    mainline revision of a branch.
    """

    def setUp(self):
        BzrSyncTestCase.setUp(self)

    def makeFixtures(self):
        super(TestBugLinking, self).makeFixtures()
        self.bug1 = self.factory.makeBug()
        sp = self.factory.makeSourcePackage()
        self.bug1.addTask(self.bug1.owner, sp)
        dsp = self.factory.makeDistributionSourcePackage()
        self.bug1.addTask(self.bug1.owner, dsp)
        distro = self.factory.makeDistribution()
        self.bug1.addTask(self.bug1.owner, distro)
        self.bug2 = self.factory.makeBug()
        self.new_db_branch = self.factory.makeAnyBranch()
        removeSecurityProxy(distro).max_bug_heat = 0
        removeSecurityProxy(dsp).max_bug_heat = 0
        self.layer.txn.commit()

    def getBugURL(self, bug):
        """Get the canonical URL for 'bug'.

        We don't use canonical_url because we don't want to have to make
        Bazaar know about launchpad.dev.
        """
        return 'https://launchpad.net/bugs/%s' % bug.id

    def assertBugBranchLinked(self, bug, branch):
        """Assert that the BugBranch for `bug` and `branch` exists.

        Raises an assertion error if there's no such bug.
        """
        bug_branch = getUtility(IBugBranchSet).getBugBranch(bug, branch)
        if bug_branch is None:
            self.fail('No BugBranch found for %r, %r' % (bug, branch))

    def test_newMainlineRevisionAddsBugBranch(self):
        """New mainline revisions with bugs properties create BugBranches."""
        self.commitRevision(
            rev_id='rev1',
            revprops={'bugs': '%s fixed' % self.getBugURL(self.bug1)})
        self.syncBazaarBranchToDatabase(self.bzr_branch, self.db_branch)
        self.assertBugBranchLinked(self.bug1, self.db_branch)

    def test_scanningTwiceDoesntMatter(self):
        """Scanning a branch twice is the same as scanning it once."""
        self.commitRevision(
            rev_id='rev1',
            revprops={'bugs': '%s fixed' % self.getBugURL(self.bug1)})
        self.syncBazaarBranchToDatabase(self.bzr_branch, self.db_branch)
        self.syncBazaarBranchToDatabase(self.bzr_branch, self.db_branch)
        self.assertBugBranchLinked(self.bug1, self.db_branch)

    def makePackageBranch(self):
        LaunchpadZopelessLayer.switchDbUser(self.lp_db_user)
        try:
            branch = self.factory.makePackageBranch()
            branch.sourcepackage.setBranch(
                PackagePublishingPocket.RELEASE, branch, branch.owner)
            LaunchpadZopelessLayer.txn.commit()
        finally:
            LaunchpadZopelessLayer.switchDbUser(config.branchscanner.dbuser)
        return branch

    def test_linking_bug_to_official_package_branch(self):
        # We can link a bug to an official package branch. Test added to catch
        # bug 391303.
        self.commitRevision(
            rev_id='rev1',
            revprops={'bugs': '%s fixed' % self.getBugURL(self.bug1)})
        branch = self.makePackageBranch()
        self.syncBazaarBranchToDatabase(self.bzr_branch, branch)
        self.assertBugBranchLinked(self.bug1, branch)

    def test_knownMainlineRevisionsDoesntMakeLink(self):
        """Don't add BugBranches for known mainline revision."""
        self.commitRevision(
            rev_id='rev1',
            revprops={'bugs': '%s fixed' % self.getBugURL(self.bug1)})
        self.syncBazaarBranchToDatabase(self.bzr_branch, self.db_branch)
        # Create a new DB branch to sync with.
        self.syncBazaarBranchToDatabase(self.bzr_branch, self.new_db_branch)
        self.assertEqual(
            getUtility(IBugBranchSet).getBugBranch(
                self.bug1, self.new_db_branch),
            None,
            "Should not create a BugBranch.")

    def test_nonMainlineRevisionsDontMakeBugBranches(self):
        """Don't add BugBranches based on non-mainline revisions."""
        # Make the base revision.
        author = self.factory.getUniqueString()
        # XXX: AaronBentley 2010-08-06 bug=614404: a bzr username is
        # required to generate the revision-id.
        with override_environ(BZR_EMAIL='me@example.com'):
            self.bzr_tree.commit(
                u'common parent', committer=author, rev_id='r1',
                allow_pointless=True)

            # Branch from the base revision.
            new_tree = self.make_branch_and_tree('bzr_branch_merged')
            new_tree.pull(self.bzr_branch)

            # Commit to both branches
            self.bzr_tree.commit(
                u'commit one', committer=author, rev_id='r2',
                allow_pointless=True)
            new_tree.commit(
                u'commit two', committer=author, rev_id='r1.1.1',
                allow_pointless=True,
                revprops={'bugs': '%s fixed' % self.getBugURL(self.bug1)})

            # Merge and commit.
            self.bzr_tree.merge_from_branch(new_tree.branch)
            self.bzr_tree.commit(
                u'merge', committer=author, rev_id='r3',
                allow_pointless=True)

        self.syncBazaarBranchToDatabase(self.bzr_branch, self.db_branch)
        self.assertEqual(
            getUtility(IBugBranchSet).getBugBranch(self.bug1, self.db_branch),
            None,
            "Should not create a BugBranch.")

    def test_ignoreNonExistentBug(self):
        """If the bug doesn't actually exist, we just ignore it."""
        self.assertRaises(NotFoundError, getUtility(IBugSet).get, 99999)
        self.assertEqual([], list(self.db_branch.linked_bugs))
        self.commitRevision(
            rev_id='rev1',
            revprops={'bugs': 'https://launchpad.net/bugs/99999 fixed'})
        self.syncBazaarBranchToDatabase(self.bzr_branch, self.db_branch)
        self.assertEqual([], list(self.db_branch.linked_bugs))

    def test_multipleBugsInProperty(self):
        """Create BugBranch links for *all* bugs in the property."""
        self.commitRevision(
            rev_id='rev1',
            revprops={'bugs': '%s fixed\n%s fixed' % (
                    self.getBugURL(self.bug1), self.getBugURL(self.bug2))})
        self.syncBazaarBranchToDatabase(self.bzr_branch, self.db_branch)

        self.assertBugBranchLinked(self.bug1, self.db_branch)
        self.assertBugBranchLinked(self.bug2, self.db_branch)


class TestSubscription(TestCaseWithFactory):

    layer = LaunchpadZopelessLayer

    def test_got_new_revision_subscribed(self):
        """got_new_revision is subscribed to NewRevision."""
        self.useBzrBranches(direct_database=True)
        db_branch, tree = self.create_branch_and_tree()
        bug = self.factory.makeBug()
        self.layer.txn.commit()
        LaunchpadZopelessLayer.switchDbUser(config.branchscanner.dbuser)
        # XXX: AaronBentley 2010-08-06 bug=614404: a bzr username is
        # required to generate the revision-id.
        with override_environ(BZR_EMAIL='me@example.com'):
            revision_id = tree.commit('fix revision',
                revprops={
                    'bugs': 'https://launchpad.net/bugs/%d fixed' % bug.id})
        bzr_revision = tree.branch.repository.get_revision(revision_id)
        revno = 1
        revision_set = getUtility(IRevisionSet)
        db_revision = revision_set.newFromBazaarRevision(bzr_revision)
        notify(events.NewRevision(
            db_branch, tree.branch, db_revision, bzr_revision, revno))
        bug_branch = getUtility(IBugBranchSet).getBugBranch(bug, db_branch)
        self.assertIsNot(None, bug_branch)


def test_suite():
    return unittest.TestLoader().loadTestsFromName(__name__)