| // 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 |