test-scheduler-thread-safe.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. // test-scheduler-thread-safe.cpp - Unit tests for _TASK_THREAD_SAFE functionality
  2. // This file contains tests for the TaskScheduler thread-safe operations
  3. // Covers multi-threaded task control, ISR simulation, and request queue operations
  4. //
  5. // THREAD SAFETY TESTING NOTES:
  6. // ============================
  7. // 1. Tests use C++ std::thread for simulating multi-threaded environments
  8. // 2. Queue implementation uses std::queue with mutex protection
  9. // 3. Tests verify that requestAction() safely handles concurrent requests
  10. // 4. StatusRequest signaling from simulated ISRs is tested
  11. // 5. Worker thread task manipulation is verified
  12. //
  13. // Linux/Desktop Implementation:
  14. // - Uses std::queue + std::mutex instead of FreeRTOS queue
  15. // - Simulates ISR context using thread-local storage
  16. // - Compatible with Google Test framework on Ubuntu
  17. // #define _TASK_HEADER_AND_CPP
  18. // #define _TASK_THREAD_SAFE
  19. // #define _TASK_STATUS_REQUEST
  20. // #define _TASK_TIMEOUT
  21. #include <gtest/gtest.h>
  22. #include "Arduino.h"
  23. #include "TaskSchedulerDeclarations.h"
  24. #include "test-queue-impl.h"
  25. #include <thread>
  26. #include <atomic>
  27. // Thread-local storage to simulate ISR context
  28. thread_local bool isInISRContext = false;
  29. // ============================================
  30. // Global Test State
  31. // ============================================
  32. std::vector<std::string> test_output;
  33. std::atomic<int> task_execution_count{0};
  34. std::atomic<int> thread1_requests{0};
  35. std::atomic<int> thread2_requests{0};
  36. std::atomic<int> isr_requests{0};
  37. // ============================================
  38. // Test Callbacks
  39. // ============================================
  40. void thread_safe_task_callback() {
  41. task_execution_count++;
  42. test_output.push_back("Thread-safe task executed");
  43. }
  44. void fast_task_callback() {
  45. test_output.push_back("Fast task executed");
  46. }
  47. void interval_test_callback() {
  48. static int counter = 0;
  49. counter++;
  50. test_output.push_back("Interval task #" + std::to_string(counter));
  51. }
  52. // ============================================
  53. // Test Fixture
  54. // ============================================
  55. class ThreadSafeTest : public ::testing::Test {
  56. protected:
  57. void SetUp() override {
  58. test_output.clear();
  59. task_execution_count = 0;
  60. thread1_requests = 0;
  61. thread2_requests = 0;
  62. isr_requests = 0;
  63. clearTaskRequestQueue();
  64. isInISRContext = false;
  65. }
  66. void TearDown() override {
  67. clearTaskRequestQueue();
  68. }
  69. // Helper: Run scheduler for specified duration
  70. void runScheduler(Scheduler& ts, unsigned long duration_ms) {
  71. unsigned long start = millis();
  72. while (millis() - start < duration_ms) {
  73. ts.execute();
  74. delay(1);
  75. }
  76. }
  77. // Helper: Wait for condition with timeout
  78. template<typename Condition>
  79. bool waitForCondition(Condition cond, unsigned long timeout_ms) {
  80. unsigned long start = millis();
  81. while (millis() - start < timeout_ms) {
  82. if (cond()) return true;
  83. delay(10);
  84. }
  85. return false;
  86. }
  87. };
  88. // ============================================
  89. // Basic Queue Tests
  90. // ============================================
  91. TEST_F(ThreadSafeTest, QueueEnqueueDequeue) {
  92. _task_request_t req;
  93. req.req_type = TASK_REQUEST_ENABLE_0;
  94. req.object_ptr = (void*)0x1234;
  95. req.param1 = 100;
  96. // Enqueue
  97. EXPECT_TRUE(_task_enqueue_request(&req));
  98. EXPECT_EQ(getQueueSize(), 1);
  99. // Dequeue
  100. _task_request_t dequeued;
  101. EXPECT_TRUE(_task_dequeue_request(&dequeued));
  102. EXPECT_EQ(getQueueSize(), 0);
  103. // Verify data
  104. EXPECT_EQ(dequeued.req_type, TASK_REQUEST_ENABLE_0);
  105. EXPECT_EQ(dequeued.object_ptr, (void*)0x1234);
  106. EXPECT_EQ(dequeued.param1, 100);
  107. }
  108. TEST_F(ThreadSafeTest, QueueFIFOOrder) {
  109. // Enqueue multiple requests
  110. for (int i = 0; i < 10; i++) {
  111. _task_request_t req;
  112. req.req_type = TASK_REQUEST_ENABLE_0;
  113. req.param1 = i;
  114. EXPECT_TRUE(_task_enqueue_request(&req));
  115. }
  116. EXPECT_EQ(getQueueSize(), 10);
  117. // Dequeue and verify order
  118. for (int i = 0; i < 10; i++) {
  119. _task_request_t req;
  120. EXPECT_TRUE(_task_dequeue_request(&req));
  121. EXPECT_EQ(req.param1, (unsigned long)i);
  122. }
  123. EXPECT_EQ(getQueueSize(), 0);
  124. }
  125. TEST_F(ThreadSafeTest, QueueEmptyDequeue) {
  126. _task_request_t req;
  127. EXPECT_FALSE(_task_dequeue_request(&req));
  128. }
  129. // ============================================
  130. // Thread-Safe Task Control Tests
  131. // ============================================
  132. TEST_F(ThreadSafeTest, RequestActionEnableTask) {
  133. Scheduler ts;
  134. Task testTask(100, 5, &thread_safe_task_callback, &ts, false);
  135. // Task should be disabled initially
  136. EXPECT_FALSE(testTask.isEnabled());
  137. // Request enable via requestAction
  138. bool success = ts.requestAction(&testTask, TASK_REQUEST_ENABLE_0, 0, 0, 0, 0, 0);
  139. EXPECT_TRUE(success);
  140. EXPECT_EQ(getQueueSize(), 1);
  141. // Process requests
  142. ts.execute();
  143. // Task should now be enabled
  144. EXPECT_TRUE(testTask.isEnabled());
  145. EXPECT_EQ(getQueueSize(), 0);
  146. }
  147. TEST_F(ThreadSafeTest, RequestActionDisableTask) {
  148. Scheduler ts;
  149. Task testTask(100, 5, &thread_safe_task_callback, &ts, true);
  150. // Task should be enabled initially
  151. EXPECT_TRUE(testTask.isEnabled());
  152. // Request disable via requestAction
  153. bool success = ts.requestAction(&testTask, TASK_REQUEST_DISABLE_0, 0, 0, 0, 0, 0);
  154. EXPECT_TRUE(success);
  155. // Process requests
  156. ts.execute();
  157. // Task should now be disabled
  158. EXPECT_FALSE(testTask.isEnabled());
  159. }
  160. TEST_F(ThreadSafeTest, RequestActionChangeInterval) {
  161. Scheduler ts;
  162. Task testTask(100, TASK_FOREVER, &thread_safe_task_callback, &ts, true);
  163. EXPECT_EQ(testTask.getInterval(), 100UL);
  164. // Request interval change
  165. ts.requestAction(&testTask, TASK_REQUEST_SETINTERVAL_1, 500, 0, 0, 0, 0);
  166. // Process requests
  167. ts.execute();
  168. // Interval should be updated
  169. EXPECT_EQ(testTask.getInterval(), 500UL);
  170. }
  171. // ============================================
  172. // Multi-Threaded Tests
  173. // ============================================
  174. TEST_F(ThreadSafeTest, ConcurrentEnableRequests) {
  175. Scheduler ts;
  176. Task task1(50, TASK_FOREVER, &thread_safe_task_callback, &ts, false);
  177. Task task2(50, TASK_FOREVER, &fast_task_callback, &ts, false);
  178. // Launch two threads that both try to enable tasks
  179. std::thread t1([&]() {
  180. for (int i = 0; i < 10; i++) {
  181. ts.requestAction(&task1, TASK_REQUEST_ENABLE_0, 0, 0, 0, 0, 0);
  182. thread1_requests++;
  183. std::this_thread::sleep_for(std::chrono::milliseconds(5));
  184. }
  185. });
  186. std::thread t2([&]() {
  187. for (int i = 0; i < 10; i++) {
  188. ts.requestAction(&task2, TASK_REQUEST_ENABLE_0, 0, 0, 0, 0, 0);
  189. thread2_requests++;
  190. std::this_thread::sleep_for(std::chrono::milliseconds(5));
  191. }
  192. });
  193. // Process requests in main thread
  194. std::thread scheduler_thread([&]() {
  195. for (int i = 0; i < 100; i++) {
  196. ts.execute();
  197. std::this_thread::sleep_for(std::chrono::milliseconds(10));
  198. }
  199. });
  200. t1.join();
  201. t2.join();
  202. scheduler_thread.join();
  203. // All requests should have been processed
  204. EXPECT_EQ(thread1_requests.load(), 10);
  205. EXPECT_EQ(thread2_requests.load(), 10);
  206. EXPECT_TRUE(task1.isEnabled());
  207. EXPECT_TRUE(task2.isEnabled());
  208. }
  209. TEST_F(ThreadSafeTest, WorkerThreadChangesInterval) {
  210. Scheduler ts;
  211. Task testTask(100, TASK_FOREVER, &interval_test_callback, &ts, true);
  212. std::atomic<bool> worker_running{true};
  213. // Worker thread that periodically changes interval
  214. std::thread worker([&]() {
  215. int intervals[] = {50, 100, 150, 200, 250};
  216. int idx = 0;
  217. while (worker_running && idx < 5) {
  218. std::this_thread::sleep_for(std::chrono::milliseconds(intervals[idx]+5));
  219. ts.requestAction(&testTask, TASK_REQUEST_SETINTERVAL_1,
  220. intervals[idx], 0, 0, 0, 0);
  221. idx++;
  222. }
  223. });
  224. // Run scheduler
  225. runScheduler(ts, 600);
  226. worker_running = false;
  227. worker.join();
  228. // Task should still be running
  229. EXPECT_TRUE(testTask.isEnabled());
  230. // Interval should have been changed multiple times
  231. EXPECT_GT(test_output.size(), 3);
  232. }
  233. // ============================================
  234. // Simulated ISR Tests
  235. // ============================================
  236. TEST_F(ThreadSafeTest, ISRTriggersStatusRequest) {
  237. Scheduler ts;
  238. StatusRequest buttonPressed;
  239. Task responseTask(TASK_IMMEDIATE, TASK_ONCE, &fast_task_callback, &ts, false);
  240. // Set up task to wait for status request
  241. responseTask.waitFor(&buttonPressed);
  242. // Simulate ISR in separate thread
  243. std::thread isr_simulation([&]() {
  244. isInISRContext = true; // Mark as ISR context
  245. std::this_thread::sleep_for(std::chrono::milliseconds(50));
  246. // Signal from "ISR"
  247. ts.requestAction(&buttonPressed, TASK_SR_REQUEST_SIGNALCOMPLETE_1, 0, 0, 0, 0, 0);
  248. isr_requests++;
  249. isInISRContext = false;
  250. });
  251. // Run scheduler
  252. runScheduler(ts, 200);
  253. isr_simulation.join();
  254. // Task should have executed in response to signal
  255. EXPECT_EQ(isr_requests.load(), 1);
  256. EXPECT_GT(test_output.size(), 0);
  257. EXPECT_EQ(test_output[0], "Fast task executed");
  258. }
  259. TEST_F(ThreadSafeTest, MultipleISRsWithDifferentRequests) {
  260. Scheduler ts;
  261. Task task1(200, TASK_FOREVER, &thread_safe_task_callback, &ts, false);
  262. Task task2(200, TASK_FOREVER, &fast_task_callback, &ts, false);
  263. // Simulate multiple ISRs
  264. std::thread isr1([&]() {
  265. for (int i = 0; i < 5; i++) {
  266. std::this_thread::sleep_for(std::chrono::milliseconds(30));
  267. ts.requestAction(&task1, TASK_REQUEST_ENABLE_0, 0, 0, 0, 0, 0);
  268. }
  269. });
  270. std::thread isr2([&]() {
  271. for (int i = 0; i < 5; i++) {
  272. std::this_thread::sleep_for(std::chrono::milliseconds(35));
  273. ts.requestAction(&task2, TASK_REQUEST_ENABLE_0, 0, 0, 0, 0, 0);
  274. }
  275. });
  276. // Run scheduler
  277. runScheduler(ts, 300);
  278. isr1.join();
  279. isr2.join();
  280. // Both tasks should be enabled
  281. EXPECT_TRUE(task1.isEnabled());
  282. EXPECT_TRUE(task2.isEnabled());
  283. }
  284. // ============================================
  285. // Stress Tests
  286. // ============================================
  287. TEST_F(ThreadSafeTest, HighFrequencyRequests) {
  288. Scheduler ts;
  289. Task testTask(10, TASK_FOREVER, &thread_safe_task_callback, &ts, true);
  290. std::atomic<int> successful_requests{0};
  291. // Many threads sending requests rapidly
  292. std::vector<std::thread> threads;
  293. for (int t = 0; t < 5; t++) {
  294. threads.emplace_back([&]() {
  295. for (int i = 0; i < 20; i++) {
  296. if (ts.requestAction(&testTask, TASK_REQUEST_SETINTERVAL_1,
  297. 10 + i, 0, 0, 0, 0)) {
  298. successful_requests++;
  299. }
  300. std::this_thread::sleep_for(std::chrono::milliseconds(1));
  301. }
  302. });
  303. }
  304. // Process requests
  305. for (int i = 0; i < 500; i++) {
  306. ts.execute();
  307. delay(1);
  308. }
  309. // Wait for all threads
  310. for (auto& t : threads) {
  311. t.join();
  312. }
  313. // Most requests should have succeeded
  314. EXPECT_GT(successful_requests.load(), 90); // Allow some to fail due to timing
  315. }
  316. TEST_F(ThreadSafeTest, QueueOverflowHandling) {
  317. Scheduler ts;
  318. Task testTask(100, TASK_FOREVER, &thread_safe_task_callback, &ts, true);
  319. // Try to overflow the queue
  320. int successful = 0;
  321. int failed = 0;
  322. for (size_t i = 0; i < MAX_QUEUE_SIZE + 50; i++) {
  323. if (ts.requestAction(&testTask, TASK_REQUEST_SETINTERVAL_1, i, 0, 0, 0, 0)) {
  324. successful++;
  325. } else {
  326. failed++;
  327. }
  328. }
  329. // Should have some failures due to queue limit
  330. EXPECT_GT(failed, 0);
  331. EXPECT_LE(successful, (int)MAX_QUEUE_SIZE);
  332. // Process all queued requests
  333. for (int i = 0; i < 200; i++) {
  334. ts.execute();
  335. delay(1);
  336. }
  337. // Queue should be empty now
  338. EXPECT_EQ(getQueueSize(), 0);
  339. }
  340. // ============================================
  341. // Integration Tests
  342. // ============================================
  343. TEST_F(ThreadSafeTest, RealWorldScenario) {
  344. Scheduler ts;
  345. Task blinkTask(100, TASK_FOREVER, &interval_test_callback, &ts, true);
  346. StatusRequest dataReady;
  347. Task dataTask(TASK_IMMEDIATE, TASK_ONCE, &fast_task_callback, &ts, false);
  348. dataTask.waitFor(&dataReady);
  349. std::atomic<bool> running{true};
  350. // Worker thread: Adjusts blink rate
  351. std::thread worker([&]() {
  352. int count = 0;
  353. while (running && count < 5) {
  354. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  355. ts.requestAction(&blinkTask, TASK_REQUEST_SETINTERVAL_1,
  356. 50 + count * 50, 0, 0, 0, 0);
  357. count++;
  358. }
  359. });
  360. // Simulated ISR: Signals data ready
  361. std::thread isr([&]() {
  362. std::this_thread::sleep_for(std::chrono::milliseconds(250));
  363. ts.requestAction(&dataReady, TASK_SR_REQUEST_SIGNALCOMPLETE_1, 0, 0, 0, 0, 0);
  364. });
  365. // Run scheduler
  366. runScheduler(ts, 600);
  367. running = false;
  368. worker.join();
  369. isr.join();
  370. // Verify both tasks executed
  371. EXPECT_GT(test_output.size(), 2);
  372. // Should have both interval task and fast task executions
  373. bool hasIntervalTask = false;
  374. bool hasFastTask = false;
  375. for (const auto& output : test_output) {
  376. if (output.find("Interval task") != std::string::npos) hasIntervalTask = true;
  377. if (output.find("Fast task") != std::string::npos) hasFastTask = true;
  378. }
  379. EXPECT_TRUE(hasIntervalTask);
  380. EXPECT_TRUE(hasFastTask);
  381. }
  382. // ============================================
  383. // Main
  384. // ============================================
  385. int main(int argc, char **argv) {
  386. ::testing::InitGoogleTest(&argc, argv);
  387. return RUN_ALL_TESTS();
  388. }