Add initial unit tests

Signed-off-by: Lei YU <yulei.sh@bytedance.com>
Change-Id: Id4850e52073ea6780a978c6cd5a5c439aa8ed846
diff --git a/meson.build b/meson.build
index 9da20c2..e58eb10 100644
--- a/meson.build
+++ b/meson.build
@@ -17,6 +17,7 @@
 phosphor_dbus_interfaces = dependency('phosphor-dbus-interfaces')
 phosphor_logging = dependency('phosphor-logging')
 libipmid = dependency('libipmid')
+sdbusplus = dependency('sdbusplus')
 
 # Common configurations for src and test
 cdata = configuration_data()
@@ -25,3 +26,8 @@
 cdata.set_quoted('BIOS_OBJPATH', get_option('BIOS_OBJPATH'))
 
 subdir('src')
+
+build_tests = get_option('tests')
+if not build_tests.disabled()
+  subdir('test')
+endif
diff --git a/meson_options.txt b/meson_options.txt
index 7b1d3da..22a4404 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,3 +1,5 @@
+option('tests', type: 'feature', description: 'Build tests')
+
 option('BIOS_OBJPATH',
        type: 'string',
        value: '/xyz/openbmc_project/software/bios_active',
diff --git a/src/inspur_oem.cpp b/src/inspur_oem.cpp
index 04df3fb..53eb429 100644
--- a/src/inspur_oem.cpp
+++ b/src/inspur_oem.cpp
@@ -2,12 +2,12 @@
 
 #include "inspur_oem.hpp"
 
+#include "sdbus_wrapper.hpp"
 #include "utils.hpp"
 
 #include <ipmid/api.h>
 
 #include <phosphor-logging/log.hpp>
-#include <sdbusplus/bus.hpp>
 
 #include <optional>
 
@@ -21,6 +21,7 @@
 constexpr auto FIRMWARE_BUILDTIME_OFFSET =
     FIRMWARE_VERSION_OFFSET + FIRMWARE_VERSION_SIZE;
 constexpr auto FIRMWARE_BUILDTIME_SIZE = 20;
+constexpr auto FIRMWARE_MIN_SIZE = FIRMWARE_BUILDTIME_OFFSET;
 
 static_assert(FIRMWARE_VERSION_OFFSET == 1);
 static_assert(FIRMWARE_BUILDTIME_OFFSET == 16);
@@ -35,7 +36,7 @@
 using namespace inspur;
 
 static void registerOEMFunctions() __attribute__((constructor));
-sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
+static auto& bus = getBus();
 
 struct ParsedAssetInfo
 {
@@ -120,6 +121,10 @@
 
 void parseBIOSInfo(const std::vector<uint8_t>& data)
 {
+    if (data.size() < FIRMWARE_MIN_SIZE)
+    {
+        return;
+    }
     bios_version_devname dev = static_cast<bios_version_devname>(data[0]);
     std::string version{data.data() + FIRMWARE_VERSION_OFFSET,
                         data.data() + FIRMWARE_VERSION_SIZE};
diff --git a/src/inspur_oem.hpp b/src/inspur_oem.hpp
index 8c47e8d..fc5fc85 100644
--- a/src/inspur_oem.hpp
+++ b/src/inspur_oem.hpp
@@ -1,4 +1,5 @@
 #pragma once
+
 #include <array>
 #include <cstdint>
 #include <string_view>
diff --git a/src/meson.build b/src/meson.build
index b6828c7..620197a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -10,6 +10,7 @@
   'inspur-ipmi-oem',
   'inspur_oem.cpp',
   'utils.cpp',
+  'sdbus_wrapper.cpp',
   dependencies: [
     phosphor_dbus_interfaces,
     phosphor_logging,
diff --git a/src/sdbus_wrapper.cpp b/src/sdbus_wrapper.cpp
new file mode 100644
index 0000000..a781036
--- /dev/null
+++ b/src/sdbus_wrapper.cpp
@@ -0,0 +1,9 @@
+#include "sdbus_wrapper.hpp"
+
+#include <ipmid/api.h>
+
+sdbusplus::bus::bus& getBus()
+{
+    static sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
+    return bus;
+}
diff --git a/src/sdbus_wrapper.hpp b/src/sdbus_wrapper.hpp
new file mode 100644
index 0000000..d6273d1
--- /dev/null
+++ b/src/sdbus_wrapper.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+
+sdbusplus::bus::bus& getBus();
diff --git a/test/meson.build b/test/meson.build
new file mode 100644
index 0000000..f2534f3
--- /dev/null
+++ b/test/meson.build
@@ -0,0 +1,24 @@
+gtest = dependency('gtest', main: true, disabler: true, required: build_tests)
+gmock = dependency('gmock', disabler: true, required: build_tests)
+
+configure_file(output: 'config.h',
+  configuration: cdata,
+)
+test_inc = include_directories('.')
+
+test_inspur_ipmi_oem = executable(
+  'test_inspur-ipmi-oem',
+  '../src/inspur_oem.cpp',
+  'test_inspur_ipmi_oem.cpp',
+  'mocked_utils.cpp',
+  'mocked_sdbus.cpp',
+  include_directories: [test_inc, src_inc],
+  dependencies: [
+    gtest,
+    gmock,
+    phosphor_logging,
+    phosphor_dbus_interfaces,
+    sdbusplus,
+  ])
+
+test('test_inspur-ipmi-oem', test_inspur_ipmi_oem)
diff --git a/test/mocked_sdbus.cpp b/test/mocked_sdbus.cpp
new file mode 100644
index 0000000..0d81853
--- /dev/null
+++ b/test/mocked_sdbus.cpp
@@ -0,0 +1,26 @@
+#include "sdbus_wrapper.hpp"
+
+#include <sdbusplus/test/sdbus_mock.hpp>
+
+#include <memory>
+
+// To support ipmid_get_sd_bus_connection, we have to make the sdbusMock global
+static std::unique_ptr<sdbusplus::SdBusMock> sdbusMock;
+static std::unique_ptr<sdbusplus::bus::bus> mockedBus;
+
+sdbusplus::bus::bus& getBus()
+{
+    if (!sdbusMock)
+    {
+        sdbusMock = std::make_unique<sdbusplus::SdBusMock>();
+    }
+    mockedBus = std::make_unique<sdbusplus::bus::bus>(
+        sdbusplus::get_mocked_new(sdbusMock.get()));
+    return *mockedBus.get();
+}
+
+void clearMockedBus()
+{
+    sdbusMock.reset();
+    mockedBus.reset();
+}
diff --git a/test/mocked_sdbus.hpp b/test/mocked_sdbus.hpp
new file mode 100644
index 0000000..5d4fcd5
--- /dev/null
+++ b/test/mocked_sdbus.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+void clearMockedBus();
diff --git a/test/mocked_utils.cpp b/test/mocked_utils.cpp
new file mode 100644
index 0000000..31cbd80
--- /dev/null
+++ b/test/mocked_utils.cpp
@@ -0,0 +1,21 @@
+#include "mocked_utils.hpp"
+
+namespace utils
+{
+
+static std::unique_ptr<MockedUtils> utils;
+const UtilsInterface& getUtils()
+{
+    if (!utils)
+    {
+        utils = std::make_unique<MockedUtils>();
+    }
+    return *utils;
+}
+
+void freeUtils()
+{
+    utils.reset();
+}
+
+} // namespace utils
diff --git a/test/mocked_utils.hpp b/test/mocked_utils.hpp
new file mode 100644
index 0000000..8cd4a43
--- /dev/null
+++ b/test/mocked_utils.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "utils.hpp"
+
+#include <gmock/gmock.h>
+
+namespace utils
+{
+
+class MockedUtils : public UtilsInterface
+{
+  public:
+    virtual ~MockedUtils() = default;
+
+    MOCK_CONST_METHOD3(getService,
+                       std::string(sdbusplus::bus::bus& bus, const char* path,
+                                   const char* interface));
+
+    MOCK_CONST_METHOD3(getServices,
+                       std::vector<std::string>(sdbusplus::bus::bus& bus,
+                                                const char* path,
+                                                const char* interface));
+
+    MOCK_CONST_METHOD5(getPropertyImpl,
+                       any(sdbusplus::bus::bus& bus, const char* service,
+                           const char* path, const char* interface,
+                           const char* propertyName));
+
+    MOCK_CONST_METHOD6(setPropertyImpl,
+                       void(sdbusplus::bus::bus& bus, const char* service,
+                            const char* path, const char* interface,
+                            const char* propertyName, ValueType&& value));
+};
+
+const UtilsInterface& getUtils();
+
+void freeUtils();
+
+} // namespace utils
diff --git a/test/test_inspur_ipmi_oem.cpp b/test/test_inspur_ipmi_oem.cpp
new file mode 100644
index 0000000..eb2360b
--- /dev/null
+++ b/test/test_inspur_ipmi_oem.cpp
@@ -0,0 +1,84 @@
+#include "config.h"
+
+#include "inspur_oem.hpp"
+#include "mocked_sdbus.hpp"
+#include "mocked_utils.hpp"
+#include "sdbus_wrapper.hpp"
+
+#include <ipmid/api.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::IsNull;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::VariantWith;
+
+namespace ipmi
+{
+
+void parseBIOSInfo(const std::vector<uint8_t>& data);
+
+}
+
+void ipmi_register_callback(ipmi_netfn_t, ipmi_cmd_t, ipmi_context_t,
+                            ipmid_callback_t, ipmi_cmd_privilege_t)
+{
+    // Empty
+}
+
+class TestInspurIpmiOem : public ::testing::Test
+{
+  public:
+    TestInspurIpmiOem() :
+        mockedUtils(
+            reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
+    {}
+    virtual ~TestInspurIpmiOem()
+    {
+        utils::freeUtils();
+        clearMockedBus();
+    }
+
+    sdbusplus::bus::bus& mockedBus = getBus();
+    const utils::MockedUtils& mockedUtils;
+};
+
+TEST_F(TestInspurIpmiOem, Empty)
+{
+    // Empty
+}
+
+TEST_F(TestInspurIpmiOem, parseBIOSInfoEmpty)
+{
+
+    EXPECT_CALL(mockedUtils, setPropertyImpl(_, _, _, _, _, _)).Times(0);
+    ipmi::parseBIOSInfo({});
+}
+
+TEST_F(TestInspurIpmiOem, parseBIOSInfoValidBIOSVersion)
+{
+
+    std::vector<uint8_t> data{
+        0x00, 0x30, 0x31, 0x2e, 0x30, 0x31, 0x2e, 0x30, 0x31, 0x2e,
+        0x30, 0x31, 0x2e, 0x30, 0x31, 0x00, 0x30, 0x38, 0x2f, 0x31,
+        0x31, 0x2f, 0x32, 0x30, 0x32, 0x30, 0x20, 0x32, 0x30, 0x3a,
+        0x31, 0x39, 0x3a, 0x33, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+    std::string dummyService = "com.test.bios.version";
+    std::string expectedVersion = "01.01.01.01.01";
+    EXPECT_CALL(mockedUtils,
+                getService(_, StrEq(BIOS_OBJPATH), StrEq(VERSION_IFACE)))
+        .WillOnce(Return(dummyService));
+    EXPECT_CALL(
+        mockedUtils,
+        setPropertyImpl(_, StrEq(dummyService), StrEq(BIOS_OBJPATH),
+                        StrEq(VERSION_IFACE), StrEq(VERSION),
+                        VariantWith<std::string>(StrEq(expectedVersion))))
+        .Times(1);
+    ipmi::parseBIOSInfo(data);
+}