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.
diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py
index 9ac75e6..fef5d90 100644
--- a/Lib/test/test_strptime.py
+++ b/Lib/test/test_strptime.py
@@ -4,6 +4,7 @@
import time
import locale
import re
+import sets
from test import test_support
import _strptime
@@ -14,7 +15,12 @@
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 @@
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 @@
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 @@
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 @@
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 @@
# 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 @@
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 @@
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 @@
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 @@
Strptime12AMPMTests,
JulianTests,
CalculationTests,
+ CacheTests
)