test_malformed.c 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /*
  2. * Malformed/edge-case NMEA regression test.
  3. *
  4. * Feeds soft/nmea.c's real gps_parse() (through the same PC_BUILD shim as
  5. * the rest of this test tool) a fixture of real and synthetic sentences
  6. * that are known to be awkward: invalid epochs from an actual hike log,
  7. * truncated/bad-checksum/short-dialect sentences, and a run long enough to
  8. * exercise the HDOP-based speed-sample gating added after that hike showed
  9. * a single poor-quality fix (HDOP 16, 44.8 km/h) skewing the whole-session
  10. * average.
  11. *
  12. * Nothing in malformed_test.nmea should crash the parser (run under a tool
  13. * like valgrind or ASan for a stronger guarantee than "the process exited
  14. * zero"), and the checks below assert the speed average is actually left
  15. * alone by the invalid/poor-quality samples, and is capped rather than
  16. * excluded for a genuinely fast, good-quality one.
  17. *
  18. * Usage: ./test-malformed [malformed_test.nmea]
  19. */
  20. #define PC_BUILD
  21. #include "main.h"
  22. volatile struct system_s System;
  23. struct location_s location;
  24. time_t utc;
  25. unsigned char FLAGS = 0;
  26. volatile unsigned char reset_cause = 0;
  27. FILE *debug_output = NULL;
  28. __flash const char gnss_name_stub[] = "n/a";
  29. __flash const char *gnss_names[] = { gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub };
  30. __flash const char uart_log_mode_stub[] = "n/a";
  31. __flash const char *uart_log_mode_names[] = { uart_log_mode_stub, uart_log_mode_stub, uart_log_mode_stub };
  32. time_t gps_parse(const char *nmea);
  33. char *get_iso_time(time_t time, unsigned char local) {
  34. (void)local;
  35. static char output[32];
  36. struct tm *ct = gmtime(&time);
  37. sprintf(output, "%04d-%02d-%02dT%02d:%02d:%02d.000Z",
  38. ct->tm_year + 1900, ct->tm_mon + 1, ct->tm_mday,
  39. ct->tm_hour, ct->tm_min, ct->tm_sec);
  40. return output;
  41. }
  42. void iso_time_to_filename(char *time) {
  43. (void)time;
  44. }
  45. static int failures;
  46. static void check(int cond, const char *what) {
  47. if (!cond) {
  48. fprintf(stderr, "FAIL: %s\n", what);
  49. failures++;
  50. } else {
  51. printf("ok: %s\n", what);
  52. }
  53. }
  54. /* Recognize the known-invalid lines from malformed_test.nmea's section 1,
  55. * to check precisely that they leave the speed average alone - not just
  56. * that nothing crashes. */
  57. static int is_invalid_epoch_line(const char *line) {
  58. return strstr(line, ",V,,,,,") != NULL /* RMC: empty lat/lon, status V */
  59. || (strstr(line, "VTG") && strstr(line, ",N*")); /* VTG: mode indicator N (not valid) */
  60. }
  61. int main(int argc, char *argv[]) {
  62. const char *path = argc > 1 ? argv[1] : "malformed_test.nmea";
  63. FILE *f = fopen(path, "r");
  64. char raw[512];
  65. char line[512];
  66. int total_lines = 0;
  67. if (!f) {
  68. fprintf(stderr, "Cannot open %s\n", path);
  69. return 1;
  70. }
  71. memset((void *)&System, 0, sizeof(System));
  72. memset(&location, 0, sizeof(location));
  73. System.conf.min_sats = 4;
  74. utc = 0;
  75. while (fgets(raw, sizeof raw, f)) {
  76. unsigned long int count_before, accum_before;
  77. size_t len = strlen(raw);
  78. while (len && (raw[len - 1] == '\n' || raw[len - 1] == '\r'))
  79. raw[--len] = '\0';
  80. if (len == 0 || raw[0] == '#')
  81. continue; /* blank line or fixture comment, not fed to the parser */
  82. snprintf(line, sizeof line, "%s\r\n", raw);
  83. total_lines++;
  84. count_before = System.speed_sample_count;
  85. accum_before = System.speed_accum_x100;
  86. gps_parse(line); /* must not crash, regardless of how malformed the line is */
  87. if (is_invalid_epoch_line(raw)) {
  88. char what[560];
  89. snprintf(what, sizeof what, "no speed sample added by: %s", raw);
  90. check(System.speed_sample_count == count_before && System.speed_accum_x100 == accum_before, what);
  91. }
  92. }
  93. fclose(f);
  94. printf("\nParsed %d lines without crashing.\n", total_lines);
  95. check(total_lines > 250, "fixture file actually has content");
  96. /* Section 3: the HDOP-16.2, 44.8 km/h sample must be excluded outright -
  97. * the average right after it must still equal the ~4.3 km/h warm-up
  98. * average, not be pulled up towards 44.8. */
  99. check(System.speed_sample_count >= 64, "warm-up samples were accepted");
  100. {
  101. double avg = (double)System.speed_accum_x100 / System.speed_sample_count / 100.0;
  102. char what[128];
  103. snprintf(what, sizeof what, "average (%.2f km/h) unaffected by the HDOP-16.2 44.8 km/h sample", avg);
  104. check(avg > 2.0 && avg < 6.0, what);
  105. }
  106. /* The 200 km/h, good-HDOP spike must be capped (added to the sum at
  107. * something well below 200, per SPEED_SAMPLE_CAP_MULT/FLOOR), not
  108. * excluded outright: a real fast segment (car/train) must still count. */
  109. {
  110. double avg_after = (double)System.speed_accum_x100 / System.speed_sample_count / 100.0;
  111. char what[160];
  112. snprintf(what, sizeof what, "200 km/h spike was capped, not excluded nor let through raw (average now %.2f km/h)", avg_after);
  113. check(avg_after > 2.5 && avg_after < 15.0, what);
  114. }
  115. if (failures) {
  116. printf("\n%d check(s) FAILED\n", failures);
  117. return 1;
  118. }
  119. printf("\nAll checks passed.\n");
  120. return 0;
  121. }