SuperTinyKernel™ RTOS 1.05.3
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
test_event.cpp
Go to the documentation of this file.
1/*
2 * SuperTinyKernel™ (STK): Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10#include <stk_config.h>
11#include <stk.h>
12#include <sync/stk_sync_event.h>
13#include <assert.h>
14#include <string.h>
15
16#include "stktest_context.h"
17
18using namespace stk;
19using namespace stk::test;
20
22
23#define _STK_EVT_TEST_TASKS_MAX 5
24#define _STK_EVT_TEST_TIMEOUT 300
25#define _STK_EVT_TEST_SHORT_SLEEP 10
26#define _STK_EVT_TEST_LONG_SLEEP 100
27#ifdef __ARM_ARCH_6M__
28#define _STK_EVT_STACK_SIZE 128 // ARM Cortex-M0
29#define STK_TASK
30#else
31#define _STK_EVT_STACK_SIZE 256
32#define STK_TASK static
33#endif
34
35#ifndef _NEW
36inline void *operator new(std::size_t, void *ptr) noexcept { return ptr; }
37inline void operator delete(void *, void *) noexcept { /* nothing for placement delete */ }
38#endif
39
40namespace stk {
41namespace test {
42
46namespace event {
47
48// Test results storage
49static volatile int32_t g_TestResult = 0;
50static volatile int32_t g_SharedCounter = 0;
51static volatile int32_t g_AcquisitionOrder[_STK_EVT_TEST_TASKS_MAX] = {0};
52static volatile int32_t g_OrderIndex = 0;
53static volatile bool g_TestComplete = false;
54
55// Kernel
57
58// Test event (re-constructed per test via ResetTestState)
60
67template <EAccessMode _AccessMode>
68class AutoResetBasicTask : public Task<_STK_EVT_STACK_SIZE, _AccessMode>
69{
70 uint8_t m_task_id;
71 int32_t m_iterations;
72
73public:
74 AutoResetBasicTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
75 {}
76
77private:
78 void Run()
79 {
80 if (m_task_id == 0)
81 {
82 // Task 0: producer - fire one Set() per iteration; each must wake exactly one consumer
83 stk::Sleep(_STK_EVT_TEST_SHORT_SLEEP); // let consumers block first
84
85 for (int32_t i = 0; i < m_iterations; ++i)
86 {
87 g_TestEvent.Set();
88 stk::Delay(1); // pace so consumer can unblock between signals
89 }
90
92
93 printf("auto-reset basic: counter=%d (expected %d)\n",
94 (int)g_SharedCounter, (int)m_iterations);
95
97 g_TestResult = 1;
98 }
99 else
100 {
101 // Tasks 1-4: consumers - each loops waiting for the event
102 for (int32_t i = 0; i < m_iterations; ++i)
103 {
106 }
107 }
108 }
109};
110
117template <EAccessMode _AccessMode>
118class ManualResetBasicTask : public Task<_STK_EVT_STACK_SIZE, _AccessMode>
119{
120 uint8_t m_task_id;
121
122public:
123 ManualResetBasicTask(uint8_t task_id, int32_t) : m_task_id(task_id)
124 {}
125
126private:
127 void Run()
128 {
129 if (m_task_id == 0)
130 {
131 // Task 0: let all consumers block, then fire a single Set()
133
134 g_TestEvent.Set();
135
137
138 // All 4 consumers must have woken; event still signaled before Reset()
139 bool still_signaled = g_TestEvent.TryWait(); // fast-path check, does NOT reset (manual-reset)
140 g_TestEvent.Reset();
141
142 printf("manual-reset basic: counter=%d (expected %d), still_signaled=%d (expected 1)\n",
143 (int)g_SharedCounter, (int)(_STK_EVT_TEST_TASKS_MAX - 1), (int)still_signaled);
144
145 if ((g_SharedCounter == (_STK_EVT_TEST_TASKS_MAX - 1)) && still_signaled)
146 g_TestResult = 1;
147 }
148 else
149 {
150 // Tasks 1-4: each waits for the single Set(); all should be released
153 }
154 }
155};
156
163template <EAccessMode _AccessMode>
164class InitialStateTask : public Task<_STK_EVT_STACK_SIZE, _AccessMode>
165{
166 uint8_t m_task_id;
167
168public:
169 InitialStateTask(uint8_t task_id, int32_t) : m_task_id(task_id)
170 {}
171
172private:
173 void Run()
174 {
175 if (m_task_id == 1)
176 {
177 // First Wait() must succeed immediately (initial_state=true, auto-reset clears it)
180
181 // Second Wait() must time out: event was auto-reset by the first Wait()
182 bool second = g_TestEvent.Wait(_STK_EVT_TEST_SHORT_SLEEP);
183
184 if (!second)
186 }
187
188 if (m_task_id == 0)
189 {
191
192 printf("initial state: counter=%d (expected 2)\n", (int)g_SharedCounter);
193
194 if (g_SharedCounter == 2)
195 g_TestResult = 1;
196 }
197 }
198};
199
205template <EAccessMode _AccessMode>
206class TimeoutWaitTask : public Task<_STK_EVT_STACK_SIZE, _AccessMode>
207{
208 uint8_t m_task_id;
209
210public:
211 TimeoutWaitTask(uint8_t task_id, int32_t) : m_task_id(task_id)
212 {}
213
214private:
215 void Run()
216 {
217 if (m_task_id == 0)
218 {
219 // Task 0: withhold Set() well past the timeout window, then fire it
220 stk::Sleep(200);
221 g_TestEvent.Set();
222 }
223 else
224 if (m_task_id == 1)
225 {
226 // Task 1: Wait with 50-tick timeout; must expire before task 0 fires Set()
228
229 int64_t start = GetTimeNowMs();
230 bool acquired = g_TestEvent.Wait(50);
231 int64_t elapsed = GetTimeNowMs() - start;
232
233 if (!acquired && elapsed >= 45 && elapsed <= 60)
235
236 if (acquired)
237 g_TestEvent.Reset(); // return state to non-signaled if unexpectedly acquired
238 }
239 else
240 if (m_task_id == 2)
241 {
242 // Task 2: Wait with generous timeout after task 0 fires Set()
243 stk::Sleep(210);
244
245 if (g_TestEvent.Wait(100))
247 }
248
249 if (m_task_id == 2)
250 {
252
253 printf("timeout wait: counter=%d (expected 2)\n", (int)g_SharedCounter);
254
255 if (g_SharedCounter == 2)
256 g_TestResult = 1;
257 }
258 }
259};
260
266template <EAccessMode _AccessMode>
267class TryWaitTask : public Task<_STK_EVT_STACK_SIZE, _AccessMode>
268{
269 uint8_t m_task_id;
270
271public:
272 TryWaitTask(uint8_t task_id, int32_t) : m_task_id(task_id)
273 {}
274
275private:
276 void Run()
277 {
278 if (m_task_id == 1)
279 {
280 // TryWait on a non-signaled event must return false immediately
281 int64_t start = GetTimeNowMs();
282 bool acquired = g_TestEvent.TryWait();
283 int64_t elapsed = GetTimeNowMs() - start;
284
285 if (!acquired && elapsed < _STK_EVT_TEST_SHORT_SLEEP)
287 }
288 else
289 if (m_task_id == 2)
290 {
291 // Signal the event, then TryWait must return true and auto-reset it
292 g_TestEvent.Set();
293
294 int64_t start = GetTimeNowMs();
295 bool acquired = g_TestEvent.TryWait();
296 int64_t elapsed = GetTimeNowMs() - start;
297
298 if (acquired && elapsed < _STK_EVT_TEST_SHORT_SLEEP)
300
301 // Verify auto-reset: a second TryWait must now return false
302 if (!g_TestEvent.TryWait())
304 }
305
306 if (m_task_id == 0)
307 {
309
310 printf("try-wait: counter=%d (expected 3)\n", (int)g_SharedCounter);
311
312 if (g_SharedCounter == 3)
313 g_TestResult = 1;
314 }
315 }
316};
317
324template <EAccessMode _AccessMode>
325class ResetManualTask : public Task<_STK_EVT_STACK_SIZE, _AccessMode>
326{
327 uint8_t m_task_id;
328
329public:
330 ResetManualTask(uint8_t task_id, int32_t) : m_task_id(task_id)
331 {}
332
333private:
334 void Run()
335 {
336 if (m_task_id == 0)
337 {
338 // Set the event (manual-reset), verify state, then Reset() and verify block
339 bool set_changed = g_TestEvent.Set();
340 bool reset_changed = g_TestEvent.Reset();
341 bool reset_again = g_TestEvent.Reset(); // already non-signaled: must return false
342
343 // Both tasks 1 and 2 were sleeping during Set/Reset; they should now time out
345
346 printf("reset manual: set_changed=%d (expected 1), reset_changed=%d (expected 1), "
347 "reset_again=%d (expected 0), counter=%d (expected 0)\n",
348 (int)set_changed, (int)reset_changed, (int)reset_again, (int)g_SharedCounter);
349
350 if (set_changed && reset_changed && !reset_again && (g_SharedCounter == 0))
351 g_TestResult = 1;
352 }
353 else
354 if (m_task_id == 1)
355 {
356 // Wait after Reset(); must time out because event was reset before we got here
357 stk::Sleep(_STK_EVT_TEST_SHORT_SLEEP); // ensure task 0 has Set+Reset'd by now
358
359 bool acquired = g_TestEvent.Wait(_STK_EVT_TEST_SHORT_SLEEP);
360
361 if (!acquired)
362 ; // expected; do not count
363 else
364 ++g_SharedCounter; // unexpected wake counts as a failure
365 }
366 else
367 if (m_task_id == 2)
368 {
369 // Same as task 1: must also time out
371
372 bool acquired = g_TestEvent.Wait(_STK_EVT_TEST_SHORT_SLEEP);
373
374 if (acquired)
375 ++g_SharedCounter; // unexpected wake counts as a failure
376 }
377 }
378};
379
385template <EAccessMode _AccessMode>
386class PulseAutoResetTask : public Task<_STK_EVT_STACK_SIZE, _AccessMode>
387{
388 uint8_t m_task_id;
390
391public:
392 PulseAutoResetTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
393 {}
394
395private:
396 void Run()
397 {
398 if (m_task_id == 0)
399 {
400 // Producer: one Pulse() per iteration wakes exactly one consumer
401 stk::Sleep(_STK_EVT_TEST_SHORT_SLEEP); // let consumers block first
402
403 for (int32_t i = 0; i < m_iterations; ++i)
404 {
405 g_TestEvent.Pulse();
406 stk::Delay(1);
407 }
408
410
411 // Pulse with no waiters must leave event non-signaled
412 g_TestEvent.Pulse(); // no one waiting
413 bool still_signaled = g_TestEvent.TryWait();
414
415 printf("pulse auto-reset: counter=%d (expected %d), still_signaled=%d (expected 0)\n",
416 (int)g_SharedCounter, (int)m_iterations, (int)still_signaled);
417
418 if ((g_SharedCounter == m_iterations) && !still_signaled)
419 g_TestResult = 1;
420 }
421 else
422 {
423 // Tasks 1-4: consume Pulse events; each loop iteration waits for one pulse
424 int32_t share = m_iterations / (_STK_EVT_TEST_TASKS_MAX - 1);
425
426 for (int32_t i = 0; i < share; ++i)
427 {
430 }
431 }
432 }
433};
434
440template <EAccessMode _AccessMode>
441class PulseManualResetTask : public Task<_STK_EVT_STACK_SIZE, _AccessMode>
442{
443 uint8_t m_task_id;
444
445public:
446 PulseManualResetTask(uint8_t task_id, int32_t) : m_task_id(task_id)
447 {}
448
449private:
450 void Run()
451 {
452 if (m_task_id == 0)
453 {
454 // Let all consumers block, then fire a single Pulse()
456
457 g_TestEvent.Pulse(); // must wake all 4 waiters and reset
458
460
461 // Event must be non-signaled after Pulse()
462 bool still_signaled = g_TestEvent.TryWait();
463
464 // Pulse() with no waiters must also leave event non-signaled
465 g_TestEvent.Pulse();
466 bool after_empty_pulse = g_TestEvent.TryWait();
467
468 printf("pulse manual-reset: counter=%d (expected %d), "
469 "still_signaled=%d (expected 0), after_empty_pulse=%d (expected 0)\n",
471 (int)still_signaled, (int)after_empty_pulse);
472
474 !still_signaled && !after_empty_pulse)
475 g_TestResult = 1;
476 }
477 else
478 {
479 // Tasks 1-4: all block and must all be released by the single Pulse()
482 }
483 }
484};
485
486// Helper function to reset test state
487static void ResetTestState(bool manual_reset = false, bool initial_state = false)
488{
489 g_TestResult = 0;
490 g_SharedCounter = 0;
491 g_OrderIndex = 0;
492 g_TestComplete = false;
493
494 for (int32_t i = 0; i < _STK_EVT_TEST_TASKS_MAX; ++i)
495 g_AcquisitionOrder[i] = 0;
496
497 // Re-construct the event in-place with the requested mode and initial state
498 g_TestEvent.~Event();
499 new (&g_TestEvent) sync::Event(manual_reset, initial_state);
500}
501
502} // namespace event
503} // namespace test
504} // namespace stk
505
506static bool NeedsExtendedTasks(const char *test_name)
507{
508 return (strcmp(test_name, "InitialState") != 0) &&
509 (strcmp(test_name, "TimeoutWait") != 0) &&
510 (strcmp(test_name, "TryWait") != 0) &&
511 (strcmp(test_name, "ResetManual") != 0);
512}
513
517template <class TaskType>
518static int32_t RunTest(const char *test_name, int32_t param = 0,
519 bool manual_reset = false, bool initial_state = false)
520{
521 using namespace stk;
522 using namespace stk::test;
523 using namespace stk::test::event;
524
525 printf("Test: %s\n", test_name);
526
527 ResetTestState(manual_reset, initial_state);
528
529 // Create tasks based on test type
530 STK_TASK TaskType task0(0, param);
531 STK_TASK TaskType task1(1, param);
532 STK_TASK TaskType task2(2, param);
533 TaskType task3(3, param);
534 TaskType task4(4, param);
535
536 g_Kernel.AddTask(&task0);
537 g_Kernel.AddTask(&task1);
538 g_Kernel.AddTask(&task2);
539
540 if (NeedsExtendedTasks(test_name))
541 {
542 g_Kernel.AddTask(&task3);
543 g_Kernel.AddTask(&task4);
544 }
545
546 g_Kernel.Start();
547
549
550 printf("Result: %s\n", result == TestContext::SUCCESS_EXIT_CODE ? "PASS" : "FAIL");
551 printf("--------------\n");
552
553 return result;
554}
555
559int main(int argc, char **argv)
560{
561 (void)argc;
562 (void)argv;
563
564 using namespace stk::test::event;
565
567
568 int total_failures = 0, total_success = 0;
569
570 printf("--------------\n");
571
572 g_Kernel.Initialize();
573
574 // Test 1: Auto-reset event wakes exactly one waiter per Set()
576 total_failures++;
577 else
578 total_success++;
579
580#ifndef __ARM_ARCH_6M__
581
582 // Test 2: Manual-reset event wakes all waiters and stays signaled until Reset()
584 total_failures++;
585 else
586 total_success++;
587
588 // Test 3: initial_state=true provides immediate fast-path Wait(), then auto-resets (tasks 0-2 only)
589 if (RunTest<InitialStateTask<ACCESS_PRIVILEGED>>("InitialState", 0, false, true) != TestContext::SUCCESS_EXIT_CODE)
590 total_failures++;
591 else
592 total_success++;
593
594 // Test 4: Wait() times out correctly when no Set() fires (tasks 0-2 only)
596 total_failures++;
597 else
598 total_success++;
599
600 // Test 5: TryWait() returns immediately and auto-resets on success (tasks 0-2 only)
602 total_failures++;
603 else
604 total_success++;
605
606 // Test 6: Reset() clears manual-reset event; return value reflects actual state change (tasks 0-2 only)
608 total_failures++;
609 else
610 total_success++;
611
612 // Test 7: Pulse() on auto-reset event wakes one waiter and always resets
614 total_failures++;
615 else
616 total_success++;
617
618 // Test 8: Pulse() on manual-reset event wakes all waiters and always resets
620 total_failures++;
621 else
622 total_success++;
623
624#endif // __ARM_ARCH_6M__
625
626 int32_t final_result = (total_failures == 0 ? TestContext::SUCCESS_EXIT_CODE : TestContext::DEFAULT_FAILURE_EXIT_CODE);
627
628 printf("##############\n");
629 printf("Total tests: %d\n", total_failures + total_success);
630 printf("Failures: %d\n", total_failures);
631
633 return final_result;
634}
Top-level STK include. Provides the Kernel class template and all built-in task-switching strategies.
Implementation of synchronization primitive: stk::sync::Event.
static int32_t RunTest(const char *test_name, int32_t param=0)
#define STK_TASK
#define _STK_EVT_TEST_LONG_SLEEP
int main(int argc, char **argv)
#define _STK_EVT_TEST_SHORT_SLEEP
#define _STK_EVT_TEST_TIMEOUT
static int32_t RunTest(const char *test_name, int32_t param=0, bool manual_reset=false, bool initial_state=false)
#define _STK_EVT_TEST_TASKS_MAX
static bool NeedsExtendedTasks(const char *test_name)
#define STK_TEST_DECL_ASSERT
Declare assertion redirector in the source file.
Namespace of STK package.
static int64_t GetTimeNowMs()
Get current time in milliseconds since kernel start.
Definition stk_helper.h:281
void Sleep(uint32_t ticks)
Put calling process into a sleep state.
Definition stk_helper.h:298
void Delay(uint32_t ticks)
Delay calling process by busy-waiting until the deadline expires.
Definition stk_helper.h:342
Namespace of the test inventory.
Namespace of Event test.
static volatile int32_t g_OrderIndex
static volatile int32_t g_SharedCounter
static sync::Event g_TestEvent
static volatile bool g_TestComplete
static volatile int32_t g_AcquisitionOrder[5]
static Kernel< KERNEL_DYNAMIC|KERNEL_SYNC, 5, SwitchStrategyRR, PlatformDefault > g_Kernel
static void ResetTestState(bool manual_reset=false, bool initial_state=false)
static volatile int32_t g_TestResult
Concrete implementation of IKernel.
Definition stk.h:83
Task(const Task &)=delete
Binary synchronization event (signaled / non-signaled) primitive.
Tests auto-reset event: Set() wakes exactly one waiting task then resets.
void Run()
Entry point of the user task.
AutoResetBasicTask(uint8_t task_id, int32_t iterations)
Tests manual-reset event: Set() wakes all waiting tasks and state stays signaled.
void Run()
Entry point of the user task.
ManualResetBasicTask(uint8_t task_id, int32_t)
Tests event constructed with initial_state=true.
InitialStateTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests Wait() timeout behavior.
void Run()
Entry point of the user task.
TimeoutWaitTask(uint8_t task_id, int32_t)
Tests TryWait() non-blocking poll behavior.
void Run()
Entry point of the user task.
TryWaitTask(uint8_t task_id, int32_t)
Tests Reset() on a manual-reset event.
ResetManualTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests Pulse() on an auto-reset event.
PulseAutoResetTask(uint8_t task_id, int32_t iterations)
void Run()
Entry point of the user task.
Tests Pulse() on a manual-reset event.
PulseManualResetTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
static void ShowTestSuitePrologue()
Show text string as prologue before tests start.
@ DEFAULT_FAILURE_EXIT_CODE
default exit code for exit() to denote failure of the test
@ SUCCESS_EXIT_CODE
exit code for exit() to denote the success of the test
static void ShowTestSuiteEpilogue(int32_t result)
Show text string as epilogue after tests end.