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_semaphore.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>
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_SEM_TEST_TASKS_MAX 5
24#define _STK_SEM_TEST_TIMEOUT 1000
25#define _STK_SEM_TEST_SHORT_SLEEP 10
26#define _STK_SEM_TEST_LONG_SLEEP 100
27#ifdef __ARM_ARCH_6M__
28#define _STK_SEM_STACK_SIZE 128 // ARM Cortex-M0
29#define STK_TASK
30#else
31#define _STK_SEM_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 semaphore {
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_SEM_TEST_TASKS_MAX] = {0};
52static volatile int32_t g_OrderIndex = 0;
53static volatile bool g_TestComplete = false;
54
55// Kernel
57
58// Test semaphore (re-constructed per test via ResetTestState)
60
66template <EAccessMode _AccessMode>
67class BasicSignalWaitTask : public Task<_STK_SEM_STACK_SIZE, _AccessMode>
68{
69 uint8_t m_task_id;
70 int32_t m_iterations;
71
72public:
73 BasicSignalWaitTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
74 {}
75
76private:
77 void Run()
78 {
79 if (m_task_id == 0)
80 {
81 // Task 0: producer - signal once per iteration for each consumer
82 for (int32_t i = 0; i < m_iterations * (_STK_SEM_TEST_TASKS_MAX - 1); ++i)
83 {
84 stk::Delay(1); // pace signals so consumers can catch up
85 g_TestSemaphore.Signal();
86 }
87
89
90 int32_t expected = m_iterations * (_STK_SEM_TEST_TASKS_MAX - 1);
91
92 printf("basic signal/wait: counter=%d (expected %d)\n", (int)g_SharedCounter, (int)expected);
93
94 if (g_SharedCounter == expected)
95 g_TestResult = 1;
96 }
97 else
98 {
99 // Tasks 1-4: consumers - wait and increment counter per acquired signal
100 for (int32_t i = 0; i < m_iterations; ++i)
101 {
102 g_TestSemaphore.Wait();
104 }
105 }
106 }
107};
108
115template <EAccessMode _AccessMode>
116class InitialCountTask : public Task<_STK_SEM_STACK_SIZE, _AccessMode>
117{
118 uint8_t m_task_id;
119
120public:
121 InitialCountTask(uint8_t task_id, int32_t) : m_task_id(task_id)
122 {}
123
124private:
125 void Run()
126 {
127 // Semaphore is pre-loaded with (_STK_SEM_TEST_TASKS_MAX - 1) permits in ResetTestState.
128 // Tasks 1-4 grab one permit each immediately; task 0 must block (count already at 0).
129 if (m_task_id != 0)
130 {
131 // Should succeed on fast path - count is pre-loaded
134 }
135 else
136 {
137 stk::Sleep(_STK_SEM_TEST_SHORT_SLEEP / 2); // let consumers drain the count first
138
139 // Count should now be 0; this Wait must block until a signal arrives
140 bool acquired = g_TestSemaphore.Wait(_STK_SEM_TEST_SHORT_SLEEP);
141
143
144 int32_t expected = _STK_SEM_TEST_TASKS_MAX - 1;
145
146 printf("initial count: fast-path counter=%d (expected %d), blocked-wait acquired=%d (expected 0)\n",
147 (int)g_SharedCounter, (int)expected, (int)acquired);
148
149 if ((g_SharedCounter == expected) && !acquired)
150 g_TestResult = 1;
151 }
152 }
153};
154
160template <EAccessMode _AccessMode>
161class TimeoutWaitTask : public Task<_STK_SEM_STACK_SIZE, _AccessMode>
162{
163 uint8_t m_task_id;
164
165public:
166 TimeoutWaitTask(uint8_t task_id, int32_t) : m_task_id(task_id)
167 {}
168
169private:
170 void Run()
171 {
172 if (m_task_id == 0)
173 {
174 // Task 0: hold the semaphore at zero; signal only after the test window
175 stk::Sleep(200); // wait well past timeout window before signalling
176 g_TestSemaphore.Signal();
177 }
178 else
179 if (m_task_id == 1)
180 {
181 // Task 1: Wait with 50-tick timeout while semaphore stays at zero
182 stk::Sleep(_STK_SEM_TEST_SHORT_SLEEP); // let task 0 establish the zero state
183
184 int64_t start = GetTimeNowMs();
185 bool acquired = g_TestSemaphore.Wait(50); // 50-tick timeout
186 int64_t elapsed = GetTimeNowMs() - start;
187
188 // Should time out after ~50 ms and return false
189 if (!acquired && elapsed >= 45 && elapsed <= 60)
191
192 if (acquired)
193 g_TestSemaphore.Signal(); // return the token
194 }
195 else
196 if (m_task_id == 2)
197 {
198 // Task 2: verify that a Wait with a generous timeout succeeds after task 0 signals
199 stk::Sleep(210); // after task 0's signal
200
201 if (g_TestSemaphore.Wait(100))
203 }
204
205 if (m_task_id == 2)
206 {
208
209 printf("timeout wait: counter=%d (expected 2)\n", (int)g_SharedCounter);
210
211 if (g_SharedCounter == 2)
212 g_TestResult = 1;
213 }
214 }
215};
216
222template <EAccessMode _AccessMode>
223class ZeroTimeoutTask : public Task<_STK_SEM_STACK_SIZE, _AccessMode>
224{
225 uint8_t m_task_id;
226
227public:
228 ZeroTimeoutTask(uint8_t task_id, int32_t) : m_task_id(task_id)
229 {}
230
231private:
232 void Run()
233 {
234 if (m_task_id == 1)
235 {
236 // Wait(0) on a zero-count semaphore must return false immediately
237 int64_t start = GetTimeNowMs();
238 bool acquired = g_TestSemaphore.Wait(NO_WAIT);
239 int64_t elapsed = GetTimeNowMs() - start;
240
241 if (!acquired && elapsed < _STK_SEM_TEST_SHORT_SLEEP)
243 }
244 else
245 if (m_task_id == 2)
246 {
247 // Pre-load one permit then Wait(0) must succeed immediately
248 g_TestSemaphore.Signal();
249
250 int64_t start = GetTimeNowMs();
251 bool acquired = g_TestSemaphore.Wait(NO_WAIT);
252 int64_t elapsed = GetTimeNowMs() - start;
253
254 if (acquired && elapsed < _STK_SEM_TEST_SHORT_SLEEP)
256 }
257
258 if (m_task_id == 0)
259 {
261
262 printf("zero-timeout wait: counter=%d (expected 2)\n", (int)g_SharedCounter);
263
264 if (g_SharedCounter == 2)
265 g_TestResult = 1;
266 }
267 }
268};
269
275template <EAccessMode _AccessMode>
276class SignalBeforeWaitTask : public Task<_STK_SEM_STACK_SIZE, _AccessMode>
277{
278 uint8_t m_task_id;
280
281public:
282 SignalBeforeWaitTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
283 {}
284
285private:
286 void Run()
287 {
288 if (m_task_id == 0)
289 {
290 // Fire all signals upfront before any waiter is ready
291 for (int32_t i = 0; i < m_iterations; ++i)
292 g_TestSemaphore.Signal();
293
294 // Verify count accumulated correctly
295 uint32_t count_after_signals = g_TestSemaphore.GetCount();
296
298
299 printf("signal-before-wait: count_after_signals=%d (expected %d), counter=%d (expected %d)\n",
300 (int)count_after_signals, (int)m_iterations, (int)g_SharedCounter, (int)m_iterations);
301
302 if ((count_after_signals == (uint32_t)m_iterations) && (g_SharedCounter == m_iterations))
303 g_TestResult = 1;
304 }
305 else
306 if (m_task_id == 1)
307 {
308 // Single consumer drains all pre-posted signals on the fast path
309 stk::Sleep(_STK_SEM_TEST_SHORT_SLEEP); // ensure signals are already posted
310
311 for (int32_t i = 0; i < m_iterations; ++i)
312 {
315 }
316 }
317 }
318};
319
324template <EAccessMode _AccessMode>
325class FIFOOrderTask : public Task<_STK_SEM_STACK_SIZE, _AccessMode>
326{
327 uint8_t m_task_id;
328
329public:
330 FIFOOrderTask(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 // Task 0: producer - stagger signals so all waiters are queued before first wake
339 stk::Sleep(50); // give consumers time to block in FIFO order
340
341 for (int32_t i = 0; i < (_STK_SEM_TEST_TASKS_MAX - 1); ++i)
342 {
343 g_TestSemaphore.Signal();
344 stk::Delay(1); // small pause between signals
345 }
346
348
349 // Check if tasks acquired in FIFO order (1, 2, 3, 4)
350 bool ordered = true;
351 for (int32_t i = 0; i < (_STK_SEM_TEST_TASKS_MAX - 1); ++i)
352 {
353 if (g_AcquisitionOrder[i] != (i + 1))
354 {
355 ordered = false;
356 printf("order violation: position %d has task %d (expected %d)\n",
357 (int)i, (int)g_AcquisitionOrder[i], (int)(i + 1));
358 break;
359 }
360 }
361
362 if (ordered)
363 g_TestResult = 1;
364 }
365 else
366 {
367 // Tasks 1-4: stagger entry so they queue in ascending order
369
370 g_TestSemaphore.Wait();
371
372 // Record acquisition order
373 int32_t idx = g_OrderIndex++;
375 }
376 }
377};
378
384template <EAccessMode _AccessMode>
385class StressTestTask : public Task<_STK_SEM_STACK_SIZE, _AccessMode>
386{
387 uint8_t m_task_id;
389
390public:
391 StressTestTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
392 {}
393
394private:
395 void Run()
396 {
397 if (m_task_id == 0)
398 {
399 // Producer: fire signals continuously
400 for (int32_t i = 0; i < m_iterations; ++i)
401 {
402 g_TestSemaphore.Signal();
403
404 if ((i % 10) == 0)
405 stk::Delay(1);
406 }
407 }
408 else
409 {
410 // Consumers: each drains its share of signals with a generous timeout
411 int32_t share = m_iterations / (_STK_SEM_TEST_TASKS_MAX - 1);
412
413 for (int32_t i = 0; i < share; ++i)
414 {
417 }
418 }
419
421 {
423
424 // Remaining permits in the semaphore plus consumed ones must equal total signals
425 int32_t total_consumed = g_SharedCounter;
426 int32_t remaining = (int32_t)g_TestSemaphore.GetCount();
427
428 printf("stress test: consumed=%d remaining=%d total=%d (expected %d)\n",
429 (int)total_consumed, (int)remaining, (int)(total_consumed + remaining), (int)m_iterations);
430
431 if ((total_consumed + remaining) == m_iterations)
432 g_TestResult = 1;
433 }
434 }
435};
436
443template <EAccessMode _AccessMode>
444class BoundedBufferTask : public Task<_STK_SEM_STACK_SIZE, _AccessMode>
445{
446 uint8_t m_task_id;
448
449public:
450 BoundedBufferTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
451 {}
452
453private:
454 void Run()
455 {
456 if (m_task_id == 0)
457 {
458 // Producer: write items and signal readiness
459 for (int32_t i = 0; i < m_iterations; ++i)
460 {
461 stk::Delay(1); // simulate work
462 g_TestSemaphore.Signal();
463 }
464 }
465 else
466 if (m_task_id == 1)
467 {
468 // Consumer: wait for each item produced by task 0
469 for (int32_t i = 0; i < m_iterations; ++i)
470 {
473 }
474 }
475
476 // Task 1 is the verifier
477 if (m_task_id == 1)
478 {
480
481 printf("bounded buffer: counter=%d (expected %d)\n", (int)g_SharedCounter, (int)m_iterations);
482
484 g_TestResult = 1;
485 }
486 }
487};
488
489// Helper function to reset test state
490static void ResetTestState(uint32_t initial_count = 0)
491{
492 g_TestResult = 0;
493 g_SharedCounter = 0;
494 g_OrderIndex = 0;
495 g_TestComplete = false;
496
497 for (int32_t i = 0; i < _STK_SEM_TEST_TASKS_MAX; ++i)
498 g_AcquisitionOrder[i] = 0;
499
500 // Re-construct the semaphore in-place with the requested initial count
501 g_TestSemaphore.~Semaphore();
502 new (&g_TestSemaphore) sync::Semaphore(initial_count);
503}
504
505} // namespace semaphore
506} // namespace test
507} // namespace stk
508
509static bool NeedsExtendedTasks(const char *test_name)
510{
511 return (strcmp(test_name, "TimeoutWait") != 0) &&
512 (strcmp(test_name, "ZeroTimeout") != 0) &&
513 (strcmp(test_name, "SignalBeforeWait") != 0) &&
514 (strcmp(test_name, "BoundedBuffer") != 0);
515}
516
520template <class TaskType>
521static int32_t RunTest(const char *test_name, int32_t param = 0, uint32_t initial_count = 0)
522{
523 using namespace stk;
524 using namespace stk::test;
525 using namespace stk::test::semaphore;
526
527 printf("Test: %s\n", test_name);
528
529 ResetTestState(initial_count);
530
531 // Create tasks based on test type
532 STK_TASK TaskType task0(0, param);
533 STK_TASK TaskType task1(1, param);
534 STK_TASK TaskType task2(2, param);
535 TaskType task3(3, param);
536 TaskType task4(4, param);
537
538 g_Kernel.AddTask(&task0);
539 g_Kernel.AddTask(&task1);
540 g_Kernel.AddTask(&task2);
541
542 if (NeedsExtendedTasks(test_name))
543 {
544 g_Kernel.AddTask(&task3);
545 g_Kernel.AddTask(&task4);
546 }
547
548 g_Kernel.Start();
549
551
552 printf("Result: %s\n", result == TestContext::SUCCESS_EXIT_CODE ? "PASS" : "FAIL");
553 printf("--------------\n");
554
555 return result;
556}
557
561int main(int argc, char **argv)
562{
563 (void)argc;
564 (void)argv;
565
566 using namespace stk::test::semaphore;
567
569
570 int total_failures = 0, total_success = 0;
571
572 printf("--------------\n");
573
574 g_Kernel.Initialize();
575
576#ifndef __ARM_ARCH_6M__
577
578 // Test 1: Basic Signal/Wait with producer-consumer mutual exclusion
580 total_failures++;
581 else
582 total_success++;
583
584 // Test 2: Non-zero initial count (fast-path drain, then block)
586 total_failures++;
587 else
588 total_success++;
589
590 // Test 3: TimedWait timeout behavior
592 total_failures++;
593 else
594 total_success++;
595
596 // Test 4: Wait(0) non-blocking behavior
598 total_failures++;
599 else
600 total_success++;
601
602 // Test 5: Signal posted before Wait is remembered
604 total_failures++;
605 else
606 total_success++;
607
608 // Test 6: FIFO wakeup ordering
610 total_failures++;
611 else
612 total_success++;
613
614 // Test 7: Classic bounded-buffer producer/consumer
616 total_failures++;
617 else
618 total_success++;
619
620#endif // __ARM_ARCH_6M__
621
622 // Test 8: Stress test (no signals lost)
624 total_failures++;
625 else
626 total_success++;
627
628 int32_t final_result = (total_failures == 0 ? TestContext::SUCCESS_EXIT_CODE : TestContext::DEFAULT_FAILURE_EXIT_CODE);
629
630 printf("##############\n");
631 printf("Total tests: %d\n", total_failures + total_success);
632 printf("Failures: %d\n", (int)total_failures);
633
635 return final_result;
636}
Top-level STK include. Provides the Kernel class template and all built-in task-switching strategies.
Implementation of synchronization primitive: stk::sync::Semaphore.
static int32_t RunTest(const char *test_name, int32_t param=0)
#define STK_TASK
#define _STK_SEM_TEST_SHORT_SLEEP
int main(int argc, char **argv)
static int32_t RunTest(const char *test_name, int32_t param=0, uint32_t initial_count=0)
#define _STK_SEM_TEST_LONG_SLEEP
#define _STK_SEM_TEST_TIMEOUT
#define _STK_SEM_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
const Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:145
Namespace of the test inventory.
Namespace of Semaphore test.
static volatile int32_t g_OrderIndex
static volatile int32_t g_AcquisitionOrder[5]
static volatile int32_t g_SharedCounter
static volatile int32_t g_TestResult
static Kernel< KERNEL_DYNAMIC|KERNEL_SYNC, 5, SwitchStrategyRR, PlatformDefault > g_Kernel
static volatile bool g_TestComplete
static void ResetTestState(uint32_t initial_count=0)
static sync::Semaphore g_TestSemaphore
Concrete implementation of IKernel.
Definition stk.h:83
Task(const Task &)=delete
Counting semaphore primitive for resource management and signaling.
Tests basic Signal/Wait functionality.
BasicSignalWaitTask(uint8_t task_id, int32_t iterations)
void Run()
Entry point of the user task.
Tests semaphore constructed with a non-zero initial count.
InitialCountTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests Wait() timeout behavior.
TimeoutWaitTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests Wait(0) (NO_WAIT) non-blocking behavior.
ZeroTimeoutTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests that Signal() before Wait() is remembered by the counter.
SignalBeforeWaitTask(uint8_t task_id, int32_t iterations)
void Run()
Entry point of the user task.
Tests FIFO ordering of waiting tasks.
FIFOOrderTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Stress test with many interleaved Signal/Wait cycles.
void Run()
Entry point of the user task.
StressTestTask(uint8_t task_id, int32_t iterations)
Tests classic producer/consumer synchronization pattern.
void Run()
Entry point of the user task.
BoundedBufferTask(uint8_t task_id, int32_t iterations)
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.