@@ -162,3 +162,161 @@ TEST_F(SchedulerTests, TaskDe_ReRegistration) {
162162 EXPECT_EQ (operational_execs, 2 );
163163 EXPECT_EQ (fault_execs, 2 );
164164}
165+
166+ struct SelfValidatingTask {
167+ static uint64_t last_fire_tick;
168+ static uint32_t expected_period;
169+ static bool error_detected;
170+ static int execution_count;
171+
172+ static void reset (uint64_t start_tick, uint32_t period) {
173+ last_fire_tick = start_tick;
174+ expected_period = period;
175+ error_detected = false ;
176+ execution_count = 0 ;
177+ }
178+
179+ static void callback () {
180+ uint64_t current_tick = Scheduler::get_global_tick ();
181+
182+ uint32_t actual_interval = static_cast <uint32_t >(current_tick - last_fire_tick);
183+
184+ if (actual_interval != expected_period) {
185+ error_detected = true ;
186+ printf (" Timing Error Exp: %u, Got: %u at Tick: %lu\n " , expected_period, actual_interval, current_tick);
187+ }
188+ printf (" Task correct Exp: %u, Got: %u at Tick: %lu\n " , expected_period, actual_interval, current_tick);
189+
190+ last_fire_tick = current_tick;
191+ execution_count++;
192+ }
193+ };
194+
195+ uint64_t SelfValidatingTask::last_fire_tick = 0 ;
196+ uint32_t SelfValidatingTask::expected_period = 0 ;
197+ bool SelfValidatingTask::error_detected = false ;
198+ int SelfValidatingTask::execution_count = 0 ;
199+
200+ TEST_F (SchedulerTests, ExplicitOverflowBoundaryCrossing) {
201+ // Start close to the 32-bit limit
202+ // 0xFFFFFFF0 is 16 ticks away from 32-bit overflow
203+ uint64_t start_time = 0xFFFFFFF0ULL ;
204+ Scheduler::global_tick_us_ = start_time;
205+
206+ // We expect the task to fire every 10 ticks
207+ SelfValidatingTask::reset (start_time, 10 );
208+
209+ Scheduler::register_task (10 , &SelfValidatingTask::callback);
210+ Scheduler::start ();
211+ TIM2_BASE->PSC = 2 ;
212+
213+ // Start: FFFFFFF0
214+ // Fire 1: FFFFFFF0 + 10 = FFFFFFFA (OK)
215+ // Fire 2: FFFFFFFA + 10 = 00000004 (OVERFLOW CROSSING)
216+ // Fire 3: 00000004 + 10 = 0000000E (OK)
217+ // Fire 4: 0000000E + 10 = 00000018 (OK)
218+ constexpr int NUM_TICKS = 50 ;
219+
220+ for (int i = 0 ; i < NUM_TICKS; i++){
221+ for (int j = 0 ; j <= TIM2_BASE->PSC ; j++) {
222+ TIM2_BASE->inc_cnt_and_check (1 );
223+ }
224+ Scheduler::update ();
225+
226+ if (SelfValidatingTask::error_detected) break ;
227+ }
228+
229+ EXPECT_FALSE (SelfValidatingTask::error_detected);
230+
231+ EXPECT_EQ (SelfValidatingTask::execution_count, 5 );
232+
233+ EXPECT_EQ (static_cast <uint32_t >(Scheduler::global_tick_us_), 0x00000022 );
234+ }
235+
236+ struct DualTaskValidator {
237+ struct Context {
238+ uint64_t last_fire_tick;
239+ uint32_t expected_period;
240+ int exec_count;
241+ bool error;
242+ };
243+
244+ static Context ctx_A; // Task that runs BEFORE wrap
245+ static Context ctx_B; // Task that runs AFTER wrap
246+
247+ static void reset (uint64_t start_tick) {
248+ ctx_A = {start_tick, 0 , 0 , false };
249+ ctx_B = {start_tick, 0 , 0 , false };
250+ }
251+
252+ // Callback for the "Pre-Wrap" Task
253+ static void callback_A () {
254+ validate (ctx_A);
255+ }
256+
257+ // Callback for the "Post-Wrap" Task
258+ static void callback_B () {
259+ validate (ctx_B);
260+ }
261+
262+ static void validate (Context& ctx) {
263+ uint64_t current_tick = Scheduler::get_global_tick ();
264+ uint32_t interval = static_cast <uint32_t >(current_tick - ctx.last_fire_tick );
265+
266+ if (interval != ctx.expected_period ) {
267+ ctx.error = true ;
268+ printf (" Error Expected %u, got %u\n " , ctx.expected_period , interval);
269+ }
270+ ctx.last_fire_tick = current_tick;
271+ ctx.exec_count ++;
272+ }
273+ };
274+
275+ DualTaskValidator::Context DualTaskValidator::ctx_A;
276+ DualTaskValidator::Context DualTaskValidator::ctx_B;
277+
278+
279+ TEST_F (SchedulerTests, TwoTasksStraddlingOverflow) {
280+ // Start close to overflow
281+ // Current Time: 0xFFFFFFF0 (16 ticks before wrap)
282+ uint64_t start_time = 0xFFFFFFF0ULL ;
283+ Scheduler::global_tick_us_ = start_time;
284+ DualTaskValidator::reset (start_time);
285+
286+ // Task A: Period 5. Target = FFFFFFF0 + 5 = FFFFFFF5 (Before Wrap)
287+ // Task B: Period 20. Target = FFFFFFF0 + 20 = 00000004 (After Wrap)
288+ DualTaskValidator::ctx_A.expected_period = 5 ;
289+ DualTaskValidator::ctx_B.expected_period = 20 ;
290+
291+ Scheduler::register_task (20 , &DualTaskValidator::callback_B); // Registers "Later" task first
292+ Scheduler::register_task (5 , &DualTaskValidator::callback_A); // Registers "Earlier" task second
293+
294+ Scheduler::start ();
295+ TIM2_BASE->PSC = 2 ;
296+
297+ // T=0 (Global FFFFFFF0)
298+ // T=5 (Global FFFFFFF5): Task A should fire.
299+ // T=10 (Global FFFFFFFA): Task A should fire again.
300+ // T=15 (Global FFFFFFFF): Task A should fire again.
301+ // T=16 (Global 00000000): --- OVERFLOW ---
302+ // T=20 (Global 00000004): Task A fires (4th time). Task B fires (1st time).
303+ constexpr int NUM_TICKS = 30 ;
304+
305+ for (int i = 0 ; i < NUM_TICKS; i++){
306+ for (int j = 0 ; j <= TIM2_BASE->PSC ; j++) {
307+ TIM2_BASE->inc_cnt_and_check (1 );
308+ }
309+ Scheduler::update ();
310+ }
311+
312+ // Task A should have fired every 5 ticks. 30 / 5 = 6 times total
313+ EXPECT_FALSE (DualTaskValidator::ctx_A.error ) << " Task A (Short Period) timing failed" ;
314+ EXPECT_EQ (DualTaskValidator::ctx_A.exec_count , 6 );
315+
316+ // Task B should have fired once at T=20 (which is 0x00000004)
317+ EXPECT_FALSE (DualTaskValidator::ctx_B.error ) << " Task B (Long Period) timing failed" ;
318+ EXPECT_EQ (DualTaskValidator::ctx_B.exec_count , 1 );
319+
320+ // 0xFFFFFFF0 + 30 = 0x10000000E -> cast to 32-bit -> 0x0000000E
321+ EXPECT_EQ (static_cast <uint32_t >(Scheduler::global_tick_us_), 0x0000000E );
322+ }
0 commit comments