changeset: 95232:ae551abe398d user: Victor Stinner date: Sat Mar 28 01:26:47 2015 +0100 files: Include/pytime.h Lib/test/test_time.py Modules/_testcapimodule.c Modules/timemodule.c Python/pytime.c description: Issue #22117: Write unit tests for _PyTime_AsTimeval() * _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 diff -r f841d3bc30ee -r ae551abe398d Include/pytime.h --- a/Include/pytime.h Fri Mar 27 22:59:32 2015 +0100 +++ b/Include/pytime.h Sat Mar 28 01:26:47 2015 +0100 @@ -140,13 +140,15 @@ 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 diff -r f841d3bc30ee -r ae551abe398d Lib/test/test_time.py --- a/Lib/test/test_time.py Fri Mar 27 22:59:32 2015 +0100 +++ b/Lib/test/test_time.py Sat Mar 28 01:26:47 2015 +0100 @@ -902,6 +902,44 @@ 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): diff -r f841d3bc30ee -r ae551abe398d Modules/_testcapimodule.c --- a/Modules/_testcapimodule.c Fri Mar 27 22:59:32 2015 +0100 +++ b/Modules/_testcapimodule.c Sat Mar 28 01:26:47 2015 +0100 @@ -14,6 +14,10 @@ #include "marshal.h" #include +#ifdef MS_WINDOWS +# include +#endif + #ifdef WITH_THREAD #include "pythread.h" #endif /* WITH_THREAD */ @@ -3408,6 +3412,32 @@ 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 @@ 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 diff -r f841d3bc30ee -r ae551abe398d Modules/timemodule.c --- a/Modules/timemodule.c Fri Mar 27 22:59:32 2015 +0100 +++ b/Modules/timemodule.c Sat Mar 28 01:26:47 2015 +0100 @@ -1405,8 +1405,11 @@ 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); diff -r f841d3bc30ee -r ae551abe398d Python/pytime.c --- a/Python/pytime.c Fri Mar 27 22:59:32 2015 +0100 +++ b/Python/pytime.c Sat Mar 28 01:26:47 2015 +0100 @@ -540,9 +540,14 @@ _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 @@ 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 @@ /* 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; }