Merge lp:~wgrant/launchpad/checkwatches-will-you-be-quiet-please into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 12443
Proposed branch: lp:~wgrant/launchpad/checkwatches-will-you-be-quiet-please
Merge into: lp:launchpad
Diff against target: 471 lines (+153/-112)
8 files modified
lib/lp/bugs/doc/bug-watch-activity.txt (+3/-3)
lib/lp/bugs/doc/externalbugtracker-bugzilla-oddities.txt (+2/-2)
lib/lp/bugs/doc/externalbugtracker-bugzilla.txt (+6/-6)
lib/lp/bugs/doc/externalbugtracker-mantis.txt (+2/-2)
lib/lp/bugs/doc/externalbugtracker.txt (+2/-42)
lib/lp/bugs/externalbugtracker/base.py (+11/-4)
lib/lp/bugs/scripts/checkwatches/remotebugupdater.py (+51/-43)
lib/lp/bugs/scripts/checkwatches/tests/test_remotebugupdater.py (+76/-10)
To merge this branch: bzr merge lp:~wgrant/launchpad/checkwatches-will-you-be-quiet-please
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+50892@code.launchpad.net

Commit message

[r=lifeless][bug=592345,719288][incr] Several checkwatches OOPSes have been demoted to INFO log entries.

Description of the change

This branch suppresses lots of checkwatches OOPSes. In particular, InvalidBugId, BugNotFound, PrivateRemoteBug, UnknownRemoteStatus, and UnknownRemoteImportance are now logged at INFO instead of as OOPSes and at WARNING.

The first three show up already in the UI as BugWatchActivity entries. The OOPS had no further useful information. There is some argument that UnknownRemote(Status|Importance) deserve OOPSes, but they will be easily greppable from logs, the Unknown value will be obvious to anyone looking at an affected bug, and they only tend to get fixed when users complain anyway.

InvalidBugId, BugNotFound, and PrivateRemoteBug were easily fixed by changing the logging in their exception handler.

RemoteBugUpdater had a wrapper around status conversion which logged the exception nicely, but there was no similar thing for importance. I turned the OOPS into an INFO log entry, and generalised it to work for importance too. _convertRemoteStatus was tested in a doctest where the log level was WARNING, making it hard to check that it logged correctly. So I replaced it with unit tests and extended them to _convertRemoteImportance.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

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

can be deleted

you might find a matcher to call _convertRemote... would be more pithy than the assertions you have at the moment.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/doc/bug-watch-activity.txt'
2--- lib/lp/bugs/doc/bug-watch-activity.txt 2010-12-20 03:21:03 +0000
3+++ lib/lp/bugs/doc/bug-watch-activity.txt 2011-02-23 22:12:50 +0000
4@@ -107,11 +107,11 @@
5 If the remote bug tracker breaks during an update the error will be
6 recorded in the activity entry for that update.
7
8- >>> from lp.bugs.externalbugtracker.base import BugNotFound
9+ >>> from lp.bugs.externalbugtracker.base import UnparsableBugData
10 >>> from lp.bugs.tests.externalbugtracker import (
11 ... TestBrokenExternalBugTracker)
12 >>> broken_bugtracker = TestBrokenExternalBugTracker('http://example.com')
13- >>> broken_bugtracker.get_remote_status_error = BugNotFound
14+ >>> broken_bugtracker.get_remote_status_error = UnparsableBugData
15
16 >>> updater.updateBugWatches(broken_bugtracker, [bug_watch])
17
18@@ -125,7 +125,7 @@
19
20 >>> most_recent_activity = bug_watch.activity.first()
21 >>> print most_recent_activity.result.title
22- Bug Not Found
23+ Unparsable Bug
24
25 The OOPS ID for the error will also have been recorded.
26
27
28=== modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla-oddities.txt'
29--- lib/lp/bugs/doc/externalbugtracker-bugzilla-oddities.txt 2011-01-19 00:10:48 +0000
30+++ lib/lp/bugs/doc/externalbugtracker-bugzilla-oddities.txt 2011-02-23 22:12:50 +0000
31@@ -34,8 +34,8 @@
32 >>> bug_watch_updater.updateBugWatches(
33 ... issuezilla, mozilla_bugzilla.watches)
34 INFO:...:Updating 4 watches for 3 bugs on https://bugzilla.mozilla.org
35- WARNING:...:Didn't find bug u'42' on https://bugzilla.mozilla.org
36- (local bugs: 1, 2). (OOPS-...)
37+ INFO:...:Didn't find bug u'42' on https://bugzilla.mozilla.org
38+ (local bugs: 1, 2).
39
40 >>> for bug_watch in mozilla_bugzilla.watches:
41 ... print "%s: %s %s" % (bug_watch.remotebug,
42
43=== modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla.txt'
44--- lib/lp/bugs/doc/externalbugtracker-bugzilla.txt 2011-02-23 06:44:36 +0000
45+++ lib/lp/bugs/doc/externalbugtracker-bugzilla.txt 2011-02-23 22:12:50 +0000
46@@ -443,8 +443,8 @@
47 >>> bug_watch_updater.updateBugWatches(
48 ... external_bugzilla, gnome_bugzilla.watches)
49 INFO:...:Updating 2 watches for 2 bugs on http://bugzilla.gnome.org/bugs
50- WARNING:...Didn't find bug u'304070' on
51- http://bugzilla.gnome.org/bugs (local bugs: 15). (OOPS-...)
52+ INFO:...Didn't find bug u'304070' on
53+ http://bugzilla.gnome.org/bugs (local bugs: 15).
54
55 >>> for bug_watch in gnome_bugzilla.watches:
56 ... print "%s: %s %s" % (bug_watch.remotebug,
57@@ -498,8 +498,8 @@
58 CALLED _postPage()
59 CALLED _postPage()
60 CALLED _postPage()
61- WARNING:...:Didn't find bug u'304070' on
62- http://bugzilla.gnome.org/bugs (local bugs: 15). (OOPS-...)
63+ INFO:...:Didn't find bug u'304070' on
64+ http://bugzilla.gnome.org/bugs (local bugs: 15).
65
66 >>> remote_statuses = dict(
67 ... [(int(bug_watch.remotebug), bug_watch.remotestatus)
68@@ -548,8 +548,8 @@
69 ... external_bugzilla, gnome_bugzilla.watches)
70 INFO:...:Updating 207 watches for 207 bugs...
71 CALLED _postPage()
72- WARNING:...:Didn't find bug u'304070' on
73- http://bugzilla.gnome.org/bugs (local bugs: 15). (OOPS-...)
74+ INFO:...:Didn't find bug u'304070' on
75+ http://bugzilla.gnome.org/bugs (local bugs: 15).
76
77 >>> remote_statuses = dict(
78 ... [(int(bug_watch.remotebug), bug_watch.remotestatus)
79
80=== modified file 'lib/lp/bugs/doc/externalbugtracker-mantis.txt'
81--- lib/lp/bugs/doc/externalbugtracker-mantis.txt 2010-10-18 22:24:59 +0000
82+++ lib/lp/bugs/doc/externalbugtracker-mantis.txt 2011-02-23 22:12:50 +0000
83@@ -142,8 +142,8 @@
84 CALLED _getPage(u'view.php?id=1738')
85 CALLED _getPage(u'view.php?id=1748')
86 CALLED _getPage(u'view.php?id=1798')
87- WARNING:...:Didn't find bug u'1798' on http://bugs.some.where
88- (local bugs: 10). (OOPS-...)
89+ INFO:...:Didn't find bug u'1798' on http://bugs.some.where
90+ (local bugs: 10).
91
92 >>> remote_statuses = dict(
93 ... (int(bug_watch.remotebug), bug_watch.remotestatus)
94
95=== modified file 'lib/lp/bugs/doc/externalbugtracker.txt'
96--- lib/lp/bugs/doc/externalbugtracker.txt 2011-01-19 00:10:48 +0000
97+++ lib/lp/bugs/doc/externalbugtracker.txt 2011-02-23 22:12:50 +0000
98@@ -643,44 +643,6 @@
99 []
100
101
102-=== Converting statuses ===
103-
104-Once it has retrieved the bugs from the remote server, RemoteBugUpdater
105-attempts to convert their statuses into Launchpad BugTaskStatuses by
106-calling the convertRemoteStatus() method on the ExternalBugTracker via
107-its own _convertRemoteStatus() method.
108-
109-ExternalBugTracker.convertRemoteStatus() will either return a
110-BugTaskStatus or will raise an UnknownRemoteStatusError.
111-
112- >>> from lp.bugs.interfaces.bugtask import BugTaskStatus
113- >>> from lp.bugs.externalbugtracker import (
114- ... UnknownRemoteStatusError)
115- >>> class StatusConvertingExternalBugTracker(TestExternalBugTracker):
116- ...
117- ... def convertRemoteStatus(self, remote_status):
118- ... if remote_status == 'new':
119- ... return BugTaskStatus.NEW
120- ... else:
121- ... raise UnknownRemoteStatusError(remote_status)
122-
123-RemoteBugUpdater._convertRemoteStatus() will handle these errors and will
124-return BugTaskStatus.UNKNOWN when they occur. It will also log a
125-warning.
126-
127- >>> remote_bug_updater = bug_watch_updater.remote_bug_updater_factory(
128- ... bug_watch_updater, StatusConvertingExternalBugTracker(),
129- ... '1', [1], [], utc_now)
130- >>> status = remote_bug_updater._convertRemoteStatus('new')
131- >>> print status.title
132- New
133-
134- >>> status = remote_bug_updater._convertRemoteStatus('spam')
135- WARNING...Unknown remote status 'spam'. (OOPS-...)
136- >>> print status.title
137- Unknown
138-
139-
140 == Configuration Options ==
141
142 All ExternalBugTrackers have a batch_query_threshold attribute which is
143@@ -776,8 +738,7 @@
144 >>> error_utility = CheckWatchesErrorUtility()
145
146 >>> external_bugtracker.initialize_remote_bugdb_error = None
147- >>> for error in [BugNotFound, InvalidBugId, UnparsableBugData,
148- ... Exception]:
149+ >>> for error in [UnparsableBugData, Exception]:
150 ... example_bugwatch.lastchecked = None
151 ... external_bugtracker.get_remote_status_error = error
152 ... bug_watch_updater.updateBugWatches(
153@@ -787,8 +748,6 @@
154 ... example_bugwatch.last_error_type.title,
155 ... example_bugwatch.lastchecked is not None,
156 ... oops.id, oops.url)
157- Bug Not Found: True (OOPS-...; http://bugs.example.com/show_bug.cgi?id=1)
158- Invalid Bug ID: True (OOPS-...; http://bugs.example.com/show_bug.cgi?id=1)
159 Unparsable Bug: True (OOPS-...; http://bugs.example.com/show_bug.cgi?id=1)
160 Unknown: True (OOPS-...; http://bugs.example.com/show_bug.cgi?id=1)
161
162@@ -808,6 +767,7 @@
163
164 First, we need a tree to document.
165
166+ >>> from lp.bugs.interfaces.bugtask import BugTaskStatus
167 >>> tree = LookupTree(
168 ... ('ASSIGNED', 'STARTED', BugTaskStatus.INPROGRESS),
169 ... ('NEEDINFO', 'WAITING', 'SUSPENDED', BugTaskStatus.INCOMPLETE),
170
171=== modified file 'lib/lp/bugs/externalbugtracker/base.py'
172--- lib/lp/bugs/externalbugtracker/base.py 2011-01-23 09:34:35 +0000
173+++ lib/lp/bugs/externalbugtracker/base.py 2011-02-23 22:12:50 +0000
174@@ -18,6 +18,7 @@
175 'UnknownBugTrackerTypeError',
176 'UnknownRemoteImportanceError',
177 'UnknownRemoteStatusError',
178+ 'UnknownRemoteValueError',
179 'UnparsableBugData',
180 'UnparsableBugTrackerVersion',
181 'UnsupportedBugTrackerVersion',
182@@ -122,12 +123,18 @@
183 """The bug was not found in the external bug tracker."""
184
185
186-class UnknownRemoteImportanceError(BugWatchUpdateWarning):
187+class UnknownRemoteValueError(BugWatchUpdateWarning):
188+ """A matching Launchpad value could not be found for the remote value."""
189+
190+
191+class UnknownRemoteImportanceError(UnknownRemoteValueError):
192 """The remote bug's importance isn't mapped to a `BugTaskImportance`."""
193-
194-
195-class UnknownRemoteStatusError(BugWatchUpdateWarning):
196+ field_name = 'importance'
197+
198+
199+class UnknownRemoteStatusError(UnknownRemoteValueError):
200 """The remote bug's status isn't mapped to a `BugTaskStatus`."""
201+ field_name = 'status'
202
203
204 class PrivateRemoteBug(BugWatchUpdateWarning):
205
206=== modified file 'lib/lp/bugs/scripts/checkwatches/remotebugupdater.py'
207--- lib/lp/bugs/scripts/checkwatches/remotebugupdater.py 2011-01-27 15:23:53 +0000
208+++ lib/lp/bugs/scripts/checkwatches/remotebugupdater.py 2011-02-23 22:12:50 +0000
209@@ -17,9 +17,12 @@
210 BugNotFound,
211 InvalidBugId,
212 PrivateRemoteBug,
213- UnknownRemoteStatusError,
214- )
215-from lp.bugs.interfaces.bugtask import BugTaskStatus
216+ UnknownRemoteValueError,
217+ )
218+from lp.bugs.interfaces.bugtask import (
219+ BugTaskImportance,
220+ BugTaskStatus,
221+ )
222 from lp.bugs.interfaces.bugwatch import (
223 BugWatchActivityStatus,
224 IBugWatchSet,
225@@ -28,6 +31,7 @@
226 ISupportsBackLinking,
227 ISupportsCommentImport,
228 ISupportsCommentPushing,
229+ UNKNOWN_REMOTE_IMPORTANCE,
230 UNKNOWN_REMOTE_STATUS,
231 )
232 from lp.bugs.scripts.checkwatches.base import (
233@@ -141,34 +145,24 @@
234 new_remote_importance = (
235 self.external_bugtracker.getRemoteImportance(
236 self.remote_bug))
237- new_malone_importance = (
238- self.external_bugtracker.convertRemoteImportance(
239- new_remote_importance))
240+ new_malone_importance = self._convertRemoteImportance(
241+ new_remote_importance)
242 except (InvalidBugId, BugNotFound, PrivateRemoteBug), ex:
243 error = get_bugwatcherrortype_for_error(ex)
244 message = self.error_type_messages.get(
245 error, self.error_type_message_default)
246- oops_id = self.warning(
247+ self.logger.info(
248 message % {
249 'bug_id': self.remote_bug,
250 'base_url': self.external_bugtracker.baseurl,
251 'local_ids': local_ids,
252- },
253- properties=[
254- ('URL', remote_bug_url),
255- ('bug_id', self.remote_bug),
256- ('local_ids', local_ids),
257- ] + get_remote_system_oops_properties(
258- self.external_bugtracker),
259- info=sys.exc_info())
260-
261+ })
262 # Set the error and activity on all bug watches
263 with self.transaction:
264 getUtility(IBugWatchSet).bulkSetError(
265 bug_watches, error)
266 getUtility(IBugWatchSet).bulkAddActivity(
267- bug_watches, result=error, oops_id=oops_id)
268-
269+ bug_watches, result=error)
270 else:
271 # Assuming nothing's gone wrong, we can now deal with
272 # each BugWatch in turn.
273@@ -178,7 +172,6 @@
274 bug_watch_updater.updateBugWatch(
275 new_remote_status, new_malone_status,
276 new_remote_importance, new_malone_importance)
277-
278 except Exception, error:
279 # Send the error to the log.
280 oops_id = self.error(
281@@ -201,31 +194,46 @@
282 bug_watches, result=error_type, oops_id=oops_id)
283
284 def _convertRemoteStatus(self, remote_status):
285- """Convert a remote bug status to a Launchpad status and return it.
286-
287- :param remote_status: The remote status to be converted into a
288- Launchpad status.
289-
290- If the remote status cannot be mapped to a Launchpad status,
291- BugTaskStatus.UNKNOWN will be returned and a warning will be
292- logged.
293+ """Convert a remote status to a Launchpad one and return it."""
294+ return self._convertRemoteValue(
295+ self.external_bugtracker.convertRemoteStatus,
296+ UNKNOWN_REMOTE_STATUS, BugTaskStatus.UNKNOWN, remote_status)
297+
298+ def _convertRemoteImportance(self, remote_importance):
299+ """Convert a remote importance to a Launchpad one and return it."""
300+ return self._convertRemoteValue(
301+ self.external_bugtracker.convertRemoteImportance,
302+ UNKNOWN_REMOTE_IMPORTANCE, BugTaskImportance.UNKNOWN,
303+ remote_importance)
304+
305+ def _convertRemoteValue(self, conversion_method, remote_unknown,
306+ launchpad_unknown, remote_value):
307+ """Convert a remote bug value to a Launchpad value and return it.
308+
309+ :param conversion_method: A method returning the Launchpad value
310+ corresponding to the given remote value.
311+ :param remote_unknown: The remote value which indicates an unknown
312+ value.
313+ :param launchpad_unknown: The Launchpad value which indicates an
314+ unknown value.
315+ :param remote_value: The remote value to be converted.
316+
317+ If the remote value cannot be mapped to a Launchpad value,
318+ launchpad_unknown will be returned and a warning will be logged.
319 """
320- # We don't bother trying to convert UNKNOWN_REMOTE_STATUS.
321- if remote_status == UNKNOWN_REMOTE_STATUS:
322- return BugTaskStatus.UNKNOWN
323+ # We don't bother trying to convert remote_unknown.
324+ if remote_value == remote_unknown:
325+ return launchpad_unknown
326
327 try:
328- launchpad_status = self.external_bugtracker.convertRemoteStatus(
329- remote_status)
330- except UnknownRemoteStatusError:
331- # We log the warning, since we need to know about statuses
332+ launchpad_value = conversion_method(remote_value)
333+ except UnknownRemoteValueError, e:
334+ # We log the warning, since we need to know about values
335 # that we don't handle correctly.
336- self.warning(
337- "Unknown remote status '%s'." % remote_status,
338- get_remote_system_oops_properties(
339- self.external_bugtracker),
340- sys.exc_info())
341-
342- launchpad_status = BugTaskStatus.UNKNOWN
343-
344- return launchpad_status
345+ self.logger.info(
346+ "Unknown remote %s '%s' for bug %r on %s." %
347+ (e.field_name, remote_value, self.remote_bug,
348+ self.bug_tracker_url))
349+ launchpad_value = launchpad_unknown
350+
351+ return launchpad_value
352
353=== modified file 'lib/lp/bugs/scripts/checkwatches/tests/test_remotebugupdater.py'
354--- lib/lp/bugs/scripts/checkwatches/tests/test_remotebugupdater.py 2010-10-04 19:50:45 +0000
355+++ lib/lp/bugs/scripts/checkwatches/tests/test_remotebugupdater.py 2011-02-23 22:12:50 +0000
356@@ -10,16 +10,54 @@
357 import transaction
358
359 from canonical.testing.layers import LaunchpadZopelessLayer
360+from lp.bugs.externalbugtracker.base import (
361+ UnknownRemoteImportanceError,
362+ UnknownRemoteStatusError,
363+ )
364 from lp.bugs.externalbugtracker.bugzilla import Bugzilla
365+from lp.bugs.interfaces.bugtask import (
366+ BugTaskImportance,
367+ BugTaskStatus,
368+ )
369 from lp.bugs.scripts.checkwatches.core import CheckwatchesMaster
370 from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
371+from lp.bugs.tests.externalbugtracker import TestExternalBugTracker
372+from lp.services.log.logger import BufferLogger
373 from lp.testing import TestCaseWithFactory
374
375
376+class StatusConvertingExternalBugTracker(TestExternalBugTracker):
377+
378+ def convertRemoteStatus(self, remote_status):
379+ if remote_status == 'new':
380+ return BugTaskStatus.NEW
381+ else:
382+ raise UnknownRemoteStatusError(remote_status)
383+
384+
385+class ImportanceConvertingExternalBugTracker(TestExternalBugTracker):
386+
387+ def convertRemoteImportance(self, remote_importance):
388+ if remote_importance == 'low':
389+ return BugTaskImportance.LOW
390+ else:
391+ raise UnknownRemoteImportanceError(remote_importance)
392+
393+
394 class RemoteBugUpdaterTestCase(TestCaseWithFactory):
395
396 layer = LaunchpadZopelessLayer
397
398+ def makeUpdater(self, remote_system=None, remote_bug_id=None,
399+ bug_watch_ids=None, unmodified_remote_ids=None,
400+ logger=None):
401+ checkwatches_master = CheckwatchesMaster(transaction)
402+ if logger is not None:
403+ checkwatches_master.logger = logger
404+ return checkwatches_master.remote_bug_updater_factory(
405+ checkwatches_master, remote_system, remote_bug_id,
406+ bug_watch_ids, unmodified_remote_ids, None)
407+
408 def test_create(self):
409 # CheckwatchesMaster.remote_bug_updater_factory points to the
410 # RemoteBugUpdater class, so it can be used to create
411@@ -28,12 +66,9 @@
412 remote_bug_id = '42'
413 bug_watch_ids = [1, 2]
414 unmodified_remote_ids = ['76']
415-
416- checkwatches_master = CheckwatchesMaster(transaction)
417- updater = checkwatches_master.remote_bug_updater_factory(
418- checkwatches_master, remote_system, remote_bug_id,
419- bug_watch_ids, unmodified_remote_ids, None)
420-
421+ updater = self.makeUpdater(
422+ remote_system, remote_bug_id, bug_watch_ids,
423+ unmodified_remote_ids)
424 self.assertTrue(
425 isinstance(updater, RemoteBugUpdater),
426 "updater should be an instance of RemoteBugUpdater.")
427@@ -51,9 +86,40 @@
428 self.assertEqual(
429 unmodified_remote_ids, updater.unmodified_remote_ids,
430 "RemoteBugUpdater's unmodified_remote_ids should be '%s', "
431- "were '%s'" %
432+ "were '%s'" %
433 (unmodified_remote_ids, updater.unmodified_remote_ids))
434
435-
436-def test_suite():
437- return unittest.TestLoader().loadTestsFromName(__name__)
438+ def test_convertRemoteStatus(self):
439+ updater = self.makeUpdater(
440+ remote_system=StatusConvertingExternalBugTracker())
441+ self.assertEqual(
442+ BugTaskStatus.NEW, updater._convertRemoteStatus('new'))
443+
444+ def test_convertRemoteStatus_logs_unknown_values(self):
445+ updater = self.makeUpdater(
446+ remote_system=StatusConvertingExternalBugTracker(),
447+ remote_bug_id=42,
448+ logger=BufferLogger())
449+ self.assertEqual(
450+ BugTaskStatus.UNKNOWN, updater._convertRemoteStatus('spam'))
451+ self.assertEqual(
452+ "INFO Unknown remote status 'spam' for bug 42 on "
453+ "http://example.com.\n", updater.logger.getLogBuffer())
454+
455+ def test_convertRemoteImportance(self):
456+ updater = self.makeUpdater(
457+ remote_system=ImportanceConvertingExternalBugTracker())
458+ self.assertEqual(
459+ BugTaskImportance.LOW, updater._convertRemoteImportance('low'))
460+
461+ def test_convertRemoteImportance_logs_unknown_values(self):
462+ updater = self.makeUpdater(
463+ remote_system=ImportanceConvertingExternalBugTracker(),
464+ remote_bug_id=42,
465+ logger=BufferLogger())
466+ self.assertEqual(
467+ BugTaskImportance.UNKNOWN,
468+ updater._convertRemoteImportance('spam'))
469+ self.assertEqual(
470+ "INFO Unknown remote importance 'spam' for bug 42 on "
471+ "http://example.com.\n", updater.logger.getLogBuffer())