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
stktest_switchstrategyswroundrobin.cpp
Go to the documentation of this file.
1/*
2 * SuperTinyKernel(TM) RTOS: 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 "stktest.h"
11
12namespace stk {
13namespace test {
14
15// ============================================================================ //
16// =================== SwitchStrategySmoothWeightedRoundRobin ================= //
17// ============================================================================ //
18
19TEST_GROUP(SwitchStrategySWRoundRobin)
20{
21 void setup() {}
22 void teardown()
23 {
24 g_TestContext.ExpectAssert(false);
25 g_TestContext.RethrowAssertException(true);
26 }
27};
28
29TEST(SwitchStrategySWRoundRobin, GetFirstEmpty)
30{
32
33 try
34 {
35 g_TestContext.ExpectAssert(true);
36 rr.GetFirst();
37 CHECK_TEXT(false, "expecting assertion when empty");
38 }
39 catch (TestAssertPassed &pass)
40 {
41 CHECK(true);
42 g_TestContext.ExpectAssert(false);
43 }
44}
45
46TEST(SwitchStrategySWRoundRobin, GetNextEmpty)
47{
50 ITaskSwitchStrategy *strategy = kernel.GetSwitchStrategy();
51
52 kernel.Initialize();
53
54 kernel.AddTask(&task1);
55 kernel.RemoveTask(&task1);
56 CHECK_EQUAL(0, strategy->GetSize());
57
58 // expect to return NULL which puts core into a sleep mode, current is ignored by this strategy
59 CHECK_EQUAL(0, strategy->GetNext());
60}
61
62TEST(SwitchStrategySWRoundRobin, OnTaskDeadlineMissedNotSupported)
63{
66 ITaskSwitchStrategy *strategy = kernel.GetSwitchStrategy();
67
68 kernel.Initialize();
69 kernel.AddTask(&task1);
70
71 try
72 {
73 g_TestContext.ExpectAssert(true);
74 strategy->OnTaskDeadlineMissed(strategy->GetFirst());
75 CHECK_TEXT(false, "expecting assertion - OnTaskDeadlineMissed not supported");
76 }
77 catch (TestAssertPassed &pass)
78 {
79 CHECK(true);
80 g_TestContext.ExpectAssert(false);
81 }
82
83 // we need this workaround to pass 100% coverage test by blocking the exception
84 g_TestContext.ExpectAssert(true);
85 g_TestContext.RethrowAssertException(false);
86 strategy->OnTaskDeadlineMissed(strategy->GetFirst());
87}
88
89TEST(SwitchStrategySWRoundRobin, EndlessNext)
90{
92 TaskMockW<1, ACCESS_USER> task1, task2, task3;
93
94 kernel.Initialize();
95 kernel.AddTask(&task1);
96
97 ITaskSwitchStrategy *strategy = kernel.GetSwitchStrategy();
98
99 IKernelTask *next = strategy->GetFirst();
100
101 // --- Stage 1: 1 task only ---------------------------------------------
102
103 // The scheduler must ALWAYS return task1
104 for (int32_t i = 0; i < 10; i++)
105 {
106 next = strategy->GetNext();
107 CHECK_EQUAL_TEXT(&task1, next->GetUserTask(), "Single task must always be selected");
108 }
109
110 // --- Stage 2: add second task -----------------------------------------
111
112 kernel.AddTask(&task2);
113
114 // Both tasks must appear, and none should be starved
115 bool seen1 = false;
116 bool seen2 = false;
117 for (int i = 0; i < 10; i++)
118 {
119 next = strategy->GetNext();
120 if (next->GetUserTask() == &task1) seen1 = true;
121 if (next->GetUserTask() == &task2) seen2 = true;
122 }
123 CHECK_TEXT(seen1, "Task1 must be selected after adding Task2");
124 CHECK_TEXT(seen2, "Task2 must be selected after adding Task2");
125
126 // --- Stage 3: add third task ------------------------------------------
127
128 kernel.AddTask(&task3);
129
130 seen1 = seen2 = false;
131 bool seen3 = false;
132 for (int32_t i = 0; i < 20; i++)
133 {
134 next = strategy->GetNext();
135 if (next->GetUserTask() == &task1) seen1 = true;
136 if (next->GetUserTask() == &task2) seen2 = true;
137 if (next->GetUserTask() == &task3) seen3 = true;
138 }
139
140 CHECK_TEXT(seen1, "Task1 must run after adding Task3");
141 CHECK_TEXT(seen2, "Task2 must run after adding Task3");
142 CHECK_TEXT(seen3, "Task3 must run after adding Task3");
143
144 // --- Stage 4: remove task1 --------------------------------------------
145
146 kernel.RemoveTask(&task1);
147
148 seen2 = seen3 = false;
149 for (int32_t i = 0; i < 10; i++)
150 {
151 next = strategy->GetNext();
152 if (next->GetUserTask() == &task2) seen2 = true;
153 if (next->GetUserTask() == &task3) seen3 = true;
154 CHECK_TEXT(next->GetUserTask() != &task1, "Task1 must not be selected after removal");
155 }
156 CHECK_TEXT(seen2, "Task2 must run after removing Task1");
157 CHECK_TEXT(seen3, "Task3 must run after removing Task1");
158}
159
160TEST(SwitchStrategySWRoundRobin, Algorithm)
161{
163 TaskMockW<1, ACCESS_USER> task1; // weight 1
164 TaskMockW<2, ACCESS_USER> task2; // weight 2
165 TaskMockW<3, ACCESS_USER> task3; // weight 3
166
167 kernel.Initialize();
168 kernel.AddTask(&task1);
169 kernel.AddTask(&task2);
170 kernel.AddTask(&task3);
171
172 ITaskSwitchStrategy *strategy = kernel.GetSwitchStrategy();
173 IKernelTask *next = strategy->GetFirst();
174
175 // scheduling stats
176 int32_t count1 = 0, count2 = 0, count3 = 0;
177
178 // Run enough steps to reach stable proportions
179 const int32_t steps = 120; // increased steps for better statistical stability
180 for (int32_t i = 0; i < steps; i++)
181 {
182 next = strategy->GetNext();
183
184 if (next->GetUserTask() == &task1) ++count1;
185 else if (next->GetUserTask() == &task2) ++count2;
186 else if (next->GetUserTask() == &task3) ++count3;
187 else CHECK_TEXT(false, "Unknown task selected");
188 }
189
190 const int32_t w1 = 1, w2 = 2, w3 = 3;
191 const int32_t total_w = w1 + w2 + w3;
192
193 const int32_t exp1 = (steps * w1) / total_w; // integer expected counts
194 const int32_t exp2 = (steps * w2) / total_w;
195 const int32_t exp3 = steps - exp1 - exp2; // ensure sum == steps
196
197 // Relative tolerance (fraction). 25% is conservative for small sample sizes.
198 const float tol_frac = 0.25f;
199
200 auto within_tol = [&](int expected, int actual, float frac) -> bool
201 {
202 int tol = (int)(expected * frac) + 1; // +1 to avoid zero tolerance for small expected
203 int diff = expected > actual ? expected - actual : actual - expected;
204 return diff <= tol;
205 };
206
207 CHECK_TRUE_TEXT(within_tol(exp1, count1, tol_frac), "Task1 proportion off (weight 1)");
208 CHECK_TRUE_TEXT(within_tol(exp2, count2, tol_frac), "Task2 proportion off (weight 2)");
209 CHECK_TRUE_TEXT(within_tol(exp3, count3, tol_frac), "Task3 proportion off (weight 3)");
210
211 // Validate non-starvation: each must get at least one selection
212 CHECK_TEXT(count1 > 0, "Task1 must run at least once");
213 CHECK_TEXT(count2 > 0, "Task2 must run at least once");
214 CHECK_TEXT(count3 > 0, "Task3 must run at least once");
215
216 // Remove highest-weight task, check proportions adjust to 1:2
217 kernel.RemoveTask(&task3);
218
219 // reset counters and sample again
220 count1 = count2 = 0;
221
222 // advance next once to avoid stuck reference (optional)
223 next = strategy->GetNext();
224
225 for (int32_t i = 0; i < steps; i++)
226 {
227 next = strategy->GetNext();
228
229 if (next->GetUserTask() == &task1) ++count1;
230 else if (next->GetUserTask() == &task2) ++count2;
231 else CHECK_TEXT(false, "Unknown task selected after removal");
232 }
233
234 const int32_t nw1 = 1, nw2 = 2;
235 const int32_t ntotal = nw1 + nw2;
236 const int32_t nexp1 = (steps * nw1) / ntotal;
237 const int32_t nexp2 = steps - nexp1;
238
239 CHECK_TRUE_TEXT(within_tol(nexp1, count1, tol_frac), "Task1 proportion off after removal");
240 CHECK_TRUE_TEXT(within_tol(nexp2, count2, tol_frac), "Task2 proportion off after removal");
241
242 CHECK_TEXT(count1 > 0, "Task1 must run after removal");
243 CHECK_TEXT(count2 > 0, "Task2 must run after removal");
244}
245
246} // namespace stk
247} // namespace test
Namespace of STK package.
SwitchStrategySmoothWeightedRoundRobin SwitchStrategySWRR
Shorthand alias for SwitchStrategySmoothWeightedRoundRobin.
Namespace of the test inventory.
TestContext g_TestContext
Global instance of the TestContext.
Definition stktest.cpp:16
TEST_GROUP(Kernel)
TEST(Kernel, MaxTasks)
Concrete implementation of IKernel.
Definition stk.h:83
void Initialize(uint32_t resolution_us=PERIODICITY_DEFAULT)
Prepare kernel for use: reset state, configure the platform, and register the service singleton.
Definition stk.h:805
ITaskSwitchStrategy * GetSwitchStrategy()
Get task-switching strategy instance owned by this kernel.
Definition stk.h:959
void RemoveTask(ITask *user_task)
Remove a previously added task from the kernel before Start().
Definition stk.h:895
void AddTask(ITask *user_task)
Register task for a soft real-time (SRT) scheduling.
Definition stk.h:832
Scheduling-strategy-facing interface for a kernel task slot.
Definition stk_common.h:493
virtual ITask * GetUserTask()=0
Get user task.
Interface for a task switching strategy implementation.
Definition stk_common.h:782
virtual size_t GetSize() const =0
Get number of tasks currently managed by this strategy.
virtual IKernelTask * GetNext()=0
Advance the internal iterator and return the next runnable task.
virtual IKernelTask * GetFirst() const =0
Get first task.
virtual bool OnTaskDeadlineMissed(IKernelTask *task)=0
Notification that a task has exceeded its HRT deadline; returns whether the strategy can recover with...
IKernelTask * GetFirst() const
Get first task in the managed set (used by the kernel for initial scheduling).
Throwable class for catching assertions from STK_ASSERT_HANDLER().
Definition stktest.h:67
Task mock for SwitchStrategySmoothWeightedRoundRobin and similar algorithms.
Definition stktest.h:357