| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| from concurrent import futures |
| 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 skip_if_package_missing |
|
|
|
|
| def create_learner_service_stub(): |
| import grpc |
|
|
| from lerobot.transport import services_pb2, services_pb2_grpc |
|
|
| class MockLearnerService(services_pb2_grpc.LearnerServiceServicer): |
| def __init__(self): |
| self.ready_call_count = 0 |
| self.should_fail = False |
|
|
| def Ready(self, request, context): |
| self.ready_call_count += 1 |
| if self.should_fail: |
| context.set_code(grpc.StatusCode.UNAVAILABLE) |
| context.set_details("Service unavailable") |
| raise grpc.RpcError("Service unavailable") |
| return services_pb2.Empty() |
|
|
| """Fixture to start a LearnerService gRPC server and provide a connected stub.""" |
|
|
| servicer = MockLearnerService() |
|
|
| |
| server = grpc.server(futures.ThreadPoolExecutor(max_workers=4)) |
| services_pb2_grpc.add_LearnerServiceServicer_to_server(servicer, server) |
| port = server.add_insecure_port("[::]:0") |
| server.start() |
|
|
| |
| channel = grpc.insecure_channel(f"localhost:{port}") |
| return services_pb2_grpc.LearnerServiceStub(channel), servicer, channel, server |
|
|
|
|
| def close_service_stub(channel, server): |
| channel.close() |
| server.stop(None) |
|
|
|
|
| @skip_if_package_missing("grpcio", "grpc") |
| def test_establish_learner_connection_success(): |
| from lerobot.rl.actor import establish_learner_connection |
|
|
| """Test successful connection establishment.""" |
| stub, _servicer, channel, server = create_learner_service_stub() |
|
|
| shutdown_event = Event() |
|
|
| |
| result = establish_learner_connection(stub, shutdown_event, attempts=5) |
|
|
| assert result is True |
|
|
| close_service_stub(channel, server) |
|
|
|
|
| @skip_if_package_missing("grpcio", "grpc") |
| def test_establish_learner_connection_failure(): |
| from lerobot.rl.actor import establish_learner_connection |
|
|
| """Test connection failure.""" |
| stub, servicer, channel, server = create_learner_service_stub() |
| servicer.should_fail = True |
|
|
| shutdown_event = Event() |
|
|
| |
| with patch("time.sleep"): |
| result = establish_learner_connection(stub, shutdown_event, attempts=2) |
|
|
| assert result is False |
|
|
| close_service_stub(channel, server) |
|
|
|
|
| @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 |
| from tests.transport.test_transport_utils import assert_transitions_equal |
|
|
| """Test pushing transitions to transport queue.""" |
| |
| transitions = [] |
| for i in range(3): |
| transition = Transition( |
| state={OBS_STR: torch.randn(3, 64, 64), "state": torch.randn(10)}, |
| action=torch.randn(5), |
| reward=torch.tensor(1.0 + i), |
| done=torch.tensor(False), |
| truncated=torch.tensor(False), |
| next_state={OBS_STR: torch.randn(3, 64, 64), "state": torch.randn(10)}, |
| complementary_info={"step": torch.tensor(i)}, |
| ) |
| transitions.append(transition) |
|
|
| transitions_queue = Queue() |
|
|
| |
| push_transitions_to_transport_queue(transitions, transitions_queue) |
|
|
| |
| serialized_data = transitions_queue.get() |
| assert isinstance(serialized_data, bytes) |
| deserialized_transitions = bytes_to_transitions(serialized_data) |
| assert len(deserialized_transitions) == len(transitions) |
| for i, deserialized_transition in enumerate(deserialized_transitions): |
| assert_transitions_equal(deserialized_transition, transitions[i]) |
|
|
|
|
| @skip_if_package_missing("grpcio", "grpc") |
| @pytest.mark.timeout(3) |
| def test_transitions_stream(): |
| from lerobot.rl.actor import transitions_stream |
|
|
| """Test transitions stream functionality.""" |
| shutdown_event = Event() |
| transitions_queue = Queue() |
|
|
| |
| test_data = [b"transition_data_1", b"transition_data_2", b"transition_data_3"] |
| for data in test_data: |
| transitions_queue.put(data) |
|
|
| |
| streamed_data = [] |
| stream_generator = transitions_stream(shutdown_event, transitions_queue, 0.1) |
|
|
| |
| for i, message in enumerate(stream_generator): |
| streamed_data.append(message) |
| if i >= len(test_data) - 1: |
| shutdown_event.set() |
| break |
|
|
| |
| assert len(streamed_data) == len(test_data) |
| assert streamed_data[0].data == b"transition_data_1" |
| assert streamed_data[1].data == b"transition_data_2" |
| assert streamed_data[2].data == b"transition_data_3" |
|
|
|
|
| @skip_if_package_missing("grpcio", "grpc") |
| @pytest.mark.timeout(3) |
| def test_interactions_stream(): |
| from lerobot.rl.actor import interactions_stream |
| from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes |
|
|
| """Test interactions stream functionality.""" |
| shutdown_event = Event() |
| interactions_queue = Queue() |
|
|
| |
| test_interactions = [ |
| {"episode_reward": 10.5, "step": 1, "policy_fps": 30.2}, |
| {"episode_reward": 15.2, "step": 2, "policy_fps": 28.7}, |
| {"episode_reward": 8.7, "step": 3, "policy_fps": 29.1}, |
| ] |
|
|
| |
| test_data = [ |
| interactions_queue.put(python_object_to_bytes(interaction)) for interaction in test_interactions |
| ] |
|
|
| |
| streamed_data = [] |
| stream_generator = interactions_stream(shutdown_event, interactions_queue, 0.1) |
|
|
| |
| for i, message in enumerate(stream_generator): |
| streamed_data.append(message) |
| if i >= len(test_data) - 1: |
| shutdown_event.set() |
| break |
|
|
| |
| assert len(streamed_data) == len(test_data) |
|
|
| |
| for i, message in enumerate(streamed_data): |
| deserialized_interaction = bytes_to_python_object(message.data) |
| assert deserialized_interaction == test_interactions[i] |
|
|