@@ -42,12 +42,38 @@ def device(vendor_id, model_id):
4242 },
4343 )
4444
45+ def child_device (parent ):
46+ return types .SimpleNamespace (
47+ device_type = "usb_interface" ,
48+ find_parent = lambda subsystem , device_type : parent ,
49+ )
50+
4551 assert resource .filter_match (device ("0403" , "6014" )) is True
52+ assert resource .filter_match (child_device (device ("0403" , "6014" ))) is True
53+ assert resource .filter_match (child_device (None )) is False
4654 assert resource .filter_match (device ("0403" , "6010" )) is True
4755 assert resource .filter_match (device ("0403" , "6011" )) is True
4856 assert resource .filter_match (device ("0403" , "6001" )) is False
4957
5058
59+ def test_network_ftdigpio_resource_validates_range (target , monkeypatch ):
60+ monkeypatch .setattr (NetworkFTDIGPIO , "manager_cls" , ResourceManager )
61+ kwargs = {
62+ "host" : "exporter" ,
63+ "busnum" : 1 ,
64+ "devnum" : 39 ,
65+ "path" : "1-12.4.2" ,
66+ "vendor_id" : 0x0403 ,
67+ "model_id" : 0x6014 ,
68+ }
69+
70+ with pytest .raises (ValueError ):
71+ NetworkFTDIGPIO (target , name = None , index = 8 , interface = 1 , ** kwargs )
72+
73+ with pytest .raises (ValueError ):
74+ NetworkFTDIGPIO (target , name = None , index = 0 , interface = 0 , ** kwargs )
75+
76+
5177def test_ftdigpio_driver_create (target , monkeypatch ):
5278 monkeypatch .setattr (NetworkFTDIGPIO , "manager_cls" , ResourceManager )
5379 NetworkFTDIGPIO (
@@ -122,6 +148,12 @@ def close(self):
122148 target .deactivate (driver )
123149
124150
151+ def test_ftdigpio_driver_release_ignores_unknown_agent ():
152+ ftdigpiodriver ._shared_agents .clear ()
153+
154+ ftdigpiodriver ._release_agent ("exporter" , 1 , 39 , 1 )
155+
156+
125157def test_ftdigpio_agent (monkeypatch ):
126158 from labgrid .util .agents import ftdigpio
127159
@@ -259,6 +291,125 @@ def ctrl_transfer(self, request_type, request, value, index, data, timeout=None)
259291 ftdigpio .handle_get (0x0403 , 0x6014 , 1 , 39 , 1 , 3 )
260292
261293
294+ def test_ftdigpio_agent_configures_device_after_usb_error (monkeypatch ):
295+ from labgrid .util .agents import ftdigpio
296+
297+ class FakeEndpoint :
298+ bEndpointAddress = 0x02
299+
300+ def write (self , data , timeout = None ):
301+ pass
302+
303+ class FakeConfig :
304+ def __getitem__ (self , item ):
305+ assert item == (0 , 0 )
306+ return [FakeEndpoint ()]
307+
308+ class FakeDevice :
309+ bus = 1
310+ address = 39
311+
312+ def __init__ (self ):
313+ self .active_config_attempts = 0
314+ self .configured = False
315+ self .detached = []
316+
317+ def set_configuration (self ):
318+ self .configured = True
319+
320+ def is_kernel_driver_active (self , interface ):
321+ assert interface == 0
322+ return True
323+
324+ def detach_kernel_driver (self , interface ):
325+ self .detached .append (interface )
326+
327+ def get_active_configuration (self ):
328+ self .active_config_attempts += 1
329+ if self .active_config_attempts == 1 :
330+ raise ftdigpio .usb .core .USBError ("not configured" )
331+ return FakeConfig ()
332+
333+ def ctrl_transfer (self , request_type , request , value , index , data , timeout = None ):
334+ if request_type == ftdigpio .IN_REQTYPE and request == ftdigpio .SIO_READ_PINS :
335+ return b"\x00 "
336+ return None
337+
338+ device = FakeDevice ()
339+ monkeypatch .setattr (ftdigpio .usb .core , "find" , lambda ** kwargs : [device ])
340+ monkeypatch .setattr (ftdigpio .usb .util , "claim_interface" , lambda dev , interface : None )
341+ monkeypatch .setattr (ftdigpio .usb .util , "release_interface" , lambda dev , interface : None )
342+ monkeypatch .setattr (ftdigpio .usb .util , "dispose_resources" , lambda dev : None )
343+
344+ assert ftdigpio .handle_get (0x0403 , 0x6014 , 1 , 39 , 1 , 0 ) is False
345+ assert device .configured is True
346+ assert device .detached == [0 , 0 ]
347+
348+
349+ def test_ftdigpio_agent_rejects_missing_output_endpoint (monkeypatch ):
350+ from labgrid .util .agents import ftdigpio
351+
352+ class FakeEndpoint :
353+ bEndpointAddress = 0x81
354+
355+ class FakeConfig :
356+ def __getitem__ (self , item ):
357+ assert item == (0 , 0 )
358+ return [FakeEndpoint ()]
359+
360+ class FakeDevice :
361+ bus = 1
362+ address = 39
363+
364+ def set_configuration (self ):
365+ pass
366+
367+ def is_kernel_driver_active (self , interface ):
368+ return False
369+
370+ def get_active_configuration (self ):
371+ return FakeConfig ()
372+
373+ device = FakeDevice ()
374+ monkeypatch .setattr (ftdigpio .usb .core , "find" , lambda ** kwargs : [device ])
375+ monkeypatch .setattr (ftdigpio .usb .util , "claim_interface" , lambda dev , interface : None )
376+
377+ with pytest .raises (ValueError , match = "output endpoint" ):
378+ ftdigpio .FTDIGPIO (0x0403 , 0x6014 , 1 , 39 , 1 )
379+
380+
381+ def test_ftdigpio_agent_close_ignores_release_error (monkeypatch ):
382+ from labgrid .util .agents import ftdigpio
383+
384+ released = []
385+ disposed = []
386+ device = types .SimpleNamespace ()
387+ gpio = object .__new__ (ftdigpio .FTDIGPIO )
388+ gpio ._dev = device
389+ gpio ._interface = 0
390+
391+ def release_interface (dev , interface ):
392+ released .append ((dev , interface ))
393+ raise ftdigpio .usb .core .USBError ("already released" )
394+
395+ monkeypatch .setattr (ftdigpio .usb .util , "release_interface" , release_interface )
396+ monkeypatch .setattr (ftdigpio .usb .util , "dispose_resources" , lambda dev : disposed .append (dev ))
397+
398+ gpio .close ()
399+
400+ assert released == [(device , 0 )]
401+ assert disposed == [device ]
402+
403+
404+ def test_ftdigpio_agent_rejects_missing_device (monkeypatch ):
405+ from labgrid .util .agents import ftdigpio
406+
407+ monkeypatch .setattr (ftdigpio .usb .core , "find" , lambda ** kwargs : [])
408+
409+ with pytest .raises (ValueError , match = "not found" ):
410+ ftdigpio .FTDIGPIO ._find_device (0x0403 , 0x6014 , 1 , 39 )
411+
412+
262413def test_ftdigpio_agent_rejects_unsupported_index ():
263414 from labgrid .util .agents import ftdigpio
264415
0 commit comments