launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #17322
[Merge] lp:~wgrant/launchpad/spr-bug-closure-move into lp:launchpad
William Grant has proposed merging lp:~wgrant/launchpad/spr-bug-closure-move into lp:launchpad with lp:~wgrant/launchpad/process-accepted-cleanup as a prerequisite.
Commit message:
Move all the bug closure code and tests from scripts.processaccepted to model.processacceptedbugsjob, since ProcessAcceptedBugsJob should ultimately be the only callsite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~wgrant/launchpad/spr-bug-closure-move/+merge/230202
Move all the bug closure code and tests from scripts.processaccepted to model.processacceptedbugsjob, since ProcessAcceptedBugsJob should ultimately be the only callsite.
--
https://code.launchpad.net/~wgrant/launchpad/spr-bug-closure-move/+merge/230202
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/spr-bug-closure-move into lp:launchpad.
=== modified file 'lib/lp/soyuz/model/processacceptedbugsjob.py'
--- lib/lp/soyuz/model/processacceptedbugsjob.py 2013-06-20 05:50:00 +0000
+++ lib/lp/soyuz/model/processacceptedbugsjob.py 2014-08-09 09:28:06 +0000
@@ -1,15 +1,17 @@
-# Copyright 2012 Canonical Ltd. This software is licensed under the
+# Copyright 2012-2014 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
__all__ = [
- "close_bug_ids_for_sourcepackagerelease",
+ 'close_bugs_for_queue_item',
+ 'close_bugs_for_sourcepublication',
"ProcessAcceptedBugsJob",
]
import logging
+from debian.deb822 import Deb822Dict
from storm.locals import (
And,
Int,
@@ -21,10 +23,13 @@
classProvides,
implements,
)
+from zope.security.management import getSecurityPolicy
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
+from lp.archiveuploader.tagfiles import parse_tagfile_content
from lp.bugs.interfaces.bug import IBugSet
from lp.bugs.interfaces.bugtask import BugTaskStatus
+from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.model.distroseries import DistroSeries
from lp.services.config import config
from lp.services.database.interfaces import (
@@ -34,6 +39,13 @@
from lp.services.database.stormbase import StormBase
from lp.services.job.model.job import Job
from lp.services.job.runner import BaseRunnableJob
+from lp.services.webapp.authorization import LaunchpadPermissiveSecurityPolicy
+from lp.soyuz.enums import (
+ ArchivePurpose,
+ re_bug_numbers,
+ re_closes,
+ re_lp_closes,
+ )
from lp.soyuz.interfaces.processacceptedbugsjob import (
IProcessAcceptedBugsJob,
IProcessAcceptedBugsJobSource,
@@ -61,6 +73,142 @@
content=content)
+def get_bug_ids_from_changes_file(changes_file):
+ """Parse the changes file and return a list of bug IDs referenced by it.
+
+ The bugs is specified in the Launchpad-bugs-fixed header, and are
+ separated by a space character. Nonexistent bug ids are ignored.
+ """
+ tags = Deb822Dict(parse_tagfile_content(changes_file.read()))
+ bugs_fixed = tags.get('Launchpad-bugs-fixed', '').split()
+ return [int(bug_id) for bug_id in bugs_fixed if bug_id.isdigit()]
+
+
+def get_bug_ids_from_changelog_entry(sourcepackagerelease, since_version):
+ """Parse the changelog_entry in the sourcepackagerelease and return a
+ list of bug IDs referenced by it.
+ """
+ changelog = sourcepackagerelease.aggregate_changelog(since_version)
+ closes = []
+ # There are 2 main regexes to match. Each match from those can then
+ # have further multiple matches from the 3rd regex:
+ # closes: NNN, NNN
+ # lp: #NNN, #NNN
+ regexes = (
+ re_closes.finditer(changelog), re_lp_closes.finditer(changelog))
+ for regex in regexes:
+ for match in regex:
+ bug_match = re_bug_numbers.findall(match.group(0))
+ closes += map(int, bug_match)
+ return closes
+
+
+def can_close_bugs(target):
+ """Whether or not bugs should be closed in the given target.
+
+ ISourcePackagePublishingHistory and IPackageUpload are the
+ currently supported targets.
+
+ Source publications or package uploads targeted to pockets
+ PROPOSED/BACKPORTS or any other archive purpose than PRIMARY will
+ not automatically close bugs.
+ """
+ banned_pockets = (
+ PackagePublishingPocket.PROPOSED,
+ PackagePublishingPocket.BACKPORTS)
+
+ if (target.pocket in banned_pockets or
+ target.archive.purpose != ArchivePurpose.PRIMARY):
+ return False
+
+ return True
+
+
+def close_bugs_for_queue_item(queue_item, changesfile_object=None):
+ """Close bugs for a given queue item.
+
+ 'queue_item' is an IPackageUpload instance and is given by the user.
+
+ 'changesfile_object' is optional if not given this function will try
+ to use the IPackageUpload.changesfile, which is only available after
+ the upload is processed and committed.
+
+ In practice, 'changesfile_object' is only set when we are closing bugs
+ in upload-time (see nascentupload-closing-bugs.txt).
+
+ Skip bug-closing if the upload is target to pocket PROPOSED or if
+ the upload is for a PPA.
+
+ Set the package bugtask status to Fix Released and the changelog is added
+ as a comment.
+ """
+ if not can_close_bugs(queue_item):
+ return
+
+ if changesfile_object is None:
+ changesfile_object = queue_item.changesfile
+
+ for source_queue_item in queue_item.sources:
+ close_bugs_for_sourcepackagerelease(
+ queue_item.distroseries, source_queue_item.sourcepackagerelease,
+ changesfile_object)
+
+
+def close_bugs_for_sourcepublication(source_publication, since_version=None):
+ """Close bugs for a given sourcepublication.
+
+ Given a `ISourcePackagePublishingHistory` close bugs mentioned in
+ upload changesfile.
+ """
+ if not can_close_bugs(source_publication):
+ return
+
+ sourcepackagerelease = source_publication.sourcepackagerelease
+ changesfile_object = sourcepackagerelease.upload_changesfile
+
+ close_bugs_for_sourcepackagerelease(
+ source_publication.distroseries, sourcepackagerelease,
+ changesfile_object, since_version)
+
+
+def close_bugs_for_sourcepackagerelease(distroseries, source_release,
+ changesfile_object,
+ since_version=None):
+ """Close bugs for a given source.
+
+ Given an `IDistroSeries`, an `ISourcePackageRelease`, and a
+ corresponding changesfile object, close bugs mentioned in the
+ changesfile in the context of the source.
+
+ If changesfile_object is None and since_version is supplied,
+ close all the bugs in changelog entries made after that version and up
+ to and including the source_release's version. It does this by parsing
+ the changelog on the sourcepackagerelease. This could be extended in
+ the future to deal with the changes file as well but there is no
+ requirement to do so right now.
+ """
+ if since_version and source_release.changelog:
+ bug_ids_to_close = get_bug_ids_from_changelog_entry(
+ source_release, since_version=since_version)
+ elif changesfile_object:
+ bug_ids_to_close = get_bug_ids_from_changes_file(changesfile_object)
+ else:
+ return
+
+ # No bugs to be closed by this upload, move on.
+ if not bug_ids_to_close:
+ return
+
+ if getSecurityPolicy() == LaunchpadPermissiveSecurityPolicy:
+ # We're already running in a script, so we can just close the bugs
+ # directly.
+ close_bug_ids_for_sourcepackagerelease(
+ distroseries, source_release, bug_ids_to_close)
+ else:
+ job_source = getUtility(IProcessAcceptedBugsJobSource)
+ job_source.create(distroseries, source_release, bug_ids_to_close)
+
+
class ProcessAcceptedBugsJob(StormBase, BaseRunnableJob):
"""Base class for jobs to close bugs for accepted package uploads."""
=== modified file 'lib/lp/soyuz/scripts/processaccepted.py'
--- lib/lp/soyuz/scripts/processaccepted.py 2014-08-09 09:28:06 +0000
+++ lib/lp/soyuz/scripts/processaccepted.py 2014-08-09 09:28:06 +0000
@@ -5,25 +5,16 @@
__metaclass__ = type
__all__ = [
- 'close_bugs_for_queue_item',
- 'close_bugs_for_sourcepackagerelease',
- 'close_bugs_for_sourcepublication',
- 'get_bug_ids_from_changes_file',
'ProcessAccepted',
]
from optparse import OptionValueError
import sys
-from debian.deb822 import Deb822Dict
from zope.component import getUtility
-from zope.security.management import getSecurityPolicy
from lp.archivepublisher.publishing import GLOBAL_PUBLISHER_LOCK
from lp.archivepublisher.scripts.base import PublisherScript
-from lp.archiveuploader.tagfiles import parse_tagfile_content
-from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.services.webapp.authorization import LaunchpadPermissiveSecurityPolicy
from lp.services.webapp.errorlog import (
ErrorReportingUtility,
ScriptRequest,
@@ -31,153 +22,9 @@
from lp.soyuz.enums import (
ArchivePurpose,
PackageUploadStatus,
- re_bug_numbers,
- re_closes,
- re_lp_closes,
)
from lp.soyuz.interfaces.archive import IArchiveSet
-from lp.soyuz.interfaces.processacceptedbugsjob import (
- IProcessAcceptedBugsJobSource,
- )
-from lp.soyuz.model.processacceptedbugsjob import (
- close_bug_ids_for_sourcepackagerelease,
- )
-
-
-def get_bug_ids_from_changes_file(changes_file):
- """Parse the changes file and return a list of bug IDs referenced by it.
-
- The bugs is specified in the Launchpad-bugs-fixed header, and are
- separated by a space character. Nonexistent bug ids are ignored.
- """
- tags = Deb822Dict(parse_tagfile_content(changes_file.read()))
- bugs_fixed = tags.get('Launchpad-bugs-fixed', '').split()
- return [int(bug_id) for bug_id in bugs_fixed if bug_id.isdigit()]
-
-
-def get_bug_ids_from_changelog_entry(sourcepackagerelease, since_version):
- """Parse the changelog_entry in the sourcepackagerelease and return a
- list of bug IDs referenced by it.
- """
- changelog = sourcepackagerelease.aggregate_changelog(since_version)
- closes = []
- # There are 2 main regexes to match. Each match from those can then
- # have further multiple matches from the 3rd regex:
- # closes: NNN, NNN
- # lp: #NNN, #NNN
- regexes = (
- re_closes.finditer(changelog), re_lp_closes.finditer(changelog))
- for regex in regexes:
- for match in regex:
- bug_match = re_bug_numbers.findall(match.group(0))
- closes += map(int, bug_match)
- return closes
-
-
-def can_close_bugs(target):
- """Whether or not bugs should be closed in the given target.
-
- ISourcePackagePublishingHistory and IPackageUpload are the
- currently supported targets.
-
- Source publications or package uploads targeted to pockets
- PROPOSED/BACKPORTS or any other archive purpose than PRIMARY will
- not automatically close bugs.
- """
- banned_pockets = (
- PackagePublishingPocket.PROPOSED,
- PackagePublishingPocket.BACKPORTS)
-
- if (target.pocket in banned_pockets or
- target.archive.purpose != ArchivePurpose.PRIMARY):
- return False
-
- return True
-
-
-def close_bugs_for_queue_item(queue_item, changesfile_object=None):
- """Close bugs for a given queue item.
-
- 'queue_item' is an IPackageUpload instance and is given by the user.
-
- 'changesfile_object' is optional if not given this function will try
- to use the IPackageUpload.changesfile, which is only available after
- the upload is processed and committed.
-
- In practice, 'changesfile_object' is only set when we are closing bugs
- in upload-time (see nascentupload-closing-bugs.txt).
-
- Skip bug-closing if the upload is target to pocket PROPOSED or if
- the upload is for a PPA.
-
- Set the package bugtask status to Fix Released and the changelog is added
- as a comment.
- """
- if not can_close_bugs(queue_item):
- return
-
- if changesfile_object is None:
- changesfile_object = queue_item.changesfile
-
- for source_queue_item in queue_item.sources:
- close_bugs_for_sourcepackagerelease(
- queue_item.distroseries, source_queue_item.sourcepackagerelease,
- changesfile_object)
-
-
-def close_bugs_for_sourcepublication(source_publication, since_version=None):
- """Close bugs for a given sourcepublication.
-
- Given a `ISourcePackagePublishingHistory` close bugs mentioned in
- upload changesfile.
- """
- if not can_close_bugs(source_publication):
- return
-
- sourcepackagerelease = source_publication.sourcepackagerelease
- changesfile_object = sourcepackagerelease.upload_changesfile
-
- close_bugs_for_sourcepackagerelease(
- source_publication.distroseries, sourcepackagerelease,
- changesfile_object, since_version)
-
-
-def close_bugs_for_sourcepackagerelease(distroseries, source_release,
- changesfile_object,
- since_version=None):
- """Close bugs for a given source.
-
- Given an `IDistroSeries`, an `ISourcePackageRelease`, and a
- corresponding changesfile object, close bugs mentioned in the
- changesfile in the context of the source.
-
- If changesfile_object is None and since_version is supplied,
- close all the bugs in changelog entries made after that version and up
- to and including the source_release's version. It does this by parsing
- the changelog on the sourcepackagerelease. This could be extended in
- the future to deal with the changes file as well but there is no
- requirement to do so right now.
- """
- if since_version and source_release.changelog:
- bug_ids_to_close = get_bug_ids_from_changelog_entry(
- source_release, since_version=since_version)
- elif changesfile_object:
- bug_ids_to_close = get_bug_ids_from_changes_file(changesfile_object)
- else:
- return
-
- # No bugs to be closed by this upload, move on.
- if not bug_ids_to_close:
- return
-
- if getSecurityPolicy() == LaunchpadPermissiveSecurityPolicy:
- # We're already running in a script, so we can just close the bugs
- # directly.
- close_bug_ids_for_sourcepackagerelease(
- distroseries, source_release, bug_ids_to_close)
- else:
- job_source = getUtility(IProcessAcceptedBugsJobSource)
- job_source.create(distroseries, source_release, bug_ids_to_close)
+from lp.soyuz.model.processacceptedbugsjob import close_bugs_for_queue_item
class ProcessAccepted(PublisherScript):
=== removed file 'lib/lp/soyuz/scripts/tests/test_processaccepted.py'
--- lib/lp/soyuz/scripts/tests/test_processaccepted.py 2014-04-24 06:45:51 +0000
+++ lib/lp/soyuz/scripts/tests/test_processaccepted.py 1970-01-01 00:00:00 +0000
@@ -1,177 +0,0 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-__metaclass__ = type
-
-from StringIO import StringIO
-from textwrap import dedent
-
-from zope.component import getUtility
-from zope.security.interfaces import ForbiddenAttribute
-from zope.security.proxy import removeSecurityProxy
-
-from lp.app.enums import InformationType
-from lp.bugs.interfaces.bugtask import BugTaskStatus
-from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.soyuz.interfaces.processacceptedbugsjob import (
- IProcessAcceptedBugsJobSource,
- )
-from lp.soyuz.scripts.processaccepted import (
- close_bugs_for_sourcepackagerelease,
- close_bugs_for_sourcepublication,
- )
-from lp.testing import (
- celebrity_logged_in,
- person_logged_in,
- TestCaseWithFactory,
- )
-from lp.testing.layers import (
- DatabaseFunctionalLayer,
- LaunchpadZopelessLayer,
- )
-
-
-class TestClosingBugs(TestCaseWithFactory):
- """Test the various bug closing methods in processaccepted.py.
-
- Tests are currently spread around the codebase; this is an attempt to
- start a unification in a single file and those other tests need
- migrating here.
- See also:
- * lib/lp/soyuz/doc/closing-bugs-from-changelogs.txt
- * lib/lp/archiveuploader/tests/nascentupload-closing-bugs.txt
- """
- layer = LaunchpadZopelessLayer
-
- def makeChangelogWithBugs(self, spr, target_series=None):
- """Create a changelog for the passed sourcepackagerelease that has
- 6 bugs referenced.
-
- :param spr: The sourcepackagerelease that needs a changelog.
- :param target_distro: the distribution context for the source package
- bug target. If None, default to its uploaded distribution.
-
- :return: A tuple which is a list of (bug, bugtask)
- """
- # Make 4 bugs and corresponding bugtasks and put them in an array
- # as tuples.
- bugs = []
- for i in range(6):
- if target_series is None:
- target_series = spr.upload_distroseries
- target = target_series.getSourcePackage(spr.sourcepackagename)
- bug = self.factory.makeBug()
- bugtask = self.factory.makeBugTask(target=target, bug=bug)
- bugs.append((bug, bugtask))
- # Make a changelog entry for a package which contains the IDs of
- # the 6 bugs separated across 3 releases.
- changelog = dedent("""
- foo (1.0-3) unstable; urgency=low
-
- * closes: %s, %s
- * lp: #%s, #%s
-
- -- Foo Bar <foo@xxxxxxxxxxx> Tue, 01 Jan 1970 01:50:41 +0000
-
- foo (1.0-2) unstable; urgency=low
-
- * closes: %s
-
- -- Foo Bar <foo@xxxxxxxxxxx> Tue, 01 Jan 1970 01:50:41 +0000
-
- foo (1.0-1) unstable; urgency=low
-
- * closes: %s
-
- -- Foo Bar <foo@xxxxxxxxxxx> Tue, 01 Jan 1970 01:50:41 +0000
-
- """ % (
- bugs[0][0].id,
- bugs[1][0].id,
- bugs[2][0].id,
- bugs[3][0].id,
- bugs[4][0].id,
- bugs[5][0].id,
- ))
- lfa = self.factory.makeLibraryFileAlias(content=changelog)
- removeSecurityProxy(spr).changelog = lfa
- self.layer.txn.commit()
- return bugs
-
- def test_close_bugs_for_sourcepackagerelease_with_no_changes_file(self):
- # If there's no changes file it should read the changelog_entry on
- # the sourcepackagerelease.
-
- spr = self.factory.makeSourcePackageRelease(changelog_entry="blah")
- bugs = self.makeChangelogWithBugs(spr)
-
- # Call the method and test it's closed the bugs.
- close_bugs_for_sourcepackagerelease(
- spr.upload_distroseries, spr, None, since_version="1.0-1")
- for bug, bugtask in bugs:
- if bug.id != bugs[5][0].id:
- self.assertEqual(BugTaskStatus.FIXRELEASED, bugtask.status)
- else:
- self.assertEqual(BugTaskStatus.NEW, bugtask.status)
-
- def test__close_bugs_for_sourcepublication__uses_right_distro(self):
- # If a source was originally uploaded to a different distro,
- # closing bugs based on a publication of the same source in a new
- # distro should work.
-
- # Create a source package that was originally uploaded to one
- # distro and publish it in a second distro.
- spr = self.factory.makeSourcePackageRelease(changelog_entry="blah")
- target_distro = self.factory.makeDistribution()
- target_distroseries = self.factory.makeDistroSeries(target_distro)
- bugs = self.makeChangelogWithBugs(
- spr, target_series=target_distroseries)
- target_spph = self.factory.makeSourcePackagePublishingHistory(
- sourcepackagerelease=spr, distroseries=target_distroseries,
- archive=target_distro.main_archive,
- pocket=PackagePublishingPocket.RELEASE)
-
- # The test depends on this pre-condition.
- self.assertNotEqual(spr.upload_distroseries.distribution,
- target_distroseries.distribution)
-
- close_bugs_for_sourcepublication(target_spph, since_version="1.0")
-
- for bug, bugtask in bugs:
- self.assertEqual(BugTaskStatus.FIXRELEASED, bugtask.status)
-
-
-class TestClosingPrivateBugs(TestCaseWithFactory):
- # The distroseries +queue page can close private bugs when accepting
- # packages.
-
- layer = DatabaseFunctionalLayer
-
- def test_close_bugs_for_sourcepackagerelease_with_private_bug(self):
- """close_bugs_for_sourcepackagerelease works with private bugs."""
- changes_file_template = "Format: 1.7\nLaunchpad-bugs-fixed: %s\n"
- # changelog_entry is required for an assertion inside the function
- # we're testing.
- spr = self.factory.makeSourcePackageRelease(changelog_entry="blah")
- archive_admin = self.factory.makePerson()
- series = spr.upload_distroseries
- dsp = series.distribution.getSourcePackage(spr.sourcepackagename)
- bug = self.factory.makeBug(
- target=dsp, information_type=InformationType.USERDATA)
- changes = StringIO(changes_file_template % bug.id)
-
- with person_logged_in(archive_admin):
- # The archive admin user can't normally see this bug.
- self.assertRaises(ForbiddenAttribute, bug, 'status')
- # But the bug closure should work.
- close_bugs_for_sourcepackagerelease(series, spr, changes)
-
- # Rather than closing the bugs immediately, this creates a
- # ProcessAcceptedBugsJob.
- with celebrity_logged_in("admin"):
- self.assertEqual(BugTaskStatus.NEW, bug.default_bugtask.status)
- job_source = getUtility(IProcessAcceptedBugsJobSource)
- [job] = list(job_source.iterReady())
- self.assertEqual(series, job.distroseries)
- self.assertEqual(spr, job.sourcepackagerelease)
- self.assertEqual([bug.id], job.bug_ids)
=== modified file 'lib/lp/soyuz/tests/test_processaccepted.py'
--- lib/lp/soyuz/tests/test_processaccepted.py 2014-08-09 09:28:06 +0000
+++ lib/lp/soyuz/tests/test_processaccepted.py 2014-08-09 09:28:06 +0000
@@ -3,10 +3,8 @@
"""Test process-accepted.py"""
-from cStringIO import StringIO
from optparse import OptionValueError
-from debian.deb822 import Changes
from testtools.matchers import LessThan
import transaction
@@ -20,10 +18,7 @@
PackageUploadStatus,
)
from lp.soyuz.model.queue import PackageUpload
-from lp.soyuz.scripts.processaccepted import (
- get_bug_ids_from_changes_file,
- ProcessAccepted,
- )
+from lp.soyuz.scripts.processaccepted import ProcessAccepted
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
from lp.testing import TestCaseWithFactory
from lp.testing.dbuser import switch_dbuser
@@ -190,62 +185,3 @@
script = ProcessAccepted(
test_args=['--all-derived', '-d', distro.name])
self.assertRaises(OptionValueError, script.validateArguments)
-
-
-class TestBugIDsFromChangesFile(TestCaseWithFactory):
- """Test get_bug_ids_from_changes_file."""
-
- layer = LaunchpadZopelessLayer
- dbuser = config.uploadqueue.dbuser
-
- def setUp(self):
- super(TestBugIDsFromChangesFile, self).setUp()
- self.changes = Changes({
- 'Format': '1.8',
- 'Source': 'swat',
- })
-
- def getBugIDs(self):
- """Serialize self.changes and use get_bug_ids_from_changes_file to
- extract bug IDs from it.
- """
- stream = StringIO()
- self.changes.dump(stream)
- stream.seek(0)
- return get_bug_ids_from_changes_file(stream)
-
- def test_no_bugs(self):
- # An empty list is returned if there are no bugs
- # mentioned.
- self.assertEqual([], self.getBugIDs())
-
- def test_invalid_bug_id(self):
- # Invalid bug ids (i.e. containing non-digit characters) are ignored.
- self.changes["Launchpad-Bugs-Fixed"] = "bla"
- self.assertEqual([], self.getBugIDs())
-
- def test_unknown_bug_id(self):
- # Unknown bug ids are passed through; they will be ignored later, by
- # close_bug_ids_for_sourcepackagerelease.
- self.changes["Launchpad-Bugs-Fixed"] = "45120"
- self.assertEqual([45120], self.getBugIDs())
-
- def test_valid_bug(self):
- # For valid bug ids the bug object is returned.
- bug = self.factory.makeBug()
- self.changes["Launchpad-Bugs-Fixed"] = "%d" % bug.id
- self.assertEqual([bug.id], self.getBugIDs())
-
- def test_case_sensitivity(self):
- # The spelling of Launchpad-Bugs-Fixed is case-insensitive.
- bug = self.factory.makeBug()
- self.changes["LaUnchpad-Bugs-fixed"] = "%d" % bug.id
- self.assertEqual([bug.id], self.getBugIDs())
-
- def test_multiple_bugs(self):
- # Multiple bug ids can be specified, separated by spaces.
- bug1 = self.factory.makeBug()
- bug2 = self.factory.makeBug()
- self.changes["Launchpad-Bugs-Fixed"] = "%d invalid %d" % (
- bug1.id, bug2.id)
- self.assertEqual([bug1.id, bug2.id], self.getBugIDs())
=== modified file 'lib/lp/soyuz/tests/test_processacceptedbugsjob.py'
--- lib/lp/soyuz/tests/test_processacceptedbugsjob.py 2014-04-24 06:45:51 +0000
+++ lib/lp/soyuz/tests/test_processacceptedbugsjob.py 2014-08-09 09:28:06 +0000
@@ -1,17 +1,22 @@
-# Copyright 2012 Canonical Ltd. This software is licensed under the
+# Copyright 2012-2014 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for jobs to close bugs for accepted package uploads."""
+from cStringIO import StringIO
from itertools import product
+from textwrap import dedent
+from debian.deb822 import Changes
from testtools.content import text_content
import transaction
from zope.component import getUtility
+from zope.security.interfaces import ForbiddenAttribute
from zope.security.proxy import removeSecurityProxy
from lp.app.enums import InformationType
from lp.bugs.interfaces.bugtask import BugTaskStatus
+from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.series import SeriesStatus
from lp.services.config import config
from lp.services.features.testing import FeatureFixture
@@ -24,9 +29,14 @@
)
from lp.soyuz.model.processacceptedbugsjob import (
close_bug_ids_for_sourcepackagerelease,
+ close_bugs_for_sourcepackagerelease,
+ close_bugs_for_sourcepublication,
+ get_bug_ids_from_changes_file,
)
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
from lp.testing import (
+ celebrity_logged_in,
+ person_logged_in,
run_script,
TestCaseWithFactory,
verifyObject,
@@ -34,10 +44,216 @@
from lp.testing.fakemethod import FakeMethod
from lp.testing.layers import (
CeleryJobLayer,
+ DatabaseFunctionalLayer,
LaunchpadZopelessLayer,
)
+class TestBugIDsFromChangesFile(TestCaseWithFactory):
+ """Test get_bug_ids_from_changes_file."""
+
+ layer = LaunchpadZopelessLayer
+ dbuser = config.uploadqueue.dbuser
+
+ def setUp(self):
+ super(TestBugIDsFromChangesFile, self).setUp()
+ self.changes = Changes({
+ 'Format': '1.8',
+ 'Source': 'swat',
+ })
+
+ def getBugIDs(self):
+ """Serialize self.changes and use get_bug_ids_from_changes_file to
+ extract bug IDs from it.
+ """
+ stream = StringIO()
+ self.changes.dump(stream)
+ stream.seek(0)
+ return get_bug_ids_from_changes_file(stream)
+
+ def test_no_bugs(self):
+ # An empty list is returned if there are no bugs
+ # mentioned.
+ self.assertEqual([], self.getBugIDs())
+
+ def test_invalid_bug_id(self):
+ # Invalid bug ids (i.e. containing non-digit characters) are ignored.
+ self.changes["Launchpad-Bugs-Fixed"] = "bla"
+ self.assertEqual([], self.getBugIDs())
+
+ def test_unknown_bug_id(self):
+ # Unknown bug ids are passed through; they will be ignored later, by
+ # close_bug_ids_for_sourcepackagerelease.
+ self.changes["Launchpad-Bugs-Fixed"] = "45120"
+ self.assertEqual([45120], self.getBugIDs())
+
+ def test_valid_bug(self):
+ # For valid bug ids the bug object is returned.
+ bug = self.factory.makeBug()
+ self.changes["Launchpad-Bugs-Fixed"] = "%d" % bug.id
+ self.assertEqual([bug.id], self.getBugIDs())
+
+ def test_case_sensitivity(self):
+ # The spelling of Launchpad-Bugs-Fixed is case-insensitive.
+ bug = self.factory.makeBug()
+ self.changes["LaUnchpad-Bugs-fixed"] = "%d" % bug.id
+ self.assertEqual([bug.id], self.getBugIDs())
+
+ def test_multiple_bugs(self):
+ # Multiple bug ids can be specified, separated by spaces.
+ bug1 = self.factory.makeBug()
+ bug2 = self.factory.makeBug()
+ self.changes["Launchpad-Bugs-Fixed"] = "%d invalid %d" % (
+ bug1.id, bug2.id)
+ self.assertEqual([bug1.id, bug2.id], self.getBugIDs())
+
+
+class TestClosingBugs(TestCaseWithFactory):
+ """Test the various bug closing methods in processaccepted.py.
+
+ Tests are currently spread around the codebase; this is an attempt to
+ start a unification in a single file and those other tests need
+ migrating here.
+ See also:
+ * lib/lp/soyuz/doc/closing-bugs-from-changelogs.txt
+ * lib/lp/archiveuploader/tests/nascentupload-closing-bugs.txt
+ """
+ layer = LaunchpadZopelessLayer
+
+ def makeChangelogWithBugs(self, spr, target_series=None):
+ """Create a changelog for the passed sourcepackagerelease that has
+ 6 bugs referenced.
+
+ :param spr: The sourcepackagerelease that needs a changelog.
+ :param target_distro: the distribution context for the source package
+ bug target. If None, default to its uploaded distribution.
+
+ :return: A tuple which is a list of (bug, bugtask)
+ """
+ # Make 4 bugs and corresponding bugtasks and put them in an array
+ # as tuples.
+ bugs = []
+ for i in range(6):
+ if target_series is None:
+ target_series = spr.upload_distroseries
+ target = target_series.getSourcePackage(spr.sourcepackagename)
+ bug = self.factory.makeBug()
+ bugtask = self.factory.makeBugTask(target=target, bug=bug)
+ bugs.append((bug, bugtask))
+ # Make a changelog entry for a package which contains the IDs of
+ # the 6 bugs separated across 3 releases.
+ changelog = dedent("""
+ foo (1.0-3) unstable; urgency=low
+
+ * closes: %s, %s
+ * lp: #%s, #%s
+
+ -- Foo Bar <foo@xxxxxxxxxxx> Tue, 01 Jan 1970 01:50:41 +0000
+
+ foo (1.0-2) unstable; urgency=low
+
+ * closes: %s
+
+ -- Foo Bar <foo@xxxxxxxxxxx> Tue, 01 Jan 1970 01:50:41 +0000
+
+ foo (1.0-1) unstable; urgency=low
+
+ * closes: %s
+
+ -- Foo Bar <foo@xxxxxxxxxxx> Tue, 01 Jan 1970 01:50:41 +0000
+
+ """ % (
+ bugs[0][0].id,
+ bugs[1][0].id,
+ bugs[2][0].id,
+ bugs[3][0].id,
+ bugs[4][0].id,
+ bugs[5][0].id,
+ ))
+ lfa = self.factory.makeLibraryFileAlias(content=changelog)
+ removeSecurityProxy(spr).changelog = lfa
+ self.layer.txn.commit()
+ return bugs
+
+ def test_close_bugs_for_sourcepackagerelease_with_no_changes_file(self):
+ # If there's no changes file it should read the changelog_entry on
+ # the sourcepackagerelease.
+
+ spr = self.factory.makeSourcePackageRelease(changelog_entry="blah")
+ bugs = self.makeChangelogWithBugs(spr)
+
+ # Call the method and test it's closed the bugs.
+ close_bugs_for_sourcepackagerelease(
+ spr.upload_distroseries, spr, None, since_version="1.0-1")
+ for bug, bugtask in bugs:
+ if bug.id != bugs[5][0].id:
+ self.assertEqual(BugTaskStatus.FIXRELEASED, bugtask.status)
+ else:
+ self.assertEqual(BugTaskStatus.NEW, bugtask.status)
+
+ def test__close_bugs_for_sourcepublication__uses_right_distro(self):
+ # If a source was originally uploaded to a different distro,
+ # closing bugs based on a publication of the same source in a new
+ # distro should work.
+
+ # Create a source package that was originally uploaded to one
+ # distro and publish it in a second distro.
+ spr = self.factory.makeSourcePackageRelease(changelog_entry="blah")
+ target_distro = self.factory.makeDistribution()
+ target_distroseries = self.factory.makeDistroSeries(target_distro)
+ bugs = self.makeChangelogWithBugs(
+ spr, target_series=target_distroseries)
+ target_spph = self.factory.makeSourcePackagePublishingHistory(
+ sourcepackagerelease=spr, distroseries=target_distroseries,
+ archive=target_distro.main_archive,
+ pocket=PackagePublishingPocket.RELEASE)
+
+ # The test depends on this pre-condition.
+ self.assertNotEqual(spr.upload_distroseries.distribution,
+ target_distroseries.distribution)
+
+ close_bugs_for_sourcepublication(target_spph, since_version="1.0")
+
+ for bug, bugtask in bugs:
+ self.assertEqual(BugTaskStatus.FIXRELEASED, bugtask.status)
+
+
+class TestClosingPrivateBugs(TestCaseWithFactory):
+ # The distroseries +queue page can close private bugs when accepting
+ # packages.
+
+ layer = DatabaseFunctionalLayer
+
+ def test_close_bugs_for_sourcepackagerelease_with_private_bug(self):
+ """close_bugs_for_sourcepackagerelease works with private bugs."""
+ changes_file_template = "Format: 1.7\nLaunchpad-bugs-fixed: %s\n"
+ # changelog_entry is required for an assertion inside the function
+ # we're testing.
+ spr = self.factory.makeSourcePackageRelease(changelog_entry="blah")
+ archive_admin = self.factory.makePerson()
+ series = spr.upload_distroseries
+ dsp = series.distribution.getSourcePackage(spr.sourcepackagename)
+ bug = self.factory.makeBug(
+ target=dsp, information_type=InformationType.USERDATA)
+ changes = StringIO(changes_file_template % bug.id)
+
+ with person_logged_in(archive_admin):
+ # The archive admin user can't normally see this bug.
+ self.assertRaises(ForbiddenAttribute, bug, 'status')
+ # But the bug closure should work.
+ close_bugs_for_sourcepackagerelease(series, spr, changes)
+
+ # Rather than closing the bugs immediately, this creates a
+ # ProcessAcceptedBugsJob.
+ with celebrity_logged_in("admin"):
+ self.assertEqual(BugTaskStatus.NEW, bug.default_bugtask.status)
+ job_source = getUtility(IProcessAcceptedBugsJobSource)
+ [job] = list(job_source.iterReady())
+ self.assertEqual(series, job.distroseries)
+ self.assertEqual(spr, job.sourcepackagerelease)
+ self.assertEqual([bug.id], job.bug_ids)
+
+
class TestCloseBugIDsForSourcePackageRelease(TestCaseWithFactory):
layer = LaunchpadZopelessLayer
Follow ups