| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "hoth_command_unittest.hpp" |
| |
| #include <sdbusplus/exception.hpp> |
| #include <sdbusplus/test/sdbus_mock.hpp> |
| |
| #include <cstdint> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| using ::testing::_; |
| using ::testing::ContainerEq; |
| using ::testing::IsEmpty; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| |
| using namespace std::literals; |
| |
| namespace ipmi_hoth |
| { |
| |
| const auto static test_str = "Hello, world!"s; |
| const std::vector<uint8_t> static test_buf(test_str.begin(), test_str.end()); |
| const auto static test2_str = "Good morning, world!"s; |
| const std::vector<uint8_t> static test2_buf(test2_str.begin(), test2_str.end()); |
| |
| sdbusplus::SdBusMock sdbusIntf; |
| |
| ACTION(SdbusThrow) |
| { |
| EXPECT_CALL(sdbusIntf, sd_bus_error_set_errno(NotNull(), _)) |
| .WillOnce(Return(0)); |
| EXPECT_CALL(sdbusIntf, sd_bus_error_is_set(NotNull())).WillOnce(Return(1)); |
| EXPECT_CALL(sdbusIntf, sd_bus_error_free(NotNull())).Times(1); |
| |
| throw sdbusplus::exception::SdBusError(0, "", &sdbusIntf); |
| } |
| |
| class HothCommandCommitTest : public HothCommandTest |
| { |
| protected: |
| void expectValidCommand(std::string_view name, |
| const std::vector<uint8_t>& input) |
| { |
| EXPECT_CALL(dbus, SendHostCommand(name, ContainerEq(input), _)) |
| .WillOnce([&](auto, const auto&, auto cb) { |
| this->cb = std::move(cb); |
| return stdplus::Cancel(std::nullopt); |
| }); |
| } |
| internal::DbusCommand::Cb cb; |
| }; |
| |
| TEST_F(HothCommandCommitTest, InvalidSessionCommitIsRejected) |
| { |
| // Verify the hoth command handler checks for a valid session. |
| |
| EXPECT_FALSE(hvn.commit(session_, std::vector<uint8_t>())); |
| } |
| |
| TEST_F(HothCommandCommitTest, UnexpectedDataParam) |
| { |
| EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true)); |
| EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath)); |
| |
| EXPECT_FALSE(hvn.commit(session_, std::vector<uint8_t>({1, 2, 3}))); |
| } |
| |
| TEST_F(HothCommandCommitTest, DbusCallFail) |
| { |
| EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true)); |
| expectValidCommand("", test_buf); |
| |
| EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath)); |
| // session, offset, data |
| EXPECT_TRUE(hvn.write(session_, 0, test_buf)); |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| |
| blobs::BlobMeta meta; |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::committing | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| |
| cb(std::nullopt); |
| |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::commit_error | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| } |
| |
| TEST_F(HothCommandCommitTest, EmptyLegacyHoth) |
| { |
| EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true)); |
| expectValidCommand("", {}); |
| |
| EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath)); |
| // session, offset, data |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| |
| blobs::BlobMeta meta; |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::committing | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| |
| cb(std::vector<uint8_t>{}); |
| |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(0, meta.size); |
| EXPECT_EQ(blobs::StateFlags::committed | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| } |
| |
| TEST_F(HothCommandCommitTest, EmptyNamedHoth) |
| { |
| EXPECT_CALL(dbus, pingHothd(std::string_view(name))).WillOnce(Return(true)); |
| expectValidCommand(name, {}); |
| |
| EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), namedPath)); |
| // session, offset, data |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| |
| blobs::BlobMeta meta; |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::committing | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| |
| cb(std::vector<uint8_t>{}); |
| |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(0, meta.size); |
| EXPECT_EQ(blobs::StateFlags::committed | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| } |
| |
| // Tests the full commit process with example data |
| TEST_F(HothCommandCommitTest, HappyPath) |
| { |
| EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true)); |
| expectValidCommand("", test_buf); |
| |
| EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath)); |
| // session, offset, data |
| EXPECT_TRUE(hvn.write(session_, 0, test_buf)); |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| |
| cb(test2_buf); |
| |
| std::vector<uint8_t> result = hvn.read(session_, 0, test2_buf.size()); |
| EXPECT_THAT(result, ContainerEq(test2_buf)); |
| |
| blobs::BlobMeta meta; |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| |
| EXPECT_EQ(blobs::StateFlags::committed | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| } |
| |
| // Tests that repeated commits only result in one D-Bus call |
| TEST_F(HothCommandCommitTest, IdempotentSuccess) |
| { |
| EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true)); |
| expectValidCommand("", {}); |
| |
| EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath)); |
| // session, offset, data |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| |
| blobs::BlobMeta meta; |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::committing | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| |
| cb(std::vector<uint8_t>{}); |
| |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::committed | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| } |
| |
| // Tests that repeated commits will retry DBus calls if there is an error |
| TEST_F(HothCommandCommitTest, ErrorRetry) |
| { |
| EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true)); |
| expectValidCommand("", {}); |
| |
| EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath)); |
| // session, offset, data |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| |
| blobs::BlobMeta meta; |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::committing | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| |
| cb(std::nullopt); |
| |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::commit_error | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| |
| expectValidCommand("", {}); |
| EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>())); |
| |
| EXPECT_TRUE(hvn.stat(session_, &meta)); |
| EXPECT_EQ(blobs::StateFlags::committing | blobs::StateFlags::open_read | |
| blobs::StateFlags::open_write, |
| meta.blobState); |
| } |
| |
| } // namespace ipmi_hoth |