tests/extmod: Add uasyncio tests.

All .exp files are included because they require CPython 3.8 which may not
always be available.
pull/5332/head
Damien George 2019-11-13 21:08:22 +11:00
rodzic 63b9944382
commit c4935f3049
35 zmienionych plików z 1161 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,26 @@
# Test that tasks return their value correctly to the caller
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def foo():
return 42
async def main():
# Call function directly via an await
print(await foo())
# Create a task and await on it
task = asyncio.create_task(foo())
print(await task)
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,2 @@
42
42

Wyświetl plik

@ -0,0 +1,43 @@
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
try:
import utime
ticks = utime.ticks_ms
ticks_diff = utime.ticks_diff
except:
import time
ticks = lambda: int(time.time() * 1000)
ticks_diff = lambda t1, t0: t1 - t0
async def delay_print(t, s):
await asyncio.sleep(t)
print(s)
async def main():
print("start")
await asyncio.sleep(0.001)
print("after sleep")
t0 = ticks()
await delay_print(0.02, "short")
t1 = ticks()
await delay_print(0.04, "long")
t2 = ticks()
print("took {} {}".format(round(ticks_diff(t1, t0), -1), round(ticks_diff(t2, t1), -1)))
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,5 @@
start
after sleep
short
long
took 20 40

Wyświetl plik

@ -0,0 +1,24 @@
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def forever():
print("forever start")
await asyncio.sleep(10)
async def main():
print("main start")
asyncio.create_task(forever())
await asyncio.sleep(0.001)
print("main done")
return 42
print(asyncio.run(main()))

Wyświetl plik

@ -0,0 +1,4 @@
main start
forever start
main done
42

Wyświetl plik

@ -0,0 +1,37 @@
# Test fairness of cancelling a task
# That tasks which continuously cancel each other don't take over the scheduler
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task(id, other):
for i in range(3):
try:
print("start", id)
await asyncio.sleep(0)
print("done", id)
except asyncio.CancelledError as er:
print("cancelled", id)
if other is not None:
print(id, "cancels", other)
tasks[other].cancel()
async def main():
global tasks
tasks = [
asyncio.create_task(task(0, 1)),
asyncio.create_task(task(1, 0)),
asyncio.create_task(task(2, None)),
]
await tasks[2]
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,24 @@
start 0
start 1
start 2
done 0
0 cancels 1
start 0
cancelled 1
1 cancels 0
start 1
done 2
start 2
cancelled 0
0 cancels 1
start 0
cancelled 1
1 cancels 0
start 1
done 2
start 2
cancelled 0
0 cancels 1
cancelled 1
1 cancels 0
done 2

Wyświetl plik

@ -0,0 +1,37 @@
# Test fairness of cancelling a task
# That tasks which keeps being cancelled by multiple other tasks gets a chance to run
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task_a():
try:
while True:
print("sleep a")
await asyncio.sleep(0)
except asyncio.CancelledError:
print("cancelled a")
async def task_b(id, other):
while other.cancel():
print("sleep b", id)
await asyncio.sleep(0)
print("done b", id)
async def main():
t = asyncio.create_task(task_a())
for i in range(3):
asyncio.create_task(task_b(i, t))
await t
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,8 @@
sleep a
sleep b 0
sleep b 1
sleep b 2
cancelled a
done b 0
done b 1
done b 2

Wyświetl plik

@ -0,0 +1,31 @@
# Test a task cancelling itself (currently unsupported)
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task():
print("task start")
global_task.cancel()
async def main():
global global_task
global_task = asyncio.create_task(task())
try:
await global_task
except asyncio.CancelledError:
print("main cancel")
print("main done")
try:
asyncio.run(main())
except RuntimeError as er:
print(er)

Wyświetl plik

@ -0,0 +1,2 @@
task start
cannot cancel self

Wyświetl plik

@ -0,0 +1,85 @@
# Test cancelling a task
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task(s, allow_cancel):
try:
print("task start")
await asyncio.sleep(s)
print("task done")
except asyncio.CancelledError as er:
print("task cancel")
if allow_cancel:
raise er
async def task2(allow_cancel):
print("task 2")
try:
await asyncio.create_task(task(0.05, allow_cancel))
except asyncio.CancelledError as er:
print("task 2 cancel")
raise er
print("task 2 done")
async def main():
# Cancel task immediately
t = asyncio.create_task(task(2, True))
print(t.cancel())
# Cancel task after it has started
t = asyncio.create_task(task(2, True))
await asyncio.sleep(0.01)
print(t.cancel())
print("main sleep")
await asyncio.sleep(0.01)
# Cancel task multiple times after it has started
t = asyncio.create_task(task(2, True))
await asyncio.sleep(0.01)
for _ in range(4):
print(t.cancel())
print("main sleep")
await asyncio.sleep(0.01)
# Await on a cancelled task
print("main wait")
try:
await t
except asyncio.CancelledError:
print("main got CancelledError")
# Cancel task after it has finished
t = asyncio.create_task(task(0.01, False))
await asyncio.sleep(0.05)
print(t.cancel())
# Nested: task2 waits on task, task2 is cancelled (should cancel task then task2)
print("----")
t = asyncio.create_task(task2(True))
await asyncio.sleep(0.01)
print("main cancel")
t.cancel()
print("main sleep")
await asyncio.sleep(0.1)
# Nested: task2 waits on task, task2 is cancelled but task doesn't allow it (task2 should continue)
print("----")
t = asyncio.create_task(task2(False))
await asyncio.sleep(0.01)
print("main cancel")
t.cancel()
print("main sleep")
await asyncio.sleep(0.1)
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,31 @@
True
task start
True
main sleep
task cancel
task start
True
True
True
True
main sleep
task cancel
main wait
main got CancelledError
task start
task done
False
----
task 2
task start
main cancel
main sleep
task cancel
task 2 cancel
----
task 2
task start
main cancel
main sleep
task cancel
task 2 done

Wyświetl plik

@ -0,0 +1,98 @@
# Test Event class
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task(id, ev):
print("start", id)
print(await ev.wait())
print("end", id)
async def task_delay_set(t, ev):
await asyncio.sleep(t)
print("set event")
ev.set()
async def main():
ev = asyncio.Event()
# Set and clear without anything waiting, and test is_set()
print(ev.is_set())
ev.set()
print(ev.is_set())
ev.clear()
print(ev.is_set())
# Create 2 tasks waiting on the event
print("----")
asyncio.create_task(task(1, ev))
asyncio.create_task(task(2, ev))
print("yield")
await asyncio.sleep(0)
print("set event")
ev.set()
print("yield")
await asyncio.sleep(0)
# Create a task waiting on the already-set event
print("----")
asyncio.create_task(task(3, ev))
print("yield")
await asyncio.sleep(0)
# Clear event, start a task, then set event again
print("----")
print("clear event")
ev.clear()
asyncio.create_task(task(4, ev))
await asyncio.sleep(0)
print("set event")
ev.set()
await asyncio.sleep(0)
# Cancel a task waiting on an event (set event then cancel task)
print("----")
ev = asyncio.Event()
t = asyncio.create_task(task(5, ev))
await asyncio.sleep(0)
ev.set()
t.cancel()
await asyncio.sleep(0.1)
# Cancel a task waiting on an event (cancel task then set event)
print("----")
ev = asyncio.Event()
t = asyncio.create_task(task(6, ev))
await asyncio.sleep(0)
t.cancel()
ev.set()
await asyncio.sleep(0.1)
# Wait for an event that does get set in time
print("----")
ev.clear()
asyncio.create_task(task_delay_set(0.01, ev))
await asyncio.wait_for(ev.wait(), 0.1)
await asyncio.sleep(0)
# Wait for an event that doesn't get set in time
print("----")
ev.clear()
asyncio.create_task(task_delay_set(0.1, ev))
try:
await asyncio.wait_for(ev.wait(), 0.01)
except asyncio.TimeoutError:
print("TimeoutError")
await ev.wait()
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,33 @@
False
True
False
----
yield
start 1
start 2
set event
yield
True
end 1
True
end 2
----
yield
start 3
True
end 3
----
clear event
start 4
set event
True
end 4
----
start 5
----
start 6
----
set event
----
TimeoutError
set event

Wyświetl plik

@ -0,0 +1,40 @@
# Test fairness of Event.set()
# That tasks which continuously wait on events don't take over the scheduler
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task1(id):
for i in range(4):
print("sleep", id)
await asyncio.sleep(0)
async def task2(id, ev):
for i in range(4):
ev.set()
ev.clear()
print("wait", id)
await ev.wait()
async def main():
ev = asyncio.Event()
tasks = [
asyncio.create_task(task1(0)),
asyncio.create_task(task2(2, ev)),
asyncio.create_task(task1(1)),
asyncio.create_task(task2(3, ev)),
]
await tasks[1]
ev.set()
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,16 @@
sleep 0
wait 2
sleep 1
wait 3
sleep 0
sleep 1
wait 2
sleep 0
sleep 1
wait 3
sleep 0
sleep 1
wait 2
wait 3
wait 2
wait 3

Wyświetl plik

@ -0,0 +1,60 @@
# Test general exception handling
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
# main task raising an exception
async def main():
print("main start")
raise ValueError(1)
print("main done")
try:
asyncio.run(main())
except ValueError as er:
print("ValueError", er.args[0])
# sub-task raising an exception
async def task():
print("task start")
raise ValueError(2)
print("task done")
async def main():
print("main start")
t = asyncio.create_task(task())
await t
print("main done")
try:
asyncio.run(main())
except ValueError as er:
print("ValueError", er.args[0])
# main task raising an exception with sub-task not yet scheduled
# TODO not currently working, task is never scheduled
async def task():
# print('task run') uncomment this line when it works
pass
async def main():
print("main start")
asyncio.create_task(task())
raise ValueError(3)
print("main done")
try:
asyncio.run(main())
except ValueError as er:
print("ValueError", er.args[0])

Wyświetl plik

@ -0,0 +1,7 @@
main start
ValueError 1
main start
task start
ValueError 2
main start
ValueError 3

Wyświetl plik

@ -0,0 +1,32 @@
# Test fairness of scheduler
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task(id, t):
print("task start", id)
while True:
if t > 0:
print("task work", id)
await asyncio.sleep(t)
async def main():
t1 = asyncio.create_task(task(1, -0.01))
t2 = asyncio.create_task(task(2, 0.1))
t3 = asyncio.create_task(task(3, 0.2))
await asyncio.sleep(0.5)
t1.cancel()
t2.cancel()
t3.cancel()
print("finish")
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,12 @@
task start 1
task start 2
task work 2
task start 3
task work 3
task work 2
task work 3
task work 2
task work 2
task work 3
task work 2
finish

Wyświetl plik

@ -0,0 +1,49 @@
# test uasyncio.gather() function
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print("Task {}: Compute factorial({})...".format(name, i))
await asyncio.sleep(0.01)
f *= i
print("Task {}: factorial({}) = {}".format(name, number, f))
return f
async def task(id):
print("start", id)
await asyncio.sleep(0.2)
print("end", id)
async def gather_task():
print("gather_task")
await asyncio.gather(task(1), task(2))
print("gather_task2")
async def main():
# Simple gather with return values
print(await asyncio.gather(factorial("A", 2), factorial("B", 3), factorial("C", 4),))
# Cancel a multi gather
# TODO doesn't work, Task should not forward cancellation from gather to sub-task
# but rather CancelledError should cancel the gather directly, which will then cancel
# all sub-tasks explicitly
# t = asyncio.create_task(gather_task())
# await asyncio.sleep(0.1)
# t.cancel()
# await asyncio.sleep(0.01)
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,10 @@
Task A: Compute factorial(2)...
Task B: Compute factorial(2)...
Task C: Compute factorial(2)...
Task A: factorial(2) = 2
Task B: Compute factorial(3)...
Task C: Compute factorial(3)...
Task B: factorial(3) = 6
Task C: Compute factorial(4)...
Task C: factorial(4) = 24
[2, 6, 24]

Wyświetl plik

@ -0,0 +1,20 @@
# Test get_event_loop()
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def main():
print("start")
await asyncio.sleep(0.01)
print("end")
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Wyświetl plik

@ -0,0 +1,46 @@
# test that basic scheduling of tasks, and uasyncio.sleep_ms, does not use the heap
import micropython
# strict stackless builds can't call functions without allocating a frame on the heap
try:
f = lambda: 0
micropython.heap_lock()
f()
micropython.heap_unlock()
except RuntimeError:
print("SKIP")
raise SystemExit
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task(id, n, t):
for i in range(n):
print(id, i)
await asyncio.sleep_ms(t)
async def main():
t1 = asyncio.create_task(task(1, 4, 10))
t2 = asyncio.create_task(task(2, 4, 25))
micropython.heap_lock()
print("start")
await asyncio.sleep_ms(1)
print("sleep")
await asyncio.sleep_ms(100)
print("finish")
micropython.heap_unlock()
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,11 @@
start
1 0
2 0
sleep
1 1
1 2
2 1
1 3
2 2
2 3
finish

Wyświetl plik

@ -0,0 +1,97 @@
# Test Lock class
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task_loop(id, lock):
print("task start", id)
for i in range(3):
async with lock:
print("task have", id, i)
print("task end", id)
async def task_sleep(lock):
async with lock:
print("task have", lock.locked())
await asyncio.sleep(0.2)
print("task release", lock.locked())
await lock.acquire()
print("task have again")
lock.release()
async def task_cancel(id, lock, to_cancel=None):
try:
async with lock:
print("task got", id)
await asyncio.sleep(0.1)
print("task release", id)
if to_cancel:
to_cancel[0].cancel()
except asyncio.CancelledError:
print("task cancel", id)
async def main():
lock = asyncio.Lock()
# Basic acquire/release
print(lock.locked())
await lock.acquire()
print(lock.locked())
await asyncio.sleep(0)
lock.release()
print(lock.locked())
await asyncio.sleep(0)
# Use with "async with"
async with lock:
print("have lock")
# 3 tasks wanting the lock
print("----")
asyncio.create_task(task_loop(1, lock))
asyncio.create_task(task_loop(2, lock))
t3 = asyncio.create_task(task_loop(3, lock))
await lock.acquire()
await asyncio.sleep(0)
lock.release()
await t3
# 2 sleeping tasks both wanting the lock
print("----")
asyncio.create_task(task_sleep(lock))
await asyncio.sleep(0.1)
await task_sleep(lock)
# 3 tasks, the first cancelling the second, the third should still run
print("----")
ts = [None]
asyncio.create_task(task_cancel(0, lock, ts))
ts[0] = asyncio.create_task(task_cancel(1, lock))
asyncio.create_task(task_cancel(2, lock))
await asyncio.sleep(0.3)
print(lock.locked())
# 3 tasks, the second and third being cancelled while waiting on the lock
print("----")
t0 = asyncio.create_task(task_cancel(0, lock))
t1 = asyncio.create_task(task_cancel(1, lock))
t2 = asyncio.create_task(task_cancel(2, lock))
await asyncio.sleep(0.05)
t1.cancel()
await asyncio.sleep(0.1)
t2.cancel()
await asyncio.sleep(0.1)
print(lock.locked())
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,41 @@
False
True
False
have lock
----
task start 1
task start 2
task start 3
task have 1 0
task have 2 0
task have 3 0
task have 1 1
task have 2 1
task have 3 1
task have 1 2
task end 1
task have 2 2
task end 2
task have 3 2
task end 3
----
task have True
task release False
task have True
task release False
task have again
task have again
----
task got 0
task release 0
task cancel 1
task got 2
task release 2
False
----
task got 0
task cancel 1
task release 0
task got 2
task cancel 2
False

Wyświetl plik

@ -0,0 +1,55 @@
# Test that locks work when cancelling multiple waiters on the lock
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task(i, lock, lock_flag):
print("task", i, "start")
try:
await lock.acquire()
except asyncio.CancelledError:
print("task", i, "cancel")
return
print("task", i, "lock_flag", lock_flag[0])
lock_flag[0] = True
await asyncio.sleep(0)
lock.release()
lock_flag[0] = False
print("task", i, "done")
async def main():
# Create a lock and acquire it so the tasks below must wait
lock = asyncio.Lock()
await lock.acquire()
lock_flag = [True]
# Create 4 tasks and let them all run
t0 = asyncio.create_task(task(0, lock, lock_flag))
t1 = asyncio.create_task(task(1, lock, lock_flag))
t2 = asyncio.create_task(task(2, lock, lock_flag))
t3 = asyncio.create_task(task(3, lock, lock_flag))
await asyncio.sleep(0)
# Cancel 2 of the tasks (which are waiting on the lock) and release the lock
t1.cancel()
t2.cancel()
lock.release()
lock_flag[0] = False
# Let the tasks run to completion
for _ in range(4):
await asyncio.sleep(0)
# The locke should be unlocked
print(lock.locked())
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,11 @@
task 0 start
task 1 start
task 2 start
task 3 start
task 1 cancel
task 2 cancel
task 0 lock_flag False
task 0 done
task 3 lock_flag False
task 3 done
False

Wyświetl plik

@ -0,0 +1,62 @@
# Test asyncio.wait_for
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def task(id, t):
print("task start", id)
await asyncio.sleep(t)
print("task end", id)
return id * 2
async def task_catch():
print("task_catch start")
try:
await asyncio.sleep(0.2)
except asyncio.CancelledError:
print("ignore cancel")
print("task_catch done")
async def task_raise():
print("task start")
raise ValueError
async def main():
# When task finished before the timeout
print(await asyncio.wait_for(task(1, 0.01), 10))
# When timeout passes and task is cancelled
try:
print(await asyncio.wait_for(task(2, 10), 0.01))
except asyncio.TimeoutError:
print("timeout")
# When timeout passes and task is cancelled, but task ignores the cancellation request
try:
print(await asyncio.wait_for(task_catch(), 0.1))
except asyncio.TimeoutError:
print("TimeoutError")
# When task raises an exception
try:
print(await asyncio.wait_for(task_raise(), 1))
except ValueError:
print("ValueError")
# Timeout of None means wait forever
print(await asyncio.wait_for(task(3, 0.1), None))
print("finish")
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,15 @@
task start 1
task end 1
2
task start 2
timeout
task_catch start
ignore cancel
task_catch done
TimeoutError
task start
ValueError
task start 3
task end 3
6
finish

Wyświetl plik

@ -0,0 +1,77 @@
# Test waiting on a task
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
try:
import utime
ticks = utime.ticks_ms
ticks_diff = utime.ticks_diff
except:
import time
ticks = lambda: int(time.time() * 1000)
ticks_diff = lambda t1, t0: t1 - t0
async def task(t):
print("task", t)
async def delay_print(t, s):
await asyncio.sleep(t)
print(s)
async def task_raise():
print("task_raise")
raise ValueError
async def main():
print("start")
# Wait on a task
t = asyncio.create_task(task(1))
await t
# Wait on a task that's already done
t = asyncio.create_task(task(2))
await asyncio.sleep(0.001)
await t
# Wait again on same task
await t
print("----")
# Create 2 tasks
ts1 = asyncio.create_task(delay_print(0.04, "hello"))
ts2 = asyncio.create_task(delay_print(0.08, "world"))
# Time how long the tasks take to finish, they should execute in parallel
print("start")
t0 = ticks()
await ts1
t1 = ticks()
await ts2
t2 = ticks()
print("took {} {}".format(round(ticks_diff(t1, t0), -1), round(ticks_diff(t2, t1), -1)))
# Wait on a task that raises an exception
t = asyncio.create_task(task_raise())
try:
await t
except ValueError:
print("ValueError")
asyncio.run(main())

Wyświetl plik

@ -0,0 +1,10 @@
start
task 1
task 2
----
start
hello
world
took 40 40
task_raise
ValueError