Commit 474335ce authored by Brett Cannon's avatar Brett Cannon

Re-introduce caching of TimeRE and compiled regexes with added thread-safety.

Also remove now unnecessary property attributes for thread safety
(no longer have lazy attributes) and code simplicity reasons.

Timezone storage has been reworked to be simpler and more flexible.  All values
in LocaleTime instances are lower-cased.  This is all done to simplify the
module.

The module now assumes nothing beyond the strptime function will be exposed for
general use beyond providing functionality for strptime.
parent 8ab27dfa
This diff is collapsed.
......@@ -4,6 +4,7 @@ import unittest
import time
import locale
import re
import sets
from test import test_support
import _strptime
......@@ -14,7 +15,12 @@ class getlang_Tests(unittest.TestCase):
self.failUnlessEqual(_strptime._getlang(), locale.getlocale(locale.LC_TIME))
class LocaleTime_Tests(unittest.TestCase):
"""Tests for _strptime.LocaleTime."""
"""Tests for _strptime.LocaleTime.
All values are lower-cased when stored in LocaleTime, so make sure to
compare values after running ``lower`` on them.
"""
def setUp(self):
"""Create time tuple based on current time."""
......@@ -27,7 +33,7 @@ class LocaleTime_Tests(unittest.TestCase):
tuple_position of time_tuple. Uses error_msg as error message.
"""
strftime_output = time.strftime(directive, self.time_tuple)
strftime_output = time.strftime(directive, self.time_tuple).lower()
comparison = testing[self.time_tuple[tuple_position]]
self.failUnless(strftime_output in testing, "%s: not found in tuple" %
error_msg)
......@@ -53,7 +59,7 @@ class LocaleTime_Tests(unittest.TestCase):
def test_am_pm(self):
# Make sure AM/PM representation done properly
strftime_output = time.strftime("%p", self.time_tuple)
strftime_output = time.strftime("%p", self.time_tuple).lower()
self.failUnless(strftime_output in self.LT_ins.am_pm,
"AM/PM representation not in tuple")
if self.time_tuple[3] < 12: position = 0
......@@ -63,9 +69,10 @@ class LocaleTime_Tests(unittest.TestCase):
def test_timezone(self):
# Make sure timezone is correct
timezone = time.strftime("%Z", self.time_tuple)
timezone = time.strftime("%Z", self.time_tuple).lower()
if timezone:
self.failUnless(timezone in self.LT_ins.timezone,
self.failUnless(timezone in self.LT_ins.timezone[0] or \
timezone in self.LT_ins.timezone[1],
"timezone %s not found in %s" %
(timezone, self.LT_ins.timezone))
......@@ -89,7 +96,8 @@ class LocaleTime_Tests(unittest.TestCase):
self.failUnless(strftime_output == time.strftime(self.LT_ins.LC_time,
magic_date),
"LC_time incorrect")
LT = _strptime.LocaleTime(am_pm=('',''))
LT = _strptime.LocaleTime()
LT.am_pm = ('', '')
self.failUnless(LT.LC_time, "LocaleTime's LC directives cannot handle "
"empty strings")
......@@ -98,44 +106,6 @@ class LocaleTime_Tests(unittest.TestCase):
# Assuming locale has not changed between now and when self.LT_ins was created
self.failUnlessEqual(self.LT_ins.lang, _strptime._getlang())
def test_by_hand_input(self):
# Test passed-in initialization value checks
self.failUnless(_strptime.LocaleTime(f_weekday=range(7)),
"Argument size check for f_weekday failed")
self.assertRaises(TypeError, _strptime.LocaleTime, f_weekday=range(8))
self.assertRaises(TypeError, _strptime.LocaleTime, f_weekday=range(6))
self.failUnless(_strptime.LocaleTime(a_weekday=range(7)),
"Argument size check for a_weekday failed")
self.assertRaises(TypeError, _strptime.LocaleTime, a_weekday=range(8))
self.assertRaises(TypeError, _strptime.LocaleTime, a_weekday=range(6))
self.failUnless(_strptime.LocaleTime(f_month=range(12)),
"Argument size check for f_month failed")
self.assertRaises(TypeError, _strptime.LocaleTime, f_month=range(11))
self.assertRaises(TypeError, _strptime.LocaleTime, f_month=range(13))
self.failUnless(len(_strptime.LocaleTime(f_month=range(12)).f_month) == 13,
"dummy value for f_month not added")
self.failUnless(_strptime.LocaleTime(a_month=range(12)),
"Argument size check for a_month failed")
self.assertRaises(TypeError, _strptime.LocaleTime, a_month=range(11))
self.assertRaises(TypeError, _strptime.LocaleTime, a_month=range(13))
self.failUnless(len(_strptime.LocaleTime(a_month=range(12)).a_month) == 13,
"dummy value for a_month not added")
self.failUnless(_strptime.LocaleTime(am_pm=range(2)),
"Argument size check for am_pm failed")
self.assertRaises(TypeError, _strptime.LocaleTime, am_pm=range(1))
self.assertRaises(TypeError, _strptime.LocaleTime, am_pm=range(3))
self.failUnless(_strptime.LocaleTime(timezone=range(2)),
"Argument size check for timezone failed")
self.assertRaises(TypeError, _strptime.LocaleTime, timezone=range(1))
self.assertRaises(TypeError, _strptime.LocaleTime, timezone=range(3))
def test_unknowntimezone(self):
# Handle timezone set to ('','') properly.
# Fixes bug #661354
locale_time = _strptime.LocaleTime(timezone=('',''))
self.failUnless("%Z" not in locale_time.LC_date,
"when timezone == ('',''), string.replace('','%Z') is "
"occuring")
class TimeRETests(unittest.TestCase):
"""Tests for TimeRE."""
......@@ -145,21 +115,6 @@ class TimeRETests(unittest.TestCase):
self.time_re = _strptime.TimeRE()
self.locale_time = _strptime.LocaleTime()
def test_getitem(self):
# Make sure that __getitem__ works properly
self.failUnless(self.time_re['m'],
"Fetching 'm' directive (built-in) failed")
self.failUnless(self.time_re['b'],
"Fetching 'b' directive (built w/ __tupleToRE) failed")
for name in self.locale_time.a_month:
self.failUnless(self.time_re['b'].find(name) != -1,
"Not all abbreviated month names in regex")
self.failUnless(self.time_re['c'],
"Fetching 'c' directive (built w/ format) failed")
self.failUnless(self.time_re['c'].find('%') == -1,
"Conversion of 'c' directive failed; '%' found")
self.assertRaises(KeyError, self.time_re.__getitem__, '1')
def test_pattern(self):
# Test TimeRE.pattern
pattern_string = self.time_re.pattern(r"%a %A %d")
......@@ -210,7 +165,8 @@ class TimeRETests(unittest.TestCase):
def test_blankpattern(self):
# Make sure when tuple or something has no values no regex is generated.
# Fixes bug #661354
test_locale = _strptime.LocaleTime(timezone=('',''))
test_locale = _strptime.LocaleTime()
test_locale.timezone = (sets.ImmutableSet(), sets.ImmutableSet())
self.failUnless(_strptime.TimeRE(test_locale).pattern("%Z") == '',
"with timezone == ('',''), TimeRE().pattern('%Z') != ''")
......@@ -413,6 +369,43 @@ class CalculationTests(unittest.TestCase):
self.failUnless(result.tm_wday == self.time_tuple.tm_wday,
"Calculation of day of the week failed;"
"%s != %s" % (result.tm_wday, self.time_tuple.tm_wday))
class CacheTests(unittest.TestCase):
"""Test that caching works properly."""
def test_time_re_recreation(self):
# Make sure cache is recreated when current locale does not match what
# cached object was created with.
_strptime.strptime("10", "%d")
_strptime._TimeRE_cache.locale_time.lang = "Ni"
original_time_re = id(_strptime._TimeRE_cache)
_strptime.strptime("10", "%d")
self.failIfEqual(original_time_re, id(_strptime._TimeRE_cache))
def test_regex_cleanup(self):
# Make sure cached regexes are discarded when cache becomes "full".
try:
del _strptime._regex_cache['%d']
except KeyError:
pass
bogus_key = 0
while len(_strptime._regex_cache) <= _strptime._CACHE_MAX_SIZE:
_strptime._regex_cache[bogus_key] = None
bogus_key += 1
_strptime.strptime("10", "%d")
self.failUnlessEqual(len(_strptime._regex_cache), 1)
def test_new_localetime(self):
# A new LocaleTime instance should be created when a new TimeRE object
# is created.
locale_time_id = id(_strptime._TimeRE_cache.locale_time)
_strptime._TimeRE_cache.locale_time.lang = "Ni"
_strptime.strptime("10", "%d")
self.failIfEqual(locale_time_id,
id(_strptime._TimeRE_cache.locale_time))
def test_main():
test_support.run_unittest(
getlang_Tests,
......@@ -422,6 +415,7 @@ def test_main():
Strptime12AMPMTests,
JulianTests,
CalculationTests,
CacheTests
)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment