Skip to content

Commit

Permalink
pythongh-53203: Improve tests for strptime()
Browse files Browse the repository at this point in the history
Run them with different locales and different date and time.

Add the @run_with_locales() decorator to run the test with multiple
locales.

Improve the run_with_locale() context manager/decorator -- it now
catches only expected exceptions and reports the test as skipped if no
appropriate locale is available.
  • Loading branch information
serhiy-storchaka committed Oct 8, 2024
1 parent 6e3c70c commit 8573136
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 85 deletions.
4 changes: 2 additions & 2 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from test import support
from test.support import os_helper
from test.support import (
TestFailed, run_with_locale, no_tracing,
TestFailed, run_with_locales, no_tracing,
_2G, _4G, bigmemtest
)
from test.support.import_helper import forget
Expand Down Expand Up @@ -2895,7 +2895,7 @@ def test_float(self):
got = self.loads(pickle)
self.assert_is_copy(value, got)

@run_with_locale('LC_ALL', 'de_DE', 'fr_FR')
@run_with_locales('LC_ALL', 'de_DE', 'fr_FR', '')
def test_float_format(self):
# make sure that floats are formatted locale independent with proto 0
self.assertEqual(self.dumps(1.2, 0)[0:3], b'F1.')
Expand Down
53 changes: 49 additions & 4 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,8 +930,8 @@ def check_sizeof(test, o, size):
test.assertEqual(result, size, msg)

#=======================================================================
# Decorator for running a function in a different locale, correctly resetting
# it afterwards.
# Decorator/context manager for running a code in a different locale,
# correctly resetting it afterwards.

@contextlib.contextmanager
def run_with_locale(catstr, *locales):
Expand All @@ -942,23 +942,68 @@ def run_with_locale(catstr, *locales):
except AttributeError:
# if the test author gives us an invalid category string
raise
except:
except Exception:
# cannot retrieve original locale, so do nothing
locale = orig_locale = None
if '' not in locales:
raise unittest.SkipTest('no locales')
else:
for loc in locales:
try:
locale.setlocale(category, loc)
break
except:
except locale.Error:
pass
else:
if '' not in locales:
raise unittest.SkipTest(f'no locales {locales}')

try:
yield
finally:
if locale and orig_locale:
locale.setlocale(category, orig_locale)

#=======================================================================
# Decorator for running a function in multiple locales (if they are
# availasble) and resetting the original locale afterwards.

def run_with_locales(catstr, *locales):
def deco(func):
@functools.wraps(func)
def wrapper(self, /, *args, **kwargs):
dry_run = '' in locales
try:
import locale
category = getattr(locale, catstr)
orig_locale = locale.setlocale(category)
except AttributeError:
# if the test author gives us an invalid category string
raise
except Exception:
# cannot retrieve original locale, so do nothing
pass
else:
try:
for loc in locales:
with self.subTest(locale=loc):
try:
locale.setlocale(category, loc)
except locale.Error:
self.skipTest(f'no locale {loc!r}')
else:
dry_run = False
func(self, *args, **kwargs)
finally:
locale.setlocale(category, orig_locale)
if dry_run:
# no locales available, so just run the test
# with the current locale
with self.subTest(locale=None):
func(self, *args, **kwargs)
return wrapper
return deco

#=======================================================================
# Decorator for running a function in a specific timezone, correctly
# resetting it afterwards.
Expand Down
9 changes: 1 addition & 8 deletions Lib/test/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import contextlib
import copy
import io
import locale
import pickle
import sys
import unittest
Expand Down Expand Up @@ -1812,16 +1811,10 @@ def test_getwriter(self):
self.assertRaises(TypeError, codecs.getwriter)
self.assertRaises(LookupError, codecs.getwriter, "__spam__")

@support.run_with_locale('LC_CTYPE', 'tr_TR')
def test_lookup_issue1813(self):
# Issue #1813: under Turkish locales, lookup of some codecs failed
# because 'I' is lowercased as "ı" (dotless i)
oldlocale = locale.setlocale(locale.LC_CTYPE)
self.addCleanup(locale.setlocale, locale.LC_CTYPE, oldlocale)
try:
locale.setlocale(locale.LC_CTYPE, 'tr_TR')
except locale.Error:
# Unsupported locale on this system
self.skipTest('test needs Turkish locale')
c = codecs.lookup('ASCII')
self.assertEqual(c.name, 'ascii')

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,7 @@ def test_deprecated_N_format(self):
self.assertRaises(ValueError, format, h, '10Nf')
self.assertRaises(ValueError, format, h, 'Nx')

@run_with_locale('LC_ALL', 'ps_AF')
@run_with_locale('LC_ALL', 'ps_AF', '')
def test_wide_char_separator_decimal_point(self):
# locale with wide char separator and decimal point
Decimal = self.decimal.Decimal
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def check(s):
# non-UTF-8 byte string
check(b'123\xa0')

@support.run_with_locale('LC_NUMERIC', 'fr_FR', 'de_DE')
@support.run_with_locale('LC_NUMERIC', 'fr_FR', 'de_DE', '')
def test_float_with_comma(self):
# set locale to something that doesn't use '.' for the decimal point
# float must not accept the locale specific decimal point but
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def timevalues(self):
timezone(timedelta(0, 2 * 60 * 60))),
'"18-May-2033 05:33:20 +0200"']

@run_with_locale('LC_ALL', 'de_DE', 'fr_FR')
@run_with_locale('LC_ALL', 'de_DE', 'fr_FR', '')
# DST rules included to work around quirk where the Gnu C library may not
# otherwise restore the previous time zone
@run_with_tz('STD-1DST,M3.2.0,M11.1.0')
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -1665,7 +1665,7 @@ def test_startswith_endswith_errors(self):
self.assertIn('str', exc)
self.assertIn('tuple', exc)

@support.run_with_locale('LC_ALL', 'de_DE', 'fr_FR')
@support.run_with_locale('LC_ALL', 'de_DE', 'fr_FR', '')
def test_format_float(self):
# should not format with a comma, but always with C locale
self.assertEqual('1.0', '%.1f' % 1.0)
Expand Down
Loading

0 comments on commit 8573136

Please sign in to comment.