Merge lp:~allenap/launchpad/checkwatches-stuff into lp:launchpad
- checkwatches-stuff
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Graham Binns |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~allenap/launchpad/checkwatches-stuff |
Merge into: | lp:launchpad |
Diff against target: |
972 lines (+280/-237) 9 files modified
lib/canonical/launchpad/scripts/ftests/test_checkwatches.py (+0/-93) lib/lp/bugs/doc/externalbugtracker.txt (+27/-6) lib/lp/bugs/interfaces/bugwatch.py (+13/-0) lib/lp/bugs/interfaces/externalbugtracker.py (+31/-7) lib/lp/bugs/model/bugwatch.py (+9/-1) lib/lp/bugs/scripts/checkwatches.py (+39/-48) lib/lp/bugs/scripts/tests/test_bugimport.py (+10/-7) lib/lp/bugs/scripts/tests/test_checkwatches.py (+82/-26) lib/lp/bugs/tests/test_bugwatch.py (+69/-49) |
To merge this branch: | bzr merge lp:~allenap/launchpad/checkwatches-stuff |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | code | Approve | |
Eleanor Berger | Pending | ||
Review via email: mp+16495@code.launchpad.net |
Commit message
Commit after every successful bug watch update.
Description of the change
Gavin Panella (allenap) wrote : | # |
Graham Binns (gmb) wrote : | # |
Hi Gavin,
I'm marking this as Needs Fixing for now because there's some
conflicty-oddness going on. Apart from that, though, and a naming issue,
I'm happy with this. Just let me know when the problems I've mentioned
are fixed.
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -414,26 +414,6 @@
> """Return the bug watch with id `bug_watch_id`."""
> return getUtility(
>
> - def _getBugWatchesB
> - """Returns a dictionary of bug watches mapped to remote bugs.
> -
> - For each bug watch id fetches the corresponding bug watch and
> - appends it to a list of bug watches pointing to one remote
> - bug - the key of the returned mapping.
> - """
> - bug_watches_
> - for bug_watch_id in bug_watch_ids:
> - bug_watch = self._getBugWat
> - remote_bug = bug_watch.remotebug
> - # There can be multiple bug watches pointing to the same
> - # remote bug; because of that, we need to store lists of bug
> - # watches related to the remote bug, and later update the
> - # status of each one of them.
> - if remote_bug not in bug_watches_
> - bug_watches_
> - bug_watches_
> - return bug_watches_
> -
> def _getExternalBug
> """Return an `ExternalBugTra
> remotesystem = externalbugtrac
> @@ -686,6 +666,17 @@
> 'unmodified_
> }
>
> + def getBugWatchesFo
This should either start with an underscore or (if you have the time and
the pain threshold) you should remove all underscores from method names
in BugWatchUpdater, because they aren't relevant. However, that's a big
change and whilst I'd like it I wouldn't advise it.
> + """Return a list of bug watches for the given remote bug.
> +
> + The returned watches will all be members of `bug_watch_ids`.
> +
> + This method exists primarily to be overridden during testing.
> + """
> + return list(
> + getUtility(
> + remote_bug_id, bug_watch_ids))
> +
> # XXX gmb 2008-11-07 [bug=295319]
> # This method is 186 lines long. It needs to be shorter.
> def updateBugWatche
> @@ -752,10 +743,6 @@
> self.txn.commit()
> raise
>
> - self.txn.begin()
> - bug_watches_
> - bug_watch_ids)
> -
> # Whether we can import and / or push comments is determined on
> # a per-bugtracker-type level....
Gavin Panella (allenap) wrote : | # |
Hi Graham, thanks for the review. I've merged devel again, changed the method name (not doing all of them; too much pain for now), and have fixed another test which I broke.
Graham Binns (gmb) wrote : | # |
Approved pending the removal of the erroneous .moved file.
Preview Diff
1 | === removed file 'lib/canonical/launchpad/scripts/ftests/test_checkwatches.py' |
2 | --- lib/canonical/launchpad/scripts/ftests/test_checkwatches.py 2009-06-25 05:30:52 +0000 |
3 | +++ lib/canonical/launchpad/scripts/ftests/test_checkwatches.py 1970-01-01 00:00:00 +0000 |
4 | @@ -1,93 +0,0 @@ |
5 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
6 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
7 | - |
8 | -"""Tests for the checkwatches remote bug synchronisation code.""" |
9 | - |
10 | -__metaclass__ = type |
11 | -__all__ = [] |
12 | - |
13 | -import unittest |
14 | - |
15 | -from zope.component import getUtility |
16 | - |
17 | -from canonical.config import config |
18 | -from canonical.database.sqlbase import commit |
19 | -from canonical.launchpad.ftests import login |
20 | -from lp.bugs.tests.externalbugtracker import new_bugtracker |
21 | -from canonical.launchpad.interfaces import (BugTaskStatus, BugTrackerType, |
22 | - IBugSet, IBugTaskSet, ILaunchpadCelebrities, IPersonSet, |
23 | - IProductSet, IQuestionSet) |
24 | -from canonical.testing import LaunchpadZopelessLayer |
25 | - |
26 | - |
27 | -class TestCheckwatches(unittest.TestCase): |
28 | - """Tests for the bugwatch updating system.""" |
29 | - layer = LaunchpadZopelessLayer |
30 | - |
31 | - def setUp(self): |
32 | - """Set up bugs, watches and questions to test with.""" |
33 | - super(TestCheckwatches, self).setUp() |
34 | - |
35 | - # For test_can_update_bug_with_questions we need a bug that has |
36 | - # a question linked to it. |
37 | - bug_with_question = getUtility(IBugSet).get(10) |
38 | - question = getUtility(IQuestionSet).get(1) |
39 | - |
40 | - # XXX gmb 2007-12-11 bug 175545: |
41 | - # We shouldn't have to login() here, but since |
42 | - # database.buglinktarget.BugLinkTargetMixin.linkBug() |
43 | - # doesn't accept a user parameter, instead depending on the |
44 | - # currently logged in user, we get an exception if we don't. |
45 | - login('test@canonical.com') |
46 | - question.linkBug(bug_with_question) |
47 | - |
48 | - # We subscribe launchpad_developers to the question since this |
49 | - # indirectly subscribes foo.bar@canonical.com to it, too. We can |
50 | - # then use this to test the updating of a question with indirect |
51 | - # subscribers from a bug watch. |
52 | - question.subscribe( |
53 | - getUtility(ILaunchpadCelebrities).launchpad_developers) |
54 | - commit() |
55 | - |
56 | - # We now need to switch to the checkwatches DB user so that |
57 | - # we're testing with the correct set of permissions. |
58 | - self.layer.switchDbUser(config.checkwatches.dbuser) |
59 | - |
60 | - # For test_can_update_bug_with_questions we also need a bug |
61 | - # watch and by extension a bug tracker. |
62 | - sample_person = getUtility(IPersonSet).getByEmail( |
63 | - 'test@canonical.com') |
64 | - bugtracker = new_bugtracker(BugTrackerType.ROUNDUP) |
65 | - self.bugtask_with_question = getUtility(IBugTaskSet).createTask( |
66 | - bug_with_question, sample_person, |
67 | - product=getUtility(IProductSet).getByName('firefox')) |
68 | - self.bugwatch_with_question = bug_with_question.addWatch( |
69 | - bugtracker, '1', getUtility(ILaunchpadCelebrities).janitor) |
70 | - self.bugtask_with_question.bugwatch = self.bugwatch_with_question |
71 | - commit() |
72 | - |
73 | - def test_can_update_bug_with_questions(self): |
74 | - """Test whether bugs with linked questions can be updated. |
75 | - |
76 | - This will also test whether indirect subscribers of linked |
77 | - questions will be notified of the changes made when the bugwatch |
78 | - is updated. |
79 | - """ |
80 | - # We need to check that the bug task we created in setUp() is |
81 | - # still being referenced by our bug watch. |
82 | - self.assertEqual(self.bugwatch_with_question.bugtasks[0].id, |
83 | - self.bugtask_with_question.id) |
84 | - |
85 | - # We can now update the bug watch, which will in turn update the |
86 | - # bug task and the linked question. |
87 | - self.bugwatch_with_question.updateStatus('some status', |
88 | - BugTaskStatus.INPROGRESS) |
89 | - self.assertEqual(self.bugwatch_with_question.bugtasks[0].status, |
90 | - BugTaskStatus.INPROGRESS, |
91 | - "BugTask status is inconsistent. Expected %s but got %s" % |
92 | - (BugTaskStatus.INPROGRESS.title, |
93 | - self.bugtask_with_question.status.title)) |
94 | - |
95 | - |
96 | -def test_suite(): |
97 | - return unittest.TestLoader().loadTestsFromName(__name__) |
98 | |
99 | === modified file 'lib/lp/bugs/doc/externalbugtracker.txt' |
100 | --- lib/lp/bugs/doc/externalbugtracker.txt 2009-11-28 08:31:15 +0000 |
101 | +++ lib/lp/bugs/doc/externalbugtracker.txt 2010-01-05 16:11:20 +0000 |
102 | @@ -42,7 +42,6 @@ |
103 | ... InitializingExternalBugTracker(), []) |
104 | COMMIT |
105 | initializeRemoteBugDB() called: [] |
106 | - BEGIN |
107 | |
108 | |
109 | === Choosing another ExternalBugTracker instance === |
110 | @@ -318,7 +317,6 @@ |
111 | COMMIT |
112 | getCurrentDBTime() called |
113 | initializeRemoteBugDB() called: [] |
114 | - BEGIN |
115 | |
116 | If the difference between what we and the remote system think the time |
117 | is, an error is raised. |
118 | @@ -370,7 +368,6 @@ |
119 | >>> bug_watch_updater.updateBugWatches( |
120 | ... CorrectTimeExternalBugTracker(), [], now=utc_now) |
121 | COMMIT |
122 | - BEGIN |
123 | |
124 | If the timezone is known, the local time time should be returned, rather |
125 | than the UTC time. |
126 | @@ -383,7 +380,6 @@ |
127 | >>> bug_watch_updater.updateBugWatches( |
128 | ... LocalTimeExternalBugTracker(), [], now=utc_now) |
129 | COMMIT |
130 | - BEGIN |
131 | |
132 | If the remote server time is unknown, we will refuse to import any |
133 | comments from it. Bug watches will still be updated, but a warning is |
134 | @@ -399,7 +395,6 @@ |
135 | COMMIT |
136 | getCurrentDBTime() called |
137 | initializeRemoteBugDB() called: [] |
138 | - BEGIN |
139 | WARNING:...:Comment importing supported, but server time can't be |
140 | trusted. No comments will be imported. |
141 | |
142 | @@ -494,9 +489,16 @@ |
143 | initializeRemoteBugDB() called: [u'1', u'2', u'3', u'4'] |
144 | BEGIN |
145 | getRemoteStatus() called: u'1' |
146 | + COMMIT |
147 | + BEGIN |
148 | getRemoteStatus() called: u'2' |
149 | + COMMIT |
150 | + BEGIN |
151 | getRemoteStatus() called: u'3' |
152 | + COMMIT |
153 | + BEGIN |
154 | getRemoteStatus() called: u'4' |
155 | + COMMIT |
156 | |
157 | If the bug watches have the lastchecked attribute set, they will be |
158 | passed to getModifiedRemoteBugs(). Only the bugs that have been modified |
159 | @@ -530,7 +532,12 @@ |
160 | initializeRemoteBugDB() called: [u'1', u'4'] |
161 | BEGIN |
162 | getRemoteStatus() called: u'1' |
163 | + COMMIT |
164 | + BEGIN |
165 | getRemoteStatus() called: u'4' |
166 | + COMMIT |
167 | + BEGIN |
168 | + BEGIN |
169 | |
170 | The bug watches that are deemed as not being modified are still marked |
171 | as being checked. |
172 | @@ -580,9 +587,16 @@ |
173 | initializeRemoteBugDB() called: [u'1', u'2', u'3', u'4'] |
174 | BEGIN |
175 | getRemoteStatus() called: u'1' |
176 | + COMMIT |
177 | + BEGIN |
178 | getRemoteStatus() called: u'2' |
179 | + COMMIT |
180 | + BEGIN |
181 | getRemoteStatus() called: u'3' |
182 | + COMMIT |
183 | + BEGIN |
184 | getRemoteStatus() called: u'4' |
185 | + COMMIT |
186 | |
187 | As mentioned earlier, getModifiedRemoteBugs() is only called if we can |
188 | get the current time of the remote system. If the time is unknown, we |
189 | @@ -600,9 +614,16 @@ |
190 | initializeRemoteBugDB() called: [u'1', u'2', u'3', u'4'] |
191 | BEGIN |
192 | getRemoteStatus() called: u'1' |
193 | + COMMIT |
194 | + BEGIN |
195 | getRemoteStatus() called: u'2' |
196 | + COMMIT |
197 | + BEGIN |
198 | getRemoteStatus() called: u'3' |
199 | + COMMIT |
200 | + BEGIN |
201 | getRemoteStatus() called: u'4' |
202 | + COMMIT |
203 | |
204 | The only exception to the rule of only updating modified bugs is the set |
205 | of bug watches which have comments that need to be pushed to the remote |
206 | @@ -1102,7 +1123,7 @@ |
207 | >>> bug_watch_updater.updateBugTrackers( |
208 | ... bug_tracker_names=[standard_bugzilla.name], batch_size=2) |
209 | DEBUG Using a global batch size of 2 |
210 | - INFO Updating 4 watches for 2 bugs on http://example.com |
211 | + INFO Updating 2 watches for 2 bugs on http://example.com |
212 | initializeRemoteBugDB() called: [u'5', u'6'] |
213 | getRemoteStatus() called: u'5' |
214 | getRemoteStatus() called: u'6' |
215 | |
216 | === modified file 'lib/lp/bugs/interfaces/bugwatch.py' |
217 | --- lib/lp/bugs/interfaces/bugwatch.py 2009-09-04 09:48:04 +0000 |
218 | +++ lib/lp/bugs/interfaces/bugwatch.py 2010-01-05 16:11:20 +0000 |
219 | @@ -268,6 +268,19 @@ |
220 | If no bug tracker type can be guessed, None is returned. |
221 | """ |
222 | |
223 | + def getBugWatchesForRemoteBug(self, remote_bug, bug_watch_ids=None): |
224 | + """Returns bug watches referring to the given remote bug. |
225 | + |
226 | + Returns a set of those bug watches, optionally limited to |
227 | + those with IDs in `bug_watch_ids`, that refer to `remote_bug`. |
228 | + |
229 | + :param remote_bug_id: The ID of the remote bug. |
230 | + :type remote_bug_id: See `IBugWatch.remotebug`. |
231 | + |
232 | + :param bug_watch_ids: A collection of `BugWatch` IDs. |
233 | + :type bug_watch_ids: An iterable of `int`s, or `None`. |
234 | + """ |
235 | + |
236 | |
237 | class NoBugTrackerFound(Exception): |
238 | """No bug tracker with the base_url is registered in Launchpad.""" |
239 | |
240 | === modified file 'lib/lp/bugs/interfaces/externalbugtracker.py' |
241 | --- lib/lp/bugs/interfaces/externalbugtracker.py 2009-06-25 00:40:31 +0000 |
242 | +++ lib/lp/bugs/interfaces/externalbugtracker.py 2010-01-05 16:11:20 +0000 |
243 | @@ -43,35 +43,56 @@ |
244 | """Return the `ExternalBugTracker` instance to use. |
245 | |
246 | Probe the remote bug tracker and choose the right |
247 | - `ExternalBugTracker` instance to use further on. |
248 | + `ExternalBugTracker` instance to use further on. In most cases |
249 | + this will simply return `self`. |
250 | """ |
251 | |
252 | def getCurrentDBTime(): |
253 | """Return the current time of the bug tracker's DB server. |
254 | |
255 | The current time will be returned as a timezone-aware datetime. |
256 | + |
257 | + :return: `datetime.datetime` with timezone. |
258 | """ |
259 | |
260 | def getModifiedRemoteBugs(remote_bug_ids, last_checked): |
261 | """Return the bug ids that have been modified. |
262 | |
263 | Return all ids if the modified bugs can't be determined. |
264 | + |
265 | + :param remote_bug_ids: The remote bug IDs to be checked. |
266 | + :type remote_bug_ids: `list` of strings |
267 | + |
268 | + :param last_checked: The date and time since when a bug should |
269 | + be considered modified. |
270 | + :param last_checked: `datetime.datetime` |
271 | """ |
272 | |
273 | def initializeRemoteBugDB(remote_bug_ids): |
274 | - """Do any initialization before each bug watch is updated.""" |
275 | + """Do any initialization before each bug watch is updated. |
276 | + |
277 | + :param remote_bug_ids: The remote bug IDs that to be checked. |
278 | + :type remote_bug_ids: `list` of strings |
279 | + """ |
280 | |
281 | def convertRemoteStatus(remote_status): |
282 | - """Convert a remote status string to a BugTaskStatus item.""" |
283 | + """Convert a remote status string to a BugTaskStatus item. |
284 | + |
285 | + :return: a member of `BugTaskStatus` |
286 | + """ |
287 | |
288 | def convertRemoteImportance(remote_importance): |
289 | - """Convert a remote importance to a BugTaskImportance item.""" |
290 | + """Convert a remote importance to a BugTaskImportance item. |
291 | + |
292 | + :return: a member of `BugTaskImportance` |
293 | + """ |
294 | |
295 | def getRemoteProduct(remote_bug): |
296 | """Return the remote product for a given remote bug. |
297 | |
298 | :param remote_bug: The ID of the remote bug for which to return |
299 | the remote product. |
300 | + :type remote_bug: string |
301 | :return: The remote product for `remote_bug`. If no remote |
302 | product is recorded for `remote_bug` return None. |
303 | :raise BugNotFound: If `remote_bug` doesn't exist for the bug |
304 | @@ -123,16 +144,19 @@ |
305 | def getBugReporter(remote_bug): |
306 | """Return the person who submitted the given bug. |
307 | |
308 | - A tuple of (display name, email) is returned. |
309 | + :return: `tuple` of (display name, email) |
310 | """ |
311 | |
312 | def getBugSummaryAndDescription(remote_bug): |
313 | - """Return a tuple of summary and description for the given bug.""" |
314 | + """Return the summary and description for the given bug. |
315 | + |
316 | + :return: `tuple` of (summary, description) |
317 | + """ |
318 | |
319 | def getBugTargetName(remote_bug): |
320 | """Return the specific target name of the bug. |
321 | |
322 | - Return None if no target can be determined. |
323 | + :return: string, or `None` if no target can be determined |
324 | """ |
325 | |
326 | |
327 | |
328 | === modified file 'lib/lp/bugs/model/bugwatch.py' |
329 | --- lib/lp/bugs/model/bugwatch.py 2009-12-24 06:44:57 +0000 |
330 | +++ lib/lp/bugs/model/bugwatch.py 2010-01-05 16:11:20 +0000 |
331 | @@ -17,7 +17,7 @@ |
332 | # SQL imports |
333 | from sqlobject import ForeignKey, SQLObjectNotFound, StringCol |
334 | |
335 | -from storm.expr import Desc, Not |
336 | +from storm.expr import Desc, In, Not |
337 | from storm.store import Store |
338 | |
339 | from lazr.lifecycle.event import ObjectModifiedEvent |
340 | @@ -31,6 +31,7 @@ |
341 | from canonical.launchpad.database.message import Message |
342 | from canonical.launchpad.helpers import shortlist |
343 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
344 | +from canonical.launchpad.interfaces.lpstorm import IStore |
345 | from canonical.launchpad.validators.email import valid_email |
346 | from canonical.launchpad.webapp import urlappend, urlsplit |
347 | from canonical.launchpad.webapp.interfaces import NotFoundError |
348 | @@ -603,3 +604,10 @@ |
349 | |
350 | raise UnrecognizedBugTrackerURL(url) |
351 | |
352 | + def getBugWatchesForRemoteBug(self, remote_bug, bug_watch_ids=None): |
353 | + """See `IBugWatchSet`.""" |
354 | + query = IStore(BugWatch).find( |
355 | + BugWatch, BugWatch.remotebug == remote_bug) |
356 | + if bug_watch_ids is not None: |
357 | + query = query.find(In(BugWatch.id, bug_watch_ids)) |
358 | + return query |
359 | |
360 | === modified file 'lib/lp/bugs/scripts/checkwatches.py' |
361 | --- lib/lp/bugs/scripts/checkwatches.py 2009-12-16 16:50:24 +0000 |
362 | +++ lib/lp/bugs/scripts/checkwatches.py 2010-01-05 16:11:20 +0000 |
363 | @@ -414,26 +414,6 @@ |
364 | """Return the bug watch with id `bug_watch_id`.""" |
365 | return getUtility(IBugWatchSet).get(bug_watch_id) |
366 | |
367 | - def _getBugWatchesByRemoteBug(self, bug_watch_ids): |
368 | - """Returns a dictionary of bug watches mapped to remote bugs. |
369 | - |
370 | - For each bug watch id fetches the corresponding bug watch and |
371 | - appends it to a list of bug watches pointing to one remote |
372 | - bug - the key of the returned mapping. |
373 | - """ |
374 | - bug_watches_by_remote_bug = {} |
375 | - for bug_watch_id in bug_watch_ids: |
376 | - bug_watch = self._getBugWatch(bug_watch_id) |
377 | - remote_bug = bug_watch.remotebug |
378 | - # There can be multiple bug watches pointing to the same |
379 | - # remote bug; because of that, we need to store lists of bug |
380 | - # watches related to the remote bug, and later update the |
381 | - # status of each one of them. |
382 | - if remote_bug not in bug_watches_by_remote_bug: |
383 | - bug_watches_by_remote_bug[remote_bug] = [] |
384 | - bug_watches_by_remote_bug[remote_bug].append(bug_watch) |
385 | - return bug_watches_by_remote_bug |
386 | - |
387 | def _getExternalBugTrackersAndWatches(self, bug_tracker, bug_watches): |
388 | """Return an `ExternalBugTracker` instance for `bug_tracker`.""" |
389 | remotesystem = externalbugtracker.get_external_bugtracker( |
390 | @@ -686,6 +666,17 @@ |
391 | 'unmodified_remote_ids': unmodified_remote_ids, |
392 | } |
393 | |
394 | + def _getBugWatchesForRemoteBug(self, remote_bug_id, bug_watch_ids): |
395 | + """Return a list of bug watches for the given remote bug. |
396 | + |
397 | + The returned watches will all be members of `bug_watch_ids`. |
398 | + |
399 | + This method exists primarily to be overridden during testing. |
400 | + """ |
401 | + return list( |
402 | + getUtility(IBugWatchSet).getBugWatchesForRemoteBug( |
403 | + remote_bug_id, bug_watch_ids)) |
404 | + |
405 | # XXX gmb 2008-11-07 [bug=295319] |
406 | # This method is 186 lines long. It needs to be shorter. |
407 | def updateBugWatches(self, remotesystem, bug_watches_to_update, now=None, |
408 | @@ -752,10 +743,6 @@ |
409 | self.txn.commit() |
410 | raise |
411 | |
412 | - self.txn.begin() |
413 | - bug_watches_by_remote_bug = self._getBugWatchesByRemoteBug( |
414 | - bug_watch_ids) |
415 | - |
416 | # Whether we can import and / or push comments is determined on |
417 | # a per-bugtracker-type level. |
418 | can_import_comments = ( |
419 | @@ -788,26 +775,32 @@ |
420 | "local bugs: %(local_ids)s" |
421 | ) |
422 | |
423 | - for bug_id in all_remote_ids: |
424 | - try: |
425 | - bug_watches = bug_watches_by_remote_bug[bug_id] |
426 | - except KeyError: |
427 | + for remote_bug_id in all_remote_ids: |
428 | + # Start a fresh transaction every time round the loop. |
429 | + self.txn.begin() |
430 | + |
431 | + bug_watches = self._getBugWatchesForRemoteBug( |
432 | + remote_bug_id, bug_watch_ids) |
433 | + if len(bug_watches) == 0: |
434 | # If there aren't any bug watches for this remote bug, |
435 | # just log a warning and carry on. |
436 | self.warning( |
437 | "Spurious remote bug ID: No watches found for " |
438 | - "remote bug %s on %s" % (bug_id, remotesystem.baseurl)) |
439 | + "remote bug %s on %s" % ( |
440 | + remote_bug_id, remotesystem.baseurl)) |
441 | continue |
442 | |
443 | for bug_watch in bug_watches: |
444 | bug_watch.lastchecked = UTC_NOW |
445 | - if bug_id in unmodified_remote_ids: |
446 | + if remote_bug_id in unmodified_remote_ids: |
447 | continue |
448 | |
449 | # Save the remote bug URL in case we need to log an error. |
450 | remote_bug_url = bug_watches[0].url |
451 | |
452 | - local_ids = ", ".join(str(watch.bug.id) for watch in bug_watches) |
453 | + local_ids = ", ".join( |
454 | + str(bug_id) for bug_id in sorted( |
455 | + watch.bug.id for watch in bug_watches)) |
456 | try: |
457 | new_remote_status = None |
458 | new_malone_status = None |
459 | @@ -820,12 +813,13 @@ |
460 | # necessary and can be refactored out when bug |
461 | # 136391 is dealt with. |
462 | try: |
463 | - new_remote_status = remotesystem.getRemoteStatus(bug_id) |
464 | + new_remote_status = ( |
465 | + remotesystem.getRemoteStatus(remote_bug_id)) |
466 | new_malone_status = self._convertRemoteStatus( |
467 | remotesystem, new_remote_status) |
468 | |
469 | - new_remote_importance = remotesystem.getRemoteImportance( |
470 | - bug_id) |
471 | + new_remote_importance = ( |
472 | + remotesystem.getRemoteImportance(remote_bug_id)) |
473 | new_malone_importance = ( |
474 | remotesystem.convertRemoteImportance( |
475 | new_remote_importance)) |
476 | @@ -835,13 +829,13 @@ |
477 | error, error_type_message_default) |
478 | self.warning( |
479 | message % { |
480 | - 'bug_id': bug_id, |
481 | + 'bug_id': remote_bug_id, |
482 | 'base_url': remotesystem.baseurl, |
483 | 'local_ids': local_ids, |
484 | }, |
485 | properties=[ |
486 | ('URL', remote_bug_url), |
487 | - ('bug_id', bug_id), |
488 | + ('bug_id', remote_bug_id), |
489 | ('local_ids', local_ids), |
490 | ] + self._getOOPSProperties(remotesystem), |
491 | info=sys.exc_info()) |
492 | @@ -868,17 +862,11 @@ |
493 | except (KeyboardInterrupt, SystemExit): |
494 | # We should never catch KeyboardInterrupt or SystemExit. |
495 | raise |
496 | + |
497 | except Exception, error: |
498 | - # If something unexpected goes wrong, we shouldn't break the |
499 | - # updating of the other bugs. |
500 | - |
501 | - # Restart the transaction so that subsequent |
502 | - # bug watches will get recorded. |
503 | + # Restart transaction before recording the error. |
504 | self.txn.abort() |
505 | self.txn.begin() |
506 | - bug_watches_by_remote_bug = self._getBugWatchesByRemoteBug( |
507 | - bug_watch_ids) |
508 | - |
509 | # We record errors against the bug watches and update |
510 | # their lastchecked dates so that we don't try to |
511 | # re-check them every time checkwatches runs. |
512 | @@ -889,17 +877,20 @@ |
513 | # We need to commit the transaction, in case the next |
514 | # bug fails to update as well. |
515 | self.txn.commit() |
516 | - self.txn.begin() |
517 | - |
518 | + # Send the error to the log too. |
519 | self.error( |
520 | "Failure updating bug %r on %s (local bugs: %s)." % |
521 | - (bug_id, bug_tracker_url, local_ids), |
522 | + (remote_bug_id, bug_tracker_url, local_ids), |
523 | properties=[ |
524 | ('URL', remote_bug_url), |
525 | - ('bug_id', bug_id), |
526 | + ('bug_id', remote_bug_id), |
527 | ('local_ids', local_ids)] + |
528 | self._getOOPSProperties(remotesystem)) |
529 | |
530 | + else: |
531 | + # All is well, save it now. |
532 | + self.txn.commit() |
533 | + |
534 | def importBug(self, external_bugtracker, bugtracker, bug_target, |
535 | remote_bug): |
536 | """Import a remote bug into Launchpad. |
537 | |
538 | === modified file 'lib/lp/bugs/scripts/tests/test_bugimport.py' |
539 | --- lib/lp/bugs/scripts/tests/test_bugimport.py 2009-11-27 16:16:12 +0000 |
540 | +++ lib/lp/bugs/scripts/tests/test_bugimport.py 2010-01-05 16:11:20 +0000 |
541 | @@ -892,15 +892,18 @@ |
542 | """See `BugWatchUpdater`.""" |
543 | return [(TestExternalBugTracker(bug_tracker.baseurl), bug_watches)] |
544 | |
545 | - def _getBugWatch(self, bug_watch_id): |
546 | - """Returns a mock bug watch object. |
547 | + def _getBugWatchesForRemoteBug(self, remote_bug_id, bug_watch_ids): |
548 | + """Returns a list of fake bug watch objects. |
549 | |
550 | - We override this method to force one of our two bug watches |
551 | - to be returned. The first is guaranteed to trigger a db error, |
552 | - the second should update successfuly. |
553 | + We override this method so that we always return bug watches |
554 | + from our list of fake bug watches. |
555 | """ |
556 | - return self.bugtracker.getBugWatchesNeedingUpdate(0)[bug_watch_id - 1] |
557 | - |
558 | + return [ |
559 | + bug_watch for bug_watch in ( |
560 | + self.bugtracker.getBugWatchesNeedingUpdate(0)) |
561 | + if (bug_watch.remotebug == remote_bug_id and |
562 | + bug_watch.id in bug_watch_ids) |
563 | + ] |
564 | |
565 | |
566 | class CheckBugWatchesErrorRecoveryTestCase(unittest.TestCase): |
567 | |
568 | === modified file 'lib/lp/bugs/scripts/tests/test_checkwatches.py' |
569 | --- lib/lp/bugs/scripts/tests/test_checkwatches.py 2010-01-04 11:39:14 +0000 |
570 | +++ lib/lp/bugs/scripts/tests/test_checkwatches.py 2010-01-05 16:11:20 +0000 |
571 | @@ -9,14 +9,20 @@ |
572 | |
573 | from zope.component import getUtility |
574 | |
575 | +from canonical.config import config |
576 | +from canonical.database.sqlbase import commit |
577 | +from canonical.launchpad.ftests import login |
578 | +from canonical.launchpad.interfaces import ( |
579 | + BugTaskStatus, BugTrackerType, IBugSet, IBugTaskSet, |
580 | + ILaunchpadCelebrities, IPersonSet, IProductSet, IQuestionSet) |
581 | from canonical.launchpad.scripts.logger import QuietFakeLogger |
582 | -from canonical.launchpad.interfaces import ILaunchpadCelebrities |
583 | from canonical.testing import LaunchpadZopelessLayer |
584 | |
585 | from lp.bugs.externalbugtracker.bugzilla import BugzillaAPI |
586 | from lp.bugs.scripts import checkwatches |
587 | from lp.bugs.scripts.checkwatches import CheckWatchesErrorUtility |
588 | -from lp.bugs.tests.externalbugtracker import TestBugzillaAPIXMLRPCTransport |
589 | +from lp.bugs.tests.externalbugtracker import ( |
590 | + TestBugzillaAPIXMLRPCTransport, new_bugtracker) |
591 | from lp.testing import TestCaseWithFactory |
592 | |
593 | |
594 | @@ -38,37 +44,17 @@ |
595 | def getExternalBugTrackerToUse(self): |
596 | return self |
597 | |
598 | - def getProductsForRemoteBugs(self, remote_bugs): |
599 | - """Return the products for some remote bugs. |
600 | - |
601 | - This method is basically the same as that of the superclass but |
602 | - without the call to initializeRemoteBugDB(). |
603 | - """ |
604 | - bug_products = {} |
605 | - for bug_id in bug_ids: |
606 | - # If one of the bugs we're trying to get the product for |
607 | - # doesn't exist, just skip it. |
608 | - try: |
609 | - actual_bug_id = self._getActualBugId(bug_id) |
610 | - except BugNotFound: |
611 | - continue |
612 | - |
613 | - bug_dict = self._bugs[actual_bug_id] |
614 | - bug_products[bug_id] = bug_dict['product'] |
615 | - |
616 | - return bug_products |
617 | - |
618 | |
619 | class NoBugWatchesByRemoteBugUpdater(checkwatches.BugWatchUpdater): |
620 | """A subclass of BugWatchUpdater with methods overridden for testing.""" |
621 | |
622 | - def _getBugWatchesByRemoteBug(self, bug_watch_ids): |
623 | - """Return an empty dict. |
624 | + def _getBugWatchesForRemoteBug(self, remote_bug_id, bug_watch_ids): |
625 | + """Return an empty list. |
626 | |
627 | - This method overrides _getBugWatchesByRemoteBug() so that bug |
628 | + This method overrides _getBugWatchesForRemoteBug() so that bug |
629 | 497141 can be regression-tested. |
630 | """ |
631 | - return {} |
632 | + return [] |
633 | |
634 | |
635 | class TestCheckwatchesWithSyncableGnomeProducts(TestCaseWithFactory): |
636 | @@ -146,5 +132,75 @@ |
637 | last_oops.value.startswith('Spurious remote bug ID')) |
638 | |
639 | |
640 | +class TestUpdateBugsWithLinkedQuestions(unittest.TestCase): |
641 | + """Tests for updating bugs with linked questions.""" |
642 | + |
643 | + layer = LaunchpadZopelessLayer |
644 | + |
645 | + def setUp(self): |
646 | + """Set up bugs, watches and questions to test with.""" |
647 | + super(TestUpdateBugsWithLinkedQuestions, self).setUp() |
648 | + |
649 | + # For test_can_update_bug_with_questions we need a bug that has |
650 | + # a question linked to it. |
651 | + bug_with_question = getUtility(IBugSet).get(10) |
652 | + question = getUtility(IQuestionSet).get(1) |
653 | + |
654 | + # XXX gmb 2007-12-11 bug 175545: |
655 | + # We shouldn't have to login() here, but since |
656 | + # database.buglinktarget.BugLinkTargetMixin.linkBug() |
657 | + # doesn't accept a user parameter, instead depending on the |
658 | + # currently logged in user, we get an exception if we don't. |
659 | + login('test@canonical.com') |
660 | + question.linkBug(bug_with_question) |
661 | + |
662 | + # We subscribe launchpad_developers to the question since this |
663 | + # indirectly subscribes foo.bar@canonical.com to it, too. We can |
664 | + # then use this to test the updating of a question with indirect |
665 | + # subscribers from a bug watch. |
666 | + question.subscribe( |
667 | + getUtility(ILaunchpadCelebrities).launchpad_developers) |
668 | + commit() |
669 | + |
670 | + # We now need to switch to the checkwatches DB user so that |
671 | + # we're testing with the correct set of permissions. |
672 | + self.layer.switchDbUser(config.checkwatches.dbuser) |
673 | + |
674 | + # For test_can_update_bug_with_questions we also need a bug |
675 | + # watch and by extension a bug tracker. |
676 | + sample_person = getUtility(IPersonSet).getByEmail( |
677 | + 'test@canonical.com') |
678 | + bugtracker = new_bugtracker(BugTrackerType.ROUNDUP) |
679 | + self.bugtask_with_question = getUtility(IBugTaskSet).createTask( |
680 | + bug_with_question, sample_person, |
681 | + product=getUtility(IProductSet).getByName('firefox')) |
682 | + self.bugwatch_with_question = bug_with_question.addWatch( |
683 | + bugtracker, '1', getUtility(ILaunchpadCelebrities).janitor) |
684 | + self.bugtask_with_question.bugwatch = self.bugwatch_with_question |
685 | + commit() |
686 | + |
687 | + def test_can_update_bug_with_questions(self): |
688 | + """Test whether bugs with linked questions can be updated. |
689 | + |
690 | + This will also test whether indirect subscribers of linked |
691 | + questions will be notified of the changes made when the bugwatch |
692 | + is updated. |
693 | + """ |
694 | + # We need to check that the bug task we created in setUp() is |
695 | + # still being referenced by our bug watch. |
696 | + self.assertEqual(self.bugwatch_with_question.bugtasks[0].id, |
697 | + self.bugtask_with_question.id) |
698 | + |
699 | + # We can now update the bug watch, which will in turn update the |
700 | + # bug task and the linked question. |
701 | + self.bugwatch_with_question.updateStatus('some status', |
702 | + BugTaskStatus.INPROGRESS) |
703 | + self.assertEqual(self.bugwatch_with_question.bugtasks[0].status, |
704 | + BugTaskStatus.INPROGRESS, |
705 | + "BugTask status is inconsistent. Expected %s but got %s" % |
706 | + (BugTaskStatus.INPROGRESS.title, |
707 | + self.bugtask_with_question.status.title)) |
708 | + |
709 | + |
710 | def test_suite(): |
711 | return unittest.TestLoader().loadTestsFromName(__name__) |
712 | |
713 | === modified file 'lib/lp/bugs/tests/test_bugwatch.py' |
714 | --- lib/lp/bugs/tests/test_bugwatch.py 2009-12-15 17:28:51 +0000 |
715 | +++ lib/lp/bugs/tests/test_bugwatch.py 2010-01-05 16:11:20 +0000 |
716 | @@ -11,18 +11,20 @@ |
717 | |
718 | from zope.component import getUtility |
719 | |
720 | +from canonical.launchpad.ftests import login, ANONYMOUS |
721 | from canonical.launchpad.webapp import urlsplit |
722 | from canonical.testing import ( |
723 | - DatabaseFunctionalLayer, LaunchpadFunctionalLayer) |
724 | + DatabaseFunctionalLayer, LaunchpadFunctionalLayer, LaunchpadZopelessLayer) |
725 | |
726 | from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet |
727 | from lp.bugs.interfaces.bugwatch import ( |
728 | IBugWatchSet, NoBugTrackerFound, UnrecognizedBugTrackerURL) |
729 | from lp.registry.interfaces.person import IPersonSet |
730 | -from lp.testing import ANONYMOUS, TestCaseWithFactory, login |
731 | - |
732 | - |
733 | -class ExtractBugTrackerAndBugTestBase(unittest.TestCase): |
734 | + |
735 | +from lp.testing import TestCaseWithFactory |
736 | + |
737 | + |
738 | +class ExtractBugTrackerAndBugTestBase: |
739 | """Test base for testing BugWatchSet.extractBugTrackerAndBug.""" |
740 | layer = LaunchpadFunctionalLayer |
741 | |
742 | @@ -85,7 +87,8 @@ |
743 | "NoBugTrackerFound wasn't raised by extractBugTrackerAndBug") |
744 | |
745 | |
746 | -class MantisExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
747 | +class MantisExtractBugTrackerAndBugTest( |
748 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
749 | """Ensure BugWatchSet.extractBugTrackerAndBug works with Mantis URLs.""" |
750 | |
751 | bugtracker_type = BugTrackerType.MANTIS |
752 | @@ -94,7 +97,8 @@ |
753 | bug_id = '3224' |
754 | |
755 | |
756 | -class BugzillaExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
757 | +class BugzillaExtractBugTrackerAndBugTest( |
758 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
759 | """Ensure BugWatchSet.extractBugTrackerAndBug works with Bugzilla URLs.""" |
760 | |
761 | bugtracker_type = BugTrackerType.BUGZILLA |
762 | @@ -103,7 +107,8 @@ |
763 | bug_id = '3224' |
764 | |
765 | |
766 | -class IssuezillaExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
767 | +class IssuezillaExtractBugTrackerAndBugTest( |
768 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
769 | """Ensure BugWatchSet.extractBugTrackerAndBug works with Issuezilla. |
770 | |
771 | Issuezilla is practically the same as Buzilla, so we treat it as a |
772 | @@ -116,7 +121,8 @@ |
773 | bug_id = '3224' |
774 | |
775 | |
776 | -class RoundUpExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
777 | +class RoundUpExtractBugTrackerAndBugTest( |
778 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
779 | """Ensure BugWatchSet.extractBugTrackerAndBug works with RoundUp URLs.""" |
780 | |
781 | bugtracker_type = BugTrackerType.ROUNDUP |
782 | @@ -125,7 +131,8 @@ |
783 | bug_id = '377' |
784 | |
785 | |
786 | -class TracExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
787 | +class TracExtractBugTrackerAndBugTest( |
788 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
789 | """Ensure BugWatchSet.extractBugTrackerAndBug works with Trac URLs.""" |
790 | |
791 | bugtracker_type = BugTrackerType.TRAC |
792 | @@ -134,7 +141,8 @@ |
793 | bug_id = '42' |
794 | |
795 | |
796 | -class DebbugsExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
797 | +class DebbugsExtractBugTrackerAndBugTest( |
798 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
799 | """Ensure BugWatchSet.extractBugTrackerAndBug works with Debbugs URLs.""" |
800 | |
801 | bugtracker_type = BugTrackerType.DEBBUGS |
802 | @@ -144,7 +152,7 @@ |
803 | |
804 | |
805 | class DebbugsExtractBugTrackerAndBugShorthandTest( |
806 | - ExtractBugTrackerAndBugTestBase): |
807 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
808 | """Ensure extractBugTrackerAndBug works for short Debbugs URLs.""" |
809 | |
810 | bugtracker_type = BugTrackerType.DEBBUGS |
811 | @@ -156,7 +164,8 @@ |
812 | # bugs.debian.org is already registered, so no dice. |
813 | pass |
814 | |
815 | -class SFExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
816 | +class SFExtractBugTrackerAndBugTest( |
817 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
818 | """Ensure BugWatchSet.extractBugTrackerAndBug works with SF URLs. |
819 | |
820 | We have only one SourceForge tracker registered in Launchpad, so we |
821 | @@ -216,7 +225,8 @@ |
822 | bug_id = '1568562' |
823 | |
824 | |
825 | -class XForgeExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
826 | +class XForgeExtractBugTrackerAndBugTest( |
827 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
828 | """Ensure extractBugTrackerAndBug works with SourceForge-like URLs. |
829 | """ |
830 | |
831 | @@ -228,7 +238,8 @@ |
832 | bug_id = '90812' |
833 | |
834 | |
835 | -class RTExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
836 | +class RTExtractBugTrackerAndBugTest( |
837 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
838 | """Ensure BugWatchSet.extractBugTrackerAndBug works with RT URLs.""" |
839 | |
840 | bugtracker_type = BugTrackerType.RT |
841 | @@ -237,7 +248,8 @@ |
842 | bug_id = '2379' |
843 | |
844 | |
845 | -class CpanExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
846 | +class CpanExtractBugTrackerAndBugTest( |
847 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
848 | """Ensure BugWatchSet.extractBugTrackerAndBug works with CPAN URLs.""" |
849 | |
850 | bugtracker_type = BugTrackerType.RT |
851 | @@ -246,7 +258,8 @@ |
852 | bug_id = '2379' |
853 | |
854 | |
855 | -class SavannahExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
856 | +class SavannahExtractBugTrackerAndBugTest( |
857 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
858 | """Ensure BugWatchSet.extractBugTrackerAndBug works with Savannah URLs. |
859 | """ |
860 | |
861 | @@ -261,7 +274,8 @@ |
862 | pass |
863 | |
864 | |
865 | -class SavaneExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTestBase): |
866 | +class SavaneExtractBugTrackerAndBugTest( |
867 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
868 | """Ensure BugWatchSet.extractBugTrackerAndBug works with Savane URLs. |
869 | """ |
870 | |
871 | @@ -272,7 +286,7 @@ |
872 | |
873 | |
874 | class EmailAddressExtractBugTrackerAndBugTest( |
875 | - ExtractBugTrackerAndBugTestBase): |
876 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
877 | """Ensure BugWatchSet.extractBugTrackerAndBug works with email addresses. |
878 | """ |
879 | |
880 | @@ -290,7 +304,7 @@ |
881 | |
882 | |
883 | class PHPProjectBugTrackerExtractBugTrackerAndBugTest( |
884 | - ExtractBugTrackerAndBugTestBase): |
885 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
886 | """Ensure BugWatchSet.extractBugTrackerAndBug works with PHP bug URLs. |
887 | """ |
888 | |
889 | @@ -301,7 +315,7 @@ |
890 | |
891 | |
892 | class GoogleCodeBugTrackerExtractBugTrackerAndBugTest( |
893 | - ExtractBugTrackerAndBugTestBase): |
894 | + ExtractBugTrackerAndBugTestBase, unittest.TestCase): |
895 | """Ensure BugWatchSet.extractBugTrackerAndBug works for Google Code URLs. |
896 | """ |
897 | |
898 | @@ -311,6 +325,39 @@ |
899 | bug_id = '12345' |
900 | |
901 | |
902 | +class TestBugWatchSet(TestCaseWithFactory): |
903 | + """Tests for the bugwatch updating system.""" |
904 | + |
905 | + layer = LaunchpadZopelessLayer |
906 | + |
907 | + def test_getBugWatchesForRemoteBug(self): |
908 | + # getBugWatchesForRemoteBug() returns bug watches from that |
909 | + # refer to the remote bug. |
910 | + bug_watches_alice = [ |
911 | + self.factory.makeBugWatch(remote_bug="alice"), |
912 | + ] |
913 | + bug_watches_bob = [ |
914 | + self.factory.makeBugWatch(remote_bug="bob"), |
915 | + self.factory.makeBugWatch(remote_bug="bob"), |
916 | + ] |
917 | + bug_watch_set = getUtility(IBugWatchSet) |
918 | + # Passing in the remote bug ID gets us every bug watch that |
919 | + # refers to that remote bug. |
920 | + self.failUnlessEqual( |
921 | + set(bug_watches_alice), |
922 | + set(bug_watch_set.getBugWatchesForRemoteBug('alice'))) |
923 | + self.failUnlessEqual( |
924 | + set(bug_watches_bob), |
925 | + set(bug_watch_set.getBugWatchesForRemoteBug('bob'))) |
926 | + # The search can be narrowed by passing in a list or other |
927 | + # iterable collection of bug watch IDs. |
928 | + bug_watches_limited = bug_watches_alice + bug_watches_bob[:1] |
929 | + self.failUnlessEqual( |
930 | + set(bug_watches_bob[:1]), |
931 | + set(bug_watch_set.getBugWatchesForRemoteBug('bob', [ |
932 | + bug_watch.id for bug_watch in bug_watches_limited]))) |
933 | + |
934 | + |
935 | class TestBugWatchBugTasks(TestCaseWithFactory): |
936 | |
937 | layer = DatabaseFunctionalLayer |
938 | @@ -326,31 +373,4 @@ |
939 | |
940 | |
941 | def test_suite(): |
942 | - suite = unittest.TestSuite() |
943 | - suite.addTest(unittest.makeSuite(BugzillaExtractBugTrackerAndBugTest)) |
944 | - suite.addTest(unittest.makeSuite(IssuezillaExtractBugTrackerAndBugTest)) |
945 | - suite.addTest(unittest.makeSuite(RoundUpExtractBugTrackerAndBugTest)) |
946 | - suite.addTest(unittest.makeSuite(TracExtractBugTrackerAndBugTest)) |
947 | - suite.addTest(unittest.makeSuite(DebbugsExtractBugTrackerAndBugTest)) |
948 | - suite.addTest( |
949 | - unittest.makeSuite(DebbugsExtractBugTrackerAndBugShorthandTest)) |
950 | - suite.addTest(unittest.makeSuite(SFExtractBugTrackerAndBugTest)) |
951 | - suite.addTest(unittest.makeSuite(SFTracker2ExtractBugTrackerAndBugTest)) |
952 | - suite.addTest(unittest.makeSuite(XForgeExtractBugTrackerAndBugTest)) |
953 | - suite.addTest(unittest.makeSuite(MantisExtractBugTrackerAndBugTest)) |
954 | - suite.addTest(unittest.makeSuite(RTExtractBugTrackerAndBugTest)) |
955 | - suite.addTest(unittest.makeSuite(CpanExtractBugTrackerAndBugTest)) |
956 | - suite.addTest(unittest.makeSuite(SavannahExtractBugTrackerAndBugTest)) |
957 | - suite.addTest(unittest.makeSuite(SavaneExtractBugTrackerAndBugTest)) |
958 | - suite.addTest(unittest.makeSuite(EmailAddressExtractBugTrackerAndBugTest)) |
959 | - suite.addTest(unittest.makeSuite( |
960 | - PHPProjectBugTrackerExtractBugTrackerAndBugTest)) |
961 | - suite.addTest(unittest.makeSuite( |
962 | - GoogleCodeBugTrackerExtractBugTrackerAndBugTest)) |
963 | - suite.addTest(unittest.makeSuite(TestBugWatchBugTasks)) |
964 | - return suite |
965 | - |
966 | - |
967 | -if __name__ == '__main__': |
968 | - unittest.main() |
969 | - |
970 | + return unittest.TestLoader().loadTestsFromName(__name__) |
971 | |
972 | === removed file 'lib/lp/translations/scripts/tests/__init__.py.moved' |
The main change here is that BugWatchUpdater .updateBugWatch es()
commits transactions regularly, instead of trying to keep them
open. It tried to do this to avoid calling an expensive function. I've
changed the code to use a different, new, function which means that
it's not necessary to hold the transaction open.
Looking at it again, I don't know that this was necessary; I could
have just added a commit in there and it would have worked. However,
the expensive function was really doing ugly things, so I feel that
the change was worth it anyway.
Lint free.
Test: bin/test -vvt 'checkwatch| bug-?watch'
There are two other small changes, detailed below.
lib/lp/ bugs/doc/ externalbugtrac ker.txt
Amended this doctest to show the ways transactions are now handled .updateBugWatch es().
in BugWatchUpdater
lib/lp/ bugs/interfaces /bugwatch. py
New method getBugWatchesFo rRemoteBug( ).
lib/lp/ bugs/interfaces /externalbugtra cker.py
Improved interface docstrings.
lib/lp/ bugs/model/ bugwatch. py
Implementation of getBugWatchesFo rRemoteBug( ).
lib/lp/ bugs/scripts/ checkwatches. py
Removed BugWatchUpdater ._getBugWatches ByRemoteBug( ). This method is
reasonably expensive because it can end up iterating through 1000s
of bug watches, each of which it queries individually from the
database.
It needed to be called after each aborted transaction (i.e. after an
error updating another bug watch), and this has resulted in trying
to hold transactions open for a long time instead of committing
after each update.
From reading the code, it's apparent that this can mean that a
failure in one bug watch can cause nothing to be recorded for many
other bug watches that are otherwise fine.
I've changed updateBugWatches() to commit on every iteration of its getBugWatchesFo rRemoteBug( ) chesByRemoteBug ().
main loop, and now use IBugWatchSet.
instead of self._getBugWat
In updateBugWatches() I also renamed bug_id to remote_bug_id because
it was confusing.
lib/canonical/ launchpad/ scripts/ ftests/ test_checkwatch es.py => bugs/scripts/ tests/test_ checkwatches. py
lib/lp/
Moved this module into the lp tree, and renamed the only TestCase in
it. I originally added some test code to this file, but later moved
it elsewhere, but I want to land the move and rename anyway.
lib/lp/ bugs/tests/ test_bugwatch. py
Refactored this because the test_suite() function was ugly and
fragile. It would be easy to add a TestCase, forget to add it to the
test_suite() method, and no one notice.
I also added TestBugWatchSet, which currently only tests the new rRemoteBug( ).
method, getBugWatchesFo