Skip to content

Commit dcb68a0

Browse files
authored
PR realsenseai#15021 from Nir-Az: Fail fast when enable_only times out waiting for device enumeration
2 parents 8284ce1 + 5b9c578 commit dcb68a0

3 files changed

Lines changed: 83 additions & 5 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# License: Apache 2.0. See LICENSE file in root directory.
2+
# Copyright(c) 2026 RealSense, Inc. All Rights Reserved.
3+
4+
"""
5+
Tests that devices.enable_only() raises when it cannot bring the requested
6+
serials online, instead of silently returning. Covers all three branches:
7+
8+
- hub + recycle: TimeoutError when _wait_for times out
9+
- no-hub + no-recycle: TimeoutError when _wait_for times out
10+
- no-hub + recycle: RuntimeError when hw_reset fails (the False return
11+
there can mean hardware_reset() raised, devices didn't disappear, or
12+
devices didn't reappear — so "timeout" doesn't accurately describe it)
13+
14+
Before this behavior existed, enable_only would silently swallow these
15+
failures and the test fixture would proceed with a non-existent device,
16+
surfacing later as confusing IndexError / "list index out of range"
17+
failures inside the test body.
18+
"""
19+
20+
import types
21+
import pytest
22+
from unittest.mock import MagicMock
23+
24+
import rspy.devices as dev
25+
26+
27+
@pytest.fixture
28+
def fake_hub_with_device(monkeypatch):
29+
"""Install a fake hub + one fake device on port 4 (D455-like)."""
30+
fake_device = types.SimpleNamespace(port=4, serial_number='111')
31+
monkeypatch.setattr(dev, '_device_by_sn', {'111': fake_device})
32+
monkeypatch.setattr(dev, 'hub', MagicMock())
33+
monkeypatch.setattr(dev, 'enabled', lambda: set()) # nothing enabled => skip the disable branch
34+
monkeypatch.setattr(dev, 'time', types.SimpleNamespace(sleep=lambda _: None))
35+
return fake_device
36+
37+
38+
def test_enable_only_raises_when_wait_for_times_out(monkeypatch, fake_hub_with_device):
39+
monkeypatch.setattr(dev, '_wait_for', lambda *a, **kw: False)
40+
with pytest.raises(TimeoutError, match="did not enumerate"):
41+
dev.enable_only(['111'], recycle=True, timeout=1)
42+
43+
44+
def test_enable_only_succeeds_when_wait_for_returns_true(monkeypatch, fake_hub_with_device):
45+
monkeypatch.setattr(dev, '_wait_for', lambda *a, **kw: True)
46+
dev.enable_only(['111'], recycle=True, timeout=1) # no exception
47+
48+
49+
def test_enable_only_no_hub_raises_when_wait_for_times_out(monkeypatch):
50+
"""Without a hub and without recycle, enable_only still waits for enumeration."""
51+
monkeypatch.setattr(dev, 'hub', None)
52+
monkeypatch.setattr(dev, '_wait_for', lambda *a, **kw: False)
53+
with pytest.raises(TimeoutError, match="did not enumerate"):
54+
dev.enable_only(['111'], recycle=False, timeout=1)
55+
56+
57+
def test_enable_only_no_hub_recycle_raises_when_hw_reset_fails(monkeypatch):
58+
"""No hub + recycle=True: enable_only delegates to hw_reset; failure should raise.
59+
60+
hw_reset can return False for several reasons (hardware_reset() raised, devices
61+
didn't disappear, devices didn't reappear) — so we raise RuntimeError, not
62+
TimeoutError, since "timeout" doesn't accurately describe all of them.
63+
"""
64+
monkeypatch.setattr(dev, 'hub', None)
65+
monkeypatch.setattr(dev, 'time', types.SimpleNamespace(sleep=lambda _: None))
66+
monkeypatch.setattr(dev, '_device_by_sn', {
67+
'111': types.SimpleNamespace(port=None, is_dds=False, handle=MagicMock())
68+
})
69+
monkeypatch.setattr(dev, '_wait_for', lambda *a, **kw: False)
70+
monkeypatch.setattr(dev, '_wait_until_removed', lambda *a, **kw: True)
71+
with pytest.raises(RuntimeError, match="hw_reset failed"):
72+
dev.enable_only(['111'], recycle=True, timeout=1)

unit-tests/py/rspy/devices.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -629,16 +629,19 @@ def enable_only( serial_numbers, recycle = False, timeout = MAX_ENUMERATION_TIME
629629
else:
630630
log.d( 'no hub ports to enable; leaving hub as-is' )
631631
#
632-
_wait_for( serial_numbers, timeout = timeout )
632+
if not _wait_for( serial_numbers, timeout = timeout ):
633+
raise TimeoutError( f'devices did not enumerate within {timeout}s after hub enable: {serial_numbers}' )
633634
#
634635
elif recycle:
635636
#
636-
hw_reset( serial_numbers )
637+
if not hw_reset( serial_numbers, timeout = timeout ):
638+
raise RuntimeError( f'hw_reset failed for: {serial_numbers}' )
637639
#
638640
else:
639641
log.d( 'no hub; ports left as-is' )
640642
# even without reset, enable_only should wait for the devices to be available again
641-
_wait_for(serial_numbers, timeout=timeout)
643+
if not _wait_for( serial_numbers, timeout = timeout ):
644+
raise TimeoutError( f'devices did not enumerate within {timeout}s: {serial_numbers}' )
642645

643646

644647
def enable_all():

wrappers/rest-api/tests/test_api_service.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ def real_rs_manager(self):
448448

449449
# Create a real RealSenseManager (not mocked)
450450
manager = RealSenseManager(sio)
451+
assert manager.get_devices(), "RealSenseManager sees no devices — hub/USB enumeration likely failed"
451452
return manager
452453

453454
def test_get_device_rs(self, real_rs_manager):
@@ -613,9 +614,10 @@ def test_sensors_endpoint_rs(self):
613614

614615
# First get devices
615616
response = real_client.get("/api/devices")
616-
617+
assert response.status_code == 200, f"/api/devices returned {response.status_code}: {response.text}"
617618

618619
devices = response.json()
620+
assert devices, "/api/devices returned no devices — hub/USB enumeration likely failed"
619621
device_id = devices[0]["device_id"]
620622

621623
# Test sensors endpoint
@@ -649,9 +651,10 @@ def test_options_endpoint_rs(self):
649651

650652
# First get devices
651653
response = real_client.get("/api/devices")
652-
654+
assert response.status_code == 200, f"/api/devices returned {response.status_code}: {response.text}"
653655

654656
devices = response.json()
657+
assert devices, "/api/devices returned no devices — hub/USB enumeration likely failed"
655658

656659
device_id = devices[0]["device_id"]
657660

0 commit comments

Comments
 (0)