Move source files into application-specific sub-directories

Currently, dbus-sensors implement multiple applications:
 - psusensor
 - adcsensor
 - intelcpusensor
 - hwmontempsensor
 - ipmbsensor
 - nvmesensor
 - externalsensor
 - mcutempsensor
 - intrusionsensor
 - fansensor
 - exitairtempsensor

This commit is to create separate directories for each application so
that things can be separated more easily and the files are smaller,
instead of creating one huge file for the sensor implementation.

There was some discussion in discord on this. [1][2]

[1]: https://discord.com/channels/775381525260664832/1187158775438778408/1284106093756289067
[2]: https://discord.com/channels/775381525260664832/867820390406422538/1303217796821553214

Signed-off-by: George Liu <liuxiwei@ieisystem.com>
Change-Id: I258fc2ee7d8f939c7b83a07350395e78775b2b8d
diff --git a/src/ADCSensor.cpp b/src/adc/ADCSensor.cpp
similarity index 100%
rename from src/ADCSensor.cpp
rename to src/adc/ADCSensor.cpp
diff --git a/src/ADCSensor.hpp b/src/adc/ADCSensor.hpp
similarity index 100%
rename from src/ADCSensor.hpp
rename to src/adc/ADCSensor.hpp
diff --git a/src/ADCSensorMain.cpp b/src/adc/ADCSensorMain.cpp
similarity index 100%
rename from src/ADCSensorMain.cpp
rename to src/adc/ADCSensorMain.cpp
diff --git a/src/adc/meson.build b/src/adc/meson.build
new file mode 100644
index 0000000..02c69c4
--- /dev/null
+++ b/src/adc/meson.build
@@ -0,0 +1,15 @@
+src_inc = include_directories('..')
+
+executable(
+    'adcsensor',
+    'ADCSensor.cpp',
+    'ADCSensorMain.cpp',
+    dependencies: [
+        default_deps,
+        gpiodcxx,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/ExitAirTempSensor.cpp b/src/exit-air/ExitAirTempSensor.cpp
similarity index 100%
rename from src/ExitAirTempSensor.cpp
rename to src/exit-air/ExitAirTempSensor.cpp
diff --git a/src/ExitAirTempSensor.hpp b/src/exit-air/ExitAirTempSensor.hpp
similarity index 100%
rename from src/ExitAirTempSensor.hpp
rename to src/exit-air/ExitAirTempSensor.hpp
diff --git a/src/exit-air/meson.build b/src/exit-air/meson.build
new file mode 100644
index 0000000..70119af
--- /dev/null
+++ b/src/exit-air/meson.build
@@ -0,0 +1,13 @@
+src_inc = include_directories('..')
+
+executable(
+    'exitairtempsensor',
+    'ExitAirTempSensor.cpp',
+    dependencies: [
+        default_deps,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/ExternalSensor.cpp b/src/external/ExternalSensor.cpp
similarity index 100%
rename from src/ExternalSensor.cpp
rename to src/external/ExternalSensor.cpp
diff --git a/src/ExternalSensor.hpp b/src/external/ExternalSensor.hpp
similarity index 100%
rename from src/ExternalSensor.hpp
rename to src/external/ExternalSensor.hpp
diff --git a/src/ExternalSensorMain.cpp b/src/external/ExternalSensorMain.cpp
similarity index 100%
rename from src/ExternalSensorMain.cpp
rename to src/external/ExternalSensorMain.cpp
diff --git a/src/external/meson.build b/src/external/meson.build
new file mode 100644
index 0000000..babde51
--- /dev/null
+++ b/src/external/meson.build
@@ -0,0 +1,14 @@
+src_inc = include_directories('..')
+
+executable(
+    'externalsensor',
+    'ExternalSensor.cpp',
+    'ExternalSensorMain.cpp',
+    dependencies: [
+        default_deps,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/FanMain.cpp b/src/fan/FanMain.cpp
similarity index 100%
rename from src/FanMain.cpp
rename to src/fan/FanMain.cpp
diff --git a/src/PresenceGpio.cpp b/src/fan/PresenceGpio.cpp
similarity index 100%
rename from src/PresenceGpio.cpp
rename to src/fan/PresenceGpio.cpp
diff --git a/src/PresenceGpio.hpp b/src/fan/PresenceGpio.hpp
similarity index 100%
rename from src/PresenceGpio.hpp
rename to src/fan/PresenceGpio.hpp
diff --git a/src/TachSensor.cpp b/src/fan/TachSensor.cpp
similarity index 100%
rename from src/TachSensor.cpp
rename to src/fan/TachSensor.cpp
diff --git a/src/TachSensor.hpp b/src/fan/TachSensor.hpp
similarity index 100%
rename from src/TachSensor.hpp
rename to src/fan/TachSensor.hpp
diff --git a/src/fan/meson.build b/src/fan/meson.build
new file mode 100644
index 0000000..93f8ae6
--- /dev/null
+++ b/src/fan/meson.build
@@ -0,0 +1,17 @@
+src_inc = include_directories('..')
+
+executable(
+    'fansensor',
+    'FanMain.cpp',
+    'PresenceGpio.cpp',
+    'TachSensor.cpp',
+    '../PwmSensor.cpp',
+    dependencies: [
+        default_deps,
+        gpiodcxx,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/HwmonTempMain.cpp b/src/hwmon-temp/HwmonTempMain.cpp
similarity index 100%
rename from src/HwmonTempMain.cpp
rename to src/hwmon-temp/HwmonTempMain.cpp
diff --git a/src/HwmonTempSensor.cpp b/src/hwmon-temp/HwmonTempSensor.cpp
similarity index 100%
rename from src/HwmonTempSensor.cpp
rename to src/hwmon-temp/HwmonTempSensor.cpp
diff --git a/src/HwmonTempSensor.hpp b/src/hwmon-temp/HwmonTempSensor.hpp
similarity index 100%
rename from src/HwmonTempSensor.hpp
rename to src/hwmon-temp/HwmonTempSensor.hpp
diff --git a/src/hwmon-temp/meson.build b/src/hwmon-temp/meson.build
new file mode 100644
index 0000000..3149960
--- /dev/null
+++ b/src/hwmon-temp/meson.build
@@ -0,0 +1,15 @@
+src_inc = include_directories('..')
+
+executable(
+    'hwmontempsensor',
+    'HwmonTempMain.cpp',
+    'HwmonTempSensor.cpp',
+    dependencies: [
+        default_deps,
+        devicemgmt_dep,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/IntelCPUSensor.cpp b/src/intel-cpu/IntelCPUSensor.cpp
similarity index 100%
rename from src/IntelCPUSensor.cpp
rename to src/intel-cpu/IntelCPUSensor.cpp
diff --git a/src/IntelCPUSensor.hpp b/src/intel-cpu/IntelCPUSensor.hpp
similarity index 100%
rename from src/IntelCPUSensor.hpp
rename to src/intel-cpu/IntelCPUSensor.hpp
diff --git a/src/IntelCPUSensorMain.cpp b/src/intel-cpu/IntelCPUSensorMain.cpp
similarity index 100%
rename from src/IntelCPUSensorMain.cpp
rename to src/intel-cpu/IntelCPUSensorMain.cpp
diff --git a/src/intel-cpu/meson.build b/src/intel-cpu/meson.build
new file mode 100644
index 0000000..03362da
--- /dev/null
+++ b/src/intel-cpu/meson.build
@@ -0,0 +1,19 @@
+src_inc = ['..']
+if not meson.get_compiler('cpp').has_header('linux/peci-ioctl.h')
+    src_inc += ['../../include']
+endif
+
+executable(
+    'intelcpusensor',
+    'IntelCPUSensorMain.cpp',
+    'IntelCPUSensor.cpp',
+    dependencies: [
+        default_deps,
+        gpiodcxx,
+        thresholds_dep,
+        utils_dep,
+        peci_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/ChassisIntrusionSensor.cpp b/src/intrusion/ChassisIntrusionSensor.cpp
similarity index 100%
rename from src/ChassisIntrusionSensor.cpp
rename to src/intrusion/ChassisIntrusionSensor.cpp
diff --git a/src/ChassisIntrusionSensor.hpp b/src/intrusion/ChassisIntrusionSensor.hpp
similarity index 100%
rename from src/ChassisIntrusionSensor.hpp
rename to src/intrusion/ChassisIntrusionSensor.hpp
diff --git a/src/IntrusionSensorMain.cpp b/src/intrusion/IntrusionSensorMain.cpp
similarity index 100%
rename from src/IntrusionSensorMain.cpp
rename to src/intrusion/IntrusionSensorMain.cpp
diff --git a/src/intrusion/meson.build b/src/intrusion/meson.build
new file mode 100644
index 0000000..f88d2c9
--- /dev/null
+++ b/src/intrusion/meson.build
@@ -0,0 +1,15 @@
+src_inc = include_directories('..')
+
+executable(
+    'intrusionsensor',
+    'ChassisIntrusionSensor.cpp',
+    'IntrusionSensorMain.cpp',
+    dependencies: [
+        default_deps,
+        gpiodcxx,
+        i2c,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/IpmbSDRSensor.cpp b/src/ipmb/IpmbSDRSensor.cpp
similarity index 100%
rename from src/IpmbSDRSensor.cpp
rename to src/ipmb/IpmbSDRSensor.cpp
diff --git a/src/IpmbSDRSensor.hpp b/src/ipmb/IpmbSDRSensor.hpp
similarity index 100%
rename from src/IpmbSDRSensor.hpp
rename to src/ipmb/IpmbSDRSensor.hpp
diff --git a/src/IpmbSensor.cpp b/src/ipmb/IpmbSensor.cpp
similarity index 100%
rename from src/IpmbSensor.cpp
rename to src/ipmb/IpmbSensor.cpp
diff --git a/src/IpmbSensor.hpp b/src/ipmb/IpmbSensor.hpp
similarity index 100%
rename from src/IpmbSensor.hpp
rename to src/ipmb/IpmbSensor.hpp
diff --git a/src/IpmbSensorMain.cpp b/src/ipmb/IpmbSensorMain.cpp
similarity index 100%
rename from src/IpmbSensorMain.cpp
rename to src/ipmb/IpmbSensorMain.cpp
diff --git a/src/ipmb/meson.build b/src/ipmb/meson.build
new file mode 100644
index 0000000..d724b80
--- /dev/null
+++ b/src/ipmb/meson.build
@@ -0,0 +1,15 @@
+src_inc = include_directories('..')
+
+executable(
+    'ipmbsensor',
+    'IpmbSensorMain.cpp',
+    'IpmbSensor.cpp',
+    'IpmbSDRSensor.cpp',
+    dependencies: [
+        default_deps,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/MCUTempSensor.cpp b/src/mcu/MCUTempSensor.cpp
similarity index 100%
rename from src/MCUTempSensor.cpp
rename to src/mcu/MCUTempSensor.cpp
diff --git a/src/MCUTempSensor.hpp b/src/mcu/MCUTempSensor.hpp
similarity index 100%
rename from src/MCUTempSensor.hpp
rename to src/mcu/MCUTempSensor.hpp
diff --git a/src/mcu/meson.build b/src/mcu/meson.build
new file mode 100644
index 0000000..a817e77
--- /dev/null
+++ b/src/mcu/meson.build
@@ -0,0 +1,14 @@
+src_inc = include_directories('..')
+
+executable(
+    'mcutempsensor',
+    'MCUTempSensor.cpp',
+    dependencies: [
+        default_deps,
+        i2c,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/meson.build b/src/meson.build
index 12f8ee5..f619572 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -70,170 +70,49 @@
 
 if get_option('intel-cpu').allowed()
     peci_dep = dependency('libpeci', required: true)
+    subdir('intel-cpu')
 endif
 
 if get_option('adc').allowed()
-    executable(
-        'adcsensor',
-        'ADCSensor.cpp',
-        'ADCSensorMain.cpp',
-        dependencies: [
-            default_deps,
-            gpiodcxx,
-            thresholds_dep,
-            utils_dep,
-        ],
-        install: true,
-    )
-endif
-
-if get_option('intel-cpu').allowed()
-    executable(
-        'intelcpusensor',
-        'IntelCPUSensorMain.cpp',
-        'IntelCPUSensor.cpp',
-        dependencies: [
-            default_deps,
-            gpiodcxx,
-            thresholds_dep,
-            utils_dep,
-            peci_dep,
-        ],
-        include_directories: peci_incdirs,
-        install: true,
-    )
+    subdir('adc')
 endif
 
 if get_option('exit-air').allowed()
-    executable(
-        'exitairtempsensor',
-        'ExitAirTempSensor.cpp',
-        dependencies: [
-            default_deps,
-            thresholds_dep,
-            utils_dep,
-        ],
-        install: true,
-    )
+    subdir('exit-air')
 endif
 
 if get_option('fan').allowed()
-    executable(
-        'fansensor',
-        'FanMain.cpp',
-        'PresenceGpio.cpp',
-        'TachSensor.cpp',
-        'PwmSensor.cpp',
-        dependencies: [
-            default_deps,
-            gpiodcxx,
-            thresholds_dep,
-            utils_dep,
-        ],
-        install: true,
-    )
+    subdir('fan')
 endif
 
 if get_option('hwmon-temp').allowed()
-    executable(
-        'hwmontempsensor',
-        'HwmonTempMain.cpp',
-        'HwmonTempSensor.cpp',
-        dependencies: [
-            default_deps,
-            devicemgmt_dep,
-            thresholds_dep,
-            utils_dep,
-        ],
-        install: true,
-    )
+    subdir('hwmon-temp')
 endif
 
 if get_option('intrusion').allowed()
-    executable(
-        'intrusionsensor',
-        'ChassisIntrusionSensor.cpp',
-        'IntrusionSensorMain.cpp',
-        dependencies: [
-            default_deps,
-            gpiodcxx,
-            i2c,
-            utils_dep,
-        ],
-        install: true,
-    )
+    subdir('intrusion')
 endif
 
 if get_option('ipmb').allowed()
-    executable(
-        'ipmbsensor',
-        'IpmbSensorMain.cpp',
-        'IpmbSensor.cpp',
-        'IpmbSDRSensor.cpp',
-        dependencies: [
-            default_deps,
-            thresholds_dep,
-            utils_dep,
-        ],
-        install: true,
-    )
+    subdir('ipmb')
 endif
 
 if get_option('mcu').allowed()
-    executable(
-        'mcutempsensor',
-        'MCUTempSensor.cpp',
-        dependencies: [
-            default_deps,
-            i2c,
-            thresholds_dep,
-            utils_dep,
-        ],
-        install: true,
-    )
+    subdir('mcu')
 endif
 
 if get_option('nvme').allowed()
-    nvme_srcs = files('NVMeSensor.cpp', 'NVMeSensorMain.cpp')
-    nvme_srcs += files('NVMeBasicContext.cpp')
-
-    nvme_deps = [default_deps, i2c, thresholds_dep, utils_dep, threads]
-
-    executable(
-        'nvmesensor',
-        sources: nvme_srcs,
-        dependencies: nvme_deps,
-        install: true,
-    )
+    subdir('nvme')
 endif
 
 if get_option('psu').allowed()
-    executable(
-        'psusensor',
-        'PSUEvent.cpp',
-        'PSUSensor.cpp',
-        'PSUSensorMain.cpp',
-        dependencies: [
-            default_deps,
-            devicemgmt_dep,
-            pwmsensor_dep,
-            thresholds_dep,
-            utils_dep,
-        ],
-        install: true,
-    )
+    subdir('psu')
 endif
 
 if get_option('external').allowed()
-    executable(
-        'externalsensor',
-        'ExternalSensor.cpp',
-        'ExternalSensorMain.cpp',
-        dependencies: [
-            default_deps,
-            thresholds_dep,
-            utils_dep,
-        ],
-        install: true,
-    )
+    subdir('external')
+endif
+
+if get_option('tests').allowed()
+    subdir('tests')
 endif
diff --git a/src/NVMeBasicContext.cpp b/src/nvme/NVMeBasicContext.cpp
similarity index 100%
rename from src/NVMeBasicContext.cpp
rename to src/nvme/NVMeBasicContext.cpp
diff --git a/src/NVMeBasicContext.hpp b/src/nvme/NVMeBasicContext.hpp
similarity index 100%
rename from src/NVMeBasicContext.hpp
rename to src/nvme/NVMeBasicContext.hpp
diff --git a/src/NVMeContext.hpp b/src/nvme/NVMeContext.hpp
similarity index 100%
rename from src/NVMeContext.hpp
rename to src/nvme/NVMeContext.hpp
diff --git a/src/NVMeSensor.cpp b/src/nvme/NVMeSensor.cpp
similarity index 100%
rename from src/NVMeSensor.cpp
rename to src/nvme/NVMeSensor.cpp
diff --git a/src/NVMeSensor.hpp b/src/nvme/NVMeSensor.hpp
similarity index 100%
rename from src/NVMeSensor.hpp
rename to src/nvme/NVMeSensor.hpp
diff --git a/src/NVMeSensorMain.cpp b/src/nvme/NVMeSensorMain.cpp
similarity index 100%
rename from src/NVMeSensorMain.cpp
rename to src/nvme/NVMeSensorMain.cpp
diff --git a/src/nvme/meson.build b/src/nvme/meson.build
new file mode 100644
index 0000000..441d3e7
--- /dev/null
+++ b/src/nvme/meson.build
@@ -0,0 +1,13 @@
+nvme_srcs = files('NVMeSensor.cpp', 'NVMeSensorMain.cpp')
+nvme_srcs += files('NVMeBasicContext.cpp')
+
+nvme_deps = [default_deps, i2c, thresholds_dep, utils_dep, threads]
+src_inc = include_directories('..')
+
+executable(
+    'nvmesensor',
+    sources: nvme_srcs,
+    dependencies: nvme_deps,
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/PSUEvent.cpp b/src/psu/PSUEvent.cpp
similarity index 100%
rename from src/PSUEvent.cpp
rename to src/psu/PSUEvent.cpp
diff --git a/src/PSUEvent.hpp b/src/psu/PSUEvent.hpp
similarity index 100%
rename from src/PSUEvent.hpp
rename to src/psu/PSUEvent.hpp
diff --git a/src/PSUSensor.cpp b/src/psu/PSUSensor.cpp
similarity index 100%
rename from src/PSUSensor.cpp
rename to src/psu/PSUSensor.cpp
diff --git a/src/PSUSensor.hpp b/src/psu/PSUSensor.hpp
similarity index 100%
rename from src/PSUSensor.hpp
rename to src/psu/PSUSensor.hpp
diff --git a/src/PSUSensorMain.cpp b/src/psu/PSUSensorMain.cpp
similarity index 100%
rename from src/PSUSensorMain.cpp
rename to src/psu/PSUSensorMain.cpp
diff --git a/src/psu/meson.build b/src/psu/meson.build
new file mode 100644
index 0000000..8f8df66
--- /dev/null
+++ b/src/psu/meson.build
@@ -0,0 +1,17 @@
+src_inc = include_directories('..')
+
+executable(
+    'psusensor',
+    'PSUEvent.cpp',
+    'PSUSensor.cpp',
+    'PSUSensorMain.cpp',
+    dependencies: [
+        default_deps,
+        devicemgmt_dep,
+        pwmsensor_dep,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file
diff --git a/src/tests/meson.build b/src/tests/meson.build
new file mode 100644
index 0000000..0415e96
--- /dev/null
+++ b/src/tests/meson.build
@@ -0,0 +1,58 @@
+gtest_dep = dependency('gtest', main: true, disabler: true, required: false)
+gmock_dep = dependency('gmock', disabler: true, required: false)
+if not gtest_dep.found() or not gmock_dep.found()
+    gtest_proj = import('cmake').subproject('googletest', required: false)
+    if gtest_proj.found()
+        gtest_dep = declare_dependency(
+            dependencies: [
+                dependency('threads'),
+                gtest_proj.dependency('gtest'),
+                gtest_proj.dependency('gtest_main'),
+            ],
+        )
+        gmock_dep = gtest_proj.dependency('gmock')
+    else
+        assert(
+            not get_option('tests').allowed(),
+            'Googletest is required if tests are enabled',
+        )
+    endif
+endif
+
+ut_deps_list = [
+    gtest_dep,
+]
+
+ut_deps_list += default_deps
+src_inc = include_directories('..')
+
+test(
+    'test_utils',
+    executable(
+        'test_utils',
+        'test_Utils.cpp',
+        '../Utils.cpp',
+        dependencies: ut_deps_list,
+        implicit_include_directories: false,
+        include_directories: src_inc,
+    ),
+)
+
+test(
+    'test_ipmb',
+    executable(
+        'test_ipmb',
+        '../ipmb/IpmbSensor.cpp',
+        '../Utils.cpp',
+        '../ipmb/IpmbSDRSensor.cpp',
+        'test_IpmbSensor.cpp',
+        dependencies: ut_deps_list,
+        link_with: [
+            utils_a,
+            thresholds_a,
+            devicemgmt_a
+        ],
+        implicit_include_directories: false,
+        include_directories: src_inc,
+    ),
+)
diff --git a/src/tests/test_IpmbSensor.cpp b/src/tests/test_IpmbSensor.cpp
new file mode 100644
index 0000000..e2b3a4e
--- /dev/null
+++ b/src/tests/test_IpmbSensor.cpp
@@ -0,0 +1,291 @@
+#include "ipmb/IpmbSensor.hpp"
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace
+{
+
+TEST(IPMBSensor, Byte0)
+{
+    std::vector<uint8_t> data;
+    data.push_back(42);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::byte0, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 42.0);
+}
+
+TEST(IPMBSensor, NineBitValidPositive)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x2a);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 42.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, NineBitValidNegative)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x9c);
+    data.push_back(0x01);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, -100.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, NineBitMin)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x01);
+    data.push_back(0x01);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, -255.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+// The Altra Family SoC BMC Interface Specification says the maximum 9-bit value
+// is 256, but that can't be represented in 9 bits, so test the max as 255.
+TEST(IPMBSensor, NineBitMax)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 255.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, NineBitTooShort)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, NineBitTooLong)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, NineBitInvalid)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, TenBitValid1)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x08);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 8.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, TenBitValid2)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x30);
+    data.push_back(0x02);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                           responseValue, errCount));
+
+    EXPECT_EQ(responseValue, 560.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, TenBitMin)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                           responseValue, errCount));
+
+    EXPECT_EQ(responseValue, 0.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, TenBitValidMax)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0x03);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                           responseValue, errCount));
+
+    EXPECT_EQ(responseValue, 1023.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, TenBitTooShort)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, TenBitTooLong)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, TenBitInvalid)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, FifteenBitValid1)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xda);
+    data.push_back(0x02);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 0.730);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, FifteenBitMin)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 0.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, FifteenBitMax)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0x7f);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 32.767);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, FifteenBitTooShort)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, FifteenBitTooLong)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, FifteenBitInvalid)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+} // namespace
diff --git a/src/tests/test_Utils.cpp b/src/tests/test_Utils.cpp
new file mode 100644
index 0000000..8192475
--- /dev/null
+++ b/src/tests/test_Utils.cpp
@@ -0,0 +1,244 @@
+#include "Utils.hpp"
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <new>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace fs = std::filesystem;
+class TestUtils : public testing::Test
+{
+  public:
+    std::string testDir;
+    fs::path hwmonDir;
+    fs::path peciDir;
+    TestUtils()
+    {
+        // Create test environment
+        auto dir = std::to_array("./testDirXXXXXX");
+        testDir = mkdtemp(dir.data());
+
+        if (testDir.empty())
+        {
+            throw std::bad_alloc();
+        }
+        hwmonDir = fs::path(testDir) / "hwmon";
+        fs::create_directory(hwmonDir);
+        auto hwmon10 = hwmonDir / "hwmon10";
+        fs::create_directory(hwmonDir / "hwmon10");
+        {
+            std::ofstream temp1Input{hwmon10 / "temp1_input"};
+            std::ofstream temp1Min{hwmon10 / "temp1_min"};
+            std::ofstream temp1Max{hwmon10 / "temp1_max"};
+            std::ofstream temp2Input{hwmon10 / "temp2_input"};
+        }
+        createPECIDir();
+    }
+
+    ~TestUtils() override
+    {
+        fs::remove_all(testDir);
+    }
+
+    TestUtils(const TestUtils&) = delete;
+    TestUtils(TestUtils&&) = delete;
+    TestUtils& operator=(const TestUtils&) = delete;
+    TestUtils& operator=(TestUtils&&) = delete;
+
+    void createPECIDir()
+    {
+        peciDir = fs::path(testDir) / "peci";
+        auto peci0 = peciDir /
+                     "peci-0/device/0-30/peci-cputemp.0/hwmon/hwmon25";
+        fs::create_directories(peci0);
+        {
+            std::ofstream temp0Input{peci0 / "temp0_input"};
+            std::ofstream temp1Input{peci0 / "temp1_input"};
+            std::ofstream temp2Input{peci0 / "temp2_input"};
+            std::ofstream name{peci0 / "name"};
+        }
+        auto devDir = peciDir / "peci-0/peci_dev/peci-0";
+        fs::create_directories(devDir);
+        fs::create_directory_symlink("../../../peci-0", devDir / "device");
+        fs::create_directory_symlink("device/0-30", peciDir / "peci-0/0-30");
+
+        // Let's keep this for debugging purpose
+        for (auto p = fs::recursive_directory_iterator(
+                 peciDir, fs::directory_options::follow_directory_symlink);
+             p != fs::recursive_directory_iterator(); ++p)
+        {
+            std::string path = p->path().string();
+            std::cerr << path << "\n";
+            if (p.depth() >= 6)
+            {
+                p.disable_recursion_pending();
+            }
+        }
+    }
+};
+
+TEST_F(TestUtils, findFiles_non_exist)
+{
+    std::vector<fs::path> foundPaths;
+    auto ret = findFiles("non-exist", "", foundPaths);
+
+    EXPECT_FALSE(ret);
+    EXPECT_TRUE(foundPaths.empty());
+}
+
+TEST_F(TestUtils, findFiles_in_hwmon_no_match)
+{
+    std::vector<fs::path> foundPaths;
+    auto ret = findFiles(hwmonDir, R"(in\d+_input)", foundPaths);
+
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(foundPaths.size(), 0U);
+}
+
+TEST_F(TestUtils, findFiles_in_hwmon_match)
+{
+    std::vector<fs::path> foundPaths;
+    auto ret = findFiles(hwmonDir, R"(temp\d+_input)", foundPaths);
+
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(foundPaths.size(), 2U);
+}
+
+TEST_F(TestUtils, findFiles_in_peci_no_match)
+{
+    std::vector<fs::path> foundPaths;
+    auto ret =
+        findFiles(peciDir, R"(peci-\d+/\d+-.+/peci-.+/hwmon/hwmon\d+/aaa$)",
+                  foundPaths, 6);
+
+    EXPECT_TRUE(ret);
+    EXPECT_TRUE(foundPaths.empty());
+}
+
+TEST_F(TestUtils, findFiles_in_peci_match)
+{
+    std::vector<fs::path> foundPaths;
+    auto ret =
+        findFiles(peciDir, R"(peci-\d+/\d+-.+/peci-.+/hwmon/hwmon\d+/name$)",
+                  foundPaths, 6);
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(foundPaths.size(), 1U);
+
+    foundPaths.clear();
+
+    ret = findFiles(peciDir,
+                    R"(peci-\d+/\d+-.+/peci-.+/hwmon/hwmon\d+/temp\d+_input)",
+                    foundPaths, 6);
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(foundPaths.size(), 3U);
+}
+
+TEST_F(TestUtils, findFiles_hwmonPath_end_with_slash)
+{
+    std::string p = hwmonDir.string() + "/";
+    std::vector<fs::path> foundPaths;
+    auto ret = findFiles(p, R"(temp\d+_input)", foundPaths);
+
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(foundPaths.size(), 2U);
+}
+
+TEST_F(TestUtils, findFiles_peciPath_end_with_slash)
+{
+    std::string p = peciDir.string() + "/";
+    std::vector<fs::path> foundPaths;
+    auto ret =
+        findFiles(p, R"(peci-\d+/\d+-.+/peci-.+/hwmon/hwmon\d+/temp\d+_input)",
+                  foundPaths, 6);
+
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(foundPaths.size(), 3U);
+}
+
+TEST_F(TestUtils, findFiles_in_sub_peci_match)
+{
+    std::vector<fs::path> foundPaths;
+    auto ret =
+        findFiles(peciDir / "peci-0", R"(\d+-.+/peci-.+/hwmon/hwmon\d+/name$)",
+                  foundPaths, 5);
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(foundPaths.size(), 1U);
+
+    foundPaths.clear();
+
+    ret = findFiles(peciDir / "peci-0",
+                    R"(\d+-.+/peci-.+/hwmon/hwmon\d+/temp\d+_input)",
+                    foundPaths, 5);
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(foundPaths.size(), 3U);
+}
+
+TEST(GetDeviceBusAddrTest, DevNameInvalid)
+{
+    size_t bus = 0;
+    size_t addr = 0;
+    std::string devName;
+
+    auto ret = getDeviceBusAddr(devName, bus, addr);
+    EXPECT_FALSE(ret);
+
+    devName = "NoHyphen";
+    ret = getDeviceBusAddr(devName, bus, addr);
+    EXPECT_FALSE(ret);
+
+    devName = "pwm-fan";
+    ret = getDeviceBusAddr(devName, bus, addr);
+    EXPECT_FALSE(ret);
+}
+
+TEST(GetDeviceBusAddrTest, BusInvalid)
+{
+    size_t bus = 0;
+    size_t addr = 0;
+    std::string devName = "FF-00FF";
+
+    auto ret = getDeviceBusAddr(devName, bus, addr);
+    EXPECT_FALSE(ret);
+}
+
+TEST(GetDeviceBusAddrTest, AddrInvalid)
+{
+    size_t bus = 0;
+    size_t addr = 0;
+    std::string devName = "12-fan";
+
+    auto ret = getDeviceBusAddr(devName, bus, addr);
+    EXPECT_FALSE(ret);
+}
+
+TEST(GetDeviceBusAddrTest, I3CBusAddrValid)
+{
+    uint64_t bus = 0;
+    uint64_t provisionedId = 0;
+    std::string devName = "0-22400000001";
+
+    auto ret = getDeviceBusAddr(devName, bus, provisionedId);
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(bus, 0);
+    EXPECT_EQ(provisionedId, 0x22400000001);
+}
+
+TEST(GetDeviceBusAddrTest, AllValid)
+{
+    size_t bus = 0;
+    size_t addr = 0;
+    std::string devName = "12-00af";
+
+    auto ret = getDeviceBusAddr(devName, bus, addr);
+    EXPECT_TRUE(ret);
+    EXPECT_EQ(bus, 12);
+    EXPECT_EQ(addr, 0xaf);
+}