/ firmware / src / hardware / system_test.cpp
system_test.cpp
  1  #ifdef PIO_UNIT_TESTING
  2  
  3  #include <config.h>
  4  #include <testing/utils.h>
  5  
  6  namespace hardware::system { void test(void); }
  7  
  8  #include <Arduino.h>
  9  #include <Esp.h>
 10  #include <freertos/FreeRTOS.h>
 11  #include <freertos/task.h>
 12  
 13  // ─────────────────────────────────────────────────────────────────────────────
 14  //  Chip temperature
 15  // ─────────────────────────────────────────────────────────────────────────────
 16  
 17  static void test_system_temperature_read(void) {
 18    WHEN("the chip temperature is read");
 19  
 20    float temp = temperatureRead();
 21    char msg[32];
 22    snprintf(msg, sizeof(msg), "chip temperature: %.1f C", temp);
 23    TEST_MESSAGE(msg);
 24  
 25    TEST_ASSERT_FLOAT_IS_DETERMINATE_MESSAGE(temp,
 26      "device: chip temperature is NaN or Inf");
 27    TEST_ASSERT_GREATER_OR_EQUAL_FLOAT_MESSAGE(0.0f, temp,
 28      "device: chip temperature below 0 C");
 29    TEST_ASSERT_LESS_OR_EQUAL_FLOAT_MESSAGE(100.0f, temp,
 30      "device: chip temperature above 100 C");
 31  }
 32  
 33  // ─────────────────────────────────────────────────────────────────────────────
 34  //  FreeRTOS task list
 35  // ─────────────────────────────────────────────────────────────────────────────
 36  
 37  static void test_system_task_list(void) {
 38    WHEN("the FreeRTOS task list is queried");
 39  
 40    uint32_t count = uxTaskGetNumberOfTasks();
 41    TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(0, count,
 42      "device: no tasks running");
 43  
 44    TaskStatus_t *tasks = (TaskStatus_t *)malloc(count * sizeof(TaskStatus_t));
 45    TEST_ASSERT_NOT_NULL_MESSAGE(tasks, "device: malloc failed for task list");
 46  
 47    uint32_t total_runtime = 0;
 48    uint32_t filled = uxTaskGetSystemState(tasks, count, &total_runtime);
 49  
 50    static const char *states[] = {"Running", "Ready", "Blocked", "Suspend", "Deleted", "Invalid"};
 51  
 52    for (uint32_t i = 0; i < filled; i++) {
 53      int state = (int)tasks[i].eCurrentState;
 54      if (state > 5) state = 5;
 55      int core = (int)tasks[i].xCoreID;
 56      char core_str[4];
 57      if (core == tskNO_AFFINITY) snprintf(core_str, sizeof(core_str), "*");
 58      else snprintf(core_str, sizeof(core_str), "%d", core);
 59  
 60      char line[80];
 61      snprintf(line, sizeof(line), "%-16s %8s prio=%lu stack=%lu core=%s",
 62               tasks[i].pcTaskName, states[state],
 63               (unsigned long)tasks[i].uxCurrentPriority,
 64               (unsigned long)tasks[i].usStackHighWaterMark, core_str);
 65      TEST_MESSAGE(line);
 66    }
 67  
 68    free(tasks);
 69  
 70    TEST_PRINTF("%lu tasks total", (unsigned long)filled);
 71  }
 72  
 73  // ─────────────────────────────────────────────────────────────────────────────
 74  //  Firmware integrity (sketch MD5, size, free space)
 75  // ─────────────────────────────────────────────────────────────────────────────
 76  
 77  static void test_system_sketch_md5(void) {
 78    WHEN("the firmware integrity hash is read");
 79  
 80    String md5 = ESP.getSketchMD5();
 81    TEST_ASSERT_EQUAL_UINT32_MESSAGE(32, md5.length(),
 82      "device: sketch MD5 should be 32-char hex string");
 83  
 84    TEST_PRINTF("sketch MD5: %s", md5.c_str());
 85  }
 86  
 87  static void test_system_sketch_size(void) {
 88    WHEN("the firmware size and free OTA space are queried");
 89  
 90    uint32_t sketch_size = ESP.getSketchSize();
 91    uint32_t free_space = ESP.getFreeSketchSpace();
 92  
 93    TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(0, sketch_size,
 94      "device: sketch size should be > 0");
 95    TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(0, free_space,
 96      "device: free sketch space should be > 0");
 97  
 98    TEST_PRINTF("sketch: %lu KB, free OTA: %lu KB",
 99             (unsigned long)(sketch_size / 1024),
100             (unsigned long)(free_space / 1024));
101  }
102  
103  // ─────────────────────────────────────────────────────────────────────────────
104  //  Watchdog timer
105  // ─────────────────────────────────────────────────────────────────────────────
106  
107  static void test_system_watchdog(void) {
108    WHEN("the watchdog is enabled and fed");
109  
110    // These should not crash
111    enableLoopWDT();
112    feedLoopWDT();
113    feedLoopWDT();
114    disableLoopWDT();
115  
116  }
117  
118  // ─────────────────────────────────────────────────────────────────────────────
119  //  Heap fragmentation
120  // ─────────────────────────────────────────────────────────────────────────────
121  
122  static void test_system_heap_fragmentation(void) {
123    WHEN("heap fragmentation is measured");
124  
125    uint32_t free_heap = ESP.getFreeHeap();
126    uint32_t max_alloc = ESP.getMaxAllocHeap();
127    uint32_t min_free = ESP.getMinFreeHeap();
128    uint32_t total = ESP.getHeapSize();
129  
130    TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(free_heap, max_alloc,
131      "device: max alloc should be <= free heap");
132    TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(total, free_heap,
133      "device: free heap should be <= total heap");
134  
135    uint32_t frag_pct = (free_heap > 0)
136      ? 100 - (max_alloc * 100 / free_heap)
137      : 0;
138  
139    char msg[128];
140    snprintf(msg, sizeof(msg), "heap: %lu/%lu KB free, max_alloc=%lu KB, min_free=%lu KB, frag=%lu%%",
141             (unsigned long)(free_heap / 1024),
142             (unsigned long)(total / 1024),
143             (unsigned long)(max_alloc / 1024),
144             (unsigned long)(min_free / 1024),
145             (unsigned long)frag_pct);
146    TEST_MESSAGE(msg);
147  }
148  
149  // ─────────────────────────────────────────────────────────────────────────────
150  //  Flash chip info
151  // ─────────────────────────────────────────────────────────────────────────────
152  
153  static void test_system_flash_chip_info(void) {
154    WHEN("the flash chip configuration is read");
155  
156    uint32_t flash_size = ESP.getFlashChipSize();
157    uint32_t flash_speed = ESP.getFlashChipSpeed();
158    FlashMode_t flash_mode = ESP.getFlashChipMode();
159  
160    TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(0, flash_size,
161      "device: flash size should be > 0");
162    TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(0, flash_speed,
163      "device: flash speed should be > 0");
164  
165    const char *mode_str = "unknown";
166    switch (flash_mode) {
167      case FM_QIO:  mode_str = "QIO"; break;
168      case FM_QOUT: mode_str = "QOUT"; break;
169      case FM_DIO:  mode_str = "DIO"; break;
170      case FM_DOUT: mode_str = "DOUT"; break;
171      default: break;
172    }
173  
174    TEST_PRINTF("flash: %lu MB, %lu MHz, mode=%s",
175             (unsigned long)(flash_size / (1024 * 1024)),
176             (unsigned long)(flash_speed / 1000000),
177             mode_str);
178  }
179  
180  // ─────────────────────────────────────────────────────────────────────────────
181  //  CPU frequency
182  // ─────────────────────────────────────────────────────────────────────────────
183  
184  static void test_system_cpu_frequency(void) {
185    WHEN("the CPU frequency is read");
186  
187    uint32_t freq = ESP.getCpuFreqMHz();
188    TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(0, freq,
189      "device: CPU frequency should be > 0");
190  
191    TEST_PRINTF("CPU: %lu MHz", (unsigned long)freq);
192  
193    // ESP32-S3 default is 240 MHz
194    TEST_ASSERT_EQUAL_UINT32_MESSAGE(240, freq,
195      "device: ESP32-S3 should default to 240 MHz");
196  }
197  
198  // ─────────────────────────────────────────────────────────────────────────────
199  //  Version strings
200  // ─────────────────────────────────────────────────────────────────────────────
201  
202  static void test_system_version_strings(void) {
203    WHEN("the firmware and SDK version strings are read");
204  
205    const char *sdk = ESP.getSdkVersion();
206    const char *idf = esp_get_idf_version();
207    const char *chip = ESP.getChipModel();
208  
209    TEST_ASSERT_NOT_NULL_MESSAGE(sdk, "device: ESP SDK version string is null");
210    TEST_ASSERT_NOT_NULL_MESSAGE(idf, "device: ESP-IDF version string is null");
211    TEST_ASSERT_NOT_NULL_MESSAGE(chip, "device: chip model string is null");
212  
213    TEST_PRINTF("chip=%s cores=%u rev=%u arduino=%s idf=%s",
214             chip, ESP.getChipCores(), ESP.getChipRevision(),
215             ESP_ARDUINO_VERSION_STR, idf);
216  }
217  
218  // ─────────────────────────────────────────────────────────────────────────────
219  //  Runner
220  // ─────────────────────────────────────────────────────────────────────────────
221  
222  void hardware::system::test(void) {
223    MODULE("System");
224    RUN_TEST(test_system_temperature_read);
225    RUN_TEST(test_system_task_list);
226    RUN_TEST(test_system_sketch_md5);
227    RUN_TEST(test_system_sketch_size);
228    RUN_TEST(test_system_watchdog);
229    RUN_TEST(test_system_heap_fragmentation);
230    RUN_TEST(test_system_flash_chip_info);
231    RUN_TEST(test_system_cpu_frequency);
232    RUN_TEST(test_system_version_strings);
233  }
234  
235  #endif