Merge lp:~mars/tarmac/fix-merge-error-updates-proposals into lp:~launchpad/tarmac/lp-tarmac

Proposed by Māris Fogels
Status: Merged
Approved by: Māris Fogels
Approved revision: 394
Merged at revision: 383
Proposed branch: lp:~mars/tarmac/fix-merge-error-updates-proposals
Merge into: lp:~launchpad/tarmac/lp-tarmac
Diff against target: 285 lines (+166/-19)
4 files modified
tarmac/bin/commands.py (+18/-5)
tarmac/branch.py (+14/-12)
tarmac/tests/test_branch.py (+35/-0)
tarmac/tests/test_commands.py (+99/-2)
To merge this branch: bzr merge lp:~mars/tarmac/fix-merge-error-updates-proposals
Reviewer Review Type Date Requested Status
Diogo Matsubara (community) Approve
Review via email: mp+43135@code.launchpad.net

Commit message

Fix the handling of TarmacMergeErrors raised by plugins.

Description of the change

Hi,

This branch fixes the handling of TarmacMergeErrors raised by plugins. I fixed this by either updating all affected merge proposals manually, or by passing the proposal update responsibilites back to the Branch object. Tests are included for the hooks that should handle TarmacMergeErrors. I also did a bit of refactoring to support cleaner interaction between the Command and Branch objects.

All tests pass.

Maris

To post a comment you must log in.
Revision history for this message
Diogo Matsubara (matsubara) wrote :

Hi Mars,

I have a small comment about some commented out code on lines 39 and 40. It looks like you commented out the code but the intention was to remove it.

Another thing is that you define the MergeErrorInjector twice in test code. Is there a reason for that?

Other than that, it looks good. Thanks!

Revision history for this message
Diogo Matsubara (matsubara) :
review: Approve
Revision history for this message
Māris Fogels (mars) wrote :

Hi Diogo,

Thanks, you are right, 39 and 40 are stale code. Also, I defined the MergeInjector twice because it uses class-level variables (yuck) to track if the method was called. Not the cleanest, but it works for now.

Thank you for the review!

Maris

Revision history for this message
Māris Fogels (mars) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'tarmac/bin/commands.py'
--- tarmac/bin/commands.py 2010-11-29 13:51:42 +0000
+++ tarmac/bin/commands.py 2010-12-15 16:38:15 +0000
@@ -160,8 +160,9 @@
160 while True:160 while True:
161 try:161 try:
162 (hook, source, proposal) = results.next()162 (hook, source, proposal) = results.next()
163 self.logger.debug('Firing %s hook' % hook)163
164 tarmac_hooks[hook].fire(self, target, source, proposal)164 self._fire_merge_hook(
165 results, hook, target, source, proposal)
165 except StopIteration:166 except StopIteration:
166 break167 break
167 # This except is here because we need the else and can't have it168 # This except is here because we need the else and can't have it
@@ -174,6 +175,16 @@
174 finally:175 finally:
175 target.cleanup()176 target.cleanup()
176177
178 def _fire_merge_hook(self, callback_gen, hook, target, source, proposal):
179 '''Fire a tarmac hook and correctly handle any errors raised.'''
180 self.logger.debug('Firing %s hook' % hook)
181 try:
182 tarmac_hooks[hook].fire(self, target, source, proposal)
183 except TarmacMergeError, e:
184 # Pass merge errors from plugins back to the target
185 # branch for handling and cleanup.
186 callback_gen.throw(e)
187
177 def run(self, branch_url=None, debug=False, http_debug=False,188 def run(self, branch_url=None, debug=False, http_debug=False,
178 launchpad=None, imply_commit_message=False):189 launchpad=None, imply_commit_message=False):
179 if debug:190 if debug:
@@ -243,14 +254,16 @@
243 try:254 try:
244 (hook, source, proposal) = results.next()255 (hook, source, proposal) = results.next()
245 collected_proposals.append((source, proposal))256 collected_proposals.append((source, proposal))
246 self.logger.debug('Firing %s hook' % hook)257 self._fire_merge_hook(
247 tarmac_hooks[hook].fire(self, target, source, proposal)258 results, hook, target, source, proposal)
248 except StopIteration:259 except StopIteration:
249 break260 break
250 try:261 try:
251 tarmac_hooks['tarmac_bundle_merge'].fire(262 tarmac_hooks['tarmac_bundle_merge'].fire(
252 self, target, collected_proposals)263 self, target, collected_proposals)
253 except TarmacMergeError:264 except TarmacMergeError, failure:
265 for source, proposal in collected_proposals:
266 target.update_proposal_with_error(proposal, failure)
254 # Uncommit and revert changes up to the point where target267 # Uncommit and revert changes up to the point where target
255 # was before the merge.268 # was before the merge.
256 target.uncommit_and_revert(restore_revno)269 target.uncommit_and_revert(restore_revno)
257270
=== modified file 'tarmac/branch.py'
--- tarmac/branch.py 2010-11-29 13:51:42 +0000
+++ tarmac/branch.py 2010-12-15 16:38:15 +0000
@@ -198,16 +198,7 @@
198 yield ("tarmac_pre_commit", source, proposal)198 yield ("tarmac_pre_commit", source, proposal)
199199
200 except TarmacMergeError, failure:200 except TarmacMergeError, failure:
201 self.logger.warn(201 self.update_proposal_with_error(proposal, failure)
202 u'Merging %(source)s into %(target)s failed: %(msg)s' %
203 {'source': proposal.source_branch.display_name,
204 'target': proposal.target_branch.display_name,
205 'msg': str(failure)})
206 if failure.comment:
207 comment = failure.comment
208 else:
209 comment = str(failure)
210 self.update_proposal(proposal, comment)
211 self.cleanup()202 self.cleanup()
212 continue203 continue
213 except PointlessMerge:204 except PointlessMerge:
@@ -246,6 +237,19 @@
246 proposal.setStatus(status=u'Needs review')237 proposal.setStatus(status=u'Needs review')
247 proposal.lp_save()238 proposal.lp_save()
248239
240 def update_proposal_with_error(self, proposal, error_exception):
241 """Update a merge proposal with a TarmacMergeError exception."""
242 self.logger.warn(
243 u'Merging %(source)s into %(target)s failed: %(msg)s' %
244 {'source': proposal.source_branch.display_name,
245 'target': proposal.target_branch.display_name,
246 'msg': str(error_exception)})
247 if error_exception.comment:
248 comment = error_exception.comment
249 else:
250 comment = str(error_exception)
251 self.update_proposal(proposal, comment)
252
249 def _get_reviews(self, proposal):253 def _get_reviews(self, proposal):
250 """Get the set of reviews from the proposal."""254 """Get the set of reviews from the proposal."""
251 reviews = []255 reviews = []
@@ -396,5 +400,3 @@
396 self._proposals.append(entry)400 self._proposals.append(entry)
397401
398 return self._proposals402 return self._proposals
399
400
401403
=== modified file 'tarmac/tests/test_branch.py'
--- tarmac/tests/test_branch.py 2010-11-11 19:36:16 +0000
+++ tarmac/tests/test_branch.py 2010-12-15 16:38:15 +0000
@@ -23,6 +23,7 @@
23from bzrlib.errors import PointlessMerge23from bzrlib.errors import PointlessMerge
2424
25from tarmac import branch25from tarmac import branch
26from tarmac.exceptions import TarmacMergeError
26from tarmac.tests import BranchTestCase27from tarmac.tests import BranchTestCase
27from tarmac.tests.mock import MockLPBranch, Thing28from tarmac.tests.mock import MockLPBranch, Thing
2829
@@ -209,3 +210,37 @@
209 self.assertEqual(210 self.assertEqual(
210 rev.properties.get('merge_url'),211 rev.properties.get('merge_url'),
211 'http://code.launchpad.net/proposal1')212 'http://code.launchpad.net/proposal1')
213
214 def test_merge_error_updates_proposal(self):
215 # Setup
216 branch = self.branch1
217 proposal = self.proposals[0]
218
219 # We need to have valid proposals for the to-be merged branch.
220 proposal.reviewed_revid=self.branch2.bzr_branch.last_revision()
221 branch._proposals = [proposal]
222
223 # Ensure the merge fails.
224 short_message = 'short error message'
225 long_message = 'long error message'
226 def injects_merge_error(*args):
227 raise TarmacMergeError(short_message, long_message)
228 branch.merge = injects_merge_error
229
230 # Override the default setStatus behaviour, which is a NOP.
231 def trace_set_status(status=None):
232 proposal.queue_status = status
233 proposal.setStatus = trace_set_status
234 proposal.setStatus(u'Approved')
235
236 # Act
237 try:
238 for callback in branch.merge_branches():
239 pass
240 except TarmacMergeError:
241 pass
242
243 # Verify
244 self.assertTrue(hasattr(self, 'error'))
245 self.assertEqual(self.error.comment, long_message)
246 self.assertEqual(proposal.queue_status, u'Needs review')
212247
=== modified file 'tarmac/tests/test_commands.py'
--- tarmac/tests/test_commands.py 2010-11-25 14:36:37 +0000
+++ tarmac/tests/test_commands.py 2010-12-15 16:38:15 +0000
@@ -8,7 +8,9 @@
8from tarmac.bin.registry import CommandRegistry8from tarmac.bin.registry import CommandRegistry
9from tarmac.branch import Branch9from tarmac.branch import Branch
10from tarmac.config import TarmacConfig10from tarmac.config import TarmacConfig
11from tarmac.exceptions import UnapprovedChanges11from tarmac.exceptions import TarmacMergeError, UnapprovedChanges
12from tarmac.hooks import TarmacHookPoint, tarmac_hooks
13from tarmac.plugins import TarmacPlugin
12from tarmac.tests import TarmacTestCase, BranchTestCase14from tarmac.tests import TarmacTestCase, BranchTestCase
13from tarmac.tests.mock import MockLPBranch, Thing15from tarmac.tests.mock import MockLPBranch, Thing
1416
@@ -286,6 +288,39 @@
286 u'%s into lp:branch1, which is not '288 u'%s into lp:branch1, which is not '
287 u'Superseded.' % branch3.lp_branch.bzr_identity)289 u'Superseded.' % branch3.lp_branch.bzr_identity)
288290
291 def test_plugin_merge_errors_are_handled(self):
292 # Setup
293 self.proposals[1].reviewed_revid = \
294 self.branch2.bzr_branch.last_revision()
295
296 # Create a plugin that throws an error
297 class MergeErrorInjector(TarmacPlugin):
298 has_run = False
299 long_message = 'a long error message'
300
301 def run(self, *args):
302 self.__class__.has_run = True
303 raise TarmacMergeError('message', self.long_message)
304
305 plugin = MergeErrorInjector()
306
307 # Replace the original hook point with our custom hook and plugin
308 original_hookpoint = tarmac_hooks.pop('tarmac_pre_commit')
309 self.addCleanup(tarmac_hooks.__setitem__, 'tarmac_pre_commit',
310 original_hookpoint)
311
312 new_hookpoint = TarmacHookPoint('tarmac_pre_commit', '', (0), False)
313 new_hookpoint.hook(plugin, 'Throws a merge error')
314 tarmac_hooks.create_hook(new_hookpoint)
315
316 # Act
317 self.command.run(launchpad=self.launchpad)
318
319 # Verify
320 self.assertTrue(MergeErrorInjector.has_run)
321 self.assertIsInstance(self.error, TarmacMergeError)
322 self.assertEqual(self.error.comment, MergeErrorInjector.long_message)
323
289324
290class TestBundleMergeCommmand(BranchTestCase):325class TestBundleMergeCommmand(BranchTestCase):
291326
@@ -324,12 +359,48 @@
324 self.branch3.lp_branch.revision_count += 1359 self.branch3.lp_branch.revision_count += 1
325 self.branches[1].landing_candidates = self.proposals360 self.branches[1].landing_candidates = self.proposals
326361
327 def test_run(self):362 def approve_all_proposals(self):
363 '''Approve all the merge proposals to be used in testing.'''
328 self.proposals[1].reviewed_revid = \364 self.proposals[1].reviewed_revid = \
329 self.branch2.bzr_branch.last_revision()365 self.branch2.bzr_branch.last_revision()
330 self.proposals[2].reviewed_revid = \366 self.proposals[2].reviewed_revid = \
331 self.branch3.bzr_branch.last_revision()367 self.branch3.bzr_branch.last_revision()
368
369 def install_merge_error_injecting_plugin(self, hook_name):
370 '''Define and install a plugin that throws a TarmacMergeError.'''
371 class MergeErrorInjector(TarmacPlugin):
372 has_run = False
373 long_message = 'a long error message'
374
375 def run(self, *args):
376 self.__class__.has_run = True
377 raise TarmacMergeError('message', self.long_message)
378
379 plugin = MergeErrorInjector()
380 self.install_plugin(plugin, hook_name)
381 return plugin
382
383 def install_plugin(self, plugin, hook_name):
384 '''Install a plugin at the given hookpoint name.
385
386 Replaces all existing hooks at that point.
387 '''
388 original_hookpoint = tarmac_hooks.pop(hook_name)
389 self.addCleanup(tarmac_hooks.__setitem__, hook_name,
390 original_hookpoint)
391
392 new_hookpoint = TarmacHookPoint(hook_name, '', (0), False)
393 new_hookpoint.hook(plugin, 'Custom hook function: {0}'.format(plugin))
394 tarmac_hooks.create_hook(new_hookpoint)
395
396 def test_run(self):
397 # Setup
398 self.approve_all_proposals()
399
400 # Act
332 self.command.run(launchpad=self.launchpad)401 self.command.run(launchpad=self.launchpad)
402
403 # Verify
333 # Check the 2 approved proposals were merged into target.404 # Check the 2 approved proposals were merged into target.
334 target = self.branches[1]._internal_bzr_branch405 target = self.branches[1]._internal_bzr_branch
335 last_rev = target.last_revision()406 last_rev = target.last_revision()
@@ -343,3 +414,29 @@
343 u'http://code.launchpad.net/proposal2')414 u'http://code.launchpad.net/proposal2')
344 #XXX need to grab the previuous revision as well and do the415 #XXX need to grab the previuous revision as well and do the
345 # assertions.416 # assertions.
417
418 def test_plugin_merge_errors_are_handled(self):
419 # Setup
420 self.approve_all_proposals()
421 plugin = self.install_merge_error_injecting_plugin('tarmac_pre_commit')
422
423 # Act
424 self.command.run(launchpad=self.launchpad)
425
426 # Verify
427 self.assertTrue(plugin.has_run)
428 self.assertIsInstance(self.error, TarmacMergeError)
429 self.assertEqual(self.error.comment, plugin.long_message)
430
431 def test_tarmac_bundle_merge_hookpoint_errors_are_handled(self):
432 # Setup
433 self.approve_all_proposals()
434 plugin = self.install_merge_error_injecting_plugin('tarmac_bundle_merge')
435
436 # Act
437 self.command.run(launchpad=self.launchpad)
438
439 # Verify
440 self.assertTrue(plugin.has_run)
441 self.assertIsInstance(self.error, TarmacMergeError)
442 self.assertEqual(self.error.comment, plugin.long_message)

Subscribers

People subscribed via source and target branches

to all changes: