From: Bartosz Golaszewski Date: Tue, 6 Aug 2019 16:05:30 +0000 (+0200) Subject: bindings: cxx: use catch2 to implement a proper test-suite X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=99d38a56e5fb1d059adcf507751159b7a296cced;p=qemu-gpiodev%2Flibgpiod.git bindings: cxx: use catch2 to implement a proper test-suite Currently the only tests we have for C++ bindings are in a simple program 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 libgpiomockup and Catch2. Signed-off-by: Bartosz Golaszewski --- diff --git a/.gitignore b/.gitignore index fdb0ed4..2635cec 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ gpiogetcxx gpioinfocxx gpiomoncxx gpiosetcxx +gpiod-cxx-test *.o *.lo *.la diff --git a/Makefile.am b/Makefile.am index adf0d9c..0f8c2b1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,7 +8,7 @@ ACLOCAL_AMFLAGS = -I m4 AUTOMAKE_OPTIONS = foreign -SUBDIRS = include lib bindings +SUBDIRS = include lib if WITH_TOOLS @@ -22,6 +22,10 @@ SUBDIRS += tests endif +# Build bindings after core tests. When building tests for bindings, we need +# libgpiomockup to be already present. +SUBDIRS += bindings + if HAS_DOXYGEN doc: diff --git a/TODO b/TODO index f857fea..e9862d0 100644 --- a/TODO +++ b/TODO @@ -25,13 +25,6 @@ and is partially functional. ---------- -* use a proper unit testing framework for C++ bindings and reuse libgpiod-test - -The actual framework for testing is TBD but libgpiod-test could be reused as is -from C++ code so that we can use gpio-mockup just like for the core library. - ----------- - * use the python unit testing library for python bindings and reuse libgpiod-test diff --git a/bindings/cxx/Makefile.am b/bindings/cxx/Makefile.am index d901836..5c40ceb 100644 --- a/bindings/cxx/Makefile.am +++ b/bindings/cxx/Makefile.am @@ -19,3 +19,9 @@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libgpiodcxx.pc SUBDIRS = . examples + +if WITH_TESTS + +SUBDIRS += tests + +endif diff --git a/bindings/cxx/examples/Makefile.am b/bindings/cxx/examples/Makefile.am index 201e1db..a66296b 100644 --- a/bindings/cxx/examples/Makefile.am +++ b/bindings/cxx/examples/Makefile.am @@ -10,16 +10,13 @@ AM_CPPFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include AM_CPPFLAGS += -Wall -Wextra -g -std=gnu++11 AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/ -check_PROGRAMS = gpiod_cxx_tests \ - gpiodetectcxx \ +noinst_PROGRAMS = gpiodetectcxx \ gpiofindcxx \ gpiogetcxx \ gpioinfocxx \ gpiomoncxx \ gpiosetcxx -gpiod_cxx_tests_SOURCES = gpiod_cxx_tests.cpp - gpiodetectcxx_SOURCES = gpiodetectcxx.cpp gpiofindcxx_SOURCES = gpiofindcxx.cpp diff --git a/bindings/cxx/examples/gpiod_cxx_tests.cpp b/bindings/cxx/examples/gpiod_cxx_tests.cpp deleted file mode 100644 index 767fe98..0000000 --- a/bindings/cxx/examples/gpiod_cxx_tests.cpp +++ /dev/null @@ -1,487 +0,0 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later -/* - * This file is part of libgpiod. - * - * Copyright (C) 2017-2018 Bartosz Golaszewski - */ - -/* - * Misc tests/examples of the C++ API. - * - * These tests assume that at least one dummy gpiochip is present in the - * system and that it's detected as gpiochip0. - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace { - -using test_func = ::std::function; - -::std::vector<::std::pair<::std::string, test_func>> test_funcs; - -struct test_case -{ - test_case(const ::std::string& name, test_func& func) - { - test_funcs.push_back(::std::make_pair(name, func)); - } -}; - -#define TEST_CASE(_func) \ - test_func _test_func_##_func(_func); \ - test_case _test_case_##_func(#_func, _test_func_##_func) - -std::string boolstr(bool b) -{ - return b ? "true" : "false"; -} - -void fire_line_event(const ::std::string& chip, unsigned int offset, bool rising) -{ - ::std::string path; - - path = "/sys/kernel/debug/gpio-mockup-event/" + chip + "/" + ::std::to_string(offset); - - ::std::ofstream out(path); - if (!out) - throw ::std::runtime_error("unable to open " + path); - - out << ::std::to_string(rising ? 1 : 0) << ::std::endl; -} - -void print_event(const ::gpiod::line_event& event) -{ - ::std::cerr << "type: " - << (event.event_type == ::gpiod::line_event::RISING_EDGE ? "rising" : "falling") - << "\n" - << "timestamp: " - << ::std::chrono::duration_cast<::std::chrono::seconds>(event.timestamp).count() - << "." - << event.timestamp.count() % 1000000000 - << "\n" - << "source line offset: " - << event.source.offset() - << ::std::endl; -} - -void chip_open_default_lookup(void) -{ - ::gpiod::chip by_name("gpiochip0"); - ::gpiod::chip by_path("/dev/gpiochip0"); - ::gpiod::chip by_label("gpio-mockup-A"); - ::gpiod::chip by_number("0"); - - ::std::cerr << "all good" << ::std::endl; -} -TEST_CASE(chip_open_default_lookup); - -void chip_open_different_modes(void) -{ - ::gpiod::chip by_name("gpiochip0", ::gpiod::chip::OPEN_BY_NAME); - ::gpiod::chip by_path("/dev/gpiochip0", ::gpiod::chip::OPEN_BY_PATH); - ::gpiod::chip by_label("gpio-mockup-A", ::gpiod::chip::OPEN_BY_LABEL); - ::gpiod::chip by_number("0", ::gpiod::chip::OPEN_BY_NUMBER); - - ::std::cerr << "all good" << ::std::endl; -} -TEST_CASE(chip_open_different_modes); - -void chip_open_nonexistent(void) -{ - try { - ::gpiod::chip chip("/nonexistent_gpiochip"); - } catch (const ::std::system_error& exc) { - ::std::cerr << "system_error thrown as expected: " << exc.what() << ::std::endl; - return; - } - - throw ::std::runtime_error("system_error should have been thrown"); -} -TEST_CASE(chip_open_nonexistent); - -void chip_open_method(void) -{ - ::gpiod::chip chip; - - if (chip) - throw ::std::runtime_error("chip should be empty"); - - chip.open("gpiochip0"); - - ::std::cerr << "all good" << ::std::endl; -} -TEST_CASE(chip_open_method); - -void chip_reset(void) -{ - ::gpiod::chip chip("gpiochip0"); - - if (!chip) - throw ::std::runtime_error("chip object is not valid"); - - chip.reset(); - - if (chip) - throw ::std::runtime_error("chip should be invalid"); - - ::std::cerr << "all good" << ::std::endl; -} -TEST_CASE(chip_reset); - -void chip_info(void) -{ - ::gpiod::chip chip("gpiochip0"); - - ::std::cerr << "chip name: " << chip.name() << ::std::endl; - ::std::cerr << "chip label: " << chip.label() << ::std::endl; - ::std::cerr << "number of lines " << chip.num_lines() << ::std::endl; -} -TEST_CASE(chip_info); - -void chip_operators(void) -{ - ::gpiod::chip chip1("gpiochip0"); - auto chip2 = chip1; - - if ((chip1 != chip2) || !(chip1 == chip2)) - throw ::std::runtime_error("chip objects should be equal"); - - ::std::cerr << "all good" << ::std::endl; -} -TEST_CASE(chip_operators); - -void chip_line_ops(void) -{ - ::gpiod::chip chip("gpiochip0"); - - auto line = chip.get_line(3); - ::std::cerr << "Got line by offset: " << line.offset() << ::std::endl; - - line = chip.find_line("gpio-mockup-A-4"); - ::std::cerr << "Got line by name: " << line.name() << ::std::endl; - - auto lines = chip.get_lines({ 1, 2, 3, 6, 6 }); - ::std::cerr << "Got multiple lines by offset: "; - for (auto& it: lines) - ::std::cerr << it.offset() << " "; - ::std::cerr << ::std::endl; - - lines = chip.find_lines({ "gpio-mockup-A-1", "gpio-mockup-A-4", "gpio-mockup-A-7" }); - ::std::cerr << "Got multiple lines by name: "; - for (auto& it: lines) - ::std::cerr << it.name() << " "; - ::std::cerr << ::std::endl; -} -TEST_CASE(chip_line_ops); - -void chip_find_lines_nonexistent(void) -{ - ::gpiod::chip chip("gpiochip0"); - - auto lines = chip.find_lines({ "gpio-mockup-A-1", "nonexistent", "gpio-mockup-A-4" }); - if (lines) - throw ::std::logic_error("line_bulk object should be invalid"); - - ::std::cerr << "line_bulk invalid as expected" << ::std::endl; -} -TEST_CASE(chip_find_lines_nonexistent); - -void line_info(void) -{ - ::gpiod::chip chip("gpiochip0"); - ::gpiod::line line = chip.get_line(2); - - ::std::cerr << "line offset: " << line.offset() << ::std::endl; - ::std::cerr << "line name: " << line.name() << ::std::endl; - ::std::cerr << "line direction: " - << (line.direction() == ::gpiod::line::DIRECTION_INPUT ? - "input" : "output") << ::std::endl; - ::std::cerr << "line active state: " - << (line.active_state() == ::gpiod::line::ACTIVE_LOW ? - "active low" : "active high") << :: std::endl; -} -TEST_CASE(line_info); - -void empty_objects(void) -{ - ::std::cerr << "Are initialized line & chip objects 'false'?" << ::std::endl; - - ::gpiod::line line; - if (line) - throw ::std::logic_error("line built with a default constructor should be 'false'"); - - ::gpiod::chip chip; - if (chip) - throw ::std::logic_error("chip built with a default constructor should be 'false'"); - - ::std::cerr << "YES" << ::std::endl; -} -TEST_CASE(empty_objects); - -void line_bulk_iterator(void) -{ - ::std::cerr << "Checking line_bulk iterators" << ::std::endl; - - ::gpiod::chip chip("gpiochip0"); - ::gpiod::line_bulk bulk = chip.get_lines({ 0, 1, 2, 3, 4 }); - - for (auto& it: bulk) - ::std::cerr << it.name() << ::std::endl; - - ::std::cerr << "DONE" << ::std::endl; -} -TEST_CASE(line_bulk_iterator); - -void single_line_test(void) -{ - const ::std::string line_name("gpio-mockup-A-4"); - - ::std::cerr << "Looking up a GPIO line by name (" << line_name << ")" << ::std::endl; - - auto line = ::gpiod::find_line(line_name); - if (!line) - throw ::std::runtime_error(line_name + " line not found"); - - ::std::cerr << "Requesting a single line" << ::std::endl; - - ::gpiod::line_request conf; - conf.consumer = "gpiod_cxx_tests"; - conf.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; - - line.request(conf, 1); - ::std::cerr << "Reading value" << ::std::endl; - ::std::cerr << line.get_value() << ::std::endl; - ::std::cerr << "Changing value to 0" << ::std::endl; - line.set_value(0); - ::std::cerr << "Reading value again" << ::std::endl; - ::std::cerr << line.get_value() << ::std::endl; -} -TEST_CASE(single_line_test); - -void line_flags(void) -{ - ::gpiod::chip chip("gpiochip0"); - - auto line = chip.get_line(0); - - ::std::cerr << "line is used: " << boolstr(line.is_used()) << ::std::endl; - ::std::cerr << "line is requested: " << boolstr(line.is_requested()) << ::std::endl; - - ::std::cerr << "requesting line" << ::std::endl; - - ::gpiod::line_request config; - config.consumer = "gpiod_cxx_tests"; - config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; - config.flags = ::gpiod::line_request::FLAG_OPEN_DRAIN; - line.request(config); - - ::std::cerr << "line is used: " << boolstr(line.is_used()) << ::std::endl; - ::std::cerr << "line is open drain: " << boolstr(line.is_open_drain()) << ::std::endl; - ::std::cerr << "line is open source: " << boolstr(line.is_open_source()) << ::std::endl; - ::std::cerr << "line is requested: " << boolstr(line.is_requested()) << ::std::endl; - - if (!line.is_open_drain()) - throw ::std::logic_error("line should be open-drain"); -} -TEST_CASE(line_flags); - -void multiple_lines_test(void) -{ - ::gpiod::chip chip("gpiochip0"); - - ::std::cerr << "Getting multiple lines by offsets" << ::std::endl; - - auto lines = chip.get_lines({ 0, 2, 3, 4, 6 }); - - ::std::cerr << "Requesting them for output" << ::std::endl; - - ::gpiod::line_request config; - config.consumer = "gpiod_cxx_tests"; - config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; - - lines.request(config); - - ::std::cerr << "Setting values" << ::std::endl; - - lines.set_values({ 0, 1, 1, 0, 1}); - - ::std::cerr << "Requesting the lines for input" << ::std::endl; - - config.request_type = ::gpiod::line_request::DIRECTION_INPUT; - lines.release(); - lines.request(config); - - ::std::cerr << "Reading the values" << ::std::endl; - - auto vals = lines.get_values(); - - for (auto& it: vals) - ::std::cerr << it << " "; - ::std::cerr << ::std::endl; -} -TEST_CASE(multiple_lines_test); - -void chip_get_all_lines(void) -{ - ::gpiod::chip chip("gpiochip0"); - - ::std::cerr << "Getting all lines from a chip" << ::std::endl; - - auto lines = chip.get_all_lines(); - - for (auto& it: lines) - ::std::cerr << "Offset: " << it.offset() << ::std::endl; -} -TEST_CASE(chip_get_all_lines); - -void line_event_single_line(void) -{ - ::gpiod::chip chip("gpiochip0"); - - auto line = chip.get_line(1); - - ::gpiod::line_request config; - config.consumer = "gpiod_cxx_tests"; - config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; - - ::std::cerr << "requesting line for events" << ::std::endl; - line.request(config); - - ::std::cerr << "generating a line event" << ::std::endl; - fire_line_event("gpiochip0", 1, true); - - if (!line.event_wait(::std::chrono::nanoseconds(1000000000))) - throw ::std::runtime_error("waiting for event timed out"); - - auto event = line.event_read(); - - ::std::cerr << "event received:" << ::std::endl; - print_event(event); -} -TEST_CASE(line_event_single_line); - -void line_event_multiple_lines(void) -{ - ::gpiod::chip chip("gpiochip0"); - - auto lines = chip.get_lines({ 1, 2, 3, 4, 5 }); - - ::gpiod::line_request config; - config.consumer = "gpiod_cxx_tests"; - config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; - - ::std::cerr << "requesting lines for events" << ::std::endl; - lines.request(config); - - ::std::cerr << "generating two line events" << ::std::endl; - fire_line_event("gpiochip0", 1, true); - fire_line_event("gpiochip0", 2, true); - - auto event_lines = lines.event_wait(::std::chrono::nanoseconds(1000000000)); - if (!event_lines || event_lines.size() != 2) - throw ::std::runtime_error("expected two line events"); - - ::std::vector<::gpiod::line_event> events; - for (auto& line: event_lines) { - auto event = line.event_read(); - events.push_back(event); - } - - ::std::cerr << "events received:" << ::std::endl; - for (auto& event: events) - print_event(event); -} -TEST_CASE(line_event_multiple_lines); - -void line_event_poll_fd(void) -{ - ::std::map fd_line_map; - - ::gpiod::chip chip("gpiochip0"); - auto lines = chip.get_lines({ 1, 2, 3, 4, 5, 6 }); - - ::gpiod::line_request config; - config.consumer = "gpiod_cxx_tests"; - config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; - - ::std::cerr << "requesting lines for events" << ::std::endl; - lines.request(config); - - ::std::cerr << "generating three line events" << ::std::endl; - fire_line_event("gpiochip0", 2, true); - fire_line_event("gpiochip0", 3, true); - fire_line_event("gpiochip0", 5, false); - - fd_line_map[lines[1].event_get_fd()] = lines[1]; - fd_line_map[lines[2].event_get_fd()] = lines[2]; - fd_line_map[lines[4].event_get_fd()] = lines[4]; - - pollfd fds[3]; - fds[0].fd = lines[1].event_get_fd(); - fds[1].fd = lines[2].event_get_fd(); - fds[2].fd = lines[4].event_get_fd(); - - fds[0].events = fds[1].events = fds[2].events = POLLIN | POLLPRI; - - int rv = poll(fds, 3, 1000); - if (rv < 0) - throw ::std::runtime_error("error polling for events: " - + ::std::string(::strerror(errno))); - else if (rv == 0) - throw ::std::runtime_error("poll() timed out while waiting for events"); - - if (rv != 3) - throw ::std::runtime_error("expected three line events"); - - ::std::cerr << "events received:" << ::std::endl; - for (unsigned int i = 0; i < 3; i++) { - if (fds[i].revents) - print_event(fd_line_map[fds[i].fd].event_read()); - } -} -TEST_CASE(line_event_poll_fd); - -void chip_iterator(void) -{ - ::std::cerr << "iterating over all GPIO chips in the system:" << ::std::endl; - - for (auto& it: ::gpiod::make_chip_iter()) - ::std::cerr << it.name() << ::std::endl; -} -TEST_CASE(chip_iterator); - -void line_iterator(void) -{ - ::std::cerr << "iterating over all lines exposed by a GPIO chip:" << ::std::endl; - - ::gpiod::chip chip("gpiochip0"); - - for (auto& it: ::gpiod::line_iter(chip)) - ::std::cerr << it.offset() << ": " << it.name() << ::std::endl; -} -TEST_CASE(line_iterator); - -} /* namespace */ - -int main(int, char **) -{ - for (auto& it: test_funcs) { - ::std::cerr << "=================================================" << ::std::endl; - ::std::cerr << it.first << ":\n" << ::std::endl; - it.second(); - } - - return EXIT_SUCCESS; -} diff --git a/bindings/cxx/tests/Makefile.am b/bindings/cxx/tests/Makefile.am new file mode 100644 index 0000000..155445f --- /dev/null +++ b/bindings/cxx/tests/Makefile.am @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +# +# This file is part of libgpiod. +# +# Copyright (C) 2017-2018 Bartosz Golaszewski +# + +AM_CPPFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include +AM_CPPFLAGS += -I$(top_srcdir)/tests/mockup/ +AM_CPPFLAGS += -Wall -Wextra -g -std=gnu++11 +AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/ +AM_LDFLAGS += -lgpiomockup -L$(top_builddir)/tests/mockup/ +AM_LDFLAGS += -pthread + +bin_PROGRAMS = gpiod-cxx-test + +gpiod_cxx_test_SOURCES = gpiod-cxx-test.cpp \ + gpio-mockup.cpp \ + gpio-mockup.hpp \ + tests-chip.cpp \ + tests-event.cpp \ + tests-iter.cpp \ + tests-line.cpp diff --git a/bindings/cxx/tests/gpio-mockup.cpp b/bindings/cxx/tests/gpio-mockup.cpp new file mode 100644 index 0000000..64333f7 --- /dev/null +++ b/bindings/cxx/tests/gpio-mockup.cpp @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2019 Bartosz Golaszewski + */ + +#include + +#include "gpio-mockup.hpp" + +namespace gpiod { +namespace test { + +const ::std::bitset<32> mockup::FLAG_NAMED_LINES("1"); + +mockup& mockup::instance(void) +{ + static mockup mockup; + + return mockup; +} + +mockup::mockup(void) : _m_mockup(::gpio_mockup_new()) +{ + if (!this->_m_mockup) + throw ::std::system_error(errno, ::std::system_category(), + "unable to create the gpio-mockup context"); +} + +mockup::~mockup(void) +{ + ::gpio_mockup_unref(this->_m_mockup); +} + +void mockup::probe(const ::std::vector& chip_sizes, + const ::std::bitset<32>& flags) +{ + int ret, probe_flags = 0; + + if (flags.to_ulong() & FLAG_NAMED_LINES.to_ulong()) + probe_flags |= GPIO_MOCKUP_FLAG_NAMED_LINES; + + ret = ::gpio_mockup_probe(this->_m_mockup, chip_sizes.size(), + chip_sizes.data(), probe_flags); + if (ret) + throw ::std::system_error(errno, ::std::system_category(), + "unable to probe gpio-mockup module"); +} + +void mockup::remove(void) +{ + int ret = ::gpio_mockup_remove(this->_m_mockup); + if (ret) + throw ::std::system_error(errno, ::std::system_category(), + "unable to remove gpio-mockup module"); +} + +std::string mockup::chip_name(unsigned int idx) const +{ + const char *name = ::gpio_mockup_chip_name(this->_m_mockup, idx); + if (!name) + throw ::std::system_error(errno, ::std::system_category(), + "unable to retrieve the chip name"); + + return ::std::string(name); +} + +std::string mockup::chip_path(unsigned int idx) const +{ + const char *path = ::gpio_mockup_chip_path(this->_m_mockup, idx); + if (!path) + throw ::std::system_error(errno, ::std::system_category(), + "unable to retrieve the chip path"); + + return ::std::string(path); +} + +unsigned int mockup::chip_num(unsigned int idx) const +{ + int num = ::gpio_mockup_chip_num(this->_m_mockup, idx); + if (num < 0) + throw ::std::system_error(errno, ::std::system_category(), + "unable to retrieve the chip number"); + + return num; +} + +int mockup::chip_get_value(unsigned int chip_idx, unsigned int line_offset) +{ + int val = ::gpio_mockup_get_value(this->_m_mockup, chip_idx, line_offset); + if (val < 0) + throw ::std::system_error(errno, ::std::system_category(), + "error reading the line value"); + + return val; +} + +void mockup::chip_set_pull(unsigned int chip_idx, unsigned int line_offset, int pull) +{ + int ret = ::gpio_mockup_set_pull(this->_m_mockup, chip_idx, line_offset, pull); + if (ret) + throw ::std::system_error(errno, ::std::system_category(), + "error setting line pull"); +} + +mockup::probe_guard::probe_guard(const ::std::vector& chip_sizes, + const ::std::bitset<32>& flags) +{ + mockup::instance().probe(chip_sizes, flags); +} + +mockup::probe_guard::~probe_guard(void) +{ + mockup::instance().remove(); +} + +mockup::event_thread::event_thread(unsigned int chip_index, + unsigned int line_offset, unsigned int freq) + : _m_chip_index(chip_index), + _m_line_offset(line_offset), + _m_freq(freq), + _m_stop(false), + _m_mutex(), + _m_cond(), + _m_thread(&event_thread::event_worker, this) +{ + +} + +mockup::event_thread::~event_thread(void) +{ + ::std::unique_lock<::std::mutex> lock(this->_m_mutex); + this->_m_stop = true; + this->_m_cond.notify_all(); + lock.unlock(); + this->_m_thread.join(); +} + +void mockup::event_thread::event_worker(void) +{ + for (unsigned int i = 0;; i++) { + ::std::unique_lock<::std::mutex> lock(this->_m_mutex); + + if (this->_m_stop) + break; + + ::std::cv_status status = this->_m_cond.wait_for(lock, + std::chrono::milliseconds(this->_m_freq)); + if (status == ::std::cv_status::timeout) + mockup::instance().chip_set_pull(this->_m_chip_index, + this->_m_line_offset, i % 2); + } +} + +} /* namespace test */ +} /* namespace gpiod */ diff --git a/bindings/cxx/tests/gpio-mockup.hpp b/bindings/cxx/tests/gpio-mockup.hpp new file mode 100644 index 0000000..1859010 --- /dev/null +++ b/bindings/cxx/tests/gpio-mockup.hpp @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2019 Bartosz Golaszewski + */ + +#ifndef __GPIOD_CXX_TEST_HPP__ +#define __GPIOD_CXX_TEST_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +namespace gpiod { +namespace test { + +class mockup +{ +public: + + static mockup& instance(void); + + mockup(const mockup& other) = delete; + mockup(mockup&& other) = delete; + mockup& operator=(const mockup& other) = delete; + mockup& operator=(mockup&& other) = delete; + + void probe(const ::std::vector& chip_sizes, + const ::std::bitset<32>& flags = 0); + void remove(void); + + std::string chip_name(unsigned int idx) const; + std::string chip_path(unsigned int idx) const; + unsigned int chip_num(unsigned int idx) const; + + int chip_get_value(unsigned int chip_idx, unsigned int line_offset); + void chip_set_pull(unsigned int chip_idx, unsigned int line_offset, int pull); + + static const ::std::bitset<32> FLAG_NAMED_LINES; + + class probe_guard + { + public: + + probe_guard(const ::std::vector& chip_sizes, + const ::std::bitset<32>& flags = 0); + ~probe_guard(void); + + probe_guard(const probe_guard& other) = delete; + probe_guard(probe_guard&& other) = delete; + probe_guard& operator=(const probe_guard& other) = delete; + probe_guard& operator=(probe_guard&& other) = delete; + }; + + class event_thread + { + public: + + event_thread(unsigned int chip_index, unsigned int line_offset, unsigned int freq); + ~event_thread(void); + + event_thread(const event_thread& other) = delete; + event_thread(event_thread&& other) = delete; + event_thread& operator=(const event_thread& other) = delete; + event_thread& operator=(event_thread&& other) = delete; + + private: + + void event_worker(void); + + unsigned int _m_chip_index; + unsigned int _m_line_offset; + unsigned int _m_freq; + + bool _m_stop; + + ::std::mutex _m_mutex; + ::std::condition_variable _m_cond; + ::std::thread _m_thread; + }; + +private: + + mockup(void); + ~mockup(void); + + ::gpio_mockup *_m_mockup; +}; + +} /* namespace test */ +} /* namespace gpiod */ + +#endif /* __GPIOD_CXX_TEST_HPP__ */ diff --git a/bindings/cxx/tests/gpiod-cxx-test.cpp b/bindings/cxx/tests/gpiod-cxx-test.cpp new file mode 100644 index 0000000..fbae84f --- /dev/null +++ b/bindings/cxx/tests/gpiod-cxx-test.cpp @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2019 Bartosz Golaszewski + */ + +#define CATCH_CONFIG_MAIN +#include +#include +#include +#include +#include + +namespace { + +class kernel_checker +{ +public: + + kernel_checker(unsigned int major, unsigned int minor, unsigned int release) + { + unsigned long curr_major, curr_minor, curr_release, curr_ver, req_ver; + ::std::string major_str, minor_str, release_str; + ::utsname un; + int ret; + + ret = ::uname(::std::addressof(un)); + if (ret) + throw ::std::system_error(errno, ::std::system_category(), + "unable to read the kernel version"); + + ::std::stringstream ver_stream(::std::string(un.release)); + ::std::getline(ver_stream, major_str, '.'); + ::std::getline(ver_stream, minor_str, '.'); + ::std::getline(ver_stream, release_str, '.'); + + curr_major = ::std::stoul(major_str, nullptr, 0); + curr_minor = ::std::stoul(minor_str, nullptr, 0); + curr_release = ::std::stoul(release_str, nullptr, 0); + + curr_ver = KERNEL_VERSION(curr_major, curr_minor, curr_release); + req_ver = KERNEL_VERSION(major, minor, release); + + if (curr_ver < req_ver) + throw ::std::system_error(EOPNOTSUPP, ::std::system_category(), + "kernel release must be at least: " + + ::std::to_string(major) + "." + + ::std::to_string(minor) + "." + + ::std::to_string(release)); + } + + kernel_checker(const kernel_checker& other) = delete; + kernel_checker(kernel_checker&& other) = delete; + kernel_checker& operator=(const kernel_checker& other) = delete; + kernel_checker& operator=(kernel_checker&& other) = delete; +}; + +kernel_checker require_kernel(5, 2, 7); + +} /* namespace */ diff --git a/bindings/cxx/tests/tests-chip.cpp b/bindings/cxx/tests/tests-chip.cpp new file mode 100644 index 0000000..276b533 --- /dev/null +++ b/bindings/cxx/tests/tests-chip.cpp @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2019 Bartosz Golaszewski + */ + +#include +#include + +#include "gpio-mockup.hpp" + +using ::gpiod::test::mockup; + +TEST_CASE("GPIO chip device can be opened in different modes", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8, 8, 8 }); + + SECTION("open by name") + { + REQUIRE_NOTHROW(::gpiod::chip(mockup::instance().chip_name(1), + ::gpiod::chip::OPEN_BY_NAME)); + } + + SECTION("open by path") + { + REQUIRE_NOTHROW(::gpiod::chip(mockup::instance().chip_path(1), + ::gpiod::chip::OPEN_BY_PATH)); + } + + SECTION("open by label") + { + REQUIRE_NOTHROW(::gpiod::chip("gpio-mockup-B", ::gpiod::chip::OPEN_BY_LABEL)); + } + + SECTION("open by number") + { + REQUIRE_NOTHROW(::gpiod::chip(::std::to_string(mockup::instance().chip_num(1)), + ::gpiod::chip::OPEN_BY_NUMBER)); + } +} + +TEST_CASE("GPIO chip device can be opened with implicit lookup", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8, 8, 8 }); + + SECTION("lookup by name") + { + REQUIRE_NOTHROW(::gpiod::chip(mockup::instance().chip_name(1))); + } + + SECTION("lookup by path") + { + REQUIRE_NOTHROW(::gpiod::chip(mockup::instance().chip_path(1))); + } + + SECTION("lookup by label") + { + REQUIRE_NOTHROW(::gpiod::chip("gpio-mockup-B")); + } + + SECTION("lookup by number") + { + REQUIRE_NOTHROW(::gpiod::chip(::std::to_string(mockup::instance().chip_num(1)))); + } +} + +TEST_CASE("GPIO chip can be opened with the open() method in different modes", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8, 8, 8 }); + ::gpiod::chip chip; + + REQUIRE_FALSE(chip); + + SECTION("open by name") + { + REQUIRE_NOTHROW(chip.open(mockup::instance().chip_name(1), + ::gpiod::chip::OPEN_BY_NAME)); + } + + SECTION("open by path") + { + REQUIRE_NOTHROW(chip.open(mockup::instance().chip_path(1), + ::gpiod::chip::OPEN_BY_PATH)); + } + + SECTION("open by label") + { + REQUIRE_NOTHROW(chip.open("gpio-mockup-B", ::gpiod::chip::OPEN_BY_LABEL)); + } + + SECTION("open by number") + { + REQUIRE_NOTHROW(chip.open(::std::to_string(mockup::instance().chip_num(1)), + ::gpiod::chip::OPEN_BY_NUMBER)); + } +} + +TEST_CASE("Uninitialized GPIO chip behaves correctly", "[chip]") +{ + ::gpiod::chip chip; + + SECTION("uninitialized chip is 'false'") + { + REQUIRE_FALSE(chip); + } + + SECTION("using uninitialized chip throws logic_error") + { + REQUIRE_THROWS_AS(chip.name(), ::std::logic_error&); + } +} + +TEST_CASE("GPIO chip can be opened with the open() method with implicit lookup", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8, 8, 8 }); + ::gpiod::chip chip; + + SECTION("lookup by name") + { + REQUIRE_NOTHROW(chip.open(mockup::instance().chip_name(1))); + } + + SECTION("lookup by path") + { + REQUIRE_NOTHROW(chip.open(mockup::instance().chip_path(1))); + } + + SECTION("lookup by label") + { + REQUIRE_NOTHROW(chip.open("gpio-mockup-B")); + } + + SECTION("lookup by number") + { + REQUIRE_NOTHROW(chip.open(::std::to_string(mockup::instance().chip_num(1)))); + } +} + +TEST_CASE("Trying to open a nonexistent chip throws system_error", "[chip]") +{ + REQUIRE_THROWS_AS(::gpiod::chip("nonexistent-chip"), ::std::system_error&); +} + +TEST_CASE("Chip object can be reset", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8 }); + + ::gpiod::chip chip(mockup::instance().chip_name(0)); + REQUIRE(chip); + chip.reset(); + REQUIRE_FALSE(chip); +} + +TEST_CASE("Chip info can be correctly retrieved", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8, 16, 8 }); + + ::gpiod::chip chip(mockup::instance().chip_name(1)); + REQUIRE(chip.name() == mockup::instance().chip_name(1)); + REQUIRE(chip.label() == "gpio-mockup-B"); + REQUIRE(chip.num_lines() == 16); +} + +TEST_CASE("Chip object can be copied and compared", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8, 8 }); + + ::gpiod::chip chip1(mockup::instance().chip_name(0)); + auto chip2 = chip1; + REQUIRE(chip1 == chip2); + REQUIRE_FALSE(chip1 != chip2); + ::gpiod::chip chip3(mockup::instance().chip_name(1)); + REQUIRE(chip1 != chip3); + REQUIRE_FALSE(chip2 == chip3); +} + +TEST_CASE("Lines can be retrieved from chip objects", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8, 8, 8 }, mockup::FLAG_NAMED_LINES); + ::gpiod::chip chip(mockup::instance().chip_name(1)); + + SECTION("get single line by offset") + { + auto line = chip.get_line(3); + REQUIRE(line.name() == "gpio-mockup-B-3"); + } + + SECTION("find single line by name") + { + auto line = chip.find_line("gpio-mockup-B-3"); + REQUIRE(line.offset() == 3); + } + + SECTION("get multiple lines by offsets") + { + auto lines = chip.get_lines({ 1, 3, 4, 7}); + REQUIRE(lines.size() == 4); + REQUIRE(lines.get(0).name() == "gpio-mockup-B-1"); + REQUIRE(lines.get(1).name() == "gpio-mockup-B-3"); + REQUIRE(lines.get(2).name() == "gpio-mockup-B-4"); + REQUIRE(lines.get(3).name() == "gpio-mockup-B-7"); + } + + SECTION("get multiple lines by offsets in mixed order") + { + auto lines = chip.get_lines({ 5, 1, 3, 2}); + REQUIRE(lines.size() == 4); + REQUIRE(lines.get(0).name() == "gpio-mockup-B-5"); + REQUIRE(lines.get(1).name() == "gpio-mockup-B-1"); + REQUIRE(lines.get(2).name() == "gpio-mockup-B-3"); + REQUIRE(lines.get(3).name() == "gpio-mockup-B-2"); + } + + SECTION("find multiple lines by names") + { + auto lines = chip.find_lines({ "gpio-mockup-B-2", + "gpio-mockup-B-5", + "gpio-mockup-B-6"}); + REQUIRE(lines.size() == 3); + REQUIRE(lines.get(0).offset() == 2); + REQUIRE(lines.get(1).offset() == 5); + REQUIRE(lines.get(2).offset() == 6); + } +} + +TEST_CASE("All lines can be retrieved from a chip at once", "[chip]") +{ + mockup::probe_guard mockup_chips({ 4 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + + auto lines = chip.get_all_lines(); + REQUIRE(lines.size() == 4); + REQUIRE(lines.get(0).offset() == 0); + REQUIRE(lines.get(1).offset() == 1); + REQUIRE(lines.get(2).offset() == 2); + REQUIRE(lines.get(3).offset() == 3); +} + +TEST_CASE("Errors occurring when retrieving lines are correctly reported", "[chip]") +{ + mockup::probe_guard mockup_chips({ 8 }, mockup::FLAG_NAMED_LINES); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + + SECTION("invalid offset (single line)") + { + REQUIRE_THROWS_AS(chip.get_line(9), ::std::out_of_range&); + } + + SECTION("invalid offset (multiple lines)") + { + REQUIRE_THROWS_AS(chip.get_lines({ 1, 19, 4, 7 }), ::std::out_of_range&); + } + + SECTION("line not found by name") + { + REQUIRE_FALSE(chip.find_line("nonexistent-line")); + } + + SECTION("line not found by name (multiple lines)") + { + REQUIRE_FALSE(chip.find_lines({ "gpio-mockup-B-2", + "nonexistent-line", + "gpio-mockup-B-6"})); + } +} diff --git a/bindings/cxx/tests/tests-event.cpp b/bindings/cxx/tests/tests-event.cpp new file mode 100644 index 0000000..f93bb72 --- /dev/null +++ b/bindings/cxx/tests/tests-event.cpp @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2019 Bartosz Golaszewski + */ + +#include +#include +#include +#include + +#include "gpio-mockup.hpp" + +using ::gpiod::test::mockup; + +namespace { + +const ::std::string consumer = "event-test"; + +} /* namespace */ + +TEST_CASE("Line events can be detected", "[event][line]") +{ + mockup::probe_guard mockup_chips({ 8 }); + mockup::event_thread events(0, 4, 200); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + auto line = chip.get_line(4); + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + + SECTION("rising edge") + { + config.request_type = ::gpiod::line_request::EVENT_RISING_EDGE; + line.request(config); + + auto got_event = line.event_wait(::std::chrono::seconds(1)); + REQUIRE(got_event); + + auto event = line.event_read(); + REQUIRE(event.source == line); + REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE); + } + + SECTION("falling edge") + { + config.request_type = ::gpiod::line_request::EVENT_FALLING_EDGE; + + line.request(config); + + auto got_event = line.event_wait(::std::chrono::seconds(1)); + REQUIRE(got_event); + + auto event = line.event_read(); + REQUIRE(event.source == line); + REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE); + } + + SECTION("both edges") + { + config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; + + line.request(config); + + auto got_event = line.event_wait(::std::chrono::seconds(1)); + REQUIRE(got_event); + + auto event = line.event_read(); + REQUIRE(event.source == line); + REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE); + + got_event = line.event_wait(::std::chrono::seconds(1)); + REQUIRE(got_event); + + event = line.event_read(); + REQUIRE(event.source == line); + REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE); + } + + SECTION("active-low") + { + config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; + config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW; + + line.request(config); + + auto got_event = line.event_wait(::std::chrono::seconds(1)); + REQUIRE(got_event); + + auto event = line.event_read(); + REQUIRE(event.source == line); + REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE); + + got_event = line.event_wait(::std::chrono::seconds(1)); + REQUIRE(got_event); + + event = line.event_read(); + REQUIRE(event.source == line); + REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE); + } +} + +TEST_CASE("Watching line_bulk objects for events works", "[event][bulk]") +{ + mockup::probe_guard mockup_chips({ 8 }); + mockup::event_thread events(0, 2, 200); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + auto lines = chip.get_lines({ 0, 1, 2, 3 }); + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; + lines.request(config); + + auto event_lines = lines.event_wait(::std::chrono::seconds(1)); + REQUIRE(event_lines); + REQUIRE(event_lines.size() == 1); + + auto event = event_lines.get(0).event_read(); + REQUIRE(event.source == event_lines.get(0)); + REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE); + + event_lines = lines.event_wait(::std::chrono::seconds(1)); + REQUIRE(event_lines); + REQUIRE(event_lines.size() == 1); + + event = event_lines.get(0).event_read(); + REQUIRE(event.source == event_lines.get(0)); + REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE); +} + +TEST_CASE("It's possible to retrieve the event file descriptor", "[event][line]") +{ + mockup::probe_guard mockup_chips({ 8 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + auto line = chip.get_line(4); + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + + SECTION("get the fd") + { + config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; + + line.request(config); + REQUIRE(line.event_get_fd() >= 0); + } + + SECTION("error if not requested") + { + REQUIRE_THROWS_AS(line.event_get_fd(), ::std::system_error&); + } + + SECTION("error if requested for values") + { + config.request_type = ::gpiod::line_request::DIRECTION_INPUT; + + line.request(config); + REQUIRE_THROWS_AS(line.event_get_fd(), ::std::system_error&); + } +} + +TEST_CASE("Event file descriptors can be used for polling", "[event]") +{ + mockup::probe_guard mockup_chips({ 8 }); + mockup::event_thread events(0, 3, 200); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + ::std::map fd_line_map; + auto lines = chip.get_lines({ 0, 1, 2, 3, 4, 5 }); + + ::gpiod::line_request config; + config.consumer = consumer.c_str(); + config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; + + lines.request(config); + + fd_line_map[lines[1].event_get_fd()] = lines[1]; + fd_line_map[lines[3].event_get_fd()] = lines[3]; + fd_line_map[lines[5].event_get_fd()] = lines[5]; + + ::pollfd fds[3]; + fds[0].fd = lines[1].event_get_fd(); + fds[1].fd = lines[3].event_get_fd(); + fds[2].fd = lines[5].event_get_fd(); + + fds[0].events = fds[1].events = fds[2].events = POLLIN | POLLPRI; + + int ret = ::poll(fds, 3, 1000); + REQUIRE(ret == 1); + + for (int i = 0; i < 3; i++) { + if (fds[i].revents) { + auto event = fd_line_map[fds[i].fd].event_read(); + REQUIRE(event.source == fd_line_map[fds[i].fd]); + REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE); + } + } +} + +TEST_CASE("It's possible to read values from lines requested for events", "[event][line]") +{ + mockup::probe_guard mockup_chips({ 8 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + auto line = chip.get_line(4); + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES; + + mockup::instance().chip_set_pull(0, 4, 1); + + SECTION("active-high (default)") + { + line.request(config); + REQUIRE(line.get_value() == 1); + } + + SECTION("active-low") + { + config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW; + line.request(config); + REQUIRE(line.get_value() == 0); + } +} diff --git a/bindings/cxx/tests/tests-iter.cpp b/bindings/cxx/tests/tests-iter.cpp new file mode 100644 index 0000000..1af0256 --- /dev/null +++ b/bindings/cxx/tests/tests-iter.cpp @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2019 Bartosz Golaszewski + */ + +#include +#include + +#include "gpio-mockup.hpp" + +using ::gpiod::test::mockup; + +TEST_CASE("Chip iterator works", "[iter][chip]") +{ + mockup::probe_guard mockup_chips({ 8, 8, 8 }); + bool gotA = false, gotB = false, gotC = false; + + for (auto& it: ::gpiod::make_chip_iter()) { + if (it.label() == "gpio-mockup-A") + gotA = true; + if (it.label() == "gpio-mockup-B") + gotB = true; + if (it.label() == "gpio-mockup-C") + gotC = true; + } + + REQUIRE(gotA); + REQUIRE(gotB); + REQUIRE(gotC); +} + +TEST_CASE("Chip iterator loop can be broken out of", "[iter][chip]") +{ + mockup::probe_guard mockup_chips({ 8, 8, 8, 8, 8, 8 }); + int count_chips = 0; + + for (auto& it: ::gpiod::make_chip_iter()) { + if (it.label() == "gpio-mockup-A" || + it.label() == "gpio-mockup-B" || + it.label() == "gpio-mockup-C") + count_chips++; + + if (count_chips == 3) + break; + } + + REQUIRE(count_chips == 3); +} + +TEST_CASE("Line iterator works", "[iter][line]") +{ + mockup::probe_guard mockup_chips({ 4 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + int count = 0; + + for (auto& it: ::gpiod::line_iter(chip)) + REQUIRE(it.offset() == count++); + + REQUIRE(count == chip.num_lines()); +} diff --git a/bindings/cxx/tests/tests-line.cpp b/bindings/cxx/tests/tests-line.cpp new file mode 100644 index 0000000..2684bcb --- /dev/null +++ b/bindings/cxx/tests/tests-line.cpp @@ -0,0 +1,334 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2019 Bartosz Golaszewski + */ + +#include +#include + +#include "gpio-mockup.hpp" + +using ::gpiod::test::mockup; + +namespace { + +const ::std::string consumer = "line-test"; + +} /* namespace */ + +TEST_CASE("Global find_line() function works", "[line]") +{ + mockup::probe_guard mockup_chips({ 8, 8, 8, 8, 8 }, mockup::FLAG_NAMED_LINES); + + auto line = ::gpiod::find_line("gpio-mockup-C-5"); + REQUIRE(line.offset() == 5); + REQUIRE(line.name() == "gpio-mockup-C-5"); + REQUIRE(line.get_chip().label() == "gpio-mockup-C"); +} + +TEST_CASE("Line information can be correctly retrieved", "[line]") +{ + mockup::probe_guard mockup_chips({ 8 }, mockup::FLAG_NAMED_LINES); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + auto line = chip.get_line(4); + + SECTION("unexported line") + { + REQUIRE(line.offset() == 4); + REQUIRE(line.name() == "gpio-mockup-A-4"); + REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT); + REQUIRE(line.active_state() == ::gpiod::line::ACTIVE_HIGH); + REQUIRE(line.consumer().empty()); + REQUIRE_FALSE(line.is_requested()); + REQUIRE_FALSE(line.is_used()); + } + + SECTION("exported line") + { + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; + line.request(config); + + REQUIRE(line.offset() == 4); + REQUIRE(line.name() == "gpio-mockup-A-4"); + REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); + REQUIRE(line.active_state() == ::gpiod::line::ACTIVE_HIGH); + REQUIRE(line.is_requested()); + REQUIRE(line.is_used()); + } + + SECTION("exported line with flags") + { + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; + config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW | + ::gpiod::line_request::FLAG_OPEN_DRAIN; + line.request(config); + + REQUIRE(line.offset() == 4); + REQUIRE(line.name() == "gpio-mockup-A-4"); + /* FIXME Uncomment the line below once this issue is fixed in the kernel. */ + //REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); + REQUIRE(line.active_state() == ::gpiod::line::ACTIVE_LOW); + REQUIRE(line.is_requested()); + REQUIRE(line.is_used()); + REQUIRE(line.is_open_drain()); + REQUIRE_FALSE(line.is_open_source()); + } +} + +TEST_CASE("Line bulk object works correctly", "[line][bulk]") +{ + mockup::probe_guard mockup_chips({ 8 }, mockup::FLAG_NAMED_LINES); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + + SECTION("lines can be added, accessed and cleared") + { + ::gpiod::line_bulk lines; + + REQUIRE(lines.empty()); + + lines.append(chip.get_line(0)); + lines.append(chip.get_line(1)); + lines.append(chip.get_line(2)); + + REQUIRE(lines.size() == 3); + REQUIRE(lines.get(1).name() == "gpio-mockup-A-1"); + REQUIRE(lines[2].name() == "gpio-mockup-A-2"); + + lines.clear(); + + REQUIRE(lines.empty()); + } + + SECTION("bulk iterator works") + { + auto lines = chip.get_all_lines(); + int count = 0; + + for (auto& it: lines) + REQUIRE(it.offset() == count++); + + REQUIRE(count == chip.num_lines()); + } + + SECTION("accessing lines out of range throws exception") + { + auto lines = chip.get_all_lines(); + + REQUIRE_THROWS_AS(lines.get(11), ::std::out_of_range&); + } +} + +TEST_CASE("Line values can be set and read", "[line]") +{ + mockup::probe_guard mockup_chips({ 8 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + + SECTION("get value (single line)") + { + auto line = chip.get_line(3); + config.request_type = ::gpiod::line_request::DIRECTION_INPUT; + line.request(config); + REQUIRE(line.get_value() == 0); + mockup::instance().chip_set_pull(0, 3, 1); + REQUIRE(line.get_value() == 1); + } + + SECTION("set value (single line)") + { + auto line = chip.get_line(3); + config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; + line.request(config); + line.set_value(1); + REQUIRE(mockup::instance().chip_get_value(0, 3) == 1); + line.set_value(0); + REQUIRE(mockup::instance().chip_get_value(0, 3) == 0); + } + + SECTION("set value with default value parameter") + { + auto line = chip.get_line(3); + config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; + line.request(config, 1); + REQUIRE(mockup::instance().chip_get_value(0, 3) == 1); + } + + SECTION("get multiple values at once") + { + auto lines = chip.get_lines({ 0, 1, 2, 3, 4 }); + config.request_type = ::gpiod::line_request::DIRECTION_INPUT; + lines.request(config); + REQUIRE(lines.get_values() == ::std::vector({ 0, 0, 0, 0, 0 })); + mockup::instance().chip_set_pull(0, 1, 1); + mockup::instance().chip_set_pull(0, 3, 1); + mockup::instance().chip_set_pull(0, 4, 1); + REQUIRE(lines.get_values() == ::std::vector({ 0, 1, 0, 1, 1 })); + } + + SECTION("set multiple values at once") + { + auto lines = chip.get_lines({ 0, 1, 2, 6, 7 }); + config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; + lines.request(config); + lines.set_values({ 1, 1, 0, 1, 0 }); + REQUIRE(mockup::instance().chip_get_value(0, 0) == 1); + REQUIRE(mockup::instance().chip_get_value(0, 1) == 1); + REQUIRE(mockup::instance().chip_get_value(0, 2) == 0); + REQUIRE(mockup::instance().chip_get_value(0, 6) == 1); + REQUIRE(mockup::instance().chip_get_value(0, 7) == 0); + } + + SECTION("set multiple values with default values paramter") + { + auto lines = chip.get_lines({ 1, 2, 4, 6, 7 }); + config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; + lines.request(config, { 1, 1, 0, 1, 0 }); + REQUIRE(mockup::instance().chip_get_value(0, 1) == 1); + REQUIRE(mockup::instance().chip_get_value(0, 2) == 1); + REQUIRE(mockup::instance().chip_get_value(0, 4) == 0); + REQUIRE(mockup::instance().chip_get_value(0, 6) == 1); + REQUIRE(mockup::instance().chip_get_value(0, 7) == 0); + } + + SECTION("get value (single line, active-low") + { + auto line = chip.get_line(4); + config.request_type = ::gpiod::line_request::DIRECTION_INPUT; + config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW; + line.request(config); + REQUIRE(line.get_value() == 1); + mockup::instance().chip_set_pull(0, 4, 1); + REQUIRE(line.get_value() == 0); + } + + SECTION("set value (single line, active-low)") + { + auto line = chip.get_line(3); + config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; + config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW; + line.request(config); + line.set_value(1); + REQUIRE(mockup::instance().chip_get_value(0, 3) == 0); + line.set_value(0); + REQUIRE(mockup::instance().chip_get_value(0, 3) == 1); + } +} + +TEST_CASE("Exported line can be released", "[line]") +{ + mockup::probe_guard mockup_chips({ 8 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + auto line = chip.get_line(4); + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + config.request_type = ::gpiod::line_request::DIRECTION_INPUT; + + line.request(config); + + REQUIRE(line.is_requested()); + REQUIRE(line.get_value() == 0); + + line.release(); + + REQUIRE_FALSE(line.is_requested()); + REQUIRE_THROWS_AS(line.get_value(), ::std::system_error&); +} + +TEST_CASE("Uninitialized GPIO line behaves correctly", "[line]") +{ + ::gpiod::line line; + + SECTION("uninitialized line is 'false'") + { + REQUIRE_FALSE(line); + } + + SECTION("using uninitialized line throws logic_error") + { + REQUIRE_THROWS_AS(line.name(), ::std::logic_error&); + } +} + +TEST_CASE("Uninitialized GPIO line_bulk behaves correctly", "[line][bulk]") +{ + ::gpiod::line_bulk bulk; + + SECTION("uninitialized line_bulk is 'false'") + { + REQUIRE_FALSE(bulk); + } + + SECTION("using uninitialized line_bulk throws logic_error") + { + REQUIRE_THROWS_AS(bulk.get(0), ::std::logic_error&); + } +} + +TEST_CASE("Cannot request the same line twice", "[line]") +{ + mockup::probe_guard mockup_chips({ 8 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + ::gpiod::line_request config; + + config.consumer = consumer.c_str(); + config.request_type = ::gpiod::line_request::DIRECTION_INPUT; + + SECTION("two separate calls to request()") + { + auto line = chip.get_line(3); + + REQUIRE_NOTHROW(line.request(config)); + REQUIRE_THROWS_AS(line.request(config), ::std::system_error&); + } + + SECTION("request the same line twice in line_bulk") + { + /* + * While a line_bulk object can hold two or more line objects + * representing the same line - requesting it will fail. + */ + auto lines = chip.get_lines({ 2, 3, 4, 4 }); + + REQUIRE_THROWS_AS(lines.request(config), ::std::system_error&); + } +} + +TEST_CASE("Cannot get/set values of unrequested lines", "[line]") +{ + mockup::probe_guard mockup_chips({ 8 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + auto line = chip.get_line(3); + + SECTION("get value") + { + REQUIRE_THROWS_AS(line.get_value(), ::std::system_error&); + } + + SECTION("set value") + { + REQUIRE_THROWS_AS(line.set_value(1), ::std::system_error&); + } +} + +TEST_CASE("Line objects can be compared") +{ + mockup::probe_guard mockup_chips({ 8 }); + ::gpiod::chip chip(mockup::instance().chip_name(0)); + auto line1 = chip.get_line(3); + auto line2 = chip.get_line(3); + auto line3 = chip.get_line(4); + + REQUIRE(line1 == line2); + REQUIRE(line2 != line3); +} diff --git a/configure.ac b/configure.ac index 8f0eb38..f72e13b 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,9 @@ AC_DEFUN([HEADER_NOT_FOUND_LIB], AC_DEFUN([HEADER_NOT_FOUND_TESTS], [ERR_NOT_FOUND([$1 header], [the test suite])]) +AC_DEFUN([HEADER_NOT_FOUND_CXX], + [ERR_NOT_FOUND([$1 header], [C++ bindings])]) + # This is always checked (library needs this) AC_HEADER_STDC AC_FUNC_MALLOC @@ -158,6 +161,13 @@ then AC_LIBTOOL_CXX # This needs autoconf-archive AX_CXX_COMPILE_STDCXX_11([ext], [mandatory]) + + if test "x$with_tests" = xtrue + then + AC_LANG_PUSH([C++]) + AC_CHECK_HEADERS([catch.hpp], [], [HEADER_NOT_FOUND_CXX([catch.hpp])]) + AC_LANG_POP([C++]) + fi fi AC_ARG_ENABLE([bindings-python], @@ -205,6 +215,7 @@ AC_CONFIG_FILES([libgpiod.pc bindings/Makefile bindings/cxx/Makefile bindings/cxx/examples/Makefile + bindings/cxx/tests/Makefile bindings/python/Makefile bindings/python/examples/Makefile man/Makefile])