fw update: tests for common code

Tests for the common code introduced in another commit.

These tests should check that the common code behaves as outlined in the
design [1]

References:
[1] https://github.com/openbmc/docs/blob/master/designs/code-update.md

Tested: unit tests pass

Change-Id: I8f12839afd47ef3403a80439af54fedcc00f10be
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/test/common/device/meson.build b/test/common/device/meson.build
new file mode 100644
index 0000000..b0b09c2
--- /dev/null
+++ b/test/common/device/meson.build
@@ -0,0 +1,27 @@
+
+testcases = [
+  'test_device_specific_update_function',
+  'test_device_start_update_async_success',
+  'test_device_start_update_async_invalid_fd',
+]
+
+foreach t : testcases
+  test(
+    t,
+    executable(
+      t,
+      f'@t@.cpp',
+      include_directories: [
+        common_include,
+      ],
+      dependencies: [
+        libpldm_dep,
+        sdbusplus_dep,
+        phosphor_logging_dep,
+        gtest,
+      ],
+      link_with: [libpldmutil, libpldmcreatepkg, software_common_lib, libnopdevice]
+    )
+  )
+endforeach
+
diff --git a/test/common/device/test_device_specific_update_function.cpp b/test/common/device/test_device_specific_update_function.cpp
new file mode 100644
index 0000000..903e10b
--- /dev/null
+++ b/test/common/device/test_device_specific_update_function.cpp
@@ -0,0 +1,73 @@
+#include "../nopdevice/nopdevice.hpp"
+#include "common/include/device.hpp"
+#include "common/include/software_manager.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Software/Update/server.hpp>
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+class DeviceTest : public testing::Test
+{
+    void SetUp() override {}
+    void TearDown() override {}
+};
+
+// NOLINTBEGIN
+sdbusplus::async::task<>
+    testDeviceSpecificUpdateFunction(sdbusplus::async::context& ctx)
+// NOLINTEND
+{
+    NopCodeUpdater nopcu(ctx);
+    NopCodeUpdater* cu = &nopcu;
+
+    auto device = std::make_unique<NopDevice>(ctx, cu);
+
+    // NOLINTBEGIN
+    std::unique_ptr<SoftwareActivationProgress> sap =
+        std::make_unique<SoftwareActivationProgress>(ctx, "/");
+    uint8_t buffer[10];
+    size_t buffer_size = 10;
+    bool success =
+        co_await device->updateDevice((const uint8_t*)buffer, buffer_size, sap);
+
+    assert(success);
+
+    // NOLINTEND
+
+    ctx.request_stop();
+
+    co_return;
+}
+
+TEST_F(DeviceTest, TestDeviceConstructor)
+{
+    sdbusplus::async::context ctx;
+    NopCodeUpdater nopcu(ctx);
+    NopCodeUpdater* cu = &nopcu;
+
+    auto device = std::make_unique<NopDevice>(ctx, cu);
+
+    assert(device->getEMConfigType().starts_with("Nop"));
+
+    // the software version is currently unknown
+    assert(device->softwareCurrent == nullptr);
+}
+
+TEST_F(DeviceTest, TestDeviceSpecificUpdateFunction)
+{
+    sdbusplus::async::context ctx;
+
+    // NOLINTBEGIN
+    ctx.spawn(testDeviceSpecificUpdateFunction(ctx));
+    // NOLINTEND
+
+    ctx.run();
+}
diff --git a/test/common/device/test_device_start_update_async_invalid_fd.cpp b/test/common/device/test_device_start_update_async_invalid_fd.cpp
new file mode 100644
index 0000000..c71f962
--- /dev/null
+++ b/test/common/device/test_device_start_update_async_invalid_fd.cpp
@@ -0,0 +1,71 @@
+#include "../nopdevice/nopdevice.hpp"
+#include "common/include/device.hpp"
+#include "common/include/software_manager.hpp"
+#include "test/create_package/create_pldm_fw_package.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Software/Update/server.hpp>
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+class DeviceTest : public testing::Test
+{
+    void SetUp() override {}
+    void TearDown() override {}
+};
+
+// NOLINTBEGIN
+sdbusplus::async::task<>
+    testDeviceStartUpdateInvalidFD(sdbusplus::async::context& ctx)
+// NOLINTEND
+{
+    NopCodeUpdater nopcu(ctx);
+    NopCodeUpdater* cu = &nopcu;
+
+    auto device = std::make_unique<NopDevice>(ctx, cu);
+
+    device->softwareCurrent =
+        std::make_unique<Software>(ctx, "swid_invalid_fd", *device);
+
+    device->softwareCurrent->setVersion("vUnknown");
+
+    std::unique_ptr<SoftwareActivationProgress> sap =
+        std::make_unique<SoftwareActivationProgress>(ctx, "/");
+
+    sdbusplus::message::unix_fd image;
+    image.fd = -1;
+
+    const auto applyTimeImmediate = sdbusplus::common::xyz::openbmc_project::
+        software::ApplyTime::RequestedApplyTimes::Immediate;
+
+    assert(!device->deviceSpecificUpdateFunctionCalled);
+
+    std::unique_ptr<Software> softwareUpdate =
+        std::make_unique<Software>(ctx, "new_swid", *device);
+
+    co_await device->startUpdateAsync(image, applyTimeImmediate,
+                                      std::move(softwareUpdate));
+
+    // assert the bad file descriptor was caught and we did not proceed
+    assert(!device->deviceSpecificUpdateFunctionCalled);
+
+    ctx.request_stop();
+
+    co_return;
+}
+
+TEST_F(DeviceTest, TestDeviceStartUpdateInvalidFD)
+{
+    sdbusplus::async::context ctx;
+
+    ctx.spawn(testDeviceStartUpdateInvalidFD(ctx));
+
+    ctx.run();
+}
diff --git a/test/common/device/test_device_start_update_async_success.cpp b/test/common/device/test_device_start_update_async_success.cpp
new file mode 100644
index 0000000..4682596
--- /dev/null
+++ b/test/common/device/test_device_start_update_async_success.cpp
@@ -0,0 +1,97 @@
+#include "../nopdevice/nopdevice.hpp"
+#include "test/create_package/create_pldm_fw_package.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Software/Update/server.hpp>
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+// NOLINTBEGIN
+sdbusplus::async::task<>
+    testDeviceStartUpdateSuccess(sdbusplus::async::context& ctx)
+// NOLINTEND
+{
+    NopCodeUpdater nopcu(ctx);
+    NopCodeUpdater* cu = &nopcu;
+
+    auto device = std::make_unique<NopDevice>(ctx, cu);
+
+    device->softwareCurrent = std::make_unique<Software>(ctx, "swid1", *device);
+
+    device->softwareCurrent->setVersion("vUnknown");
+
+    std::unique_ptr<SoftwareActivationProgress> sap =
+        std::make_unique<SoftwareActivationProgress>(ctx, "/");
+
+    const int fd = memfd_create("test_memfd", 0);
+
+    assert(fd >= 0);
+
+    lg2::debug("create fd {FD}", "FD", fd);
+
+    uint8_t component_image[] = {0x12, 0x34, 0x83, 0x21};
+
+    size_t size_out = 0;
+    std::unique_ptr<uint8_t[]> buf = create_pldm_package_buffer(
+        component_image, sizeof(component_image),
+        std::optional<uint32_t>(exampleVendorIANA),
+        std::optional<std::string>(exampleCompatible), size_out);
+
+    ssize_t bytes_written = write(fd, (void*)buf.get(), size_out);
+    if (bytes_written == -1)
+    {
+        std::cerr << "Failed to write to memfd: " << strerror(errno)
+                  << std::endl;
+        close(fd);
+        assert(false);
+    }
+    if (lseek(fd, 0, SEEK_SET) != 0)
+    {
+        lg2::error("could not seek to the beginning of the file");
+        assert(false);
+    }
+
+    int fd2 = dup(fd);
+    sdbusplus::message::unix_fd image2 = fd2;
+
+    if (fd2 < 0)
+    {
+        lg2::error("ERROR calling dup on fd: {ERR}", "ERR", strerror(errno));
+        assert(false);
+    }
+
+    lg2::debug("dup fd: {FD}", "FD", fd2);
+
+    const auto applyTimeImmediate = sdbusplus::common::xyz::openbmc_project::
+        software::ApplyTime::RequestedApplyTimes::Immediate;
+
+    std::unique_ptr<Software> softwareUpdate =
+        std::make_unique<Software>(ctx, "myotherswid", *device);
+
+    co_await device->startUpdateAsync(image2, applyTimeImmediate,
+                                      std::move(softwareUpdate));
+
+    assert(device->deviceSpecificUpdateFunctionCalled);
+
+    close(fd);
+
+    ctx.request_stop();
+
+    co_return;
+}
+
+TEST(DeviceTest, TestDeviceStartUpdateSuccess)
+{
+    sdbusplus::async::context ctx;
+
+    ctx.spawn(testDeviceStartUpdateSuccess(ctx));
+
+    ctx.run();
+}