1
0
mirror of https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git synced 2026-10-24 00:48:53 +03:00

bindings: python: use unittest to implement a proper test-suite

Currently the only tests we have for python bindings are in a simple
script that makes a lot of assumptions about the environment and
doesn't even work anymore with recent kernels.

Remove it and replace it with a proper test-suite implemented using
libgpiod and the standard python unittest package.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
This commit is contained in:
Bartosz Golaszewski
2019-08-07 14:28:30 +02:00
parent b33cb2aa56
commit 452696601a
8 changed files with 989 additions and 448 deletions

9
TODO
View File

@@ -25,15 +25,6 @@ and is partially functional.
----------
* use the python unit testing library for python bindings and reuse
libgpiod-test
The best candidate for the testing framework is the standard unittest module
available in cpython. We'd need however to wrap libgpiod-test in a C module
for python just like we did for the bindings of the core C library.
----------
* implement a simple daemon for controlling GPIOs in C together with a client
program

View File

@@ -16,3 +16,9 @@ gpiod_la_CFLAGS = -I$(top_srcdir)/include/
gpiod_la_CFLAGS += -Wall -Wextra -g $(PYTHON_CPPFLAGS)
gpiod_la_LDFLAGS = -module -avoid-version
gpiod_la_LIBADD = $(top_builddir)/lib/libgpiod.la $(PYTHON_LIBS)
if WITH_TESTS
SUBDIRS += tests
endif

View File

@@ -6,8 +6,7 @@
# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
#
EXTRA_DIST = gpiod_tests.py \
gpiodetect.py \
EXTRA_DIST = gpiodetect.py \
gpiofind.py \
gpioget.py \
gpioinfo.py \

View File

@@ -1,437 +0,0 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of libgpiod.
#
# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
#
'''Misc tests of libgpiod python bindings.
These tests assume that at least one dummy gpiochip is present in the
system and that it's detected as gpiochip0.
'''
import gpiod
import select
test_cases = []
def add_test(name, func):
global test_cases
test_cases.append((name, func))
def fire_line_event(chip, offset, rising):
path = '/sys/kernel/debug/gpio-mockup-event/{}/{}'.format(chip, offset)
with open(path, 'w') as fd:
fd.write('{}'.format(1 if rising else 0))
def print_event(event):
print('type: {}'.format('rising' if event.type == gpiod.LineEvent.RISING_EDGE else 'falling'))
print('timestamp: {}.{}'.format(event.sec, event.nsec))
print('source line offset: {}'.format(event.source.offset()))
def chip_open_default_lookup():
by_name = gpiod.Chip('gpiochip0')
by_path = gpiod.Chip('/dev/gpiochip0')
by_label = gpiod.Chip('gpio-mockup-A')
by_number = gpiod.Chip('0')
print('All good')
by_name.close()
by_path.close()
by_label.close()
by_number.close()
add_test('Open a GPIO chip using different lookup modes', chip_open_default_lookup)
def chip_open_different_modes():
chip = gpiod.Chip('/dev/gpiochip0', gpiod.Chip.OPEN_BY_PATH)
chip.close()
chip = gpiod.Chip('gpiochip0', gpiod.Chip.OPEN_BY_NAME)
chip.close()
chip = gpiod.Chip('gpio-mockup-A', gpiod.Chip.OPEN_BY_LABEL)
chip.close()
chip = gpiod.Chip('0', gpiod.Chip.OPEN_BY_NUMBER)
chip.close()
print('All good')
add_test('Open a GPIO chip using different modes', chip_open_different_modes)
def chip_open_nonexistent():
try:
chip = gpiod.Chip('/nonexistent_gpiochip')
except OSError as ex:
print('Exception raised as expected: {}'.format(ex))
return
assert False, 'OSError expected'
add_test('Try to open a nonexistent GPIO chip', chip_open_nonexistent)
def chip_open_no_args():
try:
chip = gpiod.Chip()
except TypeError:
print('Error as expected')
return
assert False, 'TypeError expected'
add_test('Open a GPIO chip without arguments', chip_open_no_args)
def chip_use_after_close():
chip = gpiod.Chip('gpiochip0')
line = chip.get_line(2)
chip.close()
try:
chip.name()
except ValueError as ex:
print('Error as expected: {}'.format(ex))
try:
line = chip.get_line(3)
except ValueError as ex:
print('Error as expected: {}'.format(ex))
return
assert False, 'ValueError expected'
add_test('Use a GPIO chip after closing it', chip_use_after_close)
def chip_with_statement():
with gpiod.Chip('gpiochip0') as chip:
print('Chip name in controlled execution: {}'.format(chip.name()))
line = chip.get_line(3)
print('Got line from chip in controlled execution: {}'.format(line.name()))
add_test('Use a GPIO chip in controlled execution', chip_with_statement)
def chip_info():
chip = gpiod.Chip('gpiochip0')
print('name: {}'.format(chip.name()))
print('label: {}'.format(chip.label()))
print('lines: {}'.format(chip.num_lines()))
chip.close()
add_test('Print chip info', chip_info)
def print_chip():
chip = gpiod.Chip('/dev/gpiochip0')
print(chip)
chip.close()
add_test('Print chip object', print_chip)
def create_chip_without_arguments():
try:
chip = gpiod.Chip()
except TypeError as ex:
print('Exception raised as expected: {}'.format(ex))
return
assert False, 'TypeError expected'
add_test('Create chip object without arguments', create_chip_without_arguments)
def create_line_object():
try:
line = gpiod.Line()
except NotImplementedError:
print('Error as expected')
return
assert False, 'NotImplementedError expected'
add_test('Create a line object - should fail', create_line_object)
def print_line():
chip = gpiod.Chip('gpio-mockup-A')
line = chip.get_line(3)
print(line)
chip.close()
add_test('Print line object', print_line)
def find_line():
line = gpiod.find_line('gpio-mockup-A-4')
print('found line - offset: {}'.format(line.offset()))
line.owner().close()
add_test('Find line globally', find_line)
def create_empty_line_bulk():
try:
lines = gpiod.LineBulk()
except TypeError:
print('Error as expected')
return
assert False, 'TypeError expected'
add_test('Create a line bulk object - should fail', create_empty_line_bulk)
def get_lines():
chip = gpiod.Chip('gpio-mockup-A')
print('getting four lines from chip')
lines = chip.get_lines([2, 4, 5, 7])
print('Retrieved lines:')
for line in lines:
print(line)
chip.close()
add_test('Get lines from chip', get_lines)
def get_all_lines():
chip = gpiod.Chip('gpio-mockup-A')
print('Retrieving all lines from chip')
lines = chip.get_all_lines()
print('Retrieved lines:')
for line in lines:
print(line)
chip.close()
add_test('Get all lines from chip', get_all_lines)
def find_lines():
chip = gpiod.Chip('gpiochip0')
print('looking up lines by names')
lines = chip.find_lines(['gpio-mockup-A-3', 'gpio-mockup-A-4', 'gpio-mockup-A-7'])
print('Retrieved lines:')
for line in lines:
print(line)
chip.close()
add_test('Find multiple lines by name', find_lines)
def find_lines_one_bad():
chip = gpiod.Chip('gpiochip0')
print('looking up lines by names')
try:
lines = chip.find_lines(['gpio-mockup-A-3', 'nonexistent', 'gpio-mockup-A-7'])
except TypeError as ex:
print('Error as expected')
return
assert False, 'TypeError expected'
add_test('Find multiple lines but one line name is non-existent', find_lines_one_bad)
def create_line_bulk_from_lines():
chip = gpiod.Chip('gpio-mockup-A')
line1 = chip.get_line(2)
line2 = chip.get_line(4)
line3 = chip.get_line(6)
lines = gpiod.LineBulk([line1, line2, line3])
print('Created LineBulk:')
print(lines)
chip.close()
add_test('Create a LineBulk from a list of lines', create_line_bulk_from_lines)
def line_bulk_to_list():
chip = gpiod.Chip('gpio-mockup-A')
lines = chip.get_lines((1, 2, 3))
print(lines.to_list())
chip.close()
add_test('Convert a LineBulk to a list', line_bulk_to_list)
def line_flags():
chip = gpiod.Chip('gpiochip0')
line = chip.get_line(3)
print('line is used: {}'.format(line.is_used()))
print('line is requested: {}'.format(line.is_requested()))
print('requesting line')
line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_OUT,
flags=(gpiod.LINE_REQ_FLAG_OPEN_DRAIN | gpiod.LINE_REQ_FLAG_ACTIVE_LOW))
print('line is used: {}'.format(line.is_used()))
print('line is open drain: {}'.format(line.is_open_drain()))
print('line is open source: {}'.format(line.is_open_source()))
print('line is requested: {}'.format(line.is_requested()))
print('line is active-low: {}'.format(
"True" if line.active_state() == gpiod.Line.ACTIVE_LOW else "False"))
chip.close()
add_test('Check various line flags', line_flags)
def get_value_single_line():
chip = gpiod.Chip('gpio-mockup-A')
line = chip.get_line(2)
line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_IN)
print('line value: {}'.format(line.get_value()))
chip.close()
add_test('Get value - single line', get_value_single_line)
def set_value_single_line():
chip = gpiod.Chip('gpiochip0')
line = chip.get_line(3)
line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_IN)
print('line value before: {}'.format(line.get_value()))
line.release()
line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_OUT)
line.set_value(1)
line.release()
line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_IN)
print('line value after: {}'.format(line.get_value()))
chip.close()
add_test('Set value - single line', set_value_single_line)
def request_line_with_default_values():
chip = gpiod.Chip('gpiochip0')
line = chip.get_line(3)
print('requesting a single line with a default value')
line.request(consumer='gpiod_test.py', type=gpiod.LINE_REQ_DIR_OUT, default_vals=[ 1 ])
print('line value after request: {}'.format(line.get_value()))
chip.close()
add_test('Request line with default value', request_line_with_default_values)
def request_multiple_lines_with_default_values():
chip = gpiod.Chip('gpiochip0')
lines = chip.get_lines(( 1, 2, 3, 4, 5 ))
print('requesting lines with default values')
lines.request(consumer='gpiod_test.py', type=gpiod.LINE_REQ_DIR_OUT, default_vals=( 1, 0, 1, 0, 1 ))
print('line values after request: {}'.format(lines.get_values()))
chip.close()
add_test('Request multiple lines with default values', request_multiple_lines_with_default_values)
def request_line_incorrect_number_of_def_vals():
with gpiod.Chip('gpiochip0') as chip:
lines = chip.get_lines(( 1, 2, 3, 4, 5 ))
print('requesting lines with incorrect number of default values')
try:
lines.request(consumer='gpiod_test.py',
type=gpiod.LINE_REQ_DIR_OUT,
default_vals=( 1, 0, 1, 0 ))
except TypeError:
print('TypeError raised as expected')
return
assert False, 'TypeError expected'
add_test('Request with incorrect number of default values', request_line_incorrect_number_of_def_vals)
def line_event_single_line():
chip = gpiod.Chip('gpiochip0')
line = chip.get_line(1)
print('requesting line for events')
line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
print('generating a line event')
fire_line_event('gpiochip0', 1, True)
assert line.event_wait(sec=1), 'Expected a line event to occur'
print('event received')
event = line.event_read()
print_event(event)
chip.close()
add_test('Monitor a single line for events', line_event_single_line)
def line_event_multiple_lines():
chip = gpiod.Chip('gpiochip0')
lines = chip.get_lines((1, 2, 3, 4, 5))
print('requesting lines for events')
lines.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
print('generating two line events')
fire_line_event('gpiochip0', 1, True)
fire_line_event('gpiochip0', 2, True)
events = lines.event_wait(sec=1)
assert events is not None and len(events) == 2, 'Expected to receive two line events'
print('events received:')
for line in events:
event = line.event_read()
print_event(event)
chip.close()
add_test('Monitor multiple lines for events', line_event_multiple_lines)
def line_event_poll_fd():
chip = gpiod.Chip('gpiochip0')
lines = chip.get_lines((1, 2, 3, 4, 5, 6))
print('requesting lines for events')
lines.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
print('generating three line events')
fire_line_event('gpiochip0', 2, True)
fire_line_event('gpiochip0', 3, False)
fire_line_event('gpiochip0', 5, True)
print('retrieving the file descriptors')
inputs = []
fd_mapping = {}
for line in lines:
inputs.append(line.event_get_fd())
fd_mapping[line.event_get_fd()] = line
readable, writable, exceptional = select.select(inputs, [], inputs, 1.0)
assert len(readable) == 3, 'Expected to receive three line events'
print('events received:')
for fd in readable:
line = fd_mapping[fd]
event = line.event_read()
print_event(event)
chip.close()
add_test('Monitor multiple lines using their file descriptors', line_event_poll_fd)
def line_event_repr():
with gpiod.Chip('gpiochip0') as chip:
line = chip.get_line(1)
print('requesting line for events')
line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
print('generating a line event')
fire_line_event('gpiochip0', 1, True)
assert line.event_wait(sec=1), 'Expected a line event to occur'
print('event received: {}'.format(line.event_read()))
add_test('Line event string repr', line_event_repr)
print('API version is {}'.format(gpiod.version_string()))
for name, func in test_cases:
print('==============================================')
print('{}:'.format(name))
print()
func()

View File

@@ -0,0 +1,18 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of libgpiod.
#
# Copyright (C) 2019 Bartosz Golaszewski <bgolaszewski@baylibre.com>
#
bin_SCRIPTS = gpiod_py_test.py
pyexec_LTLIBRARIES = gpiomockup.la
gpiomockup_la_SOURCES = gpiomockupmodule.c
gpiomockup_la_CFLAGS = -I$(top_srcdir)/tests/mockup/
gpiomockup_la_CFLAGS += -Wall -Wextra -g $(PYTHON_CPPFLAGS)
gpiomockup_la_LDFLAGS = -module -avoid-version
gpiomockup_la_LIBADD = $(top_builddir)/tests/mockup/libgpiomockup.la
gpiomockup_la_LIBADD += $(PYTHON_LIBS)

View File

@@ -0,0 +1,650 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of libgpiod.
#
# Copyright (C) 2019 Bartosz Golaszewski <bgolaszewski@baylibre.com>
#
import errno
import gpiod
import gpiomockup
import os
import select
import threading
import unittest
from packaging import version
mockup = None
default_consumer = 'gpiod-py-test'
class MockupTestCase(unittest.TestCase):
chip_sizes = None
flags = 0
def setUp(self):
mockup.probe(self.chip_sizes, flags=self.flags)
def tearDown(self):
mockup.remove()
class EventThread(threading.Thread):
def __init__(self, chip_idx, line_offset, freq):
threading.Thread.__init__(self)
self.chip_idx = chip_idx
self.line_offset = line_offset
self.freq = freq
self.lock = threading.Lock()
self.cond = threading.Condition(self.lock)
self.should_stop = False
def run(self):
i = 0;
while True:
with self.lock:
if self.should_stop:
break;
if not self.cond.wait(float(self.freq) / 1000):
mockup.chip_set_pull(self.chip_idx,
self.line_offset, i % 2)
i += 1
def stop(self):
with self.lock:
self.should_stop = True
self.cond.notify_all()
def __enter__(self):
self.start()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.stop()
self.join()
def check_kernel(major, minor, release):
current = os.uname().release
required = '{}.{}.{}'.format(major, minor, release)
if version.parse(current) < version.parse(required):
raise NotImplementedError(
'linux kernel version must be at least {} - got {}'.format(required, current))
#
# Chip test cases
#
class ChipOpen(MockupTestCase):
chip_sizes = ( 8, 8, 8 )
def test_open_chip_by_name(self):
with gpiod.Chip(mockup.chip_name(1), gpiod.Chip.OPEN_BY_NAME) as chip:
self.assertEqual(chip.name(), mockup.chip_name(1))
def test_open_chip_by_path(self):
with gpiod.Chip(mockup.chip_path(1), gpiod.Chip.OPEN_BY_PATH) as chip:
self.assertEqual(chip.name(), mockup.chip_name(1))
def test_open_chip_by_num(self):
with gpiod.Chip('{}'.format(mockup.chip_num(1)),
gpiod.Chip.OPEN_BY_NUMBER) as chip:
self.assertEqual(chip.name(), mockup.chip_name(1))
def test_open_chip_by_label(self):
with gpiod.Chip('gpio-mockup-B', gpiod.Chip.OPEN_BY_LABEL) as chip:
self.assertEqual(chip.name(), mockup.chip_name(1))
def test_lookup_chip_by_name(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
self.assertEqual(chip.name(), mockup.chip_name(1))
def test_lookup_chip_by_path(self):
with gpiod.Chip(mockup.chip_path(1)) as chip:
self.assertEqual(chip.name(), mockup.chip_name(1))
def test_lookup_chip_by_num(self):
with gpiod.Chip('{}'.format(mockup.chip_num(1))) as chip:
self.assertEqual(chip.name(), mockup.chip_name(1))
def test_lookup_chip_by_label(self):
with gpiod.Chip('gpio-mockup-B') as chip:
self.assertEqual(chip.name(), mockup.chip_name(1))
def test_nonexistent_chip(self):
with self.assertRaises(FileNotFoundError):
chip = gpiod.Chip('nonexistent-chip')
def test_open_chip_no_arguments(self):
with self.assertRaises(TypeError):
chip = gpiod.Chip()
class ChipClose(MockupTestCase):
chip_sizes = ( 8, )
def test_use_chip_after_close(self):
chip = gpiod.Chip(mockup.chip_name(0))
self.assertEqual(chip.name(), mockup.chip_name(0))
chip.close()
with self.assertRaises(ValueError):
chip.name()
class ChipInfo(MockupTestCase):
chip_sizes = ( 16, )
def test_chip_get_info(self):
chip = gpiod.Chip(mockup.chip_name(0))
self.assertEqual(chip.name(), mockup.chip_name(0))
self.assertEqual(chip.label(), 'gpio-mockup-A')
self.assertEqual(chip.num_lines(), 16)
class ChipGetLines(MockupTestCase):
chip_sizes = ( 8, 8, 4 )
flags = gpiomockup.Mockup.FLAG_NAMED_LINES
def test_get_single_line_by_offset(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
line = chip.get_line(4)
self.assertEqual(line.name(), 'gpio-mockup-B-4')
def test_find_single_line_by_name(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
line = chip.find_line('gpio-mockup-B-4')
self.assertEqual(line.offset(), 4)
def test_get_single_line_invalid_offset(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
with self.assertRaises(OSError) as err_ctx:
line = chip.get_line(11)
self.assertEqual(err_ctx.exception.errno, errno.EINVAL)
def test_find_single_line_nonexistent(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
line = chip.find_line('nonexistent-line')
self.assertEqual(line, None)
def test_get_multiple_lines_by_offsets_in_tuple(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
lines = chip.get_lines(( 1, 3, 6, 7 )).to_list()
self.assertEqual(len(lines), 4)
self.assertEqual(lines[0].name(), 'gpio-mockup-B-1')
self.assertEqual(lines[1].name(), 'gpio-mockup-B-3')
self.assertEqual(lines[2].name(), 'gpio-mockup-B-6')
self.assertEqual(lines[3].name(), 'gpio-mockup-B-7')
def test_get_multiple_lines_by_offsets_in_list(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
lines = chip.get_lines([ 1, 3, 6, 7 ]).to_list()
self.assertEqual(len(lines), 4)
self.assertEqual(lines[0].name(), 'gpio-mockup-B-1')
self.assertEqual(lines[1].name(), 'gpio-mockup-B-3')
self.assertEqual(lines[2].name(), 'gpio-mockup-B-6')
self.assertEqual(lines[3].name(), 'gpio-mockup-B-7')
def test_find_multiple_lines_by_names_in_tuple(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
lines = chip.find_lines(( 'gpio-mockup-B-0',
'gpio-mockup-B-3',
'gpio-mockup-B-4',
'gpio-mockup-B-6' )).to_list()
self.assertEqual(len(lines), 4)
self.assertEqual(lines[0].offset(), 0)
self.assertEqual(lines[1].offset(), 3)
self.assertEqual(lines[2].offset(), 4)
self.assertEqual(lines[3].offset(), 6)
def test_find_multiple_lines_by_names_in_list(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
lines = chip.find_lines([ 'gpio-mockup-B-0',
'gpio-mockup-B-3',
'gpio-mockup-B-4',
'gpio-mockup-B-6' ]).to_list()
self.assertEqual(len(lines), 4)
self.assertEqual(lines[0].offset(), 0)
self.assertEqual(lines[1].offset(), 3)
self.assertEqual(lines[2].offset(), 4)
self.assertEqual(lines[3].offset(), 6)
def test_get_multiple_lines_invalid_offset(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
with self.assertRaises(OSError) as err_ctx:
line = chip.get_lines(( 1, 3, 11, 7 ))
self.assertEqual(err_ctx.exception.errno, errno.EINVAL)
def test_find_multiple_lines_nonexistent(self):
with gpiod.Chip(mockup.chip_name(1)) as chip:
with self.assertRaises(TypeError):
lines = chip.find_lines(( 'gpio-mockup-B-0',
'nonexistent-line',
'gpio-mockup-B-4',
'gpio-mockup-B-6' )).to_list()
def test_get_all_lines(self):
with gpiod.Chip(mockup.chip_name(2)) as chip:
lines = chip.get_all_lines().to_list()
self.assertEqual(len(lines), 4)
self.assertEqual(lines[0].name(), 'gpio-mockup-C-0')
self.assertEqual(lines[1].name(), 'gpio-mockup-C-1')
self.assertEqual(lines[2].name(), 'gpio-mockup-C-2')
self.assertEqual(lines[3].name(), 'gpio-mockup-C-3')
#
# Line test cases
#
class LineGlobalFindLine(MockupTestCase):
chip_sizes = ( 4, 8, 16 )
flags = gpiomockup.Mockup.FLAG_NAMED_LINES
def test_global_find_line_function(self):
line = gpiod.find_line('gpio-mockup-B-4')
self.assertNotEqual(line, None)
try:
self.assertEqual(line.owner().label(), 'gpio-mockup-B')
self.assertEqual(line.offset(), 4)
finally:
line.owner().close()
def test_global_find_line_function_nonexistent(self):
line = gpiod.find_line('nonexistent-line')
self.assertEqual(line, None)
class LineInfo(MockupTestCase):
chip_sizes = ( 8, )
flags = gpiomockup.Mockup.FLAG_NAMED_LINES
def test_unexported_line(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(4)
self.assertEqual(line.offset(), 4)
self.assertEqual(line.name(), 'gpio-mockup-A-4')
self.assertEqual(line.direction(), gpiod.Line.DIRECTION_INPUT)
self.assertEqual(line.active_state(), gpiod.Line.ACTIVE_HIGH)
self.assertEqual(line.consumer(), None)
self.assertFalse(line.is_used())
self.assertFalse(line.is_requested())
def test_exported_line(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(4)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_OUT,
flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
self.assertEqual(line.offset(), 4)
self.assertEqual(line.name(), 'gpio-mockup-A-4')
self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
self.assertEqual(line.active_state(), gpiod.Line.ACTIVE_LOW)
self.assertEqual(line.consumer(), default_consumer)
self.assertTrue(line.is_used())
self.assertTrue(line.is_requested())
def test_exported_line_with_flags(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(4)
flags = (gpiod.LINE_REQ_FLAG_ACTIVE_LOW |
gpiod.LINE_REQ_FLAG_OPEN_DRAIN)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_OUT,
flags=flags)
self.assertEqual(line.offset(), 4)
self.assertEqual(line.name(), 'gpio-mockup-A-4')
# FIXME Uncomment the line below once this issue is fixed in the kernel.
#self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
self.assertEqual(line.active_state(), gpiod.Line.ACTIVE_LOW)
self.assertEqual(line.consumer(), default_consumer)
self.assertTrue(line.is_used())
self.assertTrue(line.is_requested())
self.assertTrue(line.is_open_drain())
self.assertFalse(line.is_open_source())
class LineValues(MockupTestCase):
chip_sizes = ( 8, )
def test_get_value_single_line(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_IN)
self.assertEqual(line.get_value(), 0)
mockup.chip_set_pull(0, 3, 1)
self.assertEqual(line.get_value(), 1)
def test_set_value_single_line(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_OUT)
line.set_value(1)
self.assertEqual(mockup.chip_get_value(0, 3), 1)
line.set_value(0)
self.assertEqual(mockup.chip_get_value(0, 3), 0)
def test_set_value_with_default_value_argument(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_OUT,
default_val=1)
self.assertEqual(mockup.chip_get_value(0, 3), 1)
def test_get_value_multiple_lines(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
lines = chip.get_lines(( 0, 3, 4, 6 ))
lines.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_IN)
self.assertEqual(lines.get_values(), [ 0, 0, 0, 0 ])
mockup.chip_set_pull(0, 0, 1)
mockup.chip_set_pull(0, 4, 1)
mockup.chip_set_pull(0, 6, 1)
self.assertEqual(lines.get_values(), [ 1, 0, 1, 1 ])
def test_set_value_multiple_lines(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
lines = chip.get_lines(( 0, 3, 4, 6 ))
lines.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_OUT)
lines.set_values(( 1, 0, 1, 1 ))
self.assertEqual(mockup.chip_get_value(0, 0), 1)
self.assertEqual(mockup.chip_get_value(0, 3), 0)
self.assertEqual(mockup.chip_get_value(0, 4), 1)
self.assertEqual(mockup.chip_get_value(0, 6), 1)
lines.set_values(( 0, 0, 1, 0 ))
self.assertEqual(mockup.chip_get_value(0, 0), 0)
self.assertEqual(mockup.chip_get_value(0, 3), 0)
self.assertEqual(mockup.chip_get_value(0, 4), 1)
self.assertEqual(mockup.chip_get_value(0, 6), 0)
def test_set_multiple_values_with_default_vals_argument(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
lines = chip.get_lines(( 0, 3, 4, 6 ))
lines.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_OUT,
default_vals=( 1, 0, 1, 1 ))
self.assertEqual(mockup.chip_get_value(0, 0), 1)
self.assertEqual(mockup.chip_get_value(0, 3), 0)
self.assertEqual(mockup.chip_get_value(0, 4), 1)
self.assertEqual(mockup.chip_get_value(0, 6), 1)
def test_get_value_active_low(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_IN,
flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
self.assertEqual(line.get_value(), 1)
mockup.chip_set_pull(0, 3, 1)
self.assertEqual(line.get_value(), 0)
def test_set_value_active_low(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_OUT,
flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
line.set_value(1)
self.assertEqual(mockup.chip_get_value(0, 3), 0)
line.set_value(0)
self.assertEqual(mockup.chip_get_value(0, 3), 1)
class LineRequestBehavior(MockupTestCase):
chip_sizes = ( 8, )
def test_line_export_release(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_IN)
self.assertTrue(line.is_requested())
self.assertEqual(line.get_value(), 0)
line.release()
self.assertFalse(line.is_requested())
def test_line_request_twice_two_calls(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_IN)
with self.assertRaises(OSError):
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_IN)
def test_line_request_twice_in_bulk(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
lines = chip.get_lines(( 2, 3, 6, 6 ))
with self.assertRaises(OSError):
lines.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_IN)
def test_use_value_unrequested(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
with self.assertRaises(OSError):
line.get_value()
#
# Iterator test cases
#
class ChipIterator(MockupTestCase):
chip_sizes = ( 4, 8, 16 )
def test_iterate_over_chips(self):
gotA = False
gotB = False
gotC = False
for chip in gpiod.ChipIter():
if chip.label() == 'gpio-mockup-A':
gotA = True
elif chip.label() == 'gpio-mockup-B':
gotB = True
elif chip.label() == 'gpio-mockup-C':
gotC = True
self.assertTrue(gotA)
self.assertTrue(gotB)
self.assertTrue(gotC)
class LineIterator(MockupTestCase):
chip_sizes = ( 4, )
def test_iterate_over_lines(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
count = 0
for line in gpiod.LineIter(chip):
self.assertEqual(line.offset(), count)
count += 1
self.assertEqual(count, chip.num_lines())
class LineBulkIter(MockupTestCase):
chip_sizes = ( 4, )
def test_line_bulk_iterator(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
lines = chip.get_all_lines()
count = 0
for line in lines:
self.assertEqual(line.offset(), count)
count += 1
self.assertEqual(count, chip.num_lines())
#
# Event test cases
#
class EventSingleLine(MockupTestCase):
chip_sizes = ( 8, )
def test_single_line_rising_edge_event(self):
with EventThread(0, 4, 200):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(4)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_RISING_EDGE)
self.assertTrue(line.event_wait(sec=1))
event = line.event_read()
self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
self.assertEqual(event.source.offset(), 4)
def test_single_line_falling_edge_event(self):
with EventThread(0, 4, 200):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(4)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_FALLING_EDGE)
self.assertTrue(line.event_wait(sec=1))
event = line.event_read()
self.assertEqual(event.type, gpiod.LineEvent.FALLING_EDGE)
self.assertEqual(event.source.offset(), 4)
def test_single_line_both_edges_events(self):
with EventThread(0, 4, 200):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(4)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_BOTH_EDGES)
self.assertTrue(line.event_wait(sec=1))
event = line.event_read()
self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
self.assertEqual(event.source.offset(), 4)
self.assertTrue(line.event_wait(sec=1))
event = line.event_read()
self.assertEqual(event.type, gpiod.LineEvent.FALLING_EDGE)
self.assertEqual(event.source.offset(), 4)
def test_single_line_both_edges_events_active_low(self):
with EventThread(0, 4, 200):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(4)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_BOTH_EDGES,
flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
self.assertTrue(line.event_wait(sec=1))
event = line.event_read()
self.assertEqual(event.type, gpiod.LineEvent.FALLING_EDGE)
self.assertEqual(event.source.offset(), 4)
self.assertTrue(line.event_wait(sec=1))
event = line.event_read()
self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
self.assertEqual(event.source.offset(), 4)
class EventBulk(MockupTestCase):
chip_sizes = ( 8, )
def test_watch_multiple_lines_for_events(self):
with EventThread(0, 2, 200):
with gpiod.Chip(mockup.chip_name(0)) as chip:
lines = chip.get_lines(( 0, 1, 2, 3, 4 ))
lines.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_BOTH_EDGES)
event_lines = lines.event_wait(sec=1)
self.assertEqual(len(event_lines), 1)
line = event_lines[0]
event = line.event_read()
self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
self.assertEqual(event.source.offset(), 2)
event_lines = lines.event_wait(sec=1)
self.assertEqual(len(event_lines), 1)
line = event_lines[0]
event = line.event_read()
self.assertEqual(event.type, gpiod.LineEvent.FALLING_EDGE)
self.assertEqual(event.source.offset(), 2)
class EventValues(MockupTestCase):
chip_sizes = ( 8, )
def test_request_for_events_get_value(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_BOTH_EDGES)
self.assertEqual(line.get_value(), 0)
mockup.chip_set_pull(0, 3, 1)
self.assertEqual(line.get_value(), 1)
def test_request_for_events_get_value_active_low(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_BOTH_EDGES,
flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
self.assertEqual(line.get_value(), 1)
mockup.chip_set_pull(0, 3, 1)
self.assertEqual(line.get_value(), 0)
class EventFileDescriptor(MockupTestCase):
chip_sizes = ( 8, )
def test_event_get_fd(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_BOTH_EDGES)
fd = line.event_get_fd();
self.assertGreaterEqual(fd, 0)
def test_event_get_fd_not_requested(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
with self.assertRaises(OSError):
fd = line.event_get_fd();
def test_event_get_fd_requested_for_values(self):
with gpiod.Chip(mockup.chip_name(0)) as chip:
line = chip.get_line(3)
line.request(consumer=default_consumer,
type=gpiod.LINE_REQ_DIR_IN)
with self.assertRaises(OSError):
fd = line.event_get_fd();
def test_event_fd_polling(self):
with EventThread(0, 2, 200):
with gpiod.Chip(mockup.chip_name(0)) as chip:
lines = chip.get_lines(( 0, 1, 2, 3, 4, 5, 6 ))
lines.request(consumer=default_consumer,
type=gpiod.LINE_REQ_EV_BOTH_EDGES)
inputs = []
for line in lines:
inputs.append(line.event_get_fd())
readable, writable, exceptional = select.select(inputs, [],
inputs, 1.0)
self.assertEqual(len(readable), 1)
event = lines.to_list()[2].event_read()
self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
self.assertEqual(event.source.offset(), 2)
#
# Main
#
if __name__ == '__main__':
check_kernel(5, 2, 7)
mockup = gpiomockup.Mockup()
unittest.main()

View File

@@ -0,0 +1,313 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
* This file is part of libgpiod.
*
* Copyright (C) 2019 Bartosz Golaszewski <bgolaszewski@baylibre.com>
*/
#include <Python.h>
#include <gpio-mockup.h>
typedef struct {
PyObject_HEAD
struct gpio_mockup *mockup;
} gpiomockup_MockupObject;
enum {
gpiomockup_FLAG_NAMED_LINES = 1,
};
static int gpiomockup_Mockup_init(gpiomockup_MockupObject *self,
PyObject *Py_UNUSED(ignored0),
PyObject *Py_UNUSED(ignored1))
{
Py_BEGIN_ALLOW_THREADS;
self->mockup = gpio_mockup_new();
Py_END_ALLOW_THREADS;
if (!self->mockup) {
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
return 0;
}
static void gpiomockup_Mockup_dealloc(gpiomockup_MockupObject *self)
{
if (self->mockup) {
Py_BEGIN_ALLOW_THREADS;
gpio_mockup_unref(self->mockup);
Py_END_ALLOW_THREADS;
}
PyObject_Del(self);
}
static PyObject *gpiomockup_Mockup_probe(gpiomockup_MockupObject *self,
PyObject *args, PyObject *kwds)
{
static char *kwlist[] = { "chip_sizes",
"flags",
NULL };
PyObject *chip_sizes_obj, *iter, *next;
unsigned int *chip_sizes;
int ret, flags = 0, i;
Py_ssize_t num_chips;
ret = PyArg_ParseTupleAndKeywords(args, kwds, "O|i", kwlist,
&chip_sizes_obj, &flags);
if (!ret)
return NULL;
num_chips = PyObject_Size(chip_sizes_obj);
if (num_chips < 0) {
return NULL;
} else if (num_chips == 0) {
PyErr_SetString(PyExc_TypeError,
"Number of chips must be greater thatn 0");
return NULL;
}
chip_sizes = PyMem_RawCalloc(num_chips, sizeof(unsigned int));
if (!chip_sizes)
return NULL;
iter = PyObject_GetIter(chip_sizes_obj);
if (!iter) {
PyMem_RawFree(chip_sizes);
return NULL;
}
for (i = 0;; i++) {
next = PyIter_Next(iter);
if (!next) {
Py_DECREF(iter);
break;
}
chip_sizes[i] = PyLong_AsUnsignedLong(next);
Py_DECREF(next);
if (PyErr_Occurred()) {
Py_DECREF(iter);
PyMem_RawFree(chip_sizes);
return NULL;
}
}
if (flags & gpiomockup_FLAG_NAMED_LINES)
flags |= GPIO_MOCKUP_FLAG_NAMED_LINES;
Py_BEGIN_ALLOW_THREADS;
ret = gpio_mockup_probe(self->mockup, num_chips, chip_sizes, flags);
Py_END_ALLOW_THREADS;
PyMem_RawFree(chip_sizes);
if (ret)
return NULL;
Py_RETURN_NONE;
}
static PyObject *gpiomockup_Mockup_remove(gpiomockup_MockupObject *self,
PyObject *Py_UNUSED(ignored))
{
int ret;
Py_BEGIN_ALLOW_THREADS;
ret = gpio_mockup_remove(self->mockup);
Py_END_ALLOW_THREADS;
if (ret) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *gpiomockup_Mockup_chip_name(gpiomockup_MockupObject *self,
PyObject *args)
{
unsigned int idx;
const char *name;
int ret;
ret = PyArg_ParseTuple(args, "I", &idx);
if (!ret)
return NULL;
name = gpio_mockup_chip_name(self->mockup, idx);
if (!name) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
return PyUnicode_FromString(name);
}
static PyObject *gpiomockup_Mockup_chip_path(gpiomockup_MockupObject *self,
PyObject *args)
{
unsigned int idx;
const char *path;
int ret;
ret = PyArg_ParseTuple(args, "I", &idx);
if (!ret)
return NULL;
path = gpio_mockup_chip_path(self->mockup, idx);
if (!path) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
return PyUnicode_FromString(path);
}
static PyObject *gpiomockup_Mockup_chip_num(gpiomockup_MockupObject *self,
PyObject *args)
{
unsigned int idx;
int ret, num;
ret = PyArg_ParseTuple(args, "I", &idx);
if (!ret)
return NULL;
num = gpio_mockup_chip_num(self->mockup, idx);
if (num < 0) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
return PyLong_FromLong(num);
}
static PyObject *gpiomockup_Mockup_chip_get_value(gpiomockup_MockupObject *self,
PyObject *args)
{
unsigned int chip_idx, line_offset;
int ret, val;
ret = PyArg_ParseTuple(args, "II", &chip_idx, &line_offset);
if (!ret)
return NULL;
Py_BEGIN_ALLOW_THREADS;
val = gpio_mockup_get_value(self->mockup, chip_idx, line_offset);
Py_END_ALLOW_THREADS;
if (val < 0) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
return PyLong_FromUnsignedLong(val);
}
static PyObject *gpiomockup_Mockup_chip_set_pull(gpiomockup_MockupObject *self,
PyObject *args)
{
unsigned int chip_idx, line_offset;
int ret, pull;
ret = PyArg_ParseTuple(args, "IIi", &chip_idx, &line_offset, &pull);
if (!ret)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = gpio_mockup_set_pull(self->mockup, chip_idx, line_offset, pull);
Py_END_ALLOW_THREADS;
if (ret) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
Py_RETURN_NONE;
}
static PyMethodDef gpiomockup_Mockup_methods[] = {
{
.ml_name = "probe",
.ml_meth = (PyCFunction)(void (*)(void))gpiomockup_Mockup_probe,
.ml_flags = METH_VARARGS | METH_KEYWORDS,
},
{
.ml_name = "remove",
.ml_meth = (PyCFunction)gpiomockup_Mockup_remove,
.ml_flags = METH_NOARGS,
},
{
.ml_name = "chip_name",
.ml_meth = (PyCFunction)gpiomockup_Mockup_chip_name,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "chip_path",
.ml_meth = (PyCFunction)gpiomockup_Mockup_chip_path,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "chip_num",
.ml_meth = (PyCFunction)gpiomockup_Mockup_chip_num,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "chip_get_value",
.ml_meth = (PyCFunction)gpiomockup_Mockup_chip_get_value,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "chip_set_pull",
.ml_meth = (PyCFunction)gpiomockup_Mockup_chip_set_pull,
.ml_flags = METH_VARARGS,
},
{ }
};
static PyTypeObject gpiomockup_MockupType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "gpiomockup.Mockup",
.tp_basicsize = sizeof(gpiomockup_MockupObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)gpiomockup_Mockup_init,
.tp_dealloc = (destructor)gpiomockup_Mockup_dealloc,
.tp_methods = gpiomockup_Mockup_methods,
};
static PyModuleDef gpiomockup_Module = {
PyModuleDef_HEAD_INIT,
.m_name = "gpiomockup",
.m_size = -1,
};
PyMODINIT_FUNC PyInit_gpiomockup(void)
{
PyObject *module, *val;
int ret;
module = PyModule_Create(&gpiomockup_Module);
if (!module)
return NULL;
ret = PyType_Ready(&gpiomockup_MockupType);
if (ret)
return NULL;
Py_INCREF(&gpiomockup_MockupType);
ret = PyModule_AddObject(module, "Mockup",
(PyObject *)&gpiomockup_MockupType);
if (ret)
return NULL;
val = PyLong_FromLong(gpiomockup_FLAG_NAMED_LINES);
if (!val)
return NULL;
ret = PyDict_SetItemString(gpiomockup_MockupType.tp_dict,
"FLAG_NAMED_LINES", val);
if (ret)
return NULL;
return module;
}

View File

@@ -220,6 +220,7 @@ AC_CONFIG_FILES([libgpiod.pc
bindings/cxx/tests/Makefile
bindings/python/Makefile
bindings/python/examples/Makefile
bindings/python/tests/Makefile
man/Makefile])
AC_OUTPUT