From faeeb84edfebecf5a5f40df9ef816e5f1cd457c6 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 29 May 2016 11:25:05 -0300 Subject: [PATCH] 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. --- tests/i18n/test_compilation.py | 16 ++++- tests/i18n/test_extraction.py | 114 ++++++++++++++++----------------- tests/i18n/test_percents.py | 4 +- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/tests/i18n/test_compilation.py b/tests/i18n/test_compilation.py index a65fcacda1..57de5d9a7e 100644 --- a/tests/i18n/test_compilation.py +++ b/tests/i18n/test_compilation.py @@ -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')) diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index bad7c23051..8d8bedabe5 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -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) diff --git a/tests/i18n/test_percents.py b/tests/i18n/test_percents.py index 4ff8e71171..af60df4577 100644 --- a/tests/i18n/test_percents.py +++ b/tests/i18n/test_percents.py @@ -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.