Merge lp:~stevenk/launchpad/set-recipe-text-bad-data into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 12385
Proposed branch: lp:~stevenk/launchpad/set-recipe-text-bad-data
Merge into: lp:launchpad
Diff against target: 141 lines (+51/-45)
2 files modified
lib/lp/code/browser/sourcepackagerecipe.py (+38/-45)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+13/-0)
To merge this branch: bzr merge lp:~stevenk/launchpad/set-recipe-text-bad-data
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+48853@code.launchpad.net

Commit message

[r=lifeless][bug=592513,683321] Don't OOPS if an invalid branch is used while editing a recipe.

Description of the change

Fix bug 683321, 'Editing recipe oopses if invalid branch used'. The real error was that the request_action for creating recipes dealt with four exceptions, but request_action for editing recipes only dealt with three. This means if the branch passed in was invalid, the exception wouldn't be caught and would leak up the stack, causing the OOPS.

I have fixed this by refactoring the exception handler to a parent class of both AddView and EditView, RecipeTextValidatorMixin. While performing this refactoring, I fixed bug 592513 by accident and removed the XXX.

I added a test that checks the error is raised correctly when a recipe is edited and is submitted with a branch that doesn't exist.

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

Broadly good; I have some tweaks that may help you.

Firstly, a docstring on the method might be nice, its not clear why you only accept *args, for instance.

Secondly to handle the fact that an error occured, you might like to raise a specific exception instead of returning None.

thusly:
class ErrorHandled(Exception):
    """A field error occured and was handled."""

try:
    self.error_handler(...)
except ErrorHandled:
    return

that, to me, would make it clearer than checking for 'is None'. What do you think?

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

To clarify what I mean, the error_handler guts would then be
+ try:
+ return callable(*args)
+ except TooNewRecipeFormat:
....
raise ErrorHandled()

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
2--- lib/lp/code/browser/sourcepackagerecipe.py 2011-02-14 00:58:45 +0000
3+++ lib/lp/code/browser/sourcepackagerecipe.py 2011-02-15 11:35:12 +0000
4@@ -399,6 +399,10 @@
5 "the owner of the recipe ."))
6
7
8+class ErrorHandled(Exception):
9+ """A field error occured and was handled."""
10+
11+
12 class RecipeTextValidatorMixin:
13 """Class to validate that the Source Package Recipe text is valid."""
14
15@@ -414,6 +418,25 @@
16 except RecipeParseError, error:
17 self.setFieldError('recipe_text', str(error))
18
19+ def error_handler(self, callable, *args, **kwargs):
20+ try:
21+ return callable(*args)
22+ except TooNewRecipeFormat:
23+ self.setFieldError(
24+ 'recipe_text',
25+ 'The recipe format version specified is not available.')
26+ except ForbiddenInstructionError, e:
27+ self.setFieldError(
28+ 'recipe_text',
29+ 'The bzr-builder instruction "%s" is not permitted '
30+ 'here.' % e.instruction_name)
31+ except NoSuchBranch, e:
32+ self.setFieldError(
33+ 'recipe_text', '%s is not a branch on Launchpad.' % e.name)
34+ except PrivateBranchRecipe, e:
35+ self.setFieldError('recipe_text', str(e))
36+ raise ErrorHandled()
37+
38
39 class RelatedBranchesWidget(Widget):
40 """A widget to render the related branches for a recipe."""
41@@ -524,36 +547,20 @@
42
43 @action('Create Recipe', name='create')
44 def request_action(self, action, data):
45+ owner = data['owner']
46+ if data['use_ppa'] == CREATE_NEW:
47+ ppa_name = data.get('ppa_name', None)
48+ ppa = owner.createPPA(ppa_name)
49+ else:
50+ ppa = data['daily_build_archive']
51 try:
52- owner = data['owner']
53- if data['use_ppa'] == CREATE_NEW:
54- ppa_name = data.get('ppa_name', None)
55- ppa = owner.createPPA(ppa_name)
56- else:
57- ppa = data['daily_build_archive']
58- source_package_recipe = getUtility(
59- ISourcePackageRecipeSource).new(
60- self.user, owner, data['name'],
61- data['recipe_text'], data['description'], data['distros'],
62- ppa, data['build_daily'])
63+ source_package_recipe = self.error_handler(
64+ getUtility(ISourcePackageRecipeSource).new,
65+ self.user, owner, data['name'],
66+ data['recipe_text'], data['description'], data['distros'],
67+ ppa, data['build_daily'])
68 Store.of(source_package_recipe).flush()
69- except TooNewRecipeFormat:
70- self.setFieldError(
71- 'recipe_text',
72- 'The recipe format version specified is not available.')
73- return
74- except ForbiddenInstructionError:
75- # XXX: bug=592513 We shouldn't be hardcoding "run" here.
76- self.setFieldError(
77- 'recipe_text',
78- 'The bzr-builder instruction "run" is not permitted here.')
79- return
80- except NoSuchBranch, e:
81- self.setFieldError(
82- 'recipe_text', '%s is not a branch on Launchpad.' % e.name)
83- return
84- except PrivateBranchRecipe, e:
85- self.setFieldError('recipe_text', str(e))
86+ except ErrorHandled:
87 return
88
89 self.next_url = canonical_url(source_package_recipe)
90@@ -638,24 +645,10 @@
91 recipe = parser.parse()
92 if self.context.builder_recipe != recipe:
93 try:
94- self.context.setRecipeText(recipe_text)
95+ self.error_handler(self.context.setRecipeText, recipe_text)
96 changed = True
97- except TooNewRecipeFormat:
98- self.setFieldError(
99- 'recipe_text',
100- 'The recipe format version specified is not available.')
101- return
102- except ForbiddenInstructionError:
103- # XXX: bug=592513 We shouldn't be hardcoding "run" here.
104- self.setFieldError(
105- 'recipe_text',
106- 'The bzr-builder instruction "run" is not permitted'
107- ' here.')
108- return
109- except PrivateBranchRecipe, e:
110- self.setFieldError('recipe_text', str(e))
111- return
112-
113+ except ErrorHandled:
114+ return
115
116 distros = data.pop('distros')
117 if distros != self.context.distroseries:
118
119=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
120--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2011-02-14 07:47:07 +0000
121+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2011-02-15 11:35:12 +0000
122@@ -986,6 +986,19 @@
123 get_message_text(browser, 1),
124 'Recipe may not refer to private branch: %s' % bzr_identity)
125
126+ def test_edit_recipe_no_branch(self):
127+ # If a user tries to set a source package recipe to use a branch
128+ # that isn't registred, they will get an error.
129+ recipe = self.factory.makeSourcePackageRecipe(owner=self.user)
130+ no_branch_recipe_text = recipe.recipe_text[:-4]
131+ expected_name = recipe.base_branch.unique_name[:-3]
132+ browser = self.getViewBrowser(recipe, '+edit')
133+ browser.getControl('Recipe text').value = no_branch_recipe_text
134+ browser.getControl('Update Recipe').click()
135+ self.assertEqual(
136+ get_message_text(browser, 1),
137+ 'lp://dev/%s is not a branch on Launchpad.' % expected_name)
138+
139 def _test_edit_recipe_with_no_related_branches(self, recipe):
140 # The Related Branches section should not appear if there are no
141 # related branches.