HF-Core Platform 0.1.0-dev
Hardware-Agnostic Handler Layer & RTOS Utilities for HardFOC
Loading...
Searching...
No Matches
TestFramework.h
Go to the documentation of this file.
1
14#pragma once
15
16// ESP-IDF C headers must be wrapped in extern "C" for C++ compatibility
17#ifdef __cplusplus
18extern "C" {
19#endif
20
21#include "esp_log.h"
22#include "esp_timer.h"
23#include "driver/gpio.h"
24#include "freertos/FreeRTOS.h"
25#include "freertos/queue.h"
26#include "freertos/semphr.h"
27#include "freertos/task.h"
28
29#ifdef __cplusplus
30}
31#endif
32
33//=============================================================================
34// GPIO14 TEST PROGRESSION INDICATOR MANAGEMENT
35//=============================================================================
36// GPIO14 test progression indicator - toggles between HIGH/LOW each time a test completes
37// providing visual feedback for test progression on oscilloscope/logic analyzer
38
39// Global GPIO14 test indicator state
40static bool g_test_progress_initialized = false;
41static bool g_test_progress_state = false;
42static constexpr gpio_num_t TEST_PROGRESS_PIN = GPIO_NUM_14;
43
44// GPIO14 test indicator functions implemented inline in this header
45
51inline bool init_test_progress_indicator() noexcept {
53 return true; // Already initialized
54 }
55
56 // Configure GPIO14 as output
57 gpio_config_t io_conf = {
58 .pin_bit_mask = (1ULL << TEST_PROGRESS_PIN),
59 .mode = GPIO_MODE_OUTPUT,
60 .pull_up_en = GPIO_PULLUP_DISABLE,
61 .pull_down_en = GPIO_PULLDOWN_DISABLE,
62 .intr_type = GPIO_INTR_DISABLE,
63 };
64
65 esp_err_t ret = gpio_config(&io_conf);
66 if (ret != ESP_OK) {
67 const char* TAG = "TestFramework";
68 ESP_LOGE(TAG, "Failed to configure GPIO14: %s", esp_err_to_name(ret));
69 return false;
70 }
71
72 // Start with GPIO14 LOW
73 gpio_set_level(TEST_PROGRESS_PIN, 0);
76
77 return true;
78}
79
84inline void flip_test_progress_indicator() noexcept {
86 return; // Not initialized
87 }
88
89 // Toggle GPIO14 state
91
92 // Set GPIO level based on state
93 gpio_set_level(TEST_PROGRESS_PIN, g_test_progress_state ? 1 : 0);
94
95 const char* TAG = "TestFramework";
96 ESP_LOGI(TAG, "Test progression indicator: %s", g_test_progress_state ? "HIGH" : "LOW");
97
98 // Small delay for visual effect
99 vTaskDelay(pdMS_TO_TICKS(50));
100}
101
106inline void cleanup_test_progress_indicator() noexcept {
108 // Ensure GPIO14 is LOW before cleanup
109 gpio_set_level(TEST_PROGRESS_PIN, 0);
110
111 // Reset GPIO configuration
112 gpio_reset_pin(TEST_PROGRESS_PIN);
113
115 g_test_progress_state = false;
116 }
117}
118
124inline void output_section_indicator(uint8_t blink_count = 5) noexcept {
126 return; // Not initialized
127 }
128
129 // Blink the specified number of times for section identification
130 for (uint8_t i = 0; i < blink_count; ++i) {
131 gpio_set_level(TEST_PROGRESS_PIN, 1); // HIGH
132 vTaskDelay(pdMS_TO_TICKS(50)); // ON
133 gpio_set_level(TEST_PROGRESS_PIN, 0); // LOW
134 vTaskDelay(pdMS_TO_TICKS(50)); // OFF
135
136 // // Pause between blinks (except after the last one)
137 // if (i < blink_count - 1) {
138 // vTaskDelay(pdMS_TO_TICKS(200)); // 200ms pause between blinks
139 // }
140 }
141
142 g_test_progress_state = false; // MARK THE STATE AS LOW AFTER THE BLINKING IS COMPLETED
143}
144
149inline void ensure_gpio14_initialized() noexcept {
152 }
153}
154
159 int total_tests = 0;
163
169 void add_result(bool passed, uint64_t execution_time) noexcept {
170 total_tests++;
171 total_execution_time_us += execution_time;
172 if (passed) {
173 passed_tests++;
174 } else {
175 failed_tests++;
176 }
177 }
178
183 float get_success_percentage() const noexcept {
184 return total_tests > 0 ? (static_cast<float>(passed_tests) / total_tests * 100.0f) : 0.0f;
185 }
186
191 float get_total_time_ms() const noexcept {
192 return total_execution_time_us / 1000.0f;
193 }
194};
195
215#define RUN_TEST_1(test_func) \
216 do { \
217 ensure_gpio14_initialized(); \
218 ESP_LOGI(TAG, \
219 "\n" \
220 "╔══════════════════════════════════════════════════════════════════════════════╗\n" \
221 "║ Running: " #test_func " \n" \
222 "╚══════════════════════════════════════════════════════════════════════════════╝"); \
223 uint64_t start_time = esp_timer_get_time(); \
224 bool result = test_func(); \
225 uint64_t end_time = esp_timer_get_time(); \
226 uint64_t execution_time = end_time - start_time; \
227 g_test_results.add_result(result, execution_time); \
228 if (result) { \
229 ESP_LOGI(TAG, "[SUCCESS] PASSED: " #test_func " (%.2f ms)", execution_time / 1000.0); \
230 } else { \
231 ESP_LOGE(TAG, "[FAILED] FAILED: " #test_func " (%.2f ms)", execution_time / 1000.0); \
232 } \
233 vTaskDelay(pdMS_TO_TICKS(100)); \
234 } while (0)
235
236#define RUN_TEST_2(test_name, test_func) \
237 do { \
238 ensure_gpio14_initialized(); \
239 ESP_LOGI(TAG, \
240 "\n" \
241 "╔══════════════════════════════════════════════════════════════════════════════╗\n" \
242 "║ Running: %s \n" \
243 "╚══════════════════════════════════════════════════════════════════════════════╝", \
244 test_name); \
245 uint64_t start_time = esp_timer_get_time(); \
246 bool result = test_func(); \
247 uint64_t end_time = esp_timer_get_time(); \
248 uint64_t execution_time = end_time - start_time; \
249 g_test_results.add_result(result, execution_time); \
250 if (result) { \
251 ESP_LOGI(TAG, "[SUCCESS] PASSED: %s (%.2f ms)", test_name, execution_time / 1000.0); \
252 } else { \
253 ESP_LOGE(TAG, "[FAILED] FAILED: %s (%.2f ms)", test_name, execution_time / 1000.0); \
254 } \
255 vTaskDelay(pdMS_TO_TICKS(100)); \
256 } while (0)
257
258/* Dispatch RUN_TEST to 1-arg or 2-arg variant */
259#define RUN_TEST_GET_MACRO(_1, _2, NAME, ...) NAME
260#define RUN_TEST(...) RUN_TEST_GET_MACRO(__VA_ARGS__, RUN_TEST_2, RUN_TEST_1)(__VA_ARGS__)
261
266 const char* test_name;
267 bool (*test_func)() noexcept;
269 const char* tag;
270 SemaphoreHandle_t completion_semaphore; // Add semaphore for synchronization
271};
272
276inline void test_task_trampoline(void* param) {
277 TestTaskContext* ctx = static_cast<TestTaskContext*>(param);
278 ESP_LOGI(ctx->tag,
279 "\n"
280 "╔══════════════════════════════════════════════════════════════════════════════╗\n"
281 "║ Running (task): %s \n"
282 "╚══════════════════════════════════════════════════════════════════════════════╝",
283 ctx->test_name);
284 uint64_t start_time = esp_timer_get_time();
285 bool result = ctx->test_func();
286 uint64_t end_time = esp_timer_get_time();
287 uint64_t execution_time = end_time - start_time;
288 ctx->results->add_result(result, execution_time);
289 if (result) {
290 ESP_LOGI(ctx->tag, "[SUCCESS] PASSED (task): %s (%.2f ms)", ctx->test_name,
291 execution_time / 1000.0);
292 } else {
293 ESP_LOGE(ctx->tag, "[FAILED] FAILED (task): %s (%.2f ms)", ctx->test_name,
294 execution_time / 1000.0);
295 }
296
297 // Signal completion before deleting task
298 if (ctx->completion_semaphore != nullptr) {
299 xSemaphoreGive(ctx->completion_semaphore);
300 }
301
302 vTaskDelete(nullptr);
303}
304
312#define RUN_TEST_IN_TASK(name, func, stack_size_bytes, priority) \
313 do { \
314 ensure_gpio14_initialized(); \
315 static TestTaskContext ctx; \
316 ctx.test_name = name; \
317 ctx.test_func = func; \
318 ctx.results = &g_test_results; \
319 ctx.tag = TAG; \
320 ctx.completion_semaphore = xSemaphoreCreateBinary(); \
321 if (ctx.completion_semaphore == nullptr) { \
322 ESP_LOGE(TAG, "Failed to create semaphore for test: %s", name); \
323 /* Fallback: run inline to avoid losing coverage */ \
324 RUN_TEST(func); \
325 } else { \
326 BaseType_t created = \
327 xTaskCreate(test_task_trampoline, name, (stack_size_bytes) / sizeof(StackType_t), &ctx, \
328 (priority), nullptr); \
329 if (created != pdPASS) { \
330 ESP_LOGE(TAG, "Failed to create test task: %s", name); \
331 vSemaphoreDelete(ctx.completion_semaphore); \
332 /* Fallback: run inline to avoid losing coverage */ \
333 RUN_TEST(func); \
334 } else { \
335 /* Wait for test completion using semaphore with timeout */ \
336 if (xSemaphoreTake(ctx.completion_semaphore, pdMS_TO_TICKS(30000)) == pdTRUE) { \
337 ESP_LOGI(TAG, "Test task completed: %s", name); \
338 } else { \
339 ESP_LOGW(TAG, "Test task timeout: %s", name); \
340 } \
341 vSemaphoreDelete(ctx.completion_semaphore); \
342 /* Add small delay between tests to ensure proper cleanup */ \
343 vTaskDelay(pdMS_TO_TICKS(100)); \
344 } \
345 } \
346 } while (0)
347
353inline void print_test_summary(const TestResults& test_results, const char* test_suite_name,
354 const char* tag) noexcept {
356 ESP_LOGI(tag, "\n=== %s TEST SUMMARY ===", test_suite_name);
357 ESP_LOGI(tag, "Total: %d, Passed: %d, Failed: %d, Success: %.2f%%, Time: %.2f ms",
358 test_results.total_tests, test_results.passed_tests, test_results.failed_tests,
359 test_results.get_success_percentage(), test_results.get_total_time_ms());
360
361 if (test_results.failed_tests == 0) {
362 ESP_LOGI(tag, "[SUCCESS] ALL %s TESTS PASSED!", test_suite_name);
363 } else {
364 ESP_LOGE(tag, "[FAILED] Some tests failed. Review the results above.");
365 }
366}
367
373inline void print_test_section_status(const char* tag, const char* test_suite_name) noexcept {
375 ESP_LOGI(tag, "\n");
376 ESP_LOGI(tag, "╔══════════════════════════════════════════════════════════════════════════════╗");
377 ESP_LOGI(tag, "║ %s TEST SECTION CONFIGURATION ║",
378 test_suite_name);
379 ESP_LOGI(tag, "╚══════════════════════════════════════════════════════════════════════════════╝");
380 ESP_LOGI(tag, "To modify test sections, edit the defines at the top of your test file");
381 ESP_LOGI(tag, "╔══════════════════════════════════════════════════════════════════════════════╗");
382}
383
389inline void print_test_section_header(const char* tag, const char* section_name,
390 bool enabled = true) noexcept {
391 if (enabled) {
392 ESP_LOGI(tag, "\n");
393 ESP_LOGI(tag,
394 "╔══════════════════════════════════════════════════════════════════════════════╗");
395 ESP_LOGI(tag, "║ %s ",
396 section_name);
397 ESP_LOGI(tag,
398 "╠══════════════════════════════════════════════════════════════════════════════╣");
399 } else {
400 ESP_LOGI(tag, "\n");
401 ESP_LOGI(tag,
402 "╔══════════════════════════════════════════════════════════════════════════════╗");
403 ESP_LOGI(tag, "║ %s (DISABLED) ",
404 section_name);
405 ESP_LOGI(tag,
406 "╚══════════════════════════════════════════════════════════════════════════════╝");
407 }
408}
409
415inline void print_test_section_footer(const char* tag, const char* section_name,
416 bool enabled = true) noexcept {
417 if (enabled) {
418 ESP_LOGI(tag,
419 "╚══════════════════════════════════════════════════════════════════════════════╝");
420 }
421}
440// Macro to conditionally run a test section
441#define RUN_TEST_SECTION_IF_ENABLED(define_name, section_name, ...) \
442 do { \
443 ensure_gpio14_initialized(); \
444 if (define_name) { \
445 print_test_section_header(TAG, section_name, true); \
446 __VA_ARGS__ \
447 print_test_section_footer(TAG, section_name, true); \
448 } else { \
449 print_test_section_header(TAG, section_name, false); \
450 ESP_LOGI(TAG, "Section disabled by configuration"); \
451 } \
452 } while (0)
453
454// Macro to conditionally run a single test
455#define RUN_SINGLE_TEST_IF_ENABLED(define_name, test_name, test_func, stack_size, priority) \
456 do { \
457 ensure_gpio14_initialized(); \
458 if (define_name) { \
459 RUN_TEST_IN_TASK(test_name, test_func, stack_size, priority); \
460 flip_test_progress_indicator(); \
461 } else { \
462 ESP_LOGI(TAG, "Test '%s' disabled by configuration", test_name); \
463 } \
464 } while (0)
465
466// Macro to conditionally run multiple tests in a section
467#define RUN_TEST_GROUP_IF_ENABLED(define_name, section_name, ...) \
468 RUN_TEST_SECTION_IF_ENABLED(define_name, section_name, __VA_ARGS__)
469
470// Macro to conditionally run a test section with custom progress indicator
471#define RUN_TEST_SECTION_IF_ENABLED_WITH_PROGRESS(define_name, section_name, progress_func, ...) \
472 do { \
473 ensure_gpio14_initialized(); \
474 if (define_name) { \
475 print_test_section_header(TAG, section_name, true); \
476 __VA_ARGS__ \
477 if (progress_func) \
478 progress_func(); \
479 print_test_section_footer(TAG, section_name, true); \
480 } else { \
481 print_test_section_header(TAG, section_name, false); \
482 ESP_LOGI(TAG, "Section disabled by configuration"); \
483 } \
484 } while (0)
485
486// Macro to conditionally run a test section with automatic progress indicator
487#define RUN_TEST_SECTION_IF_ENABLED_AUTO_PROGRESS(define_name, section_name, ...) \
488 RUN_TEST_SECTION_IF_ENABLED_WITH_PROGRESS(define_name, section_name, \
489 flip_test_progress_indicator, __VA_ARGS__)
490
491// Macro to conditionally run a test section with section indicator
492#define RUN_TEST_SECTION_IF_ENABLED_WITH_PATTERN(define_name, section_name, blink_count, ...) \
493 do { \
494 ensure_gpio14_initialized(); \
495 if (define_name) { \
496 print_test_section_header(TAG, section_name, true); \
497 output_section_indicator(blink_count); /* Section start indicator */ \
498 __VA_ARGS__ \
499 output_section_indicator(blink_count); /* Section end indicator */ \
500 print_test_section_footer(TAG, section_name, true); \
501 } else { \
502 print_test_section_header(TAG, section_name, false); \
503 ESP_LOGI(TAG, "Section disabled by configuration"); \
504 } \
505 } while (0)
506
static constexpr const char * TAG
Definition Max22200Handler.cpp:11
static constexpr gpio_num_t TEST_PROGRESS_PIN
Definition TestFramework.h:42
static bool g_test_progress_state
Definition TestFramework.h:41
void output_section_indicator(uint8_t blink_count=5) noexcept
Output section start/end indicator on GPIO14.
Definition TestFramework.h:124
void print_test_section_status(const char *tag, const char *test_suite_name) noexcept
Print standardized test summary.
Definition TestFramework.h:373
bool init_test_progress_indicator() noexcept
Initialize the test progression indicator on GPIO14.
Definition TestFramework.h:51
void ensure_gpio14_initialized() noexcept
Automatically initialize GPIO14 test indicator if not already done.
Definition TestFramework.h:149
void print_test_section_header(const char *tag, const char *section_name, bool enabled=true) noexcept
Print test section header with consistent formatting.
Definition TestFramework.h:389
void cleanup_test_progress_indicator() noexcept
Cleanup the test progression indicator GPIO.
Definition TestFramework.h:106
void print_test_section_footer(const char *tag, const char *section_name, bool enabled=true) noexcept
Print test section footer with consistent formatting.
Definition TestFramework.h:415
void flip_test_progress_indicator() noexcept
Flip the test progression indicator to show next test.
Definition TestFramework.h:84
static bool g_test_progress_initialized
Definition TestFramework.h:40
void print_test_summary(const TestResults &test_results, const char *test_suite_name, const char *tag) noexcept
Print standardized test summary.
Definition TestFramework.h:353
void test_task_trampoline(void *param)
FreeRTOS task trampoline to execute a test with a larger dedicated stack.
Definition TestFramework.h:276
int gpio_num_t
Definition Ws2812Handler.h:57
Test execution tracking and results accumulation.
Definition TestFramework.h:158
int failed_tests
Definition TestFramework.h:161
void add_result(bool passed, uint64_t execution_time) noexcept
Add test result and update statistics.
Definition TestFramework.h:169
int passed_tests
Definition TestFramework.h:160
float get_total_time_ms() const noexcept
Get total execution time in milliseconds.
Definition TestFramework.h:191
uint64_t total_execution_time_us
Definition TestFramework.h:162
float get_success_percentage() const noexcept
Calculate success percentage.
Definition TestFramework.h:183
int total_tests
Definition TestFramework.h:159
Context passed to test task trampoline.
Definition TestFramework.h:265
TestResults * results
Definition TestFramework.h:268
bool(* test_func)() noexcept
Definition TestFramework.h:267
SemaphoreHandle_t completion_semaphore
Definition TestFramework.h:270
const char * test_name
Definition TestFramework.h:266
const char * tag
Definition TestFramework.h:269