phosphor-fan:  Testcases for Timer class

Change-Id: Id8b752cc6e494dcf76d9668553292a65a3fd19d6
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/test/timertest.cpp b/test/timertest.cpp
new file mode 100644
index 0000000..40b2ddf
--- /dev/null
+++ b/test/timertest.cpp
@@ -0,0 +1,359 @@
+/**
+ * Copyright © 2017 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <iostream>
+#include <chrono>
+#include <gtest/gtest.h>
+#include "timer.hpp"
+
+/**
+ * Testcases for the Timer class
+ */
+
+using namespace phosphor::fan::util;
+using namespace std::chrono;
+
+void EventDeleter(sd_event* events)
+{
+    sd_event_unref(events);
+}
+
+using EventPtr = std::shared_ptr<sd_event>;
+
+/**
+ * Class to ensure sd_events are correctly
+ * setup and destroyed.
+ */
+class TimerTest : public ::testing::Test
+{
+    public:
+        // systemd event handler
+        EventPtr events;
+
+        // Need this so that events can be initialized.
+        int rc;
+
+        // Gets called as part of each TEST_F construction
+        TimerTest()
+        {
+            sd_event* event = nullptr;
+            auto rc = sd_event_default(&event);
+            EXPECT_GE(rc, 0);
+
+            events.reset(event, EventDeleter);
+        }
+};
+
+/**
+ * Helper class to hande tracking timer expirations
+ * via callback functions.
+ */
+class CallbackTester
+{
+    public:
+
+        CallbackTester() {}
+
+        size_t getCount()
+        {
+            return _count;
+        }
+
+        void callbackFunction()
+        {
+            _count++;
+            _gotCallback = true;
+        }
+
+        bool gotCallback()
+        {
+            return _gotCallback;
+        }
+
+    private:
+        bool _gotCallback = false;
+        size_t _count = 0;
+};
+
+
+/**
+ * Helper class that more closely mimics real usage,
+ * which is another class containing a timer and using
+ * one of its member functions as the callback.
+ */
+class CallbackTesterWithTimer : public CallbackTester
+{
+    public:
+        CallbackTesterWithTimer(EventPtr events) :
+            _timer(events,
+                   std::bind(&CallbackTesterWithTimer::callbackFunction,
+                             this))
+        {
+        }
+
+        void callbackFunction()
+        {
+            //restart the timer once from the callback
+            if (!_restarted)
+            {
+                _restarted = true;
+                auto time = duration_cast<microseconds>(seconds(1));
+                _timer.start(time, Timer::TimerType::oneshot);
+            }
+
+            CallbackTester::callbackFunction();
+        }
+
+        Timer& getTimer()
+        {
+            return _timer;
+        }
+
+        inline bool restarted() const
+        {
+            return _restarted;
+        }
+
+    private:
+
+        Timer _timer;
+        bool _restarted = false;
+};
+
+
+/**
+ * Test that a callback will occur after 2 seconds.
+ */
+TEST_F(TimerTest, timerExpiresAfter2seconds)
+{
+    CallbackTester tester;
+
+    Timer timer(events,
+                std::bind(&CallbackTester::callbackFunction, &tester));
+
+
+    auto time = duration_cast<microseconds>(seconds(2));
+
+    EXPECT_EQ(false, timer.running());
+
+    timer.start(time, Timer::TimerType::oneshot);
+    EXPECT_EQ(false, tester.gotCallback());
+    EXPECT_EQ(true, timer.running());
+
+    int count = 0;
+    auto sleepTime = duration_cast<microseconds>(seconds(1));
+
+    //Wait for 2 1s timeouts
+    while (count < 2)
+    {
+        // Returns 0 on timeout and positive number on dispatch
+        if (sd_event_run(events.get(), sleepTime.count()) == 0)
+        {
+            count++;
+        }
+    }
+
+    EXPECT_EQ(true, tester.gotCallback());
+    EXPECT_EQ(1, tester.getCount());
+    EXPECT_EQ(false, timer.running());
+}
+
+/**
+ * Test that a timer can be restarted.
+ */
+TEST_F(TimerTest, timerRestart)
+{
+    CallbackTester tester;
+
+    Timer timer(events,
+                std::bind(&CallbackTester::callbackFunction, &tester));
+
+
+    auto time = duration_cast<microseconds>(seconds(2));
+    timer.start(time, Timer::TimerType::oneshot);
+
+    //wait for a second
+    auto sleepTime = duration_cast<microseconds>(seconds(1));
+    auto rc = sd_event_run(events.get(), sleepTime.count());
+
+    //expect the timeout, not the dispatch
+    //and the timer should still be running
+    EXPECT_EQ(0, rc);
+    EXPECT_EQ(true, timer.running());
+
+    //Restart it
+    timer.start(time, Timer::TimerType::oneshot);
+
+    //Wait just 1s, make sure not done
+    rc = sd_event_run(events.get(), sleepTime.count());
+    EXPECT_EQ(0, rc);
+    EXPECT_EQ(true, timer.running());
+    EXPECT_EQ(false, tester.gotCallback());
+
+    //Wait 1 more second, this time expecting a dispatch
+    int count = 0;
+    while (count < 1)
+    {
+        // Returns 0 on timeout and positive number on dispatch
+        if (sd_event_run(events.get(), sleepTime.count()) == 0)
+        {
+            count++;
+        }
+    }
+
+    EXPECT_EQ(true, tester.gotCallback());
+    EXPECT_EQ(1, tester.getCount());
+    EXPECT_EQ(false, timer.running());
+}
+
+
+/**
+ * Test that a timer can be stopped.
+ */
+TEST_F(TimerTest, timerStop)
+{
+    CallbackTester tester;
+
+    Timer timer(events,
+                std::bind(&CallbackTester::callbackFunction, &tester));
+
+
+    auto time = duration_cast<microseconds>(seconds(2));
+    timer.start(time, Timer::TimerType::oneshot);
+
+    auto sleepTime = duration_cast<microseconds>(seconds(1));
+
+    //wait 1s
+    auto rc = sd_event_run(events.get(), sleepTime.count());
+
+    //expect the timeout, not the dispatch
+    EXPECT_EQ(rc, 0);
+    EXPECT_EQ(true, timer.running());
+
+    timer.stop();
+
+    EXPECT_EQ(false, timer.running());
+    EXPECT_EQ(false, tester.gotCallback());
+
+    //Wait another 2s, make sure no callbacks happened
+    sleepTime = duration_cast<microseconds>(seconds(2));
+    rc = sd_event_run(events.get(), sleepTime.count());
+
+    EXPECT_EQ(rc, 0);
+    EXPECT_EQ(false, timer.running());
+    EXPECT_EQ(false, tester.gotCallback());
+}
+
+
+/**
+ * Test that the timer can be restarted from within
+ * a callback function.
+ */
+TEST_F(TimerTest, timerRestartFromCallback)
+{
+    CallbackTesterWithTimer tester(events);
+
+    auto& timer = tester.getTimer();
+
+    auto time = duration_cast<microseconds>(seconds(2));
+    timer.start(time, Timer::TimerType::oneshot);
+
+    //after running for 2 seconds, the timer will get restarted
+    //for another 1s
+
+    int count = 0;
+    auto sleepTime = duration_cast<microseconds>(seconds(1));
+    while (count < 3)
+    {
+        // Returns 0 on timeout and positive number on dispatch
+        if (sd_event_run(events.get(), sleepTime.count()) == 0)
+        {
+            count++;
+        }
+    }
+
+    EXPECT_EQ(false, timer.running());
+    EXPECT_EQ(true, tester.gotCallback());
+    EXPECT_EQ(2, tester.getCount()); //2 callbacks
+    EXPECT_EQ(true, tester.restarted());
+}
+
+/**
+ * This shows what happens when the timer expires but
+ * sd_event_run never got called.
+ */
+TEST_F(TimerTest, timerNoEventRun)
+{
+    CallbackTester tester;
+
+    Timer timer(events,
+                std::bind(&CallbackTester::callbackFunction, &tester));
+
+
+    auto time = duration_cast<microseconds>(milliseconds(500));
+
+    timer.start(time, Timer::TimerType::oneshot);
+
+    sleep(1);
+
+    //The timer should have expired, but with no event processing
+    //it will still think it's running.
+
+    EXPECT_EQ(true, timer.running());
+    EXPECT_EQ(false, tester.gotCallback());
+
+    //Now process an event
+    auto sleepTime = duration_cast<microseconds>(milliseconds(5));
+    auto rc = sd_event_run(events.get(), sleepTime.count());
+
+    EXPECT_GT(rc, 0);
+    EXPECT_EQ(false, timer.running());
+    EXPECT_EQ(true, tester.gotCallback());
+}
+
+
+/**
+ * Tests that a timer in repeating mode will keep calling
+ * the callback.
+ */
+TEST_F(TimerTest, RepeatingTimer)
+{
+    CallbackTester tester;
+
+    Timer timer(events,
+                std::bind(&CallbackTester::callbackFunction, &tester));
+
+    auto time = duration_cast<microseconds>(seconds(1));
+    timer.start(time, Timer::TimerType::repeating);
+
+    int count = 0;
+    auto sleepTime = duration_cast<microseconds>(milliseconds(500));
+
+    while (count < 5)
+    {
+        if (sd_event_run(events.get(), sleepTime.count()) == 0)
+        {
+            count++;
+        }
+    }
+
+    EXPECT_EQ(true, timer.running());
+    EXPECT_EQ(true, tester.gotCallback());
+    EXPECT_EQ(4, tester.getCount());
+
+    timer.stop();
+
+    EXPECT_EQ(false, timer.running());
+}