Some fixes and tidies for testing both online and in unit_tests
This commit is contained in:
+57
-4
@@ -15,6 +15,30 @@ pio test -e native -f test_your_module
|
||||
pio test -e native -f test_your_module -vvv
|
||||
```
|
||||
|
||||
**Never pipe through `| tail -N` to shorten output.** PlatformIO prints build errors at the top of output and test results at the bottom; `tail` will show stale cached results from a prior successful build while hiding the compile error that caused the current run to fail.
|
||||
|
||||
**Preferred pattern — redirect to file, then grep:**
|
||||
|
||||
```bash
|
||||
# Redirect all output to a file; grep for errors and results after it exits
|
||||
pio test -e native -f test_your_module > /tmp/test_out.txt 2>&1
|
||||
echo "exit: $?"
|
||||
grep -E 'error:|PASS|FAIL|succeeded|failed' /tmp/test_out.txt
|
||||
tail -15 /tmp/test_out.txt
|
||||
```
|
||||
|
||||
Why: piping through `| grep` line-buffers the output and suppresses all progress until the process exits, making it look hung. The redirect approach lets the build stream normally while still giving you filtered results afterwards.
|
||||
|
||||
**`externally-managed-environment` error on Ubuntu/Debian:**
|
||||
|
||||
If `pio test` fails immediately with `error: externally-managed-environment`, the system `pio` binary is using the OS Python which newer distros lock down. Use PlatformIO's own venv instead:
|
||||
|
||||
```bash
|
||||
~/.platformio/penv/bin/python -m platformio test -e native -f test_your_module > /tmp/test_out.txt 2>&1
|
||||
grep -E 'error:|PASS|FAIL|succeeded|failed' /tmp/test_out.txt
|
||||
tail -15 /tmp/test_out.txt
|
||||
```
|
||||
|
||||
### Helper Scripts (Useful Shortcuts)
|
||||
|
||||
These wrappers are handy when local host dependencies are missing or when you want repeatable commands.
|
||||
@@ -185,20 +209,34 @@ Subclass the module under test to make protected methods callable and private me
|
||||
class YourModuleTestShim : public YourModule
|
||||
{
|
||||
public:
|
||||
// Expose protected methods
|
||||
// Pull protected methods into public scope via using.
|
||||
// IMPORTANT: using requires the method to be protected (or public) in the base —
|
||||
// friend alone does NOT satisfy this. See pitfall #6.
|
||||
using YourModule::runOnce;
|
||||
using YourModule::someProtectedMethod;
|
||||
|
||||
// Access private members via friend (see below)
|
||||
// Wrap private members with setter methods (friend grants direct access here).
|
||||
void setPrivateField(int x) { privateField = x; }
|
||||
};
|
||||
```
|
||||
|
||||
In the module header, grant friend access under the `UNIT_TEST` define (set automatically by PlatformIO's test framework):
|
||||
For methods you want to expose via `using`, use the conditional access-specifier pattern in the header — **not** plain `friend`:
|
||||
|
||||
```cpp
|
||||
// In YourModule.h, inside the class body:
|
||||
#ifdef UNIT_TEST
|
||||
#ifdef PIO_UNIT_TESTING
|
||||
protected:
|
||||
#else
|
||||
private:
|
||||
#endif
|
||||
bool someMethod();
|
||||
```
|
||||
|
||||
For private _member variables_ that a shim setter needs to touch directly, `friend` is sufficient (no `using` involved):
|
||||
|
||||
```cpp
|
||||
// In YourModule.h, inside the class body:
|
||||
#ifdef PIO_UNIT_TESTING
|
||||
friend class YourModuleTestShim;
|
||||
#endif
|
||||
```
|
||||
@@ -284,6 +322,21 @@ Fixed-size data structures (hash sets, ring buffers) overflow when tests inject
|
||||
|
||||
**Fix:** Simulate multiple realistic time windows rather than one massive burst. Let adaptive mechanisms (if any) self-tune over several rolls.
|
||||
|
||||
### 6. Granting test access to private/protected members
|
||||
|
||||
PlatformIO defines `PIO_UNIT_TESTING` during `pio test` builds. Several production headers (`TransmitHistory.h`, `CryptoEngine.h`, `MQTT.h`, `RTC.h`) use this to gate test-only visibility changes. PlatformIO also defines `UNIT_TEST` in the same builds for backward compatibility, but that spelling is deprecated — always use `PIO_UNIT_TESTING` in new code. The established pattern for exposing a private method to a test shim **without widening production visibility**:
|
||||
|
||||
```cpp
|
||||
#ifdef PIO_UNIT_TESTING
|
||||
protected:
|
||||
#else
|
||||
private:
|
||||
#endif
|
||||
bool myMethod();
|
||||
```
|
||||
|
||||
**Critical C++ rule:** a `using` declaration in a derived class (e.g. `using Base::myMethod`) requires `myMethod` to be `protected` or `public` in the base — `friend` alone does **not** satisfy this. Adding `friend class TestShim` while leaving the method `private` will still fail to compile. Use the conditional access-specifier pattern above, not `friend`.
|
||||
|
||||
## setUp/tearDown Checklist
|
||||
|
||||
- [ ] Create and clear MockNodeDB (if needed)
|
||||
|
||||
@@ -36,7 +36,6 @@ static MockMeshService *mockMeshService;
|
||||
// -----------------------------------------------------------------------
|
||||
// getRegion() tests
|
||||
// -----------------------------------------------------------------------
|
||||
extern const RegionInfo *getRegion(meshtastic_Config_LoRaConfig_RegionCode code);
|
||||
|
||||
static void test_getRegion_returnsCorrectRegion_US()
|
||||
{
|
||||
@@ -104,6 +103,29 @@ static void test_validateConfigRegion_unsetRegionReturnsTrue()
|
||||
TEST_ASSERT_TRUE(RadioInterface::validateConfigRegion(cfg));
|
||||
}
|
||||
|
||||
static void test_validateConfigRegion_unknownCodeReturnsFalse()
|
||||
{
|
||||
meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero;
|
||||
cfg.region = (meshtastic_Config_LoRaConfig_RegionCode)255;
|
||||
|
||||
devicestate.owner.is_licensed = false;
|
||||
|
||||
// Unknown code is not in the regions table; getRegion() returns the UNSET sentinel,
|
||||
// whose .code != 255, so validateConfigRegion should reject it.
|
||||
TEST_ASSERT_FALSE(RadioInterface::validateConfigRegion(cfg));
|
||||
}
|
||||
|
||||
static void test_validateConfigRegion_anotherUnknownCodeReturnsFalse()
|
||||
{
|
||||
meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero;
|
||||
cfg.region = (meshtastic_Config_LoRaConfig_RegionCode)99;
|
||||
|
||||
devicestate.owner.is_licensed = true;
|
||||
|
||||
// Unknown code should be rejected even when owner is licensed.
|
||||
TEST_ASSERT_FALSE(RadioInterface::validateConfigRegion(cfg));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Shadow tables for testing (preset lists → profiles → regions → lookup)
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -937,6 +959,8 @@ void setup()
|
||||
// validateConfigRegion()
|
||||
RUN_TEST(test_validateConfigRegion_validRegionReturnsTrue);
|
||||
RUN_TEST(test_validateConfigRegion_unsetRegionReturnsTrue);
|
||||
RUN_TEST(test_validateConfigRegion_unknownCodeReturnsFalse);
|
||||
RUN_TEST(test_validateConfigRegion_anotherUnknownCodeReturnsFalse);
|
||||
|
||||
// Shadow table tests
|
||||
RUN_TEST(test_shadowTable_spacedProfileHasNonZeroSpacing);
|
||||
|
||||
@@ -27,12 +27,6 @@
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#if defined(UNIT_TEST)
|
||||
#define IS_RUNNING_TESTS 1
|
||||
#else
|
||||
#define IS_RUNNING_TESTS 0
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// Minimal router needed to receive messages from MQTT.
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "configuration.h"
|
||||
|
||||
#if defined(UNIT_TEST)
|
||||
#define IS_RUNNING_TESTS 1
|
||||
#else
|
||||
#define IS_RUNNING_TESTS 0
|
||||
#endif
|
||||
|
||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
|
||||
!defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#include "modules/SerialModule.h"
|
||||
|
||||
Reference in New Issue
Block a user