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
=== modified file 'lib/lp/translations/browser/tests/test_translationmessage_view.py'
--- lib/lp/translations/browser/tests/test_translationmessage_view.py 2010-12-10 12:14:52 +0000
+++ lib/lp/translations/browser/tests/test_translationmessage_view.py 2011-05-17 22:09:59 +0000
@@ -304,9 +304,26 @@
304304
305 layer = ZopelessDatabaseLayer305 layer = ZopelessDatabaseLayer
306306
307 def _makeView(self):307 def _makeView(self, with_form=False):
308 message = self.factory.makeCurrentTranslationMessage()308 potemplate = self.factory.makePOTemplate()
309 request = LaunchpadTestRequest()309 pofile = self.factory.makePOFile(potemplate=potemplate)
310 potmsgset = self.factory.makePOTMsgSet(potemplate)
311 message = self.factory.makeCurrentTranslationMessage(
312 pofile=pofile, potmsgset=potmsgset)
313 message.browser_pofile = pofile
314 form = {}
315 if with_form:
316 msgset_id_field = 'msgset_%d' % potmsgset.id
317 form[msgset_id_field] = u'poit'
318 base_field_name = 'msgset_%d_%s_translation_' % (
319 message.potmsgset.id, pofile.language.code)
320 # Add the expected plural forms fields.
321 for plural_form in xrange(TranslationConstants.MAX_PLURAL_FORMS):
322 field_name = '%s%d_new' % (base_field_name, plural_form)
323 form[field_name] = u'snarf'
324 url = '/%s/%s/%s/+translate' % (
325 canonical_url(potemplate), pofile.language.code, 1)
326 request = LaunchpadTestRequest(SERVER_URL=url, form=form)
310 view = CurrentTranslationMessagePageView(message, request)327 view = CurrentTranslationMessagePageView(message, request)
311 view.lock_timestamp = datetime.now(pytz.utc)328 view.lock_timestamp = datetime.now(pytz.utc)
312 return view329 return view
@@ -353,6 +370,27 @@
353 view = self._makeView()370 view = self._makeView()
354 self.assertRaises(UnexpectedFormData, view._checkSubmitConditions)371 self.assertRaises(UnexpectedFormData, view._checkSubmitConditions)
355372
373 def test_max_plural_forms_fields_accepted(self):
374 # The plural forms translation fields are accepted if there are less
375 # than or equal to TranslationConstants.MAX_PLURAL_FORM.
376 view = self._makeView(with_form=True)
377 view.initialize()
378 self.assertEqual(
379 None, view._extractFormPostedTranslations(view.context.potmsgset))
380
381 def test_max_plural_forms_fields_greater_error(self):
382 # An AssertionError is raised if the number of plural forms
383 # translation fields exceed TranslationConstants.MAX_PLURAL_FORMS.
384 view = self._makeView(with_form=True)
385 view.initialize()
386 # Add a field that is greater than the expected MAX_PLURAL_FORMS.
387 field_name = 'msgset_%d_%s_translation_%d_new' % (
388 view.context.potmsgset.id, view.pofile.language.code,
389 TranslationConstants.MAX_PLURAL_FORMS)
390 view.request.form[field_name] = u'snarf'
391 self.assertRaises(AssertionError,
392 view._extractFormPostedTranslations, view.context.potmsgset)
393
356394
357class TestHelpers(TestCaseWithFactory):395class TestHelpers(TestCaseWithFactory):
358396
359397
=== modified file 'lib/lp/translations/browser/translationmessage.py'
--- lib/lp/translations/browser/translationmessage.py 2011-03-30 11:19:30 +0000
+++ lib/lp/translations/browser/translationmessage.py 2011-05-17 22:09:59 +0000
@@ -131,7 +131,6 @@
131 """131 """
132 schema, netloc, path, parameters, query, fragment = (132 schema, netloc, path, parameters, query, fragment = (
133 urlparse(str(request.URL)))133 urlparse(str(request.URL)))
134
135 # For safety, delete the start and batch variables, if they134 # For safety, delete the start and batch variables, if they
136 # appear in the URL. The situation in which 'start' appears135 # appear in the URL. The situation in which 'start' appears
137 # today is when the alternative language form is posted back and136 # today is when the alternative language form is posted back and
@@ -336,7 +335,6 @@
336 UTC = pytz.timezone('UTC')335 UTC = pytz.timezone('UTC')
337 self.lock_timestamp = datetime.datetime.now(UTC)336 self.lock_timestamp = datetime.datetime.now(UTC)
338337
339
340 # The batch navigator needs to be initialized early, before338 # The batch navigator needs to be initialized early, before
341 # _submitTranslations is called; the reason for this is that339 # _submitTranslations is called; the reason for this is that
342 # _submitTranslations, in the case of no errors, redirects to340 # _submitTranslations, in the case of no errors, redirects to
@@ -746,6 +744,16 @@
746 msgset_ID_LANGCODE_translation_ = 'msgset_%d_%s_translation_' % (744 msgset_ID_LANGCODE_translation_ = 'msgset_%d_%s_translation_' % (
747 potmsgset_ID, language_code)745 potmsgset_ID, language_code)
748746
747 msgset_ID_LANGCODE_translation_GREATER_PLURALFORM_new = '%s%d_new' % (
748 msgset_ID_LANGCODE_translation_,
749 TranslationConstants.MAX_PLURAL_FORMS)
750 if msgset_ID_LANGCODE_translation_GREATER_PLURALFORM_new in form:
751 # The plural form translation generation rules created too many
752 # fields, or the form was hacked.
753 raise AssertionError(
754 'More than %d plural forms were submitted!'
755 % TranslationConstants.MAX_PLURAL_FORMS)
756
749 # Extract the translations from the form, and store them in757 # Extract the translations from the form, and store them in
750 # self.form_posted_translations. We try plural forms in turn,758 # self.form_posted_translations. We try plural forms in turn,
751 # starting at 0.759 # starting at 0.
@@ -822,9 +830,6 @@
822 if store:830 if store:
823 self.form_posted_translations_has_store_flag[831 self.form_posted_translations_has_store_flag[
824 potmsgset].append(pluralform)832 potmsgset].append(pluralform)
825 else:
826 raise AssertionError('More than %d plural forms were submitted!'
827 % TranslationConstants.MAX_PLURAL_FORMS)
828833
829 def _observeTranslationUpdate(self, potmsgset):834 def _observeTranslationUpdate(self, potmsgset):
830 """Observe that a translation was updated for the potmsgset.835 """Observe that a translation was updated for the potmsgset.
@@ -1109,12 +1114,6 @@
1109 other_translation = self.getOtherTranslation(index)1114 other_translation = self.getOtherTranslation(index)
1110 shared_translation = self.getSharedTranslation(index)1115 shared_translation = self.getSharedTranslation(index)
1111 submitted_translation = self.getSubmittedTranslation(index)1116 submitted_translation = self.getSubmittedTranslation(index)
1112 if (submitted_translation is None and
1113 self.user_is_official_translator):
1114 # We don't have anything to show as the submitted translation
1115 # and the user is the official one. We prefill the 'New
1116 # translation' field with the current translation.
1117 translation = current_translation
1118 is_multi_line = (count_lines(current_translation) > 1 or1117 is_multi_line = (count_lines(current_translation) > 1 or
1119 count_lines(submitted_translation) > 1 or1118 count_lines(submitted_translation) > 1 or
1120 count_lines(self.singular_text) > 1 or1119 count_lines(self.singular_text) > 1 or