175 lines
4.7 KiB
Python
175 lines
4.7 KiB
Python
"""
|
|
Smoke-test pattern generators with a fake driver (no hardware).
|
|
|
|
Run from repo root (CPython or MicroPython):
|
|
|
|
python3 led-driver/tests/pattern_smoke.py
|
|
|
|
Requires only the stdlib; loads ``preset.py`` and ``patterns/*.py`` from
|
|
``led-driver/src`` via import paths (MicroPython: copy ``src`` tree to the
|
|
device and run the same command if ``importlib.util`` is available, or set
|
|
``PYTHONPATH`` to ``led-driver/src`` and use ``python3`` on the host).
|
|
|
|
Exit code 0 on success, 1 on any failure.
|
|
"""
|
|
|
|
import importlib.util
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Host-only ``utime`` so patterns import on CPython.
|
|
# -----------------------------------------------------------------------------
|
|
_UTIME_MS = [0]
|
|
|
|
|
|
def utime_advance(ms):
|
|
_UTIME_MS[0] += int(ms)
|
|
|
|
|
|
def _install_utime_shim():
|
|
if "utime" in sys.modules:
|
|
return
|
|
import types
|
|
|
|
m = types.ModuleType("utime")
|
|
|
|
def ticks_ms():
|
|
return _UTIME_MS[0]
|
|
|
|
def ticks_diff(a, b):
|
|
return a - b
|
|
|
|
def ticks_add(a, b):
|
|
return a + b
|
|
|
|
m.ticks_ms = ticks_ms
|
|
m.ticks_diff = ticks_diff
|
|
m.ticks_add = ticks_add
|
|
sys.modules["utime"] = m
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Load ``preset`` and pattern modules from ``led-driver/src`` (no sys.path).
|
|
# -----------------------------------------------------------------------------
|
|
_SRC = Path(__file__).resolve().parent.parent / "src"
|
|
_TESTS = Path(__file__).resolve().parent
|
|
|
|
|
|
def _load_module(name, path):
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError("no spec for %s" % path)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
def _pattern_class_from_module(mod):
|
|
for attr_name in dir(mod):
|
|
attr = getattr(mod, attr_name)
|
|
if isinstance(attr, type) and hasattr(attr, "run"):
|
|
return attr
|
|
return None
|
|
|
|
|
|
def _load_preset_class():
|
|
preset_mod = _load_module("preset", _SRC / "preset.py")
|
|
return preset_mod.Preset
|
|
|
|
|
|
def _list_pattern_basenames():
|
|
pat_dir = _SRC / "patterns"
|
|
out = []
|
|
for p in sorted(pat_dir.iterdir()):
|
|
if p.suffix != ".py":
|
|
continue
|
|
if p.name in ("__init__.py", "main.py"):
|
|
continue
|
|
out.append(p.stem)
|
|
return out
|
|
|
|
|
|
def _default_preset_dict(basename):
|
|
"""Reasonable defaults so each pattern's ``run()`` can start."""
|
|
base = {
|
|
"p": basename,
|
|
"d": 100,
|
|
"b": 200,
|
|
"a": True,
|
|
"n1": 5,
|
|
"n2": 8,
|
|
"n3": 5,
|
|
"n4": 4,
|
|
}
|
|
if basename in ("rainbow", "colour_cycle"):
|
|
base["c"] = []
|
|
base["n1"] = 2
|
|
elif basename == "transition":
|
|
base["c"] = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
|
|
base["d"] = 200
|
|
elif basename in ("chase", "circle"):
|
|
base["c"] = [(255, 0, 0), (0, 0, 255)]
|
|
elif basename == "pulse":
|
|
base["c"] = [(0, 200, 100)]
|
|
base["n1"] = 50
|
|
base["n2"] = 40
|
|
base["n3"] = 50
|
|
base["d"] = 30
|
|
elif basename in ("blink", "flicker"):
|
|
base["c"] = [(255, 100, 0)]
|
|
base["d"] = 80
|
|
elif basename == "flame":
|
|
base["c"] = []
|
|
base["d"] = 40
|
|
base["n1"] = 40
|
|
base["n2"] = 2000
|
|
base["n3"] = -1
|
|
base["n4"] = 0
|
|
else:
|
|
base["c"] = [(200, 200, 200)]
|
|
return base
|
|
|
|
|
|
def _run_pattern_ticks(Preset, driver, basename, steps, ms_per_tick):
|
|
_install_utime_shim()
|
|
mod = _load_module("patterns.%s" % basename, _SRC / "patterns" / (basename + ".py"))
|
|
cls = _pattern_class_from_module(mod)
|
|
if cls is None:
|
|
raise RuntimeError("no pattern class in %s" % basename)
|
|
|
|
preset = Preset(_default_preset_dict(basename))
|
|
gen = cls(driver).run(preset)
|
|
for _ in range(steps):
|
|
utime_advance(ms_per_tick)
|
|
next(gen)
|
|
|
|
|
|
def main():
|
|
failures = []
|
|
Preset = _load_preset_class()
|
|
fake_mod = _load_module("fake_driver", _TESTS / "fake_driver.py")
|
|
FakePresets = fake_mod.FakePresets
|
|
|
|
for basename in _list_pattern_basenames():
|
|
d = FakePresets(16)
|
|
try:
|
|
_run_pattern_ticks(Preset, d, basename, steps=80, ms_per_tick=50)
|
|
print("ok patterns.%s" % basename)
|
|
except Exception as exc:
|
|
print("FAIL patterns.%s: %r" % (basename, exc))
|
|
failures.append((basename, exc))
|
|
|
|
if failures:
|
|
print("%d pattern(s) failed" % len(failures))
|
|
return 1
|
|
print("all %d pattern smoke tests passed" % len(_list_pattern_basenames()))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except KeyboardInterrupt:
|
|
sys.exit(130)
|