power-utils: Initially use i2c in updater

Create I2CDevice in updater and invoke read() in doUpdate(), that could
be used in future.
Use mocked I2CInterface in updater's unit test case.

Tested: Manually verify on Witherspoon that the i2c device is opened
        and closed during PSU code update.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: Ie3d9f0565a2ceb000f489647a58ca967a2ef0c38
diff --git a/tools/power-utils/meson.build b/tools/power-utils/meson.build
index f4b8d45..2e6adad 100644
--- a/tools/power-utils/meson.build
+++ b/tools/power-utils/meson.build
@@ -7,10 +7,11 @@
         phosphor_dbus_interfaces,
         phosphor_logging,
     ],
-    include_directories: '../..',
+    include_directories: [libpower_inc, libi2c_inc],
     install: true,
     link_with: [
         libpower,
+        libi2c_dev,
     ]
 )
 
diff --git a/tools/power-utils/test/meson.build b/tools/power-utils/test/meson.build
index 45052ad..1f23ad0 100644
--- a/tools/power-utils/test/meson.build
+++ b/tools/power-utils/test/meson.build
@@ -9,7 +9,7 @@
             phosphor_logging,
         ],
         implicit_include_directories: false,
-        include_directories: '../../..',
+        include_directories: libpower_inc,
         link_with: [
             libpower,
         ],
@@ -25,10 +25,11 @@
         '../updater.cpp',
         dependencies: [
             gtest,
+            gmock,
             phosphor_logging,
         ],
         implicit_include_directories: false,
-        include_directories: '../../..',
+        include_directories: [libpower_inc, libi2c_inc],
         link_with: [
             libpower,
         ],
diff --git a/tools/power-utils/test/test_updater.cpp b/tools/power-utils/test/test_updater.cpp
index 2786986..4d051f7 100644
--- a/tools/power-utils/test/test_updater.cpp
+++ b/tools/power-utils/test/test_updater.cpp
@@ -14,22 +14,92 @@
  * limitations under the License.
  */
 #include "../updater.hpp"
+#include "test/mocked_i2c_interface.hpp"
+
+#include <filesystem>
 
 #include <gtest/gtest.h>
 
+namespace fs = std::filesystem;
+
+using ::testing::_;
+using ::testing::An;
+
 namespace updater
 {
 namespace internal
 {
 
 std::string getDeviceName(std::string devPath);
+std::pair<uint8_t, uint8_t> parseDeviceName(const std::string& devName);
 
 } // namespace internal
 } // namespace updater
 
 using namespace updater;
 
-TEST(Updater, getDeviceName)
+class TestUpdater : public ::testing::Test
+{
+  public:
+    using MockedI2CInterface = i2c::MockedI2CInterface;
+    using I2CInterface = i2c::I2CInterface;
+
+    TestUpdater()
+    {
+        setupDeviceSysfs();
+    }
+    ~TestUpdater()
+    {
+        fs::remove_all(tmpDir);
+    }
+
+    void setupDeviceSysfs()
+    {
+        auto tmpPath = fs::temp_directory_path();
+        tmpDir = (tmpPath / "test_XXXXXX");
+        if (!mkdtemp(tmpDir.data()))
+        {
+            throw "Failed to create temp dir";
+        }
+        // Create device path with symbol link
+        realDevicePath = fs::path(tmpDir) / "devices/3-0068";
+        devPath = fs::path(tmpDir) / "i2c";
+        fs::create_directories(realDevicePath);
+        fs::create_directories(realDevicePath / "driver");
+        fs::create_directories(devPath);
+        devPath /= "3-0068";
+        fs::create_directory_symlink(realDevicePath, devPath);
+    }
+
+    MockedI2CInterface& getMockedI2c()
+    {
+        return *reinterpret_cast<MockedI2CInterface*>(updater->i2c.get());
+    }
+
+    std::shared_ptr<I2CInterface> stolenI2C;
+    std::unique_ptr<Updater> updater;
+    fs::path realDevicePath;
+    fs::path devPath;
+    std::string tmpDir;
+    std::string psuInventoryPath = "/com/example/psu";
+    std::string imageDir = "/tmp/image/xxx";
+};
+
+TEST_F(TestUpdater, ctordtor)
+{
+    updater = std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
+}
+
+TEST_F(TestUpdater, doUpdate)
+{
+    updater = std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
+    updater->createI2CDevice();
+    auto& i2c = getMockedI2c();
+    EXPECT_CALL(i2c, read(_, An<uint8_t&>()));
+    updater->doUpdate();
+}
+
+TEST_F(TestUpdater, getDeviceName)
 {
     auto ret = internal::getDeviceName("");
     EXPECT_TRUE(ret.empty());
@@ -40,3 +110,18 @@
     ret = internal::getDeviceName("/sys/bus/i2c/devices/3-0069/");
     EXPECT_EQ("3-0069", ret);
 }
+
+TEST_F(TestUpdater, parseDeviceName)
+{
+    auto [id, addr] = internal::parseDeviceName("3-0068");
+    EXPECT_EQ(3, id);
+    EXPECT_EQ(0x68, addr);
+
+    std::tie(id, addr) = internal::parseDeviceName("11-0069");
+    EXPECT_EQ(11, id);
+    EXPECT_EQ(0x69, addr);
+
+    EXPECT_THROW(internal::parseDeviceName("no-number"), std::invalid_argument);
+
+    EXPECT_DEATH(internal::parseDeviceName("invalid"), "");
+}
diff --git a/tools/power-utils/updater.cpp b/tools/power-utils/updater.cpp
index e26d1b0..749b5dd 100644
--- a/tools/power-utils/updater.cpp
+++ b/tools/power-utils/updater.cpp
@@ -60,6 +60,17 @@
     return devicePath;
 }
 
+std::pair<uint8_t, uint8_t> parseDeviceName(const std::string& devName)
+{
+    // Get I2C bus id and device address, e.g. 3-0068
+    // is parsed to bus id 3, device address 0x68
+    auto pos = devName.find('-');
+    assert(pos != std::string::npos);
+    uint8_t busId = std::stoi(devName.substr(0, pos));
+    uint8_t devAddr = std::stoi(devName.substr(pos + 1), nullptr, 16);
+    return {busId, devAddr};
+}
+
 } // namespace internal
 
 bool update(const std::string& psuInventoryPath, const std::string& imageDir)
@@ -79,6 +90,7 @@
     }
 
     updater.bindUnbind(false);
+    updater.createI2CDevice();
     int ret = updater.doUpdate();
     updater.bindUnbind(true);
     return ret == 0;
@@ -231,7 +243,15 @@
 int Updater::doUpdate()
 {
     // TODO
+    uint8_t data;
+    i2c->read(0x00, data);
     return 0;
 }
 
+void Updater::createI2CDevice()
+{
+    auto [id, addr] = internal::parseDeviceName(devName);
+    i2c = i2c::create(id, addr);
+}
+
 } // namespace updater
diff --git a/tools/power-utils/updater.hpp b/tools/power-utils/updater.hpp
index befa4fb..f22f663 100644
--- a/tools/power-utils/updater.hpp
+++ b/tools/power-utils/updater.hpp
@@ -15,10 +15,14 @@
  */
 #pragma once
 
+#include "i2c_interface.hpp"
+
 #include <filesystem>
 #include <sdbusplus/bus.hpp>
 #include <string>
 
+class TestUpdater;
+
 namespace updater
 {
 
@@ -37,6 +41,7 @@
 class Updater
 {
   public:
+    friend TestUpdater;
     Updater() = delete;
     Updater(const Updater&) = delete;
     Updater& operator=(const Updater&) = delete;
@@ -80,6 +85,13 @@
      */
     int doUpdate();
 
+    /** @brief Create I2C device
+     *
+     * Creates the I2C device based on the device name.
+     * e.g. It opens busId 3, address 0x68 for "3-0068"
+     */
+    void createI2CDevice();
+
   private:
     /** @brief The sdbusplus DBus bus connection */
     sdbusplus::bus::bus bus;
@@ -110,6 +122,9 @@
      *   /sys/bus/i2c/drivers/ibm-cffps
      */
     fs::path driverPath;
+
+    /** @brief The i2c device interface */
+    std::unique_ptr<i2c::I2CInterface> i2c;
 };
 
 } // namespace updater