blob: 1421c7458df080448f3e315ce394c96ae6158bbd [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_update_unittest.hpp"
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
#include <gmock/gmock.h>
using ::testing::_;
using ::testing::ContainerEq;
using ::testing::IsEmpty;
using ::testing::NotNull;
using ::testing::Return;
using namespace std::literals;
namespace ipmi_hoth
{
using Cb = internal::DbusUpdate::Cb;
using FirmwareUpdateStatus = internal::DbusUpdate::FirmwareUpdateStatus;
class HothUpdateCommitTest : public HothUpdateTest
{
protected:
template <typename Req>
void expectUpdate(std::string_view hothId, Req&& req)
{
EXPECT_CALL(dbus, UpdateFirmware(hothId, req, _))
.WillOnce(
[&](std::string_view, const std::vector<uint8_t>&, Cb&& icb) {
cb = std::move(icb);
return stdplus::Cancel(std::nullopt);
});
}
Cb cb;
};
const auto static test_str = "Hello,\0 world!"s;
const std::vector<uint8_t> static test_buf(test_str.begin(), test_str.end());
TEST_F(HothUpdateCommitTest, InvalidSessionCommitIsRejected)
{
// Verify the hoth update handler checks for a valid session.
EXPECT_FALSE(hvn.commit(session_, std::vector<uint8_t>()));
}
TEST_F(HothUpdateCommitTest, NonEmptyDataParamFails)
{
// Verify that we do not accept any data passed into the commit call.
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(HothUpdateCommitTest, DbusExceptionSetsCommitErrorStateAndFails)
{
// Verify that when the dbus call hits an exception, commit method fails
// and that the state is set to blobs::StateFlags::commit_error
EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true));
expectUpdate(/*hothId=*/"", ContainerEq(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(FirmwareUpdateStatus::Error);
blobs::BlobMeta meta;
EXPECT_TRUE(hvn.stat(session_, &meta));
EXPECT_EQ(meta.blobState,
blobs::StateFlags::open_write | blobs::StateFlags::commit_error);
}
TEST_F(HothUpdateCommitTest, EmptyBufferCommitLegacyHothSuccessful)
{
// Verify that we are able to succssfully commit an empty buffer
EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true));
expectUpdate(/*hothId=*/"", IsEmpty());
EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath));
// No write call to populate the buffer here
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
cb(FirmwareUpdateStatus::Done);
blobs::BlobMeta meta;
EXPECT_TRUE(hvn.stat(session_, &meta));
EXPECT_EQ(meta.size, 0);
EXPECT_EQ(meta.blobState,
blobs::StateFlags::open_write | blobs::StateFlags::committed);
}
TEST_F(HothUpdateCommitTest, EmptyBufferCommitNamedHothSuccessful)
{
// Verify that we are able to succssfully commit an empty buffer
EXPECT_CALL(dbus, pingHothd(std::string_view(name))).WillOnce(Return(true));
expectUpdate(/*hothId=*/name, IsEmpty());
EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), namedPath));
// No write call to populate the buffer here
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
cb(FirmwareUpdateStatus::Done);
blobs::BlobMeta meta;
EXPECT_TRUE(hvn.stat(session_, &meta));
EXPECT_EQ(meta.size, 0);
EXPECT_EQ(meta.blobState,
blobs::StateFlags::open_write | blobs::StateFlags::committed);
}
TEST_F(HothUpdateCommitTest, NonEmptyBufferCommitSuccessful)
{
// Verify that we are able to succssfully commit a buffer with valid data
EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true));
expectUpdate(/*hothId=*/"", ContainerEq(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(FirmwareUpdateStatus::Done);
blobs::BlobMeta meta;
EXPECT_TRUE(hvn.stat(session_, &meta));
EXPECT_EQ(meta.size, test_buf.size());
EXPECT_EQ(meta.blobState,
blobs::StateFlags::open_write | blobs::StateFlags::committed);
}
TEST_F(HothUpdateCommitTest, IdempotentSuccess)
{
// Verify that repeated successful commits only result in one D-Bus call
EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true));
expectUpdate(/*hothId=*/"", IsEmpty());
EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath));
// No write call to populate the buffer here
// These calls will return due to the state being "committing". Calling
// stat is the only way to check if the state has changed to "committed"
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
cb(FirmwareUpdateStatus::Done);
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
blobs::BlobMeta meta;
EXPECT_TRUE(hvn.stat(session_, &meta));
// These calls will return due to the state being "committed"
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
// Stat is also idempotent, no need to mock the D-Bus call here
EXPECT_TRUE(hvn.stat(session_, &meta));
EXPECT_EQ(meta.size, 0);
EXPECT_EQ(meta.blobState,
blobs::StateFlags::open_write | blobs::StateFlags::committed);
}
TEST_F(HothUpdateCommitTest, ErrorRetry)
{
// Verify that commit after error retries the update D-Bus call
EXPECT_CALL(dbus, pingHothd(std::string_view(""))).WillOnce(Return(true));
EXPECT_TRUE(hvn.open(session_, hvn.requiredFlags(), legacyPath));
// These calls will return due to the state being "commit_error"
expectUpdate(/*hothId=*/"", IsEmpty());
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
cb(FirmwareUpdateStatus::Error);
expectUpdate(/*hothId=*/"", IsEmpty());
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
cb(FirmwareUpdateStatus::Error);
expectUpdate(/*hothId=*/"", IsEmpty());
EXPECT_TRUE(hvn.commit(session_, std::vector<uint8_t>()));
cb(FirmwareUpdateStatus::Error);
blobs::BlobMeta meta;
EXPECT_TRUE(hvn.stat(session_, &meta));
EXPECT_EQ(meta.blobState,
blobs::StateFlags::open_write | blobs::StateFlags::commit_error);
}
} // namespace ipmi_hoth