Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds a fake clock utility. #481

Merged
merged 2 commits into from
Nov 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/os/FakeClock.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/** \copyright
* Copyright (c) 2020, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \file FakeClock.cxx
*
* Helper class for unit tests that want to control the advancement of time by
* hand.
*
* @author Balazs Racz
* @date 28 Nov 2020
*/

#include "os/FakeClock.hxx"

#ifdef GTEST

extern "C"
{

long long os_get_fake_time(void)
{
if (FakeClock::exists())
{
return FakeClock::instance()->get_time_nsec();
}
else
{
return -1;
}
}

} // extern C

#endif // GTEST
86 changes: 86 additions & 0 deletions src/os/FakeClock.cxxtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include "os/FakeClock.hxx"

#include "utils/test_main.hxx"

TEST(FakeClockTest, advance)
{
long long t1 = os_get_time_monotonic();
usleep(20000);
long long t2 = os_get_time_monotonic();
EXPECT_LT(t1 + MSEC_TO_NSEC(20), t2);

FakeClock clk;
long long tfreeze = os_get_time_monotonic();
// Upon startup the time should be pretty close.
EXPECT_GT(t2 + MSEC_TO_NSEC(1), tfreeze);

// Time will not advance too much when frozen.
for (unsigned i = 0; i < 100; ++i)
{
EXPECT_GT(tfreeze + 500, os_get_time_monotonic());
}

// But still be monotonic.
t1 = os_get_time_monotonic();
t2 = os_get_time_monotonic();
EXPECT_EQ(1, t2 - t1);
}

TEST(FakeClockTest, independent_test)
{
// There should be no freezing left over for the next test.
long long t1 = os_get_time_monotonic();
usleep(20000);
long long t2 = os_get_time_monotonic();
EXPECT_LT(t1 + MSEC_TO_NSEC(20), t2);
}

class CountingTimer : public Timer
{
public:
CountingTimer()
: Timer(g_executor.active_timers())
{
start(MSEC_TO_NSEC(20));
}

long long timeout() override
{
++count_;
if (needStop_)
{
return DELETE;
}
return RESTART;
}

bool needStop_ = false;
int count_ = 0;
};

TEST(FakeClockTest, executor_timer)
{
FakeClock clk;
CountingTimer *tim = new CountingTimer;

EXPECT_EQ(0, tim->count_);
usleep(50000);
wait_for_main_executor();
EXPECT_EQ(0, tim->count_);
clk.advance(MSEC_TO_NSEC(20) - 100);
wait_for_main_executor();
EXPECT_EQ(0, tim->count_);
clk.advance(100);
wait_for_main_executor();
EXPECT_EQ(1, tim->count_);

clk.advance(MSEC_TO_NSEC(200));
wait_for_main_executor();
EXPECT_EQ(11, tim->count_);
clk.advance(MSEC_TO_NSEC(20));
wait_for_main_executor();
EXPECT_EQ(12, tim->count_);
tim->needStop_ = true;
clk.advance(MSEC_TO_NSEC(20));
wait_for_main_executor();
}
94 changes: 94 additions & 0 deletions src/os/FakeClock.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/** \copyright
* Copyright (c) 2020, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \file FakeClock.hxx
*
* Helper class for unit tests that want to control the advancement of time by
* hand.
*
* @author Balazs Racz
* @date 28 Nov 2020
*/

#ifndef _OS_FAKECLOCK_HXX_
#define _OS_FAKECLOCK_HXX_

#include "executor/Executor.hxx"
#include "os/os.h"
#include "utils/Singleton.hxx"

/// Stores the private variables of a fake clock.
struct FakeClockContent
{
protected:
/// @param t the starting timestamp for the fake clock.
FakeClockContent(long long t)
: lastTime_(t)
{
}

long long lastTime_;
};

/// Class that injects a fake progression of time for unit tests. When this
/// class is created, the time as returned by os_get_time_monotonic()
/// freezes. From that point on time only moves forward when advance() is
/// called.
///
/// There can be at most one instance of this class at any time.
class FakeClock : private FakeClockContent, public Singleton<FakeClock>
{
public:
FakeClock()
: FakeClockContent(os_get_time_monotonic())
{
}

/// Advances the time returned by os_get_time_monotonic().
/// @param nsec how much the time should jump forward (relative to now).
void advance(long long nsec)
{
lastTime_ += nsec;
// Wakes up all executors. This will cause them to evaluate if their
// timers have something expired.
ExecutorBase *current = ExecutorBase::link_head();
while (current)
{
current->add(new CallbackExecutable([]() {}));
current = current->link_next();
}
}

/// @return the currently set time.
long long get_time_nsec()
{
return lastTime_++;
}

private:
};

#endif // _OS_FAKECLOCK_HXX_
11 changes: 10 additions & 1 deletion src/os/os.c
Original file line number Diff line number Diff line change
Expand Up @@ -625,14 +625,23 @@ long long os_get_time_monotonic(void)
time *= clockmul;
time >>= 2;
#else

struct timespec ts;
#if defined (__nuttx__)
clock_gettime(CLOCK_REALTIME, &ts);
#else
clock_gettime(CLOCK_MONOTONIC, &ts);
#endif
time = ((long long)ts.tv_sec * 1000000000LL) + ts.tv_nsec;


#ifdef GTEST
long long fake_time = os_get_fake_time();
if (fake_time >= 0)
{
time = fake_time;
}
#endif // not GTEST

#endif
/* This logic ensures that every successive call is one value larger
* than the last. Each call returns a unique value.
Expand Down
5 changes: 5 additions & 0 deletions src/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ typedef struct
*/
extern long long os_get_time_monotonic(void);

/** Get the fake time for a unit test.
* @return time in nanoseconds, <= 0 if there is no fake clock.
*/
extern long long os_get_fake_time(void);

#ifndef OPENMRN_FEATURE_MUTEX_PTHREAD
/** @ref os_thread_once states.
*/
Expand Down
6 changes: 6 additions & 0 deletions src/utils/LinkedObject.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ public:
return static_cast<T *>(link_);
}

/// @return the subclass pointer of the beginning of the list.
static T *link_head()
{
return static_cast<T *>(head_);
}

/// Locks the list for modification (at any entry!).
static Atomic* head_mu() {
return LinkedObjectHeadMutex<T>::headMu_.get();
Expand Down