]> granicus.if.org Git - python/commitdiff
Issue #22117: Write unit tests for _PyTime_AsTimeval()
authorVictor Stinner <victor.stinner@gmail.com>
Sat, 28 Mar 2015 00:26:47 +0000 (01:26 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Sat, 28 Mar 2015 00:26:47 +0000 (01:26 +0100)
* _PyTime_AsTimeval() now ensures that tv_usec is always positive
* _PyTime_AsTimespec() now ensures that tv_nsec is always positive
* _PyTime_AsTimeval() now returns an integer on overflow instead of raising an
  exception

Include/pytime.h
Lib/test/test_time.py
Modules/_testcapimodule.c
Modules/timemodule.c
Python/pytime.c

index 17d5ea17c4ff3decaaa8e29d2bc7f51aaa2ab1bd..72b64606ffb40162587dffd4506e9ab5663a6862 100644 (file)
@@ -140,13 +140,15 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
 PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t);
 
 /* Convert a timestamp to a timeval structure (microsecond resolution).
-   Raise an exception and return -1 on error, return 0 on success. */
+   tv_usec is always positive.
+   Return -1 if the conversion overflowed, return 0 on success. */
 PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
     struct timeval *tv,
     _PyTime_round_t round);
 
 #ifdef HAVE_CLOCK_GETTIME
 /* Convert a timestamp to a timespec structure (nanosecond resolution).
+   tv_nsec is always positive.
    Raise an exception and return -1 on error, return 0 on success. */
 PyAPI_FUNC(int) _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts);
 #endif
index cfec329f014de709847ace8e6322762aa68b95b7..78314a7228e9cf4120f9d0623c419c8cbfd9bbba 100644 (file)
@@ -902,6 +902,44 @@ class TestPyTime_t(unittest.TestCase):
                 self.assertEqual(PyTime_AsSecondsDouble(nanoseconds),
                                  seconds)
 
+    def test_timeval(self):
+        from _testcapi import PyTime_AsTimeval
+        for rnd in ALL_ROUNDING_METHODS:
+            for ns, tv in (
+                # microseconds
+                (0, (0, 0)),
+                (1000, (0, 1)),
+                (-1000, (-1, 999999)),
+
+                # seconds
+                (2 * SEC_TO_NS, (2, 0)),
+                (-3 * SEC_TO_NS, (-3, 0)),
+
+                # seconds + nanoseconds
+                (1234567000, (1, 234567)),
+                (-1234567000, (-2, 765433)),
+            ):
+                with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
+                    self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
+
+        UP = _PyTime.ROUND_UP
+        DOWN = _PyTime.ROUND_DOWN
+        for ns, tv, rnd in (
+            # nanoseconds
+            (1, (0, 1), UP),
+            (1, (0, 0), DOWN),
+            (-1, (0, 0), DOWN),
+            (-1, (-1, 999999), UP),
+
+            # seconds + nanoseconds
+            (1234567001, (1, 234568), UP),
+            (1234567001, (1, 234567), DOWN),
+            (-1234567001, (-2, 765433), DOWN),
+            (-1234567001, (-2, 765432), UP),
+        ):
+            with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
+                self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
+
     @unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimespec'),
                          'need _testcapi.PyTime_AsTimespec')
     def test_timespec(self):
index 5029105a882d369545e03661dd40a77ff1647b65..4503dc384f45cce2ed0127f79205c4d9ad2707c2 100644 (file)
 #include "marshal.h"
 #include <signal.h>
 
+#ifdef MS_WINDOWS
+#  include <winsock2.h>
+#endif
+
 #ifdef WITH_THREAD
 #include "pythread.h"
 #endif /* WITH_THREAD */
@@ -3408,6 +3412,32 @@ test_pytime_assecondsdouble(PyObject *self, PyObject *args)
     return PyFloat_FromDouble(d);
 }
 
+static PyObject *
+test_PyTime_AsTimeval(PyObject *self, PyObject *args)
+{
+    PY_LONG_LONG ns;
+    int round;
+    _PyTime_t t;
+    struct timeval tv;
+    PyObject *seconds;
+
+    if (!PyArg_ParseTuple(args, "Li", &ns, &round))
+        return NULL;
+    if (check_time_rounding(round) < 0)
+        return NULL;
+    t = _PyTime_FromNanoseconds(ns);
+    if (_PyTime_AsTimeval(t, &tv, round) < 0) {
+        PyErr_SetString(PyExc_OverflowError,
+                        "timeout doesn't fit into C timeval");
+        return NULL;
+    }
+
+    seconds = PyLong_FromLong((PY_LONG_LONG)tv.tv_sec);
+    if (seconds == NULL)
+        return NULL;
+    return Py_BuildValue("Nl", seconds, tv.tv_usec);
+}
+
 #ifdef HAVE_CLOCK_GETTIME
 static PyObject *
 test_PyTime_AsTimespec(PyObject *self, PyObject *args)
@@ -3590,6 +3620,7 @@ static PyMethodDef TestMethods[] = {
         return_result_with_error, METH_NOARGS},
     {"PyTime_FromSecondsObject", test_pytime_fromsecondsobject,  METH_VARARGS},
     {"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
+    {"PyTime_AsTimeval", test_PyTime_AsTimeval, METH_VARARGS},
 #ifdef HAVE_CLOCK_GETTIME
     {"PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS},
 #endif
index 880f3d214626daab49e1a832b1849ee4927034f3..21e6f433ffd7e331643051eadbd5fb5701fd30a4 100644 (file)
@@ -1405,8 +1405,11 @@ pysleep(_PyTime_t secs)
 
     do {
 #ifndef MS_WINDOWS
-        if (_PyTime_AsTimeval(secs, &timeout, _PyTime_ROUND_UP) < 0)
+        if (_PyTime_AsTimeval(secs, &timeout, _PyTime_ROUND_UP) < 0) {
+            PyErr_SetString(PyExc_OverflowError,
+                            "delay doesn't fit into C timeval");
             return -1;
+        }
 
         Py_BEGIN_ALLOW_THREADS
         err = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);
index bd94787241d7a178db9a7afce94d985f226bcf33..9893116465d1ede309597f6f84c36f6a8e0d700a 100644 (file)
@@ -540,9 +540,14 @@ int
 _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round)
 {
     _PyTime_t secs, ns;
+    int res = 0;
 
     secs = t / SEC_TO_NS;
     ns = t % SEC_TO_NS;
+    if (ns < 0) {
+        ns += SEC_TO_NS;
+        secs -= 1;
+    }
 
 #ifdef MS_WINDOWS
     /* On Windows, timeval.tv_sec is a long (32 bit),
@@ -550,8 +555,12 @@ _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round)
     assert(sizeof(tv->tv_sec) == sizeof(long));
 #if SIZEOF_TIME_T > SIZEOF_LONG
     if (secs > LONG_MAX) {
-        _PyTime_overflow();
-        return -1;
+        secs = LONG_MAX;
+        res = -1;
+    }
+    else if (secs < LONG_MIN) {
+        secs = LONG_MIN;
+        res = -1;
     }
 #endif
     tv->tv_sec = (long)secs;
@@ -559,32 +568,37 @@ _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round)
     /* On OpenBSD 5.4, timeval.tv_sec is a long.
        Example: long is 64-bit, whereas time_t is 32-bit. */
     tv->tv_sec = secs;
-    if ((_PyTime_t)tv->tv_sec != secs) {
-        _PyTime_overflow();
-        return -1;
-    }
+    if ((_PyTime_t)tv->tv_sec != secs)
+        res = -1;
 #endif
 
-    if (round == _PyTime_ROUND_UP)
+    if ((round == _PyTime_ROUND_UP) ^ (tv->tv_sec < 0))
         tv->tv_usec = (int)((ns + US_TO_NS - 1) / US_TO_NS);
     else
         tv->tv_usec = (int)(ns / US_TO_NS);
-    return 0;
+
+    if (tv->tv_usec >= SEC_TO_US) {
+        tv->tv_usec -= SEC_TO_US;
+        tv->tv_sec += 1;
+    }
+
+    return res;
 }
 
 #ifdef HAVE_CLOCK_GETTIME
 int
 _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts)
 {
-    _PyTime_t sec, nsec;
-    sec = t / SEC_TO_NS;
+    _PyTime_t secs, nsec;
+
+    secs = t / SEC_TO_NS;
     nsec = t % SEC_TO_NS;
     if (nsec < 0) {
         nsec += SEC_TO_NS;
-        sec -= 1;
+        secs -= 1;
     }
-    ts->tv_sec = (time_t)sec;
-    if ((_PyTime_t)ts->tv_sec != sec) {
+    ts->tv_sec = (time_t)secs;
+    if ((_PyTime_t)ts->tv_sec != secs) {
         _PyTime_overflow();
         return -1;
     }