Files
Francesco Zanin e22a47fce1 Snapshot iniziale: bridge telemetria CRSF->LTM/MAVLink (ESP-NOW)
Stato di partenza prima dei fix. Il repo git era inizializzato a meta'
(config.lock + objects/ mancante, zero commit): riparato.
my_config.h (UID reale, tutti e 3 i sottoprogetti) escluso via .gitignore;
aggiunto my_config.h.example. .pio/ escluso.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:32:11 +02:00

155 lines
5.8 KiB
C++

// ============================================================
// Test on-device (Unity via USB, nessuna catena RF) per
// lib/EspNowCrsf: ring buffer, drop-oldest e filtro keep-alive.
//
// Usa test_injectRaw()/test_reset() (hook #ifdef UNIT_TEST,
// zero impatto sul firmware reale) per richiamare il vero
// onRecv() senza inizializzare WiFi/ESP-NOW: niente radio
// necessaria, solo la board collegata via USB.
// ============================================================
#include <Arduino.h>
#include <unity.h>
#include "EspNowCrsf.h"
void setUp(void) { EspNowCrsf::test_reset(); }
void tearDown(void) {}
// Costruisce un pacchetto "backpack": 8 byte header (contenuto
// ignorato dal parser) + payload CRSF grezzo.
static uint16_t buildPkt(uint8_t* out, const uint8_t* payload, uint16_t payloadLen) {
memset(out, 0xAA, 8); // header: contenuto qualsiasi, non ispezionato
memcpy(out + 8, payload, payloadLen);
return 8 + payloadLen;
}
void test_fifo_order_preserved(void) {
uint8_t pkt[16];
for (uint8_t i = 1; i <= 3; ++i) {
uint8_t payload[2] = {i, (uint8_t)(0xF0 + i)};
uint16_t n = buildPkt(pkt, payload, sizeof(payload));
EspNowCrsf::test_injectRaw(pkt, n);
}
uint8_t buf[64];
uint16_t len;
for (uint8_t i = 1; i <= 3; ++i) {
TEST_ASSERT_TRUE(EspNowCrsf::poll(buf, &len, sizeof(buf)));
TEST_ASSERT_EQUAL_UINT16(2, len);
TEST_ASSERT_EQUAL_UINT8(i, buf[0]);
TEST_ASSERT_EQUAL_UINT8(0xF0 + i, buf[1]);
}
TEST_ASSERT_FALSE(EspNowCrsf::poll(buf, &len, sizeof(buf))); // vuoto
}
void test_drop_oldest_when_full(void) {
const uint8_t INJECTED = 20; // ben oltre qualunque capacita' ragionevole del ring
uint8_t pkt[16];
for (uint8_t i = 1; i <= INJECTED; ++i) {
uint8_t payload[2] = {i, 0};
uint16_t n = buildPkt(pkt, payload, sizeof(payload));
EspNowCrsf::test_injectRaw(pkt, n);
}
uint8_t buf[64];
uint16_t len;
uint8_t polled = 0;
uint8_t lastMarker = 0;
while (EspNowCrsf::poll(buf, &len, sizeof(buf))) {
lastMarker = buf[0];
polled++;
}
TEST_ASSERT_EQUAL_UINT32(INJECTED, EspNowCrsf::framesReceived());
TEST_ASSERT_TRUE_MESSAGE(EspNowCrsf::framesDropped() > 0, "con 20 frame iniettati ci si aspetta almeno un drop-oldest");
TEST_ASSERT_EQUAL_UINT32(INJECTED, polled + EspNowCrsf::framesDropped());
// drop-oldest: l'ultimo frame iniettato deve essere sempre presente
TEST_ASSERT_EQUAL_UINT8(INJECTED, lastMarker);
}
// ---- Filtro keep-alive: regressione del bug off-by-one --------
// Bug originale: `in[8]==0 && in[9]==0 && len>10` leggeva in[9] OOB
// quando len==9, e non scartava il keep-alive legittimo a len==10.
void test_keepalive_len10_is_filtered(void) {
uint8_t payload[2] = {0, 0}; // header(8) + 2 byte zero = len 10
uint8_t pkt[16];
uint16_t n = buildPkt(pkt, payload, sizeof(payload));
EspNowCrsf::test_injectRaw(pkt, n);
uint8_t buf[64];
uint16_t len;
TEST_ASSERT_FALSE_MESSAGE(EspNowCrsf::poll(buf, &len, sizeof(buf)),
"un keep-alive di len==10 deve essere scartato, non accodato");
TEST_ASSERT_EQUAL_UINT32(0, EspNowCrsf::framesReceived());
}
void test_len9_payload_not_oob_and_not_keepalive(void) {
uint8_t payload[1] = {0}; // header(8) + 1 byte zero = len 9
uint8_t pkt[16];
uint16_t n = buildPkt(pkt, payload, sizeof(payload));
EspNowCrsf::test_injectRaw(pkt, n); // non deve leggere OOB / non deve crashare
uint8_t buf[64];
uint16_t len;
TEST_ASSERT_TRUE_MESSAGE(EspNowCrsf::poll(buf, &len, sizeof(buf)),
"len==9 e' troppo corto per essere un keep-alive valido: va accodato");
TEST_ASSERT_EQUAL_UINT16(1, len);
}
void test_keepalive_with_extra_trailing_byte_is_filtered(void) {
uint8_t payload[3] = {0, 0, 0x7E}; // header(8) + 3 byte, primi due zero = len 11
uint8_t pkt[16];
uint16_t n = buildPkt(pkt, payload, sizeof(payload));
EspNowCrsf::test_injectRaw(pkt, n);
uint8_t buf[64];
uint16_t len;
TEST_ASSERT_FALSE_MESSAGE(EspNowCrsf::poll(buf, &len, sizeof(buf)),
"primi due byte payload a zero -> keep-alive, va scartato anche con byte extra");
}
void test_normal_frame_with_nonzero_first_byte_is_kept(void) {
uint8_t payload[3] = {0xC8, 0x05, 0x02}; // non e' un keep-alive (primo byte != 0)
uint8_t pkt[16];
uint16_t n = buildPkt(pkt, payload, sizeof(payload));
EspNowCrsf::test_injectRaw(pkt, n);
uint8_t buf[64];
uint16_t len;
TEST_ASSERT_TRUE(EspNowCrsf::poll(buf, &len, sizeof(buf)));
TEST_ASSERT_EQUAL_UINT16(3, len);
TEST_ASSERT_EQUAL_UINT8(0xC8, buf[0]);
}
// ---- Clamping a SLOT_SIZE (64) ---------------------------------
void test_oversized_frame_is_clamped(void) {
uint8_t pkt[8 + 100];
uint8_t payload[100];
for (uint16_t i = 0; i < sizeof(payload); ++i) payload[i] = (uint8_t)(i + 1); // mai 0 in [0]/[1]
uint16_t n = buildPkt(pkt, payload, sizeof(payload));
EspNowCrsf::test_injectRaw(pkt, n);
uint8_t buf[64];
uint16_t len;
TEST_ASSERT_TRUE(EspNowCrsf::poll(buf, &len, sizeof(buf)));
TEST_ASSERT_EQUAL_UINT16(64, len); // clampato a SLOT_SIZE
TEST_ASSERT_EQUAL_UINT8(1, buf[0]); // primi byte del payload originale preservati
TEST_ASSERT_EQUAL_UINT8(64, buf[63]);
}
void setup() {
Serial.begin(115200);
delay(2000);
UNITY_BEGIN();
RUN_TEST(test_fifo_order_preserved);
RUN_TEST(test_drop_oldest_when_full);
RUN_TEST(test_keepalive_len10_is_filtered);
RUN_TEST(test_len9_payload_not_oob_and_not_keepalive);
RUN_TEST(test_keepalive_with_extra_trailing_byte_is_filtered);
RUN_TEST(test_normal_frame_with_nonzero_first_byte_is_kept);
RUN_TEST(test_oversized_frame_is_clamped);
UNITY_END();
}
void loop() {}