Merge lp:~gmb/launchpad/cw-refactor-add-rbu-bug-568881 into lp:launchpad/db-devel

Proposed by Graham Binns
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 9337
Proposed branch: lp:~gmb/launchpad/cw-refactor-add-rbu-bug-568881
Merge into: lp:launchpad/db-devel
Diff against target: 905 lines (+433/-275)
8 files modified
lib/lp/bugs/doc/externalbugtracker.txt (+14/-10)
lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py (+3/-4)
lib/lp/bugs/scripts/checkwatches/core.py (+14/-225)
lib/lp/bugs/scripts/checkwatches/remotebugupdater.py (+218/-0)
lib/lp/bugs/scripts/checkwatches/tests/test_core.py (+30/-19)
lib/lp/bugs/scripts/checkwatches/tests/test_remotebugupdater.py (+59/-0)
lib/lp/bugs/scripts/checkwatches/utilities.py (+61/-0)
lib/lp/bugs/scripts/tests/test_bugimport.py (+34/-17)
To merge this branch: bzr merge lp:~gmb/launchpad/cw-refactor-add-rbu-bug-568881
Reviewer Review Type Date Requested Status
Gavin Panella (community) code Approve
Canonical Launchpad Engineering code Pending
Review via email: mp+24404@code.launchpad.net

Commit message

The remote bug-specific part of CheckwatchesMaster has been moved into a RemoteBugUpdater class.

Description of the change

This branch is the second in the mad-checkwatches-refactoring process.
It moves the remote-bug-specific part of CheckwatchesMaster into a
RemoteBugUpdater class.

The majority of changes here are simple moves, though there are some
alterations to tests and callsites to make sure that everything works
properly.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (38.4 KiB)

Hi Graham,

This mad refactorage is looking good :) I have a few comments but
nothing major, so r=me.

Gavin.

> === modified file 'lib/lp/bugs/doc/externalbugtracker.txt'
> --- lib/lp/bugs/doc/externalbugtracker.txt 2010-04-21 10:33:55 +0000
> +++ lib/lp/bugs/doc/externalbugtracker.txt 2010-04-29 10:42:49 +0000
> @@ -641,7 +641,7 @@
>
> === Converting statuses ===
>
> -Once it has retrieved the bugs from the remote server, CheckwatchesMaster
> +Once it has retrieved the bugs from the remote server, RemoteBugUpdater
> attempts to convert their statuses into Launchpad BugTaskStatuses by
> calling the convertRemoteStatus() method on the ExternalBugTracker via
> its own _convertRemoteStatus() method.
> @@ -660,17 +660,17 @@
> ... else:
> ... raise UnknownRemoteStatusError(remote_status)
>
> -CheckwatchesMaster._convertRemoteStatus() will handle these errors and will
> +RemoteBugUpdater._convertRemoteStatus() will handle these errors and will
> return BugTaskStatus.UNKNOWN when they occur. It will also log a
> warning.
>
> - >>> status = bug_watch_updater._convertRemoteStatus(
> - ... StatusConvertingExternalBugTracker(), 'new')
> + >>> remote_bug_updater = bug_watch_updater._makeRemoteBugUpdater(
> + ... StatusConvertingExternalBugTracker(), '1', [1], [])
> + >>> status = remote_bug_updater._convertRemoteStatus('new')
> >>> print status.title
> New
>
> - >>> status = bug_watch_updater._convertRemoteStatus(
> - ... StatusConvertingExternalBugTracker(), 'spam')
> + >>> status = remote_bug_updater._convertRemoteStatus('spam')
> WARNING...Unknown remote status 'spam'. (OOPS-...)
> >>> print status.title
> Unknown
>
> === modified file 'lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py'
> --- lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py 2010-04-26 12:34:47 +0000
> +++ lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py 2010-04-29 10:42:49 +0000
> @@ -26,9 +26,12 @@
> from lp.bugs.interfaces.bug import IBugSet
> from lp.bugs.scripts.checkwatches.base import (
> WorkingBase, commit_before)
> +from lp.bugs.scripts.checkwatches.utilities import (
> + get_remote_system_oops_properties)
> from lp.registry.interfaces.person import PersonCreationRationale
>
>
> +
> class BugWatchUpdater(WorkingBase):
> """Handles the updating of a single BugWatch for checkwatches."""
>
> @@ -81,10 +84,6 @@
> @commit_before
> def importBugComments(self):
> """Import all the comments from the remote bug."""
> - # Avoid circularity.
> - from lp.bugs.scripts.checkwatches.core import (
> - get_remote_system_oops_properties)
> -
> with self.transaction:
> local_bug_id = self.bug_watch.bug.id
> remote_bug_id = self.bug_watch.remotebug
>
> === modified file 'lib/lp/bugs/scripts/checkwatches/core.py'
> --- lib/lp/bugs/scripts/checkwatches/core.py 2010-04-26 12:34:47 +0000
> +++ lib/lp/bugs/scripts/checkwatches/core.py 2010-04-29 10:42:49 +0000
> @@ -37,22 +37,21 @@
> from canonical.database.constants import UTC_NOW
> from canonical.database.sqlbase import flush_database_updates
> f...

review: Approve
Revision history for this message
Gavin Panella (allenap) :
review: Approve (code)
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (21.1 KiB)

On Thu, Apr 29, 2010 at 12:52:29PM -0000, Gavin Panella wrote:
> Review: Approve
> Hi Graham,
>
> This mad refactorage is looking good :) I have a few comments but
> nothing major, so r=me.
>
> Gavin.
>
>
> > @@ -168,6 +126,17 @@
> > else:
> > self._syncable_gnome_products = list(SYNCABLE_GNOME_PRODUCTS)
> >
> > + def _makeRemoteBugUpdater(self, external_bugtracker, remote_bug,
> > + bug_watch_ids, unmodified_remote_ids):
> > + """Create and return a `RemoteBugUpdater` instance.
> > +
> > + This method exists purely for the sake of being able to
> > + overrride it in tests.
>
> Have you been listening to Christina Aguilera recently? s/rrr/rr/

What's really sad is that you're confident enough about your knowledge
of her album names to make that gag. Shame on you.

> Another way to do this with less boiler-plate is to have a
> remote_bug_updater_factory class attribute. The signature of
> _makeRemoteBugUpdater() is identical to RemoteBugUpdater.__init__().

Right. After discussion on IRC I've done this.

> > + @commit_before
> > + def updateRemoteBug(self, can_import_comments=False,
> > + can_push_comments=False, can_back_link=False):
>
> The can_* arguments don't change for the life of the RemoteBugUpdater,
> so I think it makes sense to set them in __init__(). This could be
> done by just passing server_time in.

Agreed. Done.

> > + # Avoid circular imports
>
> Already avoided.
>

Oops, fixed.

> > + with self.transaction:
> > + bug_watches = self._getBugWatchesForRemoteBug()
> > + # If there aren't any bug watches for this remote bug,
> > + # just log a warning and carry on.
> > + if len(bug_watches) == 0:
> > + self.warning(
> > + "Spurious remote bug ID: No watches found for "
> > + "remote bug %s on %s" % (
> > + self.remote_bug, self.external_bugtracker.baseurl))
> > + return
> > + # Mark them all as checked.
> > + for bug_watch in bug_watches:
> > + bug_watch.lastchecked = UTC_NOW
> > + bug_watch.next_check = None
> > + # Next if this one is definitely unmodified.
>
> s/Next/Return/
>

Fixed.

> > + if self.remote_bug in self.unmodified_remote_ids:
> > + return
> > + # Save the remote bug URL for error reporting.
> > + remote_bug_url = bug_watches[0].url
> > + # Save the list of local bug IDs for error reporting.
> > + local_ids = ", ".join(
> > + str(bug_id) for bug_id in sorted(
> > + watch.bug.id for watch in bug_watches))
> > +
> > + try:
> > + new_remote_status = None
> > + new_malone_status = None
> > + new_remote_importance = None
> > + new_malone_importance = None
> > + error = None
> > + oops_id = None
> > +
> > + # XXX: 2007-10-17 Graham Binns
> > + # This nested set of try:excepts isn't really
> > + ...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/doc/externalbugtracker.txt'
--- lib/lp/bugs/doc/externalbugtracker.txt 2010-04-23 11:19:49 +0000
+++ lib/lp/bugs/doc/externalbugtracker.txt 2010-05-05 10:09:40 +0000
@@ -391,12 +391,15 @@
391 >>> from zope.interface import implements391 >>> from zope.interface import implements
392 >>> from canonical.launchpad.interfaces import ISupportsCommentImport392 >>> from canonical.launchpad.interfaces import ISupportsCommentImport
393 >>> class CommentImportExternalBugTracker(TimeUnknownExternalBugTracker):393 >>> class CommentImportExternalBugTracker(TimeUnknownExternalBugTracker):
394 ... baseurl = 'http://whatever.com'
394 ... implements(ISupportsCommentImport)395 ... implements(ISupportsCommentImport)
395 ... sync_comments = True396 ... sync_comments = True
396 >>> bug_watch_updater.updateBugWatches(397
397 ... CommentImportExternalBugTracker(), [], now=utc_now)398 >>> checkwatches_master = CheckwatchesMaster(
398 getCurrentDBTime() called399 ... transaction, syncable_gnome_products=[])
399 initializeRemoteBugDB() called: []400 >>> remote_bug_updater = checkwatches_master.remote_bug_updater_factory(
401 ... checkwatches_master, CommentImportExternalBugTracker(), '1',
402 ... [], [], server_time=None)
400 WARNING:...:Comment importing supported, but server time can't be403 WARNING:...:Comment importing supported, but server time can't be
401 trusted. No comments will be imported. (OOPS-...)404 trusted. No comments will be imported. (OOPS-...)
402405
@@ -641,7 +644,7 @@
641644
642=== Converting statuses ===645=== Converting statuses ===
643646
644Once it has retrieved the bugs from the remote server, CheckwatchesMaster647Once it has retrieved the bugs from the remote server, RemoteBugUpdater
645attempts to convert their statuses into Launchpad BugTaskStatuses by648attempts to convert their statuses into Launchpad BugTaskStatuses by
646calling the convertRemoteStatus() method on the ExternalBugTracker via649calling the convertRemoteStatus() method on the ExternalBugTracker via
647its own _convertRemoteStatus() method.650its own _convertRemoteStatus() method.
@@ -660,17 +663,18 @@
660 ... else:663 ... else:
661 ... raise UnknownRemoteStatusError(remote_status)664 ... raise UnknownRemoteStatusError(remote_status)
662665
663CheckwatchesMaster._convertRemoteStatus() will handle these errors and will666RemoteBugUpdater._convertRemoteStatus() will handle these errors and will
664return BugTaskStatus.UNKNOWN when they occur. It will also log a667return BugTaskStatus.UNKNOWN when they occur. It will also log a
665warning.668warning.
666669
667 >>> status = bug_watch_updater._convertRemoteStatus(670 >>> remote_bug_updater = bug_watch_updater.remote_bug_updater_factory(
668 ... StatusConvertingExternalBugTracker(), 'new')671 ... bug_watch_updater, StatusConvertingExternalBugTracker(),
672 ... '1', [1], [], utc_now)
673 >>> status = remote_bug_updater._convertRemoteStatus('new')
669 >>> print status.title674 >>> print status.title
670 New675 New
671676
672 >>> status = bug_watch_updater._convertRemoteStatus(677 >>> status = remote_bug_updater._convertRemoteStatus('spam')
673 ... StatusConvertingExternalBugTracker(), 'spam')
674 WARNING...Unknown remote status 'spam'. (OOPS-...)678 WARNING...Unknown remote status 'spam'. (OOPS-...)
675 >>> print status.title679 >>> print status.title
676 Unknown680 Unknown
677681
=== modified file 'lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py'
--- lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py 2010-04-26 12:34:47 +0000
+++ lib/lp/bugs/scripts/checkwatches/bugwatchupdater.py 2010-05-05 10:09:40 +0000
@@ -26,9 +26,12 @@
26from lp.bugs.interfaces.bug import IBugSet26from lp.bugs.interfaces.bug import IBugSet
27from lp.bugs.scripts.checkwatches.base import (27from lp.bugs.scripts.checkwatches.base import (
28 WorkingBase, commit_before)28 WorkingBase, commit_before)
29from lp.bugs.scripts.checkwatches.utilities import (
30 get_remote_system_oops_properties)
29from lp.registry.interfaces.person import PersonCreationRationale31from lp.registry.interfaces.person import PersonCreationRationale
3032
3133
34
32class BugWatchUpdater(WorkingBase):35class BugWatchUpdater(WorkingBase):
33 """Handles the updating of a single BugWatch for checkwatches."""36 """Handles the updating of a single BugWatch for checkwatches."""
3437
@@ -81,10 +84,6 @@
81 @commit_before84 @commit_before
82 def importBugComments(self):85 def importBugComments(self):
83 """Import all the comments from the remote bug."""86 """Import all the comments from the remote bug."""
84 # Avoid circularity.
85 from lp.bugs.scripts.checkwatches.core import (
86 get_remote_system_oops_properties)
87
88 with self.transaction:87 with self.transaction:
89 local_bug_id = self.bug_watch.bug.id88 local_bug_id = self.bug_watch.bug.id
90 remote_bug_id = self.bug_watch.remotebug89 remote_bug_id = self.bug_watch.remotebug
9190
=== modified file 'lib/lp/bugs/scripts/checkwatches/core.py'
--- lib/lp/bugs/scripts/checkwatches/core.py 2010-04-30 03:10:17 +0000
+++ lib/lp/bugs/scripts/checkwatches/core.py 2010-05-05 10:09:40 +0000
@@ -37,22 +37,21 @@
37from canonical.database.constants import UTC_NOW37from canonical.database.constants import UTC_NOW
38from canonical.database.sqlbase import flush_database_updates38from canonical.database.sqlbase import flush_database_updates
39from canonical.launchpad.interfaces import (39from canonical.launchpad.interfaces import (
40 BugTaskStatus, BugWatchActivityStatus, CreateBugParams,40 CreateBugParams, IBugTrackerSet, IBugWatchSet, IDistribution,
41 IBugTrackerSet, IBugWatchSet, IDistribution, ILaunchpadCelebrities,41 ILaunchpadCelebrities, IPersonSet, ISupportsCommentImport,
42 IPersonSet, ISupportsCommentImport, ISupportsCommentPushing,42 ISupportsCommentPushing, PersonCreationRationale)
43 PersonCreationRationale, UNKNOWN_REMOTE_STATUS)
44from canonical.launchpad.scripts.logger import log as default_log43from canonical.launchpad.scripts.logger import log as default_log
4544
46from lp.bugs import externalbugtracker45from lp.bugs import externalbugtracker
47from lp.bugs.externalbugtracker import (46from lp.bugs.externalbugtracker import (
48 BATCH_SIZE_UNLIMITED, BugNotFound, BugTrackerConnectError,47 BATCH_SIZE_UNLIMITED, BugWatchUpdateError,
49 BugWatchUpdateError, InvalidBugId, PrivateRemoteBug,48 UnknownBugTrackerTypeError)
50 UnknownBugTrackerTypeError, UnknownRemoteStatusError, UnparseableBugData,
51 UnparseableBugTrackerVersion, UnsupportedBugTrackerVersion)
52from lp.bugs.interfaces.externalbugtracker import ISupportsBackLinking49from lp.bugs.interfaces.externalbugtracker import ISupportsBackLinking
53from lp.bugs.scripts.checkwatches.base import (50from lp.bugs.scripts.checkwatches.base import (
54 WorkingBase, commit_before, with_interaction)51 WorkingBase, commit_before, with_interaction)
55from lp.bugs.scripts.checkwatches.bugwatchupdater import BugWatchUpdater52from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
53from lp.bugs.scripts.checkwatches.utilities import (
54 get_bugwatcherrortype_for_error)
56from lp.services.scripts.base import LaunchpadCronScript55from lp.services.scripts.base import LaunchpadCronScript
5756
5857
@@ -77,30 +76,6 @@
77 """Time difference between ourselves and the remote server is too much."""76 """Time difference between ourselves and the remote server is too much."""
7877
7978
80_exception_to_bugwatcherrortype = [
81 (BugTrackerConnectError, BugWatchActivityStatus.CONNECTION_ERROR),
82 (PrivateRemoteBug, BugWatchActivityStatus.PRIVATE_REMOTE_BUG),
83 (UnparseableBugData, BugWatchActivityStatus.UNPARSABLE_BUG),
84 (UnparseableBugTrackerVersion,
85 BugWatchActivityStatus.UNPARSABLE_BUG_TRACKER),
86 (UnsupportedBugTrackerVersion,
87 BugWatchActivityStatus.UNSUPPORTED_BUG_TRACKER),
88 (UnknownBugTrackerTypeError,
89 BugWatchActivityStatus.UNSUPPORTED_BUG_TRACKER),
90 (InvalidBugId, BugWatchActivityStatus.INVALID_BUG_ID),
91 (BugNotFound, BugWatchActivityStatus.BUG_NOT_FOUND),
92 (PrivateRemoteBug, BugWatchActivityStatus.PRIVATE_REMOTE_BUG),
93 (socket.timeout, BugWatchActivityStatus.TIMEOUT)]
94
95def get_bugwatcherrortype_for_error(error):
96 """Return the correct `BugWatchActivityStatus` for a given error."""
97 for exc_type, bugwatcherrortype in _exception_to_bugwatcherrortype:
98 if isinstance(error, exc_type):
99 return bugwatcherrortype
100 else:
101 return BugWatchActivityStatus.UNKNOWN
102
103
104def unique(iterator):79def unique(iterator):
105 """Generate only unique items from an iterator."""80 """Generate only unique items from an iterator."""
106 seen = set()81 seen = set()
@@ -126,26 +101,11 @@
126 int(SUGGESTED_BATCH_SIZE_PROPORTION * num_watches))101 int(SUGGESTED_BATCH_SIZE_PROPORTION * num_watches))
127102
128103
129def get_remote_system_oops_properties(remote_system):
130 """Return (name, value) tuples describing a remote system.
131
132 Each item in the list is intended for use as an OOPS property.
133
134 :remote_system: The `ExternalBugTracker` instance from which the
135 OOPS properties should be extracted.
136 """
137 return [
138 ('batch_size', remote_system.batch_size),
139 ('batch_query_threshold', remote_system.batch_query_threshold),
140 ('sync_comments', remote_system.sync_comments),
141 ('externalbugtracker', remote_system.__class__.__name__),
142 ('baseurl', remote_system.baseurl)
143 ]
144
145
146class CheckwatchesMaster(WorkingBase):104class CheckwatchesMaster(WorkingBase):
147 """Takes responsibility for updating remote bug watches."""105 """Takes responsibility for updating remote bug watches."""
148106
107 remote_bug_updater_factory = RemoteBugUpdater
108
149 def __init__(self, transaction_manager, logger=default_log,109 def __init__(self, transaction_manager, logger=default_log,
150 syncable_gnome_products=None):110 syncable_gnome_products=None):
151 """Initialize a CheckwatchesMaster.111 """Initialize a CheckwatchesMaster.
@@ -455,37 +415,6 @@
455 self.logger.debug(415 self.logger.debug(
456 "No watches to update on %s" % bug_tracker.baseurl)416 "No watches to update on %s" % bug_tracker.baseurl)
457417
458 def _convertRemoteStatus(self, remotesystem, remote_status):
459 """Convert a remote bug status to a Launchpad status and return it.
460
461 :param remotesystem: The `IExternalBugTracker` instance
462 representing the remote system.
463 :param remote_status: The remote status to be converted into a
464 Launchpad status.
465
466 If the remote status cannot be mapped to a Launchpad status,
467 BugTaskStatus.UNKNOWN will be returned and a warning will be
468 logged.
469 """
470 # We don't bother trying to convert UNKNOWN_REMOTE_STATUS.
471 if remote_status == UNKNOWN_REMOTE_STATUS:
472 return BugTaskStatus.UNKNOWN
473
474 try:
475 launchpad_status = remotesystem.convertRemoteStatus(
476 remote_status)
477 except UnknownRemoteStatusError:
478 # We log the warning, since we need to know about statuses
479 # that we don't handle correctly.
480 self.warning(
481 "Unknown remote status '%s'." % remote_status,
482 get_remote_system_oops_properties(remotesystem),
483 sys.exc_info())
484
485 launchpad_status = BugTaskStatus.UNKNOWN
486
487 return launchpad_status
488
489 def _getRemoteIdsToCheck(self, remotesystem, bug_watches,418 def _getRemoteIdsToCheck(self, remotesystem, bug_watches,
490 server_time=None, now=None, batch_size=None):419 server_time=None, now=None, batch_size=None):
491 """Return the remote bug IDs to check for a set of bug watches.420 """Return the remote bug IDs to check for a set of bug watches.
@@ -607,17 +536,6 @@
607 'unmodified_remote_ids': sorted(unmodified_remote_ids),536 'unmodified_remote_ids': sorted(unmodified_remote_ids),
608 }537 }
609538
610 def _getBugWatchesForRemoteBug(self, remote_bug_id, bug_watch_ids):
611 """Return a list of bug watches for the given remote bug.
612
613 The returned watches will all be members of `bug_watch_ids`.
614
615 This method exists primarily to be overridden during testing.
616 """
617 return list(
618 getUtility(IBugWatchSet).getBugWatchesForRemoteBug(
619 remote_bug_id, bug_watch_ids))
620
621 # XXX gmb 2008-11-07 [bug=295319]539 # XXX gmb 2008-11-07 [bug=295319]
622 # This method is 186 lines long. It needs to be shorter.540 # This method is 186 lines long. It needs to be shorter.
623 @commit_before541 @commit_before
@@ -678,140 +596,11 @@
678 bug_watch_ids, get_bugwatcherrortype_for_error(error))596 bug_watch_ids, get_bugwatcherrortype_for_error(error))
679 raise597 raise
680598
681 # Whether we can import and / or push comments is determined
682 # on a per-bugtracker-type level.
683 can_import_comments = (
684 ISupportsCommentImport.providedBy(remotesystem) and
685 remotesystem.sync_comments)
686 can_push_comments = (
687 ISupportsCommentPushing.providedBy(remotesystem) and
688 remotesystem.sync_comments)
689 can_back_link = (
690 ISupportsBackLinking.providedBy(remotesystem) and
691 remotesystem.sync_comments)
692
693 if can_import_comments and server_time is None:
694 can_import_comments = False
695 self.warning(
696 "Comment importing supported, but server time can't be"
697 " trusted. No comments will be imported.")
698
699 error_type_messages = {
700 BugWatchActivityStatus.INVALID_BUG_ID:
701 ("Invalid bug %(bug_id)r on %(base_url)s "
702 "(local bugs: %(local_ids)s)."),
703 BugWatchActivityStatus.BUG_NOT_FOUND:
704 ("Didn't find bug %(bug_id)r on %(base_url)s "
705 "(local bugs: %(local_ids)s)."),
706 BugWatchActivityStatus.PRIVATE_REMOTE_BUG:
707 ("Remote bug %(bug_id)r on %(base_url)s is private "
708 "(local bugs: %(local_ids)s)."),
709 }
710 error_type_message_default = (
711 "remote bug: %(bug_id)r; "
712 "base url: %(base_url)s; "
713 "local bugs: %(local_ids)s"
714 )
715
716 for remote_bug_id in all_remote_ids:599 for remote_bug_id in all_remote_ids:
717 with self.transaction:600 remote_bug_updater = self.remote_bug_updater_factory(
718 bug_watches = self._getBugWatchesForRemoteBug(601 self, remotesystem, remote_bug_id, bug_watch_ids,
719 remote_bug_id, bug_watch_ids)602 unmodified_remote_ids, server_time)
720 # If there aren't any bug watches for this remote bug,603 remote_bug_updater.updateRemoteBug()
721 # just log a warning and carry on.
722 if len(bug_watches) == 0:
723 self.warning(
724 "Spurious remote bug ID: No watches found for "
725 "remote bug %s on %s" % (
726 remote_bug_id, remotesystem.baseurl))
727 continue
728 # Mark them all as checked.
729 for bug_watch in bug_watches:
730 bug_watch.lastchecked = UTC_NOW
731 bug_watch.next_check = None
732 # Next if this one is definitely unmodified.
733 if remote_bug_id in unmodified_remote_ids:
734 continue
735 # Save the remote bug URL for error reporting.
736 remote_bug_url = bug_watches[0].url
737 # Save the list of local bug IDs for error reporting.
738 local_ids = ", ".join(
739 str(bug_id) for bug_id in sorted(
740 watch.bug.id for watch in bug_watches))
741
742 try:
743 new_remote_status = None
744 new_malone_status = None
745 new_remote_importance = None
746 new_malone_importance = None
747 error = None
748 oops_id = None
749
750 # XXX: 2007-10-17 Graham Binns
751 # This nested set of try:excepts isn't really
752 # necessary and can be refactored out when bug
753 # 136391 is dealt with.
754 try:
755 new_remote_status = (
756 remotesystem.getRemoteStatus(remote_bug_id))
757 new_malone_status = self._convertRemoteStatus(
758 remotesystem, new_remote_status)
759 new_remote_importance = (
760 remotesystem.getRemoteImportance(remote_bug_id))
761 new_malone_importance = (
762 remotesystem.convertRemoteImportance(
763 new_remote_importance))
764 except (InvalidBugId, BugNotFound, PrivateRemoteBug), ex:
765 error = get_bugwatcherrortype_for_error(ex)
766 message = error_type_messages.get(
767 error, error_type_message_default)
768 oops_id = self.warning(
769 message % {
770 'bug_id': remote_bug_id,
771 'base_url': remotesystem.baseurl,
772 'local_ids': local_ids,
773 },
774 properties=[
775 ('URL', remote_bug_url),
776 ('bug_id', remote_bug_id),
777 ('local_ids', local_ids),
778 ] + get_remote_system_oops_properties(
779 remotesystem),
780 info=sys.exc_info())
781
782 for bug_watch in bug_watches:
783 bug_watch_updater = BugWatchUpdater(
784 self, bug_watch, remotesystem)
785
786 bug_watch_updater.updateBugWatch(
787 new_remote_status, new_malone_status,
788 new_remote_importance, new_malone_importance,
789 can_import_comments, can_push_comments,
790 can_back_link, error, oops_id)
791
792 except (KeyboardInterrupt, SystemExit):
793 # We should never catch KeyboardInterrupt or SystemExit.
794 raise
795
796 except Exception, error:
797 # Send the error to the log.
798 oops_id = self.error(
799 "Failure updating bug %r on %s (local bugs: %s)." %
800 (remote_bug_id, bug_tracker_url, local_ids),
801 properties=[
802 ('URL', remote_bug_url),
803 ('bug_id', remote_bug_id),
804 ('local_ids', local_ids)] +
805 get_remote_system_oops_properties(remotesystem))
806 # We record errors against the bug watches and update
807 # their lastchecked dates so that we don't try to
808 # re-check them every time checkwatches runs.
809 error_type = get_bugwatcherrortype_for_error(error)
810 with self.transaction:
811 getUtility(IBugWatchSet).bulkSetError(
812 bug_watches, error_type)
813 getUtility(IBugWatchSet).bulkAddActivity(
814 bug_watches, result=error_type, oops_id=oops_id)
815604
816 def importBug(self, external_bugtracker, bugtracker, bug_target,605 def importBug(self, external_bugtracker, bugtracker, bug_target,
817 remote_bug):606 remote_bug):
818607
=== added file 'lib/lp/bugs/scripts/checkwatches/remotebugupdater.py'
--- lib/lp/bugs/scripts/checkwatches/remotebugupdater.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/scripts/checkwatches/remotebugupdater.py 2010-05-05 10:09:40 +0000
@@ -0,0 +1,218 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Classes and logic for the remote bug updater."""
5
6from __future__ import with_statement
7
8__metaclass__ = type
9__all__ = [
10 'RemoteBugUpdater',
11 ]
12
13import sys
14
15from zope.component import getUtility
16
17from canonical.database.constants import UTC_NOW
18
19from lp.bugs.externalbugtracker import (
20 BugNotFound, InvalidBugId, PrivateRemoteBug, UnknownRemoteStatusError)
21
22from lp.bugs.interfaces.bugtask import BugTaskStatus
23from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus, IBugWatchSet
24from lp.bugs.interfaces.externalbugtracker import (
25 ISupportsBackLinking, ISupportsCommentImport,
26 ISupportsCommentPushing, UNKNOWN_REMOTE_STATUS)
27from lp.bugs.scripts.checkwatches.base import WorkingBase, commit_before
28from lp.bugs.scripts.checkwatches.bugwatchupdater import BugWatchUpdater
29from lp.bugs.scripts.checkwatches.utilities import (
30 get_bugwatcherrortype_for_error, get_remote_system_oops_properties)
31
32
33class RemoteBugUpdater(WorkingBase):
34
35 def __init__(self, parent, external_bugtracker, remote_bug,
36 bug_watch_ids, unmodified_remote_ids, server_time):
37 self.initFromParent(parent)
38 self.external_bugtracker = external_bugtracker
39 self.bug_tracker_url = external_bugtracker.baseurl
40 self.remote_bug = remote_bug
41 self.bug_watch_ids = bug_watch_ids
42 self.unmodified_remote_ids = unmodified_remote_ids
43
44 self.error_type_messages = {
45 BugWatchActivityStatus.INVALID_BUG_ID:
46 ("Invalid bug %(bug_id)r on %(base_url)s "
47 "(local bugs: %(local_ids)s)."),
48 BugWatchActivityStatus.BUG_NOT_FOUND:
49 ("Didn't find bug %(bug_id)r on %(base_url)s "
50 "(local bugs: %(local_ids)s)."),
51 BugWatchActivityStatus.PRIVATE_REMOTE_BUG:
52 ("Remote bug %(bug_id)r on %(base_url)s is private "
53 "(local bugs: %(local_ids)s)."),
54 }
55 self.error_type_message_default = (
56 "remote bug: %(bug_id)r; "
57 "base url: %(base_url)s; "
58 "local bugs: %(local_ids)s"
59 )
60
61 # Whether we can import and / or push comments is determined
62 # on a per-bugtracker-type level.
63 self.can_import_comments = (
64 ISupportsCommentImport.providedBy(external_bugtracker) and
65 external_bugtracker.sync_comments)
66 self.can_push_comments = (
67 ISupportsCommentPushing.providedBy(external_bugtracker) and
68 external_bugtracker.sync_comments)
69 self.can_back_link = (
70 ISupportsBackLinking.providedBy(external_bugtracker) and
71 external_bugtracker.sync_comments)
72
73 if self.can_import_comments and server_time is None:
74 self.can_import_comments = False
75 self.warning(
76 "Comment importing supported, but server time can't be "
77 "trusted. No comments will be imported.")
78
79 def _getBugWatchesForRemoteBug(self):
80 """Return a list of bug watches for the current remote bug.
81
82 The returned watches will all be members of `self.bug_watch_ids`.
83
84 This method exists primarily to be overridden during testing.
85 """
86 return list(
87 getUtility(IBugWatchSet).getBugWatchesForRemoteBug(
88 self.remote_bug, self.bug_watch_ids))
89
90 @commit_before
91 def updateRemoteBug(self):
92 with self.transaction:
93 bug_watches = self._getBugWatchesForRemoteBug()
94 # If there aren't any bug watches for this remote bug,
95 # just log a warning and carry on.
96 if len(bug_watches) == 0:
97 self.warning(
98 "Spurious remote bug ID: No watches found for "
99 "remote bug %s on %s" % (
100 self.remote_bug, self.external_bugtracker.baseurl))
101 return
102 # Mark them all as checked.
103 for bug_watch in bug_watches:
104 bug_watch.lastchecked = UTC_NOW
105 bug_watch.next_check = None
106 # Return if this one is definitely unmodified.
107 if self.remote_bug in self.unmodified_remote_ids:
108 return
109 # Save the remote bug URL for error reporting.
110 remote_bug_url = bug_watches[0].url
111 # Save the list of local bug IDs for error reporting.
112 local_ids = ", ".join(
113 str(bug_id) for bug_id in sorted(
114 watch.bug.id for watch in bug_watches))
115
116 try:
117 new_remote_status = None
118 new_malone_status = None
119 new_remote_importance = None
120 new_malone_importance = None
121 error = None
122 oops_id = None
123
124 # XXX: 2007-10-17 Graham Binns
125 # This nested set of try:excepts isn't really
126 # necessary and can be refactored out when bug
127 # 136391 is dealt with.
128 try:
129 new_remote_status = (
130 self.external_bugtracker.getRemoteStatus(
131 self.remote_bug))
132 new_malone_status = self._convertRemoteStatus(
133 new_remote_status)
134 new_remote_importance = (
135 self.external_bugtracker.getRemoteImportance(
136 self.remote_bug))
137 new_malone_importance = (
138 self.external_bugtracker.convertRemoteImportance(
139 new_remote_importance))
140 except (InvalidBugId, BugNotFound, PrivateRemoteBug), ex:
141 error = get_bugwatcherrortype_for_error(ex)
142 message = self.error_type_messages.get(
143 error, self.error_type_message_default)
144 oops_id = self.warning(
145 message % {
146 'bug_id': self.remote_bug,
147 'base_url': self.external_bugtracker.baseurl,
148 'local_ids': local_ids,
149 },
150 properties=[
151 ('URL', remote_bug_url),
152 ('bug_id', self.remote_bug),
153 ('local_ids', local_ids),
154 ] + get_remote_system_oops_properties(
155 self.external_bugtracker),
156 info=sys.exc_info())
157
158 for bug_watch in bug_watches:
159 bug_watch_updater = BugWatchUpdater(
160 self, bug_watch, self.external_bugtracker)
161
162 bug_watch_updater.updateBugWatch(
163 new_remote_status, new_malone_status,
164 new_remote_importance, new_malone_importance,
165 self.can_import_comments, self.can_push_comments,
166 self.can_back_link, error, oops_id)
167
168 except Exception, error:
169 # Send the error to the log.
170 oops_id = self.error(
171 "Failure updating bug %r on %s (local bugs: %s)." %
172 (self.remote_bug, self.bug_tracker_url, local_ids),
173 properties=[
174 ('URL', remote_bug_url),
175 ('bug_id', self.remote_bug),
176 ('local_ids', local_ids)] +
177 get_remote_system_oops_properties(
178 self.external_bugtracker))
179 # We record errors against the bug watches and update
180 # their lastchecked dates so that we don't try to
181 # re-check them every time checkwatches runs.
182 error_type = get_bugwatcherrortype_for_error(error)
183 with self.transaction:
184 getUtility(IBugWatchSet).bulkSetError(
185 bug_watches, error_type)
186 getUtility(IBugWatchSet).bulkAddActivity(
187 bug_watches, result=error_type, oops_id=oops_id)
188
189 def _convertRemoteStatus(self, remote_status):
190 """Convert a remote bug status to a Launchpad status and return it.
191
192 :param remote_status: The remote status to be converted into a
193 Launchpad status.
194
195 If the remote status cannot be mapped to a Launchpad status,
196 BugTaskStatus.UNKNOWN will be returned and a warning will be
197 logged.
198 """
199 # We don't bother trying to convert UNKNOWN_REMOTE_STATUS.
200 if remote_status == UNKNOWN_REMOTE_STATUS:
201 return BugTaskStatus.UNKNOWN
202
203 try:
204 launchpad_status = self.external_bugtracker.convertRemoteStatus(
205 remote_status)
206 except UnknownRemoteStatusError:
207 # We log the warning, since we need to know about statuses
208 # that we don't handle correctly.
209 self.warning(
210 "Unknown remote status '%s'." % remote_status,
211 get_remote_system_oops_properties(
212 self.external_bugtracker),
213 sys.exc_info())
214
215 launchpad_status = BugTaskStatus.UNKNOWN
216
217 return launchpad_status
218
0219
=== modified file 'lib/lp/bugs/scripts/checkwatches/tests/test_core.py'
--- lib/lp/bugs/scripts/checkwatches/tests/test_core.py 2010-04-23 11:19:49 +0000
+++ lib/lp/bugs/scripts/checkwatches/tests/test_core.py 2010-05-05 10:09:40 +0000
@@ -11,6 +11,7 @@
1111
12import transaction12import transaction
1313
14from datetime import datetime
14from zope.component import getUtility15from zope.component import getUtility
1516
16from canonical.config import config17from canonical.config import config
@@ -24,9 +25,11 @@
24from lp.bugs.externalbugtracker.bugzilla import BugzillaAPI25from lp.bugs.externalbugtracker.bugzilla import BugzillaAPI
25from lp.bugs.interfaces.bugtracker import IBugTrackerSet26from lp.bugs.interfaces.bugtracker import IBugTrackerSet
26from lp.bugs.scripts import checkwatches27from lp.bugs.scripts import checkwatches
28from lp.bugs.scripts.checkwatches.base import (
29 CheckWatchesErrorUtility, WorkingBase)
27from lp.bugs.scripts.checkwatches.core import (30from lp.bugs.scripts.checkwatches.core import (
28 CheckwatchesMaster, TwistedThreadScheduler)31 CheckwatchesMaster, LOGIN, TwistedThreadScheduler)
29from lp.bugs.scripts.checkwatches.base import CheckWatchesErrorUtility32from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
30from lp.bugs.tests.externalbugtracker import (33from lp.bugs.tests.externalbugtracker import (
31 TestBugzillaAPIXMLRPCTransport, TestExternalBugTracker, new_bugtracker)34 TestBugzillaAPIXMLRPCTransport, TestExternalBugTracker, new_bugtracker)
32from lp.testing import TestCaseWithFactory, ZopeTestInSubProcess35from lp.testing import TestCaseWithFactory, ZopeTestInSubProcess
@@ -57,10 +60,10 @@
57 return self60 return self
5861
5962
60class NoBugWatchesByRemoteBugUpdater(checkwatches.CheckwatchesMaster):63class NoBugWatchesByRemoteBugUpdater(RemoteBugUpdater):
61 """A subclass of CheckwatchesMaster with methods overridden for testing."""64 """A subclass of RemoteBugUpdater with methods overridden for testing."""
6265
63 def _getBugWatchesForRemoteBug(self, remote_bug_id, bug_watch_ids):66 def _getBugWatchesForRemoteBug(self):
64 """Return an empty list.67 """Return an empty list.
6568
66 This method overrides _getBugWatchesForRemoteBug() so that bug69 This method overrides _getBugWatchesForRemoteBug() so that bug
@@ -154,10 +157,8 @@
154157
155 def test_bug_497141(self):158 def test_bug_497141(self):
156 # Regression test for bug 497141. KeyErrors raised in159 # Regression test for bug 497141. KeyErrors raised in
157 # CheckwatchesMaster.updateBugWatches() shouldn't cause160 # RemoteBugUpdater.updateRemoteBug() shouldn't cause
158 # checkwatches to abort.161 # checkwatches to abort.
159 updater = NoBugWatchesByRemoteBugUpdater(
160 transaction.manager, QuietFakeLogger())
161162
162 # Create a couple of bug watches for testing purposes.163 # Create a couple of bug watches for testing purposes.
163 bug_tracker = self.factory.makeBugTracker()164 bug_tracker = self.factory.makeBugTracker()
@@ -170,17 +171,27 @@
170 remote_system = NonConnectingBugzillaAPI(171 remote_system = NonConnectingBugzillaAPI(
171 bug_tracker.baseurl, xmlrpc_transport=test_transport)172 bug_tracker.baseurl, xmlrpc_transport=test_transport)
172173
173 # Calling updateBugWatches() shouldn't raise a KeyError, even174 working_base = WorkingBase()
174 # though with our broken updater _getExternalBugTrackersAndWatches()175 working_base.init(LOGIN, transaction.manager, QuietFakeLogger())
175 # will return an empty dict.176
176 updater.updateBugWatches(remote_system, bug_watches)177 for bug_watch in bug_watches:
177178 updater = NoBugWatchesByRemoteBugUpdater(
178 # An error will have been logged instead of the KeyError being179 working_base, remote_system, bug_watch.remotebug,
179 # raised.180 [bug_watch.id], [], datetime.now())
180 error_utility = CheckWatchesErrorUtility()181
181 last_oops = error_utility.getLastOopsReport()182 # Calling updateRemoteBug() shouldn't raise a KeyError,
182 self.assertTrue(183 # even though with our broken updater
183 last_oops.value.startswith('Spurious remote bug ID'))184 # _getExternalBugTrackersAndWatches() will return an empty
185 # dict.
186 updater.updateRemoteBug()
187
188 # An error will have been logged instead of the KeyError being
189 # raised.
190 error_utility = CheckWatchesErrorUtility()
191 last_oops = error_utility.getLastOopsReport()
192 self.assertTrue(
193 last_oops.value.startswith('Spurious remote bug ID'),
194 "Unexpected last OOPS value: %s" % last_oops.value)
184195
185 def test_suggest_batch_size(self):196 def test_suggest_batch_size(self):
186 class RemoteSystem: pass197 class RemoteSystem: pass
187198
=== added file 'lib/lp/bugs/scripts/checkwatches/tests/test_remotebugupdater.py'
--- lib/lp/bugs/scripts/checkwatches/tests/test_remotebugupdater.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/scripts/checkwatches/tests/test_remotebugupdater.py 2010-05-05 10:09:40 +0000
@@ -0,0 +1,59 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the checkwatches remotebugupdater module."""
5
6__metaclass__ = type
7
8import transaction
9import unittest
10
11from canonical.testing import LaunchpadZopelessLayer
12
13from lp.bugs.externalbugtracker.bugzilla import Bugzilla
14from lp.bugs.scripts.checkwatches.core import CheckwatchesMaster
15from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
16from lp.testing import TestCaseWithFactory
17
18
19class RemoteBugUpdaterTestCase(TestCaseWithFactory):
20
21 layer = LaunchpadZopelessLayer
22
23 def test_create(self):
24 # CheckwatchesMaster.remote_bug_updater_factory points to the
25 # RemoteBugUpdater class, so it can be used to create
26 # RemoteBugUpdaters.
27 remote_system = Bugzilla('http://example.com')
28 remote_bug_id = '42'
29 bug_watch_ids = [1, 2]
30 unmodified_remote_ids = ['76']
31
32 checkwatches_master = CheckwatchesMaster(transaction)
33 updater = checkwatches_master.remote_bug_updater_factory(
34 checkwatches_master, remote_system, remote_bug_id,
35 bug_watch_ids, unmodified_remote_ids, None)
36
37 self.assertTrue(
38 isinstance(updater, RemoteBugUpdater),
39 "updater should be an instance of RemoteBugUpdater.")
40 self.assertEqual(
41 remote_system, updater.external_bugtracker,
42 "Unexpected external_bugtracker for RemoteBugUpdater.")
43 self.assertEqual(
44 remote_bug_id, updater.remote_bug,
45 "RemoteBugUpdater's remote_bug should be '%s', was '%s'" %
46 (remote_bug_id, updater.remote_bug))
47 self.assertEqual(
48 bug_watch_ids, updater.bug_watch_ids,
49 "RemoteBugUpdater's bug_watch_ids should be '%s', were '%s'" %
50 (bug_watch_ids, updater.bug_watch_ids))
51 self.assertEqual(
52 unmodified_remote_ids, updater.unmodified_remote_ids,
53 "RemoteBugUpdater's unmodified_remote_ids should be '%s', "
54 "were '%s'" %
55 (unmodified_remote_ids, updater.unmodified_remote_ids))
56
57
58def test_suite():
59 return unittest.TestLoader().loadTestsFromName(__name__)
060
=== added file 'lib/lp/bugs/scripts/checkwatches/utilities.py'
--- lib/lp/bugs/scripts/checkwatches/utilities.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/scripts/checkwatches/utilities.py 2010-05-05 10:09:40 +0000
@@ -0,0 +1,61 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Utility functions for checkwatches."""
5
6__metaclass__ = type
7__all__ = [
8 'get_bugwatcherrortype_for_error',
9 'get_remote_system_oops_properties',
10 ]
11
12import socket
13
14from lp.bugs.externalbugtracker import (
15 BugNotFound, BugTrackerConnectError, InvalidBugId, PrivateRemoteBug,
16 UnknownBugTrackerTypeError, UnparseableBugData,
17 UnparseableBugTrackerVersion, UnsupportedBugTrackerVersion)
18
19from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus
20
21
22_exception_to_bugwatcherrortype = [
23 (BugTrackerConnectError, BugWatchActivityStatus.CONNECTION_ERROR),
24 (PrivateRemoteBug, BugWatchActivityStatus.PRIVATE_REMOTE_BUG),
25 (UnparseableBugData, BugWatchActivityStatus.UNPARSABLE_BUG),
26 (UnparseableBugTrackerVersion,
27 BugWatchActivityStatus.UNPARSABLE_BUG_TRACKER),
28 (UnsupportedBugTrackerVersion,
29 BugWatchActivityStatus.UNSUPPORTED_BUG_TRACKER),
30 (UnknownBugTrackerTypeError,
31 BugWatchActivityStatus.UNSUPPORTED_BUG_TRACKER),
32 (InvalidBugId, BugWatchActivityStatus.INVALID_BUG_ID),
33 (BugNotFound, BugWatchActivityStatus.BUG_NOT_FOUND),
34 (PrivateRemoteBug, BugWatchActivityStatus.PRIVATE_REMOTE_BUG),
35 (socket.timeout, BugWatchActivityStatus.TIMEOUT)]
36
37
38def get_bugwatcherrortype_for_error(error):
39 """Return the correct `BugWatchActivityStatus` for a given error."""
40 for exc_type, bugwatcherrortype in _exception_to_bugwatcherrortype:
41 if isinstance(error, exc_type):
42 return bugwatcherrortype
43 else:
44 return BugWatchActivityStatus.UNKNOWN
45
46
47def get_remote_system_oops_properties(remote_system):
48 """Return (name, value) tuples describing a remote system.
49
50 Each item in the list is intended for use as an OOPS property.
51
52 :remote_system: The `ExternalBugTracker` instance from which the
53 OOPS properties should be extracted.
54 """
55 return [
56 ('batch_size', remote_system.batch_size),
57 ('batch_query_threshold', remote_system.batch_query_threshold),
58 ('sync_comments', remote_system.sync_comments),
59 ('externalbugtracker', remote_system.__class__.__name__),
60 ('baseurl', remote_system.baseurl)
61 ]
062
=== modified file 'lib/lp/bugs/scripts/tests/test_bugimport.py'
--- lib/lp/bugs/scripts/tests/test_bugimport.py 2010-04-29 10:09:24 +0000
+++ lib/lp/bugs/scripts/tests/test_bugimport.py 2010-05-05 10:09:40 +0000
@@ -30,6 +30,7 @@
30from lp.bugs.scripts import bugimport30from lp.bugs.scripts import bugimport
31from lp.bugs.scripts.bugimport import ET31from lp.bugs.scripts.bugimport import ET
32from lp.bugs.scripts.checkwatches import CheckwatchesMaster32from lp.bugs.scripts.checkwatches import CheckwatchesMaster
33from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
33from lp.registry.interfaces.person import IPersonSet, PersonCreationRationale34from lp.registry.interfaces.person import IPersonSet, PersonCreationRationale
34from lp.registry.interfaces.product import IProductSet35from lp.registry.interfaces.product import IProductSet
35from lp.registry.model.person import generate_nick36from lp.registry.model.person import generate_nick
@@ -892,6 +893,29 @@
892 return BugTaskImportance.UNKNOWN893 return BugTaskImportance.UNKNOWN
893894
894895
896class TestRemoteBugUpdater(RemoteBugUpdater):
897
898 def __init__(self, parent, external_bugtracker, remote_bug,
899 bug_watch_ids, unmodified_remote_ids, server_time,
900 bugtracker):
901 super(TestRemoteBugUpdater, self). __init__(
902 parent, external_bugtracker, remote_bug, bug_watch_ids,
903 unmodified_remote_ids, server_time)
904 self.bugtracker = bugtracker
905
906 def _getBugWatchesForRemoteBug(self):
907 """Returns a list of fake bug watch objects.
908
909 We override this method so that we always return bug watches
910 from our list of fake bug watches.
911 """
912 return [
913 bug_watch for bug_watch in (
914 self.bugtracker.watches_needing_update)
915 if (bug_watch.remotebug == self.remote_bug and
916 bug_watch.id in self.bug_watch_ids)
917 ]
918
895919
896class TestCheckwatchesMaster(CheckwatchesMaster):920class TestCheckwatchesMaster(CheckwatchesMaster):
897 """A mock `CheckwatchesMaster` object."""921 """A mock `CheckwatchesMaster` object."""
@@ -905,28 +929,21 @@
905 """See `CheckwatchesMaster`."""929 """See `CheckwatchesMaster`."""
906 return [(TestExternalBugTracker(bug_tracker.baseurl), bug_watches)]930 return [(TestExternalBugTracker(bug_tracker.baseurl), bug_watches)]
907931
908 def _getBugWatchesForRemoteBug(self, remote_bug_id, bug_watch_ids):932 def remote_bug_updater_factory(self, parent, external_bugtracker,
909 """Returns a list of fake bug watch objects.933 remote_bug, bug_watch_ids,
910934 unmodified_remote_ids, server_time):
911 We override this method so that we always return bug watches935 return TestRemoteBugUpdater(
912 from our list of fake bug watches.936 self, external_bugtracker, remote_bug, bug_watch_ids,
913 """937 unmodified_remote_ids, server_time, self.bugtracker)
914 return [938
915 bug_watch for bug_watch in (939
916 self.bugtracker.watches_needing_update)940class CheckwatchesErrorRecoveryTestCase(unittest.TestCase):
917 if (bug_watch.remotebug == remote_bug_id and
918 bug_watch.id in bug_watch_ids)
919 ]
920
921
922class CheckBugWatchesErrorRecoveryTestCase(unittest.TestCase):
923 """Test that errors in the bugwatch import process don't941 """Test that errors in the bugwatch import process don't
924 invalidate the entire run.942 invalidate the entire run.
925 """943 """
926 layer = LaunchpadZopelessLayer944 layer = LaunchpadZopelessLayer
927945
928 def test_checkbugwatches_error_recovery(self):946 def test_checkwatches_error_recovery(self):
929
930 firefox = getUtility(IProductSet).get(4)947 firefox = getUtility(IProductSet).get(4)
931 foobar = getUtility(IPersonSet).get(16)948 foobar = getUtility(IPersonSet).get(16)
932 params = CreateBugParams(949 params = CreateBugParams(

Subscribers

People subscribed via source and target branches

to status/vote changes: