From 770875e08a149fdad69909eed93fef2850096afb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 24 Aug 2026 06:59:02 -0500 Subject: [PATCH] fix(test): assert the rejected packet is released, not that malloc reuses its address test_beginSending_oversizedPayloadAbortsSafely checked that the allocation following beginSending()'s rejection path returned the same address as the packet it released. That holds for the fixed-slot MemoryPool used on embedded targets, but native builds bind packetPool to MemoryDynamic (plain malloc and free), and the coverage env compiles with -fsanitize=address, whose quarantine withholds a freed chunk from the next malloc. The address always differs, so the test failed on every CI run that reached it, and the ASan abort also tripped the suite attribution check with an unsourced coverage:test_radio case. Check the invariant the test is named for instead: packetPool is tagged "pktpool(live)" for memaudit, so comparing live bytes across the call proves the rejected packet went back to the pool, independent of the allocator. Co-Authored-By: Claude Opus 5 --- test/test_radio/test_main.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/test/test_radio/test_main.cpp b/test/test_radio/test_main.cpp index f8a701f2a3..f3d0415b15 100644 --- a/test/test_radio/test_main.cpp +++ b/test/test_radio/test_main.cpp @@ -3,6 +3,8 @@ #include "MeshService.h" #include "RadioInterface.h" #include "TestUtil.h" +#include "memory/MemAudit.h" +#include #include #include "meshtastic/config.pb.h" @@ -417,8 +419,21 @@ static void test_regionPresetMap_unsetCarriesUserprefsIntent() #endif } +// Live bytes packetPool reports under its memaudit tag, which tracks every alloc/release. +static int32_t livePacketPoolBytes() +{ + memaudit::Tag rows[memaudit::kMaxTags]; + size_t n = memaudit::snapshot(rows, memaudit::kMaxTags); + for (size_t i = 0; i < n; i++) + if (rows[i].tag && strcmp(rows[i].tag, "pktpool(live)") == 0) + return rows[i].bytes; + return 0; +} + static void test_beginSending_oversizedPayloadAbortsSafely() { + const int32_t liveBefore = livePacketPoolBytes(); + meshtastic_MeshPacket *p = packetPool.allocZeroed(); TEST_ASSERT_NOT_NULL(p); p->from = 0x12345678; @@ -434,11 +449,11 @@ static void test_beginSending_oversizedPayloadAbortsSafely() TEST_ASSERT_EQUAL_UINT(0, result); TEST_ASSERT_NULL(testRadio->getSendingPacket()); - // Verify rejected packet was released to packetPool and its slot is reusable - meshtastic_MeshPacket *reallocated = packetPool.allocZeroed(); - TEST_ASSERT_NOT_NULL(reallocated); - TEST_ASSERT_EQUAL_PTR(p, reallocated); - packetPool.release(reallocated); + // beginSending() owns the packet it rejects, so the pool's live byte count has to land back + // where it started. Do not assert the next alloc hands the same address back: on native + // packetPool is a malloc/free MemoryDynamic and the coverage build's ASan quarantine holds the + // freed chunk, so the address always differs. + TEST_ASSERT_EQUAL_INT32(liveBefore, livePacketPoolBytes()); } void setUp(void)