blob: 9d05e550988480f5451d90211d129b3a5fec98cc [file] [log] [blame]
/**
* 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 "event.hpp"
#include "timer.hpp"
/**
* Testcases for the Timer class
*/
using namespace phosphor::fan::util;
using namespace std::chrono;
/**
* Class to ensure sd_events are correctly
* setup and destroyed.
*/
class TimerTest : public ::testing::Test
{
public:
// systemd event handler
phosphor::fan::event::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);
}
};
/**
* 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(phosphor::fan::event::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());
}