1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include <chrono>
16
17 #include "pw_chrono/system_clock.h"
18 #include "pw_chrono/system_timer.h"
19 #include "pw_sync/thread_notification.h"
20 #include "pw_unit_test/framework.h"
21
22 using namespace std::chrono_literals;
23
24 namespace pw::chrono {
25 namespace {
26
27 // We can't control the SystemClock's period configuration, so just in case
28 // duration cannot be accurately expressed in integer ticks, round the
29 // duration up.
30 constexpr SystemClock::duration kRoundedArbitraryShortDuration =
31 SystemClock::for_at_least(42ms);
32 constexpr SystemClock::duration kRoundedArbitraryLongDuration =
33 SystemClock::for_at_least(1s);
34
ShouldNotBeInvoked(SystemClock::time_point)35 void ShouldNotBeInvoked(SystemClock::time_point) { FAIL(); }
36
TEST(SystemTimer,CancelInactive)37 TEST(SystemTimer, CancelInactive) {
38 SystemTimer timer(ShouldNotBeInvoked);
39 timer.Cancel();
40 }
41
TEST(SystemTimer,CancelExplicitly)42 TEST(SystemTimer, CancelExplicitly) {
43 SystemTimer timer(ShouldNotBeInvoked);
44 timer.InvokeAfter(kRoundedArbitraryLongDuration);
45 timer.Cancel();
46 }
47
TEST(SystemTimer,CancelThroughDestruction)48 TEST(SystemTimer, CancelThroughDestruction) {
49 SystemTimer timer(ShouldNotBeInvoked);
50 timer.InvokeAfter(kRoundedArbitraryLongDuration);
51 }
52
TEST(SystemTimer,CancelThroughRescheduling)53 TEST(SystemTimer, CancelThroughRescheduling) {
54 SystemTimer timer(ShouldNotBeInvoked);
55 timer.InvokeAfter(kRoundedArbitraryLongDuration);
56 // Cancel the first with this rescheduling.
57 timer.InvokeAfter(kRoundedArbitraryLongDuration);
58 timer.Cancel();
59 }
60
61 // Helper class to let test cases easily instantiate a timer with a handler
62 // and its own context.
63 class TimerWithHandler {
64 public:
TimerWithHandler()65 TimerWithHandler()
66 : timer_([this](SystemClock::time_point expired_deadline) {
67 this->OnExpiryCallback(expired_deadline);
68 }) {}
69 virtual ~TimerWithHandler() = default;
70
71 // To be implemented by the test case.
72 virtual void OnExpiryCallback(SystemClock::time_point expired_deadline) = 0;
73
timer()74 SystemTimer& timer() { return timer_; }
75
76 private:
77 SystemTimer timer_;
78 };
79
TEST(SystemTimer,StaticInvokeAt)80 TEST(SystemTimer, StaticInvokeAt) {
81 class TimerWithContext : public TimerWithHandler {
82 public:
83 void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
84 EXPECT_GE(SystemClock::now(), expired_deadline);
85 EXPECT_EQ(expired_deadline, expected_deadline);
86 callback_ran_notification.release();
87 }
88
89 SystemClock::time_point expected_deadline;
90 sync::ThreadNotification callback_ran_notification;
91 };
92 static TimerWithContext uut;
93
94 uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
95 uut.timer().InvokeAt(uut.expected_deadline);
96 uut.callback_ran_notification.acquire();
97
98 // Ensure you can re-use the timer.
99 uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
100 uut.timer().InvokeAt(uut.expected_deadline);
101 uut.callback_ran_notification.acquire();
102 }
103
TEST(SystemTimer,InvokeAt)104 TEST(SystemTimer, InvokeAt) {
105 class TimerWithContext : public TimerWithHandler {
106 public:
107 void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
108 EXPECT_GE(SystemClock::now(), expired_deadline);
109 EXPECT_EQ(expired_deadline, expected_deadline);
110 callback_ran_notification.release();
111 }
112
113 SystemClock::time_point expected_deadline;
114 sync::ThreadNotification callback_ran_notification;
115 };
116 TimerWithContext uut;
117
118 uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
119 uut.timer().InvokeAt(uut.expected_deadline);
120 uut.callback_ran_notification.acquire();
121
122 // Ensure you can re-use the timer.
123 uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
124 uut.timer().InvokeAt(uut.expected_deadline);
125 uut.callback_ran_notification.acquire();
126
127 // Ensure scheduling it in the past causes it to execute immediately.
128 uut.expected_deadline = SystemClock::now() - SystemClock::duration(1);
129 uut.timer().InvokeAt(uut.expected_deadline);
130 uut.callback_ran_notification.acquire();
131 }
132
TEST(SystemTimer,InvokeAfter)133 TEST(SystemTimer, InvokeAfter) {
134 class TimerWithContext : public TimerWithHandler {
135 public:
136 void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
137 EXPECT_GE(SystemClock::now(), expired_deadline);
138 EXPECT_GE(expired_deadline, expected_min_deadline);
139 callback_ran_notification.release();
140 }
141
142 SystemClock::time_point expected_min_deadline;
143 sync::ThreadNotification callback_ran_notification;
144 };
145 TimerWithContext uut;
146
147 uut.expected_min_deadline =
148 SystemClock::TimePointAfterAtLeast(kRoundedArbitraryShortDuration);
149 uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
150 uut.callback_ran_notification.acquire();
151
152 // Ensure you can re-use the timer.
153 uut.expected_min_deadline =
154 SystemClock::TimePointAfterAtLeast(kRoundedArbitraryShortDuration);
155 uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
156 uut.callback_ran_notification.acquire();
157
158 // Ensure scheduling it immediately works.
159 uut.expected_min_deadline = SystemClock::now();
160 uut.timer().InvokeAfter(SystemClock::duration(0));
161 uut.callback_ran_notification.acquire();
162 }
163
TEST(SystemTimer,CancelFromCallback)164 TEST(SystemTimer, CancelFromCallback) {
165 class TimerWithContext : public TimerWithHandler {
166 public:
167 void OnExpiryCallback(SystemClock::time_point) override {
168 timer().Cancel();
169 callback_ran_notification.release();
170 }
171
172 sync::ThreadNotification callback_ran_notification;
173 };
174 TimerWithContext uut;
175
176 uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
177 uut.callback_ran_notification.acquire();
178 }
179
TEST(SystemTimer,RescheduleAndCancelFromCallback)180 TEST(SystemTimer, RescheduleAndCancelFromCallback) {
181 class TimerWithContext : public TimerWithHandler {
182 public:
183 void OnExpiryCallback(SystemClock::time_point) override {
184 timer().InvokeAfter(kRoundedArbitraryShortDuration);
185 timer().Cancel();
186 callback_ran_notification.release();
187 }
188
189 sync::ThreadNotification callback_ran_notification;
190 };
191 TimerWithContext uut;
192
193 uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
194 uut.callback_ran_notification.acquire();
195 }
196
TEST(SystemTimer,RescheduleFromCallback)197 TEST(SystemTimer, RescheduleFromCallback) {
198 class TimerWithContext : public TimerWithHandler {
199 public:
200 void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
201 EXPECT_GE(SystemClock::now(), expired_deadline);
202
203 EXPECT_EQ(expired_deadline, expected_deadline);
204 invocation_count++;
205 ASSERT_LE(invocation_count, kRequiredInvocations);
206 if (invocation_count < kRequiredInvocations) {
207 expected_deadline = expired_deadline + kPeriod;
208 timer().InvokeAt(expected_deadline);
209 } else {
210 callbacks_done_notification.release();
211 }
212 }
213
214 const uint8_t kRequiredInvocations = 5;
215 const SystemClock::duration kPeriod = kRoundedArbitraryShortDuration;
216 uint8_t invocation_count = 0;
217 SystemClock::time_point expected_deadline;
218 sync::ThreadNotification callbacks_done_notification;
219 };
220 TimerWithContext uut;
221
222 uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
223 uut.timer().InvokeAt(uut.expected_deadline);
224 uut.callbacks_done_notification.acquire();
225 }
226
TEST(SystemTimer,DoubleRescheduleFromCallback)227 TEST(SystemTimer, DoubleRescheduleFromCallback) {
228 class TimerWithContext : public TimerWithHandler {
229 public:
230 void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
231 EXPECT_GE(SystemClock::now(), expired_deadline);
232
233 EXPECT_EQ(expired_deadline, expected_deadline);
234 invocation_count++;
235 ASSERT_LE(invocation_count, kExpectedInvocations);
236 if (invocation_count == 1) {
237 expected_deadline = expired_deadline + kPeriod;
238 timer().InvokeAt(expected_deadline);
239 timer().InvokeAt(expected_deadline);
240 } else {
241 callbacks_done_notification.release();
242 }
243 }
244
245 const uint8_t kExpectedInvocations = 2;
246 const SystemClock::duration kPeriod = kRoundedArbitraryShortDuration;
247 uint8_t invocation_count = 0;
248 SystemClock::time_point expected_deadline;
249 sync::ThreadNotification callbacks_done_notification;
250 };
251 TimerWithContext uut;
252
253 uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
254 uut.timer().InvokeAt(uut.expected_deadline);
255 uut.callbacks_done_notification.acquire();
256 }
257
258 } // namespace
259 } // namespace pw::chrono
260