diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2014-02-16 23:02:43 (GMT) |
---|---|---|
committer | Victor Stinner <victor.stinner@gmail.com> | 2014-02-16 23:02:43 (GMT) |
commit | 3c1b379ebd701cbd7686d0f0be95b88c5b3da8fe (patch) | |
tree | c1759ff720c8226c333052f6773c97d5b5c6356e | |
parent | 23f628de4ab75acde14de9593793e67ec74d851c (diff) | |
download | cpython-3c1b379ebd701cbd7686d0f0be95b88c5b3da8fe.zip cpython-3c1b379ebd701cbd7686d0f0be95b88c5b3da8fe.tar.gz cpython-3c1b379ebd701cbd7686d0f0be95b88c5b3da8fe.tar.bz2 |
Issue #20320: select.select() and select.kqueue.control() now round the timeout
aways from zero, instead of rounding towards zero.
It should make test_asyncio more reliable, especially test_timeout_rounding() test.
-rw-r--r-- | Include/pytime.h | 17 | ||||
-rw-r--r-- | Lib/test/test_time.py | 130 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | Modules/_datetimemodule.c | 4 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 31 | ||||
-rw-r--r-- | Modules/posixmodule.c | 4 | ||||
-rw-r--r-- | Modules/selectmodule.c | 10 | ||||
-rw-r--r-- | Modules/signalmodule.c | 3 | ||||
-rw-r--r-- | Modules/timemodule.c | 4 | ||||
-rw-r--r-- | Python/pytime.c | 35 |
10 files changed, 179 insertions, 62 deletions
diff --git a/Include/pytime.h b/Include/pytime.h index 52902f5..b0fc6d0 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -53,10 +53,19 @@ do { \ (tv_end.tv_usec - tv_start.tv_usec) * 0.000001) #ifndef Py_LIMITED_API + +typedef enum { + /* Round towards zero. */ + _PyTime_ROUND_DOWN=0, + /* Round away from zero. */ + _PyTime_ROUND_UP +} _PyTime_round_t; + /* Convert a number of seconds, int or float, to time_t. */ PyAPI_FUNC(int) _PyTime_ObjectToTime_t( PyObject *obj, - time_t *sec); + time_t *sec, + _PyTime_round_t); /* Convert a time_t to a PyLong. */ PyAPI_FUNC(PyObject *) _PyLong_FromTime_t( @@ -72,7 +81,8 @@ PyAPI_FUNC(time_t) _PyLong_AsTime_t( PyAPI_FUNC(int) _PyTime_ObjectToTimeval( PyObject *obj, time_t *sec, - long *usec); + long *usec, + _PyTime_round_t); /* Convert a number of seconds, int or float, to a timespec structure. nsec is in the range [0; 999999999] and rounded towards zero. @@ -80,7 +90,8 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimeval( PyAPI_FUNC(int) _PyTime_ObjectToTimespec( PyObject *obj, time_t *sec, - long *nsec); + long *nsec, + _PyTime_round_t); #endif /* Dummy to force linking. */ diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index b8721a2..4bd1757 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -14,6 +14,8 @@ except ImportError: SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1 TIME_MINYEAR = -TIME_MAXYEAR - 1 +_PyTime_ROUND_DOWN = 0 +_PyTime_ROUND_UP = 1 class TimeTestCase(unittest.TestCase): @@ -585,58 +587,116 @@ class TestPytime(unittest.TestCase): @support.cpython_only def test_time_t(self): from _testcapi import pytime_object_to_time_t - for obj, time_t in ( - (0, 0), - (-1, -1), - (-1.0, -1), - (-1.9, -1), - (1.0, 1), - (1.9, 1), + for obj, time_t, rnd in ( + # Round towards zero + (0, 0, _PyTime_ROUND_DOWN), + (-1, -1, _PyTime_ROUND_DOWN), + (-1.0, -1, _PyTime_ROUND_DOWN), + (-1.9, -1, _PyTime_ROUND_DOWN), + (1.0, 1, _PyTime_ROUND_DOWN), + (1.9, 1, _PyTime_ROUND_DOWN), + # Round away from zero + (0, 0, _PyTime_ROUND_UP), + (-1, -1, _PyTime_ROUND_UP), + (-1.0, -1, _PyTime_ROUND_UP), + (-1.9, -2, _PyTime_ROUND_UP), + (1.0, 1, _PyTime_ROUND_UP), + (1.9, 2, _PyTime_ROUND_UP), ): - self.assertEqual(pytime_object_to_time_t(obj), time_t) + self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t) + rnd = _PyTime_ROUND_DOWN for invalid in self.invalid_values: - self.assertRaises(OverflowError, pytime_object_to_time_t, invalid) + self.assertRaises(OverflowError, + pytime_object_to_time_t, invalid, rnd) @support.cpython_only def test_timeval(self): from _testcapi import pytime_object_to_timeval - for obj, timeval in ( - (0, (0, 0)), - (-1, (-1, 0)), - (-1.0, (-1, 0)), - (1e-6, (0, 1)), - (-1e-6, (-1, 999999)), - (-1.2, (-2, 800000)), - (1.1234560, (1, 123456)), - (1.1234569, (1, 123456)), - (-1.1234560, (-2, 876544)), - (-1.1234561, (-2, 876543)), + for obj, timeval, rnd in ( + # Round towards zero + (0, (0, 0), _PyTime_ROUND_DOWN), + (-1, (-1, 0), _PyTime_ROUND_DOWN), + (-1.0, (-1, 0), _PyTime_ROUND_DOWN), + (1e-6, (0, 1), _PyTime_ROUND_DOWN), + (1e-7, (0, 0), _PyTime_ROUND_DOWN), + (-1e-6, (-1, 999999), _PyTime_ROUND_DOWN), + (-1e-7, (-1, 999999), _PyTime_ROUND_DOWN), + (-1.2, (-2, 800000), _PyTime_ROUND_DOWN), + (0.9999999, (0, 999999), _PyTime_ROUND_DOWN), + (0.0000041, (0, 4), _PyTime_ROUND_DOWN), + (1.1234560, (1, 123456), _PyTime_ROUND_DOWN), + (1.1234569, (1, 123456), _PyTime_ROUND_DOWN), + (-0.0000040, (-1, 999996), _PyTime_ROUND_DOWN), + (-0.0000041, (-1, 999995), _PyTime_ROUND_DOWN), + (-1.1234560, (-2, 876544), _PyTime_ROUND_DOWN), + (-1.1234561, (-2, 876543), _PyTime_ROUND_DOWN), + # Round away from zero + (0, (0, 0), _PyTime_ROUND_UP), + (-1, (-1, 0), _PyTime_ROUND_UP), + (-1.0, (-1, 0), _PyTime_ROUND_UP), + (1e-6, (0, 1), _PyTime_ROUND_UP), + (1e-7, (0, 1), _PyTime_ROUND_UP), + (-1e-6, (-1, 999999), _PyTime_ROUND_UP), + (-1e-7, (-1, 999999), _PyTime_ROUND_UP), + (-1.2, (-2, 800000), _PyTime_ROUND_UP), + (0.9999999, (1, 0), _PyTime_ROUND_UP), + (0.0000041, (0, 5), _PyTime_ROUND_UP), + (1.1234560, (1, 123457), _PyTime_ROUND_UP), + (1.1234569, (1, 123457), _PyTime_ROUND_UP), + (-0.0000040, (-1, 999996), _PyTime_ROUND_UP), + (-0.0000041, (-1, 999995), _PyTime_ROUND_UP), + (-1.1234560, (-2, 876544), _PyTime_ROUND_UP), + (-1.1234561, (-2, 876543), _PyTime_ROUND_UP), ): - self.assertEqual(pytime_object_to_timeval(obj), timeval) + with self.subTest(obj=obj, round=rnd, timeval=timeval): + self.assertEqual(pytime_object_to_timeval(obj, rnd), timeval) + rnd = _PyTime_ROUND_DOWN for invalid in self.invalid_values: - self.assertRaises(OverflowError, pytime_object_to_timeval, invalid) + self.assertRaises(OverflowError, + pytime_object_to_timeval, invalid, rnd) @support.cpython_only def test_timespec(self): from _testcapi import pytime_object_to_timespec - for obj, timespec in ( - (0, (0, 0)), - (-1, (-1, 0)), - (-1.0, (-1, 0)), - (1e-9, (0, 1)), - (-1e-9, (-1, 999999999)), - (-1.2, (-2, 800000000)), - (1.1234567890, (1, 123456789)), - (1.1234567899, (1, 123456789)), - (-1.1234567890, (-2, 876543211)), - (-1.1234567891, (-2, 876543210)), + for obj, timespec, rnd in ( + # Round towards zero + (0, (0, 0), _PyTime_ROUND_DOWN), + (-1, (-1, 0), _PyTime_ROUND_DOWN), + (-1.0, (-1, 0), _PyTime_ROUND_DOWN), + (1e-9, (0, 1), _PyTime_ROUND_DOWN), + (1e-10, (0, 0), _PyTime_ROUND_DOWN), + (-1e-9, (-1, 999999999), _PyTime_ROUND_DOWN), + (-1e-10, (-1, 999999999), _PyTime_ROUND_DOWN), + (-1.2, (-2, 800000000), _PyTime_ROUND_DOWN), + (0.9999999999, (0, 999999999), _PyTime_ROUND_DOWN), + (1.1234567890, (1, 123456789), _PyTime_ROUND_DOWN), + (1.1234567899, (1, 123456789), _PyTime_ROUND_DOWN), + (-1.1234567890, (-2, 876543211), _PyTime_ROUND_DOWN), + (-1.1234567891, (-2, 876543210), _PyTime_ROUND_DOWN), + # Round away from zero + (0, (0, 0), _PyTime_ROUND_UP), + (-1, (-1, 0), _PyTime_ROUND_UP), + (-1.0, (-1, 0), _PyTime_ROUND_UP), + (1e-9, (0, 1), _PyTime_ROUND_UP), + (1e-10, (0, 1), _PyTime_ROUND_UP), + (-1e-9, (-1, 999999999), _PyTime_ROUND_UP), + (-1e-10, (-1, 999999999), _PyTime_ROUND_UP), + (-1.2, (-2, 800000000), _PyTime_ROUND_UP), + (0.9999999999, (1, 0), _PyTime_ROUND_UP), + (1.1234567890, (1, 123456790), _PyTime_ROUND_UP), + (1.1234567899, (1, 123456790), _PyTime_ROUND_UP), + (-1.1234567890, (-2, 876543211), _PyTime_ROUND_UP), + (-1.1234567891, (-2, 876543210), _PyTime_ROUND_UP), ): - self.assertEqual(pytime_object_to_timespec(obj), timespec) + with self.subTest(obj=obj, round=rnd, timespec=timespec): + self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec) + rnd = _PyTime_ROUND_DOWN for invalid in self.invalid_values: - self.assertRaises(OverflowError, pytime_object_to_timespec, invalid) + self.assertRaises(OverflowError, + pytime_object_to_timespec, invalid, rnd) @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support") def test_localtime_timezone(self): @@ -25,6 +25,9 @@ Core and Builtins Library ------- +- Issue #20320: select.select() and select.kqueue.control() now round the + timeout aways from zero, instead of rounding towards zero. + - Issue #20616: Add a format() method to tracemalloc.Traceback. - Issue #19744: the ensurepip installation step now just prints a warning to diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index fce6bbf..496ff34 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -2459,7 +2459,7 @@ date_local_from_object(PyObject *cls, PyObject *obj) struct tm *tm; time_t t; - if (_PyTime_ObjectToTime_t(obj, &t) == -1) + if (_PyTime_ObjectToTime_t(obj, &t, _PyTime_ROUND_DOWN) == -1) return NULL; tm = localtime(&t); @@ -4127,7 +4127,7 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, time_t timet; long us; - if (_PyTime_ObjectToTimeval(timestamp, &timet, &us) == -1) + if (_PyTime_ObjectToTimeval(timestamp, &timet, &us, _PyTime_ROUND_DOWN) == -1) return NULL; return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo); } diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 9e81787..db2376d 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2516,14 +2516,27 @@ run_in_subinterp(PyObject *self, PyObject *args) return PyLong_FromLong(r); } +static int +check_time_rounding(int round) +{ + if (round != _PyTime_ROUND_DOWN && round != _PyTime_ROUND_UP) { + PyErr_SetString(PyExc_ValueError, "invalid rounding"); + return -1; + } + return 0; +} + static PyObject * test_pytime_object_to_time_t(PyObject *self, PyObject *args) { PyObject *obj; time_t sec; - if (!PyArg_ParseTuple(args, "O:pytime_object_to_time_t", &obj)) + int round; + if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_time_t", &obj, &round)) + return NULL; + if (check_time_rounding(round) < 0) return NULL; - if (_PyTime_ObjectToTime_t(obj, &sec) == -1) + if (_PyTime_ObjectToTime_t(obj, &sec, round) == -1) return NULL; return _PyLong_FromTime_t(sec); } @@ -2534,9 +2547,12 @@ test_pytime_object_to_timeval(PyObject *self, PyObject *args) PyObject *obj; time_t sec; long usec; - if (!PyArg_ParseTuple(args, "O:pytime_object_to_timeval", &obj)) + int round; + if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_timeval", &obj, &round)) return NULL; - if (_PyTime_ObjectToTimeval(obj, &sec, &usec) == -1) + if (check_time_rounding(round) < 0) + return NULL; + if (_PyTime_ObjectToTimeval(obj, &sec, &usec, round) == -1) return NULL; return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), usec); } @@ -2547,9 +2563,12 @@ test_pytime_object_to_timespec(PyObject *self, PyObject *args) PyObject *obj; time_t sec; long nsec; - if (!PyArg_ParseTuple(args, "O:pytime_object_to_timespec", &obj)) + int round; + if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_timespec", &obj, &round)) + return NULL; + if (check_time_rounding(round) < 0) return NULL; - if (_PyTime_ObjectToTimespec(obj, &sec, &nsec) == -1) + if (_PyTime_ObjectToTimespec(obj, &sec, &nsec, round) == -1) return NULL; return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), nsec); } diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 6cbc78f..dc9bd55 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4901,9 +4901,9 @@ posix_utime(PyObject *self, PyObject *args, PyObject *kwargs) } utime.now = 0; if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0), - &a_sec, &a_nsec) == -1 || + &a_sec, &a_nsec, _PyTime_ROUND_DOWN) == -1 || _PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1), - &m_sec, &m_nsec) == -1) { + &m_sec, &m_nsec, _PyTime_ROUND_DOWN) == -1) { goto exit; } utime.atime_s = a_sec; diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 0b11a01..8f8f606 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -214,7 +214,8 @@ select_select(PyObject *self, PyObject *args) else { #ifdef MS_WINDOWS time_t sec; - if (_PyTime_ObjectToTimeval(tout, &sec, &tv.tv_usec) == -1) + if (_PyTime_ObjectToTimeval(tout, &sec, &tv.tv_usec, + _PyTime_ROUND_UP) == -1) return NULL; assert(sizeof(tv.tv_sec) == sizeof(long)); #if SIZEOF_TIME_T > SIZEOF_LONG @@ -229,7 +230,8 @@ select_select(PyObject *self, PyObject *args) /* 64-bit OS X has struct timeval.tv_usec as an int (and thus still 4 bytes as required), but no longer defined by a long. */ long tv_usec; - if (_PyTime_ObjectToTimeval(tout, &tv.tv_sec, &tv_usec) == -1) + if (_PyTime_ObjectToTimeval(tout, &tv.tv_sec, &tv_usec, + _PyTime_ROUND_UP) == -1) return NULL; tv.tv_usec = tv_usec; #endif @@ -2037,8 +2039,8 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args) ptimeoutspec = NULL; } else if (PyNumber_Check(otimeout)) { - if (_PyTime_ObjectToTimespec(otimeout, - &timeout.tv_sec, &timeout.tv_nsec) == -1) + if (_PyTime_ObjectToTimespec(otimeout, &timeout.tv_sec, + &timeout.tv_nsec, _PyTime_ROUND_UP) == -1) return NULL; if (timeout.tv_sec < 0) { diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 43e3ca1..fedaddf 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -799,7 +799,8 @@ signal_sigtimedwait(PyObject *self, PyObject *args) &signals, &timeout)) return NULL; - if (_PyTime_ObjectToTimespec(timeout, &tv_sec, &tv_nsec) == -1) + if (_PyTime_ObjectToTimespec(timeout, &tv_sec, &tv_nsec, + _PyTime_ROUND_DOWN) == -1) return NULL; buf.tv_sec = tv_sec; buf.tv_nsec = tv_nsec; diff --git a/Modules/timemodule.c b/Modules/timemodule.c index d3878b9..44540ea 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -193,7 +193,7 @@ time_clock_settime(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj)) return NULL; - if (_PyTime_ObjectToTimespec(obj, &tv_sec, &tv_nsec) == -1) + if (_PyTime_ObjectToTimespec(obj, &tv_sec, &tv_nsec, _PyTime_ROUND_DOWN) == -1) return NULL; tp.tv_sec = tv_sec; tp.tv_nsec = tv_nsec; @@ -341,7 +341,7 @@ parse_time_t_args(PyObject *args, char *format, time_t *pwhen) whent = time(NULL); } else { - if (_PyTime_ObjectToTime_t(ot, &whent) == -1) + if (_PyTime_ObjectToTime_t(ot, &whent, _PyTime_ROUND_DOWN) == -1) return 0; } *pwhen = whent; diff --git a/Python/pytime.c b/Python/pytime.c index beeab87..de6a41f 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -152,7 +152,7 @@ _PyLong_FromTime_t(time_t t) static int _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator, - double denominator) + double denominator, _PyTime_round_t round) { assert(denominator <= LONG_MAX); if (PyFloat_Check(obj)) { @@ -167,6 +167,20 @@ _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator, intpart -= 1.0; } + floatpart *= denominator; + if (round == _PyTime_ROUND_UP) { + if (intpart >= 0) { + floatpart = ceil(floatpart); + if (floatpart >= denominator) { + floatpart = 0.0; + intpart += 1.0; + } + } + else { + floatpart = floor(floatpart); + } + } + *sec = (time_t)intpart; err = intpart - (double)*sec; if (err <= -1.0 || err >= 1.0) { @@ -174,7 +188,6 @@ _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator, return -1; } - floatpart *= denominator; *numerator = (long)floatpart; return 0; } @@ -188,12 +201,18 @@ _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator, } int -_PyTime_ObjectToTime_t(PyObject *obj, time_t *sec) +_PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round) { if (PyFloat_Check(obj)) { double d, intpart, err; d = PyFloat_AsDouble(obj); + if (round == _PyTime_ROUND_UP) { + if (d >= 0) + d = ceil(d); + else + d = floor(d); + } (void)modf(d, &intpart); *sec = (time_t)intpart; @@ -213,15 +232,17 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec) } int -_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec) +_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec, + _PyTime_round_t round) { - return _PyTime_ObjectToDenominator(obj, sec, nsec, 1e9); + return _PyTime_ObjectToDenominator(obj, sec, nsec, 1e9, round); } int -_PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec) +_PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec, + _PyTime_round_t round) { - return _PyTime_ObjectToDenominator(obj, sec, usec, 1e6); + return _PyTime_ObjectToDenominator(obj, sec, usec, 1e6, round); } void |