Fixed #26677 -- Converted some i18n tests to use disposable FS tree.

This allows makemessages/compilemessages tests in `test_extraction.py`
and `test_compilation.py` to actually run isolated from each other
(unaffected by stray FS objects left by cleanup actions failures, debug
sessions, etc.) and to take advantage of the parallel tests execution
feature like most of the Django test suite.

`test_percents.py` gets slightly refactored to not inherit from the new
machinery which sets up every test case to copy and run under a
temporary tree.
This commit is contained in:
Ramiro Morales 2016-05-29 11:25:05 -03:00
parent 83120af2c1
commit faeeb84edf
3 changed files with 69 additions and 65 deletions

View File

@ -5,6 +5,7 @@ import gettext as gettext_module
import os
import shutil
import stat
import tempfile
import unittest
from subprocess import Popen
@ -23,15 +24,25 @@ from django.utils.six import StringIO
from django.utils.translation import ugettext
has_msgfmt = find_command('msgfmt')
source_code_dir = os.path.dirname(upath(__file__))
@unittest.skipUnless(has_msgfmt, 'msgfmt is mandatory for compilation tests')
class MessageCompilationTests(SimpleTestCase):
test_dir = os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), 'commands'))
work_subdir = 'commands'
def setUp(self):
self._cwd = os.getcwd()
self.work_dir = tempfile.mkdtemp(prefix='i18n_')
self.test_dir = os.path.abspath(os.path.join(self.work_dir, self.work_subdir))
shutil.copytree(os.path.join(source_code_dir, self.work_subdir), self.test_dir)
# Make sure we step out of the temporary working tree before we
# remove it as we might be pulling the rug from under our own feet
# othewise. Rhis is especially true on Windows.
# Remember cleanup actions registered with addCleanup() are called in
# reverse so this ordering is important:
self.addCleanup(self._rmrf, self.test_dir)
self.addCleanup(os.chdir, self._cwd)
os.chdir(self.test_dir)
@ -114,13 +125,12 @@ class MultipleLocaleCompilationTests(MessageCompilationTests):
class ExcludedLocaleCompilationTests(MessageCompilationTests):
test_dir = os.path.abspath(os.path.join(os.path.dirname(upath(__file__)), 'exclude'))
work_subdir = 'exclude'
MO_FILE = 'locale/%s/LC_MESSAGES/django.mo'
def setUp(self):
super(ExcludedLocaleCompilationTests, self).setUp()
shutil.copytree('canned_locale', 'locale')
self.addCleanup(self._rmrf, os.path.join(self.test_dir, 'locale'))

View File

@ -5,6 +5,7 @@ import io
import os
import re
import shutil
import tempfile
import time
import warnings
from unittest import SkipTest, skipUnless
@ -17,7 +18,6 @@ from django.core.management.commands.makemessages import \
Command as MakeMessagesCommand
from django.core.management.utils import find_command
from django.test import SimpleTestCase, mock, override_settings
from django.test.testcases import SerializeMixin
from django.test.utils import captured_stderr, captured_stdout
from django.utils import six
from django.utils._os import upath
@ -27,23 +27,36 @@ from django.utils.translation import TranslatorCommentWarning
LOCALE = 'de'
has_xgettext = find_command('xgettext')
this_directory = os.path.dirname(upath(__file__))
source_code_dir = os.path.dirname(upath(__file__))
class POFileAssertionMixin(object):
def _assertPoKeyword(self, keyword, expected_value, haystack, use_quotes=True):
q = '"'
if use_quotes:
expected_value = '"%s"' % expected_value
q = "'"
needle = '%s %s' % (keyword, expected_value)
expected_value = re.escape(expected_value)
return self.assertTrue(re.search('^%s %s' % (keyword, expected_value), haystack, re.MULTILINE),
'Could not find %(q)s%(n)s%(q)s in generated PO file' % {'n': needle, 'q': q})
def assertMsgId(self, msgid, haystack, use_quotes=True):
return self._assertPoKeyword('msgid', msgid, haystack, use_quotes=use_quotes)
@skipUnless(has_xgettext, 'xgettext is mandatory for extraction tests')
class ExtractorTests(SerializeMixin, SimpleTestCase):
class ExtractorTests(POFileAssertionMixin, SimpleTestCase):
# makemessages scans the current working directory and writes in the
# locale subdirectory. There aren't any options to control this. As a
# consequence tests can't run in parallel. Since i18n tests run in less
# than 4 seconds, serializing them with SerializeMixin is acceptable.
lockfile = __file__
test_dir = os.path.abspath(os.path.join(this_directory, 'commands'))
work_subdir = 'commands'
PO_FILE = 'locale/%s/LC_MESSAGES/django.po' % LOCALE
def setUp(self):
self.work_dir = tempfile.mkdtemp(prefix='i18n_')
self.test_dir = os.path.abspath(os.path.join(self.work_dir, self.work_subdir))
shutil.copytree(os.path.join(source_code_dir, self.work_subdir), self.test_dir)
self._cwd = os.getcwd()
def _rmrf(self, dname):
@ -73,19 +86,6 @@ class ExtractorTests(SerializeMixin, SimpleTestCase):
po_contents = fp.read()
return output, po_contents
def _assertPoKeyword(self, keyword, expected_value, haystack, use_quotes=True):
q = '"'
if use_quotes:
expected_value = '"%s"' % expected_value
q = "'"
needle = '%s %s' % (keyword, expected_value)
expected_value = re.escape(expected_value)
return self.assertTrue(re.search('^%s %s' % (keyword, expected_value), haystack, re.MULTILINE),
'Could not find %(q)s%(n)s%(q)s in generated PO file' % {'n': needle, 'q': q})
def assertMsgId(self, msgid, haystack, use_quotes=True):
return self._assertPoKeyword('msgid', msgid, haystack, use_quotes=use_quotes)
def assertMsgIdPlural(self, msgid, haystack, use_quotes=True):
return self._assertPoKeyword('msgid_plural', msgid, haystack, use_quotes=use_quotes)
@ -464,16 +464,15 @@ class JavascriptExtractorTests(ExtractorTests):
self.assertMsgId("quz", po_contents)
self.assertMsgId("foobar", po_contents)
@override_settings(
STATIC_ROOT=os.path.join(this_directory, 'commands', 'static/'),
MEDIA_ROOT=os.path.join(this_directory, 'commands', 'media_root/'))
def test_media_static_dirs_ignored(self):
"""
Regression test for #23583.
"""
_, po_contents = self._run_makemessages(domain='djangojs')
self.assertMsgId("Static content inside app should be included.", po_contents)
self.assertNotMsgId("Content from STATIC_ROOT should not be included", po_contents)
with override_settings(STATIC_ROOT=os.path.join(self.test_dir, 'static/'),
MEDIA_ROOT=os.path.join(self.test_dir, 'media_root/')):
_, po_contents = self._run_makemessages(domain='djangojs')
self.assertMsgId("Static content inside app should be included.", po_contents)
self.assertNotMsgId("Content from STATIC_ROOT should not be included", po_contents)
@override_settings(STATIC_ROOT=None, MEDIA_ROOT='')
def test_default_root_settings(self):
@ -509,13 +508,12 @@ class IgnoredExtractorTests(ExtractorTests):
self.assertIn("ignoring file xxx_ignored.html", out)
self.assertNotMsgId('This should be ignored too.', po_contents)
@override_settings(
STATIC_ROOT=os.path.join(this_directory, 'commands', 'static/'),
MEDIA_ROOT=os.path.join(this_directory, 'commands', 'media_root/'))
def test_media_static_dirs_ignored(self):
out, _ = self._run_makemessages()
self.assertIn("ignoring directory static", out)
self.assertIn("ignoring directory media_root", out)
with override_settings(STATIC_ROOT=os.path.join(self.test_dir, 'static/'),
MEDIA_ROOT=os.path.join(self.test_dir, 'media_root/')):
out, _ = self._run_makemessages()
self.assertIn("ignoring directory static", out)
self.assertIn("ignoring directory media_root", out)
class SymlinkExtractorTests(ExtractorTests):
@ -721,11 +719,11 @@ class MultipleLocaleExtractionTests(ExtractorTests):
class ExcludedLocaleExtractionTests(ExtractorTests):
work_subdir = 'exclude'
LOCALES = ['en', 'fr', 'it']
PO_FILE = 'locale/%s/LC_MESSAGES/django.po'
test_dir = os.path.abspath(os.path.join(this_directory, 'exclude'))
def _set_times_for_all_po_files(self):
"""
Set access and modification times to the Unix epoch time for all the .po files.
@ -775,9 +773,7 @@ class ExcludedLocaleExtractionTests(ExtractorTests):
class CustomLayoutExtractionTests(ExtractorTests):
def setUp(self):
super(CustomLayoutExtractionTests, self).setUp()
self.test_dir = os.path.join(this_directory, 'project_dir')
work_subdir = 'project_dir'
def test_no_locale_raises(self):
os.chdir(self.test_dir)
@ -785,31 +781,29 @@ class CustomLayoutExtractionTests(ExtractorTests):
with self.assertRaisesMessage(management.CommandError, msg):
management.call_command('makemessages', locale=LOCALE, verbosity=0)
@override_settings(
LOCALE_PATHS=[os.path.join(this_directory, 'project_dir', 'project_locale')],
)
def test_project_locale_paths(self):
"""
Test that:
* translations for an app containing a locale folder are stored in that folder
* translations outside of that app are in LOCALE_PATHS[0]
"""
os.chdir(self.test_dir)
self.addCleanup(shutil.rmtree, os.path.join(settings.LOCALE_PATHS[0], LOCALE), True)
self.addCleanup(shutil.rmtree, os.path.join(self.test_dir, 'app_with_locale', 'locale', LOCALE), True)
with override_settings(LOCALE_PATHS=[os.path.join(self.test_dir, 'project_locale')]):
os.chdir(self.test_dir)
self.addCleanup(shutil.rmtree, os.path.join(settings.LOCALE_PATHS[0], LOCALE), True)
self.addCleanup(shutil.rmtree, os.path.join(self.test_dir, 'app_with_locale', 'locale', LOCALE), True)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
project_de_locale = os.path.join(
self.test_dir, 'project_locale', 'de', 'LC_MESSAGES', 'django.po')
app_de_locale = os.path.join(
self.test_dir, 'app_with_locale', 'locale', 'de', 'LC_MESSAGES', 'django.po')
self.assertTrue(os.path.exists(project_de_locale))
self.assertTrue(os.path.exists(app_de_locale))
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
project_de_locale = os.path.join(
self.test_dir, 'project_locale', 'de', 'LC_MESSAGES', 'django.po')
app_de_locale = os.path.join(
self.test_dir, 'app_with_locale', 'locale', 'de', 'LC_MESSAGES', 'django.po')
self.assertTrue(os.path.exists(project_de_locale))
self.assertTrue(os.path.exists(app_de_locale))
with open(project_de_locale, 'r') as fp:
po_contents = force_text(fp.read())
self.assertMsgId('This app has no locale directory', po_contents)
self.assertMsgId('This is a project-level string', po_contents)
with open(app_de_locale, 'r') as fp:
po_contents = force_text(fp.read())
self.assertMsgId('This app has a locale directory', po_contents)
with open(project_de_locale, 'r') as fp:
po_contents = force_text(fp.read())
self.assertMsgId('This app has no locale directory', po_contents)
self.assertMsgId('This is a project-level string', po_contents)
with open(app_de_locale, 'r') as fp:
po_contents = force_text(fp.read())
self.assertMsgId('This app has a locale directory', po_contents)

View File

@ -9,7 +9,7 @@ from django.utils._os import upath
from django.utils.encoding import force_text
from django.utils.translation import activate, get_language, trans_real
from .test_extraction import ExtractorTests
from .test_extraction import POFileAssertionMixin
SAMPLEPROJECT_DIR = os.path.join(os.path.dirname(os.path.abspath(upath(__file__))), 'sampleproject')
SAMPLEPROJECT_LOCALE = os.path.join(SAMPLEPROJECT_DIR, 'locale')
@ -31,7 +31,7 @@ class FrenchTestCase(SimpleTestCase):
activate(self._language)
class ExtractingStringsWithPercentSigns(FrenchTestCase, ExtractorTests):
class ExtractingStringsWithPercentSigns(POFileAssertionMixin, FrenchTestCase):
"""
Tests the extracted string found in the gettext catalog.