Skip to content

Commit 3f87f22

Browse files
committed
tests: cover GPIO digital input support
Document GpioDigitalInputDriver and add coverage for sysfsgpio input direction handling, cached direction reconfiguration, input driver reads, protocol compatibility, and labgrid-client io get routing. Co-developed-by: Felix Zwettler <Felix.Zwettler@duagon.com> Signed-off-by: Felix Zwettler <Felix.Zwettler@duagon.com> Signed-off-by: Ozan Durgut <ozan.durgut@analog.com>
1 parent d566e58 commit 3f87f22

3 files changed

Lines changed: 160 additions & 2 deletions

File tree

doc/configuration.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ Arguments:
651651
- invert (bool, default=False): optional, whether the logic level is inverted (active-low)
652652

653653
Used by:
654+
- `GpioDigitalInputDriver`_
654655
- `GpioDigitalOutputDriver`_
655656

656657
NetworkSysfsGPIO
@@ -684,6 +685,7 @@ Arguments:
684685
- invert (bool, default=False): optional, whether the logic level is inverted (active-low)
685686

686687
Used by:
688+
- `GpioDigitalInputDriver`_
687689
- `GpioDigitalOutputDriver`_
688690

689691
NetworkService
@@ -2559,6 +2561,30 @@ Implements:
25592561
Arguments:
25602562
- delay (float, default=2.0): delay in seconds between off and on
25612563

2564+
GpioDigitalInputDriver
2565+
~~~~~~~~~~~~~~~~~~~~~~
2566+
The :any:`GpioDigitalInputDriver` reads a digital signal from a GPIO line.
2567+
2568+
This driver configures GPIO lines via
2569+
`the sysfs kernel interface <https://www.kernel.org/doc/html/latest/gpio/sysfs.html>`__
2570+
as an input.
2571+
2572+
Binds to:
2573+
gpio:
2574+
- `SysfsGPIO`_
2575+
- `MatchedSysfsGPIO`_
2576+
- `NetworkSysfsGPIO`_
2577+
2578+
Implements:
2579+
- :any:`DigitalInputProtocol`
2580+
2581+
.. code-block:: yaml
2582+
2583+
GpioDigitalInputDriver: {}
2584+
2585+
Arguments:
2586+
- None
2587+
25622588
GpioDigitalOutputDriver
25632589
~~~~~~~~~~~~~~~~~~~~~~~
25642590
The :any:`GpioDigitalOutputDriver` writes a digital signal to a GPIO line.

tests/test_gpiodriver.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import types
2+
3+
import labgrid.driver.gpiodriver as gpiodriver
4+
from labgrid.driver.gpiodriver import GpioDigitalInputDriver, GpioDigitalOutputDriver
5+
from labgrid.remote.client import ClientSession
6+
from labgrid.resource.common import ResourceManager
7+
from labgrid.resource.remote import NetworkSysfsGPIO
8+
from labgrid.resource import SysfsGPIO
9+
10+
11+
class FakeWrapper:
12+
def __init__(self, host, proxy):
13+
self.host = host
14+
self.proxy = proxy
15+
16+
def load(self, name):
17+
assert name == 'sysfsgpio'
18+
return self.proxy
19+
20+
def close(self):
21+
pass
22+
23+
24+
def test_gpio_input_driver_get(target, monkeypatch):
25+
proxy = types.SimpleNamespace(calls=[])
26+
27+
def proxy_get(index, invert, direction):
28+
proxy.calls.append((index, invert, direction))
29+
return True
30+
31+
proxy.get = proxy_get
32+
33+
monkeypatch.setattr(gpiodriver, "AgentWrapper", lambda host: FakeWrapper(host, proxy))
34+
35+
SysfsGPIO(target, name=None, index=13, invert=True)
36+
driver = GpioDigitalInputDriver(target, name=None)
37+
38+
target.activate(driver)
39+
40+
assert driver.get() is True
41+
assert proxy.calls == [(13, True, 'in')]
42+
43+
target.deactivate(driver)
44+
45+
46+
def test_gpio_output_driver_implements_digital_input_protocol(target):
47+
SysfsGPIO(target, name=None, index=13, invert=False)
48+
driver = GpioDigitalOutputDriver(target, name=None)
49+
50+
assert target.get_driver("DigitalInputProtocol", activate=False) is driver
51+
52+
53+
def test_client_io_get_uses_configured_gpio_input_driver(target, monkeypatch, capsys):
54+
proxy = types.SimpleNamespace(calls=[])
55+
56+
def proxy_get(index, invert, direction):
57+
proxy.calls.append((index, invert, direction))
58+
return True
59+
60+
proxy.get = proxy_get
61+
62+
monkeypatch.setattr(gpiodriver, "AgentWrapper", lambda host: FakeWrapper(host, proxy))
63+
64+
SysfsGPIO(target, name="gpio_in", index=13, invert=False)
65+
GpioDigitalInputDriver(target, name="gpio_in")
66+
67+
session = object.__new__(ClientSession)
68+
session.args = types.SimpleNamespace(action="get", name="gpio_in")
69+
session.get_acquired_place = lambda: types.SimpleNamespace(name="test")
70+
session._get_target = lambda place: target
71+
72+
session.digital_io()
73+
74+
assert "digital IO gpio_in for place test is high" in capsys.readouterr().out
75+
assert proxy.calls == [(13, False, 'in')]
76+
77+
78+
def test_client_io_get_keeps_network_sysfs_output_fallback(target, monkeypatch, mocker):
79+
monkeypatch.setattr(NetworkSysfsGPIO, "manager_cls", ResourceManager)
80+
driver = types.SimpleNamespace(get=mocker.MagicMock(return_value=False))
81+
session = object.__new__(ClientSession)
82+
session.args = types.SimpleNamespace(action="get", name="gpio")
83+
session.get_acquired_place = lambda: types.SimpleNamespace(name="test")
84+
session._get_target = lambda place: target
85+
session._get_driver_or_new = mocker.MagicMock(return_value=driver)
86+
87+
NetworkSysfsGPIO(target, name="gpio", host="exporter", index=13, invert=False)
88+
89+
session.digital_io()
90+
91+
session._get_driver_or_new.assert_called_once_with(target, "GpioDigitalOutputDriver", name="gpio")
92+
driver.get.assert_called_once_with()

tests/test_sysfsgpioagent.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
import os
4+
from labgrid.util.agents import sysfsgpio
45
from labgrid.util.agents.sysfsgpio import GpioDigitalOutput
56
from tempfile import TemporaryDirectory
67

@@ -11,7 +12,6 @@ def __init__(self, **kwargs):
1112
index = kwargs['index']
1213
self.sysfs_mock_directory = TemporaryDirectory()
1314
GpioDigitalOutput._gpio_sysfs_path_prefix = self.sysfs_mock_directory.name
14-
GpioDigitalOutput._buffered_file_access = True
1515
export_file_path = os.path.join(self.sysfs_mock_directory.name, 'export')
1616
os.mknod(export_file_path)
1717
# Since there is no real device, writing to `export` does not create a corresponding
@@ -23,7 +23,7 @@ def __init__(self, **kwargs):
2323
assert export_content == str(index)
2424
self.gpio_line_directory = os.path.join(self.sysfs_mock_directory.name, f'gpio{index}')
2525
os.mkdir(self.gpio_line_directory)
26-
for control_file in ['direction', 'value']:
26+
for control_file in ['active_low', 'direction', 'value']:
2727
control_file_path = os.path.join(self.gpio_line_directory, control_file)
2828
print(f'creating control file `{control_file_path}`')
2929
os.mknod(control_file_path)
@@ -44,3 +44,43 @@ def test_set(self):
4444
for val in [True, False, True, False]:
4545
gpio_line.set(val)
4646
assert gpio_line.get() == val
47+
48+
def test_output_direction(self):
49+
gpio_line = TestGpioAgent.GpioDigitalOutputMock(index=13, invert=False)
50+
direction_file_path = os.path.join(gpio_line.gpio_line_directory, 'direction')
51+
with open(direction_file_path, mode='rb') as direction_file:
52+
assert direction_file.read() == b'out'
53+
54+
def test_input_direction(self):
55+
gpio_line = TestGpioAgent.GpioDigitalOutputMock(index=13, invert=False, direction='in')
56+
direction_file_path = os.path.join(gpio_line.gpio_line_directory, 'direction')
57+
value_file_path = os.path.join(gpio_line.gpio_line_directory, 'value')
58+
59+
with open(direction_file_path, mode='rb') as direction_file:
60+
assert direction_file.read() == b'in'
61+
62+
with open(value_file_path, mode='wb') as value_file:
63+
value_file.write(b'1')
64+
65+
assert gpio_line.get() is True
66+
67+
def test_invalid_direction(self):
68+
with pytest.raises(ValueError, match='direction'):
69+
GpioDigitalOutput(index=13, invert=False, direction='invalid')
70+
71+
def test_cached_line_reconfigures_direction(self):
72+
with TemporaryDirectory() as sysfs_mock_directory:
73+
GpioDigitalOutput._gpio_sysfs_path_prefix = sysfs_mock_directory
74+
sysfsgpio._gpios.clear()
75+
gpio_line_directory = os.path.join(sysfs_mock_directory, 'gpio13')
76+
os.mkdir(gpio_line_directory)
77+
for control_file in ['active_low', 'direction', 'value']:
78+
os.mknod(os.path.join(gpio_line_directory, control_file))
79+
80+
sysfsgpio.handle_set(13, False, True)
81+
sysfsgpio.handle_get(13, False, 'in')
82+
sysfsgpio.handle_set(13, False, False)
83+
84+
direction_file_path = os.path.join(gpio_line_directory, 'direction')
85+
with open(direction_file_path, mode='rb') as direction_file:
86+
assert direction_file.read() == b'out'

0 commit comments

Comments
 (0)