Skip to content

Commit

Permalink
gh-110088, gh-109878: Fix test_asyncio timeouts (#110092)
Browse files Browse the repository at this point in the history
Fix test_asyncio timeouts: don't measure the maximum duration, a test
should not measure a CI performance. Only measure the minimum
duration when a task has a timeout or delay. Add CLOCK_RES to
test_asyncio.utils.
  • Loading branch information
vstinner authored Sep 29, 2023
1 parent e27adc6 commit db0a258
Show file tree
Hide file tree
Showing 7 changed files with 19 additions and 54 deletions.
7 changes: 2 additions & 5 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def cb():
self.loop.stop()

self.loop._process_events = mock.Mock()
delay = 0.1
delay = 0.100

when = self.loop.time() + delay
self.loop.call_at(when, cb)
Expand All @@ -282,10 +282,7 @@ def cb():
dt = self.loop.time() - t0

# 50 ms: maximum granularity of the event loop
self.assertGreaterEqual(dt, delay - 0.050, dt)
# tolerate a difference of +800 ms because some Python buildbots
# are really slow
self.assertLessEqual(dt, 0.9, dt)
self.assertGreaterEqual(dt, delay - test_utils.CLOCK_RES)
with self.assertRaises(TypeError, msg="when cannot be None"):
self.loop.call_at(None, cb)

Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,11 @@ async def coro2():
# 15.6 msec, we use fairly long sleep times here (~100 msec).

def test_run_until_complete(self):
delay = 0.100
t0 = self.loop.time()
self.loop.run_until_complete(asyncio.sleep(0.1))
t1 = self.loop.time()
self.assertTrue(0.08 <= t1-t0 <= 0.8, t1-t0)
self.loop.run_until_complete(asyncio.sleep(delay))
dt = self.loop.time() - t0
self.assertGreaterEqual(dt, delay - test_utils.CLOCK_RES)

def test_run_until_complete_stopped(self):

Expand Down Expand Up @@ -1717,7 +1718,6 @@ def _run_once():
self.loop._run_once = _run_once

async def wait():
loop = self.loop
await asyncio.sleep(1e-2)
await asyncio.sleep(1e-4)
await asyncio.sleep(1e-6)
Expand Down
20 changes: 0 additions & 20 deletions Lib/test/test_asyncio/test_timeouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ async def test_nested_timeouts(self):
self.assertTrue(cm2.expired())

async def test_waiter_cancelled(self):
loop = asyncio.get_running_loop()
cancelled = False
with self.assertRaises(TimeoutError):
async with asyncio.timeout(0.01):
Expand All @@ -59,39 +58,26 @@ async def test_waiter_cancelled(self):

async def test_timeout_not_called(self):
loop = asyncio.get_running_loop()
t0 = loop.time()
async with asyncio.timeout(10) as cm:
await asyncio.sleep(0.01)
t1 = loop.time()

self.assertFalse(cm.expired())
# 2 sec for slow CI boxes
self.assertLess(t1-t0, 2)
self.assertGreater(cm.when(), t1)

async def test_timeout_disabled(self):
loop = asyncio.get_running_loop()
t0 = loop.time()
async with asyncio.timeout(None) as cm:
await asyncio.sleep(0.01)
t1 = loop.time()

self.assertFalse(cm.expired())
self.assertIsNone(cm.when())
# 2 sec for slow CI boxes
self.assertLess(t1-t0, 2)

async def test_timeout_at_disabled(self):
loop = asyncio.get_running_loop()
t0 = loop.time()
async with asyncio.timeout_at(None) as cm:
await asyncio.sleep(0.01)
t1 = loop.time()

self.assertFalse(cm.expired())
self.assertIsNone(cm.when())
# 2 sec for slow CI boxes
self.assertLess(t1-t0, 2)

async def test_timeout_zero(self):
loop = asyncio.get_running_loop()
Expand All @@ -101,8 +87,6 @@ async def test_timeout_zero(self):
await asyncio.sleep(10)
t1 = loop.time()
self.assertTrue(cm.expired())
# 2 sec for slow CI boxes
self.assertLess(t1-t0, 2)
self.assertTrue(t0 <= cm.when() <= t1)

async def test_timeout_zero_sleep_zero(self):
Expand All @@ -113,8 +97,6 @@ async def test_timeout_zero_sleep_zero(self):
await asyncio.sleep(0)
t1 = loop.time()
self.assertTrue(cm.expired())
# 2 sec for slow CI boxes
self.assertLess(t1-t0, 2)
self.assertTrue(t0 <= cm.when() <= t1)

async def test_timeout_in_the_past_sleep_zero(self):
Expand All @@ -125,8 +107,6 @@ async def test_timeout_in_the_past_sleep_zero(self):
await asyncio.sleep(0)
t1 = loop.time()
self.assertTrue(cm.expired())
# 2 sec for slow CI boxes
self.assertLess(t1-t0, 2)
self.assertTrue(t0 >= cm.when() <= t1)

async def test_foreign_exception_passed(self):
Expand Down
15 changes: 0 additions & 15 deletions Lib/test/test_asyncio/test_waitfor.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,30 +66,22 @@ async def test_wait_for_timeout_less_then_0_or_0_future_done(self):
fut = loop.create_future()
fut.set_result('done')

t0 = loop.time()
ret = await asyncio.wait_for(fut, 0)
t1 = loop.time()

self.assertEqual(ret, 'done')
self.assertTrue(fut.done())
self.assertLess(t1 - t0, 0.1)

async def test_wait_for_timeout_less_then_0_or_0_coroutine_do_not_started(self):
loop = asyncio.get_running_loop()

foo_started = False

async def foo():
nonlocal foo_started
foo_started = True

with self.assertRaises(asyncio.TimeoutError):
t0 = loop.time()
await asyncio.wait_for(foo(), 0)
t1 = loop.time()

self.assertEqual(foo_started, False)
self.assertLess(t1 - t0, 0.1)

async def test_wait_for_timeout_less_then_0_or_0(self):
loop = asyncio.get_running_loop()
Expand All @@ -113,18 +105,14 @@ async def foo():
await started

with self.assertRaises(asyncio.TimeoutError):
t0 = loop.time()
await asyncio.wait_for(fut, timeout)
t1 = loop.time()

self.assertTrue(fut.done())
# it should have been cancelled due to the timeout
self.assertTrue(fut.cancelled())
self.assertEqual(foo_running, False)
self.assertLess(t1 - t0, 0.1)

async def test_wait_for(self):
loop = asyncio.get_running_loop()
foo_running = None

async def foo():
Expand All @@ -139,13 +127,10 @@ async def foo():
fut = asyncio.create_task(foo())

with self.assertRaises(asyncio.TimeoutError):
t0 = loop.time()
await asyncio.wait_for(fut, 0.1)
t1 = loop.time()
self.assertTrue(fut.done())
# it should have been cancelled due to the timeout
self.assertTrue(fut.cancelled())
self.assertLess(t1 - t0, support.SHORT_TIMEOUT)
self.assertEqual(foo_running, False)

async def test_wait_for_blocking(self):
Expand Down
13 changes: 3 additions & 10 deletions Lib/test/test_asyncio/test_windows_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,29 +163,25 @@ def test_wait_for_handle(self):

# Wait for unset event with 0.5s timeout;
# result should be False at timeout
fut = self.loop._proactor.wait_for_handle(event, 0.5)
timeout = 0.5
fut = self.loop._proactor.wait_for_handle(event, timeout)
start = self.loop.time()
done = self.loop.run_until_complete(fut)
elapsed = self.loop.time() - start

self.assertEqual(done, False)
self.assertFalse(fut.result())
# bpo-31008: Tolerate only 450 ms (at least 500 ms expected),
# because of bad clock resolution on Windows
self.assertTrue(0.45 <= elapsed <= 0.9, elapsed)
self.assertGreaterEqual(elapsed, timeout - test_utils.CLOCK_RES)

_overlapped.SetEvent(event)

# Wait for set event;
# result should be True immediately
fut = self.loop._proactor.wait_for_handle(event, 10)
start = self.loop.time()
done = self.loop.run_until_complete(fut)
elapsed = self.loop.time() - start

self.assertEqual(done, True)
self.assertTrue(fut.result())
self.assertTrue(0 <= elapsed < 0.3, elapsed)

# asyncio issue #195: cancelling a done _WaitHandleFuture
# must not crash
Expand All @@ -199,11 +195,8 @@ def test_wait_for_handle_cancel(self):
# CancelledError should be raised immediately
fut = self.loop._proactor.wait_for_handle(event, 10)
fut.cancel()
start = self.loop.time()
with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(fut)
elapsed = self.loop.time() - start
self.assertTrue(0 <= elapsed < 0.1, elapsed)

# asyncio issue #195: cancelling a _WaitHandleFuture twice
# must not crash
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_asyncio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
from test.support import threading_helper


# Use the maximum known clock resolution (gh-75191, gh-110088): Windows
# GetTickCount64() has a resolution of 15.6 ms. Use 20 ms to tolerate rounding
# issues.
CLOCK_RES = 0.020


def data_file(*filename):
fullname = os.path.join(support.TEST_HOME_DIR, *filename)
if os.path.isfile(fullname):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix test_asyncio timeouts: don't measure the maximum duration, a test should
not measure a CI performance. Only measure the minimum duration when a task has
a timeout or delay. Add ``CLOCK_RES`` to ``test_asyncio.utils``. Patch by
Victor Stinner.

0 comments on commit db0a258

Please sign in to comment.