1
0
Fork 0
forked from github/pelican

Add unit test utilities temporary_locale and TestCaseWithCLocale (#3224)

This commit is contained in:
boxydog 2023-10-28 17:40:40 -05:00 committed by GitHub
commit fad2ff7ae3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 119 deletions

View file

@ -254,3 +254,16 @@ class LoggedTestCase(unittest.TestCase):
actual, count, actual, count,
msg='expected {} occurrences of {!r}, but found {}'.format( msg='expected {} occurrences of {!r}, but found {}'.format(
count, msg, actual)) count, msg, actual))
class TestCaseWithCLocale(unittest.TestCase):
"""Set locale to C for each test case, then restore afterward.
Use utils.temporary_locale if you want a context manager ("with" statement).
"""
def setUp(self):
self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, 'C')
def tearDown(self):
locale.setlocale(locale.LC_ALL, self.old_locale)

View file

@ -1,4 +1,3 @@
import locale
import os import os
import sys import sys
from shutil import copy, rmtree from shutil import copy, rmtree
@ -9,26 +8,21 @@ from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator,
PelicanTemplateNotFound, StaticGenerator, PelicanTemplateNotFound, StaticGenerator,
TemplatePagesGenerator) TemplatePagesGenerator)
from pelican.tests.support import (can_symlink, get_context, get_settings, from pelican.tests.support import (can_symlink, get_context, get_settings,
unittest) unittest, TestCaseWithCLocale)
from pelican.writers import Writer from pelican.writers import Writer
CUR_DIR = os.path.dirname(__file__) CUR_DIR = os.path.dirname(__file__)
CONTENT_DIR = os.path.join(CUR_DIR, 'content') CONTENT_DIR = os.path.join(CUR_DIR, 'content')
class TestGenerator(unittest.TestCase): class TestGenerator(TestCaseWithCLocale):
def setUp(self): def setUp(self):
self.old_locale = locale.setlocale(locale.LC_ALL) super().setUp()
locale.setlocale(locale.LC_ALL, 'C')
self.settings = get_settings() self.settings = get_settings()
self.settings['READERS'] = {'asc': None} self.settings['READERS'] = {'asc': None}
self.generator = Generator(self.settings.copy(), self.settings, self.generator = Generator(self.settings.copy(), self.settings,
CUR_DIR, self.settings['THEME'], None) CUR_DIR, self.settings['THEME'], None)
def tearDown(self):
locale.setlocale(locale.LC_ALL, self.old_locale)
def test_include_path(self): def test_include_path(self):
self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'} self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'}
@ -408,8 +402,6 @@ class TestArticlesGenerator(unittest.TestCase):
def test_period_archives_context(self): def test_period_archives_context(self):
"""Test correctness of the period_archives context values.""" """Test correctness of the period_archives context values."""
old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, 'C')
settings = get_settings() settings = get_settings()
settings['CACHE_PATH'] = self.temp_cache settings['CACHE_PATH'] = self.temp_cache
@ -532,15 +524,11 @@ class TestArticlesGenerator(unittest.TestCase):
self.assertEqual(sample_archive['dates'][0].title, dates[0].title) self.assertEqual(sample_archive['dates'][0].title, dates[0].title)
self.assertEqual(sample_archive['dates'][0].date, dates[0].date) self.assertEqual(sample_archive['dates'][0].date, dates[0].date)
locale.setlocale(locale.LC_ALL, old_locale)
def test_period_in_timeperiod_archive(self): def test_period_in_timeperiod_archive(self):
""" """
Test that the context of a generated period_archive is passed Test that the context of a generated period_archive is passed
'period' : a tuple of year, month, day according to the time period 'period' : a tuple of year, month, day according to the time period
""" """
old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, 'C')
settings = get_settings() settings = get_settings()
settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html'
@ -625,7 +613,6 @@ class TestArticlesGenerator(unittest.TestCase):
dates=dates, template_name='period_archives', dates=dates, template_name='period_archives',
url="posts/1970/Jan/01/", url="posts/1970/Jan/01/",
all_articles=generator.articles) all_articles=generator.articles)
locale.setlocale(locale.LC_ALL, old_locale)
def test_nonexistent_template(self): def test_nonexistent_template(self):
"""Attempt to load a non-existent template""" """Attempt to load a non-existent template"""
@ -926,20 +913,18 @@ class TestPageGenerator(unittest.TestCase):
context['static_links']) context['static_links'])
class TestTemplatePagesGenerator(unittest.TestCase): class TestTemplatePagesGenerator(TestCaseWithCLocale):
TEMPLATE_CONTENT = "foo: {{ foo }}" TEMPLATE_CONTENT = "foo: {{ foo }}"
def setUp(self): def setUp(self):
super().setUp()
self.temp_content = mkdtemp(prefix='pelicantests.') self.temp_content = mkdtemp(prefix='pelicantests.')
self.temp_output = mkdtemp(prefix='pelicantests.') self.temp_output = mkdtemp(prefix='pelicantests.')
self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, 'C')
def tearDown(self): def tearDown(self):
rmtree(self.temp_content) rmtree(self.temp_content)
rmtree(self.temp_output) rmtree(self.temp_output)
locale.setlocale(locale.LC_ALL, self.old_locale)
def test_generate_output(self): def test_generate_output(self):
@ -1299,18 +1284,15 @@ class TestStaticGenerator(unittest.TestCase):
self.assertTrue(os.path.isfile(self.endfile)) self.assertTrue(os.path.isfile(self.endfile))
class TestJinja2Environment(unittest.TestCase): class TestJinja2Environment(TestCaseWithCLocale):
def setUp(self): def setUp(self):
self.temp_content = mkdtemp(prefix='pelicantests.') self.temp_content = mkdtemp(prefix='pelicantests.')
self.temp_output = mkdtemp(prefix='pelicantests.') self.temp_output = mkdtemp(prefix='pelicantests.')
self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, 'C')
def tearDown(self): def tearDown(self):
rmtree(self.temp_content) rmtree(self.temp_content)
rmtree(self.temp_output) rmtree(self.temp_output)
locale.setlocale(locale.LC_ALL, self.old_locale)
def _test_jinja2_helper(self, additional_settings, content, expected): def _test_jinja2_helper(self, additional_settings, content, expected):
settings = get_settings() settings = get_settings()

View file

@ -1,4 +1,3 @@
import locale
import os import os
import re import re
from posixpath import join as posix_join from posixpath import join as posix_join
@ -6,7 +5,7 @@ from unittest.mock import patch
from pelican.settings import DEFAULT_CONFIG from pelican.settings import DEFAULT_CONFIG
from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder, from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder,
unittest) unittest, TestCaseWithCLocale)
from pelican.tools.pelican_import import (blogger2fields, build_header, from pelican.tools.pelican_import import (blogger2fields, build_header,
build_markdown_header, build_markdown_header,
decode_wp_content, decode_wp_content,
@ -16,7 +15,6 @@ from pelican.tools.pelican_import import (blogger2fields, build_header,
) )
from pelican.utils import path_to_file_url, slugify from pelican.utils import path_to_file_url, slugify
CUR_DIR = os.path.abspath(os.path.dirname(__file__)) CUR_DIR = os.path.abspath(os.path.dirname(__file__))
BLOGGER_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'bloggerexport.xml') BLOGGER_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'bloggerexport.xml')
WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml') WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml')
@ -38,19 +36,9 @@ except ImportError:
LXML = False LXML = False
class TestWithOsDefaults(unittest.TestCase):
"""Set locale to C and timezone to UTC for tests, then restore."""
def setUp(self):
self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, 'C')
def tearDown(self):
locale.setlocale(locale.LC_ALL, self.old_locale)
@skipIfNoExecutable(['pandoc', '--version']) @skipIfNoExecutable(['pandoc', '--version'])
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
class TestBloggerXmlImporter(TestWithOsDefaults): class TestBloggerXmlImporter(TestCaseWithCLocale):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -95,7 +83,7 @@ class TestBloggerXmlImporter(TestWithOsDefaults):
@skipIfNoExecutable(['pandoc', '--version']) @skipIfNoExecutable(['pandoc', '--version'])
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
class TestWordpressXmlImporter(TestWithOsDefaults): class TestWordpressXmlImporter(TestCaseWithCLocale):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -433,15 +421,11 @@ class TestBuildHeader(unittest.TestCase):
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
@unittest.skipUnless(LXML, 'Needs lxml module') @unittest.skipUnless(LXML, 'Needs lxml module')
class TestWordpressXMLAttachements(unittest.TestCase): class TestWordpressXMLAttachements(TestCaseWithCLocale):
def setUp(self): def setUp(self):
self.old_locale = locale.setlocale(locale.LC_ALL) super().setUp()
locale.setlocale(locale.LC_ALL, 'C')
self.attachments = get_attachments(WORDPRESS_XML_SAMPLE) self.attachments = get_attachments(WORDPRESS_XML_SAMPLE)
def tearDown(self):
locale.setlocale(locale.LC_ALL, self.old_locale)
def test_recognise_attachments(self): def test_recognise_attachments(self):
self.assertTrue(self.attachments) self.assertTrue(self.attachments)
self.assertTrue(len(self.attachments.keys()) == 3) self.assertTrue(len(self.attachments.keys()) == 3)
@ -485,7 +469,7 @@ class TestWordpressXMLAttachements(unittest.TestCase):
directory) directory)
class TestTumblrImporter(TestWithOsDefaults): class TestTumblrImporter(TestCaseWithCLocale):
@patch("pelican.tools.pelican_import._get_tumblr_posts") @patch("pelican.tools.pelican_import._get_tumblr_posts")
def test_posts(self, get): def test_posts(self, get):
def get_posts(api_key, blogname, offset=0): def get_posts(api_key, blogname, offset=0):

View file

@ -484,33 +484,25 @@ class TestUtils(LoggedTestCase):
locale_available('Turkish'), locale_available('Turkish'),
'Turkish locale needed') 'Turkish locale needed')
def test_strftime_locale_dependent_turkish(self): def test_strftime_locale_dependent_turkish(self):
# store current locale temp_locale = 'Turkish' if platform == 'win32' else 'tr_TR.UTF-8'
old_locale = locale.setlocale(locale.LC_ALL)
if platform == 'win32': with utils.temporary_locale(temp_locale):
locale.setlocale(locale.LC_ALL, 'Turkish') d = utils.SafeDatetime(2012, 8, 29)
else:
locale.setlocale(locale.LC_ALL, 'tr_TR.UTF-8')
d = utils.SafeDatetime(2012, 8, 29) # simple
self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 Ağustos 2012')
self.assertEqual(utils.strftime(d, '%A, %d %B %Y'),
'Çarşamba, 29 Ağustos 2012')
# simple # with text
self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 Ağustos 2012') self.assertEqual(
self.assertEqual(utils.strftime(d, '%A, %d %B %Y'), utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'),
'Çarşamba, 29 Ağustos 2012') 'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012')
# with text # non-ascii format candidate (someone might pass it… for some reason)
self.assertEqual( self.assertEqual(
utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'), utils.strftime(d, '%Y yılında %üretim artışı'),
'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012') '2012 yılında %üretim artışı')
# non-ascii format candidate (someone might pass it… for some reason)
self.assertEqual(
utils.strftime(d, '%Y yılında %üretim artışı'),
'2012 yılında %üretim artışı')
# restore locale back
locale.setlocale(locale.LC_ALL, old_locale)
# test the output of utils.strftime in a different locale # test the output of utils.strftime in a different locale
# French locale # French locale
@ -518,34 +510,26 @@ class TestUtils(LoggedTestCase):
locale_available('French'), locale_available('French'),
'French locale needed') 'French locale needed')
def test_strftime_locale_dependent_french(self): def test_strftime_locale_dependent_french(self):
# store current locale temp_locale = 'French' if platform == 'win32' else 'fr_FR.UTF-8'
old_locale = locale.setlocale(locale.LC_ALL)
if platform == 'win32': with utils.temporary_locale(temp_locale):
locale.setlocale(locale.LC_ALL, 'French') d = utils.SafeDatetime(2012, 8, 29)
else:
locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8')
d = utils.SafeDatetime(2012, 8, 29) # simple
self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 août 2012')
# simple # depending on OS, the first letter is m or M
self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 août 2012') self.assertTrue(utils.strftime(d, '%A') in ('mercredi', 'Mercredi'))
# depending on OS, the first letter is m or M # with text
self.assertTrue(utils.strftime(d, '%A') in ('mercredi', 'Mercredi')) self.assertEqual(
utils.strftime(d, 'Écrit le %d %B %Y'),
'Écrit le 29 août 2012')
# with text # non-ascii format candidate (someone might pass it… for some reason)
self.assertEqual( self.assertEqual(
utils.strftime(d, 'Écrit le %d %B %Y'), utils.strftime(d, '%écrits en %Y'),
'Écrit le 29 août 2012') '%écrits en 2012')
# non-ascii format candidate (someone might pass it… for some reason)
self.assertEqual(
utils.strftime(d, '%écrits en %Y'),
'%écrits en 2012')
# restore locale back
locale.setlocale(locale.LC_ALL, old_locale)
def test_maybe_pluralize(self): def test_maybe_pluralize(self):
self.assertEqual( self.assertEqual(
@ -558,6 +542,23 @@ class TestUtils(LoggedTestCase):
utils.maybe_pluralize(2, 'Article', 'Articles'), utils.maybe_pluralize(2, 'Article', 'Articles'),
'2 Articles') '2 Articles')
def test_temporary_locale(self):
# test with default LC category
orig_locale = locale.setlocale(locale.LC_ALL)
with utils.temporary_locale('C'):
self.assertEqual(locale.setlocale(locale.LC_ALL), 'C')
self.assertEqual(locale.setlocale(locale.LC_ALL), orig_locale)
# test with custom LC category
orig_locale = locale.setlocale(locale.LC_TIME)
with utils.temporary_locale('C', locale.LC_TIME):
self.assertEqual(locale.setlocale(locale.LC_TIME), 'C')
self.assertEqual(locale.setlocale(locale.LC_TIME), orig_locale)
class TestCopy(unittest.TestCase): class TestCopy(unittest.TestCase):
'''Tests the copy utility''' '''Tests the copy utility'''
@ -673,27 +674,27 @@ class TestDateFormatter(unittest.TestCase):
def test_french_strftime(self): def test_french_strftime(self):
# This test tries to reproduce an issue that # This test tries to reproduce an issue that
# occurred with python3.3 under macos10 only # occurred with python3.3 under macos10 only
if platform == 'win32': temp_locale = 'French' if platform == 'win32' else 'fr_FR.UTF-8'
locale.setlocale(locale.LC_ALL, 'French')
else: with utils.temporary_locale(temp_locale):
locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8') date = utils.SafeDatetime(2014, 8, 14)
date = utils.SafeDatetime(2014, 8, 14) # we compare the lower() dates since macos10 returns
# we compare the lower() dates since macos10 returns # "Jeudi" for %A whereas linux reports "jeudi"
# "Jeudi" for %A whereas linux reports "jeudi" self.assertEqual(
self.assertEqual( 'jeudi, 14 août 2014',
'jeudi, 14 août 2014', utils.strftime(date, date_format="%A, %d %B %Y").lower())
utils.strftime(date, date_format="%A, %d %B %Y").lower()) df = utils.DateFormatter()
df = utils.DateFormatter() self.assertEqual(
self.assertEqual( 'jeudi, 14 août 2014',
'jeudi, 14 août 2014', df(date, date_format="%A, %d %B %Y").lower())
df(date, date_format="%A, %d %B %Y").lower())
# Let us now set the global locale to C: # Let us now set the global locale to C:
locale.setlocale(locale.LC_ALL, 'C') with utils.temporary_locale('C'):
# DateFormatter should still work as expected # DateFormatter should still work as expected
# since it is the whole point of DateFormatter # since it is the whole point of DateFormatter
# (This is where pre-2014/4/15 code fails on macos10) # (This is where pre-2014/4/15 code fails on macos10)
df_date = df(date, date_format="%A, %d %B %Y").lower() df_date = df(date, date_format="%A, %d %B %Y").lower()
self.assertEqual('jeudi, 14 août 2014', df_date) self.assertEqual('jeudi, 14 août 2014', df_date)
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or @unittest.skipUnless(locale_available('fr_FR.UTF-8') or
locale_available('French'), locale_available('French'),

View file

@ -116,18 +116,14 @@ class DateFormatter:
self.locale = locale.setlocale(locale.LC_TIME) self.locale = locale.setlocale(locale.LC_TIME)
def __call__(self, date, date_format): def __call__(self, date, date_format):
old_lc_time = locale.setlocale(locale.LC_TIME)
old_lc_ctype = locale.setlocale(locale.LC_CTYPE)
locale.setlocale(locale.LC_TIME, self.locale)
# on OSX, encoding from LC_CTYPE determines the unicode output in PY3 # on OSX, encoding from LC_CTYPE determines the unicode output in PY3
# make sure it's same as LC_TIME # make sure it's same as LC_TIME
locale.setlocale(locale.LC_CTYPE, self.locale) with temporary_locale(self.locale, locale.LC_TIME), \
temporary_locale(self.locale, locale.LC_CTYPE):
formatted = strftime(date, date_format) formatted = strftime(date, date_format)
locale.setlocale(locale.LC_TIME, old_lc_time)
locale.setlocale(locale.LC_CTYPE, old_lc_ctype)
return formatted return formatted
@ -872,3 +868,19 @@ def maybe_pluralize(count, singular, plural):
if count == 1: if count == 1:
selection = singular selection = singular
return '{} {}'.format(count, selection) return '{} {}'.format(count, selection)
@contextmanager
def temporary_locale(temp_locale=None, lc_category=locale.LC_ALL):
'''
Enable code to run in a context with a temporary locale
Resets the locale back when exiting context.
Use tests.support.TestCaseWithCLocale if you want every unit test in a
class to use the C locale.
'''
orig_locale = locale.setlocale(lc_category)
if temp_locale:
locale.setlocale(lc_category, temp_locale)
yield
locale.setlocale(lc_category, orig_locale)