blob: 5a9b50de5a4983c159d80613874d618e85a0725b [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
//
// 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
//
// https://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 <chrono>
#include "gtest/gtest.h"
#include "pw_chrono/system_clock.h"
#include "pw_chrono/system_timer.h"
#include "pw_sync/thread_notification.h"
using namespace std::chrono_literals;
namespace pw::chrono {
namespace {
// We can't control the SystemClock's period configuration, so just in case
// duration cannot be accurately expressed in integer ticks, round the
// duration up.
constexpr SystemClock::duration kRoundedArbitraryShortDuration =
SystemClock::for_at_least(42ms);
constexpr SystemClock::duration kRoundedArbitraryLongDuration =
SystemClock::for_at_least(1s);
void ShouldNotBeInvoked(SystemClock::time_point) { FAIL(); }
TEST(SystemTimer, CancelInactive) {
SystemTimer timer(ShouldNotBeInvoked);
timer.Cancel();
}
TEST(SystemTimer, CancelExplicitly) {
SystemTimer timer(ShouldNotBeInvoked);
timer.InvokeAfter(kRoundedArbitraryLongDuration);
timer.Cancel();
}
TEST(SystemTimer, CancelThroughDestruction) {
SystemTimer timer(ShouldNotBeInvoked);
timer.InvokeAfter(kRoundedArbitraryLongDuration);
}
TEST(SystemTimer, CancelThroughRescheduling) {
SystemTimer timer(ShouldNotBeInvoked);
timer.InvokeAfter(kRoundedArbitraryLongDuration);
// Cancel the first with this rescheduling.
timer.InvokeAfter(kRoundedArbitraryLongDuration);
timer.Cancel();
}
// Helper class to let test cases easily instantiate a timer with a handler
// and its own context.
class TimerWithHandler {
public:
TimerWithHandler()
: timer_([this](SystemClock::time_point expired_deadline) {
this->OnExpiryCallback(expired_deadline);
}) {}
virtual ~TimerWithHandler() = default;
// To be implemented by the test case.
virtual void OnExpiryCallback(SystemClock::time_point expired_deadline) = 0;
SystemTimer& timer() { return timer_; }
private:
SystemTimer timer_;
};
TEST(SystemTimer, StaticInvokeAt) {
class TimerWithContext : public TimerWithHandler {
public:
void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
EXPECT_GE(SystemClock::now(), expired_deadline);
EXPECT_EQ(expired_deadline, expected_deadline);
callback_ran_notification.release();
}
SystemClock::time_point expected_deadline;
sync::ThreadNotification callback_ran_notification;
};
static TimerWithContext uut;
uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
uut.timer().InvokeAt(uut.expected_deadline);
uut.callback_ran_notification.acquire();
// Ensure you can re-use the timer.
uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
uut.timer().InvokeAt(uut.expected_deadline);
uut.callback_ran_notification.acquire();
}
TEST(SystemTimer, InvokeAt) {
class TimerWithContext : public TimerWithHandler {
public:
void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
EXPECT_GE(SystemClock::now(), expired_deadline);
EXPECT_EQ(expired_deadline, expected_deadline);
callback_ran_notification.release();
}
SystemClock::time_point expected_deadline;
sync::ThreadNotification callback_ran_notification;
};
TimerWithContext uut;
uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
uut.timer().InvokeAt(uut.expected_deadline);
uut.callback_ran_notification.acquire();
// Ensure you can re-use the timer.
uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
uut.timer().InvokeAt(uut.expected_deadline);
uut.callback_ran_notification.acquire();
// Ensure scheduling it in the past causes it to execute immediately.
uut.expected_deadline = SystemClock::now() - SystemClock::duration(1);
uut.timer().InvokeAt(uut.expected_deadline);
uut.callback_ran_notification.acquire();
}
TEST(SystemTimer, InvokeAfter) {
class TimerWithContext : public TimerWithHandler {
public:
void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
EXPECT_GE(SystemClock::now(), expired_deadline);
EXPECT_GE(expired_deadline, expected_min_deadline);
callback_ran_notification.release();
}
SystemClock::time_point expected_min_deadline;
sync::ThreadNotification callback_ran_notification;
};
TimerWithContext uut;
uut.expected_min_deadline =
SystemClock::TimePointAfterAtLeast(kRoundedArbitraryShortDuration);
uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
uut.callback_ran_notification.acquire();
// Ensure you can re-use the timer.
uut.expected_min_deadline =
SystemClock::TimePointAfterAtLeast(kRoundedArbitraryShortDuration);
uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
uut.callback_ran_notification.acquire();
// Ensure scheduling it immediately works.
uut.expected_min_deadline = SystemClock::now();
uut.timer().InvokeAfter(SystemClock::duration(0));
uut.callback_ran_notification.acquire();
}
TEST(SystemTimer, CancelFromCallback) {
class TimerWithContext : public TimerWithHandler {
public:
void OnExpiryCallback(SystemClock::time_point) override {
timer().Cancel();
callback_ran_notification.release();
}
sync::ThreadNotification callback_ran_notification;
};
TimerWithContext uut;
uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
uut.callback_ran_notification.acquire();
}
TEST(SystemTimer, RescheduleAndCancelFromCallback) {
class TimerWithContext : public TimerWithHandler {
public:
void OnExpiryCallback(SystemClock::time_point) override {
timer().InvokeAfter(kRoundedArbitraryShortDuration);
timer().Cancel();
callback_ran_notification.release();
}
sync::ThreadNotification callback_ran_notification;
};
TimerWithContext uut;
uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
uut.callback_ran_notification.acquire();
}
TEST(SystemTimer, RescheduleFromCallback) {
class TimerWithContext : public TimerWithHandler {
public:
void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
EXPECT_GE(SystemClock::now(), expired_deadline);
EXPECT_EQ(expired_deadline, expected_deadline);
invocation_count++;
ASSERT_LE(invocation_count, kRequiredInvocations);
if (invocation_count < kRequiredInvocations) {
expected_deadline = expired_deadline + kPeriod;
timer().InvokeAt(expected_deadline);
} else {
callbacks_done_notification.release();
}
}
const uint8_t kRequiredInvocations = 5;
const SystemClock::duration kPeriod = kRoundedArbitraryShortDuration;
uint8_t invocation_count = 0;
SystemClock::time_point expected_deadline;
sync::ThreadNotification callbacks_done_notification;
};
TimerWithContext uut;
uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
uut.timer().InvokeAt(uut.expected_deadline);
uut.callbacks_done_notification.acquire();
}
TEST(SystemTimer, DoubleRescheduleFromCallback) {
class TimerWithContext : public TimerWithHandler {
public:
void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
EXPECT_GE(SystemClock::now(), expired_deadline);
EXPECT_EQ(expired_deadline, expected_deadline);
invocation_count++;
ASSERT_LE(invocation_count, kExpectedInvocations);
if (invocation_count == 1) {
expected_deadline = expired_deadline + kPeriod;
timer().InvokeAt(expected_deadline);
timer().InvokeAt(expected_deadline);
} else {
callbacks_done_notification.release();
}
}
const uint8_t kExpectedInvocations = 2;
const SystemClock::duration kPeriod = kRoundedArbitraryShortDuration;
uint8_t invocation_count = 0;
SystemClock::time_point expected_deadline;
sync::ThreadNotification callbacks_done_notification;
};
TimerWithContext uut;
uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
uut.timer().InvokeAt(uut.expected_deadline);
uut.callbacks_done_notification.acquire();
}
} // namespace
} // namespace pw::chrono