]> granicus.if.org Git - python/commitdiff
Issue #20320: select.select() and select.kqueue.control() now round the timeout
authorVictor Stinner <victor.stinner@gmail.com>
Sun, 16 Feb 2014 23:02:43 +0000 (00:02 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Sun, 16 Feb 2014 23:02:43 +0000 (00:02 +0100)
aways from zero, instead of rounding towards zero.

It should make test_asyncio more reliable, especially test_timeout_rounding() test.

Include/pytime.h
Lib/test/test_time.py
Misc/NEWS
Modules/_datetimemodule.c
Modules/_testcapimodule.c
Modules/posixmodule.c
Modules/selectmodule.c
Modules/signalmodule.c
Modules/timemodule.c
Python/pytime.c

index 52902f5e3ac254116dc4e550f008210ef72dec3d..b0fc6d0354377deca062a6667561a6cd6a3eeb95 100644 (file)
@@ -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. */
index b8721a234f91ca77059e1c27aafc5a41e443c918..4bd1757b9c077957296c7975faeff339e4fa236c 100644 (file)
@@ -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):
index ac3a27af0479aaaef0e0491e9454087602758dab..0e442566bafa755e0709a11d80d154e337f7219b 100644 (file)
--- 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
index fce6bbf3fa573d770122a5fdc40c76549f985456..496ff348f173de257548192304168d5a9f5b367b 100644 (file)
@@ -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);
 }
index 9e81787845383dd276e68a072c2e29ccca8a681f..db2376d0369d58e885214a1688dd0544b3311602 100644 (file)
@@ -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);
 }
index 6cbc78f7b67e3863d9abc745fd831c1922a239cd..dc9bd555b80399175ff71fda57b701489a853cb2 100644 (file)
@@ -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;
index 0b11a01e743afffca1c8e981feb934744248ad04..8f8f6064ea986c9450e00460270e112f8ba4d9f0 100644 (file)
@@ -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) {
index 43e3ca1f2d291122894d7b75af3f63b8fced80bd..fedaddff356cd2fa7e74a812d647403ec76b0d22 100644 (file)
@@ -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;
index d3878b95d99fbdb5b17e5660cd036d01939c7b36..44540ea919741eebf8b3ff26bd510550fb378449 100644 (file)
@@ -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;
index beeab87e2c7399f3a6ee0e6553317bd3b7f74752..de6a41fe00bee8be7e121f3d6aecb95c64c0b948 100644 (file)
@@ -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