test-scheduler.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // test_scheduler.cpp - Unit tests for TaskScheduler library
  2. // This file contains comprehensive tests for the TaskScheduler cooperative multitasking library
  3. // Tests cover basic scheduler operations, task lifecycle, timing accuracy, and edge cases
  4. #include <gtest/gtest.h>
  5. #include "Arduino.h"
  6. #include "TaskScheduler.h"
  7. // Global test state - used to capture and verify callback executions
  8. std::vector<std::string> test_output;
  9. /**
  10. * @brief Simple callback function for Task 1 testing
  11. *
  12. * Records execution in the global test_output vector for verification.
  13. * Used primarily for single-task execution tests and timing verification.
  14. * Outputs execution timestamp for debugging timing-related issues.
  15. */
  16. void task1_callback() {
  17. test_output.push_back("Task1 executed");
  18. std::cout << "Task1 executed at " << millis() << "ms" << std::endl;
  19. }
  20. /**
  21. * @brief Simple callback function for Task 2 testing
  22. *
  23. * Records execution with Task2 identifier to distinguish from other tasks
  24. * in multi-task scheduling tests. Essential for verifying proper task
  25. * isolation and execution ordering in concurrent scenarios.
  26. */
  27. void task2_callback() {
  28. test_output.push_back("Task2 executed");
  29. std::cout << "Task2 executed at " << millis() << "ms" << std::endl;
  30. }
  31. /**
  32. * @brief Simple callback function for Task 3 testing
  33. *
  34. * Third task callback used in execution order tests and multi-task scenarios.
  35. * Helps verify that the scheduler can handle multiple concurrent tasks
  36. * and execute them in the correct chronological order.
  37. */
  38. void task3_callback() {
  39. test_output.push_back("Task3 executed");
  40. std::cout << "Task3 executed at " << millis() << "ms" << std::endl;
  41. }
  42. /**
  43. * @brief Callback for testing repeating task functionality
  44. *
  45. * Uses static counter to track execution number, enabling verification
  46. * of proper iteration counting and repeated execution behavior.
  47. * Critical for testing tasks with limited iteration counts and infinite tasks.
  48. */
  49. void repeating_callback() {
  50. static int counter = 0;
  51. counter++;
  52. test_output.push_back("Repeating task #" + std::to_string(counter));
  53. std::cout << "Repeating task #" << counter << " executed at " << millis() << "ms" << std::endl;
  54. }
  55. /**
  56. * @brief Test fixture class for TaskScheduler unit tests
  57. *
  58. * Provides common setup and teardown functionality for all scheduler tests.
  59. * Ensures clean test state between test runs and provides utility methods
  60. * for common test operations like running scheduler with timeout conditions.
  61. */
  62. class SchedulerTest : public ::testing::Test {
  63. protected:
  64. /**
  65. * @brief Set up test environment before each test
  66. *
  67. * Clears any previous test output and initializes timing system.
  68. * Called automatically by Google Test framework before each test method.
  69. * Ensures each test starts with a clean, predictable state.
  70. */
  71. void SetUp() override {
  72. clearTestOutput();
  73. // Reset time by creating new static start point
  74. millis(); // Initialize timing
  75. }
  76. /**
  77. * @brief Clean up test environment after each test
  78. *
  79. * Clears test output to prevent interference between tests.
  80. * Called automatically by Google Test framework after each test method.
  81. * Ensures no test artifacts affect subsequent tests.
  82. */
  83. void TearDown() override {
  84. clearTestOutput();
  85. }
  86. /**
  87. * @brief Helper method to run scheduler until condition is met or timeout
  88. *
  89. * Executes the scheduler repeatedly until either the specified condition
  90. * returns true or the timeout period expires. Essential for testing
  91. * time-dependent scheduler behavior without creating infinite loops.
  92. *
  93. * @param ts Reference to the Scheduler object to execute
  94. * @param condition Lambda function that returns true when test condition is met
  95. * @param timeout_ms Maximum time to wait before giving up (default: 1000ms)
  96. * @return true if condition was met within timeout, false otherwise
  97. */
  98. bool runSchedulerUntil(Scheduler& ts, std::function<bool()> condition, unsigned long timeout_ms = 1000) {
  99. return waitForCondition([&]() {
  100. ts.execute();
  101. return condition();
  102. }, timeout_ms);
  103. }
  104. };
  105. /**
  106. * @brief Test basic scheduler object creation
  107. *
  108. * Verifies that a Scheduler object can be instantiated without throwing
  109. * exceptions or causing crashes. This is the most fundamental test that
  110. * ensures the library can be used at all.
  111. */
  112. TEST_F(SchedulerTest, BasicSchedulerCreation) {
  113. Scheduler ts;
  114. EXPECT_TRUE(true); // Scheduler creation should not throw
  115. }
  116. /**
  117. * @brief Test scheduler behavior in initial empty state
  118. *
  119. * Verifies that calling execute() on an empty scheduler (no tasks added)
  120. * behaves safely and doesn't produce any unexpected output or crashes.
  121. * Important for validating scheduler robustness in edge cases.
  122. */
  123. TEST_F(SchedulerTest, SchedulerInitialState) {
  124. Scheduler ts;
  125. // Execute empty scheduler - should not crash
  126. ts.execute();
  127. EXPECT_EQ(getTestOutputCount(), 0);
  128. }
  129. /**
  130. * @brief Test execution of a single task
  131. *
  132. * Creates one task scheduled to run once after 100ms and verifies:
  133. * 1. Task executes within reasonable timeout period
  134. * 2. Task executes exactly once
  135. * 3. Callback produces expected output
  136. * This test validates core task scheduling functionality.
  137. */
  138. TEST_F(SchedulerTest, SingleTaskExecution) {
  139. Scheduler ts;
  140. // Create a task that runs once after 100ms
  141. Task task1(100, 1, &task1_callback, &ts, true);
  142. // Run scheduler until task executes
  143. bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 1; });
  144. EXPECT_TRUE(success) << "Task did not execute within timeout";
  145. EXPECT_EQ(getTestOutputCount(), 1);
  146. EXPECT_EQ(getTestOutput(0), "Task1 executed");
  147. }
  148. /**
  149. * @brief Test concurrent execution of multiple tasks
  150. *
  151. * Creates three tasks with different execution delays (50ms, 100ms, 150ms)
  152. * and verifies:
  153. * 1. All tasks execute within timeout
  154. * 2. Tasks execute in chronological order (not creation order)
  155. * 3. Each task produces correct output
  156. * Critical for validating multi-task scheduling and execution ordering.
  157. */
  158. TEST_F(SchedulerTest, MultipleTaskExecution) {
  159. Scheduler ts;
  160. // Create multiple tasks with different intervals
  161. Task task1(50, 1, &task1_callback, &ts, true); // Run once after 50ms
  162. Task task2(100, 1, &task2_callback, &ts, true); // Run once after 100ms
  163. Task task3(150, 1, &task3_callback, &ts, true); // Run once after 150ms
  164. // Run scheduler until all tasks execute
  165. bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 3; });
  166. EXPECT_TRUE(success) << "Not all tasks executed within timeout";
  167. EXPECT_EQ(getTestOutputCount(), 3);
  168. EXPECT_EQ(getTestOutput(0), "Task1 executed");
  169. EXPECT_EQ(getTestOutput(1), "Task2 executed");
  170. EXPECT_EQ(getTestOutput(2), "Task3 executed");
  171. }
  172. /**
  173. * @brief Test task with limited number of iterations
  174. *
  175. * Creates a task configured to run exactly 3 times with 80ms intervals
  176. * and verifies:
  177. * 1. Task executes the correct number of times
  178. * 2. Executions are properly numbered/sequenced
  179. * 3. Task stops after reaching iteration limit
  180. * Validates the iteration counting mechanism.
  181. */
  182. TEST_F(SchedulerTest, RepeatingTaskExecution) {
  183. Scheduler ts;
  184. // Create a task that runs 3 times with 80ms interval
  185. Task repeating_task(80, 3, &repeating_callback, &ts, true);
  186. // Run scheduler until task executes 3 times
  187. bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 3; }, 1500);
  188. EXPECT_TRUE(success) << "Repeating task did not complete within timeout";
  189. EXPECT_EQ(getTestOutputCount(), 3);
  190. EXPECT_EQ(getTestOutput(0), "Repeating task #1");
  191. EXPECT_EQ(getTestOutput(1), "Repeating task #2");
  192. EXPECT_EQ(getTestOutput(2), "Repeating task #3");
  193. }
  194. /**
  195. * @brief Test task configured for infinite execution
  196. *
  197. * Creates a task with TASK_FOREVER iterations and runs scheduler for
  198. * a fixed time period (250ms) to verify:
  199. * 1. Task continues executing indefinitely
  200. * 2. Execution frequency matches expected interval (50ms)
  201. * 3. Task doesn't stop after arbitrary number of executions
  202. * Tests the infinite iteration functionality.
  203. */
  204. TEST_F(SchedulerTest, InfiniteRepeatingTask) {
  205. Scheduler ts;
  206. // Create a task that runs indefinitely with 50ms interval
  207. Task infinite_task(50, TASK_FOREVER, &repeating_callback, &ts, true);
  208. // Run for a specific time and count executions
  209. unsigned long start_time = millis();
  210. while (millis() - start_time < 250) { // Run for 250ms
  211. ts.execute();
  212. delay(10);
  213. }
  214. // Should have executed approximately 250/50 = 5 times (allowing for timing variance)
  215. EXPECT_GE(getTestOutputCount(), 3);
  216. EXPECT_LE(getTestOutputCount(), 7);
  217. }
  218. /**
  219. * @brief Test task enable/disable functionality
  220. *
  221. * Creates a task in disabled state, verifies it doesn't execute,
  222. * then enables it and verifies execution occurs. Tests:
  223. * 1. Disabled tasks don't execute even if scheduler runs
  224. * 2. Enabling a task allows it to execute on next scheduler pass
  225. * 3. Task state changes are properly handled
  226. * Critical for dynamic task management.
  227. */
  228. TEST_F(SchedulerTest, TaskEnableDisable) {
  229. Scheduler ts;
  230. // Create a disabled task
  231. Task task1(100, 1, &task1_callback, &ts, false);
  232. // Run scheduler - task should not execute (it's disabled)
  233. delay(150);
  234. ts.execute();
  235. EXPECT_EQ(getTestOutputCount(), 0);
  236. // Now enable the task
  237. task1.enable();
  238. // Run scheduler - task should execute now
  239. bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 1; });
  240. EXPECT_TRUE(success) << "Task did not execute after being enabled";
  241. EXPECT_EQ(getTestOutputCount(), 1);
  242. EXPECT_EQ(getTestOutput(0), "Task1 executed");
  243. }
  244. /**
  245. * @brief Test disabling a task while it's actively running
  246. *
  247. * Creates an infinite repeating task, lets it execute several times,
  248. * then disables it and verifies no further executions occur. Tests:
  249. * 1. Running tasks can be disabled mid-execution
  250. * 2. Disabled tasks immediately stop executing
  251. * 3. Task state changes take effect on next scheduler pass
  252. * Important for dynamic task control scenarios.
  253. */
  254. TEST_F(SchedulerTest, TaskDisableDuringExecution) {
  255. Scheduler ts;
  256. // Create a repeating task
  257. Task repeating_task(60, TASK_FOREVER, &repeating_callback, &ts, true);
  258. // Let it run a few times
  259. bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 2; });
  260. EXPECT_TRUE(success);
  261. size_t executions_before_disable = getTestOutputCount();
  262. // Disable the task
  263. repeating_task.disable();
  264. // Continue running scheduler
  265. delay(200);
  266. for (int i = 0; i < 10; i++) {
  267. ts.execute();
  268. delay(20);
  269. }
  270. // Should not have executed any more times
  271. EXPECT_EQ(getTestOutputCount(), executions_before_disable);
  272. }
  273. /**
  274. * @brief Test scheduler behavior with no registered tasks
  275. *
  276. * Runs scheduler execute() method multiple times when no tasks are
  277. * registered and verifies:
  278. * 1. No crashes or exceptions occur
  279. * 2. No unexpected output is generated
  280. * 3. Scheduler handles empty task list gracefully
  281. * Edge case test for scheduler robustness.
  282. */
  283. TEST_F(SchedulerTest, SchedulerWithNoTasks) {
  284. Scheduler ts;
  285. // Execute scheduler with no tasks multiple times
  286. for (int i = 0; i < 100; i++) {
  287. ts.execute();
  288. delay(1);
  289. }
  290. EXPECT_EQ(getTestOutputCount(), 0);
  291. }
  292. /**
  293. * @brief Test correct execution order of tasks with different schedules
  294. *
  295. * Creates three tasks with deliberately mixed creation order vs execution order:
  296. * - task_late: 200ms delay (should execute last)
  297. * - task_early: 50ms delay (should execute first)
  298. * - task_mid: 100ms delay (should execute second)
  299. *
  300. * Verifies scheduler executes tasks in chronological order, not creation order.
  301. * Critical for validating proper task scheduling algorithm implementation.
  302. */
  303. TEST_F(SchedulerTest, TaskExecutionOrder) {
  304. Scheduler ts;
  305. // Create tasks that should execute in specific order
  306. Task task_late(200, 1, &task3_callback, &ts, true); // Latest
  307. Task task_early(50, 1, &task1_callback, &ts, true); // Earliest
  308. Task task_mid(100, 1, &task2_callback, &ts, true); // Middle
  309. // Run until all execute
  310. bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 3; });
  311. EXPECT_TRUE(success);
  312. EXPECT_EQ(getTestOutputCount(), 3);
  313. // Tasks should execute in chronological order regardless of creation order
  314. EXPECT_EQ(getTestOutput(0), "Task1 executed"); // First (50ms)
  315. EXPECT_EQ(getTestOutput(1), "Task2 executed"); // Second (100ms)
  316. EXPECT_EQ(getTestOutput(2), "Task3 executed"); // Third (200ms)
  317. }
  318. /**
  319. * @brief Test scheduler performance with large number of concurrent tasks
  320. *
  321. * Creates 10 tasks with slightly staggered execution times (100ms + i*10ms)
  322. * and verifies:
  323. * 1. All tasks execute successfully within timeout
  324. * 2. No performance degradation causes task loss
  325. * 3. Scheduler can handle realistic task loads
  326. *
  327. * Stress test for scheduler scalability and performance under load.
  328. * Uses smart pointers to manage task memory automatically.
  329. */
  330. TEST_F(SchedulerTest, SchedulerHandlesLargeNumberOfTasks) {
  331. Scheduler ts;
  332. std::vector<std::unique_ptr<Task>> tasks;
  333. // Create many tasks with similar timing
  334. for (int i = 0; i < 10; i++) {
  335. auto task = std::make_unique<Task>(100 + i * 10, 1, &task1_callback, &ts, true);
  336. tasks.push_back(std::move(task));
  337. }
  338. // Run until all tasks execute
  339. bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 10; }, 2000);
  340. EXPECT_TRUE(success) << "Not all tasks executed within timeout";
  341. EXPECT_EQ(getTestOutputCount(), 10);
  342. }
  343. /**
  344. * @brief Main test runner function
  345. *
  346. * Initializes Google Test framework and runs all registered test cases.
  347. * Called by the test execution environment to start the testing process.
  348. * Returns 0 for success, non-zero for test failures.
  349. *
  350. * @param argc Command line argument count
  351. * @param argv Command line argument values
  352. * @return Test execution status (0 = success)
  353. */
  354. int main(int argc, char **argv) {
  355. ::testing::InitGoogleTest(&argc, argv);
  356. return RUN_ALL_TESTS();
  357. }