tests: test FspYhPsu in separate test file

The test takes a long time (~20s). By separating it into separate file
we get better parallelization when running with ctest.

Change-Id: I6b7dfb0bebc5563038e6a6dbd0ea12050fa29f36
diff --git a/tests/hardware_fspyhpsu.cpp b/tests/hardware_fspyhpsu.cpp
new file mode 100644
index 0000000..fbf36c4
--- /dev/null
+++ b/tests/hardware_fspyhpsu.cpp
@@ -0,0 +1,199 @@
+#include "trompeloeil_doctest.h"
+#include <fstream>
+#include "fs-helpers/utils.h"
+#include "ietf-hardware/FspYhPsu.h"
+#include "pretty_printers.h"
+#include "test_log_setup.h"
+#include "test_sysrepo_helpers.h"
+#include "tests/configure.cmake.h"
+
+using namespace std::literals;
+
+class FakeI2C : public velia::ietf_hardware::TransientI2C {
+public:
+    FakeI2C(const std::string& fakeHwmonRoot)
+        : TransientI2C({}, {}, {})
+        , m_fakeHwmonRoot(fakeHwmonRoot)
+    {
+    }
+
+    MAKE_CONST_MOCK0(isPresent, bool(), override);
+    MAKE_CONST_MOCK0(bind_mock, void());
+    MAKE_CONST_MOCK0(unbind_mock, void());
+
+    void removeHwmonFile(const std::string& name) const
+    {
+        std::filesystem::remove(m_fakeHwmonRoot / ("hwmon" + std::to_string(m_hwmonNo)) / name);
+    }
+
+    void bind() const override
+    {
+        bind_mock();
+        removeDirectoryTreeIfExists(m_fakeHwmonRoot);
+        std::filesystem::create_directory(m_fakeHwmonRoot);
+        std::filesystem::create_directory(m_fakeHwmonRoot / ("hwmon" + std::to_string(m_hwmonNo)));
+
+        for (const auto& filename : {"name", "temp1_input", "temp2_input", "curr1_input", "curr2_input", "curr3_input", "in1_input", "in2_input", "in3_input", "power1_input", "power2_input", "fan1_input"}) {
+            std::ofstream ofs(m_fakeHwmonRoot / ("hwmon" + std::to_string(m_hwmonNo)) / filename);
+            // I don't really care about the values here, I just need the HWMon class to think that the files exist.
+            ofs << 0 << "\n";
+        }
+    }
+    void unbind() const override
+    {
+        unbind_mock();
+        removeDirectoryTreeIfExists(m_fakeHwmonRoot);
+        m_hwmonNo++;
+    }
+
+private:
+    std::filesystem::path m_fakeHwmonRoot;
+    mutable std::atomic<int> m_hwmonNo = 1;
+};
+
+TEST_CASE("FspYhPsu")
+{
+    TEST_INIT_LOGS;
+    std::atomic<int> counter = 0;
+    const auto fakeHwmonRoot = CMAKE_CURRENT_BINARY_DIR + "/tests/psu"s;
+    removeDirectoryTreeIfExists(fakeHwmonRoot);
+    auto fakeI2c = std::make_shared<FakeI2C>(fakeHwmonRoot);
+    trompeloeil::sequence seq1;
+    std::shared_ptr<velia::ietf_hardware::FspYhPsu> psu;
+    std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
+
+    auto i2cPresence = [&counter] {
+        switch (counter) {
+        case 0:
+        case 2:
+        case 4:
+            return false;
+        case 1:
+        case 3:
+            return true;
+        }
+
+        REQUIRE(false);
+        __builtin_unreachable();
+    };
+
+    ALLOW_CALL(*fakeI2c, isPresent()).LR_RETURN(i2cPresence());
+    REQUIRE_CALL(*fakeI2c, bind_mock()).LR_WITH(counter == 1).IN_SEQUENCE(seq1);
+    REQUIRE_CALL(*fakeI2c, unbind_mock()).LR_WITH(counter == 2).IN_SEQUENCE(seq1);
+    REQUIRE_CALL(*fakeI2c, bind_mock()).LR_WITH(counter == 3).IN_SEQUENCE(seq1);
+    REQUIRE_CALL(*fakeI2c, unbind_mock()).LR_WITH(counter == 4).IN_SEQUENCE(seq1);
+
+    psu = std::make_shared<velia::ietf_hardware::FspYhPsu>(fakeHwmonRoot, "psu", fakeI2c);
+
+    for (auto i : {0, 1, 2, 3, 4}) {
+        std::this_thread::sleep_for(std::chrono::seconds(4));
+        velia::ietf_hardware::DataTree expected;
+
+        switch (i) {
+        case 0:
+            break;
+        case 1:
+            expected = {
+                {"/ietf-hardware:hardware/component[name='ne:psu']/class", "iana-hardware:power-supply"},
+                {"/ietf-hardware:hardware/component[name='ne:psu']/parent", "ne"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-12V']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-12V']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-12V']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-12V']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-12V']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-12V']/sensor-data/value-scale", "milli"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-12V']/sensor-data/value-type", "amperes"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-5Vsb']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-5Vsb']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-5Vsb']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-5Vsb']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-5Vsb']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-5Vsb']/sensor-data/value-scale", "milli"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-5Vsb']/sensor-data/value-type", "amperes"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-in']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-in']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-in']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-in']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-in']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-in']/sensor-data/value-scale", "milli"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:current-in']/sensor-data/value-type", "amperes"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan']/class", "iana-hardware:module"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1']/class", "iana-hardware:fan"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1']/parent", "ne:psu:fan"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1:rpm']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1:rpm']/parent", "ne:psu:fan:fan1"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1:rpm']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1:rpm']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1:rpm']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1:rpm']/sensor-data/value-scale", "units"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:fan:fan1:rpm']/sensor-data/value-type", "rpm"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-in']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-in']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-in']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-in']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-in']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-in']/sensor-data/value-scale", "micro"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-in']/sensor-data/value-type", "watts"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-out']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-out']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-out']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-out']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-out']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-out']/sensor-data/value-scale", "micro"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:power-out']/sensor-data/value-type", "watts"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-1']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-1']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-1']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-1']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-1']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-1']/sensor-data/value-scale", "milli"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-1']/sensor-data/value-type", "celsius"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-2']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-2']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-2']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-2']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-2']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-2']/sensor-data/value-scale", "milli"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:temperature-2']/sensor-data/value-type", "celsius"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-12V']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-12V']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-12V']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-12V']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-12V']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-12V']/sensor-data/value-scale", "milli"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-12V']/sensor-data/value-type", "volts-DC"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-5Vsb']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-5Vsb']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-5Vsb']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-5Vsb']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-5Vsb']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-5Vsb']/sensor-data/value-scale", "milli"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-5Vsb']/sensor-data/value-type", "volts-DC"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-in']/class", "iana-hardware:sensor"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-in']/parent", "ne:psu"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-in']/sensor-data/oper-status", "ok"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-in']/sensor-data/value", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-in']/sensor-data/value-precision", "0"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-in']/sensor-data/value-scale", "milli"},
+                {"/ietf-hardware:hardware/component[name='ne:psu:voltage-in']/sensor-data/value-type", "volts-AC"},
+            };
+            break;
+        case 2:
+            break;
+        case 3:
+            // Here I simulate read failure by a file from the hwmon directory. This happens when the user wants data from
+            // a PSU that's no longer there and the watcher thread didn't unbind it yet.
+            fakeI2c->removeHwmonFile("temp1_input");
+            break;
+        case 4:
+            break;
+        }
+
+        REQUIRE(psu->readValues() == expected);
+
+        counter++;
+    }
+
+    waitForCompletionAndBitMore(seq1);
+}