Remove @require_x

This commit is contained in:
Remi Cadene
2024-09-26 14:35:17 +02:00
parent b6b7fda5f8
commit 8a7b5c45c7
5 changed files with 88 additions and 148 deletions

View File

@@ -25,7 +25,11 @@ import numpy as np
import pytest import pytest
from lerobot.common.robot_devices.utils import RobotDeviceAlreadyConnectedError, RobotDeviceNotConnectedError from lerobot.common.robot_devices.utils import RobotDeviceAlreadyConnectedError, RobotDeviceNotConnectedError
from tests.utils import TEST_CAMERA_TYPES, make_camera, require_camera from tests.utils import (
TEST_CAMERA_TYPES,
make_camera,
mock_camera_or_skip_test_when_not_available,
)
# Maximum absolute difference between two consecutive images recored by a camera. # Maximum absolute difference between two consecutive images recored by a camera.
# This value differs with respect to the camera. # This value differs with respect to the camera.
@@ -37,8 +41,7 @@ def compute_max_pixel_difference(first_image, second_image):
@pytest.mark.parametrize("camera_type, mock", TEST_CAMERA_TYPES) @pytest.mark.parametrize("camera_type, mock", TEST_CAMERA_TYPES)
@require_camera def test_camera(monkeypatch, camera_type, mock):
def test_camera(request, camera_type, mock):
"""Test assumes that `camera.read()` returns the same image when called multiple times in a row. """Test assumes that `camera.read()` returns the same image when called multiple times in a row.
So the environment should not change (you shouldnt be in front of the camera) and the camera should not be moving. So the environment should not change (you shouldnt be in front of the camera) and the camera should not be moving.
@@ -48,6 +51,8 @@ def test_camera(request, camera_type, mock):
# TODO(rcadene): measure fps in nightly? # TODO(rcadene): measure fps in nightly?
# TODO(rcadene): test logs # TODO(rcadene): test logs
mock_camera_or_skip_test_when_not_available(monkeypatch, camera_type, mock)
# Test instantiating # Test instantiating
camera = make_camera(camera_type) camera = make_camera(camera_type)
@@ -141,9 +146,10 @@ def test_camera(request, camera_type, mock):
@pytest.mark.parametrize("camera_type, mock", TEST_CAMERA_TYPES) @pytest.mark.parametrize("camera_type, mock", TEST_CAMERA_TYPES)
@require_camera def test_save_images_from_cameras(tmpdir, monkeypatch, camera_type, mock):
def test_save_images_from_cameras(tmpdir, request, camera_type, mock):
# TODO(rcadene): refactor # TODO(rcadene): refactor
mock_camera_or_skip_test_when_not_available(monkeypatch, camera_type, mock)
if camera_type == "opencv": if camera_type == "opencv":
from lerobot.common.robot_devices.cameras.opencv import save_images_from_cameras from lerobot.common.robot_devices.cameras.opencv import save_images_from_cameras
elif camera_type == "intelrealsense": elif camera_type == "intelrealsense":

View File

@@ -31,12 +31,18 @@ from lerobot.common.policies.factory import make_policy
from lerobot.common.utils.utils import init_hydra_config from lerobot.common.utils.utils import init_hydra_config
from lerobot.scripts.control_robot import calibrate, get_available_arms, record, replay, teleoperate from lerobot.scripts.control_robot import calibrate, get_available_arms, record, replay, teleoperate
from tests.test_robots import make_robot from tests.test_robots import make_robot
from tests.utils import DEFAULT_CONFIG_PATH, DEVICE, TEST_ROBOT_TYPES, require_robot from tests.utils import (
DEFAULT_CONFIG_PATH,
DEVICE,
TEST_ROBOT_TYPES,
mock_robot_or_skip_test_when_not_available,
)
@pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES) @pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES)
@require_robot def test_teleoperate(monkeypatch, robot_type, mock):
def test_teleoperate(request, robot_type, mock): mock_robot_or_skip_test_when_not_available(monkeypatch, robot_type, mock)
robot = make_robot(robot_type) robot = make_robot(robot_type)
teleoperate(robot, teleop_time_s=1) teleoperate(robot, teleop_time_s=1)
teleoperate(robot, fps=30, teleop_time_s=1) teleoperate(robot, fps=30, teleop_time_s=1)
@@ -45,16 +51,18 @@ def test_teleoperate(request, robot_type, mock):
@pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES) @pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES)
@require_robot def test_calibrate(monkeypatch, robot_type, mock):
def test_calibrate(request, robot_type, mock): mock_robot_or_skip_test_when_not_available(monkeypatch, robot_type, mock)
robot = make_robot(robot_type) robot = make_robot(robot_type)
calibrate(robot, arms=get_available_arms(robot)) calibrate(robot, arms=get_available_arms(robot))
del robot del robot
@pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES) @pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES)
@require_robot def test_record_without_cameras(tmpdir, monkeypatch, robot_type, mock):
def test_record_without_cameras(tmpdir, request, robot_type, mock): mock_robot_or_skip_test_when_not_available(monkeypatch, robot_type, mock)
root = Path(tmpdir) root = Path(tmpdir)
repo_id = "lerobot/debug" repo_id = "lerobot/debug"
@@ -74,8 +82,9 @@ def test_record_without_cameras(tmpdir, request, robot_type, mock):
@pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES) @pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES)
@require_robot def test_record_and_replay_and_policy(tmpdir, monkeypatch, robot_type, mock):
def test_record_and_replay_and_policy(tmpdir, request, robot_type, mock): mock_robot_or_skip_test_when_not_available(monkeypatch, robot_type, mock)
env_name = "koch_real" env_name = "koch_real"
policy_name = "act_koch_real" policy_name = "act_koch_real"

View File

@@ -31,17 +31,22 @@ import numpy as np
import pytest import pytest
from lerobot.common.robot_devices.utils import RobotDeviceAlreadyConnectedError, RobotDeviceNotConnectedError from lerobot.common.robot_devices.utils import RobotDeviceAlreadyConnectedError, RobotDeviceNotConnectedError
from tests.utils import TEST_MOTOR_TYPES, make_motors_bus, mock_input, require_motor from tests.utils import (
TEST_MOTOR_TYPES,
make_motors_bus,
mock_input,
mock_motor_or_skip_test_when_not_available,
)
@pytest.mark.parametrize("motor_type, mock", TEST_MOTOR_TYPES) @pytest.mark.parametrize("motor_type, mock", TEST_MOTOR_TYPES)
@require_motor def test_find_port(monkeypatch, motor_type, mock):
def test_find_port(request, motor_type, mock): mock_motor_or_skip_test_when_not_available(monkeypatch, motor_type, mock)
from lerobot.common.robot_devices.motors.dynamixel import find_port from lerobot.common.robot_devices.motors.dynamixel import find_port
if mock: if mock:
# To run find_port without user input # To run find_port without user input
monkeypatch = request.getfixturevalue("monkeypatch")
monkeypatch.setattr("builtins.input", mock_input) monkeypatch.setattr("builtins.input", mock_input)
with pytest.raises(OSError): with pytest.raises(OSError):
@@ -51,11 +56,11 @@ def test_find_port(request, motor_type, mock):
@pytest.mark.parametrize("motor_type, mock", TEST_MOTOR_TYPES) @pytest.mark.parametrize("motor_type, mock", TEST_MOTOR_TYPES)
@require_motor def test_configure_motors_all_ids_1(monkeypatch, motor_type, mock):
def test_configure_motors_all_ids_1(request, motor_type, mock): mock_motor_or_skip_test_when_not_available(monkeypatch, motor_type, mock)
if mock: if mock:
# To run find_port without user input # To run find_port without user input
monkeypatch = request.getfixturevalue("monkeypatch")
monkeypatch.setattr("builtins.input", mock_input) monkeypatch.setattr("builtins.input", mock_input)
input("Are you sure you want to re-configure the motors? Press enter to continue...") input("Are you sure you want to re-configure the motors? Press enter to continue...")
@@ -75,8 +80,9 @@ def test_configure_motors_all_ids_1(request, motor_type, mock):
@pytest.mark.parametrize("motor_type, mock", TEST_MOTOR_TYPES) @pytest.mark.parametrize("motor_type, mock", TEST_MOTOR_TYPES)
@require_motor def test_motors_bus(monkeypatch, motor_type, mock):
def test_motors_bus(request, motor_type, mock): mock_motor_or_skip_test_when_not_available(monkeypatch, motor_type, mock)
motors_bus = make_motors_bus(motor_type) motors_bus = make_motors_bus(motor_type)
# Test reading and writting before connecting raises an error # Test reading and writting before connecting raises an error

View File

@@ -29,15 +29,16 @@ import pytest
import torch import torch
from lerobot.common.robot_devices.utils import RobotDeviceAlreadyConnectedError, RobotDeviceNotConnectedError from lerobot.common.robot_devices.utils import RobotDeviceAlreadyConnectedError, RobotDeviceNotConnectedError
from tests.utils import TEST_ROBOT_TYPES, make_robot, require_robot from tests.utils import TEST_ROBOT_TYPES, make_robot, mock_robot_or_skip_test_when_not_available
@pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES) @pytest.mark.parametrize("robot_type, mock", TEST_ROBOT_TYPES)
@require_robot def test_robot(tmpdir, monkeypatch, robot_type, mock):
def test_robot(tmpdir, request, robot_type, mock):
# TODO(rcadene): measure fps in nightly? # TODO(rcadene): measure fps in nightly?
# TODO(rcadene): test logs # TODO(rcadene): test logs
# TODO(rcadene): add compatibility with other robots # TODO(rcadene): add compatibility with other robots
mock_robot_or_skip_test_when_not_available(monkeypatch, robot_type, mock)
from lerobot.common.robot_devices.robots.manipulator import ManipulatorRobot from lerobot.common.robot_devices.robots.manipulator import ManipulatorRobot
if robot_type == "aloha" and mock: if robot_type == "aloha" and mock:

View File

@@ -185,134 +185,52 @@ def require_package(package_name):
return decorator return decorator
def require_robot(func): def mock_robot_or_skip_test_when_not_available(monkeypatch, robot_type, mock):
""" if mock:
Decorator that skips the test if a robot is not available
The decorated function must have two arguments `request` and `robot_type`.
Example of usage:
```python
@pytest.mark.parametrize(
"robot_type", ["koch", "aloha"]
)
@require_robot
def test_require_robot(request, robot_type):
pass
```
"""
@wraps(func)
def wrapper(*args, **kwargs):
# Access the pytest request context to get the mockeypatch fixture
request = kwargs.get("request")
robot_type = kwargs.get("robot_type")
mock = kwargs.get("mock")
if robot_type is None:
raise ValueError("The 'robot_type' must be an argument of the test function.")
if request is None:
raise ValueError("The 'request' fixture must be an argument of the test function.")
if mock is None:
raise ValueError("The 'mock' variable must be an argument of the test function.")
if robot_type not in available_robots:
raise ValueError(
f"The camera type '{robot_type}' is not valid. Expected one of these '{available_robots}"
)
# Run test with a monkeypatched version of the robot devices. # Run test with a monkeypatched version of the robot devices.
if mock: # TODO(rcadene): redesign mocking to not have this hardcoded logic
# TODO(rcadene): redesign mocking to not have this hardcoded logic if robot_type in ["koch", "koch_bimanual"]:
if robot_type in ["koch", "koch_bimanual"]: camera_type = "opencv"
camera_type = "opencv" elif robot_type == "aloha":
elif robot_type == "aloha": camera_type = "intelrealsense"
camera_type = "intelrealsense"
else:
camera_type = "all"
mock_cameras(request, camera_type)
mock_motors(request)
# To run calibration without user input
monkeypatch = request.getfixturevalue("monkeypatch")
monkeypatch.setattr("builtins.input", mock_input)
# Run test with a real robot. Skip test if robot connection fails.
else: else:
if not is_robot_available(robot_type): camera_type = "all"
pytest.skip(f"A {robot_type} robot is not available.")
return func(*args, **kwargs) mock_cameras(monkeypatch, camera_type)
mock_motors(monkeypatch)
return wrapper # To run calibration without user input
monkeypatch.setattr("builtins.input", mock_input)
def require_camera(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Access the pytest request context to get the mockeypatch fixture
request = kwargs.get("request")
camera_type = kwargs.get("camera_type")
mock = kwargs.get("mock")
if request is None:
raise ValueError("The 'request' fixture must be an argument of the test function.")
if camera_type is None:
raise ValueError("The 'camera_type' must be an argument of the test function.")
if mock is None:
raise ValueError("The 'mock' variable must be an argument of the test function.")
if camera_type not in available_cameras:
raise ValueError(
f"The camera type '{camera_type}' is not valid. Expected one of these '{available_cameras}"
)
# Run test with a monkeypatched version of the robot devices.
if mock:
mock_cameras(request, camera_type)
elif not is_robot_available(robot_type):
# Run test with a real robot. Skip test if robot connection fails. # Run test with a real robot. Skip test if robot connection fails.
else: pytest.skip(f"A {robot_type} robot is not available.")
if not is_camera_available(camera_type):
pytest.skip(f"A {camera_type} camera is not available.")
return func(*args, **kwargs)
return wrapper
def require_motor(func): def mock_camera_or_skip_test_when_not_available(monkeypatch, camera_type, mock):
@wraps(func) if camera_type not in available_cameras:
def wrapper(*args, **kwargs): raise ValueError(
# Access the pytest request context to get the mockeypatch fixture f"The camera type '{camera_type}' is not valid. Expected one of these '{available_cameras}"
request = kwargs.get("request") )
motor_type = kwargs.get("motor_type") if mock:
mock = kwargs.get("mock") # Run test with a monkeypatched version of the cameras
mock_cameras(monkeypatch, camera_type)
elif not is_camera_available(camera_type):
# Run test with a real camera. Skip test if camera connection fails
pytest.skip(f"A {camera_type} camera is not available.")
if request is None:
raise ValueError("The 'request' fixture must be an argument of the test function.")
if motor_type is None:
raise ValueError("The 'motor_type' must be an argument of the test function.")
if mock is None:
raise ValueError("The 'mock' variable must be an argument of the test function.")
if motor_type not in available_motors: def mock_motor_or_skip_test_when_not_available(monkeypatch, motor_type, mock):
raise ValueError( if motor_type not in available_motors:
f"The motor type '{motor_type}' is not valid. Expected one of these '{available_motors}" raise ValueError(
) f"The motor type '{motor_type}' is not valid. Expected one of these '{available_motors}"
)
# Run test with a monkeypatched version of the robot devices. if mock:
if mock: # Run test with a monkeypatched version of the motors
mock_motors(request) mock_motors(monkeypatch)
elif not is_motor_available(motor_type):
# Run test with a real robot. Skip test if robot connection fails. # Run test with a real motor. Skip test if motor connection fails
else: pytest.skip(f"A {motor_type} motor is not available.")
if not is_motor_available(motor_type):
pytest.skip(f"A {motor_type} motor is not available.")
return func(*args, **kwargs)
return wrapper
def mock_input(text=None): def mock_input(text=None):
@@ -320,9 +238,8 @@ def mock_input(text=None):
print(text) print(text)
def mock_cameras(request, camera_type="all"): def mock_cameras(monkeypatch, camera_type="all"):
# TODO(rcadene): Redesign the mocking tests # TODO(rcadene): Redesign the mocking tests
monkeypatch = request.getfixturevalue("monkeypatch")
if camera_type in ["opencv", "all"]: if camera_type in ["opencv", "all"]:
try: try:
@@ -359,9 +276,8 @@ def mock_cameras(request, camera_type="all"):
) )
def mock_motors(request): def mock_motors(monkeypatch):
# TODO(rcadene): Redesign the mocking tests # TODO(rcadene): Redesign the mocking tests
monkeypatch = request.getfixturevalue("monkeypatch")
try: try:
import dynamixel_sdk import dynamixel_sdk
@@ -433,6 +349,8 @@ def is_robot_available(robot_type):
config_path = ROBOT_CONFIG_PATH_TEMPLATE.format(robot=robot_type) config_path = ROBOT_CONFIG_PATH_TEMPLATE.format(robot=robot_type)
robot_cfg = init_hydra_config(config_path) robot_cfg = init_hydra_config(config_path)
robot = make_robot(robot_cfg) robot = make_robot(robot_cfg)
print("DEBUG", robot.leader_arms)
print("DEBUG", robot.cameras)
robot.connect() robot.connect()
del robot del robot
return True return True