| #include <systemd/sd-bus-protocol.h> |
| |
| #include <sdbusplus/exception.hpp> |
| #include <sdbusplus/message.hpp> |
| #include <sdbusplus/test/sdbus_mock.hpp> |
| |
| #include <cerrno> |
| #include <map> |
| #include <set> |
| #include <string> |
| #include <tuple> |
| #include <unordered_map> |
| #include <variant> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| namespace |
| { |
| |
| using testing::DoAll; |
| using testing::Return; |
| using testing::StrEq; |
| |
| ACTION_TEMPLATE(AssignReadVal, HAS_1_TEMPLATE_PARAMS(typename, T), |
| AND_1_VALUE_PARAMS(val)) |
| { |
| *static_cast<T*>(arg2) = val; |
| } |
| |
| class ReadTest : public testing::Test |
| { |
| protected: |
| testing::StrictMock<sdbusplus::SdBusMock> mock; |
| |
| void SetUp() override |
| { |
| EXPECT_CALL(mock, sd_bus_message_new_method_call(testing::_, testing::_, |
| nullptr, nullptr, |
| nullptr, nullptr)) |
| .WillRepeatedly(Return(0)); |
| }; |
| |
| sdbusplus::message::message new_message() |
| { |
| return sdbusplus::get_mocked_new(&mock).new_method_call( |
| nullptr, nullptr, nullptr, nullptr); |
| } |
| |
| void expect_basic_error(char type, int ret) |
| { |
| EXPECT_CALL(mock, sd_bus_message_read_basic(nullptr, type, testing::_)) |
| .WillOnce(Return(ret)); |
| } |
| |
| template <typename T> |
| void expect_basic(char type, T val) |
| { |
| EXPECT_CALL(mock, sd_bus_message_read_basic(nullptr, type, testing::_)) |
| .WillOnce(DoAll(AssignReadVal<T>(val), Return(0))); |
| } |
| |
| void expect_verify_type(char type, const char* contents, int ret) |
| { |
| EXPECT_CALL(mock, |
| sd_bus_message_verify_type(nullptr, type, StrEq(contents))) |
| .WillOnce(Return(ret)); |
| } |
| |
| void expect_at_end(bool complete, int ret) |
| { |
| EXPECT_CALL(mock, sd_bus_message_at_end(nullptr, complete)) |
| .WillOnce(Return(ret)); |
| } |
| |
| void expect_skip(const char* contents, int ret = 0) |
| { |
| EXPECT_CALL(mock, sd_bus_message_skip(nullptr, StrEq(contents))) |
| .WillOnce(Return(ret)); |
| } |
| |
| void expect_enter_container(char type, const char* contents, int ret = 0) |
| { |
| EXPECT_CALL(mock, sd_bus_message_enter_container(nullptr, type, |
| StrEq(contents))) |
| .WillOnce(Return(ret)); |
| } |
| |
| void expect_exit_container(int ret = 0) |
| { |
| EXPECT_CALL(mock, sd_bus_message_exit_container(nullptr)) |
| .WillOnce(Return(ret)); |
| } |
| }; |
| |
| TEST_F(ReadTest, Int) |
| { |
| const int i = 1; |
| expect_basic<int>(SD_BUS_TYPE_INT32, i); |
| int ret; |
| new_message().read(ret); |
| EXPECT_EQ(i, ret); |
| } |
| |
| TEST_F(ReadTest, Bool) |
| { |
| const bool b = true; |
| expect_basic<int>(SD_BUS_TYPE_BOOLEAN, b); |
| bool ret; |
| new_message().read(ret); |
| EXPECT_EQ(b, ret); |
| } |
| |
| TEST_F(ReadTest, Double) |
| { |
| const double d = 1.1; |
| expect_basic<double>(SD_BUS_TYPE_DOUBLE, d); |
| double ret; |
| new_message().read(ret); |
| EXPECT_EQ(d, ret); |
| } |
| |
| TEST_F(ReadTest, CString) |
| { |
| const char* const s = "asdf"; |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, s); |
| const char* ret; |
| new_message().read(ret); |
| EXPECT_EQ(s, ret); |
| } |
| |
| TEST_F(ReadTest, String) |
| { |
| const char* const s = "fsda"; |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, s); |
| std::string ret; |
| new_message().read(ret); |
| // Pointer comparison here is intentional as we don't expect a copy |
| EXPECT_EQ(s, ret); |
| } |
| |
| TEST_F(ReadTest, ObjectPath) |
| { |
| const char* const s = "/fsda"; |
| expect_basic<const char*>(SD_BUS_TYPE_OBJECT_PATH, s); |
| sdbusplus::message::object_path ret; |
| new_message().read(ret); |
| EXPECT_EQ(s, ret.str); |
| } |
| |
| TEST_F(ReadTest, Signature) |
| { |
| const char* const s = "{ii}"; |
| expect_basic<const char*>(SD_BUS_TYPE_SIGNATURE, s); |
| sdbusplus::message::signature ret; |
| new_message().read(ret); |
| EXPECT_EQ(s, ret.str); |
| } |
| |
| TEST_F(ReadTest, CombinedBasic) |
| { |
| const double a = 2.2; |
| const char* const b = "ijkd"; |
| const bool c = false; |
| const int d = 18; |
| |
| { |
| testing::InSequence seq; |
| expect_basic<double>(SD_BUS_TYPE_DOUBLE, a); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, b); |
| expect_basic<int>(SD_BUS_TYPE_BOOLEAN, c); |
| expect_basic<int>(SD_BUS_TYPE_INT32, d); |
| } |
| |
| double ret_a; |
| const char* ret_b; |
| bool ret_c; |
| int ret_d; |
| new_message().read(ret_a, ret_b, ret_c, ret_d); |
| EXPECT_EQ(a, ret_a); |
| EXPECT_EQ(b, ret_b); |
| EXPECT_EQ(c, ret_c); |
| EXPECT_EQ(d, ret_d); |
| } |
| |
| TEST_F(ReadTest, BasicError) |
| { |
| expect_basic_error(SD_BUS_TYPE_INT32, -EINVAL); |
| int ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, BasicStringError) |
| { |
| expect_basic_error(SD_BUS_TYPE_STRING, -EINVAL); |
| std::string ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, BasicStringWrapperError) |
| { |
| expect_basic_error(SD_BUS_TYPE_SIGNATURE, -EINVAL); |
| sdbusplus::message::signature ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, BasicBoolError) |
| { |
| expect_basic_error(SD_BUS_TYPE_BOOLEAN, -EINVAL); |
| bool ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, Vector) |
| { |
| const std::vector<int> vi{1, 2, 3, 4}; |
| |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "i"); |
| for (const auto& i : vi) |
| { |
| expect_at_end(false, 0); |
| expect_basic<int>(SD_BUS_TYPE_INT32, i); |
| } |
| expect_at_end(false, 1); |
| expect_exit_container(); |
| } |
| |
| std::vector<int> ret_vi; |
| new_message().read(ret_vi); |
| EXPECT_EQ(vi, ret_vi); |
| } |
| |
| TEST_F(ReadTest, VectorEnterError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "i", -EINVAL); |
| } |
| |
| std::vector<int> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, VectorIterError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "i"); |
| expect_at_end(false, 0); |
| expect_basic<int>(SD_BUS_TYPE_INT32, 1); |
| expect_at_end(false, -EINVAL); |
| } |
| |
| std::vector<int> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, VectorExitError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "i"); |
| expect_at_end(false, 0); |
| expect_basic<int>(SD_BUS_TYPE_INT32, 1); |
| expect_at_end(false, 0); |
| expect_basic<int>(SD_BUS_TYPE_INT32, 2); |
| expect_at_end(false, 1); |
| expect_exit_container(-EINVAL); |
| } |
| |
| std::vector<int> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, Set) |
| { |
| const std::set<std::string> ss{"one", "two", "eight"}; |
| |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "s"); |
| for (const auto& s : ss) |
| { |
| expect_at_end(false, 0); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, s.c_str()); |
| } |
| expect_at_end(false, 1); |
| expect_exit_container(); |
| } |
| |
| std::set<std::string> ret_ss; |
| new_message().read(ret_ss); |
| EXPECT_EQ(ss, ret_ss); |
| } |
| |
| TEST_F(ReadTest, Map) |
| { |
| const std::map<int, std::string> mis{ |
| {1, "a"}, |
| {2, "bc"}, |
| {3, "def"}, |
| {4, "ghij"}, |
| }; |
| |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "{is}"); |
| for (const auto& is : mis) |
| { |
| expect_at_end(false, 0); |
| expect_enter_container(SD_BUS_TYPE_DICT_ENTRY, "is"); |
| expect_basic<int>(SD_BUS_TYPE_INT32, is.first); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, is.second.c_str()); |
| expect_exit_container(); |
| } |
| expect_at_end(false, 1); |
| expect_exit_container(); |
| } |
| |
| std::map<int, std::string> ret_mis; |
| new_message().read(ret_mis); |
| EXPECT_EQ(mis, ret_mis); |
| } |
| |
| TEST_F(ReadTest, MapEnterError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "{si}", -EINVAL); |
| } |
| |
| std::map<std::string, int> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, MapEntryEnterError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "{si}"); |
| expect_at_end(false, 0); |
| expect_enter_container(SD_BUS_TYPE_DICT_ENTRY, "si", -EINVAL); |
| } |
| |
| std::map<std::string, int> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, MapEntryExitError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "{si}"); |
| expect_at_end(false, 0); |
| expect_enter_container(SD_BUS_TYPE_DICT_ENTRY, "si"); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, "ab"); |
| expect_basic<int>(SD_BUS_TYPE_INT32, 1); |
| expect_exit_container(-EINVAL); |
| } |
| |
| std::map<std::string, int> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, MapIterError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "{si}"); |
| expect_at_end(false, 0); |
| expect_enter_container(SD_BUS_TYPE_DICT_ENTRY, "si"); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, "ab"); |
| expect_basic<int>(SD_BUS_TYPE_INT32, 1); |
| expect_exit_container(); |
| expect_at_end(false, -EINVAL); |
| } |
| |
| std::map<std::string, int> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, MapExitError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "{si}"); |
| expect_at_end(false, 0); |
| expect_enter_container(SD_BUS_TYPE_DICT_ENTRY, "si"); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, "ab"); |
| expect_basic<int>(SD_BUS_TYPE_INT32, 1); |
| expect_exit_container(); |
| expect_at_end(false, 1); |
| expect_exit_container(-EINVAL); |
| } |
| |
| std::map<std::string, int> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, UnorderedMap) |
| { |
| const std::unordered_map<int, std::string> mis{ |
| {1, "a"}, |
| {2, "bc"}, |
| {3, "def"}, |
| {4, "ghij"}, |
| }; |
| |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "{is}"); |
| for (const auto& is : mis) |
| { |
| expect_at_end(false, 0); |
| expect_enter_container(SD_BUS_TYPE_DICT_ENTRY, "is"); |
| expect_basic<int>(SD_BUS_TYPE_INT32, is.first); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, is.second.c_str()); |
| expect_exit_container(); |
| } |
| expect_at_end(false, 1); |
| expect_exit_container(); |
| } |
| |
| std::unordered_map<int, std::string> ret_mis; |
| new_message().read(ret_mis); |
| EXPECT_EQ(mis, ret_mis); |
| } |
| |
| TEST_F(ReadTest, Tuple) |
| { |
| const std::tuple<int, std::string, bool> tisb{3, "hi", false}; |
| |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_STRUCT, "isb"); |
| expect_basic<int>(SD_BUS_TYPE_INT32, std::get<0>(tisb)); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, |
| std::get<1>(tisb).c_str()); |
| expect_basic<int>(SD_BUS_TYPE_BOOLEAN, std::get<2>(tisb)); |
| expect_exit_container(); |
| } |
| |
| std::tuple<int, std::string, bool> ret_tisb; |
| new_message().read(ret_tisb); |
| EXPECT_EQ(tisb, ret_tisb); |
| } |
| |
| TEST_F(ReadTest, TupleEnterError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_STRUCT, "bis", -EINVAL); |
| } |
| |
| std::tuple<bool, int, std::string> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, TupleExitError) |
| { |
| { |
| testing::InSequence seq; |
| expect_enter_container(SD_BUS_TYPE_STRUCT, "bis"); |
| expect_basic<int>(SD_BUS_TYPE_BOOLEAN, false); |
| expect_basic<int>(SD_BUS_TYPE_INT32, 1); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, "ab"); |
| expect_exit_container(-EINVAL); |
| } |
| |
| std::tuple<bool, int, std::string> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, Variant) |
| { |
| const bool b1 = false; |
| const std::string s2{"asdf"}; |
| const std::variant<int, std::string, bool> v1{b1}, v2{s2}; |
| |
| { |
| testing::InSequence seq; |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", false); |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "s", false); |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "b", true); |
| expect_enter_container(SD_BUS_TYPE_VARIANT, "b"); |
| expect_basic<int>(SD_BUS_TYPE_BOOLEAN, b1); |
| expect_exit_container(); |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", false); |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "s", true); |
| expect_enter_container(SD_BUS_TYPE_VARIANT, "s"); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, s2.c_str()); |
| expect_exit_container(); |
| } |
| |
| std::variant<int, std::string, bool> ret_v1, ret_v2; |
| new_message().read(ret_v1, ret_v2); |
| EXPECT_EQ(v1, ret_v1); |
| EXPECT_EQ(v2, ret_v2); |
| } |
| |
| TEST_F(ReadTest, VariantVerifyError) |
| { |
| { |
| testing::InSequence seq; |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", -EINVAL); |
| } |
| |
| std::variant<int, bool> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, VariantSkipUnmatched) |
| { |
| { |
| testing::InSequence seq; |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", false); |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "b", false); |
| expect_skip("v"); |
| } |
| |
| std::variant<int, bool> ret; |
| new_message().read(ret); |
| } |
| |
| TEST_F(ReadTest, VariantSkipError) |
| { |
| { |
| testing::InSequence seq; |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", false); |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "b", false); |
| expect_skip("v", -EINVAL); |
| } |
| |
| std::variant<int, bool> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, VariantEnterError) |
| { |
| { |
| testing::InSequence seq; |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", true); |
| expect_enter_container(SD_BUS_TYPE_VARIANT, "i", -EINVAL); |
| } |
| |
| std::variant<int, bool> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, VariantExitError) |
| { |
| { |
| testing::InSequence seq; |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", true); |
| expect_enter_container(SD_BUS_TYPE_VARIANT, "i"); |
| expect_basic<int>(SD_BUS_TYPE_INT32, 10); |
| expect_exit_container(-EINVAL); |
| } |
| |
| std::variant<int, bool> ret; |
| EXPECT_THROW(new_message().read(ret), sdbusplus::exception::SdBusError); |
| } |
| |
| TEST_F(ReadTest, LargeCombo) |
| { |
| const std::vector<std::set<std::string>> vas{ |
| {"a", "b", "c"}, |
| {"d", "", "e"}, |
| }; |
| const std::map<std::string, std::variant<int, double>> msv = { |
| {"a", 3.3}, {"b", 1}, {"c", 4.4}}; |
| |
| { |
| testing::InSequence seq; |
| |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "as"); |
| for (const auto& as : vas) |
| { |
| expect_at_end(false, 0); |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "s"); |
| for (const auto& s : as) |
| { |
| expect_at_end(false, 0); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, s.c_str()); |
| } |
| expect_at_end(false, 1); |
| expect_exit_container(); |
| } |
| expect_at_end(false, 1); |
| expect_exit_container(); |
| |
| expect_enter_container(SD_BUS_TYPE_ARRAY, "{sv}"); |
| for (const auto& sv : msv) |
| { |
| expect_at_end(false, 0); |
| expect_enter_container(SD_BUS_TYPE_DICT_ENTRY, "sv"); |
| expect_basic<const char*>(SD_BUS_TYPE_STRING, sv.first.c_str()); |
| if (std::holds_alternative<int>(sv.second)) |
| { |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", true); |
| expect_enter_container(SD_BUS_TYPE_VARIANT, "i"); |
| expect_basic<int>(SD_BUS_TYPE_INT32, std::get<int>(sv.second)); |
| expect_exit_container(); |
| } |
| else |
| { |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "i", false); |
| expect_verify_type(SD_BUS_TYPE_VARIANT, "d", true); |
| expect_enter_container(SD_BUS_TYPE_VARIANT, "d"); |
| expect_basic<double>(SD_BUS_TYPE_DOUBLE, |
| std::get<double>(sv.second)); |
| expect_exit_container(); |
| } |
| expect_exit_container(); |
| } |
| expect_at_end(false, 1); |
| expect_exit_container(); |
| } |
| |
| std::vector<std::set<std::string>> ret_vas; |
| std::map<std::string, std::variant<int, double>> ret_msv; |
| new_message().read(ret_vas, ret_msv); |
| EXPECT_EQ(vas, ret_vas); |
| EXPECT_EQ(msv, ret_msv); |
| } |
| |
| } // namespace |