Add support for log-handler
This handler is based off of version handler with major differences in
handler_builder
Tested:
created the blackbox blob successfully using this lib.
Read the data from the blob using a host side tool
Signed-off-by: Gaurav Gandhi <gauravgandhi@google.com>
Change-Id: I9ef775af752156a1647453ff3831ef4c0449d546
diff --git a/bmc/log-handler/test/log_canhandle_enumerate_unittest.cpp b/bmc/log-handler/test/log_canhandle_enumerate_unittest.cpp
new file mode 100644
index 0000000..f27737d
--- /dev/null
+++ b/bmc/log-handler/test/log_canhandle_enumerate_unittest.cpp
@@ -0,0 +1,43 @@
+// Copyright 2021 Google Inc.
+//
+// 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 "log_handler.hpp"
+#include "log_mock.hpp"
+
+#include <array>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+
+TEST(LogHandlerCanHandleTest, VerifyGoodInfoMap)
+{
+ constexpr std::array blobNames{"blob0", "blob1", "blob2", "blob3"};
+ LogBlobHandler handler(createMockLogConfigs(blobNames));
+ for (const auto& blobName : blobNames)
+ {
+ EXPECT_TRUE(handler.canHandleBlob(blobName));
+ }
+}
+
+TEST(LogHandlerEnumerateTest, VerifyGoodInfoMap)
+{
+ constexpr std::array blobNames{"blob0", "blob1", "blob2", "blob3"};
+ LogBlobHandler handler(createMockLogConfigs(blobNames));
+ EXPECT_THAT(handler.getBlobIds(),
+ ::testing::UnorderedElementsAreArray(blobNames));
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/log-handler/test/log_close_unittest.cpp b/bmc/log-handler/test/log_close_unittest.cpp
new file mode 100644
index 0000000..8755541
--- /dev/null
+++ b/bmc/log-handler/test/log_close_unittest.cpp
@@ -0,0 +1,94 @@
+// Copyright 2021 Google Inc.
+//
+// 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 "log_handler.hpp"
+#include "log_mock.hpp"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using ::testing::Return;
+
+namespace ipmi_flash
+{
+
+class LogCloseExpireBlobTest : public ::testing::Test
+{
+ protected:
+ void SetUp() override
+ {
+ h = std::make_unique<LogBlobHandler>(
+ createMockLogConfigs(blobNames, &im, &tm));
+ }
+
+ std::unique_ptr<blobs::GenericBlobInterface> h;
+ std::vector<std::string> blobNames{"blob0", "blob1", "blob2", "blob3"};
+ std::unordered_map<std::string, TriggerMock*> tm;
+ std::unordered_map<std::string, ImageHandlerMock*> im;
+};
+
+TEST_F(LogCloseExpireBlobTest, VerifyOpenThenClose)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob0"));
+ EXPECT_CALL(*tm.at("blob0"), abort()).Times(1);
+ EXPECT_TRUE(h->close(0));
+}
+
+TEST_F(LogCloseExpireBlobTest, VerifySingleAbort)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob0"));
+ EXPECT_TRUE(h->open(1, blobs::read, "blob0"));
+ EXPECT_TRUE(h->close(0));
+ EXPECT_CALL(*tm.at("blob0"), abort()).Times(1);
+ EXPECT_TRUE(h->close(1));
+}
+
+TEST_F(LogCloseExpireBlobTest, VerifyUnopenedBlobCloseFails)
+{
+ EXPECT_FALSE(h->close(0));
+}
+
+TEST_F(LogCloseExpireBlobTest, VerifyDoubleCloseFails)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob0"));
+ EXPECT_CALL(*tm.at("blob0"), abort()).Times(1);
+ EXPECT_TRUE(h->close(0));
+ EXPECT_FALSE(h->close(0));
+}
+
+TEST_F(LogCloseExpireBlobTest, VerifyBadSessionNumberCloseFails)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob0"));
+ EXPECT_FALSE(h->close(1));
+ EXPECT_CALL(*tm.at("blob0"), abort()).Times(1);
+ EXPECT_TRUE(h->close(0));
+}
+
+TEST_F(LogCloseExpireBlobTest, VerifyRunningActionIsAborted)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob0"));
+ EXPECT_CALL(*tm.at("blob0"), abort()).Times(1);
+ EXPECT_TRUE(h->close(0));
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/log-handler/test/log_createhandler_unittest.cpp b/bmc/log-handler/test/log_createhandler_unittest.cpp
new file mode 100644
index 0000000..99d5d3e
--- /dev/null
+++ b/bmc/log-handler/test/log_createhandler_unittest.cpp
@@ -0,0 +1,44 @@
+// Copyright 2021 Google Inc.
+//
+// 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 "log_handler.hpp"
+#include "log_mock.hpp"
+
+#include <array>
+#include <utility>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+
+TEST(LogHandlerCanHandleTest, VerifyGoodInfoMapPasses)
+{
+ constexpr std::array blobs{"blob0", "blob1"};
+ LogBlobHandler handler(createMockLogConfigs(blobs));
+ EXPECT_THAT(handler.getBlobIds(),
+ testing::UnorderedElementsAreArray(blobs));
+}
+
+TEST(LogHandlerCanHandleTest, VerifyDuplicatesIgnored)
+{
+ constexpr std::array blobs{"blob0"};
+ auto configs = createMockLogConfigs(blobs);
+ configs.push_back(createMockLogConfig(blobs[0]));
+ LogBlobHandler handler(std::move(configs));
+ EXPECT_THAT(handler.getBlobIds(),
+ testing::UnorderedElementsAreArray(blobs));
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/log-handler/test/log_json_unittest.cpp b/bmc/log-handler/test/log_json_unittest.cpp
new file mode 100644
index 0000000..5759c25
--- /dev/null
+++ b/bmc/log-handler/test/log_json_unittest.cpp
@@ -0,0 +1,314 @@
+// Copyright 2021 Google Inc.
+//
+// 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 "general_systemd.hpp"
+#include "log_handlers_builder.hpp"
+#include "skip_action.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+using ::testing::IsEmpty;
+
+using json = nlohmann::json;
+
+TEST(LogJsonTest, ValidConfigurationNoLogHandler)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/sink_seq",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions":{
+ "open" :{
+ "type" : "systemd",
+ "unit" : "absolute"
+ }
+ }
+ }
+ }]
+ )"_json;
+ auto h = LogHandlersBuilder().buildHandlerFromJson(j2);
+ ASSERT_THAT(h, ::testing::SizeIs(1));
+ EXPECT_THAT(h[0].blobId, "/log/sink_seq");
+ EXPECT_FALSE(h[0].actions == nullptr);
+ EXPECT_FALSE(h[0].handler == nullptr);
+}
+
+TEST(LogJsonTest, ValidConfigurationLogBlobName)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/log/sink_seq",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions": {
+ "open" : {
+ "type" : "systemd",
+ "unit" : "phosphor-ipmi-flash-log-sink-sequencer.target"
+ }
+ }
+ }
+ }]
+ )"_json;
+ auto h = LogHandlersBuilder().buildHandlerFromJson(j2);
+ ASSERT_THAT(h, ::testing::SizeIs(1));
+ EXPECT_THAT(h[0].blobId, "/log/sink_seq");
+ EXPECT_FALSE(h[0].actions == nullptr);
+ EXPECT_FALSE(h[0].handler == nullptr);
+}
+
+TEST(LogJsonTest, MissingHandlerType)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "log":{
+ "handler": {
+ "path" : "/tmp/log_info"
+ },
+ "actions": {
+ "open" : {
+ "type" : "systemd",
+ "unit" : "absolute"}
+ }
+ }
+ }]
+ )"_json;
+ EXPECT_THAT(LogHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(LogJsonTest, BadBlobName)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/bad/image",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions": {
+ "open" : {
+ "type" : "systemd",
+ "unit" : "absolute"}
+ }
+ }
+ }]
+ )"_json;
+ EXPECT_THAT(LogHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(LogJsonTest, MissingActions)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ }
+ }
+ }]
+ )"_json;
+ EXPECT_THAT(LogHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(LogJsonTest, MissingOpenAction)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/image",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions": {}
+ }
+ }]
+ )"_json;
+ EXPECT_THAT(LogHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(LogJsonTest, OneInvalidTwoValidSucceeds)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/sink_seq0",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions":{
+ "open" :{
+ "type" : "systemd",
+ "unit" : "absolute"
+ }
+ }
+ }
+ },
+ {
+ "blob" : "/log/sink_seq1",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions":{
+ "open" :{
+ "type" : "systemd",
+ "unit" : "absolute"
+ }
+ }
+ }
+ },
+ {
+ "blob" : "/bad/sink_seq",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions":{
+ "open" :{
+ "type" : "systemd",
+ "unit" : "absolute"
+ }
+ }
+ }
+ }
+ ]
+ )"_json;
+ auto h = LogHandlersBuilder().buildHandlerFromJson(j2);
+ ASSERT_THAT(h, ::testing::SizeIs(2));
+ EXPECT_THAT(h[0].blobId, "/log/sink_seq0");
+ EXPECT_THAT(h[1].blobId, "/log/sink_seq1");
+}
+
+TEST(LogJsonTest, BlobNameIsTooShort)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions":{
+ "open" :{
+ "type" : "systemd",
+ "unit" : "absolute"
+ }
+ }
+ }
+ }]
+ )"_json;
+ EXPECT_THAT(LogHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(LogJsonTest, OpenSkipAction)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/sink_seqs",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions":{
+ "open" :{
+ "type" : "skip"
+ }
+ }
+ }
+ }]
+ )"_json;
+ auto h = LogHandlersBuilder().buildHandlerFromJson(j2);
+ EXPECT_THAT(h, ::testing::SizeIs(1));
+ EXPECT_TRUE(h[0].blobId == "/log/sink_seqs");
+ ASSERT_FALSE(h[0].actions == nullptr);
+ EXPECT_FALSE(h[0].actions->onOpen == nullptr);
+}
+
+TEST(LogJsonTest, OpenActionsWithDifferentModes)
+{
+ auto j2 = R"(
+ [{
+ "blob" : "/flash/blob1",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions":{
+ "open" :{
+ "type" : "systemd",
+ "unit" : "absolute",
+ "mode" : "replace-nope"
+ }
+ }
+ }
+ },
+ {
+ "blob" : "/flash/blob2",
+ "log":{
+ "handler": {
+ "type" : "file",
+ "path" : "/tmp/log_info"
+ },
+ "actions":{
+ "open" :{
+ "type" : "systemd",
+ "unit" : "absolute",
+ "mode" : "replace-fake"
+ }
+ }
+ }
+ }
+ ]
+ )"_json;
+ auto h = LogHandlersBuilder().buildHandlerFromJson(j2);
+ ASSERT_THAT(h, ::testing::SizeIs(2));
+
+ EXPECT_FALSE(h[0].handler == nullptr);
+ EXPECT_FALSE(h[0].actions == nullptr);
+ EXPECT_THAT(h[0].blobId, "/log/blob1");
+ auto onOpen0 = reinterpret_cast<SystemdNoFile*>(h[0].actions->onOpen.get());
+ EXPECT_THAT(onOpen0->getMode(), "replace-nope");
+
+ EXPECT_FALSE(h[1].handler == nullptr);
+ EXPECT_FALSE(h[1].actions == nullptr);
+ EXPECT_THAT(h[1].blobId, "/log/blob2");
+ auto onOpen1 = reinterpret_cast<SystemdNoFile*>(h[1].actions->onOpen.get());
+ EXPECT_THAT(onOpen1->getMode(), "replace-fake");
+}
+} // namespace
+} // namespace ipmi_flash
diff --git a/bmc/log-handler/test/log_mock.hpp b/bmc/log-handler/test/log_mock.hpp
new file mode 100644
index 0000000..e9628eb
--- /dev/null
+++ b/bmc/log-handler/test/log_mock.hpp
@@ -0,0 +1,61 @@
+// Copyright 2021 Google Inc.
+//
+// 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 "handler_config.hpp"
+#include "image_mock.hpp"
+#include "log_handler.hpp"
+#include "triggerable_mock.hpp"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace ipmi_flash
+{
+
+auto createMockLogConfig(const std::string& id, ImageHandlerMock** im = nullptr,
+ TriggerMock** tm = nullptr)
+{
+ HandlerConfig<LogBlobHandler::ActionPack> ret;
+ ret.blobId = id;
+ auto handler = std::make_unique<testing::StrictMock<ImageHandlerMock>>();
+ if (im != nullptr)
+ {
+ *im = handler.get();
+ }
+ ret.handler = std::move(handler);
+ ret.actions = std::make_unique<LogBlobHandler::ActionPack>();
+ auto trigger = std::make_unique<testing::StrictMock<TriggerMock>>();
+ if (tm != nullptr)
+ {
+ *tm = trigger.get();
+ }
+ ret.actions->onOpen = std::move(trigger);
+ return ret;
+}
+
+template <typename C, typename Im = std::map<std::string, ImageHandlerMock*>,
+ typename Tm = std::map<std::string, TriggerMock*>>
+auto createMockLogConfigs(const C& ids, Im* im = nullptr, Tm* tm = nullptr)
+{
+ std::vector<HandlerConfig<LogBlobHandler::ActionPack>> ret;
+ for (const auto& id : ids)
+ {
+ ret.push_back(
+ createMockLogConfig(id, im == nullptr ? nullptr : &(*im)[id],
+ tm == nullptr ? nullptr : &(*tm)[id]));
+ }
+ return ret;
+}
+} // namespace ipmi_flash
diff --git a/bmc/log-handler/test/log_open_unittest.cpp b/bmc/log-handler/test/log_open_unittest.cpp
new file mode 100644
index 0000000..301da86
--- /dev/null
+++ b/bmc/log-handler/test/log_open_unittest.cpp
@@ -0,0 +1,101 @@
+// Copyright 2021 Google Inc.
+//
+// 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 "log_handler.hpp"
+#include "log_mock.hpp"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using ::testing::Return;
+
+namespace ipmi_flash
+{
+
+class LogOpenBlobTest : public ::testing::Test
+{
+ protected:
+ void SetUp() override
+ {
+ h = std::make_unique<LogBlobHandler>(
+ createMockLogConfigs(blobNames, &im, &tm));
+ }
+
+ std::unique_ptr<blobs::GenericBlobInterface> h;
+ std::vector<std::string> blobNames{"blob0", "blob1", "blob2", "blob3"};
+ std::unordered_map<std::string, TriggerMock*> tm;
+ std::unordered_map<std::string, ImageHandlerMock*> im;
+ const std::uint16_t defaultSessionNumber{0};
+};
+
+TEST_F(LogOpenBlobTest, VerifySingleBlobOpen)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger()).Times(1).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(defaultSessionNumber, blobs::read, "blob0"));
+}
+
+TEST_F(LogOpenBlobTest, VerifyMultipleBlobOpens)
+{
+ for (const auto& [_, val] : tm)
+ {
+ /* set the expectation that every onOpen will be triggered */
+ EXPECT_CALL(*val, trigger()).WillOnce(Return(true));
+ }
+ int i{defaultSessionNumber};
+ for (const auto& blob : blobNames)
+ {
+ EXPECT_TRUE(h->open(i++, blobs::read, blob));
+ }
+}
+
+TEST_F(LogOpenBlobTest, VerifyOpenAfterClose)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(defaultSessionNumber, blobs::read, "blob0"));
+
+ EXPECT_CALL(*tm.at("blob0"), abort()).Times(1);
+ EXPECT_TRUE(h->close(defaultSessionNumber));
+
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(defaultSessionNumber, blobs::read, "blob0"));
+}
+
+TEST_F(LogOpenBlobTest, VerifyMultiOpenWorks)
+{
+ EXPECT_CALL(*tm.at("blob1"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob1"));
+ EXPECT_TRUE(h->open(1, blobs::read, "blob1"));
+ EXPECT_TRUE(h->open(2, blobs::read, "blob1"));
+}
+
+TEST_F(LogOpenBlobTest, VerifyFailedTriggerFails)
+{
+ EXPECT_CALL(*tm.at("blob1"), trigger()).WillOnce(Return(false));
+ EXPECT_FALSE(h->open(0, blobs::read, "blob1"));
+ EXPECT_CALL(*tm.at("blob1"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob1"));
+}
+
+TEST_F(LogOpenBlobTest, VerifyUnsupportedOpenFlagsFails)
+{
+ EXPECT_FALSE(h->open(0, blobs::write, "blob1"));
+ EXPECT_CALL(*tm.at("blob1"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob1"));
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/log-handler/test/log_read_unittest.cpp b/bmc/log-handler/test/log_read_unittest.cpp
new file mode 100644
index 0000000..ab952e6
--- /dev/null
+++ b/bmc/log-handler/test/log_read_unittest.cpp
@@ -0,0 +1,153 @@
+// Copyright 2021 Google Inc.
+//
+// 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 "log_handler.hpp"
+#include "log_mock.hpp"
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::ElementsAreArray;
+using ::testing::Ge;
+using ::testing::IsEmpty;
+using ::testing::Return;
+
+namespace ipmi_flash
+{
+
+class LogReadBlobTest : public ::testing::Test
+{
+ protected:
+ void SetUp() override
+ {
+ h = std::make_unique<LogBlobHandler>(
+ createMockLogConfigs(blobNames, &im, &tm));
+ }
+ std::unique_ptr<blobs::GenericBlobInterface> h;
+ std::vector<std::string> blobNames{"blob0", "blob1", "blob2", "blob3"};
+ std::unordered_map<std::string, TriggerMock*> tm;
+ std::unordered_map<std::string, ImageHandlerMock*> im;
+ const std::uint16_t defaultSessionNumber{200};
+ std::vector<uint8_t> vector1{0xDE, 0xAD, 0xBE, 0xEF,
+ 0xBA, 0xDF, 0xEE, 0x0D};
+ std::vector<uint8_t> vector2{0xCE, 0xAD, 0xDE, 0xFF};
+};
+
+TEST_F(LogReadBlobTest, VerifyValidRead)
+{
+ testing::InSequence seq;
+ EXPECT_CALL(*tm.at("blob0"), trigger())
+ .WillOnce(DoAll([&]() { tm.at("blob0")->cb(*tm.at("blob0")); },
+ Return(true)));
+ EXPECT_CALL(*tm.at("blob0"), status())
+ .WillOnce(Return(ActionStatus::success));
+ EXPECT_CALL(*im.at("blob0"), open(_, std::ios::in)).WillOnce(Return(true));
+ EXPECT_CALL(*im.at("blob0"), read(0, Ge(vector1.size())))
+ .WillOnce(Return(vector1));
+ EXPECT_CALL(*im.at("blob0"), close()).Times(1);
+ EXPECT_TRUE(h->open(defaultSessionNumber, blobs::read, "blob0"));
+
+ std::basic_string_view<uint8_t> vectorS(vector1.data(), vector1.size());
+ EXPECT_THAT(h->read(defaultSessionNumber, 0, 7),
+ ElementsAreArray(vectorS.substr(0, 7)));
+ EXPECT_THAT(h->read(defaultSessionNumber, 2, 10),
+ ElementsAreArray(vectorS.substr(2, 6)));
+ EXPECT_THAT(h->read(defaultSessionNumber, 10, 0), IsEmpty());
+}
+
+TEST_F(LogReadBlobTest, VerifyMultipleSession)
+{
+ testing::InSequence seq;
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob0"));
+ EXPECT_TRUE(h->open(1, blobs::read, "blob0"));
+
+ EXPECT_CALL(*tm.at("blob0"), status())
+ .WillOnce(Return(ActionStatus::success));
+ EXPECT_CALL(*im.at("blob0"), open(_, std::ios::in)).WillOnce(Return(true));
+ EXPECT_CALL(*im.at("blob0"), read(0, Ge(vector1.size())))
+ .WillOnce(Return(vector1));
+ EXPECT_CALL(*im.at("blob0"), close()).Times(1);
+ tm.at("blob0")->cb(*tm.at("blob0"));
+
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(2, blobs::read, "blob0"));
+
+ EXPECT_CALL(*tm.at("blob0"), status())
+ .WillOnce(Return(ActionStatus::success));
+ EXPECT_CALL(*im.at("blob0"), open(_, std::ios::in)).WillOnce(Return(true));
+ EXPECT_CALL(*im.at("blob0"), read(0, Ge(vector2.size())))
+ .WillOnce(Return(vector2));
+ EXPECT_CALL(*im.at("blob0"), close()).Times(1);
+ tm.at("blob0")->cb(*tm.at("blob0"));
+
+ EXPECT_THAT(h->read(0, 0, 10), ElementsAreArray(vector1));
+ EXPECT_THAT(h->read(1, 0, 10), ElementsAreArray(vector1));
+ EXPECT_THAT(h->read(2, 0, 10), ElementsAreArray(vector2));
+}
+
+TEST_F(LogReadBlobTest, VerifyReadEarlyFails)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+
+ EXPECT_TRUE(h->open(defaultSessionNumber, blobs::read, "blob0"));
+ EXPECT_THROW(h->read(defaultSessionNumber, 0, 10), std::runtime_error);
+}
+
+TEST_F(LogReadBlobTest, VerifyTriggerFailureReadFails)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger())
+ .WillOnce(DoAll([&]() { tm.at("blob0")->cb(*tm.at("blob0")); },
+ Return(true)));
+ EXPECT_CALL(*tm.at("blob0"), status())
+ .WillOnce(Return(ActionStatus::failed));
+ EXPECT_TRUE(h->open(defaultSessionNumber, blobs::read, "blob0"));
+ EXPECT_THROW(h->read(defaultSessionNumber, 0, 10), std::runtime_error);
+}
+
+TEST_F(LogReadBlobTest, VerifyReadFailsOnFileOpenFailure)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger())
+ .WillOnce(DoAll([&]() { tm.at("blob0")->cb(*tm.at("blob0")); },
+ Return(true)));
+ EXPECT_CALL(*tm.at("blob0"), status())
+ .WillOnce(Return(ActionStatus::success));
+ EXPECT_CALL(*im.at("blob0"), open(_, std::ios::in)).WillOnce(Return(false));
+
+ EXPECT_TRUE(h->open(defaultSessionNumber, blobs::read, "blob0"));
+ EXPECT_THROW(h->read(defaultSessionNumber, 0, 10), std::runtime_error);
+}
+
+TEST_F(LogReadBlobTest, VerifyReadFailsOnFileReadFailure)
+{
+ EXPECT_CALL(*tm.at("blob0"), trigger())
+ .WillOnce(DoAll([&]() { tm.at("blob0")->cb(*tm.at("blob0")); },
+ Return(true)));
+ EXPECT_CALL(*tm.at("blob0"), status())
+ .WillOnce(Return(ActionStatus::success));
+ EXPECT_CALL(*im.at("blob0"), open(_, std::ios::in)).WillOnce(Return(true));
+ EXPECT_CALL(*im.at("blob0"), read(_, _)).WillOnce(Return(std::nullopt));
+ EXPECT_CALL(*im.at("blob0"), close()).Times(1);
+
+ EXPECT_TRUE(h->open(defaultSessionNumber, blobs::read, "blob0"));
+ EXPECT_THROW(h->read(defaultSessionNumber, 0, 10), std::runtime_error);
+}
+
+} // namespace ipmi_flash
diff --git a/bmc/log-handler/test/log_stat_unittest.cpp b/bmc/log-handler/test/log_stat_unittest.cpp
new file mode 100644
index 0000000..6eb91b5
--- /dev/null
+++ b/bmc/log-handler/test/log_stat_unittest.cpp
@@ -0,0 +1,95 @@
+// Copyright 2021 Google Inc.
+//
+// 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 "log_handler.hpp"
+#include "log_mock.hpp"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace ipmi_flash
+{
+
+class LogStatBlobTest : public ::testing::Test
+{
+ protected:
+ void SetUp() override
+ {
+ h = std::make_unique<LogBlobHandler>(
+ createMockLogConfigs(blobNames, &im, &tm));
+
+ EXPECT_CALL(*tm.at("blob0"), trigger()).WillOnce(Return(true));
+ EXPECT_TRUE(h->open(0, blobs::read, "blob0"));
+
+ blobs::BlobMeta meta;
+ EXPECT_TRUE(h->stat(0, &meta));
+ EXPECT_EQ(blobs::StateFlags::committing, meta.blobState);
+ }
+
+ std::unique_ptr<blobs::GenericBlobInterface> h;
+ std::vector<std::string> blobNames{"blob0"};
+ std::unordered_map<std::string, TriggerMock*> tm;
+ std::unordered_map<std::string, ImageHandlerMock*> im;
+};
+
+TEST_F(LogStatBlobTest, CreateError)
+{
+ EXPECT_CALL(*tm.at("blob0"), status())
+ .WillOnce(Return(ActionStatus::failed));
+ tm.at("blob0")->cb(*tm.at("blob0"));
+
+ blobs::BlobMeta meta;
+ EXPECT_TRUE(h->stat(0, &meta));
+ EXPECT_EQ(blobs::StateFlags::commit_error, meta.blobState);
+}
+
+class LogStatSizeBlobTest :
+ public LogStatBlobTest,
+ public ::testing::WithParamInterface<std::vector<uint8_t>>
+{};
+
+TEST_P(LogStatSizeBlobTest, StatWithSize)
+{
+ const std::vector<uint8_t> data = GetParam();
+ EXPECT_CALL(*tm.at("blob0"), status())
+ .WillOnce(Return(ActionStatus::success));
+ EXPECT_CALL(*im.at("blob0"), open(_, std::ios::in)).WillOnce(Return(true));
+ EXPECT_CALL(*im.at("blob0"), read(0, ::testing::Ge(data.size())))
+ .WillOnce(Return(data));
+ EXPECT_CALL(*im.at("blob0"), close()).Times(1);
+ tm.at("blob0")->cb(*tm.at("blob0"));
+
+ blobs::BlobMeta meta;
+ EXPECT_TRUE(h->stat(0, &meta));
+ EXPECT_EQ(blobs::StateFlags::committed | blobs::StateFlags::open_read,
+ meta.blobState);
+ EXPECT_EQ(data.size(), meta.size);
+}
+
+const std::vector<std::vector<uint8_t>> datas = {
+ {},
+ {0, 1, 2, 3, 4, 5, 6},
+};
+
+INSTANTIATE_TEST_SUITE_P(DifferentData, LogStatSizeBlobTest,
+ testing::ValuesIn(datas));
+
+} // namespace ipmi_flash
diff --git a/bmc/log-handler/test/meson.build b/bmc/log-handler/test/meson.build
new file mode 100644
index 0000000..36b4917
--- /dev/null
+++ b/bmc/log-handler/test/meson.build
@@ -0,0 +1,14 @@
+log_tests = [
+ 'canhandle_enumerate',
+ 'createhandler']
+
+foreach t : log_tests
+ test(
+ t,
+ executable(
+ t.underscorify(), 'log_' + t + '_unittest.cpp',
+ build_by_default: false,
+ implicit_include_directories: false,
+ include_directories: [root_inc, bmc_test_inc, log_inc],
+ dependencies: [log_dep, blobs_dep, gtest, gmock]))
+endforeach