summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2014-02-16 23:02:43 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2014-02-16 23:02:43 (GMT)
commit3c1b379ebd701cbd7686d0f0be95b88c5b3da8fe (patch)
treec1759ff720c8226c333052f6773c97d5b5c6356e
parent23f628de4ab75acde14de9593793e67ec74d851c (diff)
downloadcpython-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.h17
-rw-r--r--Lib/test/test_time.py130
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/_datetimemodule.c4
-rw-r--r--Modules/_testcapimodule.c31
-rw-r--r--Modules/posixmodule.c4
-rw-r--r--Modules/selectmodule.c10
-rw-r--r--Modules/signalmodule.c3
-rw-r--r--Modules/timemodule.c4
-rw-r--r--Python/pytime.c35
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):
diff --git a/Misc/NEWS b/Misc/NEWS
index ac3a27a..0e44256 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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