Merge lp:~gmb/launchpad/comment-error-reporting-bug-571692-a into lp:launchpad

Proposed by Graham Binns
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 10868
Proposed branch: lp:~gmb/launchpad/comment-error-reporting-bug-571692-a
Merge into: lp:launchpad
Prerequisite: lp:~gmb/launchpad/halp
Diff against target: 577 lines (+324/-67)
8 files modified
lib/lp/bugs/doc/externalbugtracker-comment-imports.txt (+12/-13)
lib/lp/bugs/doc/externalbugtracker-comment-pushing.txt (+9/-11)
lib/lp/bugs/doc/externalbugtracker-linking-back.txt (+6/-10)
lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py (+49/-16)
lib/lp/bugs/scripts/checkwatches/core.py (+1/-4)
lib/lp/bugs/scripts/checkwatches/remotebugupdater.py (+17/-9)
lib/lp/bugs/scripts/checkwatches/tests/test_bugwatchupdater.py (+214/-0)
lib/lp/testing/factory.py (+16/-4)
To merge this branch: bzr merge lp:~gmb/launchpad/comment-error-reporting-bug-571692-a
Reviewer Review Type Date Requested Status
Paul Hummer (community) code Approve
Review via email: mp+25061@code.launchpad.net

Commit message

Errors encountered when syncing comments or back-linking to remote bugs will now be handled and reported correctly rather than being returned as "bug watch couldn't be synchronised."

Description of the change

This branch improves the handling and importing of errors that occur
when trying to sync comments with remote bugs. At the moment, those
errors aren't reported properly (i.e. they're masked as "Bug watch
failed to update" errors, which are useless. Once this branch lands the
errors will be displayed clearly on the bug watch pages.o

No lint.

== lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py ==

 - I've updated BugWatchUpdater.updateBugWatch() so that it now records
   errors correctly if something goes wrong when syncing comments or
   back-linking.

== lib/lp/bugs/scripts/checkwatches/core.py ==

 - I've tidied up some lint.

== lib/lp/bugs/scripts/checkwatches/remotebugupdater.py ==

 - I've altered RemoteBugUpdater.updateRemoteBug() so that it handles
   errors that occur when importing remote statuses an importances
   explicitly before relying on BugWatchUpdater to deal with errors that
   occur during comment syncing.

== lib/lp/bugs/scripts/checkwatches/tests/test_bugwatchupdater.py ==

 - I've added tests to cover error handling when syncing comments and
   back-linking.
 - I've added tests to cover the fact that the can_* booleans should be
   inherited from RemoteBugUpdater.

== lib/lp/testing/factory.py ==

 - I've update makeBugWatch() to allow it to accept a bug_task argument.
 - I've updated makeBugComment to allow it to accept a bug_watch
   argument.

To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/doc/externalbugtracker-comment-imports.txt'
2--- lib/lp/bugs/doc/externalbugtracker-comment-imports.txt 2010-04-26 12:01:20 +0000
3+++ lib/lp/bugs/doc/externalbugtracker-comment-imports.txt 2010-05-14 14:06:33 +0000
4@@ -86,16 +86,13 @@
5 comments in the comment_dict being imported into Launchpad.
6
7 >>> from canonical.launchpad.scripts.logger import log
8- >>> from lp.bugs.scripts.checkwatches.base import (
9- ... WorkingBase)
10- >>> from lp.bugs.scripts.checkwatches.bugwatchupdater import (
11- ... BugWatchUpdater)
12+ >>> from lp.bugs.scripts.checkwatches.core import CheckwatchesMaster
13+ >>> from lp.bugs.scripts.checkwatches.tests.test_bugwatchupdater import (
14+ ... make_bug_watch_updater)
15
16- >>> working_base = WorkingBase()
17- >>> working_base.init(
18- ... 'bugwatch@bugs.launchpad.net', transaction, log)
19- >>> bugwatch_updater = BugWatchUpdater(
20- ... working_base, bug_watch, external_bugtracker)
21+ >>> bugwatch_updater = make_bug_watch_updater(
22+ ... CheckwatchesMaster(transaction), bug_watch,
23+ ... external_bugtracker)
24 >>> bugwatch_updater.importBugComments()
25 INFO:...:Imported 3 comments for remote bug 123456 on ...
26
27@@ -396,8 +393,9 @@
28 >>> LaunchpadZopelessLayer.switchDbUser(config.checkwatches.dbuser)
29 >>> external_bugtracker.remote_comments = {
30 ... '5':"A comment containing a CVE entry: CVE-1991-9911."}
31- >>> bugwatch_updater = BugWatchUpdater(
32- ... working_base, bug_watch, external_bugtracker)
33+ >>> bugwatch_updater = make_bug_watch_updater(
34+ ... CheckwatchesMaster(transaction), bug_watch,
35+ ... external_bugtracker)
36 >>> bugwatch_updater.importBugComments()
37 INFO:...:Imported 1 comments for remote bug 123456...
38
39@@ -470,8 +468,9 @@
40
41 >>> transaction.commit()
42
43- >>> bugwatch_updater = BugWatchUpdater(
44- ... working_base, bug_watch, external_bugtracker)
45+ >>> bugwatch_updater = make_bug_watch_updater(
46+ ... CheckwatchesMaster(transaction), bug_watch,
47+ ... external_bugtracker)
48 >>> bugwatch_updater.importBugComments()
49 INFO:...:Imported 2 comments for remote bug 42 ...
50
51
52=== modified file 'lib/lp/bugs/doc/externalbugtracker-comment-pushing.txt'
53--- lib/lp/bugs/doc/externalbugtracker-comment-pushing.txt 2010-04-26 12:15:05 +0000
54+++ lib/lp/bugs/doc/externalbugtracker-comment-pushing.txt 2010-05-14 14:06:33 +0000
55@@ -96,16 +96,13 @@
56 tracker.
57
58 >>> from canonical.launchpad.scripts.logger import log
59- >>> from lp.bugs.scripts.checkwatches.base import (
60- ... WorkingBase)
61- >>> from lp.bugs.scripts.checkwatches.bugwatchupdater import (
62- ... BugWatchUpdater)
63+ >>> from lp.bugs.scripts.checkwatches.core import CheckwatchesMaster
64+ >>> from lp.bugs.scripts.checkwatches.tests.test_bugwatchupdater import (
65+ ... make_bug_watch_updater)
66
67- >>> working_base = WorkingBase()
68- >>> working_base.init(
69- ... 'bugwatch@bugs.launchpad.net', transaction, log)
70- >>> bugwatch_updater = BugWatchUpdater(
71- ... working_base, bug_watch, external_bugtracker)
72+ >>> bugwatch_updater = make_bug_watch_updater(
73+ ... CheckwatchesMaster(transaction), bug_watch,
74+ ... external_bugtracker)
75
76 >>> bugwatch_updater.pushBugComments()
77 Comment added as remote comment 1
78@@ -270,8 +267,9 @@
79 >>> broken_external_bugtracker = ErroringExternalBugTracker(
80 ... 'http://example.com')
81
82- >>> bugwatch_updater = BugWatchUpdater(
83- ... working_base, bug_watch, external_bugtracker)
84+ >>> bugwatch_updater = make_bug_watch_updater(
85+ ... CheckwatchesMaster(transaction), bug_watch,
86+ ... external_bugtracker)
87 >>> bugwatch_updater.external_bugtracker = broken_external_bugtracker
88 >>> bugwatch_updater.pushBugComments()
89 Traceback (most recent call last):
90
91=== modified file 'lib/lp/bugs/doc/externalbugtracker-linking-back.txt'
92--- lib/lp/bugs/doc/externalbugtracker-linking-back.txt 2010-04-26 12:15:05 +0000
93+++ lib/lp/bugs/doc/externalbugtracker-linking-back.txt 2010-05-14 14:06:33 +0000
94@@ -91,17 +91,13 @@
95
96 >>> transaction.commit()
97
98- >>> from canonical.launchpad.scripts.logger import log
99- >>> from lp.bugs.scripts.checkwatches.base import (
100- ... WorkingBase)
101- >>> from lp.bugs.scripts.checkwatches.bugwatchupdater import (
102- ... BugWatchUpdater)
103+ >>> from lp.bugs.scripts.checkwatches.core import CheckwatchesMaster
104+ >>> from lp.bugs.scripts.checkwatches.tests.test_bugwatchupdater import (
105+ ... make_bug_watch_updater)
106
107- >>> working_base = WorkingBase()
108- >>> working_base.init(
109- ... 'bugwatch@bugs.launchpad.net', transaction, log)
110- >>> bug_watch_updater = BugWatchUpdater(
111- ... working_base, bug_watch, external_bugtracker)
112+ >>> bug_watch_updater = make_bug_watch_updater(
113+ ... CheckwatchesMaster(transaction), bug_watch,
114+ ... external_bugtracker)
115 >>> bug_watch_updater.linkLaunchpadBug()
116 Getting Launchpad id for bug 42
117
118
119=== modified file 'lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py'
120--- lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py 2010-04-27 10:32:21 +0000
121+++ lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py 2010-05-14 14:06:33 +0000
122@@ -24,6 +24,7 @@
123 from lazr.lifecycle.event import ObjectCreatedEvent
124
125 from lp.bugs.interfaces.bug import IBugSet
126+from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus
127 from lp.bugs.scripts.checkwatches.base import (
128 WorkingBase, commit_before)
129 from lp.bugs.scripts.checkwatches.utilities import (
130@@ -31,7 +32,6 @@
131 from lp.registry.interfaces.person import PersonCreationRationale
132
133
134-
135 class BugWatchUpdater(WorkingBase):
136 """Handles the updating of a single BugWatch for checkwatches."""
137
138@@ -40,26 +40,41 @@
139 self.bug_watch = bug_watch
140 self.external_bugtracker = external_bugtracker
141
142+ # We save these for the sake of error reporting.
143+ self.remote_bug = self.bug_watch.remotebug
144+ self.local_bug = self.bug_watch.bug.id
145+ self.oops_properties = get_remote_system_oops_properties(
146+ self.external_bugtracker)
147+ self.oops_properties.extend([
148+ ('URL', self.bug_watch.url),
149+ ('bug_id', self.remote_bug),
150+ ('local_ids', str(self.local_bug))])
151+
152+ self.can_import_comments = parent.can_import_comments
153+ self.can_push_comments = parent.can_push_comments
154+ self.can_back_link = parent.can_back_link
155+
156+ # XXX 2010-05-11 gmb bug=578714:
157+ # The last three parameters on this method aren't needed and
158+ # should be removed.
159 @commit_before
160 def updateBugWatch(self, new_remote_status, new_malone_status,
161 new_remote_importance, new_malone_importance,
162- can_import_comments, can_push_comments, can_back_link,
163- error, oops_id):
164+ can_import_comments=None, can_push_comments=None,
165+ can_back_link=None):
166 """Update the BugWatch."""
167 with self.transaction:
168- self.bug_watch.last_error_type = error
169 if new_malone_status is not None:
170 self.bug_watch.updateStatus(
171 new_remote_status, new_malone_status)
172 if new_malone_importance is not None:
173 self.bug_watch.updateImportance(
174 new_remote_importance, new_malone_importance)
175- # Only sync comments and backlink if there was no
176- # earlier error, the local bug isn't a duplicate,
177- # *and* if the bug watch is associated with a bug
178- # task. This helps us to avoid spamming upstream.
179+ # Only sync comments and backlink if the local bug isn't a
180+ # duplicate and the bug watch is associated with a bug task.
181+ # This helps us to avoid spamming both upstream and
182+ # ourselves.
183 do_sync = (
184- error is None and
185 self.bug_watch.bug.duplicateof is None and
186 len(self.bug_watch.bugtasks) > 0
187 )
188@@ -69,17 +84,35 @@
189 # throw an exception, *all* the watches in
190 # self.bug_watches, even those that have not errored,
191 # will have negative activity added.
192+ error_message = None
193+ error_status = None
194+ oops_id = None
195 if do_sync:
196- if can_import_comments:
197- self.importBugComments()
198- if can_push_comments:
199- self.pushBugComments()
200- if can_back_link:
201- self.linkLaunchpadBug()
202+ try:
203+ if can_import_comments or self.can_import_comments:
204+ error_status = (
205+ BugWatchActivityStatus.COMMENT_IMPORT_FAILED)
206+ self.importBugComments()
207+ if can_push_comments or self.can_push_comments:
208+ error_status = BugWatchActivityStatus.COMMENT_PUSH_FAILED
209+ self.pushBugComments()
210+ if can_back_link or self.can_back_link:
211+ error_status = BugWatchActivityStatus.BACKLINK_FAILED
212+ self.linkLaunchpadBug()
213+ except Exception, ex:
214+ error_message = ex.message
215+ oops_id = self.error(
216+ "Failure updating bug %r on %s (local bug: %s)." %
217+ (self.remote_bug, self.external_bugtracker.baseurl,
218+ self.local_bug),
219+ self.oops_properties)
220+ else:
221+ error_status = None
222
223 with self.transaction:
224 self.bug_watch.addActivity(
225- result=error, oops_id=oops_id)
226+ result=error_status, message=error_message, oops_id=oops_id)
227+ self.bug_watch.last_error_type = error_status
228
229 @commit_before
230 def importBugComments(self):
231
232=== modified file 'lib/lp/bugs/scripts/checkwatches/core.py'
233--- lib/lp/bugs/scripts/checkwatches/core.py 2010-05-06 10:32:58 +0000
234+++ lib/lp/bugs/scripts/checkwatches/core.py 2010-05-14 14:06:33 +0000
235@@ -34,19 +34,16 @@
236
237 from zope.component import getUtility
238
239-from canonical.database.constants import UTC_NOW
240 from canonical.database.sqlbase import flush_database_updates
241 from canonical.launchpad.interfaces import (
242 CreateBugParams, IBugTrackerSet, IBugWatchSet, IDistribution,
243- ILaunchpadCelebrities, IPersonSet, ISupportsCommentImport,
244- ISupportsCommentPushing, PersonCreationRationale)
245+ ILaunchpadCelebrities, IPersonSet, PersonCreationRationale)
246 from canonical.launchpad.scripts.logger import log as default_log
247
248 from lp.bugs import externalbugtracker
249 from lp.bugs.externalbugtracker import (
250 BATCH_SIZE_UNLIMITED, BugWatchUpdateError,
251 UnknownBugTrackerTypeError)
252-from lp.bugs.interfaces.externalbugtracker import ISupportsBackLinking
253 from lp.bugs.scripts.checkwatches.base import (
254 WorkingBase, commit_before, with_interaction)
255 from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
256
257=== modified file 'lib/lp/bugs/scripts/checkwatches/remotebugupdater.py'
258--- lib/lp/bugs/scripts/checkwatches/remotebugupdater.py 2010-05-04 10:27:44 +0000
259+++ lib/lp/bugs/scripts/checkwatches/remotebugupdater.py 2010-05-14 14:06:33 +0000
260@@ -155,15 +155,23 @@
261 self.external_bugtracker),
262 info=sys.exc_info())
263
264- for bug_watch in bug_watches:
265- bug_watch_updater = BugWatchUpdater(
266- self, bug_watch, self.external_bugtracker)
267-
268- bug_watch_updater.updateBugWatch(
269- new_remote_status, new_malone_status,
270- new_remote_importance, new_malone_importance,
271- self.can_import_comments, self.can_push_comments,
272- self.can_back_link, error, oops_id)
273+ # Set the error and activity on all bug watches
274+ with self.transaction:
275+ getUtility(IBugWatchSet).bulkSetError(
276+ bug_watches, error)
277+ getUtility(IBugWatchSet).bulkAddActivity(
278+ bug_watches, result=error, oops_id=oops_id)
279+
280+ else:
281+ # Assuming nothing's gone wrong, we can now deal with
282+ # each BugWatch in turn.
283+ for bug_watch in bug_watches:
284+ bug_watch_updater = BugWatchUpdater(
285+ self, bug_watch, self.external_bugtracker)
286+
287+ bug_watch_updater.updateBugWatch(
288+ new_remote_status, new_malone_status,
289+ new_remote_importance, new_malone_importance)
290
291 except Exception, error:
292 # Send the error to the log.
293
294=== added file 'lib/lp/bugs/scripts/checkwatches/tests/test_bugwatchupdater.py'
295--- lib/lp/bugs/scripts/checkwatches/tests/test_bugwatchupdater.py 1970-01-01 00:00:00 +0000
296+++ lib/lp/bugs/scripts/checkwatches/tests/test_bugwatchupdater.py 2010-05-14 14:06:33 +0000
297@@ -0,0 +1,214 @@
298+# Copyright 2010 Canonical Ltd. This software is licensed under the
299+# GNU Affero General Public License version 3 (see the file LICENSE).
300+
301+"""Tests for the checkwatches.bugwatchupdater module."""
302+
303+__metaclass__ = type
304+
305+import transaction
306+import unittest
307+
308+from datetime import datetime
309+
310+from canonical.testing import LaunchpadZopelessLayer
311+
312+from lp.bugs.externalbugtracker.bugzilla import BugzillaAPI
313+from lp.bugs.interfaces.bugtask import BugTaskImportance, BugTaskStatus
314+from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus
315+from lp.bugs.scripts.checkwatches.bugwatchupdater import BugWatchUpdater
316+from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
317+from lp.bugs.scripts.checkwatches.core import CheckwatchesMaster
318+from lp.bugs.tests.externalbugtracker import TestExternalBugTracker
319+from lp.testing import TestCaseWithFactory
320+
321+
322+def make_bug_watch_updater(checkwatches_master, bug_watch,
323+ external_bugtracker, server_time=None):
324+ """Helper function to create a BugWatchUpdater instance."""
325+ if server_time is None:
326+ server_time = datetime.now()
327+
328+ remote_bug_updater = checkwatches_master.remote_bug_updater_factory(
329+ checkwatches_master, external_bugtracker, bug_watch.remotebug,
330+ [bug_watch.id], [], server_time)
331+ return BugWatchUpdater(
332+ remote_bug_updater, bug_watch,
333+ remote_bug_updater.external_bugtracker)
334+
335+
336+class BrokenCommentSyncingExternalBugTracker(TestExternalBugTracker):
337+ """An ExternalBugTracker that can't sync comments."""
338+
339+ import_comments_error_message = "Can't import comments, sorry."
340+ push_comments_error_message = "Can't push comments, sorry."
341+ back_link_error_message = "Can't back link, sorry."
342+
343+ def getCommentIds(self, remote_bug_id):
344+ raise Exception(self.import_comments_error_message)
345+
346+ def addRemoteComment(self, remote_bug_id, formatted_comment, message_id):
347+ raise Exception(self.push_comments_error_message)
348+
349+ def getLaunchpadBugId(self, remote_bug):
350+ raise Exception(self.back_link_error_message)
351+
352+
353+class LoggingBugWatchUpdater(BugWatchUpdater):
354+ """A BugWatchUpdater that logs what's going on."""
355+
356+ import_bug_comments_called = False
357+ push_bug_comments_called = False
358+ link_launchpad_bug_called = False
359+
360+ def importBugComments(self):
361+ self.import_bug_comments_called = True
362+
363+ def pushBugComments(self):
364+ self.push_bug_comments_called = True
365+
366+ def linkLaunchpadBug(self):
367+ self.link_launchpad_bug_called = True
368+
369+
370+class BugWatchUpdaterTestCase(TestCaseWithFactory):
371+ """Tests the functionality of the BugWatchUpdater class."""
372+
373+ layer = LaunchpadZopelessLayer
374+
375+ def setUp(self):
376+ super(BugWatchUpdaterTestCase, self).setUp()
377+ self.checkwatches_master = CheckwatchesMaster(transaction)
378+ self.bug_task = self.factory.makeBugTask()
379+ self.bug_watch = self.factory.makeBugWatch(bug_task=self.bug_task)
380+
381+ def _checkLastErrorAndMessage(self, expected_last_error,
382+ expected_message):
383+ """Check the latest activity and last_error_type for a BugWatch."""
384+ latest_activity = self.bug_watch.activity[0]
385+ self.assertEqual(expected_last_error, self.bug_watch.last_error_type)
386+ self.assertEqual(expected_last_error, latest_activity.result)
387+ self.assertEqual(expected_message, latest_activity.message)
388+
389+ def test_updateBugWatch(self):
390+ # Calling BugWatchUpdater.updateBugWatch() will update the
391+ # updater's current BugWatch.
392+ bug_watch_updater = make_bug_watch_updater(
393+ self.checkwatches_master, self.bug_watch,
394+ TestExternalBugTracker('http://example.com'))
395+
396+ bug_watch_updater.updateBugWatch(
397+ 'FIXED', BugTaskStatus.FIXRELEASED, 'LOW',
398+ BugTaskImportance.LOW, can_import_comments=False,
399+ can_push_comments=False, can_back_link=False)
400+
401+ self.assertEqual('FIXED', self.bug_watch.remotestatus)
402+ self.assertEqual(BugTaskStatus.FIXRELEASED, self.bug_task.status)
403+ self.assertEqual('LOW', self.bug_watch.remote_importance)
404+ self.assertEqual(BugTaskImportance.LOW, self.bug_task.importance)
405+ self.assertEqual(None, self.bug_watch.last_error_type)
406+
407+ latest_activity = self.bug_watch.activity[0]
408+ self.assertEqual(
409+ BugWatchActivityStatus.SYNC_SUCCEEDED, latest_activity.result)
410+
411+ def test_importBugComments_error_handling(self):
412+ # If an error occurs when importing bug comments, it will be
413+ # recorded as BugWatchActivityStatus.COMMENT_IMPORT_FAILED.
414+ external_bugtracker = BrokenCommentSyncingExternalBugTracker(
415+ 'http://example.com')
416+ bug_watch_updater = make_bug_watch_updater(
417+ self.checkwatches_master, self.bug_watch, external_bugtracker)
418+
419+ bug_watch_updater.updateBugWatch(
420+ 'FIXED', BugTaskStatus.FIXRELEASED, 'LOW',
421+ BugTaskImportance.LOW, can_import_comments=True,
422+ can_push_comments=False, can_back_link=False)
423+
424+ self._checkLastErrorAndMessage(
425+ BugWatchActivityStatus.COMMENT_IMPORT_FAILED,
426+ external_bugtracker.import_comments_error_message)
427+
428+ def test_pushBugComments_error_handling(self):
429+ # If an error occurs when pushing bug comments, it will be
430+ # recorded as BugWatchActivityStatus.COMMENT_IMPORT_FAILED.
431+ external_bugtracker = BrokenCommentSyncingExternalBugTracker(
432+ 'http://example.com')
433+ bug_watch_updater = make_bug_watch_updater(
434+ self.checkwatches_master, self.bug_watch, external_bugtracker)
435+
436+ self.factory.makeBugComment(
437+ bug=self.bug_task.bug, bug_watch=self.bug_watch)
438+
439+ bug_watch_updater.updateBugWatch(
440+ 'FIXED', BugTaskStatus.FIXRELEASED, 'LOW',
441+ BugTaskImportance.LOW, can_import_comments=False,
442+ can_push_comments=True, can_back_link=False)
443+
444+ self._checkLastErrorAndMessage(
445+ BugWatchActivityStatus.COMMENT_PUSH_FAILED,
446+ external_bugtracker.push_comments_error_message)
447+
448+ def test_linkLaunchpadBug_error_handling(self):
449+ # If an error occurs when linking back to a remote bug, it will
450+ # be recorded as BugWatchActivityStatus.BACKLINK_FAILED.
451+ external_bugtracker = BrokenCommentSyncingExternalBugTracker(
452+ 'http://example.com')
453+ bug_watch_updater = make_bug_watch_updater(
454+ self.checkwatches_master, self.bug_watch, external_bugtracker)
455+
456+ bug_watch_updater.updateBugWatch(
457+ 'FIXED', BugTaskStatus.FIXRELEASED, 'LOW',
458+ BugTaskImportance.LOW, can_import_comments=False,
459+ can_push_comments=False, can_back_link=True)
460+
461+ self._checkLastErrorAndMessage(
462+ BugWatchActivityStatus.BACKLINK_FAILED,
463+ external_bugtracker.back_link_error_message)
464+
465+ def test_comment_bools_inherited(self):
466+ # BugWatchUpdater.updateBugWatches() doesn't have to be passed
467+ # values for can_import_comments, can_push_comments and
468+ # can_back_link. Instead, these can be taken from the parent
469+ # object passed to it upon instantiation, usually a
470+ # RemoteBugUpdater.
471+ # XXX 2010-05-11 gmb bug=578714:
472+ # This test can be removed when bug 578714 is fixed.
473+ remote_bug_updater = RemoteBugUpdater(
474+ self.checkwatches_master,
475+ BugzillaAPI("http://example.com"),
476+ self.bug_watch.remotebug, [self.bug_watch.id],
477+ [], datetime.now())
478+ bug_watch_updater = LoggingBugWatchUpdater(
479+ remote_bug_updater, self.bug_watch,
480+ remote_bug_updater.external_bugtracker)
481+
482+ # If all the can_* properties of remote_bug_updater are True,
483+ # bug_watch_updater will attempt to import, push and backlink.
484+ self.assertTrue(remote_bug_updater.can_import_comments)
485+ self.assertTrue(remote_bug_updater.can_push_comments)
486+ self.assertTrue(remote_bug_updater.can_back_link)
487+ bug_watch_updater.updateBugWatch(
488+ 'FIXED', BugTaskStatus.FIXRELEASED, 'LOW', BugTaskImportance.LOW)
489+
490+ self.assertTrue(bug_watch_updater.import_bug_comments_called)
491+ self.assertTrue(bug_watch_updater.push_bug_comments_called)
492+ self.assertTrue(bug_watch_updater.link_launchpad_bug_called)
493+
494+ # Otherwise, bug_watch_updater won't attempt those functions
495+ # whose can_* properties are False.
496+ remote_bug_updater.can_import_comments = False
497+ remote_bug_updater.can_push_comments = False
498+ remote_bug_updater.can_back_link = False
499+ bug_watch_updater = LoggingBugWatchUpdater(
500+ remote_bug_updater, self.bug_watch,
501+ remote_bug_updater.external_bugtracker)
502+ bug_watch_updater.updateBugWatch(
503+ 'FIXED', BugTaskStatus.FIXRELEASED, 'LOW', BugTaskImportance.LOW)
504+
505+ self.assertFalse(bug_watch_updater.import_bug_comments_called)
506+ self.assertFalse(bug_watch_updater.push_bug_comments_called)
507+ self.assertFalse(bug_watch_updater.link_launchpad_bug_called)
508+
509+
510+def test_suite():
511+ return unittest.TestLoader().loadTestsFromName(__name__)
512
513=== modified file 'lib/lp/testing/factory.py'
514--- lib/lp/testing/factory.py 2010-05-08 12:30:49 +0000
515+++ lib/lp/testing/factory.py 2010-05-14 14:06:33 +0000
516@@ -1169,7 +1169,7 @@
517 base_url, owner, bugtrackertype)
518
519 def makeBugWatch(self, remote_bug=None, bugtracker=None, bug=None,
520- owner=None):
521+ owner=None, bug_task=None):
522 """Make a new bug watch."""
523 if remote_bug is None:
524 remote_bug = self.getUniqueInteger()
525@@ -1177,7 +1177,14 @@
526 if bugtracker is None:
527 bugtracker = self.makeBugTracker()
528
529- if bug is None:
530+ if bug_task is not None:
531+ # If someone passes a value for bug *and* a value for
532+ # bug_task then the bug value will get clobbered, but that
533+ # doesn't matter since the bug should be the one that the
534+ # bug task belongs to anyway (unless they're having a crazy
535+ # moment, in which case we're saving them from themselves).
536+ bug = bug_task.bug
537+ elif bug is None:
538 bug = self.makeBug()
539
540 if owner is None:
541@@ -1185,6 +1192,8 @@
542
543 bug_watch = getUtility(IBugWatchSet).createBugWatch(
544 bug, owner, bugtracker, str(remote_bug))
545+ if bug_task is not None:
546+ bug_task.bugwatch = bug_watch
547
548 # You need to be an admin to set next_check on a BugWatch.
549 def set_next_check(bug_watch):
550@@ -1194,7 +1203,8 @@
551 run_with_login(person, set_next_check, bug_watch)
552 return bug_watch
553
554- def makeBugComment(self, bug=None, owner=None, subject=None, body=None):
555+ def makeBugComment(self, bug=None, owner=None, subject=None, body=None,
556+ bug_watch=None):
557 """Create and return a new bug comment.
558
559 :param bug: An `IBug` or a bug ID or name, or None, in which
560@@ -1205,6 +1215,8 @@
561 case a new message will be generated.
562 :param body: An `IMessage` or a string, or None, in which
563 case a new message will be generated.
564+ :param bug_watch: An `IBugWatch`, which will be used to set the
565+ new comment's bugwatch attribute.
566 :return: An `IBugMessage`.
567 """
568 if bug is None:
569@@ -1218,7 +1230,7 @@
570 if body is None:
571 body = self.getUniqueString()
572 return bug.newMessage(owner=owner, subject=subject,
573- content=body, parent=None, bugwatch=None,
574+ content=body, parent=None, bugwatch=bug_watch,
575 remote_comment_id=None)
576
577 def makeBugAttachment(self, bug=None, owner=None, data=None,