Skip to content

Commit 9758c2b

Browse files
committed
tests: Address PR review comments on example validation.
1 parent 69df812 commit 9758c2b

1 file changed

Lines changed: 28 additions & 40 deletions

File tree

tests/test_examples.py

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
11
"""Validate example files: syntax, method names, and basic consistency."""
22

33
import ast
4-
import re
5-
import importlib
6-
import inspect
74
from pathlib import Path
85

96
import pytest
107

118
LIB_DIR = Path(__file__).parent.parent / "lib"
129

13-
# Modules that exist only in MicroPython — skip import resolution for these.
14-
MICROPYTHON_MODULES = {
15-
"machine", "micropython", "utime", "ustruct", "ubinascii",
16-
"framebuf", "gc", "sys", "os", "struct", "time", "math",
17-
"neopixel", "network", "bluetooth", "ubluetooth",
18-
}
19-
2010

2111
def _discover_examples():
2212
"""Yield (driver_dir, example_path) for every example file."""
@@ -29,7 +19,7 @@ def _discover_examples():
2919

3020

3121
def _discover_driver_methods(driver_dir):
32-
"""Return the set of public method names from a driver's device.py."""
22+
"""Return the set of all method names from a driver's Python files."""
3323
# Find the module directory (may differ from driver dir name)
3424
module_dirs = [
3525
d for d in driver_dir.iterdir()
@@ -38,18 +28,26 @@ def _discover_driver_methods(driver_dir):
3828
if not module_dirs:
3929
return set()
4030

41-
device_py = module_dirs[0] / "device.py"
42-
try:
43-
tree = ast.parse(device_py.read_text(encoding="utf-8"))
44-
except SyntaxError:
45-
return set()
31+
# Prefer module dir matching driver name, else first alphabetically.
32+
driver_module = driver_dir.name.replace("-", "_")
33+
preferred = next((d for d in module_dirs if d.name == driver_module), None)
34+
module_dir = preferred or sorted(module_dirs, key=lambda d: d.name)[0]
4635

4736
methods = set()
48-
for node in ast.walk(tree):
49-
if isinstance(node, ast.ClassDef):
50-
for item in node.body:
51-
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
52-
methods.add(item.name)
37+
# Scan all .py files in the module (device.py, pin.py, etc.)
38+
for py_file in module_dir.glob("*.py"):
39+
if py_file.name == "__init__.py" or py_file.name == "const.py":
40+
continue
41+
try:
42+
tree = ast.parse(py_file.read_text(encoding="utf-8"))
43+
except SyntaxError as e:
44+
pytest.fail(f"Syntax error in driver {py_file}: {e}")
45+
46+
for node in ast.walk(tree):
47+
if isinstance(node, ast.ClassDef):
48+
for item in node.body:
49+
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
50+
methods.add(item.name)
5351
return methods
5452

5553

@@ -104,29 +102,19 @@ def test_method_calls_exist_in_driver(self, driver_dir, example_path):
104102

105103
# Only check methods that look like driver calls:
106104
# exclude Python builtins and common MicroPython methods
107-
builtins_and_common = {
108-
"print", "format", "range", "len", "int", "float", "str",
109-
"bytes", "bytearray", "list", "tuple", "dict", "set",
110-
"abs", "min", "max", "round", "hex", "type", "isinstance",
111-
"append", "extend", "join", "split", "strip", "encode",
112-
"decode", "replace", "startswith", "endswith", "find",
113-
"upper", "lower", "keys", "values", "items", "get", "pop",
114-
"update", "write", "read", "close", "open", "sleep",
105+
# Methods that are NOT from the driver but may appear on
106+
# driver variables due to MicroPython patterns or inheritance.
107+
# Keep this list minimal — only names that cause false positives.
108+
non_driver_methods = {
115109
"sleep_ms", "sleep_us", "ticks_ms", "ticks_us", "ticks_diff",
116-
"init", "value", "on", "off", "irq", "const",
117-
"I2C", "SPI", "Pin", "ADC", "PWM", "UART", "Timer",
118-
"fill", "text", "show", "pixel", "line", "rect",
119-
"fill_rect", "scroll", "blit", "hline", "vline",
120-
"framebuf", "FrameBuffer",
121110
"collect", "mem_free", "mem_alloc",
122-
"scan", "readfrom_mem", "writeto_mem", "readfrom",
123-
"writeto", "readfrom_mem_into",
124-
# Common method names shared across drivers
125-
"toggle", "mode", "pull", "freq",
126111
}
127112

128113
# Find the driver class name to identify which variable holds it
129-
tree = ast.parse(source)
114+
try:
115+
tree = ast.parse(source)
116+
except SyntaxError:
117+
return # Covered by test_syntax_valid
130118
driver_imports = set()
131119
for node in ast.walk(tree):
132120
if isinstance(node, ast.ImportFrom):
@@ -169,7 +157,7 @@ def test_method_calls_exist_in_driver(self, driver_dir, example_path):
169157
obj = node.func.value
170158
if isinstance(obj, ast.Name) and obj.id in driver_vars:
171159
method = node.func.attr
172-
if method not in driver_methods and method not in builtins_and_common:
160+
if method not in driver_methods and method not in non_driver_methods:
173161
missing.append((node.lineno, method))
174162

175163
if missing:

0 commit comments

Comments
 (0)