From 88fc55360a4f8cec257473456889d74b6d9ea496 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Mon, 16 Sep 2024 09:38:32 +0100 Subject: [PATCH] Add github action to run unit tests on every PR (#19) Fixes #5 --- .github/workflows/check_docs.yaml | 2 +- .github/workflows/run_tests.yaml | 23 ++++++++++++++ ci/deploy_to_device.py | 6 +++- ci/test.py | 53 +++++++++++++++++++++++++++++++ tests/ultimo/test_core.py | 28 ++++++++-------- tests/ultimo/test_poll.py | 30 ++++++++--------- 6 files changed, 111 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/run_tests.yaml create mode 100644 ci/test.py diff --git a/.github/workflows/check_docs.yaml b/.github/workflows/check_docs.yaml index cd2c316..fa68b2e 100644 --- a/.github/workflows/check_docs.yaml +++ b/.github/workflows/check_docs.yaml @@ -1,4 +1,4 @@ -name: "Pull Request Docs Check" +name: "Check Docs" on: - workflow_dispatch diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml new file mode 100644 index 0000000..18b5e44 --- /dev/null +++ b/.github/workflows/run_tests.yaml @@ -0,0 +1,23 @@ +name: "Run Tests" + +on: +- workflow_dispatch +- pull_request + +jobs: + run-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies and local packages + run: python -m pip install mpremote click + - name: Install MicroPython + uses: BrianPugh/install-micropython@v2 + - name: Install Micropython dependencies + run: micropython -m mip install unittest + - name: Run tests + run: python -m ci.test diff --git a/ci/deploy_to_device.py b/ci/deploy_to_device.py index 7616a1e..cd518c6 100644 --- a/ci/deploy_to_device.py +++ b/ci/deploy_to_device.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2024-present Unital Software +# +# SPDX-License-Identifier: MIT + from pathlib import Path import subprocess @@ -6,6 +10,7 @@ @click.command() def deploy(): + """Deploy files to a device via mpremote""" try: deploy_py_files(Path("docs/source/examples"), ":") deploy_py_files(Path("docs/source/examples/devices"), ":/devices") @@ -17,7 +22,6 @@ def deploy(): print(exc.stderr) raise - def deploy_py_files(path: Path, destination): try: mpremote("mkdir", destination) diff --git a/ci/test.py b/ci/test.py new file mode 100644 index 0000000..1d16434 --- /dev/null +++ b/ci/test.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2024-present Unital Software +# +# SPDX-License-Identifier: MIT + +from pathlib import Path +import os +import subprocess +import sys + +import click + + + +@click.command() +def test(): + """Run unit tests in micropython""" + print("Running Tests") + failures = [] + test_dir = Path("tests/ultimo") + os.environ["MICROPYPATH"] = "src:" + os.environ.get('MICROPYPATH', ":.frozen:~/.micropython/lib:/usr/lib/micropython") + for path in sorted(test_dir.glob("*.py")): + print(path.name, "... ", end="", flush=True) + result = run_test(path) + if result: + failures.append(result) + print('FAILED') + else: + print('OK') + print() + + for path, stdout, stderr in failures: + print("FAILURE: ", path.name) + print("STDOUT ", "="*70) + print(stdout.decode('utf-8')) + print() + print("STDERR ", "="*70) + print(stderr.decode('utf-8')) + print() + + if failures: + sys.exit(1) + else: + print("PASSED") + + +def run_test(path): + try: + subprocess.run(["micropython", path], capture_output=True, check=True) + except subprocess.CalledProcessError as exc: + return (path, exc.stdout, exc.stderr) + +if __name__ == "__main__": + test() \ No newline at end of file diff --git a/tests/ultimo/test_core.py b/tests/ultimo/test_core.py index ff30fa3..825eea2 100644 --- a/tests/ultimo/test_core.py +++ b/tests/ultimo/test_core.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT import unittest -import asyncio +import uasyncio from ultimo.core import APipeline, ASource, ASink, asynchronize @@ -13,7 +13,7 @@ def __init__(self, count): self.count = count async def __call__(self): - await asyncio.sleep(0.01) + await uasyncio.sleep(0.01) value = self.count self.count -= 1 if value < 0: @@ -42,7 +42,7 @@ class TestASource(unittest.TestCase): def test_immediate(self): source = FiniteSource(1) - result = asyncio.run(source()) + result = uasyncio.run(source()) self.assertEqual(result, 1) @@ -55,7 +55,7 @@ async def iterate(): async for value in source: result.append(value) - asyncio.run(iterate()) + uasyncio.run(iterate()) self.assertEqual(result, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]) @@ -66,7 +66,7 @@ def test_immediate(self): source = FiniteSource(1) sink = CollectingSink(source=source) - asyncio.run(sink()) + uasyncio.run(sink()) self.assertEqual(sink.results, [1]) @@ -74,7 +74,7 @@ def test_iterate(self): source = FiniteSource(10) sink = CollectingSink(source=source) - asyncio.run(sink.run()) + uasyncio.run(sink.run()) self.assertEqual(sink.results, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]) @@ -82,28 +82,28 @@ def test_connect(self): source = FiniteSource(10) sink = CollectingSink() - asyncio.run((source | sink).run()) + uasyncio.run((source | sink).run()) self.assertEqual(sink.results, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]) def test_no_source_immediate(self): sink = CollectingSink() - asyncio.run(sink(1)) + uasyncio.run(sink(1)) self.assertEqual(sink.results, [1]) def test_no_source_no_value(self): sink = CollectingSink() - asyncio.run(sink()) + uasyncio.run(sink()) self.assertEqual(sink.results, []) def test_no_source_iterate(self): sink = CollectingSink() - asyncio.run(sink.run()) + uasyncio.run(sink.run()) self.assertEqual(sink.results, []) @@ -115,7 +115,7 @@ def test_immediate(self): pipeline = IncrementPipeline(source=source) sink = CollectingSink(source=pipeline) - asyncio.run(sink()) + uasyncio.run(sink()) self.assertEqual(sink.results, [2]) @@ -124,7 +124,7 @@ def test_iterate(self): pipeline = IncrementPipeline(source=source) sink = CollectingSink(source=pipeline) - asyncio.run(sink.run()) + uasyncio.run(sink.run()) self.assertEqual(sink.results, [11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]) @@ -133,7 +133,7 @@ def test_connect(self): pipeline = IncrementPipeline(source=source) sink = CollectingSink(source=pipeline) - asyncio.run((source | pipeline | sink).run()) + uasyncio.run((source | pipeline | sink).run()) self.assertEqual(sink.results, [11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]) @@ -146,7 +146,7 @@ def sync_example(): asynchronized = asynchronize(sync_example) - result = asyncio.run(asynchronized()) + result = uasyncio.run(asynchronized()) self.assertEqual(result, 1) diff --git a/tests/ultimo/test_poll.py b/tests/ultimo/test_poll.py index 73add59..726d906 100644 --- a/tests/ultimo/test_poll.py +++ b/tests/ultimo/test_poll.py @@ -3,8 +3,8 @@ # SPDX-License-Identifier: MIT import unittest -import asyncio -import time +import uasyncio +import utime from ultimo.poll import Poll, apoll, poll @@ -16,7 +16,7 @@ def test_immediate(self): async def decrement(): nonlocal count - await asyncio.sleep(0.001) + await uasyncio.sleep(0.001) value = count count -= 1 if value < 0: @@ -26,7 +26,7 @@ async def decrement(): source = Poll(decrement, 0.01) - result = asyncio.run(source()) + result = uasyncio.run(source()) self.assertEqual(result, 1) @@ -35,7 +35,7 @@ def test_iterate(self): async def decrement(): nonlocal count - await asyncio.sleep(0.001) + await uasyncio.sleep(0.001) value = count count -= 1 if value < 0: @@ -51,9 +51,9 @@ async def iterate(): async for value in source: result.append(value) - start = time.ticks_ms() - asyncio.run(iterate()) - elapsed = time.ticks_diff(time.ticks_ms(), start) + start = utime.ticks_ms() + uasyncio.run(iterate()) + elapsed = utime.ticks_diff(utime.ticks_ms(), start) self.assertEqual(result, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]) self.assertGreaterEqual(elapsed, 100) @@ -64,7 +64,7 @@ def test_apoll(self): @apoll async def decrement(): - await asyncio.sleep(0.001) + await uasyncio.sleep(0.001) nonlocal count value = count count -= 1 @@ -81,9 +81,9 @@ async def iterate(): async for value in source: result.append(value) - start = time.ticks_ms() - asyncio.run(iterate()) - elapsed = time.ticks_diff(time.ticks_ms(), start) + start = utime.ticks_ms() + uasyncio.run(iterate()) + elapsed = utime.ticks_diff(utime.ticks_ms(), start) self.assertEqual(result, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]) self.assertGreaterEqual(elapsed, 100) @@ -110,9 +110,9 @@ async def iterate(): async for value in source: result.append(value) - start = time.ticks_ms() - asyncio.run(iterate()) - elapsed = time.ticks_diff(time.ticks_ms(), start) + start = utime.ticks_ms() + uasyncio.run(iterate()) + elapsed = utime.ticks_diff(utime.ticks_ms(), start) self.assertEqual(result, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]) self.assertGreaterEqual(elapsed, 100)