From 523f5128e01de5296e8abdb0049b61f91e74a210 Mon Sep 17 00:00:00 2001 From: James Ward Date: Sat, 11 Nov 2023 20:13:17 -0500 Subject: [PATCH 1/6] test: add test runner and gitignore --- tests/.gitignore | 4 ++ tests/run_tests.py | 140 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 tests/.gitignore create mode 100755 tests/run_tests.py diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..eb826a6 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +*.out diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100755 index 0000000..60305df --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,140 @@ +#! /usr/bin/env python3 + +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# + +import sys +import os + +try: + from typing import List, Tuple +except ImportError: + pass + +AVAILABLE_SUITES = ["asyncio"] + + +LICENSE_PREFIX = """# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +""" + + +def get_interpreter(): + interpreter = os.getenv("MICROPY_MICROPYTHON") + + if interpreter: + return interpreter + + if sys.platform == "win32": + return "micropython.exe" + + return "micropython" + + +def get_testcases(suite: str) -> List[str]: + if sys.platform == "win32": + # dir /b prints only contained filenames, one on a line + # http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/dir.mspx + result = os.system("dir /b %s/*.py >tests.lst" % suite) + else: + result = os.system("ls %s/*.py | xargs -n1 basename >tests.lst" % suite) + + assert result == 0 + + with open("tests.lst") as test_list_file: + testcases = test_list_file.readlines() + testcases = [l[:-1] for l in testcases] + + os.system("rm tests.lst") + assert testcases, "No tests found in dir '%s', which is implausible" % suite + + return testcases + + +def run_testcase(suite: str, testcase: str): + qtest = "%s/%s" % (suite, testcase) + + try: + with open("%s.exp" % qtest) as expected_output_file: + expected_output = expected_output_file.read() + except OSError as exc: + raise RuntimeError("SKIP") from exc + + with open("{0}.out".format(qtest), "w") as actual_output_file: + actual_output_file.write(LICENSE_PREFIX) + + result = os.system( + "{0} {1} 2>> {1}.out >> {1}.out".format(get_interpreter(), qtest) + ) + + with open("%s.out" % qtest) as actual_output_file: + actual_output = actual_output_file.read() + + if result != 0: + actual_output += "\n\nCRASH\n" + + if actual_output == LICENSE_PREFIX + "SKIP\n": + print("skip %s" % qtest) + raise RuntimeError("SKIP") + + if actual_output != expected_output: + print("FAIL %s" % qtest) + os.system("diff -u {0}.exp {0}.out".format(qtest)) + return False + + print("pass %s" % qtest) + return True + + +def run_suite(suite: str) -> Tuple[int, int, int]: + test_count = 0 + passed_count = 0 + skip_count = 0 + + testcases = get_testcases(suite) + + for testcase in testcases: + try: + if run_testcase(suite, testcase): + passed_count += 1 + + test_count += 1 + except RuntimeError as exc: + if str(exc) == "SKIP": + skip_count += 1 + + return test_count, passed_count, skip_count + + +def main(): + test_count = 0 + passed_count = 0 + skip_count = 0 + + for suite in AVAILABLE_SUITES: + suite_test_count, suite_passed_count, suite_skip_count = run_suite(suite) + + test_count += suite_test_count + passed_count += suite_passed_count + skip_count += suite_skip_count + + print("-" * 20) + print("%s tests performed" % test_count) + print("%s tests passed" % passed_count) + if test_count != passed_count: + print("%s tests failed" % (test_count - passed_count)) + if skip_count: + print("%s tests skipped" % skip_count) + + if test_count - passed_count > 0: + sys.exit(1) + + +if __name__ == "__main__": + main() From 5d980f2304a68f1be3bf932ccef89295ad022f9f Mon Sep 17 00:00:00 2001 From: James Ward Date: Sat, 11 Nov 2023 21:41:23 -0500 Subject: [PATCH 2/6] chore: add ci to run tests --- .github/workflows/run-tests.yml | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..cda6158 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) +# +# SPDX-License-Identifier: MIT + +name: Run tests + +on: [pull_request, push] + +jobs: + run: + runs-on: ubuntu-22.04 + steps: + - name: Set up repository + uses: actions/checkout@v3 + with: + submodules: false + fetch-depth: 1 + - name: Set up circuitpython repository + uses: actions/checkout@v3 + with: + repository: adafruit/circuitpython + path: ./circuitpython/ + submodules: false + fetch-depth: 1 + fetch-tags: true + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: CircuitPython dependencies + id: cp-deps + run: python tools/ci_fetch_deps.py tests + shell: bash + working-directory: ./circuitpython/ + - name: Install python dependencies + run: pip install -r requirements-dev.txt + shell: bash + working-directory: ./circuitpython/ + - name: Build unix port + run: make -C ports/unix VARIANT=coverage -j2 + working-directory: ./circuitpython/ + - name: Run tests + run: ./run_tests.py + working-directory: tests + env: + MICROPY_CPYTHON3: python3.8 + MICROPY_MICROPYTHON: ../circuitpython/ports/unix/build-coverage/micropython + MICROPYPATH: ../:../circuitpython/frozen/Adafruit_CircuitPython_Ticks From c457e546764422e8def3c98f223c1a3d60672fc5 Mon Sep 17 00:00:00 2001 From: James Ward Date: Sat, 11 Nov 2023 16:39:55 -0500 Subject: [PATCH 3/6] test: add tests from circuitpython proper --- tests/asyncio/asyncio_await_return.py | 28 ++++ tests/asyncio/asyncio_await_return.py.exp | 5 + tests/asyncio/asyncio_basic.py | 50 +++++++ tests/asyncio/asyncio_basic.py.exp | 9 ++ tests/asyncio/asyncio_basic2.py | 26 ++++ tests/asyncio/asyncio_basic2.py.exp | 7 + tests/asyncio/asyncio_cancel_fair.py | 38 +++++ tests/asyncio/asyncio_cancel_fair.py.exp | 27 ++++ tests/asyncio/asyncio_cancel_fair2.py | 38 +++++ tests/asyncio/asyncio_cancel_fair2.py.exp | 11 ++ tests/asyncio/asyncio_cancel_self.py | 32 +++++ tests/asyncio/asyncio_cancel_self.py.exp | 5 + tests/asyncio/asyncio_cancel_task.py | 91 ++++++++++++ tests/asyncio/asyncio_cancel_task.py.exp | 34 +++++ .../asyncio_cancel_wait_on_finished.py | 42 ++++++ .../asyncio_cancel_wait_on_finished.py.exp | 10 ++ tests/asyncio/asyncio_current_task.py | 26 ++++ tests/asyncio/asyncio_current_task.py.exp | 4 + tests/asyncio/asyncio_event.py | 99 +++++++++++++ tests/asyncio/asyncio_event.py.exp | 36 +++++ tests/asyncio/asyncio_event_fair.py | 41 ++++++ tests/asyncio/asyncio_event_fair.py.exp | 19 +++ tests/asyncio/asyncio_exception.py | 63 +++++++++ tests/asyncio/asyncio_exception.py.exp | 10 ++ tests/asyncio/asyncio_fair.py | 35 +++++ tests/asyncio/asyncio_fair.py.exp | 16 +++ tests/asyncio/asyncio_gather.py | 118 ++++++++++++++++ tests/asyncio/asyncio_gather.py.exp | 59 ++++++++ tests/asyncio/asyncio_gather_notimpl.py | 66 +++++++++ tests/asyncio/asyncio_gather_notimpl.py.exp | 22 +++ tests/asyncio/asyncio_heaplock.py | 84 +++++++++++ tests/asyncio/asyncio_heaplock.py.exp | 15 ++ tests/asyncio/asyncio_lock.py | 98 +++++++++++++ tests/asyncio/asyncio_lock.py.exp | 44 ++++++ tests/asyncio/asyncio_lock_cancel.py | 56 ++++++++ tests/asyncio/asyncio_lock_cancel.py.exp | 14 ++ tests/asyncio/asyncio_loop_stop.py | 46 ++++++ tests/asyncio/asyncio_loop_stop.py.exp | 10 ++ tests/asyncio/asyncio_new_event_loop.py | 37 +++++ tests/asyncio/asyncio_new_event_loop.py.exp | 9 ++ .../asyncio/asyncio_set_exception_handler.py | 57 ++++++++ .../asyncio_set_exception_handler.py.exp | 12 ++ tests/asyncio/asyncio_task_done.py | 67 +++++++++ tests/asyncio/asyncio_task_done.py.exp | 27 ++++ tests/asyncio/asyncio_wait_for.py | 133 ++++++++++++++++++ tests/asyncio/asyncio_wait_for.py.exp | 65 +++++++++ tests/asyncio/asyncio_wait_for_fwd.py | 61 ++++++++ tests/asyncio/asyncio_wait_for_fwd.py.exp | 29 ++++ tests/asyncio/asyncio_wait_task.py | 83 +++++++++++ tests/asyncio/asyncio_wait_task.py.exp | 13 ++ 50 files changed, 2027 insertions(+) create mode 100644 tests/asyncio/asyncio_await_return.py create mode 100644 tests/asyncio/asyncio_await_return.py.exp create mode 100644 tests/asyncio/asyncio_basic.py create mode 100644 tests/asyncio/asyncio_basic.py.exp create mode 100644 tests/asyncio/asyncio_basic2.py create mode 100644 tests/asyncio/asyncio_basic2.py.exp create mode 100644 tests/asyncio/asyncio_cancel_fair.py create mode 100644 tests/asyncio/asyncio_cancel_fair.py.exp create mode 100644 tests/asyncio/asyncio_cancel_fair2.py create mode 100644 tests/asyncio/asyncio_cancel_fair2.py.exp create mode 100644 tests/asyncio/asyncio_cancel_self.py create mode 100644 tests/asyncio/asyncio_cancel_self.py.exp create mode 100644 tests/asyncio/asyncio_cancel_task.py create mode 100644 tests/asyncio/asyncio_cancel_task.py.exp create mode 100644 tests/asyncio/asyncio_cancel_wait_on_finished.py create mode 100644 tests/asyncio/asyncio_cancel_wait_on_finished.py.exp create mode 100644 tests/asyncio/asyncio_current_task.py create mode 100644 tests/asyncio/asyncio_current_task.py.exp create mode 100644 tests/asyncio/asyncio_event.py create mode 100644 tests/asyncio/asyncio_event.py.exp create mode 100644 tests/asyncio/asyncio_event_fair.py create mode 100644 tests/asyncio/asyncio_event_fair.py.exp create mode 100644 tests/asyncio/asyncio_exception.py create mode 100644 tests/asyncio/asyncio_exception.py.exp create mode 100644 tests/asyncio/asyncio_fair.py create mode 100644 tests/asyncio/asyncio_fair.py.exp create mode 100644 tests/asyncio/asyncio_gather.py create mode 100644 tests/asyncio/asyncio_gather.py.exp create mode 100644 tests/asyncio/asyncio_gather_notimpl.py create mode 100644 tests/asyncio/asyncio_gather_notimpl.py.exp create mode 100644 tests/asyncio/asyncio_heaplock.py create mode 100644 tests/asyncio/asyncio_heaplock.py.exp create mode 100644 tests/asyncio/asyncio_lock.py create mode 100644 tests/asyncio/asyncio_lock.py.exp create mode 100644 tests/asyncio/asyncio_lock_cancel.py create mode 100644 tests/asyncio/asyncio_lock_cancel.py.exp create mode 100644 tests/asyncio/asyncio_loop_stop.py create mode 100644 tests/asyncio/asyncio_loop_stop.py.exp create mode 100644 tests/asyncio/asyncio_new_event_loop.py create mode 100644 tests/asyncio/asyncio_new_event_loop.py.exp create mode 100644 tests/asyncio/asyncio_set_exception_handler.py create mode 100644 tests/asyncio/asyncio_set_exception_handler.py.exp create mode 100644 tests/asyncio/asyncio_task_done.py create mode 100644 tests/asyncio/asyncio_task_done.py.exp create mode 100644 tests/asyncio/asyncio_wait_for.py create mode 100644 tests/asyncio/asyncio_wait_for.py.exp create mode 100644 tests/asyncio/asyncio_wait_for_fwd.py create mode 100644 tests/asyncio/asyncio_wait_for_fwd.py.exp create mode 100644 tests/asyncio/asyncio_wait_task.py create mode 100644 tests/asyncio/asyncio_wait_task.py.exp diff --git a/tests/asyncio/asyncio_await_return.py b/tests/asyncio/asyncio_await_return.py new file mode 100644 index 0000000..5885de6 --- /dev/null +++ b/tests/asyncio/asyncio_await_return.py @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test that tasks return their value correctly to the caller + +import asyncio + + +async def example(): + return 42 + + +async def main(): + # Call function directly via an await + print(await example()) + + # Create a task and await on it + task = asyncio.create_task(example()) + print(await task) + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_await_return.py.exp b/tests/asyncio/asyncio_await_return.py.exp new file mode 100644 index 0000000..4c963ba --- /dev/null +++ b/tests/asyncio/asyncio_await_return.py.exp @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +42 +42 diff --git a/tests/asyncio/asyncio_basic.py b/tests/asyncio/asyncio_basic.py new file mode 100644 index 0000000..ebd7882 --- /dev/null +++ b/tests/asyncio/asyncio_basic.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file + +import asyncio +import time + + +if hasattr(time, "ticks_ms"): + ticks = time.ticks_ms + ticks_diff = time.ticks_diff +else: + 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.2, "short") + t1 = ticks() + await delay_print(0.4, "long") + t2 = ticks() + await delay_print(-1, "negative") + t3 = ticks() + + print( + "took {} {} {}".format( + round(ticks_diff(t1, t0), -2), + round(ticks_diff(t2, t1), -2), + round(ticks_diff(t3, t2), -2), + ) + ) + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_basic.py.exp b/tests/asyncio/asyncio_basic.py.exp new file mode 100644 index 0000000..1729c28 --- /dev/null +++ b/tests/asyncio/asyncio_basic.py.exp @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +start +after sleep +short +long +negative +took 200 400 0 diff --git a/tests/asyncio/asyncio_basic2.py b/tests/asyncio/asyncio_basic2.py new file mode 100644 index 0000000..b0e2abe --- /dev/null +++ b/tests/asyncio/asyncio_basic2.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file + +import asyncio + + +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())) diff --git a/tests/asyncio/asyncio_basic2.py.exp b/tests/asyncio/asyncio_basic2.py.exp new file mode 100644 index 0000000..81abe7a --- /dev/null +++ b/tests/asyncio/asyncio_basic2.py.exp @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +main start +forever start +main done +42 diff --git a/tests/asyncio/asyncio_cancel_fair.py b/tests/asyncio/asyncio_cancel_fair.py new file mode 100644 index 0000000..f1b66fd --- /dev/null +++ b/tests/asyncio/asyncio_cancel_fair.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test fairness of cancelling a task +# That tasks which continuously cancel each other don't take over the scheduler +import asyncio + + +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()) diff --git a/tests/asyncio/asyncio_cancel_fair.py.exp b/tests/asyncio/asyncio_cancel_fair.py.exp new file mode 100644 index 0000000..2fbde21 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_fair.py.exp @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +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 diff --git a/tests/asyncio/asyncio_cancel_fair2.py b/tests/asyncio/asyncio_cancel_fair2.py new file mode 100644 index 0000000..6f61826 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_fair2.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test fairness of cancelling a task +# That tasks which keeps being cancelled by multiple other tasks gets a chance to run +import asyncio + + +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()) diff --git a/tests/asyncio/asyncio_cancel_fair2.py.exp b/tests/asyncio/asyncio_cancel_fair2.py.exp new file mode 100644 index 0000000..1315b21 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_fair2.py.exp @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +sleep a +sleep b 0 +sleep b 1 +sleep b 2 +cancelled a +done b 0 +done b 1 +done b 2 diff --git a/tests/asyncio/asyncio_cancel_self.py b/tests/asyncio/asyncio_cancel_self.py new file mode 100644 index 0000000..25491d7 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_self.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test a task cancelling itself (currently unsupported) +import asyncio + + +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) diff --git a/tests/asyncio/asyncio_cancel_self.py.exp b/tests/asyncio/asyncio_cancel_self.py.exp new file mode 100644 index 0000000..36d0023 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_self.py.exp @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +task start +can't cancel self diff --git a/tests/asyncio/asyncio_cancel_task.py b/tests/asyncio/asyncio_cancel_task.py new file mode 100644 index 0000000..7962999 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_task.py @@ -0,0 +1,91 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test cancelling a task + +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()) diff --git a/tests/asyncio/asyncio_cancel_task.py.exp b/tests/asyncio/asyncio_cancel_task.py.exp new file mode 100644 index 0000000..6a4c206 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_task.py.exp @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +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 diff --git a/tests/asyncio/asyncio_cancel_wait_on_finished.py b/tests/asyncio/asyncio_cancel_wait_on_finished.py new file mode 100644 index 0000000..5ebc934 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_wait_on_finished.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test cancelling a task that is waiting on a task that just finishes. +import asyncio + + +async def sleep_task(): + print("sleep_task sleep") + await asyncio.sleep(0) + print("sleep_task wake") + + +async def wait_task(t): + print("wait_task wait") + await t + print("wait_task wake") + + +async def main(): + waiting_task = asyncio.create_task(wait_task(asyncio.create_task(sleep_task()))) + + print("main sleep") + await asyncio.sleep(0) + print("main sleep") + await asyncio.sleep(0) + + waiting_task.cancel() + print("main wait") + try: + await waiting_task + except asyncio.CancelledError as er: + print(repr(er)) + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_cancel_wait_on_finished.py.exp b/tests/asyncio/asyncio_cancel_wait_on_finished.py.exp new file mode 100644 index 0000000..7a91c76 --- /dev/null +++ b/tests/asyncio/asyncio_cancel_wait_on_finished.py.exp @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +main sleep +sleep_task sleep +wait_task wait +main sleep +sleep_task wake +main wait +CancelledError() diff --git a/tests/asyncio/asyncio_current_task.py b/tests/asyncio/asyncio_current_task.py new file mode 100644 index 0000000..c21dc2a --- /dev/null +++ b/tests/asyncio/asyncio_current_task.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test current_task() function +import asyncio + + +async def task(result): + result[0] = asyncio.current_task() + + +async def main(): + result = [None] + t = asyncio.create_task(task(result)) + await asyncio.sleep(0) + await asyncio.sleep(0) + print(t is result[0]) + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_current_task.py.exp b/tests/asyncio/asyncio_current_task.py.exp new file mode 100644 index 0000000..69f0ff0 --- /dev/null +++ b/tests/asyncio/asyncio_current_task.py.exp @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +True diff --git a/tests/asyncio/asyncio_event.py b/tests/asyncio/asyncio_event.py new file mode 100644 index 0000000..f2247c7 --- /dev/null +++ b/tests/asyncio/asyncio_event.py @@ -0,0 +1,99 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test Event class +import asyncio + + +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()) diff --git a/tests/asyncio/asyncio_event.py.exp b/tests/asyncio/asyncio_event.py.exp new file mode 100644 index 0000000..348b13c --- /dev/null +++ b/tests/asyncio/asyncio_event.py.exp @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +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 diff --git a/tests/asyncio/asyncio_event_fair.py b/tests/asyncio/asyncio_event_fair.py new file mode 100644 index 0000000..00b2986 --- /dev/null +++ b/tests/asyncio/asyncio_event_fair.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test fairness of Event.set() +# That tasks which continuously wait on events don't take over the scheduler +import asyncio + + +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()) diff --git a/tests/asyncio/asyncio_event_fair.py.exp b/tests/asyncio/asyncio_event_fair.py.exp new file mode 100644 index 0000000..b7d6d4b --- /dev/null +++ b/tests/asyncio/asyncio_event_fair.py.exp @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +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 diff --git a/tests/asyncio/asyncio_exception.py b/tests/asyncio/asyncio_exception.py new file mode 100644 index 0000000..35b607e --- /dev/null +++ b/tests/asyncio/asyncio_exception.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test general exception handling +import asyncio + + +# main task raising an exception +async def main(): + print("main start") + raise ValueError(1) + + +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]) diff --git a/tests/asyncio/asyncio_exception.py.exp b/tests/asyncio/asyncio_exception.py.exp new file mode 100644 index 0000000..98304ca --- /dev/null +++ b/tests/asyncio/asyncio_exception.py.exp @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +main start +ValueError 1 +main start +task start +ValueError 2 +main start +ValueError 3 diff --git a/tests/asyncio/asyncio_fair.py b/tests/asyncio/asyncio_fair.py new file mode 100644 index 0000000..7935555 --- /dev/null +++ b/tests/asyncio/asyncio_fair.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test fairness of scheduler +import asyncio + + +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.18)) + t4 = asyncio.create_task(task(4, -100)) + await asyncio.sleep(0.5) + t1.cancel() + t2.cancel() + t3.cancel() + t4.cancel() + print("finish") + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_fair.py.exp b/tests/asyncio/asyncio_fair.py.exp new file mode 100644 index 0000000..95af54f --- /dev/null +++ b/tests/asyncio/asyncio_fair.py.exp @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +task start 1 +task start 2 +task work 2 +task start 3 +task work 3 +task start 4 +task work 2 +task work 3 +task work 2 +task work 2 +task work 3 +task work 2 +finish diff --git a/tests/asyncio/asyncio_gather.py b/tests/asyncio/asyncio_gather.py new file mode 100644 index 0000000..3a1df60 --- /dev/null +++ b/tests/asyncio/asyncio_gather.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# test asyncio.gather() function +import asyncio + + +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, t=0.1): + print("start", id) + await asyncio.sleep(t) + print("end", id) + return id + + +async def task_loop(id): + print("task_loop start", id) + while True: + await asyncio.sleep(0.1) + print("task_loop loop", id) + + +async def task_raise(id, t=0.1): + print("task_raise start", id) + await asyncio.sleep(t) + print("task_raise raise", id) + raise ValueError(id) + + +async def gather_task(t0, t1): + print("gather_task") + await asyncio.gather(t0, t1) + print("gather_task2") + + +async def main(): + # Simple gather with return values + print(await asyncio.gather(factorial("A", 2), factorial("B", 3), factorial("C", 4))) + + print("====") + + # Gather with no awaitables + print(await asyncio.gather()) + + print("====") + + # Test return_exceptions, where one task is cancelled and the other finishes normally + tasks = [asyncio.create_task(task(1)), asyncio.create_task(task(2))] + tasks[0].cancel() + print(await asyncio.gather(*tasks, return_exceptions=True)) + + print("====") + + # Test return_exceptions, where one task raises an exception and the other finishes normally. + tasks = [asyncio.create_task(task(1)), asyncio.create_task(task_raise(2))] + print(await asyncio.gather(*tasks, return_exceptions=True)) + + print("====") + + # Test case where one task raises an exception and other task keeps running. + tasks = [asyncio.create_task(task_loop(1)), asyncio.create_task(task_raise(2))] + try: + await asyncio.gather(*tasks) + except ValueError as er: + print(repr(er)) + print(tasks[0].done(), tasks[1].done()) + for t in tasks: + t.cancel() + await asyncio.sleep(0.2) + + print("====") + + # Test case where both tasks raise an exception. + # Use t=0 so they raise one after the other, between the gather starting and finishing. + tasks = [ + asyncio.create_task(task_raise(1, t=0)), + asyncio.create_task(task_raise(2, t=0)), + ] + try: + await asyncio.gather(*tasks) + except ValueError as er: + print(repr(er)) + print(tasks[0].done(), tasks[1].done()) + + print("====") + + # Cancel a multi gather. + t = asyncio.create_task(gather_task(task(1), task(2))) + await asyncio.sleep(0.05) + t.cancel() + await asyncio.sleep(0.2) + + # Test edge cases where the gather is cancelled just as tasks are created and ending. + for i in range(1, 4): + print("====") + t = asyncio.create_task(gather_task(task(1, t=0), task(2, t=0))) + for _ in range(i): + await asyncio.sleep(0) + t.cancel() + await asyncio.sleep(0.2) + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_gather.py.exp b/tests/asyncio/asyncio_gather.py.exp new file mode 100644 index 0000000..c518570 --- /dev/null +++ b/tests/asyncio/asyncio_gather.py.exp @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +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] +==== +[] +==== +start 2 +end 2 +[CancelledError(), 2] +==== +start 1 +task_raise start 2 +end 1 +task_raise raise 2 +[1, ValueError(2,)] +==== +task_loop start 1 +task_raise start 2 +task_loop loop 1 +task_raise raise 2 +ValueError(2,) +False True +==== +task_raise start 1 +task_raise start 2 +task_raise raise 1 +task_raise raise 2 +ValueError(1,) +True True +==== +gather_task +start 1 +start 2 +==== +gather_task +start 1 +start 2 +==== +gather_task +start 1 +start 2 +end 1 +end 2 +==== +gather_task +start 1 +start 2 +end 1 +end 2 diff --git a/tests/asyncio/asyncio_gather_notimpl.py b/tests/asyncio/asyncio_gather_notimpl.py new file mode 100644 index 0000000..f91cb87 --- /dev/null +++ b/tests/asyncio/asyncio_gather_notimpl.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test asyncio.gather() function, features that are not implemented. +import asyncio + + +def custom_handler(loop, context): + print(repr(context["exception"])) + + +async def task(id): + print("task start", id) + await asyncio.sleep(0.01) + print("task end", id) + return id + + +async def gather_task(t0, t1): + print("gather_task start") + await asyncio.gather(t0, t1) + print("gather_task end") + + +async def main(): + loop = asyncio.get_event_loop() + loop.set_exception_handler(custom_handler) + + # Test case where can't wait on a task being gathered. + print("=" * 10) + tasks = [asyncio.create_task(task(1)), asyncio.create_task(task(2))] + gt = asyncio.create_task(gather_task(tasks[0], tasks[1])) + await asyncio.sleep(0) # let the gather start + try: + await tasks[0] # can't await because this task is part of the gather + except RuntimeError as er: + print(repr(er)) + await gt + + # Test case where can't gather on a task being waited. + print("=" * 10) + tasks = [asyncio.create_task(task(1)), asyncio.create_task(task(2))] + asyncio.create_task(gather_task(tasks[0], tasks[1])) + await tasks[0] # wait on this task before the gather starts + await tasks[1] + + # Can't gather after a task has completed + print("=" * 10) + task_1 = asyncio.create_task(task(1)) + + try: + # Wait for task_1 to complete + await task_1 + + await asyncio.gather(task_1) + except RuntimeError as er: + print(repr(er)) + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_gather_notimpl.py.exp b/tests/asyncio/asyncio_gather_notimpl.py.exp new file mode 100644 index 0000000..0609519 --- /dev/null +++ b/tests/asyncio/asyncio_gather_notimpl.py.exp @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +========== +task start 1 +task start 2 +gather_task start +RuntimeError("can't wait",) +task end 1 +task end 2 +gather_task end +========== +task start 1 +task start 2 +gather_task start +RuntimeError("can't gather",) +task end 1 +task end 2 +========== +task start 1 +task end 1 +RuntimeError("can't gather",) diff --git a/tests/asyncio/asyncio_heaplock.py b/tests/asyncio/asyncio_heaplock.py new file mode 100644 index 0000000..9515932 --- /dev/null +++ b/tests/asyncio/asyncio_heaplock.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# test that the following do not use the heap: +# - basic scheduling of tasks +# - asyncio.sleep_ms +# - StreamWriter.write, stream is blocked and data to write is a bytes object +# - StreamWriter.write, when stream is not blocked + +import micropython + +# strict stackless builds can't call functions without allocating a frame on the heap +try: + # force bytecode (in case we're running with emit=native) and verify + # that bytecode-calling-bytecode doesn't allocate + @micropython.bytecode + def f(x): + x and f(x - 1) + + micropython.heap_lock() + f(1) + micropython.heap_unlock() +except RuntimeError: + # RuntimeError (max recursion depth) not MemoryError because effectively + # the recursion depth is at the limit while the heap is locked with + # stackless + print("SKIP") + raise SystemExit + +import asyncio + + +class TestStream: + def __init__(self, blocked): + self.blocked = blocked + + def write(self, data): + print("TestStream.write", data) + if self.blocked: + return None + return len(data) + + +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, 100)) + t2 = asyncio.create_task(task(2, 2, 250)) + + # test scheduling tasks, and calling sleep_ms + micropython.heap_lock() + print("start") + await asyncio.sleep_ms(5) + print("sleep") + await asyncio.sleep_ms(350) + print("finish") + micropython.heap_unlock() + + # test writing to a stream, when the underlying stream is blocked + s = asyncio.StreamWriter(TestStream(True), None) + micropython.heap_lock() + s.write(b"12") + micropython.heap_unlock() + + # test writing to a stream, when the underlying stream is not blocked + buf = bytearray(b"56") + s = asyncio.StreamWriter(TestStream(False), None) + micropython.heap_lock() + s.write(b"34") + s.write(buf) + micropython.heap_unlock() + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_heaplock.py.exp b/tests/asyncio/asyncio_heaplock.py.exp new file mode 100644 index 0000000..0551dde --- /dev/null +++ b/tests/asyncio/asyncio_heaplock.py.exp @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +start +1 0 +2 0 +sleep +1 1 +1 2 +2 1 +1 3 +finish +TestStream.write b'12' +TestStream.write b'34' +TestStream.write bytearray(b'56') diff --git a/tests/asyncio/asyncio_lock.py b/tests/asyncio/asyncio_lock.py new file mode 100644 index 0000000..b8b7758 --- /dev/null +++ b/tests/asyncio/asyncio_lock.py @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test Lock class +import asyncio + + +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()) diff --git a/tests/asyncio/asyncio_lock.py.exp b/tests/asyncio/asyncio_lock.py.exp new file mode 100644 index 0000000..2c4cbac --- /dev/null +++ b/tests/asyncio/asyncio_lock.py.exp @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +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 diff --git a/tests/asyncio/asyncio_lock_cancel.py b/tests/asyncio/asyncio_lock_cancel.py new file mode 100644 index 0000000..59739e4 --- /dev/null +++ b/tests/asyncio/asyncio_lock_cancel.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test that locks work when cancelling multiple waiters on the lock +import asyncio + + +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()) diff --git a/tests/asyncio/asyncio_lock_cancel.py.exp b/tests/asyncio/asyncio_lock_cancel.py.exp new file mode 100644 index 0000000..d5decca --- /dev/null +++ b/tests/asyncio/asyncio_lock_cancel.py.exp @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +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 diff --git a/tests/asyncio/asyncio_loop_stop.py b/tests/asyncio/asyncio_loop_stop.py new file mode 100644 index 0000000..1ce3ac6 --- /dev/null +++ b/tests/asyncio/asyncio_loop_stop.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test Loop.stop() to stop the event loop +import asyncio + + +async def task(): + print("task") + + +async def main(): + print("start") + + # Stop the loop after next yield + loop.stop() + + # Check that calling stop() again doesn't do/break anything + loop.stop() + + # This await should stop + print("sleep") + await asyncio.sleep(0) + + # Schedule stop, then create a new task, then yield + loop.stop() + asyncio.create_task(task()) + await asyncio.sleep(0) + + # Final stop + print("end") + loop.stop() + + +loop = asyncio.get_event_loop() +loop.create_task(main()) + +for i in range(3): + print("run", i) + loop.run_forever() diff --git a/tests/asyncio/asyncio_loop_stop.py.exp b/tests/asyncio/asyncio_loop_stop.py.exp new file mode 100644 index 0000000..2815fb6 --- /dev/null +++ b/tests/asyncio/asyncio_loop_stop.py.exp @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +run 0 +start +sleep +run 1 +run 2 +task +end diff --git a/tests/asyncio/asyncio_new_event_loop.py b/tests/asyncio/asyncio_new_event_loop.py new file mode 100644 index 0000000..e5566a6 --- /dev/null +++ b/tests/asyncio/asyncio_new_event_loop.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test Loop.new_event_loop() +import asyncio + + +async def task(): + for i in range(4): + print("task", i) + await asyncio.sleep(0) + await asyncio.sleep(0) + + +async def main(): + print("start") + loop.create_task(task()) + await asyncio.sleep(0) + print("stop") + loop.stop() + + +# Use default event loop to run some tasks +loop = asyncio.get_event_loop() +loop.create_task(main()) +loop.run_forever() + +# Create new event loop, old one should not keep running +loop = asyncio.new_event_loop() +loop.create_task(main()) +loop.run_forever() diff --git a/tests/asyncio/asyncio_new_event_loop.py.exp b/tests/asyncio/asyncio_new_event_loop.py.exp new file mode 100644 index 0000000..708c24c --- /dev/null +++ b/tests/asyncio/asyncio_new_event_loop.py.exp @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +start +task 0 +stop +start +task 0 +stop diff --git a/tests/asyncio/asyncio_set_exception_handler.py b/tests/asyncio/asyncio_set_exception_handler.py new file mode 100644 index 0000000..99ad3ab --- /dev/null +++ b/tests/asyncio/asyncio_set_exception_handler.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test that tasks return their value correctly to the caller +import asyncio + + +def custom_handler(loop, context): + print("custom_handler", repr(context["exception"])) + + +async def task(i): + # Raise with 2 args so exception prints the same in uPy and CPython + raise ValueError(i, i + 1) + + +async def main(): + loop = asyncio.get_event_loop() + + # Check default exception handler, should be None + print(loop.get_exception_handler()) + + # Set exception handler and test it was set + loop.set_exception_handler(custom_handler) + print(loop.get_exception_handler() == custom_handler) + + # Create a task that raises and uses the custom exception handler + asyncio.create_task(task(0)) + print("sleep") + for _ in range(2): + await asyncio.sleep(0) + + # Create 2 tasks to test order of printing exception + asyncio.create_task(task(1)) + asyncio.create_task(task(2)) + print("sleep") + for _ in range(2): + await asyncio.sleep(0) + + # Create a task, let it run, then await it (no exception should be printed) + t = asyncio.create_task(task(3)) + await asyncio.sleep(0) + try: + await t + except ValueError as er: + print(repr(er)) + + print("done") + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_set_exception_handler.py.exp b/tests/asyncio/asyncio_set_exception_handler.py.exp new file mode 100644 index 0000000..ee777e2 --- /dev/null +++ b/tests/asyncio/asyncio_set_exception_handler.py.exp @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +None +True +sleep +custom_handler ValueError(0, 1) +sleep +custom_handler ValueError(1, 2) +custom_handler ValueError(2, 3) +ValueError(3, 4) +done diff --git a/tests/asyncio/asyncio_task_done.py b/tests/asyncio/asyncio_task_done.py new file mode 100644 index 0000000..aa1b8c1 --- /dev/null +++ b/tests/asyncio/asyncio_task_done.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test the Task.done() method +import asyncio + + +async def task(t, exc=None): + print("task start") + if t >= 0: + await asyncio.sleep(t) + if exc: + raise exc + print("task done") + + +async def main(): + # Task that finishes immediately. + print("=" * 10) + t = asyncio.create_task(task(-1)) + print(t.done()) + await asyncio.sleep(0) + print(t.done()) + await t + print(t.done()) + + # Task that starts, runs and finishes. + print("=" * 10) + t = asyncio.create_task(task(0.01)) + print(t.done()) + await asyncio.sleep(0) + print(t.done()) + await t + print(t.done()) + + # Task that raises immediately. + print("=" * 10) + t = asyncio.create_task(task(-1, ValueError)) + print(t.done()) + await asyncio.sleep(0) + print(t.done()) + try: + await t + except ValueError as er: + print(repr(er)) + print(t.done()) + + # Task that raises after a delay. + print("=" * 10) + t = asyncio.create_task(task(0.01, ValueError)) + print(t.done()) + await asyncio.sleep(0) + print(t.done()) + try: + await t + except ValueError as er: + print(repr(er)) + print(t.done()) + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_task_done.py.exp b/tests/asyncio/asyncio_task_done.py.exp new file mode 100644 index 0000000..97b4cb5 --- /dev/null +++ b/tests/asyncio/asyncio_task_done.py.exp @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +========== +False +task start +task done +True +True +========== +False +task start +False +task done +True +========== +False +task start +True +ValueError() +True +========== +False +task start +False +ValueError() +True diff --git a/tests/asyncio/asyncio_wait_for.py b/tests/asyncio/asyncio_wait_for.py new file mode 100644 index 0000000..f224d66 --- /dev/null +++ b/tests/asyncio/asyncio_wait_for.py @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test asyncio.wait_for +import asyncio + + +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 task_cancel_other(t, other): + print("task_cancel_other start") + await asyncio.sleep(t) + print("task_cancel_other cancel") + other.cancel() + + +async def task_wait_for_cancel(id, t, t_wait): + print("task_wait_for_cancel start") + try: + await asyncio.wait_for(task(id, t), t_wait) + except asyncio.CancelledError as er: + print("task_wait_for_cancel cancelled") + raise er + + +async def task_wait_for_cancel_ignore(t_wait): + print("task_wait_for_cancel_ignore start") + try: + await asyncio.wait_for(task_catch(), t_wait) + except asyncio.CancelledError as er: + print("task_wait_for_cancel_ignore cancelled") + raise er + + +async def main(): + sep = "-" * 10 + + # When task finished before the timeout + print(await asyncio.wait_for(task(1, 0.01), 10)) + print(sep) + + # When timeout passes and task is cancelled + try: + print(await asyncio.wait_for(task(2, 10), 0.01)) + except asyncio.TimeoutError: + print("timeout") + print(sep) + + # 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") + print(sep) + + # When task raises an exception + try: + print(await asyncio.wait_for(task_raise(), 1)) + except ValueError: + print("ValueError") + print(sep) + + # Timeout of None means wait forever + print(await asyncio.wait_for(task(3, 0.1), None)) + print(sep) + + # When task is cancelled by another task + t = asyncio.create_task(task(4, 10)) + asyncio.create_task(task_cancel_other(0.01, t)) + try: + print(await asyncio.wait_for(t, 1)) + except asyncio.CancelledError as er: + print(repr(er)) + print(sep) + + # When wait_for gets cancelled + t = asyncio.create_task(task_wait_for_cancel(4, 1, 2)) + await asyncio.sleep(0.01) + t.cancel() + await asyncio.sleep(0.01) + print(sep) + + # When wait_for gets cancelled and awaited task ignores the cancellation request + t = asyncio.create_task(task_wait_for_cancel_ignore(2)) + await asyncio.sleep(0.01) + t.cancel() + await asyncio.sleep(0.01) + print(sep) + + # When wait_for gets cancelled and the task it's waiting on finishes around the + # same time as the cancellation of the wait_for + for num_sleep in range(1, 5): + t = asyncio.create_task(task_wait_for_cancel(4 + num_sleep, 0, 2)) + for _ in range(num_sleep): + await asyncio.sleep(0) + assert not t.done() + print("cancel wait_for") + t.cancel() + try: + await t + except asyncio.CancelledError as er: + print(repr(er)) + print(sep) + + print("finish") + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_wait_for.py.exp b/tests/asyncio/asyncio_wait_for.py.exp new file mode 100644 index 0000000..899e560 --- /dev/null +++ b/tests/asyncio/asyncio_wait_for.py.exp @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +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 +---------- +task start 4 +task_cancel_other start +task_cancel_other cancel +CancelledError() +---------- +task_wait_for_cancel start +task start 4 +task_wait_for_cancel cancelled +---------- +task_wait_for_cancel_ignore start +task_catch start +task_wait_for_cancel_ignore cancelled +ignore cancel +task_catch done +---------- +task_wait_for_cancel start +cancel wait_for +task start 5 +task_wait_for_cancel cancelled +CancelledError() +---------- +task_wait_for_cancel start +task start 6 +cancel wait_for +task end 6 +task_wait_for_cancel cancelled +CancelledError() +---------- +task_wait_for_cancel start +task start 7 +task end 7 +cancel wait_for +task_wait_for_cancel cancelled +CancelledError() +---------- +task_wait_for_cancel start +task start 8 +task end 8 +cancel wait_for +task_wait_for_cancel cancelled +CancelledError() +---------- +finish diff --git a/tests/asyncio/asyncio_wait_for_fwd.py b/tests/asyncio/asyncio_wait_for_fwd.py new file mode 100644 index 0000000..b6b3629 --- /dev/null +++ b/tests/asyncio/asyncio_wait_for_fwd.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# pylint: skip-file +# +# Test asyncio.wait_for, with forwarding cancellation +import asyncio + + +async def awaiting(t, return_if_fail): + try: + print("awaiting started") + await asyncio.sleep(t) + except asyncio.CancelledError as er: + # CPython wait_for raises CancelledError inside task but TimeoutError in wait_for + print("awaiting canceled") + if return_if_fail: + return False # return has no effect if Cancelled + else: + raise er + except Exception as er: + print("caught exception", er) + raise er + + +async def test_cancellation_forwarded(catch, catch_inside): + print("----------") + + async def wait(): + try: + await asyncio.wait_for(awaiting(2, catch_inside), 1) + except asyncio.TimeoutError as er: + print("Got timeout error") + raise er + except asyncio.CancelledError as er: + print("Got canceled") + if not catch: + raise er + + async def cancel(t): + print("cancel started") + await asyncio.sleep(0.01) + print("cancel wait()") + t.cancel() + + t = asyncio.create_task(wait()) + k = asyncio.create_task(cancel(t)) + try: + await t + except asyncio.CancelledError: + print("waiting got cancelled") + + +asyncio.run(test_cancellation_forwarded(False, False)) +asyncio.run(test_cancellation_forwarded(False, True)) +asyncio.run(test_cancellation_forwarded(True, True)) +asyncio.run(test_cancellation_forwarded(True, False)) diff --git a/tests/asyncio/asyncio_wait_for_fwd.py.exp b/tests/asyncio/asyncio_wait_for_fwd.py.exp new file mode 100644 index 0000000..2cd3705 --- /dev/null +++ b/tests/asyncio/asyncio_wait_for_fwd.py.exp @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +---------- +cancel started +awaiting started +cancel wait() +Got canceled +awaiting canceled +waiting got cancelled +---------- +cancel started +awaiting started +cancel wait() +Got canceled +awaiting canceled +waiting got cancelled +---------- +cancel started +awaiting started +cancel wait() +Got canceled +awaiting canceled +---------- +cancel started +awaiting started +cancel wait() +Got canceled +awaiting canceled diff --git a/tests/asyncio/asyncio_wait_task.py b/tests/asyncio/asyncio_wait_task.py new file mode 100644 index 0000000..b5c4c8b --- /dev/null +++ b/tests/asyncio/asyncio_wait_task.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George +# +# Test waiting on a task +import asyncio + + +import time + +if hasattr(time, "ticks_ms"): + ticks = time.ticks_ms + ticks_diff = time.ticks_diff +else: + + def ticks(): + return int(time.time() * 1000) + + def ticks_diff(ticks_a, ticks_b): + return ticks_b - ticks_a + + +async def task(task_id): + print("task", task_id) + + +async def delay_print(delay_time, task_id): + await asyncio.sleep(delay_time) + print(task_id) + + +async def task_raise(): + print("task_raise") + raise ValueError + + +async def main(): + print("start") + + # Wait on a task + task_1 = asyncio.create_task(task(1)) + await task_1 + + # Wait on a task that's already done + task_1 = asyncio.create_task(task(2)) + await asyncio.sleep(0.001) + await task_1 + + # Wait again on same task + await task_1 + + print("----") + + # Create 2 tasks + task_1 = asyncio.create_task(delay_print(0.2, "hello")) + task_2 = asyncio.create_task(delay_print(0.4, "world")) + + # Time how long the tasks take to finish, they should execute in parallel + print("start") + ticks_1 = ticks() + await task_1 + ticks_2 = ticks() + await task_2 + ticks_3 = ticks() + print( + "took {} {}".format( + round(ticks_diff(ticks_2, ticks_1), -2), + round(ticks_diff(ticks_3, ticks_2), -2), + ) + ) + + # Wait on a task that raises an exception + task_1 = asyncio.create_task(task_raise()) + try: + await task_1 + except ValueError: + print("ValueError") + + +asyncio.run(main()) diff --git a/tests/asyncio/asyncio_wait_task.py.exp b/tests/asyncio/asyncio_wait_task.py.exp new file mode 100644 index 0000000..65db976 --- /dev/null +++ b/tests/asyncio/asyncio_wait_task.py.exp @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +start +task 1 +task 2 +---- +start +hello +world +took 200 200 +task_raise +ValueError From 7e8bfde863e700567329650457f7659f88484c24 Mon Sep 17 00:00:00 2001 From: James Ward Date: Sat, 11 Nov 2023 22:18:03 -0500 Subject: [PATCH 4/6] chore: add a placeholder test so pytest doesn't complain --- tests/test_placeholder.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/test_placeholder.py diff --git a/tests/test_placeholder.py b/tests/test_placeholder.py new file mode 100644 index 0000000..9f0d2b3 --- /dev/null +++ b/tests/test_placeholder.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2019 Damien P. George +# +# SPDX-License-Identifier: MIT +# +# MicroPython uasyncio module +# MIT license; Copyright (c) 2019 Damien P. George + + +def test_placeholder(): + # Because we have the tests directory, the CI will attempt to run pytest + # and fail because no tests exist. By adding a placeholder test that + # we can avoid that without having to avoid the directory name `tests` + pass From 8bcddd2c3f229f5b053c8492d30fcfbd391c2fe1 Mon Sep 17 00:00:00 2001 From: James Ward Date: Sun, 12 Nov 2023 05:16:11 -0500 Subject: [PATCH 5/6] fix: properly redirect stderr to stdout --- tests/run_tests.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/run_tests.py b/tests/run_tests.py index 60305df..35be22b 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -69,9 +69,7 @@ def run_testcase(suite: str, testcase: str): with open("{0}.out".format(qtest), "w") as actual_output_file: actual_output_file.write(LICENSE_PREFIX) - result = os.system( - "{0} {1} 2>> {1}.out >> {1}.out".format(get_interpreter(), qtest) - ) + result = os.system("{0} {1} >> {1}.out 2>&1".format(get_interpreter(), qtest)) with open("%s.out" % qtest) as actual_output_file: actual_output = actual_output_file.read() From a9bd9362d6163dd77d6f839dd41cc350af358b6e Mon Sep 17 00:00:00 2001 From: James Ward Date: Tue, 14 Nov 2023 21:11:56 -0500 Subject: [PATCH 6/6] ci: explicitly test against circuitpython versions --- .github/workflows/build.yml | 5 +++++ .github/workflows/run-tests.yml | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4803cc9..95b8d7e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,3 +14,8 @@ jobs: uses: adafruit/workflows-circuitpython-libs/build@main with: package-prefix: "asyncio" + + test-v9-0-0-alpha5: + uses: ./.github/workflows/run-tests.yml + with: + cp-version: 9.0.0-alpha.5 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index cda6158..f6cf38d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -4,11 +4,18 @@ name: Run tests -on: [pull_request, push] +on: + workflow_call: + inputs: + cp-version: + required: true + type: string jobs: run: runs-on: ubuntu-22.04 + env: + CP_VERSION: ${{ inputs.cp-version }} steps: - name: Set up repository uses: actions/checkout@v3 @@ -18,6 +25,7 @@ jobs: - name: Set up circuitpython repository uses: actions/checkout@v3 with: + ref: ${{ inputs.cp-version }} repository: adafruit/circuitpython path: ./circuitpython/ submodules: false @@ -32,12 +40,19 @@ jobs: run: python tools/ci_fetch_deps.py tests shell: bash working-directory: ./circuitpython/ + - name: Fetch relevant submodules + id: submodules + run: python tools/ci_fetch_deps.py tests + working-directory: ./circuitpython - name: Install python dependencies run: pip install -r requirements-dev.txt shell: bash working-directory: ./circuitpython/ + - name: Build mpy-cross + run: make -C mpy-cross -j2 + working-directory: ./circuitpython/ - name: Build unix port - run: make -C ports/unix VARIANT=coverage -j2 + run: make -C ports/unix VARIANT=coverage BUILD=build-coverage PROG=micropython -j2 working-directory: ./circuitpython/ - name: Run tests run: ./run_tests.py