// ============================================================ // 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 #include #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() {}