mirror of
https://github.com/MrUnknownDE/OpenIris-ESPIDF.git
synced 2026-04-19 06:23:44 +02:00
Initial support for a hardware test harness with pytest and UV
This commit is contained in:
3
tests/.env.example
Normal file
3
tests/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
WIFI_SSID=
|
||||
WIFI_PASS=
|
||||
SWITCH_MODE_REBOOT_TIME=10
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
165
tests/conftest.py
Normal file
165
tests/conftest.py
Normal file
@@ -0,0 +1,165 @@
|
||||
import dotenv
|
||||
import pytest
|
||||
import time
|
||||
|
||||
from tests.utils import (
|
||||
OpenIrisDeviceManager,
|
||||
has_command_failed,
|
||||
get_current_ports,
|
||||
get_new_port,
|
||||
)
|
||||
|
||||
board_capabilities = {
|
||||
"esp_eye": ["wired", "wireless"],
|
||||
"esp32AIThinker": ["wireless"],
|
||||
"esp32Cam": ["wireless"],
|
||||
"esp32M5Stack": ["wireless"],
|
||||
"facefocusvr_eye_L": ["wired", "measure_current"],
|
||||
"facefocusvr_eye_R": ["wired", "measure_current"],
|
||||
"facefocusvr_face": ["wired", "measure_current"],
|
||||
"project_babble": ["wireless", "wired"],
|
||||
"seed_studio": ["wireless", "wired"],
|
||||
"wrooms3": ["wireless", "wired"],
|
||||
"wrooms3QIO": ["wireless", "wired"],
|
||||
"wrover": ["wireless", "wired"],
|
||||
}
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--board", action="store")
|
||||
parser.addoption(
|
||||
"--connection",
|
||||
action="store",
|
||||
help="Defines how to connect to the given board, wireless by ip/mdns or wired by com/cdc",
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
config.addinivalue_line(
|
||||
"markers", "has_capability(cap): skip if the board does not have the capability"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_capability_marker(request, board_lacks_capability):
|
||||
"""
|
||||
Autorun on each test, checks if the board we started with, has the required capability
|
||||
|
||||
This lets us skip tests that are impossible to run on some boards - like for example:
|
||||
|
||||
It's impossible to run wired tests on a wireless board
|
||||
It's impossible to run tests for measuring current on boards without this feature
|
||||
"""
|
||||
if marker := request.node.get_closest_marker("has_capability"):
|
||||
if not len(marker.args):
|
||||
raise ValueError(
|
||||
"has_capability marker must be provided with a capability to check"
|
||||
)
|
||||
|
||||
required_capability = marker.args[0]
|
||||
if board_lacks_capability(required_capability):
|
||||
pytest.skip(f"Board does not have capability {required_capability}")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def board_name(request):
|
||||
board_name = request.config.getoption("--board")
|
||||
if not board_name:
|
||||
raise ValueError("No board defined")
|
||||
|
||||
yield board_name
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def board_lacks_capability(board_name):
|
||||
def func(capability: str):
|
||||
if board_name:
|
||||
if board_name not in board_capabilities:
|
||||
raise ValueError(f"Unknown board {board_name}")
|
||||
|
||||
return capability not in board_capabilities[board_name]
|
||||
return True
|
||||
|
||||
return func
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def board_connection(request):
|
||||
"""
|
||||
Grabs the specified connection connection method, to be used ONLY for the initial connection. Everything after it HAS to be handled via Device Manager.
|
||||
Ports WILL change throughout the tests, device manager can keep track of that and reconnect the board as needed.
|
||||
"""
|
||||
board_connection = request.config.getoption("--connection")
|
||||
|
||||
if not board_connection:
|
||||
raise ValueError("No connection method defined")
|
||||
|
||||
yield board_connection
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def config():
|
||||
config = dotenv.dotenv_values()
|
||||
yield config
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def openiris_device_manager(board_connection, config):
|
||||
manager = OpenIrisDeviceManager()
|
||||
manager.get_device(board_connection, config)
|
||||
yield manager
|
||||
|
||||
if manager._device:
|
||||
manager._device.disconnect()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def get_openiris_device(openiris_device_manager):
|
||||
def func():
|
||||
return openiris_device_manager.get_device()
|
||||
|
||||
return func
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def ensure_board_in_mode(openiris_device_manager, config):
|
||||
"""
|
||||
Given the OpenIrisDevice manager, grabs the current device and ensures it's in the required mode
|
||||
|
||||
if not, sends the command to switch and attempts reconnection if necessary, returning the device back
|
||||
"""
|
||||
supported_modes = ["wifi", "uvc"]
|
||||
|
||||
def func(mode, device):
|
||||
if mode not in supported_modes:
|
||||
raise ValueError(f"{mode} is not a supported mode")
|
||||
|
||||
command_result = device.send_command("get_device_mode")
|
||||
if has_command_failed(command_result):
|
||||
raise ValueError(f"Failed to get device mode, error: {command_result}")
|
||||
|
||||
current_mode = command_result["results"][0]["result"]["data"]["mode"].lower()
|
||||
if mode == current_mode:
|
||||
return device
|
||||
|
||||
old_ports = get_current_ports()
|
||||
command_result = device.send_command("switch_mode", {"mode": mode})
|
||||
if has_command_failed(command_result):
|
||||
raise ValueError("Failed to switch mode, rerun the tests")
|
||||
|
||||
print("Rebooting the board after changing mode")
|
||||
device.send_command("restart_device")
|
||||
|
||||
sleep_timeout = int(config["SWITCH_MODE_REBOOT_TIME"])
|
||||
print(
|
||||
f"Sleeping for {sleep_timeout} seconds to allow the device to switch modes and boot up"
|
||||
)
|
||||
time.sleep(sleep_timeout)
|
||||
new_ports = get_current_ports()
|
||||
|
||||
new_device = openiris_device_manager.get_device(
|
||||
get_new_port(old_ports, new_ports), config
|
||||
)
|
||||
return new_device
|
||||
|
||||
return func
|
||||
11
tests/test_basic_functionality.py
Normal file
11
tests/test_basic_functionality.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from tests.utils import has_command_failed
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.has_capability("wired")
|
||||
def test_ping_wired(get_openiris_device, ensure_board_in_mode):
|
||||
device = get_openiris_device()
|
||||
device = ensure_board_in_mode("wifi", device)
|
||||
|
||||
command_result = device.send_command("ping")
|
||||
assert not has_command_failed(command_result)
|
||||
49
tests/utils.py
Normal file
49
tests/utils.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import time
|
||||
import serial.tools.list_ports
|
||||
from tools.openiris_device import OpenIrisDevice
|
||||
|
||||
OPENIRIS_DEVICE = None
|
||||
|
||||
|
||||
class OpenIrisDeviceManager:
|
||||
def __init__(self):
|
||||
self._device: OpenIrisDevice | None = None
|
||||
self._current_port: str | None = None
|
||||
|
||||
def get_device(self, port: str | None = None, config=None) -> OpenIrisDevice:
|
||||
"""
|
||||
Returns the current OpenIrisDevice connection helper
|
||||
|
||||
if the port changed from the one given previously, it will attempt to reconnect
|
||||
if no device exists, we will create one and try to connect
|
||||
|
||||
This helper is designed to be used within a session long fixture
|
||||
"""
|
||||
if not port and not self._device:
|
||||
raise ValueError("No device connected yet, provide a port first")
|
||||
|
||||
if port and port != self._current_port:
|
||||
print(f"Port changed from {self._current_port} to {port}, reconnecting...")
|
||||
self._current_port = port
|
||||
|
||||
if self._device:
|
||||
self._device.disconnect()
|
||||
self._device = None
|
||||
|
||||
self._device = OpenIrisDevice(port, False, False)
|
||||
self._device.connect()
|
||||
time.sleep(int(config["SWITCH_MODE_REBOOT_TIME"]))
|
||||
|
||||
return self._device
|
||||
|
||||
|
||||
def has_command_failed(result) -> bool:
|
||||
return "error" in result or result["results"][0]["result"]["status"] != "success"
|
||||
|
||||
|
||||
def get_current_ports() -> list[str]:
|
||||
return [port.name for port in serial.tools.list_ports.comports()]
|
||||
|
||||
|
||||
def get_new_port(old_ports, new_ports) -> str:
|
||||
return list(set(new_ports) - set(old_ports))[0]
|
||||
Reference in New Issue
Block a user