Skip to content

Commit 31d7c91

Browse files
authored
Merge pull request #7059 from jepler/asyncio-tests-dogfood
asyncio: we should dogfood our own asyncio implementation during automated tests
2 parents 08e1cdb + f1b7bcb commit 31d7c91

12 files changed

+96
-30
lines changed

extmod/moduasyncio.c

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,32 +70,20 @@ STATIC mp_obj_t task_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf);
7070
/******************************************************************************/
7171
// Ticks for task ordering in pairing heap
7272

73-
#if !CIRCUITPY || (defined(__unix__) || defined(__APPLE__))
74-
STATIC mp_obj_t ticks(void) {
75-
return MP_OBJ_NEW_SMALL_INT(mp_hal_ticks_ms() & (MICROPY_PY_UTIME_TICKS_PERIOD - 1));
76-
}
77-
78-
STATIC mp_int_t ticks_diff(mp_obj_t t1_in, mp_obj_t t0_in) {
79-
mp_uint_t t0 = MP_OBJ_SMALL_INT_VALUE(t0_in);
80-
mp_uint_t t1 = MP_OBJ_SMALL_INT_VALUE(t1_in);
81-
mp_int_t diff = ((t1 - t0 + MICROPY_PY_UTIME_TICKS_PERIOD / 2) & (MICROPY_PY_UTIME_TICKS_PERIOD - 1))
82-
- MICROPY_PY_UTIME_TICKS_PERIOD / 2;
83-
return diff;
84-
}
85-
#else
8673
#define _TICKS_PERIOD (1lu << 29)
8774
#define _TICKS_MAX (_TICKS_PERIOD - 1)
8875
#define _TICKS_HALFPERIOD (_TICKS_PERIOD >> 1)
8976

90-
#define ticks() supervisor_ticks_ms()
77+
STATIC mp_obj_t ticks(void) {
78+
return MP_OBJ_NEW_SMALL_INT(mp_hal_ticks_ms() & _TICKS_MAX);
79+
}
9180

9281
STATIC mp_int_t ticks_diff(mp_obj_t t1_in, mp_obj_t t0_in) {
9382
mp_uint_t t0 = MP_OBJ_SMALL_INT_VALUE(t0_in);
9483
mp_uint_t t1 = MP_OBJ_SMALL_INT_VALUE(t1_in);
9584
mp_int_t diff = ((t1 - t0 + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD;
9685
return diff;
9786
}
98-
#endif
9987

10088
STATIC int task_lt(mp_pairheap_t *n1, mp_pairheap_t *n2) {
10189
mp_obj_task_t *t1 = (mp_obj_task_t *)n1;
@@ -302,8 +290,13 @@ STATIC mp_obj_t task_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
302290
STATIC mp_obj_t task_iternext(mp_obj_t self_in) {
303291
mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in);
304292
if (TASK_IS_DONE(self)) {
305-
// Task finished, raise return value to caller so it can continue.
306-
nlr_raise(self->data);
293+
if (self->data == mp_const_none) {
294+
// Task finished but has already been sent to the loop's exception handler.
295+
mp_raise_StopIteration(MP_OBJ_NULL);
296+
} else {
297+
// Task finished, raise return value to caller so it can continue.
298+
nlr_raise(self->data);
299+
}
307300
} else {
308301
// Put calling task on waiting queue.
309302
mp_obj_t cur_task = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));

extmod/uasyncio/task.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,12 @@ def __await__(self):
141141

142142
def __next__(self):
143143
if not self.state:
144-
# Task finished, raise return value to caller so it can continue.
145-
raise self.data
144+
if self.data is None:
145+
# Task finished but has already been sent to the loop's exception handler.
146+
raise StopIteration
147+
else:
148+
# Task finished, raise return value to caller so it can continue.
149+
raise self.data
146150
else:
147151
# Put calling task on waiting queue.
148152
self.state.push_head(core.cur_task)

ports/unix/Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,10 @@ include $(TOP)/py/mkrules.mk
296296

297297
.PHONY: test test_full
298298

299+
TEST_EXTRA ?=
299300
test: $(PROG) $(TOP)/tests/run-tests.py
300301
$(eval DIRNAME=ports/$(notdir $(CURDIR)))
301-
cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests.py
302+
cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests.py $(TEST_EXTRA)
302303

303304
test_full: $(PROG) $(TOP)/tests/run-tests.py
304305
$(eval DIRNAME=ports/$(notdir $(CURDIR)))
@@ -309,6 +310,10 @@ test_full: $(PROG) $(TOP)/tests/run-tests.py
309310
cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native -d basics float micropython
310311
cat $(TOP)/tests/basics/0prelim.py | ./$(PROG) | grep -q 'abc'
311312

313+
.PHONY: print-failures clean-failures
314+
print-failures clean-failures:
315+
../../tests/run-tests.py --$@
316+
312317
test_gcov: test_full
313318
gcov -o $(BUILD)/py $(TOP)/py/*.c
314319
gcov -o $(BUILD)/extmod $(TOP)/extmod/*.c
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# In MicroPython, a non-awaited task with a pending exception will raise to
2+
# the loop's exception handler the second time it is scheduled. This is
3+
# because without reference counting we have no way to know when the task is
4+
# truly "non awaited" -- i.e. we only know that it wasn't awaited in the time
5+
# it took to be re-scheduled.
6+
7+
# If the task _is_ subsequently awaited, then the await should succeed without
8+
# raising.
9+
10+
try:
11+
import uasyncio as asyncio
12+
except ImportError:
13+
try:
14+
import asyncio
15+
except ImportError:
16+
print("SKIP")
17+
raise SystemExit
18+
19+
20+
def custom_handler(loop, context):
21+
print("exception handler", type(context["exception"]).__name__)
22+
23+
24+
async def main():
25+
loop = asyncio.get_event_loop()
26+
loop.set_exception_handler(custom_handler)
27+
28+
async def task():
29+
print("raise")
30+
raise OSError
31+
32+
print("create")
33+
t = asyncio.create_task(task())
34+
print("sleep 1")
35+
await asyncio.sleep(0)
36+
print("sleep 2")
37+
await asyncio.sleep(0)
38+
print("await")
39+
await t # should not raise.
40+
41+
42+
asyncio.run(main())
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
create
2+
sleep 1
3+
raise
4+
sleep 2
5+
exception handler OSError
6+
await

tests/run-tests.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,17 @@ def send_get(what):
183183

184184
# run the actual test
185185
try:
186-
output_mupy = subprocess.check_output(cmdlist, stderr=subprocess.STDOUT)
186+
result = subprocess.run(
187+
cmdlist,
188+
stderr=subprocess.STDOUT,
189+
stdout=subprocess.PIPE,
190+
check=True,
191+
timeout=10,
192+
)
193+
output_mupy = result.stdout
194+
except subprocess.TimeoutExpired as er:
195+
had_crash = True
196+
output_mupy = (er.output or b"") + b"TIMEOUT"
187197
except subprocess.CalledProcessError as er:
188198
had_crash = True
189199
output_mupy = er.output + b"CRASH"
@@ -869,9 +879,15 @@ def main():
869879
tests = args.files
870880

871881
if not args.keep_path:
872-
# clear search path to make sure tests use only builtin modules and those in extmod
873-
os.environ["MICROPYPATH"] = os.pathsep + ".frozen" + os.pathsep + base_path("../extmod")
874-
882+
# clear search path to make sure tests use only builtin modules and those that can be frozen
883+
os.environ["MICROPYPATH"] = os.pathsep.join(
884+
[
885+
"",
886+
".frozen",
887+
base_path("../frozen/Adafruit_CircuitPython_asyncio"),
888+
base_path("../frozen/Adafruit_CircuitPython_Ticks"),
889+
]
890+
)
875891
try:
876892
os.makedirs(args.result_dir, exist_ok=True)
877893
res = run_tests(pyb, tests, args, args.result_dir, args.jobs)

0 commit comments

Comments
 (0)