mirror of
https://github.com/adafruit/Adafruit_GPS.git
synced 2026-10-27 06:20:18 +00:00
177 lines
7.1 KiB
C++
177 lines
7.1 KiB
C++
/* Host regression for decimal parsing and command construction:
|
|
g++ -std=c++11 -Wall -Wextra -Werror -fsanitize=address,undefined -Isrc \
|
|
src/Adafruit_NMEA.cpp extras/tests/nmea_utilities/nmea_utilities.cpp \
|
|
-o /tmp/nmea_utilities
|
|
/tmp/nmea_utilities
|
|
*/
|
|
|
|
#include "Adafruit_NMEA.h"
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static void expectNumber(nmea_span_t field, nmea_number_status_t status,
|
|
int64_t coefficient = 0, uint8_t decimalPlaces = 0);
|
|
static void expectCommand(const char *body, const char *expected);
|
|
static void expectRejected(const char *body, size_t length);
|
|
|
|
int main() {
|
|
struct {
|
|
const char *text;
|
|
int64_t coefficient;
|
|
uint8_t decimalPlaces;
|
|
} numbers[] = {{"0", 0, 0},
|
|
{"-0.000", 0, 3},
|
|
{"-123.4500", -1234500, 4},
|
|
{"+12", 12, 0},
|
|
{"-.5", -5, 1},
|
|
{"1.", 1, 0},
|
|
{"00012.0300", 120300, 4},
|
|
{"0009223372036854775807", INT64_MAX, 0},
|
|
{"-0009223372036854775808", INT64_MIN, 0},
|
|
{"1000000000000000000", INT64_C(1000000000000000000), 0},
|
|
{"4807.038123456", INT64_C(4807038123456), 9},
|
|
{"9223372036854775807", INT64_MAX, 0},
|
|
{"-9223372036854775808", INT64_MIN, 0},
|
|
{"922337203685477580.7", INT64_MAX, 1},
|
|
{"-922337203685477580.8", INT64_MIN, 1}};
|
|
for (const auto &number : numbers)
|
|
expectNumber({number.text, strlen(number.text)}, NMEA_NUMBER_VALID,
|
|
number.coefficient, number.decimalPlaces);
|
|
puts("PASS: exact decimals, retained zeros, and signed 64-bit limits");
|
|
|
|
const char *badNumbers[] = {
|
|
".", "+", "-", "+.", "--1", "1.2.3", " 1",
|
|
"1 ", "1e2", "NaN", "inf", "1,2", "1*00", "9223372036854775808x"};
|
|
for (const char *text : badNumbers)
|
|
expectNumber({text, strlen(text)}, NMEA_NUMBER_BAD_FORMAT);
|
|
const char *overflow[] = {"9223372036854775808", "-9223372036854775809",
|
|
"99999999999999999999999999",
|
|
"10000000000000000000", "0009223372036854775808"};
|
|
for (const char *text : overflow)
|
|
expectNumber({text, strlen(text)}, NMEA_NUMBER_OUT_OF_RANGE);
|
|
expectNumber({NULL, 100}, NMEA_NUMBER_MISSING);
|
|
expectNumber({"1", 0}, NMEA_NUMBER_EMPTY);
|
|
const char embeddedNul[] = {'1', 0, '2'};
|
|
expectNumber({embeddedNul, sizeof(embeddedNul)}, NMEA_NUMBER_BAD_FORMAT);
|
|
const char highByte[] = {'1', (char)0x80};
|
|
expectNumber({highByte, sizeof(highByte)}, NMEA_NUMBER_BAD_FORMAT);
|
|
puts("PASS: missing, empty, malformed, and overflowing numbers are distinct");
|
|
|
|
const char bounded[] = {'-', '1', '.', '2'};
|
|
expectNumber({bounded, sizeof(bounded)}, NMEA_NUMBER_VALID, -12, 1);
|
|
expectNumber({"12x", 2}, NMEA_NUMBER_VALID, 12, 0);
|
|
char fraction[258];
|
|
memset(fraction, '0', sizeof(fraction));
|
|
fraction[1] = '.';
|
|
expectNumber({fraction, 257}, NMEA_NUMBER_VALID, 0, 255);
|
|
expectNumber({fraction, sizeof(fraction)}, NMEA_NUMBER_OUT_OF_RANGE);
|
|
char zeros[300];
|
|
memset(zeros, '0', sizeof(zeros));
|
|
expectNumber({zeros, sizeof(zeros)}, NMEA_NUMBER_VALID);
|
|
puts("PASS: bounded numeric input and decimal-place limits");
|
|
|
|
expectCommand("PQTMVERNO", "$PQTMVERNO*58\r\n");
|
|
expectCommand("PQTMVERNO,", "$PQTMVERNO,*74\r\n");
|
|
expectCommand("PAIR002", "$PAIR002*38\r\n");
|
|
expectCommand("PAIR001,004,0", "$PAIR001,004,0*3F\r\n");
|
|
expectCommand("PMTK220,1000", "$PMTK220,1000*1F\r\n");
|
|
puts("PASS: known command checksums and every output capacity boundary");
|
|
|
|
const char *badBodies[] = {"", ",", ",A", "A B", "$A", "!A",
|
|
"A*41", "A\r", "A\n", "A,\t", "A,\x7f", "A,\x80"};
|
|
for (const char *body : badBodies)
|
|
expectRejected(body, strlen(body));
|
|
expectRejected(NULL, 10);
|
|
expectRejected("A", SIZE_MAX);
|
|
expectRejected(embeddedNul, sizeof(embeddedNul));
|
|
assert(Adafruit_NMEA::buildCommand(NULL, 100, "A", 1) == 0);
|
|
puts("PASS: malformed commands and invalid arguments produce no partial "
|
|
"output");
|
|
|
|
char overlap[40];
|
|
memset(overlap, 'A', sizeof(overlap));
|
|
assert(Adafruit_NMEA::buildCommand(overlap, 20, overlap + 4, 7) == 0);
|
|
assert(overlap[0] == 0);
|
|
for (size_t i = 1; i < sizeof(overlap); i++)
|
|
assert(overlap[i] == 'A');
|
|
memset(overlap, 'A', sizeof(overlap));
|
|
assert(Adafruit_NMEA::buildCommand(overlap + 4, 20, overlap, 7) == 0);
|
|
for (size_t i = 0; i < sizeof(overlap); i++) {
|
|
if (i == 4)
|
|
assert(overlap[i] == 0);
|
|
else
|
|
assert(overlap[i] == 'A');
|
|
}
|
|
memset(overlap, 'A', sizeof(overlap));
|
|
assert(Adafruit_NMEA::buildCommand(overlap, 20, overlap, 7) == 0);
|
|
puts("PASS: overlapping source and destination ranges are rejected");
|
|
|
|
char adjacent[13];
|
|
memcpy(adjacent + 10, "A,1", 3);
|
|
assert(Adafruit_NMEA::buildCommand(adjacent, 10, adjacent + 10, 3) == 9);
|
|
assert(strcmp(adjacent, "$A,1*5C\r\n") == 0);
|
|
assert(memcmp(adjacent + 10, "A,1", 3) == 0);
|
|
memcpy(adjacent, "A,1", 3);
|
|
assert(Adafruit_NMEA::buildCommand(adjacent + 3, 10, adjacent, 3) == 9);
|
|
assert(strcmp(adjacent + 3, "$A,1*5C\r\n") == 0);
|
|
assert(memcmp(adjacent, "A,1", 3) == 0);
|
|
puts("PASS: adjacent buffers and nonterminated command bodies are supported");
|
|
return 0;
|
|
}
|
|
|
|
static void expectNumber(nmea_span_t field, nmea_number_status_t status,
|
|
int64_t coefficient, uint8_t decimalPlaces) {
|
|
nmea_decimal_t result = Adafruit_NMEA::parseDecimal(field);
|
|
assert(result.status == status);
|
|
assert(Adafruit_NMEA::validateDecimal(field) == status);
|
|
nmea_number_status_t nonnegative = status;
|
|
if (status == NMEA_NUMBER_VALID && coefficient < 0)
|
|
nonnegative = NMEA_NUMBER_OUT_OF_RANGE;
|
|
assert(Adafruit_NMEA::validateDecimal(field, false) == nonnegative);
|
|
assert(result.coefficient == coefficient);
|
|
assert(result.decimalPlaces == decimalPlaces);
|
|
}
|
|
|
|
static void expectCommand(const char *body, const char *expected) {
|
|
size_t required = strlen(expected) + 1;
|
|
for (size_t capacity = 0; capacity <= required + 1; capacity++) {
|
|
size_t allocated = capacity;
|
|
if (!allocated)
|
|
allocated = 1;
|
|
char *output = (char *)malloc(allocated);
|
|
assert(output != NULL);
|
|
memset(output, '?', allocated);
|
|
size_t length =
|
|
Adafruit_NMEA::buildCommand(output, capacity, body, strlen(body));
|
|
if (capacity < required) {
|
|
assert(length == 0);
|
|
if (capacity)
|
|
assert(output[0] == 0);
|
|
else
|
|
assert(output[0] == '?');
|
|
for (size_t i = 1; i < allocated; i++)
|
|
assert(output[i] == '?');
|
|
} else {
|
|
assert(length == required - 1);
|
|
assert(strcmp(output, expected) == 0);
|
|
assert(Adafruit_NMEA::validate(output, length).status ==
|
|
NMEA_FRAME_VALID);
|
|
if (capacity > required)
|
|
assert(output[required] == '?');
|
|
}
|
|
free(output);
|
|
}
|
|
}
|
|
|
|
static void expectRejected(const char *body, size_t length) {
|
|
char output[64];
|
|
memset(output, '?', sizeof(output));
|
|
assert(Adafruit_NMEA::buildCommand(output, sizeof(output), body, length) ==
|
|
0);
|
|
assert(output[0] == 0);
|
|
for (size_t i = 1; i < sizeof(output); i++)
|
|
assert(output[i] == '?');
|
|
}
|