~launchpad-pqm/launchpad/devel

14575.2.1 by Jeroen Vermeulen
Lint.
1
Updating Product.remote_product
2
===============================
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
3
4
The remote_product attribute of a Product is used to present links for
5
filing and searching bugs in the Product's bug tracker, in case it's not
6
using Launchpad to track its bugs. We don't expect users to set the
7
remote_product themselves, so we have a script that tries to set this
8
automatically.
9
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
10
    >>> from lp.services.webapp.interfaces import (
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
11
    ...     IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE)
12
    >>> store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
13
    >>> store.execute("UPDATE Product SET remote_product = 'not-None'")
14
    <storm...>
15
8523.3.1 by Gavin Panella
Bugs tree reorg after automated migration.
16
    >>> from lp.bugs.interfaces.bugtracker import BugTrackerType
12070.1.4 by Tim Penhey
Move FakeLogger and BufferLogger to lp.services.log.logging and delete the QuietFakeLogger.
17
    >>> from lp.services.log.logger import FakeLogger, BufferLogger
14538.2.45 by Curtis Hovey
Moved scripts and tests to lp.bugs.
18
    >>> from lp.bugs.scripts.updateremoteproduct import (
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
19
    ...     RemoteProductUpdater)
10315.2.4 by Jeroen Vermeulen
Replace all "Fake" and "Mock" transaction managers I can find with one implementation.
20
    >>> from lp.testing.faketransaction import FakeTransaction
12070.1.4 by Tim Penhey
Move FakeLogger and BufferLogger to lp.services.log.logging and delete the QuietFakeLogger.
21
    >>> updater = RemoteProductUpdater(FakeTransaction(), BufferLogger())
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
22
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
23
14575.2.1 by Jeroen Vermeulen
Lint.
24
Testing
25
-------
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
26
27
To help testing, there is a method, _getExternalBugTracker(), that
28
creates the ExternalBugTracker for the given BugTracker.
29
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
30
    >>> rt = factory.makeBugTracker(
31
    ...     bugtrackertype=BugTrackerType.RT,
32
    ...     base_url=u'http://rt.example.com/')
33
    >>> rt_external = updater._getExternalBugTracker(rt)
34
    >>> rt_external.__class__.__name__
35
    'RequestTracker'
36
    >>> rt_external.baseurl
37
    u'http://rt.example.com'
38
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
39
For testing, _getExternalBugTracker() can be overridden to return an
40
ExternalBugTracker that doesn't require network access.
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
41
42
    >>> class FakeExternalBugTracker:
7840.1.1 by Graham Binns
Fixed bug 333354.
43
    ...
44
    ...     def initializeRemoteBugDB(self, bug_ids):
45
    ...         print "Initializing DB for bugs: %s." % bug_ids
46
    ...
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
47
    ...     def getRemoteProduct(self, remote_bug):
48
    ...         return 'product-for-bug-%s' % remote_bug
49
50
7793.5.8 by Bjorn Tillenius
Rename TestRemoteProductUpdater to show intent.
51
    >>> class NoNetworkRemoteProductUpdater(RemoteProductUpdater):
7844.2.1 by Graham Binns
update-remote-product should now handle errors.
52
    ...
53
    ...     external_bugtracker_to_return = FakeExternalBugTracker
54
    ...
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
55
    ...     def _getExternalBugTracker(self, bug_tracker):
7844.2.1 by Graham Binns
update-remote-product should now handle errors.
56
    ...         return self.external_bugtracker_to_return()
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
57
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
58
14575.2.1 by Jeroen Vermeulen
Lint.
59
update()
60
--------
7793.5.10 by Bjorn Tillenius
Add method for updating all products needing updating.
61
62
The update method simply loops over all the bug tracker types that can
7793.5.16 by Bjorn Tillenius
Don't try to update EMAILADDRESS bug trackers.
63
track more than one product, and calls updateByBugTrackerType(). Any bug
64
tracker type that isn't specified as being for a single product is being
7793.5.17 by Bjorn Tillenius
Typo.
65
looped over. The EMAILADDRESS one is special, though. It could be used
7793.5.16 by Bjorn Tillenius
Don't try to update EMAILADDRESS bug trackers.
66
for more than one product, but we have no way of interacting with it, so
67
it's skipped as well.
7793.5.10 by Bjorn Tillenius
Add method for updating all products needing updating.
68
69
    >>> class TrackerTypeCollectingUpdater(RemoteProductUpdater):
70
    ...     def __init__(self):
12070.1.4 by Tim Penhey
Move FakeLogger and BufferLogger to lp.services.log.logging and delete the QuietFakeLogger.
71
    ...         self.logger = BufferLogger()
7793.5.10 by Bjorn Tillenius
Add method for updating all products needing updating.
72
    ...         self.looped_over_bug_tracker_types = set()
73
    ...     def updateByBugTrackerType(self, bugtracker_type):
74
    ...         self.looped_over_bug_tracker_types.add(bugtracker_type)
75
8523.3.1 by Gavin Panella
Bugs tree reorg after automated migration.
76
    >>> from lp.bugs.interfaces.bugtracker import (
7793.5.10 by Bjorn Tillenius
Add method for updating all products needing updating.
77
    ...     SINGLE_PRODUCT_BUGTRACKERTYPES)
78
    >>> multi_product_trackers = set(
79
    ...     bugtracker_type for bugtracker_type in BugTrackerType.items
80
    ...     if bugtracker_type not in SINGLE_PRODUCT_BUGTRACKERTYPES)
7793.5.16 by Bjorn Tillenius
Don't try to update EMAILADDRESS bug trackers.
81
    >>> multi_product_trackers.remove(BugTrackerType.EMAILADDRESS)
7793.5.10 by Bjorn Tillenius
Add method for updating all products needing updating.
82
83
    >>> updater = TrackerTypeCollectingUpdater()
84
    >>> updater.update()
7793.5.16 by Bjorn Tillenius
Don't try to update EMAILADDRESS bug trackers.
85
    >>> multi_product_trackers.symmetric_difference(
7793.5.10 by Bjorn Tillenius
Add method for updating all products needing updating.
86
    ...     updater.looped_over_bug_tracker_types)
87
    set([])
88
89
14575.2.1 by Jeroen Vermeulen
Lint.
90
updateByBugTrackerType()
91
------------------------
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
92
7793.5.9 by Bjorn Tillenius
Rename updateRemoteProduct() to something more sensible.
93
The updateByBugTrackerType() method looks at the bug watches that are
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
94
linked to the product, to decide what remote_product should be set to.
95
It accepts a single parameter, the type of the bug tracker that should
96
be updated.
97
98
14575.2.1 by Jeroen Vermeulen
Lint.
99
No bug watches
100
..............
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
101
102
If there are no bug watches, nothing will be done.
103
104
    >>> bugzilla_product = factory.makeProduct(
105
    ...     name=u'bugzilla-product', official_malone=False)
106
    >>> bugzilla = factory.makeBugTracker(
107
    ...     bugtrackertype=BugTrackerType.BUGZILLA)
108
    >>> bugzilla_product.bugtracker = bugzilla
109
    >>> rt_product = factory.makeProduct(
110
    ...     name=u'rt-product', official_malone=False)
111
    >>> rt = factory.makeBugTracker(
112
    ...     bugtrackertype=BugTrackerType.RT)
113
    >>> rt_product.bugtracker = rt
114
115
    >>> list(bugzilla_product.getLinkedBugWatches())
116
    []
7793.5.9 by Bjorn Tillenius
Rename updateRemoteProduct() to something more sensible.
117
    >>> updater.updateByBugTrackerType(BugTrackerType.RT)
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
118
    >>> print bugzilla_product.remote_product
119
    None
120
    >>> print rt_product.remote_product
121
    None
122
123
14575.2.1 by Jeroen Vermeulen
Lint.
124
Linked bug watches
125
..................
7793.5.4 by Bjorn Tillenius
First cut at implementing updateRemoteProduct().
126
127
If there are bug watches for a product having a None remote_product, an
128
arbitrary bug watch will be retrieved, and queried for its remote
129
product. Products having a bug tracker of a different type than the
130
given one are ignored.
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
131
8342.5.11 by Gavin Panella
Amend some more tests.
132
    >>> import transaction
14605.1.1 by Curtis Hovey
Moved canonical.config to lp.services.
133
    >>> from lp.services.config import config
14604.1.1 by Curtis Hovey
Separate test-authoring classes from test-running classes.
134
    >>> from lp.testing.layers import LaunchpadZopelessLayer
8342.5.11 by Gavin Panella
Amend some more tests.
135
136
    >>> def switch_db_to_launchpad():
137
    ...     transaction.commit()
138
    ...     LaunchpadZopelessLayer.switchDbUser('launchpad')
139
140
    >>> def switch_db_to_updateremoteproduct():
141
    ...     transaction.commit()
142
    ...     LaunchpadZopelessLayer.switchDbUser(
143
    ...         config.updateremoteproduct.dbuser)
144
7793.5.14 by Bjorn Tillenius
Add logging to show that the script actually does the right thing.
145
    >>> updater = NoNetworkRemoteProductUpdater(
12070.1.4 by Tim Penhey
Move FakeLogger and BufferLogger to lp.services.log.logging and delete the QuietFakeLogger.
146
    ...     FakeTransaction(), BufferLogger())
8342.5.11 by Gavin Panella
Amend some more tests.
147
148
    >>> switch_db_to_launchpad()
149
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
150
    >>> bugzilla_bugtask = factory.makeBugTask(target=bugzilla_product)
151
    >>> bugzilla_bugwatch = factory.makeBugWatch(
152
    ...     '42', bugtracker=bugzilla, bug=bugzilla_bugtask.bug)
153
    >>> bugzilla_bugtask.bugwatch = bugzilla_bugwatch
154
    >>> rt_bugtask = factory.makeBugTask(target=rt_product)
155
    >>> rt_bugwatch = factory.makeBugWatch(
156
    ...     '84', bugtracker=rt, bug=rt_bugtask.bug)
157
    >>> rt_bugtask.bugwatch = rt_bugwatch
158
8342.5.11 by Gavin Panella
Amend some more tests.
159
    >>> switch_db_to_updateremoteproduct()
7675.85.2 by Jonathan Lange
Undo revision generated by step 2 of process.
160
7793.5.9 by Bjorn Tillenius
Rename updateRemoteProduct() to something more sensible.
161
    >>> updater.updateByBugTrackerType(BugTrackerType.RT)
7840.1.1 by Graham Binns
Fixed bug 333354.
162
    Initializing DB for bugs: [u'84'].
163
7793.5.1 by Bjorn Tillenius
Add basic tests and pseudo-code for updating the remote_product attributes.
164
    >>> print rt_product.remote_product
165
    product-for-bug-84
166
167
    >>> print bugzilla_product.remote_product
168
    None
7793.5.5 by Bjorn Tillenius
Make sure that Products having a remote_product set aren't updated.
169
170
14575.2.1 by Jeroen Vermeulen
Lint.
171
remote_product already set
172
..........................
7793.5.5 by Bjorn Tillenius
Make sure that Products having a remote_product set aren't updated.
173
174
If a product already has remote_product set, it will not be updated.
175
8342.5.11 by Gavin Panella
Amend some more tests.
176
    >>> switch_db_to_launchpad()
177
7793.5.5 by Bjorn Tillenius
Make sure that Products having a remote_product set aren't updated.
178
    >>> rt_product = factory.makeProduct(official_malone=False)
179
    >>> rt = factory.makeBugTracker(
180
    ...     bugtrackertype=BugTrackerType.RT)
181
    >>> rt_product.bugtracker = rt
182
    >>> rt_bugtask = factory.makeBugTask(target=rt_product)
183
    >>> rt_bugwatch = factory.makeBugWatch(
184
    ...     '84', bugtracker=rt, bug=rt_bugtask.bug)
185
    >>> rt_bugtask.bugwatch = rt_bugwatch
186
8342.5.11 by Gavin Panella
Amend some more tests.
187
    >>> switch_db_to_updateremoteproduct()
188
7793.5.5 by Bjorn Tillenius
Make sure that Products having a remote_product set aren't updated.
189
    >>> rt_product.remote_product = u'already-set'
7793.5.14 by Bjorn Tillenius
Add logging to show that the script actually does the right thing.
190
    >>> updater = NoNetworkRemoteProductUpdater(
12070.1.4 by Tim Penhey
Move FakeLogger and BufferLogger to lp.services.log.logging and delete the QuietFakeLogger.
191
    ...     FakeTransaction(), BufferLogger())
7793.5.9 by Bjorn Tillenius
Rename updateRemoteProduct() to something more sensible.
192
    >>> updater.updateByBugTrackerType(BugTrackerType.RT)
7793.5.5 by Bjorn Tillenius
Make sure that Products having a remote_product set aren't updated.
193
    >>> print rt_product.remote_product
194
    already-set
7793.5.6 by Bjorn Tillenius
Commit the transaction after each update.
195
7793.5.11 by Bjorn Tillenius
Whitespace fixes.
196
14575.2.1 by Jeroen Vermeulen
Lint.
197
Transaction handling
198
....................
7793.5.6 by Bjorn Tillenius
Commit the transaction after each update.
199
200
To avoid long-running write transactions, the transaction is committed
201
after each product's remote_product has been updated.
202
8342.5.11 by Gavin Panella
Amend some more tests.
203
    >>> switch_db_to_launchpad()
204
7793.5.6 by Bjorn Tillenius
Commit the transaction after each update.
205
    >>> for index in range(3):
206
    ...     rt_product = factory.makeProduct(official_malone=False)
207
    ...     rt = factory.makeBugTracker(
208
    ...         bugtrackertype=BugTrackerType.RT)
209
    ...     rt_product.bugtracker = rt
210
    ...     rt_bugtask = factory.makeBugTask(target=rt_product)
211
    ...     rt_bugwatch = factory.makeBugWatch(
212
    ...         '84', bugtracker=rt, bug=rt_bugtask.bug)
213
    ...     rt_bugtask.bugwatch = rt_bugwatch
8342.5.11 by Gavin Panella
Amend some more tests.
214
215
    >>> switch_db_to_updateremoteproduct()
216
7793.5.8 by Bjorn Tillenius
Rename TestRemoteProductUpdater to show intent.
217
    >>> updater = NoNetworkRemoteProductUpdater(
12070.1.4 by Tim Penhey
Move FakeLogger and BufferLogger to lp.services.log.logging and delete the QuietFakeLogger.
218
    ...     FakeTransaction(log_calls=True), BufferLogger())
7840.1.1 by Graham Binns
Fixed bug 333354.
219
    >>> updater.print_method_calls = False
7793.5.9 by Bjorn Tillenius
Rename updateRemoteProduct() to something more sensible.
220
    >>> updater.updateByBugTrackerType(BugTrackerType.RT)
7840.1.1 by Graham Binns
Fixed bug 333354.
221
    Initializing DB for bugs: [u'84'].
222
    COMMIT
223
    Initializing DB for bugs: [u'84'].
224
    COMMIT
225
    Initializing DB for bugs: [u'84'].
7793.5.6 by Bjorn Tillenius
Commit the transaction after each update.
226
    COMMIT
7793.5.13 by Bjorn Tillenius
Add a cronscript for updating the remote_product.
227
228
14575.2.1 by Jeroen Vermeulen
Lint.
229
Error handling
230
..............
7844.2.1 by Graham Binns
update-remote-product should now handle errors.
231
232
If the ExternalBugTracker raises any BugWatchUpdateErrors,
233
updateByBugTrackerType() will simply log the error and then continue.
234
This is a simplistic approach but it means that problems with one bug
235
tracker don't break the run for all bug trackers.
236
8342.5.11 by Gavin Panella
Amend some more tests.
237
    >>> switch_db_to_launchpad()
238
7844.2.1 by Graham Binns
update-remote-product should now handle errors.
239
    >>> new_rt_product = factory.makeProduct(
240
    ...     name='fooix', official_malone=False)
241
    >>> new_rt_product.bugtracker = rt
242
    >>> new_rt_bugtask = factory.makeBugTask(target=new_rt_product)
243
    >>> new_rt_bugwatch = factory.makeBugWatch(
244
    ...     '42', bugtracker=rt, bug=new_rt_bugtask.bug)
245
    >>> new_rt_bugtask.bugwatch = new_rt_bugwatch
246
8342.5.11 by Gavin Panella
Amend some more tests.
247
    >>> switch_db_to_updateremoteproduct()
8523.3.1 by Gavin Panella
Bugs tree reorg after automated migration.
248
    >>> from lp.bugs.externalbugtracker.base import (
7844.2.1 by Graham Binns
update-remote-product should now handle errors.
249
    ...     BugNotFound, BugWatchUpdateError)
250
    >>> class BrokenOnInitExternalBugTracker(
251
    ...         FakeExternalBugTracker):
252
    ...     def initializeRemoteBugDB(self, bug_ids):
253
    ...         raise BugWatchUpdateError("This here is an error")
254
255
    >>> updater.logger = FakeLogger()
256
    >>> updater.external_bugtracker_to_return = (
257
    ...     BrokenOnInitExternalBugTracker)
258
    >>> updater.updateByBugTrackerType(BugTrackerType.RT)
12070.1.41 by Tim Penhey
Changed my mind about the colon. Removed it.
259
    INFO  1 projects using RT needing updating.
260
    DEBUG Trying to update fooix
7844.2.1 by Graham Binns
update-remote-product should now handle errors.
261
    ERROR Unable to set remote_product for 'fooix': This here is an error
262
263
    >>> class BrokenOnGetRemoteProductExternalBugTracker(
264
    ...         FakeExternalBugTracker):
265
    ...     def getRemoteProduct(self, remote_bug):
266
    ...         raise BugNotFound("Didn't find bug %s." % remote_bug)
267
268
    >>> updater.external_bugtracker_to_return = (
269
    ...     BrokenOnGetRemoteProductExternalBugTracker)
270
    >>> updater.updateByBugTrackerType(BugTrackerType.RT)
12070.1.41 by Tim Penhey
Changed my mind about the colon. Removed it.
271
    INFO  1 projects using RT needing updating.
272
    DEBUG Trying to update fooix
7844.2.1 by Graham Binns
update-remote-product should now handle errors.
273
    Initializing DB for bugs: [u'42'].
12070.1.41 by Tim Penhey
Changed my mind about the colon. Removed it.
274
    ERROR Unable to set remote_product for 'fooix': Didn't find bug 42.
7844.2.1 by Graham Binns
update-remote-product should now handle errors.
275
7849.2.1 by Graham Binns
Fixed bug 334448
276
AssertionErrors are also handled.
277
278
    >>> class RaisesAssertionErrorExternalBugTracker(FakeExternalBugTracker):
279
    ...     def initializeRemoteBugDB(self, bug_ids):
280
    ...         assert True == False, "True isn't False!"
281
282
    >>> updater.external_bugtracker_to_return = (
283
    ...     RaisesAssertionErrorExternalBugTracker)
284
    >>> updater.updateByBugTrackerType(BugTrackerType.RT)
12070.1.41 by Tim Penhey
Changed my mind about the colon. Removed it.
285
    INFO  1 projects using RT needing updating.
286
    DEBUG Trying to update fooix
287
    ERROR Unable to set remote_product for 'fooix': True isn't False!
7849.2.1 by Graham Binns
Fixed bug 334448
288