Skip to content

Commit 7d103c2

Browse files
committed
tests: add FTDI GPIO coverage
Cover FTDI GPIO resource validation, supported USB IDs, driver set/get behavior, agent read-modify-write output handling, exporter parameters, and labgrid-client io integration. Signed-off-by: Ozan Durgut <ozan.durgut@analog.com>
1 parent 910713c commit 7d103c2

1 file changed

Lines changed: 365 additions & 0 deletions

File tree

tests/test_ftdigpiodriver.py

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
import types
2+
3+
import pytest
4+
5+
from labgrid.driver import ftdigpiodriver
6+
from labgrid.driver.ftdigpiodriver import FTDIGPIODriver
7+
from labgrid.remote.client import ClientSession
8+
from labgrid.resource.common import ResourceManager
9+
from labgrid.resource.remote import NetworkFTDIGPIO
10+
from labgrid.resource.udev import FTDIGPIO
11+
12+
13+
def test_ftdigpio_resource_create(target, monkeypatch):
14+
monkeypatch.setattr(FTDIGPIO, "manager_cls", ResourceManager)
15+
resource = FTDIGPIO(target, name=None, index=0, interface=1)
16+
17+
assert resource.index == 0
18+
assert resource.interface == 1
19+
assert resource.match["DEVTYPE"] == "usb_device"
20+
21+
22+
def test_ftdigpio_resource_validates_range(target, monkeypatch):
23+
monkeypatch.setattr(FTDIGPIO, "manager_cls", ResourceManager)
24+
25+
with pytest.raises(ValueError):
26+
FTDIGPIO(target, name=None, index=8, interface=1)
27+
28+
with pytest.raises(ValueError):
29+
FTDIGPIO(target, name=None, index=0, interface=0)
30+
31+
32+
def test_ftdigpio_resource_filters_supported_devices(target, monkeypatch):
33+
monkeypatch.setattr(FTDIGPIO, "manager_cls", ResourceManager)
34+
resource = FTDIGPIO(target, name=None)
35+
36+
def device(vendor_id, model_id):
37+
return types.SimpleNamespace(
38+
device_type="usb_device",
39+
properties={
40+
"ID_VENDOR_ID": vendor_id,
41+
"ID_MODEL_ID": model_id,
42+
},
43+
)
44+
45+
assert resource.filter_match(device("0403", "6014")) is True
46+
assert resource.filter_match(device("0403", "6010")) is True
47+
assert resource.filter_match(device("0403", "6011")) is True
48+
assert resource.filter_match(device("0403", "6001")) is False
49+
50+
51+
def test_ftdigpio_driver_create(target, monkeypatch):
52+
monkeypatch.setattr(NetworkFTDIGPIO, "manager_cls", ResourceManager)
53+
NetworkFTDIGPIO(
54+
target,
55+
name=None,
56+
host="exporter",
57+
busnum=1,
58+
devnum=39,
59+
path="1-12.4.2",
60+
vendor_id=0x0403,
61+
model_id=0x6014,
62+
index=0,
63+
interface=1,
64+
)
65+
66+
driver = FTDIGPIODriver(target, name=None)
67+
68+
assert isinstance(driver, FTDIGPIODriver)
69+
70+
71+
def test_ftdigpio_driver_set_get(target, monkeypatch):
72+
monkeypatch.setattr(NetworkFTDIGPIO, "manager_cls", ResourceManager)
73+
proxy = types.SimpleNamespace(calls=[], value=False)
74+
75+
def proxy_set(vendor_id, model_id, busnum, devnum, interface, index, status):
76+
proxy.calls.append((vendor_id, model_id, busnum, devnum, interface, index, status))
77+
proxy.value = status
78+
79+
def proxy_get(vendor_id, model_id, busnum, devnum, interface, index):
80+
proxy.calls.append((vendor_id, model_id, busnum, devnum, interface, index))
81+
return proxy.value
82+
83+
proxy.set = proxy_set
84+
proxy.get = proxy_get
85+
proxy.close = lambda: None
86+
87+
class FakeWrapper:
88+
def __init__(self, host):
89+
self.host = host
90+
91+
def load(self, name):
92+
assert name == "ftdigpio"
93+
return proxy
94+
95+
def close(self):
96+
pass
97+
98+
monkeypatch.setattr(ftdigpiodriver, "AgentWrapper", FakeWrapper)
99+
ftdigpiodriver._shared_agents.clear()
100+
101+
resource = NetworkFTDIGPIO(
102+
target,
103+
name=None,
104+
host="exporter",
105+
busnum=1,
106+
devnum=39,
107+
path="1-12.4.2",
108+
vendor_id=0x0403,
109+
model_id=0x6014,
110+
index=2,
111+
interface=1,
112+
invert=True,
113+
)
114+
resource.avail = True
115+
driver = FTDIGPIODriver(target, name=None)
116+
117+
target.activate(driver)
118+
driver.set(True)
119+
assert proxy.calls[-1] == (0x0403, 0x6014, 1, 39, 1, 2, False)
120+
assert driver.get() is True
121+
122+
target.deactivate(driver)
123+
124+
125+
def test_ftdigpio_agent(monkeypatch):
126+
from labgrid.util.agents import ftdigpio
127+
128+
class FakeEndpoint:
129+
def __init__(self, address):
130+
self.bEndpointAddress = address
131+
self.writes = []
132+
133+
def write(self, data, timeout=None):
134+
self.writes.append(bytes(data))
135+
136+
out_ep = FakeEndpoint(0x02)
137+
in_ep = FakeEndpoint(0x81)
138+
139+
class FakeConfig:
140+
def __getitem__(self, item):
141+
assert item == (0, 0)
142+
return [out_ep, in_ep]
143+
144+
class FakeDevice:
145+
bus = 1
146+
address = 39
147+
148+
def __init__(self):
149+
self.control = []
150+
self.detached = []
151+
self.released = []
152+
self.pins = b"\x00"
153+
154+
def set_configuration(self):
155+
pass
156+
157+
def is_kernel_driver_active(self, interface):
158+
assert interface == 0
159+
return False
160+
161+
def detach_kernel_driver(self, interface):
162+
self.detached.append(interface)
163+
164+
def get_active_configuration(self):
165+
return FakeConfig()
166+
167+
def ctrl_transfer(self, request_type, request, value, index, data, timeout=None):
168+
self.control.append((request_type, request, value, index, data))
169+
if request_type == ftdigpio.IN_REQTYPE and request == ftdigpio.SIO_READ_PINS:
170+
return self.pins
171+
return None
172+
173+
device = FakeDevice()
174+
monkeypatch.setattr(ftdigpio.usb.core, "find", lambda **kwargs: [device])
175+
monkeypatch.setattr(ftdigpio.usb.util, "claim_interface", lambda dev, interface: None)
176+
monkeypatch.setattr(
177+
ftdigpio.usb.util,
178+
"release_interface",
179+
lambda dev, interface: device.released.append(interface),
180+
)
181+
monkeypatch.setattr(ftdigpio.usb.util, "dispose_resources", lambda dev: None)
182+
183+
device.pins = b"\x02"
184+
assert ftdigpio.handle_set(0x0403, 0x6014, 1, 39, 1, 2, True) is True
185+
assert device.control[-1] == (
186+
ftdigpio.OUT_REQTYPE,
187+
ftdigpio.SIO_SET_BITMODE,
188+
0x01ff,
189+
1,
190+
None,
191+
)
192+
assert out_ep.writes[-1] == b"\x06"
193+
194+
device.pins = b"\x06"
195+
ftdigpio.handle_set(0x0403, 0x6014, 1, 39, 1, 3, True)
196+
assert device.control[-1] == (
197+
ftdigpio.OUT_REQTYPE,
198+
ftdigpio.SIO_SET_BITMODE,
199+
0x01ff,
200+
1,
201+
None,
202+
)
203+
assert out_ep.writes[-1] == b"\x0e"
204+
205+
device.pins = b"\x02"
206+
ftdigpio.handle_set(0x0403, 0x6014, 1, 39, 1, 2, False)
207+
assert out_ep.writes[-1] == b"\x02"
208+
209+
device.pins = b"\x08"
210+
assert ftdigpio.handle_get(0x0403, 0x6014, 1, 39, 1, 3) is True
211+
assert ftdigpio.handle_get(0x0403, 0x6014, 1, 39, 1, 2) is False
212+
assert device.released == [0, 0, 0, 0, 0]
213+
assert ftdigpio.handle_close() is True
214+
215+
216+
def test_ftdigpio_agent_rejects_empty_pin_read(monkeypatch):
217+
from labgrid.util.agents import ftdigpio
218+
219+
class FakeEndpoint:
220+
def __init__(self, address):
221+
self.bEndpointAddress = address
222+
223+
def write(self, data, timeout=None):
224+
pass
225+
226+
out_ep = FakeEndpoint(0x02)
227+
in_ep = FakeEndpoint(0x81)
228+
229+
class FakeConfig:
230+
def __getitem__(self, item):
231+
assert item == (0, 0)
232+
return [out_ep, in_ep]
233+
234+
class FakeDevice:
235+
bus = 1
236+
address = 39
237+
238+
def set_configuration(self):
239+
pass
240+
241+
def is_kernel_driver_active(self, interface):
242+
return False
243+
244+
def get_active_configuration(self):
245+
return FakeConfig()
246+
247+
def ctrl_transfer(self, request_type, request, value, index, data, timeout=None):
248+
if request_type == ftdigpio.IN_REQTYPE and request == ftdigpio.SIO_READ_PINS:
249+
return b""
250+
return None
251+
252+
device = FakeDevice()
253+
monkeypatch.setattr(ftdigpio.usb.core, "find", lambda **kwargs: [device])
254+
monkeypatch.setattr(ftdigpio.usb.util, "claim_interface", lambda dev, interface: None)
255+
monkeypatch.setattr(ftdigpio.usb.util, "release_interface", lambda dev, interface: None)
256+
monkeypatch.setattr(ftdigpio.usb.util, "dispose_resources", lambda dev: None)
257+
258+
with pytest.raises(TimeoutError, match="no data"):
259+
ftdigpio.handle_get(0x0403, 0x6014, 1, 39, 1, 3)
260+
261+
262+
def test_ftdigpio_agent_rejects_unsupported_index():
263+
from labgrid.util.agents import ftdigpio
264+
265+
with pytest.raises(ValueError, match="0-7"):
266+
ftdigpio.FTDIGPIO._validate_index(8)
267+
268+
269+
def test_ftdigpio_agent_validates_supported_devices():
270+
from labgrid.util.agents import ftdigpio
271+
272+
ftdigpio.FTDIGPIO._validate_device(0x0403, 0x6010, 2)
273+
ftdigpio.FTDIGPIO._validate_device(0x0403, 0x6011, 4)
274+
ftdigpio.FTDIGPIO._validate_device(0x0403, 0x6014, 1)
275+
276+
with pytest.raises(ValueError, match="Unsupported"):
277+
ftdigpio.FTDIGPIO._validate_device(0x0403, 0x6001, 1)
278+
279+
with pytest.raises(ValueError, match="interface"):
280+
ftdigpio.FTDIGPIO._validate_device(0x0403, 0x6014, 2)
281+
282+
283+
def test_ftdigpio_export_params():
284+
from labgrid.remote.exporter import USBFTDIGPIOExport
285+
286+
export = object.__new__(USBFTDIGPIOExport)
287+
export.host = "exporter"
288+
export.local = types.SimpleNamespace(
289+
busnum=1,
290+
devnum=39,
291+
path="1-12.4.2",
292+
vendor_id=0x0403,
293+
model_id=0x6014,
294+
index=2,
295+
interface=1,
296+
invert=True,
297+
)
298+
299+
assert export._get_params() == {
300+
"host": "exporter",
301+
"busnum": 1,
302+
"devnum": 39,
303+
"path": "1-12.4.2",
304+
"vendor_id": 0x0403,
305+
"model_id": 0x6014,
306+
"index": 2,
307+
"interface": 1,
308+
"invert": True,
309+
}
310+
311+
312+
def test_ftdigpio_client_digital_io(target, mocker, monkeypatch):
313+
monkeypatch.setattr(NetworkFTDIGPIO, "manager_cls", ResourceManager)
314+
driver = types.SimpleNamespace(set=mocker.MagicMock())
315+
session = object.__new__(ClientSession)
316+
session.args = types.SimpleNamespace(action="high", name=None)
317+
session.get_acquired_place = lambda: types.SimpleNamespace(name="test")
318+
session._get_target = lambda place: target
319+
session._get_driver_or_new = mocker.MagicMock(return_value=driver)
320+
321+
NetworkFTDIGPIO(
322+
target,
323+
name=None,
324+
host="exporter",
325+
busnum=1,
326+
devnum=39,
327+
path="1-12.4.2",
328+
vendor_id=0x0403,
329+
model_id=0x6014,
330+
index=2,
331+
interface=1,
332+
)
333+
334+
session.digital_io()
335+
336+
session._get_driver_or_new.assert_called_once_with(target, "FTDIGPIODriver", name=None)
337+
driver.set.assert_called_once_with(True)
338+
339+
340+
def test_ftdigpio_client_digital_io_get(target, mocker, monkeypatch, capsys):
341+
monkeypatch.setattr(NetworkFTDIGPIO, "manager_cls", ResourceManager)
342+
driver = types.SimpleNamespace(get=mocker.MagicMock(return_value=True))
343+
session = object.__new__(ClientSession)
344+
session.args = types.SimpleNamespace(action="get", name=None)
345+
session.get_acquired_place = lambda: types.SimpleNamespace(name="test")
346+
session._get_target = lambda place: target
347+
session._get_driver_or_new = mocker.MagicMock(return_value=driver)
348+
349+
NetworkFTDIGPIO(
350+
target,
351+
name=None,
352+
host="exporter",
353+
busnum=1,
354+
devnum=39,
355+
path="1-12.4.2",
356+
vendor_id=0x0403,
357+
model_id=0x6014,
358+
index=2,
359+
interface=1,
360+
)
361+
362+
session.digital_io()
363+
364+
session._get_driver_or_new.assert_called_once_with(target, "FTDIGPIODriver", name=None)
365+
assert "digital IO for place test is high" in capsys.readouterr().out

0 commit comments

Comments
 (0)