|
| 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) |
0 commit comments