Re: [libgpiod v2][PATCH v2 4/5] bindings: python: add tests for v2 API

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.



[Index of Archives]     [Linux SPI]     [Linux Kernel]     [Linux ARM (vger)]     [Linux ARM MSM]     [Linux Omap]     [Linux Arm]     [Linux Tegra]     [Fedora ARM]     [Linux for Samsung SOC]     [eCos]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [Linux MIPS]     [Yosemite Campsites]

  Powered by Linux