Merge lp:~sinzui/launchpad/6-plural-forms into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Merged at revision: 13068
Proposed branch: lp:~sinzui/launchpad/6-plural-forms
Merge into: lp:launchpad
Diff against target: 121 lines (+51/-14)
2 files modified
lib/lp/translations/browser/tests/test_translationmessage_view.py (+41/-3)
lib/lp/translations/browser/translationmessage.py (+10/-11)
To merge this branch: bzr merge lp:~sinzui/launchpad/6-plural-forms
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+61322@code.launchpad.net

Description of the change

Do not raise an AssertionError when the MAX_PLURAL_FORMS is honoured.

    Launchpad bug: https://bugs.launchpad.net/bugs/708875
    Pre-implementation: no one

OOPS-1853D2494 shows that a user submitted 6 translations, each field indexed
from 0 to 5, but an assert error was raised stating that more than 6
translations were submitted.

JTV wrote:
The check does something like…

    for pluralform in xrange(6):
        # …
        if pluralform not in translations_submitted_in_form:
            break
        # …
    else:
        raise AssertionError("More than 6 plural forms were submitted!")

That goes wrong if you submit exactly 6 plural forms. The loop then never hits
a plural-form number that didn't have a translation submitted, and drops into
the "else" part.

The loop requires that 'break' be called during when an empty translation
is found while iterating over exactly 6 items. It is impossible to submit
6 translations! The assertion exists to ensure that the generated form
fields match the configured number of possible translations.

--------------------------------------------------------------------

RULES

    * Since the assertion is predicated on the existence of a 7th field with
      the index of 6, a guard can check for the existence of the field before
      there is an attempt to store the submitted data.

QA

    * Visit https://translations.qastaging.launchpad.net/wubi/trunk/+pots/wubi/ar/5/+translate
    * Submit 6 translations/suggestions
    * Verify the form does not oops.

LINT

    lib/lp/translations/browser/translationmessage.py
    lib/lp/translations/browser/tests/test_translationmessage_view.py

TEST

    ./bin/test -vv -t TestCurrentTranslationMessagePageView.

IMPLEMENTATION

Spent hours constructing a test to instrument the base view to reproduce the
the error. Moved the assertion before the loop to store the translations
since the work will be lost if there is an error. Lint reported:
    1123: local variable 'translation' is assigned to but never used
Which reveals an if-statement and assignment have no purpose. I checked that
vars() and locals() were not used.
    lib/lp/translations/browser/translationmessage.py
    lib/lp/translations/browser/tests/test_translationmessage_view.py

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/translations/browser/tests/test_translationmessage_view.py'
2--- lib/lp/translations/browser/tests/test_translationmessage_view.py 2010-12-10 12:14:52 +0000
3+++ lib/lp/translations/browser/tests/test_translationmessage_view.py 2011-05-17 22:09:59 +0000
4@@ -304,9 +304,26 @@
5
6 layer = ZopelessDatabaseLayer
7
8- def _makeView(self):
9- message = self.factory.makeCurrentTranslationMessage()
10- request = LaunchpadTestRequest()
11+ def _makeView(self, with_form=False):
12+ potemplate = self.factory.makePOTemplate()
13+ pofile = self.factory.makePOFile(potemplate=potemplate)
14+ potmsgset = self.factory.makePOTMsgSet(potemplate)
15+ message = self.factory.makeCurrentTranslationMessage(
16+ pofile=pofile, potmsgset=potmsgset)
17+ message.browser_pofile = pofile
18+ form = {}
19+ if with_form:
20+ msgset_id_field = 'msgset_%d' % potmsgset.id
21+ form[msgset_id_field] = u'poit'
22+ base_field_name = 'msgset_%d_%s_translation_' % (
23+ message.potmsgset.id, pofile.language.code)
24+ # Add the expected plural forms fields.
25+ for plural_form in xrange(TranslationConstants.MAX_PLURAL_FORMS):
26+ field_name = '%s%d_new' % (base_field_name, plural_form)
27+ form[field_name] = u'snarf'
28+ url = '/%s/%s/%s/+translate' % (
29+ canonical_url(potemplate), pofile.language.code, 1)
30+ request = LaunchpadTestRequest(SERVER_URL=url, form=form)
31 view = CurrentTranslationMessagePageView(message, request)
32 view.lock_timestamp = datetime.now(pytz.utc)
33 return view
34@@ -353,6 +370,27 @@
35 view = self._makeView()
36 self.assertRaises(UnexpectedFormData, view._checkSubmitConditions)
37
38+ def test_max_plural_forms_fields_accepted(self):
39+ # The plural forms translation fields are accepted if there are less
40+ # than or equal to TranslationConstants.MAX_PLURAL_FORM.
41+ view = self._makeView(with_form=True)
42+ view.initialize()
43+ self.assertEqual(
44+ None, view._extractFormPostedTranslations(view.context.potmsgset))
45+
46+ def test_max_plural_forms_fields_greater_error(self):
47+ # An AssertionError is raised if the number of plural forms
48+ # translation fields exceed TranslationConstants.MAX_PLURAL_FORMS.
49+ view = self._makeView(with_form=True)
50+ view.initialize()
51+ # Add a field that is greater than the expected MAX_PLURAL_FORMS.
52+ field_name = 'msgset_%d_%s_translation_%d_new' % (
53+ view.context.potmsgset.id, view.pofile.language.code,
54+ TranslationConstants.MAX_PLURAL_FORMS)
55+ view.request.form[field_name] = u'snarf'
56+ self.assertRaises(AssertionError,
57+ view._extractFormPostedTranslations, view.context.potmsgset)
58+
59
60 class TestHelpers(TestCaseWithFactory):
61
62
63=== modified file 'lib/lp/translations/browser/translationmessage.py'
64--- lib/lp/translations/browser/translationmessage.py 2011-03-30 11:19:30 +0000
65+++ lib/lp/translations/browser/translationmessage.py 2011-05-17 22:09:59 +0000
66@@ -131,7 +131,6 @@
67 """
68 schema, netloc, path, parameters, query, fragment = (
69 urlparse(str(request.URL)))
70-
71 # For safety, delete the start and batch variables, if they
72 # appear in the URL. The situation in which 'start' appears
73 # today is when the alternative language form is posted back and
74@@ -336,7 +335,6 @@
75 UTC = pytz.timezone('UTC')
76 self.lock_timestamp = datetime.datetime.now(UTC)
77
78-
79 # The batch navigator needs to be initialized early, before
80 # _submitTranslations is called; the reason for this is that
81 # _submitTranslations, in the case of no errors, redirects to
82@@ -746,6 +744,16 @@
83 msgset_ID_LANGCODE_translation_ = 'msgset_%d_%s_translation_' % (
84 potmsgset_ID, language_code)
85
86+ msgset_ID_LANGCODE_translation_GREATER_PLURALFORM_new = '%s%d_new' % (
87+ msgset_ID_LANGCODE_translation_,
88+ TranslationConstants.MAX_PLURAL_FORMS)
89+ if msgset_ID_LANGCODE_translation_GREATER_PLURALFORM_new in form:
90+ # The plural form translation generation rules created too many
91+ # fields, or the form was hacked.
92+ raise AssertionError(
93+ 'More than %d plural forms were submitted!'
94+ % TranslationConstants.MAX_PLURAL_FORMS)
95+
96 # Extract the translations from the form, and store them in
97 # self.form_posted_translations. We try plural forms in turn,
98 # starting at 0.
99@@ -822,9 +830,6 @@
100 if store:
101 self.form_posted_translations_has_store_flag[
102 potmsgset].append(pluralform)
103- else:
104- raise AssertionError('More than %d plural forms were submitted!'
105- % TranslationConstants.MAX_PLURAL_FORMS)
106
107 def _observeTranslationUpdate(self, potmsgset):
108 """Observe that a translation was updated for the potmsgset.
109@@ -1109,12 +1114,6 @@
110 other_translation = self.getOtherTranslation(index)
111 shared_translation = self.getSharedTranslation(index)
112 submitted_translation = self.getSubmittedTranslation(index)
113- if (submitted_translation is None and
114- self.user_is_official_translator):
115- # We don't have anything to show as the submitted translation
116- # and the user is the official one. We prefill the 'New
117- # translation' field with the current translation.
118- translation = current_translation
119 is_multi_line = (count_lines(current_translation) > 1 or
120 count_lines(submitted_translation) > 1 or
121 count_lines(self.singular_text) > 1 or