diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 8352b69f7..af11a6118 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -1519,7 +1519,7 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase): # bpo-41260: The parameter was named "fmt" in the pure python impl. t.strftime(format="%f") - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON; chrono fallback on Windows") def test_strftime_trailing_percent(self): # bpo-35066: Make sure trailing '%' doesn't cause datetime's strftime to # complain. Different libcs have different handling of trailing diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index caf36cf61..37b5543ba 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -614,7 +614,6 @@ class TestSupport(unittest.TestCase): self.check_print_warning("a\nb", 'Warning -- a\nWarning -- b\n') - @unittest.expectedFailureIf(sys.platform != "win32", "TODO: RUSTPYTHON; no has_strftime_extensions yet") def test_has_strftime_extensions(self): if sys.platform == "win32": self.assertFalse(support.has_strftime_extensions) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 35055e03b..1464aaffa 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -2,7 +2,6 @@ from test import support from test.support import warnings_helper import decimal import enum -import locale import math import platform import sys @@ -14,8 +13,12 @@ try: import _testcapi except ImportError: _testcapi = None +try: + import _testinternalcapi +except ImportError: + _testinternalcapi = None -from test.support import skip_if_buggy_ucrt_strfptime +from test.support import skip_if_buggy_ucrt_strfptime, SuppressCrashReport # Max year is only limited by the size of C int. SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 @@ -38,6 +41,10 @@ class _PyTime(enum.IntEnum): # Round away from zero ROUND_UP = 3 +# _PyTime_t is int64_t +PyTime_MIN = -2 ** 63 +PyTime_MAX = 2 ** 63 - 1 + # Rounding modes supported by PyTime ROUNDING_MODES = ( # (PyTime rounding method, decimal rounding method) @@ -109,6 +116,7 @@ class TimeTestCase(unittest.TestCase): 'need time.pthread_getcpuclockid()') @unittest.skipUnless(hasattr(time, 'clock_gettime'), 'need time.clock_gettime()') + @unittest.skipIf(support.is_emscripten, "Fails to find clock") def test_pthread_getcpuclockid(self): clk_id = time.pthread_getcpuclockid(threading.get_ident()) self.assertTrue(type(clk_id) is int) @@ -150,13 +158,32 @@ class TimeTestCase(unittest.TestCase): self.assertEqual(int(time.mktime(time.localtime(self.t))), int(self.t)) - def test_sleep(self): + def test_sleep_exceptions(self): + self.assertRaises(TypeError, time.sleep, []) + self.assertRaises(TypeError, time.sleep, "a") + self.assertRaises(TypeError, time.sleep, complex(0, 0)) + self.assertRaises(ValueError, time.sleep, -2) self.assertRaises(ValueError, time.sleep, -1) - time.sleep(1.2) + self.assertRaises(ValueError, time.sleep, -0.1) + + # Improved exception #81267 + with self.assertRaises(TypeError) as errmsg: + time.sleep([]) + self.assertIn("integer or float", str(errmsg.exception)) + + def test_sleep(self): + for value in [-0.0, 0, 0.0, 1e-100, 1e-9, 1e-6, 1, 1.2]: + with self.subTest(value=value): + time.sleep(value) + + def test_epoch(self): + # bpo-43869: Make sure that Python use the same Epoch on all platforms: + # January 1, 1970, 00:00:00 (UTC). + epoch = time.gmtime(0) + # Only test the date and time, ignore other gmtime() members + self.assertEqual(tuple(epoch)[:6], (1970, 1, 1, 0, 0, 0), epoch) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_strftime(self): tt = time.gmtime(self.t) for directive in ('a', 'A', 'b', 'B', 'c', 'd', 'H', 'I', @@ -169,8 +196,46 @@ class TimeTestCase(unittest.TestCase): self.fail('conversion specifier: %r failed.' % format) self.assertRaises(TypeError, time.strftime, b'%S', tt) - # embedded null character - self.assertRaises(ValueError, time.strftime, '%S\0', tt) + + def test_strftime_invalid_format(self): + tt = time.gmtime(self.t) + with SuppressCrashReport(): + for i in range(1, 128): + format = ' %' + chr(i) + with self.subTest(format=format): + try: + time.strftime(format, tt) + except ValueError as exc: + self.assertEqual(str(exc), 'Invalid format string') + + # TODO: RUSTPYTHON; chrono fallback on Windows does not preserve surrogates + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") + def test_strftime_special(self): + tt = time.gmtime(self.t) + s1 = time.strftime('%c', tt) + s2 = time.strftime('%B', tt) + # gh-52551, gh-78662: Unicode strings should pass through strftime, + # independently from locale. + self.assertEqual(time.strftime('\U0001f40d', tt), '\U0001f40d') + self.assertEqual(time.strftime('\U0001f4bb%c\U0001f40d%B', tt), f'\U0001f4bb{s1}\U0001f40d{s2}') + self.assertEqual(time.strftime('%c\U0001f4bb%B\U0001f40d', tt), f'{s1}\U0001f4bb{s2}\U0001f40d') + # Lone surrogates should pass through. + self.assertEqual(time.strftime('\ud83d', tt), '\ud83d') + self.assertEqual(time.strftime('\udc0d', tt), '\udc0d') + self.assertEqual(time.strftime('\ud83d%c\udc0d%B', tt), f'\ud83d{s1}\udc0d{s2}') + self.assertEqual(time.strftime('%c\ud83d%B\udc0d', tt), f'{s1}\ud83d{s2}\udc0d') + self.assertEqual(time.strftime('%c\udc0d%B\ud83d', tt), f'{s1}\udc0d{s2}\ud83d') + # Surrogate pairs should not recombine. + self.assertEqual(time.strftime('\ud83d\udc0d', tt), '\ud83d\udc0d') + self.assertEqual(time.strftime('%c\ud83d\udc0d%B', tt), f'{s1}\ud83d\udc0d{s2}') + # Surrogate-escaped bytes should not recombine. + self.assertEqual(time.strftime('\udcf0\udc9f\udc90\udc8d', tt), '\udcf0\udc9f\udc90\udc8d') + self.assertEqual(time.strftime('%c\udcf0\udc9f\udc90\udc8d%B', tt), f'{s1}\udcf0\udc9f\udc90\udc8d{s2}') + # gh-124531: The null character should not terminate the format string. + self.assertEqual(time.strftime('\0', tt), '\0') + self.assertEqual(time.strftime('\0'*1000, tt), '\0'*1000) + self.assertEqual(time.strftime('\0%c\0%B', tt), f'\0{s1}\0{s2}') + self.assertEqual(time.strftime('%c\0%B\0', tt), f'{s1}\0{s2}\0') def _bounds_checking(self, func): # Make sure that strftime() checks the bounds of the various parts @@ -229,8 +294,8 @@ class TimeTestCase(unittest.TestCase): self.assertRaises(ValueError, func, (1900, 1, 1, 0, 0, 0, 0, 367, -1)) - # TODO: RUSTPYTHON, ValueError: invalid struct_time parameter - @unittest.expectedFailure + # TODO: RUSTPYTHON; chrono on Windows rejects month=0/day=0 and raises wrong error type + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") def test_strftime_bounding_check(self): self._bounds_checking(lambda tup: time.strftime('', tup)) @@ -247,8 +312,8 @@ class TimeTestCase(unittest.TestCase): except ValueError: pass - # TODO: RUSTPYTHON, ValueError: invalid struct_time parameter - @unittest.expectedFailure + # TODO: RUSTPYTHON; chrono on Windows does not handle month=0/day=0 in struct_time + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") def test_default_values_for_zero(self): # Make sure that using all zeros uses the proper default # values. No test for daylight savings since strftime() does @@ -259,8 +324,8 @@ class TimeTestCase(unittest.TestCase): result = time.strftime("%Y %m %d %H %M %S %w %j", (2000,)+(0,)*8) self.assertEqual(expected, result) - # TODO: RUSTPYTHON - @unittest.expectedFailure + # TODO: RUSTPYTHON; chrono %Z on Windows not compatible with strptime + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") @skip_if_buggy_ucrt_strfptime def test_strptime(self): # Should be able to go round-trip from strftime to strptime without @@ -270,6 +335,8 @@ class TimeTestCase(unittest.TestCase): 'j', 'm', 'M', 'p', 'S', 'U', 'w', 'W', 'x', 'X', 'y', 'Y', 'Z', '%'): format = '%' + directive + if directive == 'd': + format += ',%Y' # Avoid GH-70647. strf_output = time.strftime(format, tt) try: time.strptime(strf_output, format) @@ -286,14 +353,20 @@ class TimeTestCase(unittest.TestCase): # check that this doesn't chain exceptions needlessly (see #17572) with self.assertRaises(ValueError) as e: time.strptime('', '%D') - self.assertIs(e.exception.__suppress_context__, True) - # additional check for IndexError branch (issue #19545) + self.assertTrue(e.exception.__suppress_context__) + # additional check for stray % branch with self.assertRaises(ValueError) as e: - time.strptime('19', '%Y %') - self.assertIs(e.exception.__suppress_context__, True) + time.strptime('%', '%') + self.assertTrue(e.exception.__suppress_context__) - # TODO: RUSTPYTHON, ValueError: invalid struct_time parameter - @unittest.expectedFailure + def test_strptime_leap_year(self): + # GH-70647: warns if parsing a format with a day and no year. + with self.assertWarnsRegex(DeprecationWarning, + r'.*day of month without a year.*'): + time.strptime('02-07 18:28', '%m-%d %H:%M') + + # TODO: RUSTPYTHON; chrono on Windows cannot handle month=0/big years + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") def test_asctime(self): time.asctime(time.gmtime(self.t)) @@ -309,13 +382,13 @@ class TimeTestCase(unittest.TestCase): self.assertRaises(TypeError, time.asctime, ()) self.assertRaises(TypeError, time.asctime, (0,) * 10) - # TODO: RUSTPYTHON, ValueError: invalid struct_time parameter - @unittest.expectedFailure + # TODO: RUSTPYTHON; chrono on Windows rejects month=0/day=0 + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") def test_asctime_bounding_check(self): self._bounds_checking(time.asctime) - # TODO: RUSTPYTHON - @unittest.expectedFailure + # TODO: RUSTPYTHON; chrono on Windows formats negative years differently + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") def test_ctime(self): t = time.mktime((1973, 9, 16, 1, 3, 52, 0, 0, -1)) self.assertEqual(time.ctime(t), 'Sun Sep 16 01:03:52 1973') @@ -415,8 +488,6 @@ class TimeTestCase(unittest.TestCase): for unreasonable in -1e200, 1e200: self.assertRaises(OverflowError, func, unreasonable) - # TODO: RUSTPYTHON, TypeError: unexpected type NoneType - @unittest.expectedFailure def test_ctime_without_arg(self): # Not sure how to check the values, since the clock could tick # at any time. Make sure these are at least accepted and @@ -424,8 +495,6 @@ class TimeTestCase(unittest.TestCase): time.ctime() time.ctime(None) - # TODO: RUSTPYTHON, TypeError: unexpected type NoneType - @unittest.expectedFailure def test_gmtime_without_arg(self): gt0 = time.gmtime() gt1 = time.gmtime(None) @@ -433,8 +502,6 @@ class TimeTestCase(unittest.TestCase): t1 = time.mktime(gt1) self.assertAlmostEqual(t1, t0, delta=0.2) - # TODO: RUSTPYTHON, TypeError: unexpected type NoneType - @unittest.expectedFailure def test_localtime_without_arg(self): lt0 = time.localtime() lt1 = time.localtime(None) @@ -469,8 +536,6 @@ class TimeTestCase(unittest.TestCase): pass self.assertEqual(time.strftime('%Z', tt), tzname) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform == "win32", "Implement get_clock_info for Windows.") def test_monotonic(self): # monotonic() should not go backward times = [time.monotonic() for n in range(100)] @@ -497,6 +562,12 @@ class TimeTestCase(unittest.TestCase): def test_perf_counter(self): time.perf_counter() + @unittest.skipIf( + support.is_wasi, "process_time not available on WASI" + ) + @unittest.skipIf( + support.is_emscripten, "process_time present but doesn't exclude sleep" + ) def test_process_time(self): # process_time() should not include time spend during a sleep start = time.process_time() @@ -512,7 +583,7 @@ class TimeTestCase(unittest.TestCase): def test_thread_time(self): if not hasattr(time, 'thread_time'): - if sys.platform.startswith(('linux', 'win')): + if sys.platform.startswith(('linux', 'android', 'win')): self.fail("time.thread_time() should be available on %r" % (sys.platform,)) else: @@ -520,11 +591,10 @@ class TimeTestCase(unittest.TestCase): # thread_time() should not include time spend during a sleep start = time.thread_time() - time.sleep(0.100) + time.sleep(0.200) stop = time.thread_time() - # use 20 ms because thread_time() has usually a resolution of 15 ms - # on Windows - self.assertLess(stop - start, 0.020) + # gh-143528: use 100 ms to support slow CI + self.assertLess(stop - start, 0.100) info = time.get_clock_info('thread_time') self.assertTrue(info.monotonic) @@ -572,8 +642,9 @@ class TimeTestCase(unittest.TestCase): 'perf_counter', 'process_time', 'time', - 'thread_time', ] + if hasattr(time, 'thread_time'): + clocks.append('thread_time') for name in clocks: with self.subTest(name=name): @@ -592,17 +663,8 @@ class TimeTestCase(unittest.TestCase): class TestLocale(unittest.TestCase): - def setUp(self): - self.oldloc = locale.setlocale(locale.LC_ALL) - - def tearDown(self): - locale.setlocale(locale.LC_ALL, self.oldloc) - + @support.run_with_locale('LC_ALL', 'fr_FR', '') def test_bug_3061(self): - try: - tmp = locale.setlocale(locale.LC_ALL, "fr_FR") - except locale.Error: - self.skipTest('could not set locale.LC_ALL to fr_FR') # This should not cause an exception time.strftime("%B", (2009,2,1,0,0,0,0,0,0)) @@ -613,8 +675,6 @@ class _TestAsctimeYear: def yearstr(self, y): return time.asctime((y,) + (0,) * 8).split()[-1] - # TODO: RUSTPYTHON, ValueError: invalid struct_time parameter - @unittest.expectedFailure def test_large_year(self): # Check that it doesn't crash for year > 9999 self.assertEqual(self.yearstr(12345), '12345') @@ -627,15 +687,17 @@ class _TestStrftimeYear: # assumes year >= 1900, so it does not specify the number # of digits. - # TODO: RUSTPYTHON - # if time.strftime('%Y', (1,) + (0,) * 8) == '0001': - # _format = '%04d' - # else: - # _format = '%d' + if time.strftime('%Y', (1,) + (0,) * 8) == '0001': + _format = '%04d' + else: + _format = '%d' def yearstr(self, y): return time.strftime('%Y', (y,) + (0,) * 8) + @unittest.skipUnless( + support.has_strftime_extensions, "requires strftime extension" + ) def test_4dyear(self): # Check that we can return the zero padded value. if self._format == '%04d': @@ -646,8 +708,7 @@ class _TestStrftimeYear: self.test_year('%04d', func=year4d) def skip_if_not_supported(y): - msg = "strftime() is limited to [1; 9999] with Visual Studio" - # Check that it doesn't crash for year > 9999 + msg = f"strftime() does not support year {y} on this platform" try: time.strftime('%Y', (y,) + (0,) * 8) except ValueError: @@ -670,8 +731,6 @@ class _TestStrftimeYear: class _Test4dYear: _format = '%d' - # TODO: RUSTPYTHON, ValueError: invalid struct_time parameter - @unittest.expectedFailure def test_year(self, fmt=None, func=None): fmt = fmt or self._format func = func or self.yearstr @@ -688,8 +747,6 @@ class _Test4dYear: self.assertEqual(self.yearstr(TIME_MAXYEAR).lstrip('+'), str(TIME_MAXYEAR)) self.assertRaises(OverflowError, self.yearstr, TIME_MAXYEAR + 1) - # TODO: RUSTPYTHON, ValueError: invalid struct_time parameter - @unittest.expectedFailure def test_negative(self): self.assertEqual(self.yearstr(-1), self._format % -1) self.assertEqual(self.yearstr(-1234), '-1234') @@ -704,32 +761,54 @@ class _Test4dYear: class TestAsctime4dyear(_TestAsctimeYear, _Test4dYear, unittest.TestCase): - pass + # TODO: RUSTPYTHON; chrono on Windows cannot handle month=0/day=0 in struct_time + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") + def test_year(self, *args, **kwargs): + return super().test_year(*args, **kwargs) -# class TestStrftime4dyear(_TestStrftimeYear, _Test4dYear, unittest.TestCase): -# pass + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") + def test_large_year(self): + return super().test_large_year() + + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") + def test_negative(self): + return super().test_negative() + +class TestStrftime4dyear(_TestStrftimeYear, _Test4dYear, unittest.TestCase): + # TODO: RUSTPYTHON; chrono on Windows cannot handle month=0/day=0 in struct_time + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") + def test_year(self, *args, **kwargs): + return super().test_year(*args, **kwargs) + + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") + def test_large_year(self): + return super().test_large_year() + + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") + def test_negative(self): + return super().test_negative() class TestPytime(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + # TODO: RUSTPYTHON; chrono %Z on Windows gives offset instead of timezone name + @unittest.expectedFailureIf(sys.platform == "win32", "TODO: RUSTPYTHON") @skip_if_buggy_ucrt_strfptime @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support") def test_localtime_timezone(self): # Get the localtime and examine it for the offset and zone. lt = time.localtime() - self.assertTrue(hasattr(lt, "tm_gmtoff")) - self.assertTrue(hasattr(lt, "tm_zone")) + self.assertHasAttr(lt, "tm_gmtoff") + self.assertHasAttr(lt, "tm_zone") # See if the offset and zone are similar to the module # attributes. if lt.tm_gmtoff is None: - self.assertTrue(not hasattr(time, "timezone")) + self.assertNotHasAttr(time, "timezone") else: self.assertEqual(lt.tm_gmtoff, -[time.timezone, time.altzone][lt.tm_isdst]) if lt.tm_zone is None: - self.assertTrue(not hasattr(time, "tzname")) + self.assertNotHasAttr(time, "tzname") else: self.assertEqual(lt.tm_zone, time.tzname[lt.tm_isdst]) @@ -748,18 +827,14 @@ class TestPytime(unittest.TestCase): self.assertEqual(new_lt9, lt) self.assertEqual(new_lt.tm_gmtoff, lt.tm_gmtoff) self.assertEqual(new_lt9.tm_zone, lt.tm_zone) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support") def test_strptime_timezone(self): t = time.strptime("UTC", "%Z") self.assertEqual(t.tm_zone, 'UTC') t = time.strptime("+0500", "%z") self.assertEqual(t.tm_gmtoff, 5 * 3600) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support") def test_short_times(self): @@ -772,7 +847,8 @@ class TestPytime(unittest.TestCase): self.assertIs(lt.tm_zone, None) -@unittest.skipIf(_testcapi is None, 'need the _testcapi module') +@unittest.skipIf(_testcapi is None, 'need the _testinternalcapi module') +@unittest.skipIf(_testinternalcapi is None, 'need the _testinternalcapi module') class CPyTimeTestCase: """ Base class to test the C _PyTime_t API. @@ -780,7 +856,7 @@ class CPyTimeTestCase: OVERFLOW_SECONDS = None def setUp(self): - from _testcapi import SIZEOF_TIME_T + from _testinternalcapi import SIZEOF_TIME_T bits = SIZEOF_TIME_T * 8 - 1 self.time_t_min = -2 ** bits self.time_t_max = 2 ** bits - 1 @@ -859,7 +935,7 @@ class CPyTimeTestCase: # test rounding ns_timestamps = self._rounding_values(use_float) valid_values = convert_values(ns_timestamps) - for time_rnd, decimal_rnd in ROUNDING_MODES : + for time_rnd, decimal_rnd in ROUNDING_MODES: with decimal.localcontext() as context: context.rounding = decimal_rnd @@ -908,36 +984,36 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase): OVERFLOW_SECONDS = math.ceil((2**63 + 1) / SEC_TO_NS) def test_FromSeconds(self): - from _testcapi import PyTime_FromSeconds + from _testinternalcapi import _PyTime_FromSeconds - # PyTime_FromSeconds() expects a C int, reject values out of range + # _PyTime_FromSeconds() expects a C int, reject values out of range def c_int_filter(secs): return (_testcapi.INT_MIN <= secs <= _testcapi.INT_MAX) - self.check_int_rounding(lambda secs, rnd: PyTime_FromSeconds(secs), + self.check_int_rounding(lambda secs, rnd: _PyTime_FromSeconds(secs), lambda secs: secs * SEC_TO_NS, value_filter=c_int_filter) # test nan for time_rnd, _ in ROUNDING_MODES: with self.assertRaises(TypeError): - PyTime_FromSeconds(float('nan')) + _PyTime_FromSeconds(float('nan')) def test_FromSecondsObject(self): - from _testcapi import PyTime_FromSecondsObject + from _testinternalcapi import _PyTime_FromSecondsObject self.check_int_rounding( - PyTime_FromSecondsObject, + _PyTime_FromSecondsObject, lambda secs: secs * SEC_TO_NS) self.check_float_rounding( - PyTime_FromSecondsObject, + _PyTime_FromSecondsObject, lambda ns: self.decimal_round(ns * SEC_TO_NS)) # test nan for time_rnd, _ in ROUNDING_MODES: with self.assertRaises(ValueError): - PyTime_FromSecondsObject(float('nan'), time_rnd) + _PyTime_FromSecondsObject(float('nan'), time_rnd) def test_AsSecondsDouble(self): from _testcapi import PyTime_AsSecondsDouble @@ -952,11 +1028,6 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase): float_converter, NS_TO_SEC) - # test nan - for time_rnd, _ in ROUNDING_MODES: - with self.assertRaises(TypeError): - PyTime_AsSecondsDouble(float('nan')) - def create_decimal_converter(self, denominator): denom = decimal.Decimal(denominator) @@ -967,7 +1038,7 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase): return converter def test_AsTimeval(self): - from _testcapi import PyTime_AsTimeval + from _testinternalcapi import _PyTime_AsTimeval us_converter = self.create_decimal_converter(US_TO_NS) @@ -984,35 +1055,78 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase): else: seconds_filter = self.time_t_filter - self.check_int_rounding(PyTime_AsTimeval, + self.check_int_rounding(_PyTime_AsTimeval, timeval_converter, NS_TO_SEC, value_filter=seconds_filter) - @unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimespec'), - 'need _testcapi.PyTime_AsTimespec') + @unittest.skipUnless(hasattr(_testinternalcapi, '_PyTime_AsTimespec'), + 'need _testinternalcapi._PyTime_AsTimespec') def test_AsTimespec(self): - from _testcapi import PyTime_AsTimespec + from _testinternalcapi import _PyTime_AsTimespec def timespec_converter(ns): return divmod(ns, SEC_TO_NS) - self.check_int_rounding(lambda ns, rnd: PyTime_AsTimespec(ns), + self.check_int_rounding(lambda ns, rnd: _PyTime_AsTimespec(ns), timespec_converter, NS_TO_SEC, value_filter=self.time_t_filter) - def test_AsMilliseconds(self): - from _testcapi import PyTime_AsMilliseconds + @unittest.skipUnless(hasattr(_testinternalcapi, '_PyTime_AsTimeval_clamp'), + 'need _testinternalcapi._PyTime_AsTimeval_clamp') + def test_AsTimeval_clamp(self): + from _testinternalcapi import _PyTime_AsTimeval_clamp - self.check_int_rounding(PyTime_AsMilliseconds, + if sys.platform == 'win32': + from _testcapi import LONG_MIN, LONG_MAX + tv_sec_max = LONG_MAX + tv_sec_min = LONG_MIN + else: + tv_sec_max = self.time_t_max + tv_sec_min = self.time_t_min + + for t in (PyTime_MIN, PyTime_MAX): + ts = _PyTime_AsTimeval_clamp(t, _PyTime.ROUND_CEILING) + with decimal.localcontext() as context: + context.rounding = decimal.ROUND_CEILING + us = self.decimal_round(decimal.Decimal(t) / US_TO_NS) + tv_sec, tv_usec = divmod(us, SEC_TO_US) + if tv_sec_max < tv_sec: + tv_sec = tv_sec_max + tv_usec = 0 + elif tv_sec < tv_sec_min: + tv_sec = tv_sec_min + tv_usec = 0 + self.assertEqual(ts, (tv_sec, tv_usec)) + + @unittest.skipUnless(hasattr(_testinternalcapi, '_PyTime_AsTimespec_clamp'), + 'need _testinternalcapi._PyTime_AsTimespec_clamp') + def test_AsTimespec_clamp(self): + from _testinternalcapi import _PyTime_AsTimespec_clamp + + for t in (PyTime_MIN, PyTime_MAX): + ts = _PyTime_AsTimespec_clamp(t) + tv_sec, tv_nsec = divmod(t, NS_TO_SEC) + if self.time_t_max < tv_sec: + tv_sec = self.time_t_max + tv_nsec = 0 + elif tv_sec < self.time_t_min: + tv_sec = self.time_t_min + tv_nsec = 0 + self.assertEqual(ts, (tv_sec, tv_nsec)) + + def test_AsMilliseconds(self): + from _testinternalcapi import _PyTime_AsMilliseconds + + self.check_int_rounding(_PyTime_AsMilliseconds, self.create_decimal_converter(MS_TO_NS), NS_TO_SEC) def test_AsMicroseconds(self): - from _testcapi import PyTime_AsMicroseconds + from _testinternalcapi import _PyTime_AsMicroseconds - self.check_int_rounding(PyTime_AsMicroseconds, + self.check_int_rounding(_PyTime_AsMicroseconds, self.create_decimal_converter(US_TO_NS), NS_TO_SEC) @@ -1026,13 +1140,13 @@ class TestOldPyTime(CPyTimeTestCase, unittest.TestCase): OVERFLOW_SECONDS = 2 ** 64 def test_object_to_time_t(self): - from _testcapi import pytime_object_to_time_t + from _testinternalcapi import _PyTime_ObjectToTime_t - self.check_int_rounding(pytime_object_to_time_t, + self.check_int_rounding(_PyTime_ObjectToTime_t, lambda secs: secs, value_filter=self.time_t_filter) - self.check_float_rounding(pytime_object_to_time_t, + self.check_float_rounding(_PyTime_ObjectToTime_t, self.decimal_round, value_filter=self.time_t_filter) @@ -1052,36 +1166,36 @@ class TestOldPyTime(CPyTimeTestCase, unittest.TestCase): return converter def test_object_to_timeval(self): - from _testcapi import pytime_object_to_timeval + from _testinternalcapi import _PyTime_ObjectToTimeval - self.check_int_rounding(pytime_object_to_timeval, + self.check_int_rounding(_PyTime_ObjectToTimeval, lambda secs: (secs, 0), value_filter=self.time_t_filter) - self.check_float_rounding(pytime_object_to_timeval, + self.check_float_rounding(_PyTime_ObjectToTimeval, self.create_converter(SEC_TO_US), value_filter=self.time_t_filter) # test nan for time_rnd, _ in ROUNDING_MODES: with self.assertRaises(ValueError): - pytime_object_to_timeval(float('nan'), time_rnd) + _PyTime_ObjectToTimeval(float('nan'), time_rnd) def test_object_to_timespec(self): - from _testcapi import pytime_object_to_timespec + from _testinternalcapi import _PyTime_ObjectToTimespec - self.check_int_rounding(pytime_object_to_timespec, + self.check_int_rounding(_PyTime_ObjectToTimespec, lambda secs: (secs, 0), value_filter=self.time_t_filter) - self.check_float_rounding(pytime_object_to_timespec, + self.check_float_rounding(_PyTime_ObjectToTimespec, self.create_converter(SEC_TO_NS), value_filter=self.time_t_filter) # test nan for time_rnd, _ in ROUNDING_MODES: with self.assertRaises(ValueError): - pytime_object_to_timespec(float('nan'), time_rnd) + _PyTime_ObjectToTimespec(float('nan'), time_rnd) @unittest.skipUnless(sys.platform == "darwin", "test weak linking on macOS") class TestTimeWeaklinking(unittest.TestCase): @@ -1107,11 +1221,11 @@ class TestTimeWeaklinking(unittest.TestCase): if mac_ver >= (10, 12): for name in clock_names: - self.assertTrue(hasattr(time, name), f"time.{name} is not available") + self.assertHasAttr(time, name) else: for name in clock_names: - self.assertFalse(hasattr(time, name), f"time.{name} is available") + self.assertNotHasAttr(time, name) if __name__ == "__main__":