test-scheduler-basic.cpp 16 KB

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