Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 95e9cef

Browse files
committed
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
1 parent b7df314 commit 95e9cef

5 files changed

Lines changed: 103 additions & 15 deletions

File tree

Include/pytime.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,15 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
140140
PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t);
141141

142142
/* Convert a timestamp to a timeval structure (microsecond resolution).
143-
Raise an exception and return -1 on error, return 0 on success. */
143+
tv_usec is always positive.
144+
Return -1 if the conversion overflowed, return 0 on success. */
144145
PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
145146
struct timeval *tv,
146147
_PyTime_round_t round);
147148

148149
#ifdef HAVE_CLOCK_GETTIME
149150
/* Convert a timestamp to a timespec structure (nanosecond resolution).
151+
tv_nsec is always positive.
150152
Raise an exception and return -1 on error, return 0 on success. */
151153
PyAPI_FUNC(int) _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts);
152154
#endif

Lib/test/test_time.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,44 @@ def test_AsSecondsDouble(self):
902902
self.assertEqual(PyTime_AsSecondsDouble(nanoseconds),
903903
seconds)
904904

905+
def test_timeval(self):
906+
from _testcapi import PyTime_AsTimeval
907+
for rnd in ALL_ROUNDING_METHODS:
908+
for ns, tv in (
909+
# microseconds
910+
(0, (0, 0)),
911+
(1000, (0, 1)),
912+
(-1000, (-1, 999999)),
913+
914+
# seconds
915+
(2 * SEC_TO_NS, (2, 0)),
916+
(-3 * SEC_TO_NS, (-3, 0)),
917+
918+
# seconds + nanoseconds
919+
(1234567000, (1, 234567)),
920+
(-1234567000, (-2, 765433)),
921+
):
922+
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
923+
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
924+
925+
UP = _PyTime.ROUND_UP
926+
DOWN = _PyTime.ROUND_DOWN
927+
for ns, tv, rnd in (
928+
# nanoseconds
929+
(1, (0, 1), UP),
930+
(1, (0, 0), DOWN),
931+
(-1, (0, 0), DOWN),
932+
(-1, (-1, 999999), UP),
933+
934+
# seconds + nanoseconds
935+
(1234567001, (1, 234568), UP),
936+
(1234567001, (1, 234567), DOWN),
937+
(-1234567001, (-2, 765433), DOWN),
938+
(-1234567001, (-2, 765432), UP),
939+
):
940+
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
941+
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
942+
905943
@unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimespec'),
906944
'need _testcapi.PyTime_AsTimespec')
907945
def test_timespec(self):

Modules/_testcapimodule.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
#include "marshal.h"
1515
#include <signal.h>
1616

17+
#ifdef MS_WINDOWS
18+
# include <winsock2.h>
19+
#endif
20+
1721
#ifdef WITH_THREAD
1822
#include "pythread.h"
1923
#endif /* WITH_THREAD */
@@ -3408,6 +3412,32 @@ test_pytime_assecondsdouble(PyObject *self, PyObject *args)
34083412
return PyFloat_FromDouble(d);
34093413
}
34103414

3415+
static PyObject *
3416+
test_PyTime_AsTimeval(PyObject *self, PyObject *args)
3417+
{
3418+
PY_LONG_LONG ns;
3419+
int round;
3420+
_PyTime_t t;
3421+
struct timeval tv;
3422+
PyObject *seconds;
3423+
3424+
if (!PyArg_ParseTuple(args, "Li", &ns, &round))
3425+
return NULL;
3426+
if (check_time_rounding(round) < 0)
3427+
return NULL;
3428+
t = _PyTime_FromNanoseconds(ns);
3429+
if (_PyTime_AsTimeval(t, &tv, round) < 0) {
3430+
PyErr_SetString(PyExc_OverflowError,
3431+
"timeout doesn't fit into C timeval");
3432+
return NULL;
3433+
}
3434+
3435+
seconds = PyLong_FromLong((PY_LONG_LONG)tv.tv_sec);
3436+
if (seconds == NULL)
3437+
return NULL;
3438+
return Py_BuildValue("Nl", seconds, tv.tv_usec);
3439+
}
3440+
34113441
#ifdef HAVE_CLOCK_GETTIME
34123442
static PyObject *
34133443
test_PyTime_AsTimespec(PyObject *self, PyObject *args)
@@ -3590,6 +3620,7 @@ static PyMethodDef TestMethods[] = {
35903620
return_result_with_error, METH_NOARGS},
35913621
{"PyTime_FromSecondsObject", test_pytime_fromsecondsobject, METH_VARARGS},
35923622
{"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
3623+
{"PyTime_AsTimeval", test_PyTime_AsTimeval, METH_VARARGS},
35933624
#ifdef HAVE_CLOCK_GETTIME
35943625
{"PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS},
35953626
#endif

Modules/timemodule.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1405,8 +1405,11 @@ pysleep(_PyTime_t secs)
14051405

14061406
do {
14071407
#ifndef MS_WINDOWS
1408-
if (_PyTime_AsTimeval(secs, &timeout, _PyTime_ROUND_UP) < 0)
1408+
if (_PyTime_AsTimeval(secs, &timeout, _PyTime_ROUND_UP) < 0) {
1409+
PyErr_SetString(PyExc_OverflowError,
1410+
"delay doesn't fit into C timeval");
14091411
return -1;
1412+
}
14101413

14111414
Py_BEGIN_ALLOW_THREADS
14121415
err = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);

Python/pytime.c

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -540,51 +540,65 @@ int
540540
_PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round)
541541
{
542542
_PyTime_t secs, ns;
543+
int res = 0;
543544

544545
secs = t / SEC_TO_NS;
545546
ns = t % SEC_TO_NS;
547+
if (ns < 0) {
548+
ns += SEC_TO_NS;
549+
secs -= 1;
550+
}
546551

547552
#ifdef MS_WINDOWS
548553
/* On Windows, timeval.tv_sec is a long (32 bit),
549554
whereas time_t can be 64-bit. */
550555
assert(sizeof(tv->tv_sec) == sizeof(long));
551556
#if SIZEOF_TIME_T > SIZEOF_LONG
552557
if (secs > LONG_MAX) {
553-
_PyTime_overflow();
554-
return -1;
558+
secs = LONG_MAX;
559+
res = -1;
560+
}
561+
else if (secs < LONG_MIN) {
562+
secs = LONG_MIN;
563+
res = -1;
555564
}
556565
#endif
557566
tv->tv_sec = (long)secs;
558567
#else
559568
/* On OpenBSD 5.4, timeval.tv_sec is a long.
560569
Example: long is 64-bit, whereas time_t is 32-bit. */
561570
tv->tv_sec = secs;
562-
if ((_PyTime_t)tv->tv_sec != secs) {
563-
_PyTime_overflow();
564-
return -1;
565-
}
571+
if ((_PyTime_t)tv->tv_sec != secs)
572+
res = -1;
566573
#endif
567574

568-
if (round == _PyTime_ROUND_UP)
575+
if ((round == _PyTime_ROUND_UP) ^ (tv->tv_sec < 0))
569576
tv->tv_usec = (int)((ns + US_TO_NS - 1) / US_TO_NS);
570577
else
571578
tv->tv_usec = (int)(ns / US_TO_NS);
572-
return 0;
579+
580+
if (tv->tv_usec >= SEC_TO_US) {
581+
tv->tv_usec -= SEC_TO_US;
582+
tv->tv_sec += 1;
583+
}
584+
585+
return res;
573586
}
574587

575588
#ifdef HAVE_CLOCK_GETTIME
576589
int
577590
_PyTime_AsTimespec(_PyTime_t t, struct timespec *ts)
578591
{
579-
_PyTime_t sec, nsec;
580-
sec = t / SEC_TO_NS;
592+
_PyTime_t secs, nsec;
593+
594+
secs = t / SEC_TO_NS;
581595
nsec = t % SEC_TO_NS;
582596
if (nsec < 0) {
583597
nsec += SEC_TO_NS;
584-
sec -= 1;
598+
secs -= 1;
585599
}
586-
ts->tv_sec = (time_t)sec;
587-
if ((_PyTime_t)ts->tv_sec != sec) {
600+
ts->tv_sec = (time_t)secs;
601+
if ((_PyTime_t)ts->tv_sec != secs) {
588602
_PyTime_overflow();
589603
return -1;
590604
}

0 commit comments

Comments
 (0)