/* 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); SECTION("line found") { 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"); } SECTION("line not found") { auto line = ::gpiod::find_line("nonexistent-line"); REQUIRE_FALSE(line); } } 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()); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); REQUIRE(line.bias() == ::gpiod::line::BIAS_AS_IS); } 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()); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); REQUIRE(line.bias() == ::gpiod::line::BIAS_AS_IS); } 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"); 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()); REQUIRE(line.bias() == ::gpiod::line::BIAS_AS_IS); } SECTION("exported open source line") { ::gpiod::line_request config; config.consumer = consumer.c_str(); config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; config.flags = ::gpiod::line_request::FLAG_OPEN_SOURCE; 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()); REQUIRE_FALSE(line.is_open_drain()); REQUIRE(line.is_open_source()); REQUIRE(line.bias() == ::gpiod::line::BIAS_AS_IS); } SECTION("exported bias disable line") { ::gpiod::line_request config; config.consumer = consumer.c_str(); config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; config.flags = ::gpiod::line_request::FLAG_BIAS_DISABLE; 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()); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); REQUIRE(line.bias() == ::gpiod::line::BIAS_DISABLE); } SECTION("exported pull-down line") { ::gpiod::line_request config; config.consumer = consumer.c_str(); config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; config.flags = ::gpiod::line_request::FLAG_BIAS_PULL_DOWN; 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()); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); REQUIRE(line.bias() == ::gpiod::line::BIAS_PULL_DOWN); } SECTION("exported pull-up line") { ::gpiod::line_request config; config.consumer = consumer.c_str(); config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; config.flags = ::gpiod::line_request::FLAG_BIAS_PULL_UP; 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()); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); REQUIRE(line.bias() == ::gpiod::line::BIAS_PULL_UP); } SECTION("update line info") { REQUIRE_NOTHROW(line.update()); } } 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(); unsigned 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 parameter") { 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("Line can be reconfigured", "[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("set config (single line, active-state)") { auto line = chip.get_line(3); config.request_type = ::gpiod::line_request::DIRECTION_INPUT; config.flags = 0; line.request(config); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT); REQUIRE(line.active_state() == ::gpiod::line::ACTIVE_HIGH); line.set_config(::gpiod::line_request::DIRECTION_OUTPUT, ::gpiod::line_request::FLAG_ACTIVE_LOW,1); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE(line.active_state() == ::gpiod::line::ACTIVE_LOW); REQUIRE(mockup::instance().chip_get_value(0, 3) == 0); line.set_value(0); REQUIRE(mockup::instance().chip_get_value(0, 3) == 1); line.set_config(::gpiod::line_request::DIRECTION_OUTPUT, 0); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE(line.active_state() == ::gpiod::line::ACTIVE_HIGH); REQUIRE(mockup::instance().chip_get_value(0, 3) == 0); line.set_value(1); REQUIRE(mockup::instance().chip_get_value(0, 3) == 1); } SECTION("set flags (single line, active-state)") { auto line = chip.get_line(3); config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; config.flags = 0; line.request(config,1); REQUIRE(mockup::instance().chip_get_value(0, 3) == 1); line.set_flags(::gpiod::line_request::FLAG_ACTIVE_LOW); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE(line.active_state() == ::gpiod::line::ACTIVE_LOW); REQUIRE(mockup::instance().chip_get_value(0, 3) == 0); line.set_flags(0); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE(line.active_state() == ::gpiod::line::ACTIVE_HIGH); REQUIRE(mockup::instance().chip_get_value(0, 3) == 1); } SECTION("set flags (single line, drive)") { auto line = chip.get_line(3); config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; config.flags = 0; line.request(config); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); line.set_flags(::gpiod::line_request::FLAG_OPEN_DRAIN); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); line.set_flags(::gpiod::line_request::FLAG_OPEN_SOURCE); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE_FALSE(line.is_open_drain()); REQUIRE(line.is_open_source()); line.set_flags(0); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); } SECTION("set flags (single line, bias)") { auto line = chip.get_line(3); config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; config.flags = 0; line.request(config); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); line.set_flags(::gpiod::line_request::FLAG_OPEN_DRAIN); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); line.set_flags(::gpiod::line_request::FLAG_OPEN_SOURCE); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE_FALSE(line.is_open_drain()); REQUIRE(line.is_open_source()); line.set_flags(0); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); REQUIRE_FALSE(line.is_open_drain()); REQUIRE_FALSE(line.is_open_source()); } SECTION("set direction input (single line)") { auto line = chip.get_line(3); config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; config.flags = 0; line.request(config); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); line.set_direction_input(); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT); } SECTION("set direction output (single line)") { auto line = chip.get_line(3); config.request_type = ::gpiod::line_request::DIRECTION_INPUT; config.flags = 0; line.request(config); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT); line.set_direction_output(1); REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT); 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); }