test: physical: Introduce LED mocks

This removes the dependency on touching the filesystem entirely. All
methods are now mocked into an ignored state when called by the NiceMock
template class. The filesystem is only touched by the SysfsLed tests,
though we still need to provide a temporary path to its constructor in
the decended mock class to ensure we're isolated if something does
manage to get written.

Change-Id: I3955a6e0fb5c3c42887da847239d381ef151fa3e
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/test/physical.cpp b/test/physical.cpp
index 4316b16..1b546ed 100644
--- a/test/physical.cpp
+++ b/test/physical.cpp
@@ -1,26 +1,89 @@
 #include "physical.hpp"
 
+#include <sys/param.h>
+
 #include <sdbusplus/bus.hpp>
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 constexpr auto LED_OBJ = "/foo/bar/led";
-constexpr auto LED_SYSFS = "/sys/class/leds/test";
 
 using Action = sdbusplus::xyz::openbmc_project::Led::server::Physical::Action;
 namespace fs = std::experimental::filesystem;
 
+fs::path create_sandbox()
+{
+    /* If your tests need to touch the filesystem, always use mkdtemp() or
+     * mkstemp() for creating directories and files. Tests can be run in
+     * parallel with `make -j`, and if use the same path in multiple tests they
+     * will stomp on eachother and likely fail.
+     */
+    static constexpr auto tmplt = "/tmp/MockLed.XXXXXX";
+    char buffer[MAXPATHLEN] = {0};
+
+    strncpy(buffer, tmplt, sizeof(buffer) - 1);
+    auto dir = mkdtemp(buffer);
+    if (!dir)
+    {
+        throw std::system_error(errno, std::system_category());
+    }
+
+    /* We want to limit behaviours to mocks, and if methods aren't mocked they
+     * may fall back to their base class implementation. Stop read/write to
+     * directory to prevent streams from creating files.
+     */
+    if (chmod(dir, S_IXUSR | S_IXGRP) == -1)
+    {
+        throw std::system_error(errno, std::system_category());
+    }
+
+    return fs::path(dir);
+}
+
+class MockLed : public phosphor::led::SysfsLed
+{
+  public:
+    /* Use a no-args ctor here to avoid headaches with {Nice,Strict}Mock */
+    MockLed() : SysfsLed(create_sandbox())
+    {
+    }
+
+    virtual ~MockLed()
+    {
+        chmod(root.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
+        fs::remove_all(root);
+    }
+
+    MOCK_METHOD0(getBrightness, unsigned long());
+    MOCK_METHOD1(setBrightness, void(unsigned long value));
+    MOCK_METHOD0(getMaxBrightness, unsigned long());
+    MOCK_METHOD0(getTrigger, std::string());
+    MOCK_METHOD1(setTrigger, void(const std::string& trigger));
+    MOCK_METHOD0(getDelayOn, unsigned long());
+    MOCK_METHOD1(setDelayOn, void(unsigned long ms));
+    MOCK_METHOD0(getDelayOff, unsigned long());
+    MOCK_METHOD1(setDelayOff, void(unsigned long ms));
+};
+
+using ::testing::NiceMock;
+using ::testing::Return;
+using ::testing::Throw;
+
 TEST(Physical, ctor)
 {
     sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
-    phosphor::led::SysfsLed led{fs::path(LED_SYSFS)};
+    /* NiceMock ignores calls to methods with no expectations defined */
+    NiceMock<MockLed> led;
+    ON_CALL(led, getTrigger()).WillByDefault(Return("none"));
     phosphor::led::Physical phy(bus, LED_OBJ, led);
 }
 
 TEST(Physical, off)
 {
     sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
-    phosphor::led::SysfsLed led{fs::path(LED_SYSFS)};
+    NiceMock<MockLed> led;
+    ON_CALL(led, getTrigger()).WillByDefault(Return("none"));
     phosphor::led::Physical phy(bus, LED_OBJ, led);
     phy.state(Action::Off);
 }
@@ -28,7 +91,8 @@
 TEST(Physical, on)
 {
     sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
-    phosphor::led::SysfsLed led{fs::path(LED_SYSFS)};
+    NiceMock<MockLed> led;
+    ON_CALL(led, getTrigger()).WillByDefault(Return("none"));
     phosphor::led::Physical phy(bus, LED_OBJ, led);
     phy.state(Action::On);
 }
@@ -36,7 +100,8 @@
 TEST(Physical, blink)
 {
     sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
-    phosphor::led::SysfsLed led{fs::path(LED_SYSFS)};
+    NiceMock<MockLed> led;
+    ON_CALL(led, getTrigger()).WillByDefault(Return("none"));
     phosphor::led::Physical phy(bus, LED_OBJ, led);
     phy.state(Action::Blink);
 }