--- /dev/null
+/*
+ Copyright (c) 2018-2019 Nokia.
+
+ 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 <type_traits>
+#include <memory>
+#include <boost/optional.hpp>
+#include <sys/timerfd.h>
+#include <gmock/gmock.h>
+#include "private/timerfd.hpp"
+#include "private/tst/systemmock.hpp"
+#include "private/tst/enginemock.hpp"
+
+using namespace shareddatalayer;
+using namespace shareddatalayer::tst;
+using namespace testing;
+
+namespace
+{
+ class TimerFDTest: public testing::Test
+ {
+ public:
+ const int tfd;
+ NiceMock<SystemMock> systemMock;
+ EngineMock engineMock;
+ std::unique_ptr<TimerFD> timerFD;
+ std::unique_ptr<Timer> timer1;
+ std::unique_ptr<Timer> timer2;
+ Engine::EventHandler savedEventHandler;
+
+ TimerFDTest(): tfd(123)
+ {
+ InSequence dummy;
+ EXPECT_CALL(systemMock, timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC))
+ .Times(1)
+ .WillOnce(Return(tfd));
+ EXPECT_CALL(engineMock, addMonitoredFD(Matcher<FileDescriptor&>(_), Engine::EVENT_IN, _))
+ .Times(1)
+ .WillOnce(Invoke([this] (FileDescriptor& fd, unsigned int, const Engine::EventHandler& eh)
+ {
+ EXPECT_EQ(tfd, static_cast<int>(fd));
+ savedEventHandler = eh;
+ }));
+ timerFD.reset(new TimerFD(systemMock, engineMock));
+
+ Mock::VerifyAndClear(&systemMock);
+ Mock::VerifyAndClear(&engineMock);
+
+ EXPECT_CALL(engineMock, armTimer(_, _, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([this] (Timer& timer, const Timer::Duration& duration, const Timer::Callback& cb)
+ {
+ timerFD.get()->arm(timer, duration, cb);
+ }));
+
+ EXPECT_CALL(engineMock, disarmTimer(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([this](const Timer& timer)
+ {
+ timerFD.get()->disarm(timer);
+ }));
+
+ ON_CALL(systemMock, time_since_epoch())
+ .WillByDefault(Return(std::chrono::steady_clock::duration(0)));
+
+ timer1.reset(new Timer(engineMock));
+ timer2.reset(new Timer(engineMock));
+ }
+
+ void expectSetTime(int seconds, int nanoseconds)
+ {
+ EXPECT_CALL(systemMock, timerfd_settime(tfd, TFD_TIMER_ABSTIME, NotNull(), nullptr))
+ .Times(1)
+ .WillOnce(Invoke([seconds, nanoseconds] (int, int, const itimerspec* new_value, itimerspec*)
+ {
+ EXPECT_EQ(0, new_value->it_interval.tv_sec);
+ EXPECT_EQ(0, new_value->it_interval.tv_nsec);
+ EXPECT_EQ(seconds, new_value->it_value.tv_sec);
+ EXPECT_EQ(nanoseconds, new_value->it_value.tv_nsec);
+ }));
+ }
+
+ void expectRead(const boost::optional<uint64_t>& count)
+ {
+ EXPECT_CALL(systemMock, read(tfd, NotNull(), sizeof(uint64_t)))
+ .Times(1)
+ .WillOnce(Invoke([count] (int, void* buf, size_t) -> ssize_t
+ {
+ if (count)
+ {
+ *static_cast<uint64_t *>(buf) = *count;
+ return sizeof(uint64_t);
+ }
+ return -1;
+ }));
+ }
+
+ void expectTimeSinceEpoch(const std::chrono::steady_clock::duration& ret)
+ {
+ EXPECT_CALL(systemMock, time_since_epoch())
+ .Times(1)
+ .WillOnce(Return(ret));
+ }
+
+ void arm(Timer& timer, int seconds, int nanoseconds, const std::string& param = std::string())
+ {
+ timer.arm(std::chrono::duration_cast<Timer::Duration>(std::chrono::seconds(seconds) + std::chrono::nanoseconds(nanoseconds)),
+ std::bind(&TimerFDTest::callback, this, param));
+ }
+
+ void arm(int seconds, int nanoseconds, const std::string& param = std::string())
+ {
+ arm(*timer1, seconds, nanoseconds, param);
+ }
+
+ void disarm(Timer& timer)
+ {
+ timer.disarm();
+ }
+
+ void disarm()
+ {
+ disarm(*timer1);
+ }
+
+ MOCK_METHOD1(callback, void(const std::string& param));
+ };
+}
+
+TEST_F(TimerFDTest, IsNotCopyableAndIsNotMovable)
+{
+ EXPECT_FALSE(std::is_copy_assignable<TimerFD>::value);
+ EXPECT_FALSE(std::is_move_assignable<TimerFD>::value);
+ EXPECT_FALSE(std::is_copy_constructible<TimerFD>::value);
+ EXPECT_FALSE(std::is_move_constructible<TimerFD>::value);
+}
+
+TEST_F(TimerFDTest, ArmingTheFirstTimerCallsSetTimeWithProperValues)
+{
+ expectSetTime(3, 4000);
+ arm(3, 4000);
+ Mock::VerifyAndClear(&systemMock);
+}
+
+TEST_F(TimerFDTest, ArmingAnotherTimerWithLongerTimeoutDoesntCallSetTime)
+{
+ expectSetTime(3, 0);
+ arm(*timer1, 3, 0);
+ arm(*timer2, 10, 0);
+ Mock::VerifyAndClear(&systemMock);
+}
+
+TEST_F(TimerFDTest, DisarminTheOnlyTimerCallsSetTimeWithZeroValues)
+{
+ arm(3, 0);
+ expectSetTime(0, 0);
+ disarm();
+}
+
+TEST_F(TimerFDTest, DisarminTheFirstTimerCallsSetTimeWithProperValues)
+{
+ arm(*timer1, 3, 0);
+ arm(*timer2, 4, 0);
+ expectSetTime(4, 0);
+ disarm(*timer1);
+ Mock::VerifyAndClear(&systemMock);
+}
+
+TEST_F(TimerFDTest, AfterExecutingTheFirstTimerSetTimeIsCalledWithProperValues)
+{
+ InSequence dummy;
+ arm(*timer1, 1, 0, "first");
+ arm(*timer2, 2, 0, "second");
+ expectRead(1);
+ EXPECT_CALL(*this, callback("first"))
+ .Times(1);
+ expectSetTime(2, 0);
+ savedEventHandler(Engine::EVENT_IN);
+ Mock::VerifyAndClear(&systemMock);
+}
+
+TEST_F(TimerFDTest, AfterExecutingTheLastTimerSetTimeIsCalledWithZeroValues)
+{
+ InSequence dummy;
+ arm(*timer1, 1, 0, "first");
+ expectRead(1);
+ EXPECT_CALL(*this, callback("first"))
+ .Times(1);
+ expectSetTime(0, 0);
+ savedEventHandler(Engine::EVENT_IN);
+ Mock::VerifyAndClear(&systemMock);
+}
+
+TEST_F(TimerFDTest, IfReadReturnsNegativeOnHandleEventsNothingIsDone)
+{
+ arm(10, 0);
+ expectRead(boost::none);
+ EXPECT_CALL(*this, callback(_))
+ .Times(0);
+ EXPECT_CALL(systemMock, timerfd_settime(_, _, _, _))
+ .Times(0);
+ savedEventHandler(Engine::EVENT_IN);
+ Mock::VerifyAndClear(&systemMock);
+}
+
+TEST_F(TimerFDTest, IfReadReturnsNoEventsOnHandleEventsNothingIsDone)
+{
+ arm(10, 0);
+ expectRead(static_cast<uint64_t>(0U));
+ EXPECT_CALL(*this, callback(_))
+ .Times(0);
+ EXPECT_CALL(systemMock, timerfd_settime(_, _, _, _))
+ .Times(0);
+ savedEventHandler(Engine::EVENT_IN);
+ Mock::VerifyAndClear(&systemMock);
+}
+
+TEST_F(TimerFDTest, AllTimersThatHaveExpiredDuringTheEventLoopAreExecutedWithTheSameTimerFdExpiration)
+{
+ InSequence dummy;
+
+ /* The first timer is armed to expire after 10 seconds */
+ expectTimeSinceEpoch(std::chrono::seconds(0));
+ arm(*timer1, 10, 0, "first");
+
+ /* Time has passed 2 seconds, the second timer is armed to expire after 8 seconds */
+ expectTimeSinceEpoch(std::chrono::seconds(2));
+ arm(*timer2, 8, 0, "second");
+
+ /* Time has passed 8 more seconds, both timers expire at once */
+ expectRead(1);
+ expectTimeSinceEpoch(std::chrono::seconds(10));
+ EXPECT_CALL(*this, callback("first"))
+ .Times(1);
+ EXPECT_CALL(*this, callback("second"))
+ .Times(1);
+ savedEventHandler(Engine::EVENT_IN);
+}