blob: 495aa59f9dd1bf14b186fbe99bbbdd3343e33fe1 [file] [log] [blame]
// 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