-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_platform.py
More file actions
309 lines (245 loc) · 11.1 KB
/
test_platform.py
File metadata and controls
309 lines (245 loc) · 11.1 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
"""
Tests for closeclaw.platform — cross-platform hardware detection.
All tests run without real hardware (simulation fallbacks).
"""
import pytest
from unittest.mock import patch, MagicMock
from closeclaw.platform import (
PlatformInfo,
detect_platform,
get_platform,
reset_platform_cache,
recommend_model,
is_raspberry_pi,
is_simulation,
SimulatedI2C,
SimulatedSerial,
_detect_gpio,
_detect_i2c,
_detect_serial,
_detect_camera,
_detect_audio,
)
# ── PlatformInfo dataclass ─────────────────────────────────────────────────────
class TestPlatformInfo:
def test_defaults_are_simulation(self):
info = PlatformInfo()
assert info.gpio_backend == "simulation"
assert info.i2c_backend == "simulation"
assert info.serial_backend == "simulation"
assert info.camera_backend == "simulation"
assert info.audio_backend == "simulation"
def test_is_simulated_when_all_simulation(self):
info = PlatformInfo()
assert info.is_simulated is True
def test_is_simulated_false_when_one_real(self):
info = PlatformInfo(gpio_backend="RPi.GPIO")
assert info.is_simulated is False
def test_hardware_flags_default_false(self):
info = PlatformInfo()
assert info.has_gpio is False
assert info.has_i2c is False
assert info.has_serial is False
assert info.has_camera is False
assert info.has_audio is False
def test_hardware_profile_linux_x86(self):
info = PlatformInfo(is_linux=True, arch="x86_64")
assert "linux" in info.hardware_profile
assert "x86_64" in info.hardware_profile
def test_hardware_profile_raspberry_pi(self):
info = PlatformInfo(is_raspberry_pi=True, is_linux=True, arch="arm64")
assert "raspberry-pi" in info.hardware_profile
def test_hardware_profile_macos(self):
info = PlatformInfo(is_macos=True, arch="arm64")
assert "macos" in info.hardware_profile
def test_hardware_profile_windows(self):
info = PlatformInfo(is_windows=True, arch="x86_64")
assert "windows" in info.hardware_profile
def test_summary_contains_os_and_arch(self):
info = PlatformInfo(os_name="Linux", os_version="6.1", arch="arm64")
summary = info.summary()
assert "Linux" in summary
assert "arm64" in summary
def test_summary_shows_simulation_mode(self):
info = PlatformInfo()
assert "SIMULATION" in info.summary()
def test_summary_shows_real_caps(self):
info = PlatformInfo(
gpio_backend="RPi.GPIO", has_gpio=True,
i2c_backend="smbus2", has_i2c=True,
)
summary = info.summary()
assert "GPIO" in summary
# ── Backend detectors ──────────────────────────────────────────────────────────
class TestBackendDetectors:
def test_gpio_returns_simulation_without_hardware(self):
with patch.dict("sys.modules", {"RPi": None, "RPi.GPIO": None,
"gpiod": None, "lgpio": None}):
result = _detect_gpio()
# Either simulation or whatever is truly installed
assert isinstance(result, str)
assert result in ("RPi.GPIO", "gpiod", "lgpio", "simulation")
def test_i2c_returns_string(self):
result = _detect_i2c()
assert result in ("smbus2", "simulation")
def test_serial_returns_string(self):
result = _detect_serial()
assert result in ("pyserial", "simulation")
def test_camera_returns_string(self):
result = _detect_camera()
assert result in ("picamera2", "opencv", "simulation")
def test_audio_returns_string(self):
result = _detect_audio()
assert result in ("pyaudio", "sounddevice", "simulation")
def test_gpio_rpigpio_mock(self):
mock_mod = MagicMock()
with patch.dict("sys.modules", {"RPi": mock_mod, "RPi.GPIO": mock_mod}):
result = _detect_gpio()
assert result in ("RPi.GPIO", "gpiod", "lgpio", "simulation")
def test_camera_picamera2_mock(self):
mock_mod = MagicMock()
with patch.dict("sys.modules", {"picamera2": mock_mod}):
result = _detect_camera()
assert result in ("picamera2", "simulation")
# ── detect_platform() ──────────────────────────────────────────────────────────
class TestDetectPlatform:
def test_returns_platform_info(self):
info = detect_platform()
assert isinstance(info, PlatformInfo)
def test_os_name_is_set(self):
info = detect_platform()
assert info.os_name in ("Linux", "Darwin", "Windows")
def test_arch_is_set(self):
info = detect_platform()
assert info.arch != ""
def test_os_flags_exactly_one(self):
info = detect_platform()
flags = [info.is_linux, info.is_macos, info.is_windows]
assert sum(flags) == 1
def test_backends_are_valid_strings(self):
info = detect_platform()
for backend in [info.gpio_backend, info.i2c_backend,
info.serial_backend, info.camera_backend,
info.audio_backend]:
assert isinstance(backend, str)
assert len(backend) > 0
def test_capability_flags_consistent(self):
info = detect_platform()
assert info.has_gpio == (info.gpio_backend != "simulation")
assert info.has_i2c == (info.i2c_backend != "simulation")
assert info.has_serial == (info.serial_backend != "simulation")
assert info.has_camera == (info.camera_backend != "simulation")
assert info.has_audio == (info.audio_backend != "simulation")
def test_raspberry_pi_false_on_non_pi(self):
# In CI/dev environments, this should be False
info = detect_platform()
# We can't assert False (might actually run on a Pi),
# but we can assert it's a bool
assert isinstance(info.is_raspberry_pi, bool)
# ── get_platform / reset cache ────────────────────────────────────────────────
class TestGetPlatform:
def test_returns_same_object_twice(self):
reset_platform_cache()
a = get_platform()
b = get_platform()
assert a is b
def test_reset_forces_redetection(self):
reset_platform_cache()
a = get_platform()
reset_platform_cache()
b = get_platform()
# Different objects, same data
assert a.os_name == b.os_name
def test_returns_platform_info_type(self):
info = get_platform()
assert isinstance(info, PlatformInfo)
# ── recommend_model ────────────────────────────────────────────────────────────
class TestRecommendModel:
def test_returns_string(self):
result = recommend_model()
assert isinstance(result, str)
assert len(result) > 0
def test_small_ram_returns_small_model(self):
with patch("closeclaw.platform.get_platform") as mock_gp:
mock_gp.return_value = PlatformInfo(ram_mb=1024)
result = recommend_model()
assert "1b" in result or "e2b" in result or "e4b" in result
def test_large_ram_returns_large_model(self):
with patch("closeclaw.platform.get_platform") as mock_gp:
mock_gp.return_value = PlatformInfo(ram_mb=32_000)
result = recommend_model()
assert "26b" in result or "31b" in result or "12b" in result
def test_unknown_ram_returns_model(self):
with patch("closeclaw.platform.get_platform") as mock_gp:
mock_gp.return_value = PlatformInfo(ram_mb=0)
result = recommend_model()
assert isinstance(result, str)
# ── SimulatedI2C ──────────────────────────────────────────────────────────────
class TestSimulatedI2C:
def test_read_write_byte_data(self):
i2c = SimulatedI2C()
i2c.write_byte_data(0x48, 0x00, 42)
assert i2c.read_byte_data(0x48, 0x00) == 42
def test_read_unwritten_returns_zero(self):
i2c = SimulatedI2C()
assert i2c.read_byte_data(0x10, 0x05) == 0
def test_scan_returns_written_addresses(self):
i2c = SimulatedI2C()
i2c.write_byte_data(0x3C, 0x00, 1)
i2c.write_byte_data(0x48, 0x00, 2)
addrs = i2c.scan()
assert 0x3C in addrs
assert 0x48 in addrs
def test_close_does_not_raise(self):
i2c = SimulatedI2C()
i2c.close() # should not raise
def test_different_buses_independent(self):
i2c1 = SimulatedI2C(bus=1)
i2c2 = SimulatedI2C(bus=2)
i2c1.write_byte_data(0x48, 0x00, 99)
# i2c2 doesn't share state with i2c1
assert i2c2.read_byte_data(0x48, 0x00) == 0
# ── SimulatedSerial ───────────────────────────────────────────────────────────
class TestSimulatedSerial:
def test_write_returns_length(self):
ser = SimulatedSerial()
n = ser.write(b"hello")
assert n == 5
def test_read_returns_written_data(self):
ser = SimulatedSerial()
ser.write(b"ABCDE")
data = ser.read(5)
assert data == b"ABCDE"
def test_read_empty_returns_empty(self):
ser = SimulatedSerial()
data = ser.read(10)
assert data == b""
def test_readline_returns_bytes(self):
ser = SimulatedSerial()
line = ser.readline()
assert isinstance(line, bytes)
def test_close_sets_open_false(self):
ser = SimulatedSerial()
assert ser.is_open is True
ser.close()
assert ser.is_open is False
def test_port_attribute(self):
ser = SimulatedSerial(port="/dev/ttyUSB0")
assert ser.port == "/dev/ttyUSB0"
# ── is_raspberry_pi / is_simulation ──────────────────────────────────────────
class TestConvenienceFunctions:
def test_is_raspberry_pi_returns_bool(self):
reset_platform_cache()
result = is_raspberry_pi()
assert isinstance(result, bool)
def test_is_simulation_returns_bool(self):
reset_platform_cache()
result = is_simulation()
assert isinstance(result, bool)
def test_is_simulation_true_on_dev_machine(self):
# In normal dev env, no robot hardware → simulation
reset_platform_cache()
info = detect_platform()
# If all backends are simulation, is_simulation() should be True
if info.is_simulated:
assert is_simulation() is True