Files
firmware/test/test_radio/test_main.cpp
Ben Meadors 222d3e6b62 Fix zero CR and add unit tests for applyModemConfig coding rate behavior (#10070)
* Fix zero CR and add unit tests for applyModemConfig coding rate behavior

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: initialize region configuration in setUp for RadioInterface tests

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-04 08:27:39 +11:00

230 lines
8.1 KiB
C++

#include "MeshRadio.h"
#include "MeshService.h"
#include "RadioInterface.h"
#include "TestUtil.h"
#include <unity.h>
#include "meshtastic/config.pb.h"
class MockMeshService : public MeshService
{
public:
void sendClientNotification(meshtastic_ClientNotification *n) override { releaseClientNotificationToPool(n); }
};
static MockMeshService *mockMeshService;
// Test shim to expose protected radio parameters set by applyModemConfig()
class TestableRadioInterface : public RadioInterface
{
public:
TestableRadioInterface() : RadioInterface() {}
uint8_t getCr() const { return cr; }
uint8_t getSf() const { return sf; }
float getBw() const { return bw; }
// Override reconfigure to call the base which invokes applyModemConfig()
bool reconfigure() override { return RadioInterface::reconfigure(); }
// Stubs for pure virtual methods required by RadioInterface
uint32_t getPacketTime(uint32_t, bool) override { return 0; }
ErrorCode send(meshtastic_MeshPacket *p) override { return ERRNO_OK; }
};
static void test_bwCodeToKHz_specialMappings()
{
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 31.25f, bwCodeToKHz(31));
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 62.5f, bwCodeToKHz(62));
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 203.125f, bwCodeToKHz(200));
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 406.25f, bwCodeToKHz(400));
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 812.5f, bwCodeToKHz(800));
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 1625.0f, bwCodeToKHz(1600));
}
static void test_bwCodeToKHz_passthrough()
{
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 125.0f, bwCodeToKHz(125));
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 250.0f, bwCodeToKHz(250));
}
static void test_validateConfigLora_noopWhenUsePresetFalse()
{
meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero;
cfg.use_preset = false;
cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US;
cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
cfg.bandwidth = 123;
cfg.spread_factor = 8;
RadioInterface::validateConfigLora(cfg);
TEST_ASSERT_EQUAL_UINT16(123, cfg.bandwidth);
TEST_ASSERT_EQUAL_UINT32(8, cfg.spread_factor);
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST, cfg.modem_preset);
}
static void test_validateConfigLora_validPreset_nonWideRegion()
{
meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero;
cfg.use_preset = true;
cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US;
cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
TEST_ASSERT_TRUE(RadioInterface::validateConfigLora(cfg));
}
static void test_validateConfigLora_validPreset_wideRegion()
{
meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero;
cfg.use_preset = true;
cfg.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24;
cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
TEST_ASSERT_TRUE(RadioInterface::validateConfigLora(cfg));
}
static void test_validateConfigLora_rejectsInvalidPresetForRegion()
{
meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero;
cfg.use_preset = true;
cfg.region = meshtastic_Config_LoRaConfig_RegionCode_EU_868;
cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO;
TEST_ASSERT_FALSE(RadioInterface::validateConfigLora(cfg));
}
static void test_clampConfigLora_invalidPresetClampedToDefault()
{
meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero;
cfg.use_preset = true;
cfg.region = meshtastic_Config_LoRaConfig_RegionCode_EU_868;
cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO;
RadioInterface::clampConfigLora(cfg);
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, cfg.modem_preset);
}
static void test_clampConfigLora_validPresetUnchanged()
{
meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero;
cfg.use_preset = true;
cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US;
cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
RadioInterface::clampConfigLora(cfg);
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST, cfg.modem_preset);
}
// -----------------------------------------------------------------------
// applyModemConfig() coding rate tests (via reconfigure)
// -----------------------------------------------------------------------
static TestableRadioInterface *testRadio;
// After fresh flash: coding_rate=0, use_preset=true, modem_preset=LONG_FAST
// CR should come from the preset (5 for LONG_FAST), not from the zero default.
static void test_applyModemConfig_freshFlashCodingRateNotZero()
{
config.lora = meshtastic_Config_LoRaConfig_init_zero;
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
config.lora.use_preset = true;
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
// coding_rate is 0 (default after init_zero, same as fresh flash)
testRadio->reconfigure();
// LONG_FAST preset has cr=5; must never be 0
TEST_ASSERT_EQUAL_UINT8(5, testRadio->getCr());
TEST_ASSERT_EQUAL_UINT8(11, testRadio->getSf());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 250.0f, testRadio->getBw());
}
// When coding_rate matches the preset exactly, should still use the preset value
static void test_applyModemConfig_codingRateMatchesPreset()
{
config.lora = meshtastic_Config_LoRaConfig_init_zero;
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
config.lora.use_preset = true;
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW;
config.lora.coding_rate = 8; // LONG_SLOW default is cr=8
testRadio->reconfigure();
TEST_ASSERT_EQUAL_UINT8(8, testRadio->getCr());
}
// Custom CR higher than preset should be used
static void test_applyModemConfig_customCodingRateHigherThanPreset()
{
config.lora = meshtastic_Config_LoRaConfig_init_zero;
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
config.lora.use_preset = true;
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
config.lora.coding_rate = 7; // LONG_FAST preset has cr=5, 7 > 5
testRadio->reconfigure();
TEST_ASSERT_EQUAL_UINT8(7, testRadio->getCr());
}
// Custom CR lower than preset: preset wins (higher is more robust)
static void test_applyModemConfig_customCodingRateLowerThanPreset()
{
config.lora = meshtastic_Config_LoRaConfig_init_zero;
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
config.lora.use_preset = true;
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW;
config.lora.coding_rate = 5; // LONG_SLOW preset has cr=8, 5 < 8
testRadio->reconfigure();
TEST_ASSERT_EQUAL_UINT8(8, testRadio->getCr());
}
void setUp(void)
{
mockMeshService = new MockMeshService();
service = mockMeshService;
// RadioInterface computes slotTimeMsec during construction and expects myRegion to be valid.
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
initRegion();
testRadio = new TestableRadioInterface();
}
void tearDown(void)
{
delete testRadio;
testRadio = nullptr;
service = nullptr;
delete mockMeshService;
mockMeshService = nullptr;
}
void setup()
{
delay(10);
delay(2000);
initializeTestEnvironment();
UNITY_BEGIN();
RUN_TEST(test_bwCodeToKHz_specialMappings);
RUN_TEST(test_bwCodeToKHz_passthrough);
RUN_TEST(test_validateConfigLora_noopWhenUsePresetFalse);
RUN_TEST(test_validateConfigLora_validPreset_nonWideRegion);
RUN_TEST(test_validateConfigLora_validPreset_wideRegion);
RUN_TEST(test_validateConfigLora_rejectsInvalidPresetForRegion);
RUN_TEST(test_clampConfigLora_invalidPresetClampedToDefault);
RUN_TEST(test_clampConfigLora_validPresetUnchanged);
RUN_TEST(test_applyModemConfig_freshFlashCodingRateNotZero);
RUN_TEST(test_applyModemConfig_codingRateMatchesPreset);
RUN_TEST(test_applyModemConfig_customCodingRateHigherThanPreset);
RUN_TEST(test_applyModemConfig_customCodingRateLowerThanPreset);
exit(UNITY_END());
}
void loop() {}