On Tue, Jun 28, 2022 at 10:42:25AM +0200, Bartosz Golaszewski wrote: > This adds a python wrapper around libgpiosim and a set of test cases > for the v2 API using python's standard unittest module. > > Signed-off-by: Bartosz Golaszewski <brgl@xxxxxxxx> > --- > bindings/python/tests/Makefile.am | 14 + > bindings/python/tests/cases/__init__.py | 12 + > bindings/python/tests/cases/tests_chip.py | 157 +++++++ > .../python/tests/cases/tests_chip_info.py | 59 +++ > .../python/tests/cases/tests_edge_event.py | 279 +++++++++++ > .../python/tests/cases/tests_info_event.py | 135 ++++++ > .../python/tests/cases/tests_line_config.py | 254 ++++++++++ > .../python/tests/cases/tests_line_info.py | 90 ++++ > .../python/tests/cases/tests_line_request.py | 345 ++++++++++++++ > bindings/python/tests/cases/tests_misc.py | 53 +++ > .../tests/cases/tests_request_config.py | 77 ++++ > bindings/python/tests/gpiod_py_test.py | 25 + > bindings/python/tests/gpiosimmodule.c | 434 ++++++++++++++++++ > 13 files changed, 1934 insertions(+) > create mode 100644 bindings/python/tests/Makefile.am > create mode 100644 bindings/python/tests/cases/__init__.py > create mode 100644 bindings/python/tests/cases/tests_chip.py > create mode 100644 bindings/python/tests/cases/tests_chip_info.py > create mode 100644 bindings/python/tests/cases/tests_edge_event.py > create mode 100644 bindings/python/tests/cases/tests_info_event.py > create mode 100644 bindings/python/tests/cases/tests_line_config.py > create mode 100644 bindings/python/tests/cases/tests_line_info.py > create mode 100644 bindings/python/tests/cases/tests_line_request.py > create mode 100644 bindings/python/tests/cases/tests_misc.py > create mode 100644 bindings/python/tests/cases/tests_request_config.py > create mode 100755 bindings/python/tests/gpiod_py_test.py > create mode 100644 bindings/python/tests/gpiosimmodule.c > > diff --git a/bindings/python/tests/Makefile.am b/bindings/python/tests/Makefile.am > new file mode 100644 > index 0000000..099574f > --- /dev/null > +++ b/bindings/python/tests/Makefile.am > @@ -0,0 +1,14 @@ > +# SPDX-License-Identifier: GPL-2.0-or-later > +# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@xxxxxxxxx> > + It is 2022? Which email address are you going with? gmail here and bgdev below. > +dist_bin_SCRIPTS = gpiod_py_test.py > + > +pyexec_LTLIBRARIES = gpiosim.la > + > +gpiosim_la_SOURCES = gpiosimmodule.c > +gpiosim_la_CFLAGS = -I$(top_srcdir)/tests/gpiosim/ > +gpiosim_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS) > +gpiosim_la_LDFLAGS = -module -avoid-version > +gpiosim_la_LIBADD = $(top_builddir)/tests/gpiosim/libgpiosim.la > +gpiosim_la_LIBADD += $(top_builddir)/bindings/python/enum/libpycenum.la > +gpiosim_la_LIBADD += $(PYTHON_LIBS) > diff --git a/bindings/python/tests/cases/__init__.py b/bindings/python/tests/cases/__init__.py > new file mode 100644 > index 0000000..6503663 > --- /dev/null > +++ b/bindings/python/tests/cases/__init__.py > @@ -0,0 +1,12 @@ > +# SPDX-License-Identifier: GPL-2.0-or-later > +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@xxxxxxxx> > + > +from .tests_chip import * > +from .tests_chip_info import * > +from .tests_edge_event import * > +from .tests_info_event import * > +from .tests_line_config import * > +from .tests_line_info import * > +from .tests_line_request import * > +from .tests_misc import * > +from .tests_request_config import * > diff --git a/bindings/python/tests/cases/tests_chip.py b/bindings/python/tests/cases/tests_chip.py > new file mode 100644 > index 0000000..844dbfc > --- /dev/null > +++ b/bindings/python/tests/cases/tests_chip.py [snip] > +class WaitingForEdgeEvents(unittest.TestCase): > + def setUp(self): > + self.sim = gpiosim.Chip(num_lines=8) > + self.thread = None > + > + def tearDown(self): > + if self.thread: > + self.thread.join() > + self.sim = None > + > + def trigger_falling_and_rising_edge(self, offset): > + time.sleep(0.05) > + self.sim.set_pull(offset, Pull.PULL_UP) > + time.sleep(0.05) > + self.sim.set_pull(offset, Pull.PULL_DOWN) > + > + def trigger_rising_edge_events_on_two_offsets(self, offset0, offset1): > + time.sleep(0.05) > + self.sim.set_pull(offset0, Pull.PULL_UP) > + time.sleep(0.05) > + self.sim.set_pull(offset1, Pull.PULL_UP) > + > + def test_both_edge_events(self): > + with gpiod.request_lines( > + self.sim.dev_path, > + gpiod.RequestConfig(offsets=[2]), > + gpiod.LineConfig(edge_detection=Edge.BOTH), > + ) as req: > + buf = gpiod.EdgeEventBuffer() > + self.thread = threading.Thread( > + target=partial(self.trigger_falling_and_rising_edge, 2) > + ) > + self.thread.start() > + > + self.assertTrue(req.wait_edge_event(datetime.timedelta(seconds=1))) > + self.assertEqual(req.read_edge_event(buf), 1) > + self.assertEqual(len(buf), 1) > + event = buf[0] > + self.assertEqual(event.type, EventType.RISING_EDGE) > + self.assertEqual(event.line_offset, 2) > + ts_rising = event.timestamp_ns > + > + self.assertTrue(req.wait_edge_event(datetime.timedelta(seconds=1))) > + self.assertEqual(req.read_edge_event(buf), 1) > + self.assertEqual(len(buf), 1) > + event = buf[0] > + self.assertEqual(event.type, EventType.FALLING_EDGE) > + self.assertEqual(event.line_offset, 2) > + ts_falling = event.timestamp_ns > + > + self.assertGreater(ts_falling, ts_rising) > + > + def test_rising_edge_event(self): > + with gpiod.request_lines( > + self.sim.dev_path, > + gpiod.RequestConfig(offsets=[6]), > + gpiod.LineConfig(edge_detection=Edge.RISING), > + ) as req: > + buf = gpiod.EdgeEventBuffer() > + self.thread = threading.Thread( > + target=partial(self.trigger_falling_and_rising_edge, 6) > + ) > + self.thread.start() > + > + self.assertTrue(req.wait_edge_event(datetime.timedelta(seconds=1))) > + self.assertEqual(req.read_edge_event(buf), 1) > + self.assertEqual(len(buf), 1) > + event = buf[0] > + self.assertEqual(event.type, EventType.RISING_EDGE) > + self.assertEqual(event.line_offset, 6) > + > + self.assertFalse( > + req.wait_edge_event(datetime.timedelta(microseconds=10000)) > + ) > + > + def test_falling_edge_event(self): > + with gpiod.request_lines( > + self.sim.dev_path, > + gpiod.RequestConfig(offsets=[6]), > + gpiod.LineConfig(edge_detection=Edge.FALLING), > + ) as req: > + buf = gpiod.EdgeEventBuffer() > + self.thread = threading.Thread( > + target=partial(self.trigger_falling_and_rising_edge, 6) > + ) > + self.thread.start() > + Benefit of the thread? (and elsewhere a background thread is used) The sleeps therein are only necessary because it is run in the background. > + self.assertTrue(req.wait_edge_event(datetime.timedelta(seconds=1))) > + self.assertEqual(req.read_edge_event(buf), 1) > + self.assertEqual(len(buf), 1) > + event = buf[0] > + self.assertEqual(event.type, EventType.FALLING_EDGE) > + self.assertEqual(event.line_offset, 6) > + > + self.assertFalse( > + req.wait_edge_event(datetime.timedelta(microseconds=10000)) > + ) > + > + def test_sequence_numbers(self): > + with gpiod.request_lines( > + self.sim.dev_path, > + gpiod.RequestConfig(offsets=[2, 4]), > + gpiod.LineConfig(edge_detection=Edge.BOTH), > + ) as req: > + buf = gpiod.EdgeEventBuffer() > + self.thread = threading.Thread( > + target=partial(self.trigger_rising_edge_events_on_two_offsets, 2, 4) > + ) > + self.thread.start() > + > + self.assertTrue(req.wait_edge_event(datetime.timedelta(seconds=1))) > + self.assertEqual(req.read_edge_event(buf), 1) > + self.assertEqual(len(buf), 1) > + event = buf[0] > + self.assertEqual(event.type, EventType.RISING_EDGE) > + self.assertEqual(event.line_offset, 2) > + self.assertEqual(event.global_seqno, 1) > + self.assertEqual(event.line_seqno, 1) > + > + self.assertTrue(req.wait_edge_event(datetime.timedelta(seconds=1))) > + self.assertEqual(req.read_edge_event(buf), 1) > + self.assertEqual(len(buf), 1) > + event = buf[0] > + self.assertEqual(event.type, EventType.RISING_EDGE) > + self.assertEqual(event.line_offset, 4) > + self.assertEqual(event.global_seqno, 2) > + self.assertEqual(event.line_seqno, 1) > + > + [snip] > +++ b/bindings/python/tests/cases/tests_line_request.py > @@ -0,0 +1,345 @@ > +# SPDX-License-Identifier: GPL-2.0-or-later > +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@xxxxxxxx> > + > +import errno > +import gpiod > +import gpiosim > +import unittest > + > + > +Direction = gpiod.Line.Direction > +Edge = gpiod.Line.Edge > +Bias = gpiod.Line.Bias > +Value = gpiod.Line.Value > +SimVal = gpiosim.Chip.Value > +Pull = gpiosim.Chip.Pull > + > + > +class LineRequestConstructor(unittest.TestCase): > + def test_line_request_cannot_be_instantiated(self): > + with self.assertRaises(TypeError): > + info = gpiod.LineRequest() > + > + > +class ChipLineRequestWorks(unittest.TestCase): > + def test_chip_line_request(self): > + sim = gpiosim.Chip() > + > + with gpiod.Chip(sim.dev_path) as chip: > + with chip.request_lines( > + gpiod.RequestConfig(offsets=[0]), gpiod.LineConfig() > + ) as req: > + pass > + > + > +class ModuleLineRequestWorks(unittest.TestCase): Put module level tests in a module level file, say tests_module.py? All the tests in this file should test LineRequest methods. (i.e. where request_lines() has succeeded so the req is constructed) Chip.request_lines() tests should be in tests_chip.py. Particularly the failure cases. > + def test_module_line_request(self): > + sim = gpiosim.Chip() > + > + with gpiod.request_lines( > + sim.dev_path, gpiod.RequestConfig(offsets=[0]), gpiod.LineConfig() > + ) as req: > + pass > + > + def test_module_line_request_lines_arg(self): > + sim = gpiosim.Chip(num_lines=16, line_names={0: "foo", 2: "bar", 5: "xyz"}) > + > + with gpiod.request_lines(sim.dev_path, lines=["foo", "bar", "xyz"]) as req: > + self.assertEqual(req.offsets, [0, 2, 5]) > + > + with gpiod.request_lines(sim.dev_path, lines=["foo", 9, "xyz", 12]) as req: > + self.assertEqual(req.offsets, [0, 9, 5, 12]) > + Test name mapping failures. Test lines=[]. Test lines=None. > + def test_module_line_request_direction(self): > + sim = gpiosim.Chip(num_lines=2) > + > + with gpiod.request_lines( > + sim.dev_path, lines=[0, 1], direction=Direction.OUTPUT > + ) as req: > + with gpiod.Chip(sim.dev_path) as chip: > + info = chip.get_line_info(0) > + self.assertEqual(info.direction, Direction.OUTPUT) > + self.assertTrue(info.used) > + > + def test_module_line_request_edge_detection(self): > + sim = gpiosim.Chip() > + > + with gpiod.request_lines( > + sim.dev_path, lines=[0], edge_detection=Edge.BOTH > + ) as req: > + sim.set_pull(0, Pull.PULL_UP) > + self.assertTrue(req.wait_edge_event()) > + self.assertEqual(req.read_edge_event()[0].line_offset, 0) > + > + > +class RequestingLinesFailsWithInvalidArguments(unittest.TestCase): These tests should be in tests_chip.py, as they are testing the Chip.request_lines() method. And they should have module level equivalents (don't assume one wraps the other). > + def setUp(self): > + self.sim = gpiosim.Chip(num_lines=8) > + self.chip = gpiod.Chip(self.sim.dev_path) > + > + def tearDown(self): > + self.chip.close() > + self.chip = None > + self.sim = None > + > + def test_passing_invalid_types_as_configs(self): > + with self.assertRaises(TypeError): > + self.chip.request_lines("foobar", gpiod.LineConfig()) > + > + with self.assertRaises(TypeError): > + self.chip.request_lines(gpiod.RequestConfig(offsets=[0]), "foobar") > + > + def test_no_offsets(self): > + with self.assertRaises(ValueError): > + self.chip.request_lines(gpiod.RequestConfig(), gpiod.LineConfig()) > + > + def test_duplicate_offsets(self): > + with self.assertRaises(OSError) as ex: > + self.chip.request_lines( > + gpiod.RequestConfig(offsets=[2, 5, 1, 7, 5]), gpiod.LineConfig() > + ) > + > + self.assertEqual(ex.exception.errno, errno.EBUSY) > + > + def test_offset_out_of_range(self): > + with self.assertRaises(ValueError): > + self.chip.request_lines( > + gpiod.RequestConfig(offsets=[1, 0, 4, 8]), gpiod.LineConfig() > + ) > + [snip] > +++ b/bindings/python/tests/cases/tests_misc.py > @@ -0,0 +1,53 @@ > +# SPDX-License-Identifier: GPL-2.0-or-later > +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@xxxxxxxx> > + The tests in this file are all module scope, and cover functions from module.c, so rename to tests_module.py. > +import gpiod > +import gpiosim > +import os > +import re > +import unittest > + > + > +class LinkGuard: > + def __init__(self, src, dst): > + self.src = src > + self.dst = dst > + > + def __enter__(self): > + os.symlink(self.src, self.dst) > + > + def __exit__(self, type, val, tb): > + os.unlink(self.dst) > + > + > +class IsGPIOChip(unittest.TestCase): > + def test_is_gpiochip_bad(self): > + self.assertFalse(gpiod.is_gpiochip_device("/dev/null")) > + self.assertFalse(gpiod.is_gpiochip_device("/dev/nonexistent")) > + > + def test_is_gpiochip_good(self): > + sim = gpiosim.Chip() > + > + self.assertTrue(gpiod.is_gpiochip_device(sim.dev_path)) > + > + def test_is_gpiochip_link_good(self): > + link = "/tmp/gpiod-py-test-link.{}".format(os.getpid()) > + sim = gpiosim.Chip() > + > + with LinkGuard(sim.dev_path, link): > + self.assertTrue(gpiod.is_gpiochip_device(link)) > + > + def test_is_gpiochip_link_bad(self): > + link = "/tmp/gpiod-py-test-link.{}".format(os.getpid()) > + > + with LinkGuard("/dev/null", link): > + self.assertFalse(gpiod.is_gpiochip_device(link)) > + > + > +class VersionString(unittest.TestCase): > + def test_version_string(self): > + self.assertTrue( > + re.match( > + "^[0-9][1-9]?\\.[0-9][1-9]?([\\.0-9]?|\\-devel)$", gpiod.__version__ > + ) > + ) > diff --git a/bindings/python/tests/cases/tests_request_config.py b/bindings/python/tests/cases/tests_request_config.py [snip] A complete audit of the test coverage would be beneficial. I haven't attempted that - only pointed out any gaps I happened to notice. Are there any coverage tools available for Python C modules? Cheers, Kent.