blob: e74be4cb46603816a27a6216295081b801910bc1 [file] [log] [blame]
#include "aos/util/phased_loop.h"
#include <memory>
#include <ratio>
#include "gtest/gtest.h"
#include "aos/time/time.h"
namespace aos::time::testing {
using ::std::chrono::milliseconds;
using ::std::chrono::nanoseconds;
typedef ::testing::Test PhasedLoopTest;
typedef PhasedLoopTest PhasedLoopDeathTest;
monotonic_clock::time_point InMs(int ms) {
return monotonic_clock::time_point(::std::chrono::milliseconds(ms));
}
TEST_F(PhasedLoopTest, Reset) {
{
PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
milliseconds(0));
loop.Reset(monotonic_clock::epoch());
EXPECT_EQ(InMs(0), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
EXPECT_EQ(InMs(100), loop.sleep_time());
loop.Reset(InMs(99));
EXPECT_EQ(InMs(0), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(99)));
EXPECT_EQ(InMs(100), loop.sleep_time());
loop.Reset(InMs(100));
EXPECT_EQ(InMs(100), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(199)));
EXPECT_EQ(InMs(200), loop.sleep_time());
loop.Reset(InMs(101));
EXPECT_EQ(InMs(100), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(101)));
EXPECT_EQ(InMs(200), loop.sleep_time());
}
{
PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
milliseconds(1));
loop.Reset(monotonic_clock::epoch());
EXPECT_EQ(InMs(-99), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
EXPECT_EQ(InMs(1), loop.sleep_time());
}
{
PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
milliseconds(99));
loop.Reset(monotonic_clock::epoch());
EXPECT_EQ(InMs(-1), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
EXPECT_EQ(InMs(99), loop.sleep_time());
loop.Reset(InMs(98));
EXPECT_EQ(InMs(-1), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(98)));
EXPECT_EQ(InMs(99), loop.sleep_time());
loop.Reset(InMs(99));
EXPECT_EQ(InMs(99), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(99)));
EXPECT_EQ(InMs(199), loop.sleep_time());
loop.Reset(InMs(100));
EXPECT_EQ(InMs(99), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(100)));
EXPECT_EQ(InMs(199), loop.sleep_time());
}
}
TEST_F(PhasedLoopTest, Iterate) {
{
PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
milliseconds(99));
loop.Reset(monotonic_clock::epoch());
EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
EXPECT_EQ(InMs(99), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(100)));
EXPECT_EQ(InMs(199), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(100)));
EXPECT_EQ(InMs(199), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(101)));
EXPECT_EQ(InMs(199), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(198)));
EXPECT_EQ(InMs(199), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(199)));
EXPECT_EQ(InMs(299), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(300)));
EXPECT_EQ(InMs(399), loop.sleep_time());
EXPECT_EQ(3, loop.Iterate(InMs(600)));
EXPECT_EQ(InMs(699), loop.sleep_time());
}
{
PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
milliseconds(1));
loop.Reset(monotonic_clock::epoch());
EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
EXPECT_EQ(InMs(1), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(100)));
EXPECT_EQ(InMs(101), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(100)));
EXPECT_EQ(InMs(101), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(103)));
EXPECT_EQ(InMs(201), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(198)));
EXPECT_EQ(InMs(201), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(200)));
EXPECT_EQ(InMs(201), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(201)));
EXPECT_EQ(InMs(301), loop.sleep_time());
EXPECT_EQ(3, loop.Iterate(InMs(600)));
EXPECT_EQ(InMs(601), loop.sleep_time());
}
}
// Makes sure that everything works correctly when crossing zero.
// This seems like a rare case at first, but starting from zero needs to
// work, which means negatives should too.
TEST_F(PhasedLoopTest, CrossingZero) {
PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(), milliseconds(1));
loop.Reset(InMs(-1000));
EXPECT_EQ(InMs(-1099), loop.sleep_time());
EXPECT_EQ(9, loop.Iterate(InMs(-250)));
EXPECT_EQ(InMs(-199), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(-199)));
EXPECT_EQ(InMs(-99), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(-90)));
EXPECT_EQ(InMs(1), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(0)));
EXPECT_EQ(InMs(1), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(1)));
EXPECT_EQ(InMs(101), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(2)));
EXPECT_EQ(InMs(101), loop.sleep_time());
EXPECT_EQ(-2, loop.Iterate(InMs(-101)));
EXPECT_EQ(InMs(-99), loop.sleep_time());
EXPECT_EQ(1, loop.Iterate(InMs(-99)));
EXPECT_EQ(InMs(1), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(InMs(-99)));
EXPECT_EQ(InMs(1), loop.sleep_time());
}
// Tests OffsetFromIntervalAndTime for various edge conditions.
TEST_F(PhasedLoopTest, OffsetFromIntervalAndTimeTest) {
PhasedLoop loop(milliseconds(1000), monotonic_clock::epoch(),
milliseconds(300));
EXPECT_EQ(milliseconds(1),
loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(1001)));
EXPECT_EQ(milliseconds(0),
loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(1000)));
EXPECT_EQ(milliseconds(0),
loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(0)));
EXPECT_EQ(milliseconds(999),
loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(-1)));
EXPECT_EQ(milliseconds(7),
loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(19115007)));
EXPECT_EQ(milliseconds(7), loop.OffsetFromIntervalAndTime(milliseconds(1000),
InMs(-19115993)));
}
// Tests that passing invalid values to the constructor dies correctly.
TEST_F(PhasedLoopDeathTest, InvalidValues) {
EXPECT_DEATH(
PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(2)),
".*offset < interval.*");
EXPECT_DEATH(
PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(1)),
".*offset < interval.*");
EXPECT_DEATH(
PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(-1)),
".*offset >= monotonic_clock::duration\\(0\\).*");
EXPECT_DEATH(
PhasedLoop(milliseconds(0), monotonic_clock::epoch(), milliseconds(0)),
".*interval > monotonic_clock::duration\\(0\\).*");
}
// Tests that every single value within two intervals of 0 works.
// This is good at finding edge cases in the rounding.
TEST_F(PhasedLoopTest, SweepingZero) {
for (int i = -30; i < -20; ++i) {
PhasedLoop loop(nanoseconds(20),
monotonic_clock::epoch() - nanoseconds(30));
EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
}
for (int i = -20; i < 0; ++i) {
PhasedLoop loop(nanoseconds(20),
monotonic_clock::epoch() - nanoseconds(30));
EXPECT_EQ(2, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
}
for (int i = 0; i < 20; ++i) {
PhasedLoop loop(nanoseconds(20),
monotonic_clock::epoch() - nanoseconds(30));
EXPECT_EQ(3, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
}
for (int i = 20; i < 30; ++i) {
PhasedLoop loop(nanoseconds(20),
monotonic_clock::epoch() - nanoseconds(30));
EXPECT_EQ(4, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
}
}
// Tests that the phased loop is correctly adjusting when the offset is
// decremented multiple times.
TEST_F(PhasedLoopTest, DecrementingOffset) {
constexpr int kCount = 5;
constexpr int kIterations = 10;
const auto kOffset = milliseconds(400);
const auto kInterval = milliseconds(1000);
const auto kAllIterationsInterval = kInterval * kIterations;
PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
ASSERT_EQ(5, loop.Iterate(last_time));
for (int i = 1; i < kCount; i++) {
const auto offset = kOffset - milliseconds(i);
// First, set the interval/offset without specifying a "now". If we then
// attempt to Iterate() to the same time as the last iteration, this should
// always result in zero cycles elapsed.
{
const monotonic_clock::time_point original_time = loop.sleep_time();
loop.set_interval_and_offset(kInterval, offset);
EXPECT_EQ(original_time - milliseconds(1), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(last_time));
}
// Now, explicitly update/clear things to last_time. This should have the
// same behavior as not specifying a monotonic_now.
{
loop.set_interval_and_offset(kInterval, offset, last_time);
EXPECT_EQ(0, loop.Iterate(last_time));
}
const auto next_time = last_time - milliseconds(1) + kAllIterationsInterval;
EXPECT_EQ(kIterations, loop.Iterate(next_time));
last_time = next_time;
}
}
// Tests that the phased loop is correctly adjusting when the offset is
// incremented multiple times.
TEST_F(PhasedLoopTest, IncrementingOffset) {
constexpr int kCount = 5;
constexpr int kIterations = 10;
const auto kOffset = milliseconds(0);
const auto kInterval = milliseconds(1000);
const auto kAllIterationsInterval = kInterval * kIterations;
PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
ASSERT_EQ(4, loop.Iterate(last_time));
for (int i = 1; i < kCount; i++) {
const auto offset = kOffset + milliseconds(i);
{
const monotonic_clock::time_point original_time = loop.sleep_time();
loop.set_interval_and_offset(kInterval, offset);
EXPECT_EQ(original_time - kInterval + milliseconds(1), loop.sleep_time());
EXPECT_EQ(0, loop.Iterate(last_time));
}
// Now, explicitly update/clear things to a set time. We add a milliseconds
// so that when we call Iterate() next we actually get the expected number
// of iterations (otherwise, there is an iteration that would happen at
// last_time + 1 that gets counted, which is correct behavior, and so just
// needs to be accounted for somehow).
{
loop.set_interval_and_offset(kInterval, offset,
last_time + milliseconds(1));
EXPECT_EQ(0, loop.Iterate(last_time + milliseconds(1)));
}
const auto next_time = last_time + milliseconds(1) + kAllIterationsInterval;
EXPECT_EQ(kIterations, loop.Iterate(next_time));
last_time = next_time;
}
}
// Tests that the phased loop is correctly adjusting when the offset is
// changed to 0.
TEST_F(PhasedLoopTest, ChangingOffset) {
const auto kOffset = milliseconds(900);
const auto kInterval = milliseconds(1000);
PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
const auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
ASSERT_EQ(5, loop.Iterate(last_time));
loop.set_interval_and_offset(kInterval, milliseconds(0));
EXPECT_EQ(4, loop.Iterate((last_time - kOffset) + (kInterval * 4)));
}
} // namespace aos::time::testing