mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-20 00:11:59 +00:00
feat(dependencies): minimal default tag install (#3362)
This commit is contained in:
@@ -19,7 +19,7 @@ import torch
|
||||
from safetensors.torch import save_file
|
||||
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
from lerobot.datasets.transforms import (
|
||||
from lerobot.transforms import (
|
||||
ImageTransformConfig,
|
||||
ImageTransforms,
|
||||
ImageTransformsConfig,
|
||||
|
||||
@@ -21,7 +21,7 @@ from safetensors.torch import save_file
|
||||
|
||||
from lerobot.configs.default import DatasetConfig
|
||||
from lerobot.configs.train import TrainPipelineConfig
|
||||
from lerobot.datasets.factory import make_dataset
|
||||
from lerobot.datasets import make_dataset
|
||||
from lerobot.optim.factory import make_optimizer_and_scheduler
|
||||
from lerobot.policies.factory import make_policy, make_policy_config, make_pre_post_processors
|
||||
from lerobot.utils.constants import OBS_STR
|
||||
|
||||
@@ -35,8 +35,10 @@ from concurrent import futures
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
# Skip entire module if grpc is not available
|
||||
# Skip entire module if required deps are not available
|
||||
pytest.importorskip("grpc")
|
||||
pytest.importorskip("serial", reason="pyserial is required (install lerobot[hardware])")
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# End-to-end test
|
||||
|
||||
@@ -16,10 +16,14 @@ import math
|
||||
import pickle
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import pytest
|
||||
|
||||
from lerobot.async_inference.helpers import (
|
||||
pytest.importorskip("grpc")
|
||||
|
||||
import numpy as np # noqa: E402
|
||||
import torch # noqa: E402
|
||||
|
||||
from lerobot.async_inference.helpers import ( # noqa: E402
|
||||
FPSTracker,
|
||||
TimedAction,
|
||||
TimedObservation,
|
||||
|
||||
@@ -24,7 +24,7 @@ import torch
|
||||
|
||||
from lerobot.configs.types import PolicyFeature
|
||||
from lerobot.utils.constants import OBS_STATE
|
||||
from tests.utils import require_package
|
||||
from tests.utils import skip_if_package_missing
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Test fixtures
|
||||
@@ -62,7 +62,7 @@ class MockPolicy:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def policy_server():
|
||||
"""Fresh `PolicyServer` instance with a stubbed-out policy model."""
|
||||
# Import only when the test actually runs (after decorator check)
|
||||
|
||||
@@ -25,8 +25,10 @@ from queue import Queue
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
# Skip entire module if grpc is not available
|
||||
# Skip entire module if required deps are not available
|
||||
pytest.importorskip("grpc")
|
||||
pytest.importorskip("serial", reason="pyserial is required (install lerobot[hardware])")
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Test fixtures
|
||||
|
||||
+21
-6
@@ -17,24 +17,39 @@
|
||||
import traceback
|
||||
|
||||
import pytest
|
||||
from serial import SerialException
|
||||
|
||||
from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature
|
||||
from lerobot.utils.import_utils import is_package_available
|
||||
from tests.utils import DEVICE
|
||||
|
||||
# Import fixture modules as plugins
|
||||
# Import fixture modules as plugins.
|
||||
# Fixtures that depend on optional packages are only registered when those packages are available,
|
||||
# so that tests can be collected and run even with a minimal install.
|
||||
pytest_plugins = [
|
||||
"tests.fixtures.dataset_factories",
|
||||
"tests.fixtures.files",
|
||||
"tests.fixtures.hub",
|
||||
"tests.fixtures.optimizers",
|
||||
]
|
||||
|
||||
if is_package_available("datasets"):
|
||||
pytest_plugins += [
|
||||
"tests.fixtures.dataset_factories",
|
||||
"tests.fixtures.files",
|
||||
"tests.fixtures.hub",
|
||||
]
|
||||
|
||||
|
||||
def pytest_collection_finish():
|
||||
print(f"\nTesting with {DEVICE=}")
|
||||
|
||||
|
||||
def _is_serial_exception(exc: Exception) -> bool:
|
||||
"""Check if an exception is a SerialException without requiring pyserial."""
|
||||
if not is_package_available("pyserial", import_name="serial"):
|
||||
return False
|
||||
from serial import SerialException
|
||||
|
||||
return isinstance(exc, SerialException)
|
||||
|
||||
|
||||
def _check_component_availability(component_type, available_components, make_component):
|
||||
"""Generic helper to check if a hardware component is available"""
|
||||
if component_type not in available_components:
|
||||
@@ -53,7 +68,7 @@ def _check_component_availability(component_type, available_components, make_com
|
||||
|
||||
if isinstance(e, ModuleNotFoundError):
|
||||
print(f"\nInstall module '{e.name}'")
|
||||
elif isinstance(e, SerialException):
|
||||
elif _is_serial_exception(e):
|
||||
print("\nNo physical device detected.")
|
||||
elif isinstance(e, ValueError) and "camera_index" in str(e):
|
||||
print("\nNo physical camera detected.")
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import datasets
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
import datasets # noqa: E402
|
||||
import torch
|
||||
|
||||
from lerobot.datasets.aggregate import aggregate_datasets
|
||||
|
||||
@@ -18,6 +18,8 @@ from unittest.mock import patch
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.compute_stats import (
|
||||
RunningQuantileStats,
|
||||
_assert_type_and_shape,
|
||||
|
||||
@@ -20,6 +20,8 @@ import json
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata
|
||||
from lerobot.datasets.utils import INFO_PATH
|
||||
from tests.fixtures.constants import DEFAULT_FPS, DUMMY_ROBOT_TYPE
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
# limitations under the License.
|
||||
"""Contract tests for DatasetReader."""
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.dataset_reader import DatasetReader
|
||||
from lerobot.datasets.video_utils import get_safe_default_codec
|
||||
from lerobot.utils.import_utils import get_safe_default_codec
|
||||
|
||||
# ── Loading ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.dataset_tools import (
|
||||
add_features,
|
||||
delete_episodes,
|
||||
|
||||
@@ -16,13 +16,16 @@
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from datasets import Dataset
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from datasets import Dataset # noqa: E402
|
||||
from huggingface_hub import DatasetCard
|
||||
|
||||
from lerobot.datasets.feature_utils import combine_feature_dicts
|
||||
from lerobot.datasets.io_utils import hf_transform_to_torch
|
||||
from lerobot.datasets.utils import create_lerobot_dataset_card
|
||||
from lerobot.utils.constants import ACTION, OBS_IMAGES
|
||||
from lerobot.utils.feature_utils import combine_feature_dicts
|
||||
|
||||
|
||||
def calculate_episode_data_index(hf_dataset: Dataset) -> dict[str, torch.Tensor]:
|
||||
|
||||
@@ -23,6 +23,8 @@ import pytest
|
||||
import torch
|
||||
from PIL import Image
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.dataset_writer import _encode_video_worker
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
from lerobot.datasets.utils import DEFAULT_IMAGE_PATH
|
||||
|
||||
@@ -21,21 +21,22 @@ from pathlib import Path
|
||||
import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from huggingface_hub import HfApi
|
||||
from PIL import Image
|
||||
from safetensors.torch import load_file
|
||||
from torchvision.transforms import v2
|
||||
|
||||
import lerobot
|
||||
from lerobot.configs.default import DatasetConfig
|
||||
from lerobot.configs.train import TrainPipelineConfig
|
||||
from lerobot.datasets.factory import make_dataset
|
||||
from lerobot.datasets.feature_utils import get_hf_features_from_features, hw_to_dataset_features
|
||||
from lerobot.datasets import make_dataset
|
||||
from lerobot.datasets.feature_utils import get_hf_features_from_features
|
||||
from lerobot.datasets.image_writer import image_array_to_pil_image
|
||||
from lerobot.datasets.io_utils import hf_transform_to_torch
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
from lerobot.datasets.multi_dataset import MultiLeRobotDataset
|
||||
from lerobot.datasets.transforms import ImageTransforms, ImageTransformsConfig
|
||||
from lerobot.datasets.utils import (
|
||||
DEFAULT_CHUNK_SIZE,
|
||||
DEFAULT_DATA_FILE_SIZE_IN_MB,
|
||||
@@ -46,7 +47,9 @@ from lerobot.datasets.video_utils import VALID_VIDEO_CODECS
|
||||
from lerobot.envs.factory import make_env_config
|
||||
from lerobot.policies.factory import make_policy_config
|
||||
from lerobot.robots import make_robot_from_config
|
||||
from lerobot.transforms import ImageTransforms, ImageTransformsConfig
|
||||
from lerobot.utils.constants import ACTION, DONE, OBS_IMAGES, OBS_STATE, OBS_STR, REWARD
|
||||
from lerobot.utils.feature_utils import hw_to_dataset_features
|
||||
from tests.fixtures.constants import DUMMY_CHW, DUMMY_HWC, DUMMY_REPO_ID
|
||||
from tests.mocks.mock_robot import MockRobotConfig
|
||||
from tests.utils import require_x86_64_kernel
|
||||
@@ -493,13 +496,28 @@ def test_tmp_mixed_deletion(tmp_path, empty_lerobot_dataset_factory):
|
||||
# - [ ] remove old tests
|
||||
|
||||
|
||||
ENV_DATASET_POLICY_TRIPLETS = [
|
||||
("aloha", dataset, "act")
|
||||
for dataset in [
|
||||
"lerobot/aloha_sim_insertion_human",
|
||||
"lerobot/aloha_sim_insertion_scripted",
|
||||
"lerobot/aloha_sim_transfer_cube_human",
|
||||
"lerobot/aloha_sim_transfer_cube_scripted",
|
||||
"lerobot/aloha_sim_insertion_human_image",
|
||||
"lerobot/aloha_sim_insertion_scripted_image",
|
||||
"lerobot/aloha_sim_transfer_cube_human_image",
|
||||
"lerobot/aloha_sim_transfer_cube_scripted_image",
|
||||
]
|
||||
] + [
|
||||
("pusht", dataset, policy)
|
||||
for dataset in ["lerobot/pusht", "lerobot/pusht_image"]
|
||||
for policy in ["diffusion", "vqbet"]
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"env_name, repo_id, policy_name",
|
||||
# Single dataset
|
||||
lerobot.env_dataset_policy_triplets,
|
||||
# Multi-dataset
|
||||
# TODO after fix multidataset
|
||||
# + [("aloha", ["lerobot/aloha_sim_insertion_human", "lerobot/aloha_sim_transfer_cube_human"], "act")],
|
||||
ENV_DATASET_POLICY_TRIPLETS,
|
||||
)
|
||||
def test_factory(env_name, repo_id, policy_name):
|
||||
"""
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
# limitations under the License.
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.feature_utils import (
|
||||
check_delta_timestamps,
|
||||
get_delta_indices,
|
||||
|
||||
@@ -21,7 +21,13 @@ from safetensors.torch import load_file
|
||||
from torchvision.transforms import v2
|
||||
from torchvision.transforms.v2 import functional as F # noqa: N812
|
||||
|
||||
from lerobot.datasets.transforms import (
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.scripts.lerobot_imgtransform_viz import (
|
||||
save_all_transforms,
|
||||
save_each_transform,
|
||||
)
|
||||
from lerobot.transforms import (
|
||||
ImageTransformConfig,
|
||||
ImageTransforms,
|
||||
ImageTransformsConfig,
|
||||
@@ -29,10 +35,6 @@ from lerobot.datasets.transforms import (
|
||||
SharpnessJitter,
|
||||
make_transform_from_config,
|
||||
)
|
||||
from lerobot.scripts.lerobot_imgtransform_viz import (
|
||||
save_all_transforms,
|
||||
save_each_transform,
|
||||
)
|
||||
from lerobot.utils.random_utils import seeded_context
|
||||
from tests.artifacts.image_transforms.save_image_transforms_to_safetensors import ARTIFACT_DIR
|
||||
from tests.utils import require_x86_64_kernel
|
||||
|
||||
@@ -20,6 +20,8 @@ import numpy as np
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.image_writer import (
|
||||
AsyncImageWriter,
|
||||
image_array_to_pil_image,
|
||||
|
||||
@@ -25,6 +25,8 @@ from unittest.mock import Mock
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
import lerobot.datasets.dataset_metadata as dataset_metadata_module
|
||||
import lerobot.datasets.lerobot_dataset as lerobot_dataset_module
|
||||
from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ import logging
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from datasets import Dataset
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from datasets import Dataset # noqa: E402
|
||||
|
||||
from lerobot.datasets.io_utils import (
|
||||
hf_transform_to_torch,
|
||||
|
||||
@@ -17,6 +17,8 @@ import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.streaming_dataset import StreamingLeRobotDataset
|
||||
from lerobot.datasets.utils import safe_shard
|
||||
from lerobot.utils.constants import ACTION
|
||||
|
||||
@@ -20,10 +20,13 @@ import queue
|
||||
import threading
|
||||
from unittest.mock import patch
|
||||
|
||||
import av
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("av", reason="av is required (install lerobot[dataset])")
|
||||
|
||||
import av # noqa: E402
|
||||
|
||||
from lerobot.datasets.video_utils import (
|
||||
VALID_VIDEO_CODECS,
|
||||
StreamingVideoEncoder,
|
||||
|
||||
@@ -23,8 +23,11 @@ These tests verify that:
|
||||
- Subtask handling gracefully handles missing data
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("pandas", reason="pandas is required (install lerobot[dataset])")
|
||||
|
||||
import pandas as pd # noqa: E402
|
||||
import torch
|
||||
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# limitations under the License.
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.scripts.lerobot_dataset_viz import visualize_dataset
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import torch
|
||||
from gymnasium.envs.registration import register, registry as gym_registry
|
||||
from gymnasium.utils.env_checker import check_env
|
||||
|
||||
import lerobot
|
||||
from lerobot.configs.types import PolicyFeature
|
||||
from lerobot.envs.configs import EnvConfig
|
||||
from lerobot.envs.factory import make_env, make_env_config
|
||||
@@ -36,9 +35,16 @@ from tests.utils import require_env
|
||||
|
||||
OBS_TYPES = ["state", "pixels", "pixels_agent_pos"]
|
||||
|
||||
ENV_TASK_PAIRS = [
|
||||
("aloha", "AlohaInsertion-v0"),
|
||||
("aloha", "AlohaTransferCube-v0"),
|
||||
("pusht", "PushT-v0"),
|
||||
]
|
||||
AVAILABLE_ENVS = ["aloha", "pusht"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obs_type", OBS_TYPES)
|
||||
@pytest.mark.parametrize("env_name, env_task", lerobot.env_task_pairs)
|
||||
@pytest.mark.parametrize("env_name, env_task", ENV_TASK_PAIRS)
|
||||
@require_env
|
||||
def test_env(env_name, env_task, obs_type):
|
||||
if env_name == "aloha" and obs_type == "state":
|
||||
@@ -51,7 +57,7 @@ def test_env(env_name, env_task, obs_type):
|
||||
env.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("env_name", lerobot.available_envs)
|
||||
@pytest.mark.parametrize("env_name", AVAILABLE_ENVS)
|
||||
@require_env
|
||||
def test_factory(env_name):
|
||||
cfg = make_env_config(env_name)
|
||||
|
||||
Vendored
+2
-2
@@ -34,12 +34,12 @@ from lerobot.datasets.utils import (
|
||||
DEFAULT_CHUNK_SIZE,
|
||||
DEFAULT_DATA_FILE_SIZE_IN_MB,
|
||||
DEFAULT_DATA_PATH,
|
||||
DEFAULT_FEATURES,
|
||||
DEFAULT_VIDEO_FILE_SIZE_IN_MB,
|
||||
DEFAULT_VIDEO_PATH,
|
||||
flatten_dict,
|
||||
)
|
||||
from lerobot.datasets.video_utils import encode_video_frames
|
||||
from lerobot.utils.constants import DEFAULT_FEATURES
|
||||
from lerobot.utils.utils import flatten_dict
|
||||
from tests.fixtures.constants import (
|
||||
DEFAULT_FPS,
|
||||
DUMMY_CAMERA_FEATURES,
|
||||
|
||||
@@ -21,10 +21,25 @@ import dynamixel_sdk as dxl
|
||||
import serial
|
||||
from mock_serial.mock_serial import MockSerial
|
||||
|
||||
from lerobot.motors.dynamixel.dynamixel import _split_into_byte_chunks
|
||||
|
||||
from .mock_serial_patch import WaitableStub
|
||||
|
||||
|
||||
def _split_into_byte_chunks(value: int, length: int) -> list[int]:
|
||||
"""Split an integer into a list of byte-sized integers (little-endian)."""
|
||||
if length == 1:
|
||||
data = [value]
|
||||
elif length == 2:
|
||||
data = [dxl.DXL_LOBYTE(value), dxl.DXL_HIBYTE(value)]
|
||||
elif length == 4:
|
||||
data = [
|
||||
dxl.DXL_LOBYTE(dxl.DXL_LOWORD(value)),
|
||||
dxl.DXL_HIBYTE(dxl.DXL_LOWORD(value)),
|
||||
dxl.DXL_LOBYTE(dxl.DXL_HIWORD(value)),
|
||||
dxl.DXL_HIBYTE(dxl.DXL_HIWORD(value)),
|
||||
]
|
||||
return data
|
||||
|
||||
|
||||
# https://emanual.robotis.com/docs/en/dxl/crc/
|
||||
DXL_CRC_TABLE = [
|
||||
0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011,
|
||||
|
||||
@@ -21,11 +21,27 @@ import scservo_sdk as scs
|
||||
import serial
|
||||
from mock_serial import MockSerial
|
||||
|
||||
from lerobot.motors.feetech.feetech import _split_into_byte_chunks, patch_setPacketTimeout
|
||||
from lerobot.motors.feetech.feetech import patch_setPacketTimeout
|
||||
|
||||
from .mock_serial_patch import WaitableStub
|
||||
|
||||
|
||||
def _split_into_byte_chunks(value: int, length: int) -> list[int]:
|
||||
"""Split an integer into a list of byte-sized integers (little-endian)."""
|
||||
if length == 1:
|
||||
data = [value]
|
||||
elif length == 2:
|
||||
data = [scs.SCS_LOBYTE(value), scs.SCS_HIBYTE(value)]
|
||||
elif length == 4:
|
||||
data = [
|
||||
scs.SCS_LOBYTE(scs.SCS_LOWORD(value)),
|
||||
scs.SCS_HIBYTE(scs.SCS_LOWORD(value)),
|
||||
scs.SCS_LOBYTE(scs.SCS_HIWORD(value)),
|
||||
scs.SCS_HIBYTE(scs.SCS_HIWORD(value)),
|
||||
]
|
||||
return data
|
||||
|
||||
|
||||
class MockFeetechPacket(abc.ABC):
|
||||
@classmethod
|
||||
def build(cls, scs_id: int, params: list[int], length: int, *args, **kwargs) -> bytes:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
from lerobot.motors.motors_bus import (
|
||||
Motor,
|
||||
MotorsBus,
|
||||
MotorsBusBase,
|
||||
)
|
||||
|
||||
DUMMY_CTRL_TABLE_1 = {
|
||||
@@ -122,6 +123,12 @@ class MockPortHandler:
|
||||
|
||||
|
||||
class MockMotorsBus(MotorsBus):
|
||||
"""Mock motor bus that bypasses hardware dependency checks.
|
||||
|
||||
Inherits from MotorsBus (alias for SerialMotorsBus) for type compatibility,
|
||||
but calls MotorsBusBase.__init__ directly to skip the pyserial/deepdiff guards.
|
||||
"""
|
||||
|
||||
available_baudrates = [500_000, 1_000_000]
|
||||
default_timeout = 1000
|
||||
model_baudrate_table = DUMMY_MODEL_BAUDRATE_TABLE
|
||||
@@ -132,8 +139,13 @@ class MockMotorsBus(MotorsBus):
|
||||
normalized_data = ["Present_Position", "Goal_Position"]
|
||||
|
||||
def __init__(self, port: str, motors: dict[str, Motor]):
|
||||
super().__init__(port, motors)
|
||||
# Skip SerialMotorsBus.__init__ (which guards pyserial/deepdiff)
|
||||
# and call the base class directly — this mock never touches real serial.
|
||||
MotorsBusBase.__init__(self, port, motors)
|
||||
self.port_handler = MockPortHandler(port)
|
||||
self._id_to_model_dict = {m.id: m.model for m in self.motors.values()}
|
||||
self._id_to_name_dict = {m.id: name for name, m in self.motors.items()}
|
||||
self._model_nb_to_model_dict = {v: k for k, v in self.model_number_table.items()}
|
||||
|
||||
def _assert_protocol_is_compatible(self, instruction_name): ...
|
||||
def _handshake(self): ...
|
||||
|
||||
@@ -19,6 +19,8 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("serial", reason="pyserial is required (install lerobot[hardware])")
|
||||
|
||||
from lerobot.motors.motors_bus import (
|
||||
Motor,
|
||||
MotorNormMode,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import pytest
|
||||
import torch
|
||||
from packaging.version import Version
|
||||
from torch.optim.lr_scheduler import LambdaLR
|
||||
@@ -23,8 +24,10 @@ from lerobot.optim.schedulers import (
|
||||
save_scheduler_state,
|
||||
)
|
||||
from lerobot.utils.constants import SCHEDULER_STATE
|
||||
from lerobot.utils.import_utils import is_package_available
|
||||
|
||||
|
||||
@pytest.mark.skipif(not is_package_available("diffusers"), reason="diffusers not installed")
|
||||
def test_diffuser_scheduler(optimizer):
|
||||
config = DiffuserSchedulerConfig(name="cosine", num_warmup_steps=5)
|
||||
scheduler = config.build(optimizer, num_training_steps=100)
|
||||
|
||||
@@ -31,7 +31,7 @@ from lerobot.policies.groot.processor_groot import make_groot_pre_post_processor
|
||||
from lerobot.processor import PolicyProcessorPipeline
|
||||
from lerobot.types import PolicyAction
|
||||
from lerobot.utils.device_utils import auto_select_torch_device
|
||||
from tests.utils import require_cuda # noqa: E402
|
||||
from tests.utils import require_cuda
|
||||
|
||||
pytest.importorskip("transformers")
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from lerobot.configs.types import FeatureType, NormalizationMode, PolicyFeature
|
||||
from lerobot.policies.sac.reward_model.configuration_classifier import RewardClassifierConfig
|
||||
from lerobot.policies.sac.reward_model.modeling_classifier import ClassifierOutput
|
||||
from lerobot.utils.constants import OBS_IMAGE, REWARD
|
||||
from tests.utils import require_package
|
||||
from tests.utils import skip_if_package_missing
|
||||
|
||||
|
||||
def test_classifier_output():
|
||||
@@ -37,7 +37,7 @@ def test_classifier_output():
|
||||
)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@pytest.mark.skip(
|
||||
reason="helper2424/resnet10 needs to be updated to work with the latest version of transformers"
|
||||
)
|
||||
@@ -81,7 +81,7 @@ def test_binary_classifier_with_default_params():
|
||||
assert not torch.isnan(output.hidden_states).any(), "Tensor contains NaN values"
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@pytest.mark.skip(
|
||||
reason="helper2424/resnet10 needs to be updated to work with the latest version of transformers"
|
||||
)
|
||||
@@ -123,7 +123,7 @@ def test_multiclass_classifier():
|
||||
assert not torch.isnan(output.hidden_states).any(), "Tensor contains NaN values"
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@pytest.mark.skip(
|
||||
reason="helper2424/resnet10 needs to be updated to work with the latest version of transformers"
|
||||
)
|
||||
@@ -138,7 +138,7 @@ def test_default_device():
|
||||
assert p.device == torch.device("cpu")
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@pytest.mark.skip(
|
||||
reason="helper2424/resnet10 needs to be updated to work with the latest version of transformers"
|
||||
)
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from lerobot.configs.types import FeatureType, PolicyFeature, RTCAttentionSchedule # noqa: E402
|
||||
from lerobot.policies.factory import make_pre_post_processors # noqa: E402
|
||||
from lerobot.policies.rtc.configuration_rtc import RTCConfig # noqa: E402
|
||||
from lerobot.configs.types import FeatureType, PolicyFeature, RTCAttentionSchedule
|
||||
from lerobot.policies.factory import make_pre_post_processors
|
||||
from lerobot.policies.rtc.configuration_rtc import RTCConfig
|
||||
from lerobot.policies.smolvla.configuration_smolvla import SmolVLAConfig # noqa: F401
|
||||
from lerobot.utils.random_utils import set_seed # noqa: E402
|
||||
from tests.utils import require_cuda, require_package # noqa: E402
|
||||
from lerobot.utils.random_utils import set_seed
|
||||
from tests.utils import require_cuda, skip_if_package_missing
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@require_cuda
|
||||
def test_smolvla_rtc_initialization():
|
||||
from lerobot.policies.smolvla.modeling_smolvla import SmolVLAPolicy # noqa: F401
|
||||
@@ -65,7 +65,7 @@ def test_smolvla_rtc_initialization():
|
||||
print("✓ SmolVLA RTC initialization: Test passed")
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@require_cuda
|
||||
def test_smolvla_rtc_initialization_without_rtc_config():
|
||||
from lerobot.policies.smolvla.modeling_smolvla import SmolVLAPolicy # noqa: F401
|
||||
@@ -87,7 +87,7 @@ def test_smolvla_rtc_initialization_without_rtc_config():
|
||||
print("✓ SmolVLA RTC initialization without RTC config: Test passed")
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@require_cuda
|
||||
@pytest.mark.skipif(True, reason="Requires pretrained SmolVLA model weights")
|
||||
def test_smolvla_rtc_inference_with_prev_chunk():
|
||||
@@ -170,7 +170,7 @@ def test_smolvla_rtc_inference_with_prev_chunk():
|
||||
print("✓ SmolVLA RTC inference with prev_chunk: Test passed")
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@require_cuda
|
||||
@pytest.mark.skipif(True, reason="Requires pretrained SmolVLA model weights")
|
||||
def test_smolvla_rtc_inference_without_prev_chunk():
|
||||
@@ -244,7 +244,7 @@ def test_smolvla_rtc_inference_without_prev_chunk():
|
||||
print("✓ SmolVLA RTC inference without prev_chunk: Test passed")
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@require_cuda
|
||||
@pytest.mark.skipif(True, reason="Requires pretrained SmolVLA model weights")
|
||||
def test_smolvla_rtc_validation_rules():
|
||||
|
||||
@@ -20,16 +20,16 @@ from pathlib import Path
|
||||
import einops
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from packaging import version
|
||||
from safetensors.torch import load_file
|
||||
|
||||
from lerobot import available_policies
|
||||
from lerobot.configs.default import DatasetConfig
|
||||
from lerobot.configs.train import TrainPipelineConfig
|
||||
from lerobot.configs.types import FeatureType, PolicyFeature
|
||||
from lerobot.datasets.factory import make_dataset
|
||||
from lerobot.datasets.feature_utils import dataset_to_policy_features
|
||||
from lerobot.datasets.utils import cycle
|
||||
from lerobot.datasets import make_dataset
|
||||
from lerobot.envs.factory import make_env, make_env_config
|
||||
from lerobot.envs.utils import close_envs, preprocess_observation
|
||||
from lerobot.optim.factory import make_optimizer_and_scheduler
|
||||
@@ -45,10 +45,23 @@ from lerobot.policies.pretrained import PreTrainedPolicy
|
||||
from lerobot.policies.vqbet.configuration_vqbet import VQBeTConfig
|
||||
from lerobot.policies.vqbet.modeling_vqbet import VQBeTHead
|
||||
from lerobot.utils.constants import ACTION, OBS_IMAGES, OBS_STATE
|
||||
from lerobot.utils.feature_utils import dataset_to_policy_features
|
||||
from lerobot.utils.import_utils import is_package_available
|
||||
from lerobot.utils.random_utils import seeded_context
|
||||
from lerobot.utils.utils import cycle
|
||||
from tests.artifacts.policies.save_policy_to_safetensors import get_policy_stats
|
||||
from tests.utils import DEVICE, require_cpu, require_env, require_x86_64_kernel
|
||||
|
||||
# Policies that require optional heavy dependencies to instantiate
|
||||
_POLICY_REQUIRED_PACKAGES: dict[str, tuple[str, ...]] = {
|
||||
"diffusion": ("diffusers",),
|
||||
}
|
||||
|
||||
_ALL_POLICIES = ["act", "diffusion", "tdmpc", "vqbet"]
|
||||
AVAILABLE_POLICIES = [
|
||||
p for p in _ALL_POLICIES if all(is_package_available(pkg) for pkg in _POLICY_REQUIRED_PACKAGES.get(p, ()))
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_dataset_metadata(lerobot_dataset_metadata_factory, info_factory, tmp_path):
|
||||
@@ -84,7 +97,7 @@ def dummy_dataset_metadata(lerobot_dataset_metadata_factory, info_factory, tmp_p
|
||||
return ds_meta
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policy_name", available_policies)
|
||||
@pytest.mark.parametrize("policy_name", AVAILABLE_POLICIES)
|
||||
def test_get_policy_and_config_classes(policy_name: str):
|
||||
"""Check that the correct policy and config classes are returned."""
|
||||
policy_cls = get_policy_class(policy_name)
|
||||
@@ -255,7 +268,7 @@ def test_act_backbone_lr():
|
||||
assert len(optimizer.param_groups[1]["params"]) == 20
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policy_name", available_policies)
|
||||
@pytest.mark.parametrize("policy_name", AVAILABLE_POLICIES)
|
||||
def test_policy_defaults(dummy_dataset_metadata, policy_name: str):
|
||||
"""Check that the policy can be instantiated with defaults."""
|
||||
policy_cls = get_policy_class(policy_name)
|
||||
@@ -268,7 +281,7 @@ def test_policy_defaults(dummy_dataset_metadata, policy_name: str):
|
||||
policy_cls(policy_cfg)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policy_name", available_policies)
|
||||
@pytest.mark.parametrize("policy_name", AVAILABLE_POLICIES)
|
||||
def test_save_and_load_pretrained(dummy_dataset_metadata, tmp_path, policy_name: str):
|
||||
policy_cls = get_policy_class(policy_name)
|
||||
policy_cfg = make_policy_config(policy_name)
|
||||
@@ -343,7 +356,7 @@ def test_multikey_construction(multikey: bool):
|
||||
# to normalize the image at all. In our current codebase we dont normalize at all. But there is still a minor difference
|
||||
# that fails the test. However, by testing to normalize the image with 0.5 0.5 in the current codebase, the test pass.
|
||||
# Thus, we deactivate this test for now.
|
||||
(
|
||||
pytest.param(
|
||||
"lerobot/pusht",
|
||||
"diffusion",
|
||||
{
|
||||
@@ -352,6 +365,7 @@ def test_multikey_construction(multikey: bool):
|
||||
"down_dims": [128, 256, 512],
|
||||
},
|
||||
"",
|
||||
marks=pytest.mark.skipif(not is_package_available("diffusers"), reason="diffusers not installed"),
|
||||
),
|
||||
("lerobot/aloha_sim_insertion_human", "act", {"n_action_steps": 10}, ""),
|
||||
(
|
||||
|
||||
@@ -10,6 +10,8 @@ import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.configs.types import FeatureType, NormalizationMode, PolicyFeature
|
||||
from lerobot.datasets.compute_stats import get_feature_stats
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
|
||||
@@ -25,6 +25,8 @@ import pytest
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature
|
||||
from lerobot.datasets.pipeline_features import aggregate_pipeline_dataset_features
|
||||
from lerobot.processor import (
|
||||
|
||||
@@ -22,14 +22,12 @@ import torch
|
||||
|
||||
from lerobot.configs.types import FeatureType, NormalizationMode, PipelineFeatureType, PolicyFeature
|
||||
from lerobot.policies.smolvla.configuration_smolvla import SmolVLAConfig
|
||||
from lerobot.policies.smolvla.processor_smolvla import (
|
||||
SmolVLANewLineProcessor,
|
||||
make_smolvla_pre_post_processors,
|
||||
)
|
||||
from lerobot.policies.smolvla.processor_smolvla import make_smolvla_pre_post_processors
|
||||
from lerobot.processor import (
|
||||
AddBatchDimensionProcessorStep,
|
||||
DeviceProcessorStep,
|
||||
EnvTransition,
|
||||
NewLineTaskProcessorStep,
|
||||
NormalizerProcessorStep,
|
||||
ProcessorStep,
|
||||
RenameObservationsProcessorStep,
|
||||
@@ -108,7 +106,7 @@ def test_make_smolvla_processor_basic():
|
||||
assert len(preprocessor.steps) == 6
|
||||
assert isinstance(preprocessor.steps[0], RenameObservationsProcessorStep)
|
||||
assert isinstance(preprocessor.steps[1], AddBatchDimensionProcessorStep)
|
||||
assert isinstance(preprocessor.steps[2], SmolVLANewLineProcessor)
|
||||
assert isinstance(preprocessor.steps[2], NewLineTaskProcessorStep)
|
||||
# Step 3 would be TokenizerProcessorStep but it's mocked
|
||||
assert isinstance(preprocessor.steps[4], DeviceProcessorStep)
|
||||
assert isinstance(preprocessor.steps[5], NormalizerProcessorStep)
|
||||
@@ -120,8 +118,8 @@ def test_make_smolvla_processor_basic():
|
||||
|
||||
|
||||
def test_smolvla_newline_processor_single_task():
|
||||
"""Test SmolVLANewLineProcessor with single task string."""
|
||||
processor = SmolVLANewLineProcessor()
|
||||
"""Test NewLineTaskProcessorStep with single task string."""
|
||||
processor = NewLineTaskProcessorStep()
|
||||
|
||||
# Test with task that doesn't have newline
|
||||
transition = create_transition(complementary_data={"task": "test task"})
|
||||
@@ -135,8 +133,8 @@ def test_smolvla_newline_processor_single_task():
|
||||
|
||||
|
||||
def test_smolvla_newline_processor_list_of_tasks():
|
||||
"""Test SmolVLANewLineProcessor with list of task strings."""
|
||||
processor = SmolVLANewLineProcessor()
|
||||
"""Test NewLineTaskProcessorStep with list of task strings."""
|
||||
processor = NewLineTaskProcessorStep()
|
||||
|
||||
# Test with list of tasks
|
||||
tasks = ["task1", "task2\n", "task3"]
|
||||
@@ -147,8 +145,8 @@ def test_smolvla_newline_processor_list_of_tasks():
|
||||
|
||||
|
||||
def test_smolvla_newline_processor_empty_transition():
|
||||
"""Test SmolVLANewLineProcessor with empty transition."""
|
||||
processor = SmolVLANewLineProcessor()
|
||||
"""Test NewLineTaskProcessorStep with empty transition."""
|
||||
processor = NewLineTaskProcessorStep()
|
||||
|
||||
# Test with no complementary_data
|
||||
transition = create_transition()
|
||||
@@ -361,8 +359,8 @@ def test_smolvla_processor_without_stats():
|
||||
|
||||
|
||||
def test_smolvla_newline_processor_state_dict():
|
||||
"""Test SmolVLANewLineProcessor state dict methods."""
|
||||
processor = SmolVLANewLineProcessor()
|
||||
"""Test NewLineTaskProcessorStep state dict methods."""
|
||||
processor = NewLineTaskProcessorStep()
|
||||
|
||||
# Test state_dict (should be empty)
|
||||
state = processor.state_dict()
|
||||
@@ -380,8 +378,8 @@ def test_smolvla_newline_processor_state_dict():
|
||||
|
||||
|
||||
def test_smolvla_newline_processor_transform_features():
|
||||
"""Test SmolVLANewLineProcessor transform_features method."""
|
||||
processor = SmolVLANewLineProcessor()
|
||||
"""Test NewLineTaskProcessorStep transform_features method."""
|
||||
processor = NewLineTaskProcessorStep()
|
||||
|
||||
# Test transform_features
|
||||
features = {
|
||||
|
||||
@@ -36,7 +36,7 @@ from lerobot.utils.constants import (
|
||||
OBS_LANGUAGE_SUBTASK_TOKENS,
|
||||
OBS_STATE,
|
||||
)
|
||||
from tests.utils import require_package
|
||||
from tests.utils import skip_if_package_missing
|
||||
|
||||
|
||||
class MockTokenizer:
|
||||
@@ -94,7 +94,7 @@ def mock_tokenizer():
|
||||
return MockTokenizer(vocab_size=100)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_basic_tokenization(mock_auto_tokenizer):
|
||||
"""Test basic string tokenization functionality."""
|
||||
@@ -129,7 +129,7 @@ def test_basic_tokenization(mock_auto_tokenizer):
|
||||
assert attention_mask.shape == (10,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_basic_tokenization_with_tokenizer_object():
|
||||
"""Test basic string tokenization functionality using tokenizer object directly."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -161,7 +161,7 @@ def test_basic_tokenization_with_tokenizer_object():
|
||||
assert attention_mask.shape == (10,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_list_of_strings_tokenization(mock_auto_tokenizer):
|
||||
"""Test tokenization of a list of strings."""
|
||||
@@ -189,7 +189,7 @@ def test_list_of_strings_tokenization(mock_auto_tokenizer):
|
||||
assert attention_mask.shape == (2, 8)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_tuple_of_strings_tokenization(mock_auto_tokenizer):
|
||||
"""Test tokenization of a tuple of strings (returned by VectorEnv.call())."""
|
||||
@@ -213,7 +213,7 @@ def test_tuple_of_strings_tokenization(mock_auto_tokenizer):
|
||||
assert attention_mask.shape == (2, 8)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_custom_keys(mock_auto_tokenizer):
|
||||
"""Test using custom task_key."""
|
||||
@@ -239,7 +239,7 @@ def test_custom_keys(mock_auto_tokenizer):
|
||||
assert tokens.shape == (5,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_none_complementary_data(mock_auto_tokenizer):
|
||||
"""Test handling of None complementary_data."""
|
||||
@@ -255,7 +255,7 @@ def test_none_complementary_data(mock_auto_tokenizer):
|
||||
processor(transition)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_missing_task_key(mock_auto_tokenizer):
|
||||
"""Test handling when task key is missing."""
|
||||
@@ -270,7 +270,7 @@ def test_missing_task_key(mock_auto_tokenizer):
|
||||
processor(transition)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_none_task_value(mock_auto_tokenizer):
|
||||
"""Test handling when task value is None."""
|
||||
@@ -285,7 +285,7 @@ def test_none_task_value(mock_auto_tokenizer):
|
||||
processor(transition)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_unsupported_task_type(mock_auto_tokenizer):
|
||||
"""Test handling of unsupported task types."""
|
||||
@@ -307,14 +307,14 @@ def test_unsupported_task_type(mock_auto_tokenizer):
|
||||
processor(transition)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_no_tokenizer_error():
|
||||
"""Test that ValueError is raised when neither tokenizer nor tokenizer_name is provided."""
|
||||
with pytest.raises(ValueError, match="Either 'tokenizer' or 'tokenizer_name' must be provided"):
|
||||
TokenizerProcessorStep()
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_invalid_tokenizer_name_error():
|
||||
"""Test that error is raised when invalid tokenizer_name is provided."""
|
||||
with patch("lerobot.processor.tokenizer_processor.AutoTokenizer") as mock_auto_tokenizer:
|
||||
@@ -325,7 +325,7 @@ def test_invalid_tokenizer_name_error():
|
||||
TokenizerProcessorStep(tokenizer_name="invalid-tokenizer")
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_get_config_with_tokenizer_name(mock_auto_tokenizer):
|
||||
"""Test configuration serialization when using tokenizer_name."""
|
||||
@@ -354,7 +354,7 @@ def test_get_config_with_tokenizer_name(mock_auto_tokenizer):
|
||||
assert config == expected
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_config_with_tokenizer_object():
|
||||
"""Test configuration serialization when using tokenizer object."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -382,7 +382,7 @@ def test_get_config_with_tokenizer_object():
|
||||
assert "tokenizer_name" not in config
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_state_dict_methods(mock_auto_tokenizer):
|
||||
"""Test state_dict and load_state_dict methods."""
|
||||
@@ -399,7 +399,7 @@ def test_state_dict_methods(mock_auto_tokenizer):
|
||||
processor.load_state_dict({})
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_reset_method(mock_auto_tokenizer):
|
||||
"""Test reset method."""
|
||||
@@ -412,7 +412,7 @@ def test_reset_method(mock_auto_tokenizer):
|
||||
processor.reset()
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_integration_with_robot_processor(mock_auto_tokenizer):
|
||||
"""Test integration with RobotProcessor."""
|
||||
@@ -449,7 +449,7 @@ def test_integration_with_robot_processor(mock_auto_tokenizer):
|
||||
assert torch.equal(result[TransitionKey.ACTION], transition[TransitionKey.ACTION])
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_save_and_load_pretrained_with_tokenizer_name(mock_auto_tokenizer):
|
||||
"""Test saving and loading processor with tokenizer_name."""
|
||||
@@ -489,7 +489,7 @@ def test_save_and_load_pretrained_with_tokenizer_name(mock_auto_tokenizer):
|
||||
assert f"{OBS_LANGUAGE}.attention_mask" in result[TransitionKey.OBSERVATION]
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_save_and_load_pretrained_with_tokenizer_object():
|
||||
"""Test saving and loading processor with tokenizer object using overrides."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -528,7 +528,7 @@ def test_save_and_load_pretrained_with_tokenizer_object():
|
||||
assert f"{OBS_LANGUAGE}.attention_mask" in result[TransitionKey.OBSERVATION]
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_registry_functionality():
|
||||
"""Test that the processor is properly registered."""
|
||||
from lerobot.processor import ProcessorStepRegistry
|
||||
@@ -541,7 +541,7 @@ def test_registry_functionality():
|
||||
assert retrieved_class is TokenizerProcessorStep
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_features_basic():
|
||||
"""Test basic feature contract functionality."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -574,7 +574,7 @@ def test_features_basic():
|
||||
assert attention_mask_feature.shape == (128,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_features_with_custom_max_length():
|
||||
"""Test feature contract with custom max_length."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -596,7 +596,7 @@ def test_features_with_custom_max_length():
|
||||
assert attention_mask_feature.shape == (64,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_features_existing_features():
|
||||
"""Test feature contract when tokenized features already exist."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -618,7 +618,7 @@ def test_features_existing_features():
|
||||
assert output_features[PipelineFeatureType.OBSERVATION][f"{OBS_LANGUAGE}.attention_mask"].shape == (100,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_tokenization_parameters(mock_auto_tokenizer):
|
||||
"""Test that tokenization parameters are correctly passed to tokenizer."""
|
||||
@@ -666,7 +666,7 @@ def test_tokenization_parameters(mock_auto_tokenizer):
|
||||
assert tracking_tokenizer.last_call_kwargs["return_tensors"] == "pt"
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_preserves_other_complementary_data(mock_auto_tokenizer):
|
||||
"""Test that other complementary data fields are preserved."""
|
||||
@@ -701,7 +701,7 @@ def test_preserves_other_complementary_data(mock_auto_tokenizer):
|
||||
assert f"{OBS_LANGUAGE}.attention_mask" in observation
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_deterministic_tokenization(mock_auto_tokenizer):
|
||||
"""Test that tokenization is deterministic for the same input."""
|
||||
@@ -729,7 +729,7 @@ def test_deterministic_tokenization(mock_auto_tokenizer):
|
||||
assert torch.equal(attention_mask1, attention_mask2)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_empty_string_task(mock_auto_tokenizer):
|
||||
"""Test handling of empty string task."""
|
||||
@@ -753,7 +753,7 @@ def test_empty_string_task(mock_auto_tokenizer):
|
||||
assert tokens.shape == (8,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_very_long_task(mock_auto_tokenizer):
|
||||
"""Test handling of very long task strings."""
|
||||
@@ -779,7 +779,7 @@ def test_very_long_task(mock_auto_tokenizer):
|
||||
assert attention_mask.shape == (5,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_custom_padding_side(mock_auto_tokenizer):
|
||||
"""Test using custom padding_side parameter."""
|
||||
@@ -833,7 +833,7 @@ def test_custom_padding_side(mock_auto_tokenizer):
|
||||
assert tracking_tokenizer.padding_side_calls[-1] == "right"
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_device_detection_cpu():
|
||||
"""Test that tokenized tensors stay on CPU when other tensors are on CPU."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -857,7 +857,7 @@ def test_device_detection_cpu():
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_device_detection_cuda():
|
||||
"""Test that tokenized tensors are moved to CUDA when other tensors are on CUDA."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -882,7 +882,7 @@ def test_device_detection_cuda():
|
||||
|
||||
|
||||
@pytest.mark.skipif(torch.cuda.device_count() < 2, reason="Requires at least 2 GPUs")
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_device_detection_multi_gpu():
|
||||
"""Test that tokenized tensors match device in multi-GPU setup."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -906,7 +906,7 @@ def test_device_detection_multi_gpu():
|
||||
assert attention_mask.device == device
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_device_detection_no_tensors():
|
||||
"""Test that tokenized tensors stay on CPU when no other tensors exist."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -928,7 +928,7 @@ def test_device_detection_no_tensors():
|
||||
assert attention_mask.device.type == "cpu"
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_device_detection_mixed_devices():
|
||||
"""Test device detection when tensors are on different devices (uses first found)."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -956,7 +956,7 @@ def test_device_detection_mixed_devices():
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_device_detection_from_action():
|
||||
"""Test that device is detected from action tensor when no observation tensors exist."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -979,7 +979,7 @@ def test_device_detection_from_action():
|
||||
assert attention_mask.device.type == "cuda"
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_device_detection_preserves_dtype():
|
||||
"""Test that device detection doesn't affect dtype of tokenized tensors."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1000,7 +1000,7 @@ def test_device_detection_preserves_dtype():
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_integration_with_device_processor(mock_auto_tokenizer):
|
||||
"""Test that TokenizerProcessorStep works correctly with DeviceProcessorStep in pipeline."""
|
||||
@@ -1039,7 +1039,7 @@ def test_integration_with_device_processor(mock_auto_tokenizer):
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_simulated_accelerate_scenario():
|
||||
"""Test scenario simulating Accelerate with data already on GPU."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1077,7 +1077,7 @@ def test_simulated_accelerate_scenario():
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_missing_key():
|
||||
"""Test get_subtask returns None when subtask key is missing from complementary_data."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1093,7 +1093,7 @@ def test_get_subtask_missing_key():
|
||||
assert result is None
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_none_value():
|
||||
"""Test get_subtask returns None when subtask value is None."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1109,7 +1109,7 @@ def test_get_subtask_none_value():
|
||||
assert result is None
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_none_complementary_data():
|
||||
"""Test get_subtask returns None when complementary_data is None."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1125,7 +1125,7 @@ def test_get_subtask_none_complementary_data():
|
||||
assert result is None
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_string():
|
||||
"""Test get_subtask returns list with single string when subtask is a string."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1143,7 +1143,7 @@ def test_get_subtask_string():
|
||||
assert len(result) == 1
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_list_of_strings():
|
||||
"""Test get_subtask returns the list when subtask is already a list of strings."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1162,7 +1162,7 @@ def test_get_subtask_list_of_strings():
|
||||
assert len(result) == 3
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_unsupported_type_integer():
|
||||
"""Test get_subtask returns None when subtask is an unsupported type (integer)."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1178,7 +1178,7 @@ def test_get_subtask_unsupported_type_integer():
|
||||
assert result is None
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_unsupported_type_mixed_list():
|
||||
"""Test get_subtask returns None when subtask is a list with mixed types."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1194,7 +1194,7 @@ def test_get_subtask_unsupported_type_mixed_list():
|
||||
assert result is None
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_unsupported_type_dict():
|
||||
"""Test get_subtask returns None when subtask is a dictionary."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1210,7 +1210,7 @@ def test_get_subtask_unsupported_type_dict():
|
||||
assert result is None
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_empty_string():
|
||||
"""Test get_subtask with empty string returns list with empty string."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1226,7 +1226,7 @@ def test_get_subtask_empty_string():
|
||||
assert result == [""]
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_get_subtask_empty_list():
|
||||
"""Test get_subtask with empty list returns empty list."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1247,7 +1247,7 @@ def test_get_subtask_empty_list():
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_tokenization_when_present():
|
||||
"""Test that subtask is tokenized and added to observation when present."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1276,7 +1276,7 @@ def test_subtask_tokenization_when_present():
|
||||
assert subtask_attention_mask.dtype == torch.bool
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_tokenization_not_added_when_none():
|
||||
"""Test that subtask tokens are NOT added to observation when subtask is None."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1300,7 +1300,7 @@ def test_subtask_tokenization_not_added_when_none():
|
||||
assert f"{OBS_LANGUAGE}.attention_mask" in observation
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_tokenization_not_added_when_subtask_value_is_none():
|
||||
"""Test that subtask tokens are NOT added when subtask value is explicitly None."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1320,7 +1320,7 @@ def test_subtask_tokenization_not_added_when_subtask_value_is_none():
|
||||
assert OBS_LANGUAGE_SUBTASK_ATTENTION_MASK not in observation
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_tokenization_list_of_strings():
|
||||
"""Test subtask tokenization with list of strings."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1346,7 +1346,7 @@ def test_subtask_tokenization_list_of_strings():
|
||||
assert subtask_attention_mask.shape == (2, 8)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_tokenization_device_cpu():
|
||||
"""Test that subtask tokens are on CPU when other tensors are on CPU."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1372,7 +1372,7 @@ def test_subtask_tokenization_device_cpu():
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_tokenization_device_cuda():
|
||||
"""Test that subtask tokens are moved to CUDA when other tensors are on CUDA."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1397,7 +1397,7 @@ def test_subtask_tokenization_device_cuda():
|
||||
assert subtask_attention_mask.device.type == "cuda"
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_tokenization_preserves_other_observation_data():
|
||||
"""Test that subtask tokenization preserves other observation data."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1423,7 +1423,7 @@ def test_subtask_tokenization_preserves_other_observation_data():
|
||||
assert OBS_LANGUAGE_SUBTASK_ATTENTION_MASK in observation
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_attention_mask_dtype():
|
||||
"""Test that subtask attention mask has correct dtype (bool)."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1442,7 +1442,7 @@ def test_subtask_attention_mask_dtype():
|
||||
assert subtask_attention_mask.dtype == torch.bool
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_tokenization_deterministic():
|
||||
"""Test that subtask tokenization is deterministic for the same input."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
@@ -1467,7 +1467,7 @@ def test_subtask_tokenization_deterministic():
|
||||
assert torch.equal(subtask_mask1, subtask_mask2)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
|
||||
def test_subtask_tokenization_integration_with_pipeline(mock_auto_tokenizer):
|
||||
"""Test subtask tokenization works correctly with DataProcessorPipeline."""
|
||||
@@ -1504,7 +1504,7 @@ def test_subtask_tokenization_integration_with_pipeline(mock_auto_tokenizer):
|
||||
assert observation[OBS_LANGUAGE_SUBTASK_TOKENS].shape == (6,)
|
||||
|
||||
|
||||
@require_package("transformers")
|
||||
@skip_if_package_missing("transformers")
|
||||
def test_subtask_not_added_for_unsupported_types():
|
||||
"""Test that subtask tokens are not added when subtask has unsupported type."""
|
||||
mock_tokenizer = MockTokenizer(vocab_size=100)
|
||||
|
||||
@@ -19,11 +19,14 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from torch.multiprocessing import Event, Queue
|
||||
|
||||
from lerobot.utils.constants import OBS_STR
|
||||
from lerobot.utils.transition import Transition
|
||||
from tests.utils import require_package
|
||||
from tests.utils import skip_if_package_missing
|
||||
|
||||
|
||||
def create_learner_service_stub():
|
||||
@@ -64,7 +67,7 @@ def close_service_stub(channel, server):
|
||||
server.stop(None)
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_establish_learner_connection_success():
|
||||
from lerobot.rl.actor import establish_learner_connection
|
||||
|
||||
@@ -81,7 +84,7 @@ def test_establish_learner_connection_success():
|
||||
close_service_stub(channel, server)
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_establish_learner_connection_failure():
|
||||
from lerobot.rl.actor import establish_learner_connection
|
||||
|
||||
@@ -100,7 +103,7 @@ def test_establish_learner_connection_failure():
|
||||
close_service_stub(channel, server)
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_push_transitions_to_transport_queue():
|
||||
from lerobot.rl.actor import push_transitions_to_transport_queue
|
||||
from lerobot.transport.utils import bytes_to_transitions
|
||||
@@ -135,7 +138,7 @@ def test_push_transitions_to_transport_queue():
|
||||
assert_transitions_equal(deserialized_transition, transitions[i])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_transitions_stream():
|
||||
from lerobot.rl.actor import transitions_stream
|
||||
@@ -167,7 +170,7 @@ def test_transitions_stream():
|
||||
assert streamed_data[2].data == b"transition_data_3"
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_interactions_stream():
|
||||
from lerobot.rl.actor import interactions_stream
|
||||
|
||||
@@ -20,13 +20,16 @@ import time
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from torch.multiprocessing import Event, Queue
|
||||
|
||||
from lerobot.configs.train import TrainRLServerPipelineConfig
|
||||
from lerobot.policies.sac.configuration_sac import SACConfig
|
||||
from lerobot.utils.constants import OBS_STR
|
||||
from lerobot.utils.transition import Transition
|
||||
from tests.utils import require_package
|
||||
from tests.utils import skip_if_package_missing
|
||||
|
||||
|
||||
def create_test_transitions(count: int = 3) -> list[Transition]:
|
||||
@@ -88,7 +91,7 @@ def cfg():
|
||||
return cfg
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(10) # force cross-platform watchdog
|
||||
def test_end_to_end_transitions_flow(cfg):
|
||||
from lerobot.rl.actor import (
|
||||
@@ -150,7 +153,7 @@ def test_end_to_end_transitions_flow(cfg):
|
||||
assert_transitions_equal(transition, input_transitions[i])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(10)
|
||||
def test_end_to_end_interactions_flow(cfg):
|
||||
from lerobot.rl.actor import (
|
||||
@@ -223,7 +226,7 @@ def test_end_to_end_interactions_flow(cfg):
|
||||
assert received == expected
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.parametrize("data_size", ["small", "large"])
|
||||
@pytest.mark.timeout(10)
|
||||
def test_end_to_end_parameters_flow(cfg, data_size):
|
||||
|
||||
@@ -20,7 +20,7 @@ from multiprocessing import Event, Queue
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.utils import require_package # our gRPC servicer class
|
||||
from tests.utils import skip_if_package_missing # our gRPC servicer class
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@@ -39,7 +39,7 @@ def learner_service_stub():
|
||||
close_learner_service_stub(channel, server)
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def create_learner_service_stub(
|
||||
shutdown_event: Event,
|
||||
parameters_queue: Queue,
|
||||
@@ -75,7 +75,7 @@ def create_learner_service_stub(
|
||||
return services_pb2_grpc.LearnerServiceStub(channel), channel, server
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def close_learner_service_stub(channel, server):
|
||||
channel.close()
|
||||
server.stop(None)
|
||||
@@ -91,7 +91,7 @@ def test_ready_method(learner_service_stub):
|
||||
assert response == services_pb2.Empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_send_interactions():
|
||||
from lerobot.transport import services_pb2
|
||||
@@ -135,7 +135,7 @@ def test_send_interactions():
|
||||
assert interactions == [b"123", b"4", b"5", b"678"]
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_send_transitions():
|
||||
from lerobot.transport import services_pb2
|
||||
@@ -181,7 +181,7 @@ def test_send_transitions():
|
||||
assert transitions == [b"transition_1transition_2transition_3", b"batch_1batch_2"]
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_send_transitions_empty_stream():
|
||||
from lerobot.transport import services_pb2
|
||||
@@ -209,7 +209,7 @@ def test_send_transitions_empty_stream():
|
||||
assert transitions_queue.empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(10) # force cross-platform watchdog
|
||||
def test_stream_parameters():
|
||||
import time
|
||||
@@ -267,7 +267,7 @@ def test_stream_parameters():
|
||||
assert time_diff == pytest.approx(seconds_between_pushes, abs=0.1)
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_stream_parameters_with_shutdown():
|
||||
from lerobot.transport import services_pb2
|
||||
@@ -319,7 +319,7 @@ def test_stream_parameters_with_shutdown():
|
||||
assert received_params == [b"param_batch_1", b"stop"]
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
@pytest.mark.timeout(3) # force cross-platform watchdog
|
||||
def test_stream_parameters_waits_and_retries_on_empty_queue():
|
||||
import threading
|
||||
|
||||
@@ -18,9 +18,13 @@ import threading
|
||||
import time
|
||||
from queue import Queue
|
||||
|
||||
from torch.multiprocessing import Queue as TorchMPQueue
|
||||
import pytest
|
||||
|
||||
from lerobot.rl.queue import get_last_item_from_queue
|
||||
pytest.importorskip("grpc")
|
||||
|
||||
from torch.multiprocessing import Queue as TorchMPQueue # noqa: E402
|
||||
|
||||
from lerobot.rl.queue import get_last_item_from_queue # noqa: E402
|
||||
|
||||
|
||||
def test_get_last_item_single_item():
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import draccus
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.scripts.lerobot_edit_dataset import (
|
||||
ConvertImageToVideoConfig,
|
||||
DeleteEpisodesConfig,
|
||||
|
||||
+37
-35
@@ -13,48 +13,50 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import importlib
|
||||
|
||||
import gymnasium as gym
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import lerobot
|
||||
from lerobot.policies.act.modeling_act import ACTPolicy
|
||||
from lerobot.policies.diffusion.modeling_diffusion import DiffusionPolicy
|
||||
from lerobot.policies.tdmpc.modeling_tdmpc import TDMPCPolicy
|
||||
from lerobot.policies.vqbet.modeling_vqbet import VQBeTPolicy
|
||||
from tests.utils import require_env
|
||||
from lerobot.utils.import_utils import _require_package_cache, require_package
|
||||
|
||||
|
||||
@pytest.mark.parametrize("env_name, task_name", lerobot.env_task_pairs)
|
||||
@require_env
|
||||
def test_available_env_task(env_name: str, task_name: list):
|
||||
"""
|
||||
This test verifies that all environments listed in `lerobot/__init__.py` can
|
||||
be successfully imported — if they're installed — and that their
|
||||
`available_tasks_per_env` are valid.
|
||||
"""
|
||||
package_name = f"gym_{env_name}"
|
||||
importlib.import_module(package_name)
|
||||
gym_handle = f"{package_name}/{task_name}"
|
||||
assert gym_handle in gym.envs.registry, gym_handle
|
||||
def test_version():
|
||||
"""Verify the package exposes a version string."""
|
||||
assert isinstance(lerobot.__version__, str)
|
||||
assert len(lerobot.__version__) > 0
|
||||
|
||||
|
||||
def test_available_policies():
|
||||
"""
|
||||
This test verifies that the class attribute `name` for all policies is
|
||||
consistent with those listed in `lerobot/__init__.py`.
|
||||
"""
|
||||
policy_classes = [ACTPolicy, DiffusionPolicy, TDMPCPolicy, VQBeTPolicy]
|
||||
policies = [pol_cls.name for pol_cls in policy_classes]
|
||||
assert set(policies) == set(lerobot.available_policies), policies
|
||||
def test_require_package_raises_when_missing():
|
||||
"""require_package raises ImportError with install instructions when a package is missing."""
|
||||
with patch("lerobot.utils.import_utils.is_package_available", return_value=False):
|
||||
# Clear the cache so the mock takes effect
|
||||
_require_package_cache.clear()
|
||||
try:
|
||||
with pytest.raises(ImportError, match=r"pip install 'lerobot\[dataset\]'"):
|
||||
require_package("datasets", extra="dataset")
|
||||
finally:
|
||||
_require_package_cache.clear()
|
||||
|
||||
|
||||
def test_print():
|
||||
print(lerobot.available_envs)
|
||||
print(lerobot.available_tasks_per_env)
|
||||
print(lerobot.available_datasets)
|
||||
print(lerobot.available_datasets_per_env)
|
||||
print(lerobot.available_real_world_datasets)
|
||||
print(lerobot.available_policies)
|
||||
print(lerobot.available_policies_per_env)
|
||||
def test_require_package_passes_when_available():
|
||||
"""require_package does not raise when the package is installed."""
|
||||
with patch("lerobot.utils.import_utils.is_package_available", return_value=True):
|
||||
_require_package_cache.clear()
|
||||
try:
|
||||
# Should not raise
|
||||
require_package("datasets", extra="dataset")
|
||||
finally:
|
||||
_require_package_cache.clear()
|
||||
|
||||
|
||||
def test_require_package_error_message_includes_uv():
|
||||
"""Error message includes both pip and uv install commands."""
|
||||
with patch("lerobot.utils.import_utils.is_package_available", return_value=False):
|
||||
_require_package_cache.clear()
|
||||
try:
|
||||
with pytest.raises(ImportError, match=r"uv pip install"):
|
||||
require_package("grpcio", extra="async", import_name="grpc")
|
||||
finally:
|
||||
_require_package_cache.clear()
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from safetensors.torch import load_file
|
||||
|
||||
from .utils import require_package
|
||||
from .utils import skip_if_package_missing
|
||||
|
||||
# Skip this entire module in CI
|
||||
pytestmark = pytest.mark.skipif(
|
||||
@@ -37,7 +37,7 @@ def resolve_model_id_for_peft_training(policy_type):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policy_type", ["smolvla"])
|
||||
@require_package("peft")
|
||||
@skip_if_package_missing("peft")
|
||||
def test_peft_training_push_to_hub_works(policy_type, tmp_path):
|
||||
"""Ensure that push to hub stores PEFT only the adapter, not the full model weights."""
|
||||
output_dir = tmp_path / f"output_{policy_type}"
|
||||
@@ -76,7 +76,7 @@ def test_peft_training_push_to_hub_works(policy_type, tmp_path):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policy_type", ["smolvla"])
|
||||
@require_package("peft")
|
||||
@skip_if_package_missing("peft")
|
||||
def test_peft_training_works(policy_type, tmp_path):
|
||||
"""Check whether the standard case of fine-tuning a (partially) pre-trained policy with PEFT works."""
|
||||
output_dir = tmp_path / f"output_{policy_type}"
|
||||
@@ -125,7 +125,7 @@ def test_peft_training_works(policy_type, tmp_path):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policy_type", ["smolvla"])
|
||||
@require_package("peft")
|
||||
@skip_if_package_missing("peft")
|
||||
def test_peft_training_params_are_fewer(policy_type, tmp_path):
|
||||
"""Check whether the standard case of fine-tuning a (partially) pre-trained policy with PEFT works."""
|
||||
output_dir = tmp_path / f"output_{policy_type}"
|
||||
@@ -176,7 +176,7 @@ def dummy_make_robot_from_config(*args, **kwargs):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policy_type", ["smolvla"])
|
||||
@require_package("peft")
|
||||
@skip_if_package_missing("peft")
|
||||
def test_peft_record_loads_policy(policy_type, tmp_path):
|
||||
"""Train a policy with PEFT and attempt to load it with `lerobot-record`."""
|
||||
from peft import PeftModel
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
pytest.importorskip("deepdiff", reason="deepdiff is required (install lerobot[hardware])")
|
||||
|
||||
from lerobot.scripts.lerobot_calibrate import CalibrateConfig, calibrate
|
||||
from lerobot.scripts.lerobot_record import DatasetRecordConfig, RecordConfig, record
|
||||
from lerobot.scripts.lerobot_replay import DatasetReplayConfig, ReplayConfig, replay
|
||||
|
||||
@@ -33,6 +33,8 @@ from pathlib import Path
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ from pathlib import Path
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
from lerobot.configs.default import DatasetConfig
|
||||
from lerobot.configs.policies import PreTrainedConfig
|
||||
from lerobot.configs.train import TrainPipelineConfig
|
||||
|
||||
@@ -23,10 +23,10 @@ import torch
|
||||
|
||||
from lerobot.utils.constants import ACTION
|
||||
from lerobot.utils.transition import Transition
|
||||
from tests.utils import require_cuda, require_package
|
||||
from tests.utils import require_cuda, skip_if_package_missing
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_bytes_buffer_size_empty_buffer():
|
||||
from lerobot.transport.utils import bytes_buffer_size
|
||||
|
||||
@@ -37,7 +37,7 @@ def test_bytes_buffer_size_empty_buffer():
|
||||
assert buffer.tell() == 0
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_bytes_buffer_size_small_buffer():
|
||||
from lerobot.transport.utils import bytes_buffer_size
|
||||
|
||||
@@ -47,7 +47,7 @@ def test_bytes_buffer_size_small_buffer():
|
||||
assert buffer.tell() == 0
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_bytes_buffer_size_large_buffer():
|
||||
from lerobot.transport.utils import CHUNK_SIZE, bytes_buffer_size
|
||||
|
||||
@@ -58,7 +58,7 @@ def test_bytes_buffer_size_large_buffer():
|
||||
assert buffer.tell() == 0
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_send_bytes_in_chunks_empty_data():
|
||||
from lerobot.transport.utils import send_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -68,7 +68,7 @@ def test_send_bytes_in_chunks_empty_data():
|
||||
assert len(chunks) == 0
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_single_chunk_small_data():
|
||||
from lerobot.transport.utils import send_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -82,7 +82,7 @@ def test_single_chunk_small_data():
|
||||
assert chunks[0].transfer_state == services_pb2.TransferState.TRANSFER_END
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_not_silent_mode():
|
||||
from lerobot.transport.utils import send_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -94,7 +94,7 @@ def test_not_silent_mode():
|
||||
assert chunks[0].data == b"Some data"
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_send_bytes_in_chunks_large_data():
|
||||
from lerobot.transport.utils import CHUNK_SIZE, send_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -111,7 +111,7 @@ def test_send_bytes_in_chunks_large_data():
|
||||
assert chunks[2].transfer_state == services_pb2.TransferState.TRANSFER_END
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_send_bytes_in_chunks_large_data_with_exact_chunk_size():
|
||||
from lerobot.transport.utils import CHUNK_SIZE, send_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -124,7 +124,7 @@ def test_send_bytes_in_chunks_large_data_with_exact_chunk_size():
|
||||
assert chunks[0].transfer_state == services_pb2.TransferState.TRANSFER_END
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_empty_data():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks
|
||||
|
||||
@@ -138,7 +138,7 @@ def test_receive_bytes_in_chunks_empty_data():
|
||||
assert queue.empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_single_chunk():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -157,7 +157,7 @@ def test_receive_bytes_in_chunks_single_chunk():
|
||||
assert queue.empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_single_not_end_chunk():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -175,7 +175,7 @@ def test_receive_bytes_in_chunks_single_not_end_chunk():
|
||||
assert queue.empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_multiple_chunks():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -199,7 +199,7 @@ def test_receive_bytes_in_chunks_multiple_chunks():
|
||||
assert queue.empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_multiple_messages():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -235,7 +235,7 @@ def test_receive_bytes_in_chunks_multiple_messages():
|
||||
assert queue.empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_shutdown_during_receive():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -259,7 +259,7 @@ def test_receive_bytes_in_chunks_shutdown_during_receive():
|
||||
assert queue.empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_only_begin_chunk():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -279,7 +279,7 @@ def test_receive_bytes_in_chunks_only_begin_chunk():
|
||||
assert queue.empty()
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_missing_begin():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
|
||||
|
||||
@@ -303,7 +303,7 @@ def test_receive_bytes_in_chunks_missing_begin():
|
||||
|
||||
|
||||
# Tests for state_to_bytes and bytes_to_state_dict
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_state_to_bytes_empty_dict():
|
||||
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
|
||||
|
||||
@@ -314,7 +314,7 @@ def test_state_to_bytes_empty_dict():
|
||||
assert reconstructed == state_dict
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_bytes_to_state_dict_empty_data():
|
||||
from lerobot.transport.utils import bytes_to_state_dict
|
||||
|
||||
@@ -323,7 +323,7 @@ def test_bytes_to_state_dict_empty_data():
|
||||
bytes_to_state_dict(b"")
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_state_to_bytes_simple_dict():
|
||||
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
|
||||
|
||||
@@ -347,7 +347,7 @@ def test_state_to_bytes_simple_dict():
|
||||
assert torch.allclose(state_dict[key], reconstructed[key])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_state_to_bytes_various_dtypes():
|
||||
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
|
||||
|
||||
@@ -372,7 +372,7 @@ def test_state_to_bytes_various_dtypes():
|
||||
assert torch.allclose(state_dict[key], reconstructed[key])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_bytes_to_state_dict_invalid_data():
|
||||
from lerobot.transport.utils import bytes_to_state_dict
|
||||
|
||||
@@ -382,7 +382,7 @@ def test_bytes_to_state_dict_invalid_data():
|
||||
|
||||
|
||||
@require_cuda
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_state_to_bytes_various_dtypes_cuda():
|
||||
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
|
||||
|
||||
@@ -407,7 +407,7 @@ def test_state_to_bytes_various_dtypes_cuda():
|
||||
assert torch.allclose(state_dict[key], reconstructed[key])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_python_object_to_bytes_none():
|
||||
from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes
|
||||
|
||||
@@ -439,7 +439,7 @@ def test_python_object_to_bytes_none():
|
||||
(1, 2, 3),
|
||||
],
|
||||
)
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_python_object_to_bytes_simple_types(obj):
|
||||
from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes
|
||||
|
||||
@@ -450,7 +450,7 @@ def test_python_object_to_bytes_simple_types(obj):
|
||||
assert type(reconstructed) is type(obj)
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_python_object_to_bytes_with_tensors():
|
||||
from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes
|
||||
|
||||
@@ -475,7 +475,7 @@ def test_python_object_to_bytes_with_tensors():
|
||||
assert torch.equal(obj["nested"]["tensor2"], reconstructed["nested"]["tensor2"])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_transitions_to_bytes_empty_list():
|
||||
from lerobot.transport.utils import bytes_to_transitions, transitions_to_bytes
|
||||
|
||||
@@ -487,7 +487,7 @@ def test_transitions_to_bytes_empty_list():
|
||||
assert isinstance(reconstructed, list)
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_transitions_to_bytes_single_transition():
|
||||
from lerobot.transport.utils import bytes_to_transitions, transitions_to_bytes
|
||||
|
||||
@@ -509,7 +509,7 @@ def test_transitions_to_bytes_single_transition():
|
||||
assert_transitions_equal(transitions[0], reconstructed[0])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def assert_transitions_equal(t1: Transition, t2: Transition):
|
||||
"""Helper to assert two transitions are equal."""
|
||||
assert_observation_equal(t1["state"], t2["state"])
|
||||
@@ -519,7 +519,7 @@ def assert_transitions_equal(t1: Transition, t2: Transition):
|
||||
assert_observation_equal(t1["next_state"], t2["next_state"])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def assert_observation_equal(o1: dict, o2: dict):
|
||||
"""Helper to assert two observations are equal."""
|
||||
assert set(o1.keys()) == set(o2.keys())
|
||||
@@ -527,7 +527,7 @@ def assert_observation_equal(o1: dict, o2: dict):
|
||||
assert torch.allclose(o1[key], o2[key])
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_transitions_to_bytes_multiple_transitions():
|
||||
from lerobot.transport.utils import bytes_to_transitions, transitions_to_bytes
|
||||
|
||||
@@ -551,7 +551,7 @@ def test_transitions_to_bytes_multiple_transitions():
|
||||
assert_transitions_equal(original, reconstructed_item)
|
||||
|
||||
|
||||
@require_package("grpcio", "grpc")
|
||||
@skip_if_package_missing("grpcio", "grpc")
|
||||
def test_receive_bytes_in_chunks_unknown_state():
|
||||
from lerobot.transport.utils import receive_bytes_in_chunks
|
||||
|
||||
|
||||
+2
-14
@@ -20,23 +20,11 @@ from functools import wraps
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from lerobot import available_cameras, available_motors, available_robots
|
||||
from lerobot.utils.device_utils import auto_select_torch_device
|
||||
from lerobot.utils.import_utils import is_package_available
|
||||
|
||||
DEVICE = os.environ.get("LEROBOT_TEST_DEVICE", str(auto_select_torch_device()))
|
||||
|
||||
TEST_ROBOT_TYPES = []
|
||||
for robot_type in available_robots:
|
||||
TEST_ROBOT_TYPES += [(robot_type, True), (robot_type, False)]
|
||||
|
||||
TEST_CAMERA_TYPES = []
|
||||
for camera_type in available_cameras:
|
||||
TEST_CAMERA_TYPES += [(camera_type, True), (camera_type, False)]
|
||||
|
||||
TEST_MOTOR_TYPES = []
|
||||
for motor_type in available_motors:
|
||||
TEST_MOTOR_TYPES += [(motor_type, True), (motor_type, False)]
|
||||
|
||||
# Camera indices used for connecting physical cameras
|
||||
OPENCV_CAMERA_INDEX = int(os.environ.get("LEROBOT_TEST_OPENCV_CAMERA_INDEX", 0))
|
||||
@@ -152,7 +140,7 @@ def require_env(func):
|
||||
return wrapper
|
||||
|
||||
|
||||
def require_package_arg(func):
|
||||
def skip_if_package_arg_missing(func):
|
||||
"""
|
||||
Decorator that skips the test if the required package is not installed.
|
||||
This is similar to `require_env` but more general in that it can check any package (not just environments).
|
||||
@@ -184,7 +172,7 @@ def require_package_arg(func):
|
||||
return wrapper
|
||||
|
||||
|
||||
def require_package(package_name, import_name=None):
|
||||
def skip_if_package_missing(package_name, import_name=None):
|
||||
"""
|
||||
Decorator that skips the test if the specified package is not installed.
|
||||
"""
|
||||
|
||||
@@ -22,7 +22,9 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from lerobot.rl.process import ProcessSignalHandler
|
||||
pytest.importorskip("grpc")
|
||||
|
||||
from lerobot.rl.process import ProcessSignalHandler # noqa: E402
|
||||
|
||||
|
||||
# Fixture to reset shutdown_event_counter and original signal handlers before and after each test
|
||||
|
||||
@@ -18,12 +18,16 @@ import sys
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
from lerobot.rl.buffer import BatchTransition, ReplayBuffer, random_crop_vectorized
|
||||
from lerobot.utils.constants import ACTION, DONE, OBS_IMAGE, OBS_STATE, OBS_STR, REWARD
|
||||
from tests.fixtures.constants import DUMMY_REPO_ID
|
||||
pytest.importorskip("grpc")
|
||||
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
||||
|
||||
import torch # noqa: E402
|
||||
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset # noqa: E402
|
||||
from lerobot.rl.buffer import BatchTransition, ReplayBuffer, random_crop_vectorized # noqa: E402
|
||||
from lerobot.utils.constants import ACTION, DONE, OBS_IMAGE, OBS_STATE, OBS_STR, REWARD # noqa: E402
|
||||
from tests.fixtures.constants import DUMMY_REPO_ID # noqa: E402
|
||||
|
||||
|
||||
def state_dims() -> list[str]:
|
||||
|
||||
@@ -17,6 +17,16 @@
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from lerobot.common.train_utils import (
|
||||
get_step_checkpoint_dir,
|
||||
get_step_identifier,
|
||||
load_training_state,
|
||||
load_training_step,
|
||||
save_checkpoint,
|
||||
save_training_state,
|
||||
save_training_step,
|
||||
update_last_checkpoint,
|
||||
)
|
||||
from lerobot.utils.constants import (
|
||||
CHECKPOINTS_DIR,
|
||||
LAST_CHECKPOINT_LINK,
|
||||
@@ -27,16 +37,6 @@ from lerobot.utils.constants import (
|
||||
TRAINING_STATE_DIR,
|
||||
TRAINING_STEP,
|
||||
)
|
||||
from lerobot.utils.train_utils import (
|
||||
get_step_checkpoint_dir,
|
||||
get_step_identifier,
|
||||
load_training_state,
|
||||
load_training_step,
|
||||
save_checkpoint,
|
||||
save_training_state,
|
||||
save_training_step,
|
||||
update_last_checkpoint,
|
||||
)
|
||||
|
||||
|
||||
def test_get_step_identifier():
|
||||
@@ -72,7 +72,7 @@ def test_update_last_checkpoint(tmp_path):
|
||||
assert last_checkpoint.resolve() == checkpoint
|
||||
|
||||
|
||||
@patch("lerobot.utils.train_utils.save_training_state")
|
||||
@patch("lerobot.common.train_utils.save_training_state")
|
||||
def test_save_checkpoint(mock_save_training_state, tmp_path, optimizer):
|
||||
policy = Mock()
|
||||
cfg = Mock()
|
||||
@@ -82,7 +82,7 @@ def test_save_checkpoint(mock_save_training_state, tmp_path, optimizer):
|
||||
mock_save_training_state.assert_called_once()
|
||||
|
||||
|
||||
@patch("lerobot.utils.train_utils.save_training_state")
|
||||
@patch("lerobot.common.train_utils.save_training_state")
|
||||
def test_save_checkpoint_peft(mock_save_training_state, tmp_path, optimizer):
|
||||
policy = Mock()
|
||||
policy.config = Mock()
|
||||
|
||||
@@ -21,6 +21,8 @@ from types import SimpleNamespace
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("rerun", reason="rerun-sdk is required (install lerobot[viz])")
|
||||
|
||||
from lerobot.types import TransitionKey
|
||||
from lerobot.utils.constants import OBS_STATE
|
||||
|
||||
@@ -48,6 +50,9 @@ def mock_rerun(monkeypatch):
|
||||
calls.append((key, obj, kwargs))
|
||||
|
||||
dummy_rr = SimpleNamespace(
|
||||
__name__="rerun",
|
||||
__package__="rerun",
|
||||
__spec__=SimpleNamespace(name="rerun", submodule_search_locations=None),
|
||||
Scalars=DummyScalar,
|
||||
Image=DummyImage,
|
||||
log=dummy_log,
|
||||
|
||||
Reference in New Issue
Block a user