Refs #26677 -- Simplified i18n test cleanups.

The fact that we aren't dealing with the Django source tree anymore
allows us to drop several tearDown()/addCleanup() calls that were
concerned with removing apiece files/dirs/symlinks created by test
cases, as we are covered by the removal of the parent temporary tree
anyways.

Thanks Tim Graham for advice and review.
This commit is contained in:
Ramiro Morales 2016-06-09 10:08:31 -03:00
parent 0451dcc2eb
commit bb7bb379e8
4 changed files with 77 additions and 161 deletions

View File

@ -3,9 +3,7 @@ from __future__ import unicode_literals
import gettext as gettext_module
import os
import shutil
import stat
import tempfile
import unittest
from subprocess import Popen
@ -18,43 +16,20 @@ from django.core.management.utils import find_command
from django.test import SimpleTestCase, mock, override_settings
from django.test.utils import captured_stderr, captured_stdout
from django.utils import six, translation
from django.utils._os import upath
from django.utils.encoding import force_text
from django.utils.six import StringIO
from django.utils.translation import ugettext
from .utils import RunInTmpDirMixin, copytree
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):
class MessageCompilationTests(RunInTmpDirMixin, SimpleTestCase):
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)
def _rmrf(self, dname):
if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir:
return
shutil.rmtree(dname)
def rmfile(self, filepath):
if os.path.exists(filepath):
os.remove(filepath)
class PoFileTests(MessageCompilationTests):
@ -87,10 +62,6 @@ class PoFileContentsTests(MessageCompilationTests):
LOCALE = 'fr'
MO_FILE = 'locale/%s/LC_MESSAGES/django.mo' % LOCALE
def setUp(self):
super(PoFileContentsTests, self).setUp()
self.addCleanup(os.unlink, os.path.join(self.test_dir, self.MO_FILE))
def test_percent_symbol_in_po_file(self):
call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO())
self.assertTrue(os.path.exists(self.MO_FILE))
@ -106,8 +77,6 @@ class MultipleLocaleCompilationTests(MessageCompilationTests):
localedir = os.path.join(self.test_dir, 'locale')
self.MO_FILE_HR = os.path.join(localedir, 'hr/LC_MESSAGES/django.mo')
self.MO_FILE_FR = os.path.join(localedir, 'fr/LC_MESSAGES/django.mo')
self.addCleanup(self.rmfile, os.path.join(localedir, self.MO_FILE_HR))
self.addCleanup(self.rmfile, os.path.join(localedir, self.MO_FILE_FR))
def test_one_locale(self):
with override_settings(LOCALE_PATHS=[os.path.join(self.test_dir, 'locale')]):
@ -131,8 +100,7 @@ class ExcludedLocaleCompilationTests(MessageCompilationTests):
def setUp(self):
super(ExcludedLocaleCompilationTests, self).setUp()
shutil.copytree('canned_locale', 'locale')
self.addCleanup(self._rmrf, os.path.join(self.test_dir, 'locale'))
copytree('canned_locale', 'locale')
def test_command_help(self):
with captured_stdout(), captured_stderr():
@ -170,15 +138,11 @@ class ExcludedLocaleCompilationTests(MessageCompilationTests):
class CompilationErrorHandling(MessageCompilationTests):
def test_error_reported_by_msgfmt(self):
# po file contains wrong po formatting.
mo_file = 'locale/ja/LC_MESSAGES/django.mo'
self.addCleanup(self.rmfile, os.path.join(self.test_dir, mo_file))
with self.assertRaises(CommandError):
call_command('compilemessages', locale=['ja'], verbosity=0)
def test_msgfmt_error_including_non_ascii(self):
# po file contains invalid msgstr content (triggers non-ascii error content).
mo_file = 'locale/ko/LC_MESSAGES/django.mo'
self.addCleanup(self.rmfile, os.path.join(self.test_dir, mo_file))
# Make sure the output of msgfmt is unaffected by the current locale.
env = os.environ.copy()
env.update({str('LANG'): str('C')})
@ -202,11 +166,6 @@ class ProjectAndAppTests(MessageCompilationTests):
PROJECT_MO_FILE = 'locale/%s/LC_MESSAGES/django.mo' % LOCALE
APP_MO_FILE = 'app_with_locale/locale/%s/LC_MESSAGES/django.mo' % LOCALE
def setUp(self):
super(ProjectAndAppTests, self).setUp()
self.addCleanup(self.rmfile, os.path.join(self.test_dir, self.PROJECT_MO_FILE))
self.addCleanup(self.rmfile, os.path.join(self.test_dir, self.APP_MO_FILE))
class FuzzyTranslationTest(ProjectAndAppTests):

View File

@ -5,12 +5,10 @@ import io
import os
import re
import shutil
import tempfile
import time
import warnings
from unittest import SkipTest, skipUnless
from django.conf import settings
from django.core import management
from django.core.management import execute_from_command_line
from django.core.management.base import CommandError
@ -20,62 +18,23 @@ from django.core.management.utils import find_command
from django.test import SimpleTestCase, mock, override_settings
from django.test.utils import captured_stderr, captured_stdout
from django.utils import six
from django.utils._os import upath
from django.utils.encoding import force_text
from django.utils.six import StringIO
from django.utils.translation import TranslatorCommentWarning
from .utils import POFileAssertionMixin, RunInTmpDirMixin, copytree
LOCALE = 'de'
has_xgettext = find_command('xgettext')
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(POFileAssertionMixin, SimpleTestCase):
class ExtractorTests(POFileAssertionMixin, RunInTmpDirMixin, SimpleTestCase):
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):
if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir:
return
shutil.rmtree(dname)
def rmfile(self, filepath):
if os.path.exists(filepath):
os.remove(filepath)
def tearDown(self):
os.chdir(self.test_dir)
try:
self._rmrf('locale/%s' % LOCALE)
except OSError:
pass
os.chdir(self._cwd)
def _run_makemessages(self, **options):
os.chdir(self.test_dir)
out = StringIO()
@ -177,7 +136,6 @@ class ExtractorTests(POFileAssertionMixin, SimpleTestCase):
class BasicExtractorTests(ExtractorTests):
def test_comments_extractor(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with io.open(self.PO_FILE, 'r', encoding='utf-8') as fp:
@ -211,7 +169,6 @@ class BasicExtractorTests(ExtractorTests):
)
def test_special_char_extracted(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with io.open(self.PO_FILE, 'r', encoding='utf-8') as fp:
@ -219,7 +176,6 @@ class BasicExtractorTests(ExtractorTests):
self.assertMsgId("Non-breaking space\u00a0:", po_contents)
def test_blocktrans_trimmed(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
@ -237,7 +193,6 @@ class BasicExtractorTests(ExtractorTests):
self.assertTrue(MakeMessagesCommand.leave_locale_alone)
def test_extraction_error(self):
os.chdir(self.test_dir)
msg = (
'Translation blocks must not include other block tags: blocktrans '
'(file %s, line 3)' % os.path.join('templates', 'template_with_error.tpl')
@ -248,9 +203,7 @@ class BasicExtractorTests(ExtractorTests):
self.assertFalse(os.path.exists('./templates/template_with_error.tpl.py'))
def test_unicode_decode_error(self):
os.chdir(self.test_dir)
shutil.copyfile('./not_utf8.sample', './not_utf8.txt')
self.addCleanup(self.rmfile, os.path.join(self.test_dir, 'not_utf8.txt'))
out = StringIO()
management.call_command('makemessages', locale=[LOCALE], stdout=out)
self.assertIn("UnicodeDecodeError: skipped file not_utf8.txt in .",
@ -258,9 +211,7 @@ class BasicExtractorTests(ExtractorTests):
def test_extraction_warning(self):
"""test xgettext warning about multiple bare interpolation placeholders"""
os.chdir(self.test_dir)
shutil.copyfile('./code.sample', './code_sample.py')
self.addCleanup(self.rmfile, os.path.join(self.test_dir, 'code_sample.py'))
out = StringIO()
management.call_command('makemessages', locale=[LOCALE], stdout=out)
self.assertIn("code_sample.py:4", force_text(out.getvalue()))
@ -271,7 +222,6 @@ class BasicExtractorTests(ExtractorTests):
{% trans %} and {% blocktrans %} template tags.
Refs #14806.
"""
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
@ -302,7 +252,6 @@ class BasicExtractorTests(ExtractorTests):
self.assertMsgId("Translatable literal #8d %(a)s", po_contents)
def test_context_in_single_quotes(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
@ -317,7 +266,6 @@ class BasicExtractorTests(ExtractorTests):
def test_template_comments(self):
"""Template comment tags on the same line of other constructs (#19552)"""
os.chdir(self.test_dir)
# Test detection/end user reporting of old, incorrect templates
# translator comments syntax
with warnings.catch_warnings(record=True) as ws:
@ -432,9 +380,7 @@ class BasicExtractorTests(ExtractorTests):
def test_po_file_encoding_when_updating(self):
"""Update of PO file doesn't corrupt it with non-UTF-8 encoding on Python3+Windows (#23271)"""
BR_PO_BASE = 'locale/pt_BR/LC_MESSAGES/django'
os.chdir(self.test_dir)
shutil.copyfile(BR_PO_BASE + '.pristine', BR_PO_BASE + '.po')
self.addCleanup(self.rmfile, os.path.join(self.test_dir, 'locale', 'pt_BR', 'LC_MESSAGES', 'django.po'))
management.call_command('makemessages', locale=['pt_BR'], verbosity=0)
self.assertTrue(os.path.exists(BR_PO_BASE + '.po'))
with io.open(BR_PO_BASE + '.po', 'r', encoding='utf-8') as fp:
@ -447,7 +393,6 @@ class JavascriptExtractorTests(ExtractorTests):
PO_FILE = 'locale/%s/LC_MESSAGES/djangojs.po' % LOCALE
def test_javascript_literals(self):
os.chdir(self.test_dir)
_, po_contents = self._run_makemessages(domain='djangojs')
self.assertMsgId('This literal should be included.', po_contents)
self.assertMsgId('gettext_noop should, too.', po_contents)
@ -522,15 +467,6 @@ class SymlinkExtractorTests(ExtractorTests):
super(SymlinkExtractorTests, self).setUp()
self.symlinked_dir = os.path.join(self.test_dir, 'templates_symlinked')
def tearDown(self):
super(SymlinkExtractorTests, self).tearDown()
os.chdir(self.test_dir)
try:
os.remove(self.symlinked_dir)
except OSError:
pass
os.chdir(self._cwd)
def test_symlink(self):
# On Python < 3.2 os.symlink() exists only on Unix
if hasattr(os, 'symlink'):
@ -561,17 +497,7 @@ class CopyPluralFormsExtractorTests(ExtractorTests):
PO_FILE_ES = 'locale/es/LC_MESSAGES/django.po'
def tearDown(self):
super(CopyPluralFormsExtractorTests, self).tearDown()
os.chdir(self.test_dir)
try:
self._rmrf('locale/es')
except OSError:
pass
os.chdir(self._cwd)
def test_copy_plural_forms(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
@ -580,7 +506,6 @@ class CopyPluralFormsExtractorTests(ExtractorTests):
def test_override_plural_forms(self):
"""Ticket #20311."""
os.chdir(self.test_dir)
management.call_command('makemessages', locale=['es'], extensions=['djtpl'], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE_ES))
with io.open(self.PO_FILE_ES, 'r', encoding='utf-8') as fp:
@ -594,7 +519,6 @@ class CopyPluralFormsExtractorTests(ExtractorTests):
found inside a {% trans %} tag and also in another file inside a
{% blocktrans %} with a plural (#17375).
"""
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], extensions=['html', 'djtpl'], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
@ -607,7 +531,6 @@ class CopyPluralFormsExtractorTests(ExtractorTests):
class NoWrapExtractorTests(ExtractorTests):
def test_no_wrap_enabled(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0, no_wrap=True)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
@ -619,7 +542,6 @@ class NoWrapExtractorTests(ExtractorTests):
)
def test_no_wrap_disabled(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0, no_wrap=False)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
@ -636,14 +558,12 @@ class LocationCommentsTests(ExtractorTests):
def test_no_location_enabled(self):
"""Behavior is correct if --no-location switch is specified. See #16903."""
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0, no_location=True)
self.assertTrue(os.path.exists(self.PO_FILE))
self.assertLocationCommentNotPresent(self.PO_FILE, None, 'test.html')
def test_no_location_disabled(self):
"""Behavior is correct if --no-location switch isn't specified."""
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0, no_location=False)
self.assertTrue(os.path.exists(self.PO_FILE))
# #16903 -- Standard comment with source file relative path should be present
@ -654,7 +574,6 @@ class LocationCommentsTests(ExtractorTests):
Ensure no leaky paths in comments, e.g. #: path\to\file.html.py:123
Refs #21209/#26341.
"""
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
@ -668,28 +587,16 @@ class KeepPotFileExtractorTests(ExtractorTests):
POT_FILE = 'locale/django.pot'
def tearDown(self):
super(KeepPotFileExtractorTests, self).tearDown()
os.chdir(self.test_dir)
try:
os.unlink(self.POT_FILE)
except OSError:
pass
os.chdir(self._cwd)
def test_keep_pot_disabled_by_default(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
self.assertFalse(os.path.exists(self.POT_FILE))
def test_keep_pot_explicitly_disabled(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0,
keep_pot=False)
self.assertFalse(os.path.exists(self.POT_FILE))
def test_keep_pot_enabled(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=[LOCALE], verbosity=0,
keep_pot=True)
self.assertTrue(os.path.exists(self.POT_FILE))
@ -700,18 +607,7 @@ class MultipleLocaleExtractionTests(ExtractorTests):
PO_FILE_DE = 'locale/de/LC_MESSAGES/django.po'
LOCALES = ['pt', 'de', 'ch']
def tearDown(self):
super(MultipleLocaleExtractionTests, self).tearDown()
os.chdir(self.test_dir)
for locale in self.LOCALES:
try:
self._rmrf('locale/%s' % locale)
except OSError:
pass
os.chdir(self._cwd)
def test_multiple_locales(self):
os.chdir(self.test_dir)
management.call_command('makemessages', locale=['pt', 'de'], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE_PT))
self.assertTrue(os.path.exists(self.PO_FILE_DE))
@ -733,10 +629,8 @@ class ExcludedLocaleExtractionTests(ExtractorTests):
def setUp(self):
super(ExcludedLocaleExtractionTests, self).setUp()
os.chdir(self.test_dir) # ExtractorTests.tearDown() takes care of restoring.
shutil.copytree('canned_locale', 'locale')
copytree('canned_locale', 'locale')
self._set_times_for_all_po_files()
self.addCleanup(self._rmrf, os.path.join(self.test_dir, 'locale'))
def test_command_help(self):
with captured_stdout(), captured_stderr():
@ -776,7 +670,6 @@ class CustomLayoutExtractionTests(ExtractorTests):
work_subdir = 'project_dir'
def test_no_locale_raises(self):
os.chdir(self.test_dir)
msg = "Unable to find a locale path to store translations for file"
with self.assertRaisesMessage(management.CommandError, msg):
management.call_command('makemessages', locale=LOCALE, verbosity=0)
@ -788,10 +681,6 @@ class CustomLayoutExtractionTests(ExtractorTests):
* translations outside of that app are in LOCALE_PATHS[0]
"""
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')

View File

@ -9,7 +9,8 @@ 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 POFileAssertionMixin
from .utils import POFileAssertionMixin
SAMPLEPROJECT_DIR = os.path.join(os.path.dirname(os.path.abspath(upath(__file__))), 'sampleproject')
SAMPLEPROJECT_LOCALE = os.path.join(SAMPLEPROJECT_DIR, 'locale')

67
tests/i18n/utils.py Normal file
View File

@ -0,0 +1,67 @@
import os
import re
import shutil
import tempfile
from django.utils._os import upath
source_code_dir = os.path.dirname(upath(__file__))
def copytree(src, dst):
shutil.copytree(src, dst, ignore=shutil.ignore_patterns('*.pyc', '__pycache__'))
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)
class RunInTmpDirMixin(object):
"""
Allow i18n tests that need to generate .po/.mo files to run in an isolated
temporary filesystem tree created by tempfile.mkdtemp() that contains a
clean copy of the relevant test code.
Test classes using this mixin need to define a `work_subdir` attribute
which designates the subdir under `tests/i18n/` that will be copied to the
temporary tree from which its test cases will run.
The setUp() method sets the current working dir to the temporary tree.
It'll be removed when cleaning up.
"""
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))
copytree(os.path.join(source_code_dir, self.work_subdir), self.test_dir)
# Step out of the temporary working tree before removing it to avoid
# deletion problems on Windows. Cleanup actions registered with
# addCleanup() are called in reverse so preserve this ordering.
self.addCleanup(self._rmrf, self.test_dir)
self.addCleanup(os.chdir, self._cwd)
os.chdir(self.test_dir)
def _rmrf(self, dname):
if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir:
return
shutil.rmtree(dname)
def rmfile(self, filepath):
if os.path.exists(filepath):
os.remove(filepath)