-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathconftest.py
More file actions
108 lines (87 loc) · 4.27 KB
/
Copy pathconftest.py
File metadata and controls
108 lines (87 loc) · 4.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
"""Shared pytest fixtures for the leLab test suite."""
from __future__ import annotations
from collections.abc import Iterator
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from fastapi.testclient import TestClient
@pytest.fixture
def client() -> Iterator[TestClient]:
"""FastAPI TestClient bound to the real `lelab.server.app`."""
from lelab.server import app
with TestClient(app) as c:
yield c
@pytest.fixture
def tmp_lerobot_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""Redirect every persisted-state path under `~/.cache/huggingface/lerobot/`
into a tmp directory.
Patches the module-level constants in `lelab.utils.config` so any code
importing them through `from lelab.utils.config import LEADER_CONFIG_PATH`
sees the redirected path. Also sets `HF_LEROBOT_HOME` env var for any
consumer (e.g. `lelab.datasets._lerobot_cache_root`) reading it directly.
"""
cache = tmp_path / "lerobot"
cache.mkdir()
monkeypatch.setenv("HF_LEROBOT_HOME", str(cache))
from lelab.utils import config as cfg
teleop_dir = cache / "calibration" / "teleoperators" / "so101_leader"
robot_dir = cache / "calibration" / "robots" / "so101_follower"
leader_cfg_dir = cache / "configs" / "so_leader"
follower_cfg_dir = cache / "configs" / "so_follower"
port_dir = cache / "ports"
saved_dir = cache / "saved_configs"
for d in (teleop_dir, robot_dir, leader_cfg_dir, follower_cfg_dir, port_dir, saved_dir):
d.mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(cfg, "CALIBRATION_BASE_PATH_TELEOP", str(teleop_dir))
monkeypatch.setattr(cfg, "CALIBRATION_BASE_PATH_ROBOTS", str(robot_dir))
monkeypatch.setattr(cfg, "LEADER_CONFIG_PATH", str(leader_cfg_dir))
monkeypatch.setattr(cfg, "FOLLOWER_CONFIG_PATH", str(follower_cfg_dir))
monkeypatch.setattr(cfg, "PORT_CONFIG_PATH", str(port_dir))
monkeypatch.setattr(cfg, "LEADER_PORT_FILE", str(port_dir / "leader_port.txt"))
monkeypatch.setattr(cfg, "FOLLOWER_PORT_FILE", str(port_dir / "follower_port.txt"))
monkeypatch.setattr(cfg, "CONFIG_STORAGE_PATH", str(saved_dir))
monkeypatch.setattr(cfg, "LEADER_CONFIG_FILE", str(saved_dir / "leader_config.txt"))
monkeypatch.setattr(cfg, "FOLLOWER_CONFIG_FILE", str(saved_dir / "follower_config.txt"))
return cache
@pytest.fixture
def mock_lerobot_record(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
"""Patch `lerobot.record.record` so no real recording loop runs.
Returns the MagicMock; tests can assert on `mock.called` or `mock.call_args`.
"""
spy = MagicMock(name="lerobot.record.record")
monkeypatch.setattr("lerobot.record.record", spy)
return spy
@pytest.fixture
def mock_lerobot_teleoperate(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
"""Patch `lerobot.teleoperate` so no real teleop loop runs."""
spy = MagicMock(name="lerobot.teleoperate")
monkeypatch.setattr("lerobot.teleoperate", spy)
return spy
@pytest.fixture
def mock_subprocess_popen(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
"""Patch `subprocess.Popen` (the symbol in lelab.jobs) so no real
subprocess is launched. Returns a MagicMock whose return_value has the
attributes a `Popen` instance is expected to have."""
fake_proc = MagicMock(name="Popen()")
fake_proc.pid = 12345
fake_proc.poll.return_value = None # still running
fake_proc.stdout = iter([])
fake_proc.terminate.return_value = None
fake_proc.wait.return_value = 0
fake_proc.kill.return_value = None
spy = MagicMock(name="subprocess.Popen", return_value=fake_proc)
monkeypatch.setattr("lelab.jobs.subprocess.Popen", spy, raising=False)
return spy