* DynamicConfigSerializer fixes, and unit tests

This commit is contained in:
Scott Powell
2026-08-24 19:42:55 +10:00
parent 845242f4aa
commit 8ccc9928a8
6 changed files with 115 additions and 41 deletions
+1
View File
@@ -171,6 +171,7 @@ build_src_filter =
+<../src/Utils.cpp>
+<../src/Packet.cpp>
+<../src/helpers/ConfigSerializer.cpp>
+<../src/helpers/DynamicConfigSerializer.cpp>
lib_deps =
google/googletest @ 1.17.0
+2 -2
View File
@@ -104,13 +104,13 @@ private:
float getBandwidth() const override { return _parent->bw; }
void setBandwidth(float bw) override { _parent->bw = bw; markDirty(); }
uint8_t getSpreadFactor() const override { return _parent->sf; }
void setSpreadFactor(uint8_t sf) override { _parent->sf; markDirty(); }
void setSpreadFactor(uint8_t sf) override { _parent->sf = sf; markDirty(); }
uint8_t getCodingRate() const override { return _parent->cr; }
void setCodingRate(uint8_t cr) override { _parent->cr = cr; markDirty(); }
float getAirtimeFactor() const override { return _parent->airtime_factor; }
void setAirtimeFactor(float af) override { _parent->airtime_factor = af; markDirty(); }
bool isCadEnabled() const override { return _parent->cad_enabled; }
void setCadEnabled(bool en) override { _parent->cad_enabled; markDirty(); }
void setCadEnabled(bool en) override { _parent->cad_enabled = en; markDirty(); }
uint8_t getIntThresh() const override { return _parent->interference_threshold; }
void setIntThresh(uint8_t t) override { _parent->interference_threshold = t; markDirty(); }
uint8_t getRxGain() const override { return _parent->rx_boosted_gain; }
+3 -1
View File
@@ -19,6 +19,7 @@ class ConfigSerializer {
int8_t _depth;
bool _dirty = false;
protected:
enum OP { READ, WRITE };
class Context {
@@ -39,13 +40,14 @@ class ConfigSerializer {
const char* getToken() const { return rd_buf; }
bool keyMatch(int8_t depth, const char* key) { return strcmp(key, _keys[depth]) == 0; }
void setKey(uint8_t depth, const char* key) { strcpy(_keys[depth], key); }
const char* getKey(uint8_t depth) { return _keys[depth]; }
};
Context* _context = NULL;
int8_t getDepth() const { return _depth; }
void writeComma();
protected:
ConfigSerializer() { }
void def(const char* key, char* value, size_t max_len); // max_len inclusive of null
+26 -13
View File
@@ -6,7 +6,7 @@
#define KEY_SEP_CHAR ':'
#define KEY_SEP_STR ":"
bool DynamicConfigSerializer::setByKey(const char* key, const char* value) {
bool DynamicConfigSerializer::setByKeyPrv(const char* key, const char* value) {
if (_fallback && _fallback->setByKey(key, value)) return true;
// TODO: guard for bad chars (':' or '|')
@@ -34,16 +34,26 @@ bool DynamicConfigSerializer::setByKey(const char* key, const char* value) {
}
// now append new key/value (if it fits)
if (strlen(new_config) + strlen(key) + strlen(value) + 2 < sizeof(_config)-1) {
if (new_config[0]) {
strcat(new_config, PROP_SEP_STR);
}
strcat(new_config, key);
strcat(new_config, KEY_SEP_STR);
strcat(new_config, value);
strcpy(_config, new_config); // commit new serialized string
markDirty();
return true;
}
return false; // didn't fit in _config[]
}
bool DynamicConfigSerializer::setByKey(const char* key, const char* value) {
if (setByKeyPrv(key, value)) {
markDirty();
return true;
}
return false;
}
bool DynamicConfigSerializer::getByKey(const char* key, char* value, size_t max_len) {
if (_fallback && _fallback->getByKey(key, value, max_len)) return true;
@@ -66,19 +76,22 @@ bool DynamicConfigSerializer::getByKey(const char* key, char* value, size_t max_
}
void DynamicConfigSerializer::structure() {
char tmp[MAX_DYNAMIC_CONFG];
strcpy(tmp, _config); // make a (modifiable) copy
if (_context->op() == OP::WRITE) {
char tmp[MAX_DYNAMIC_CONFG];
strcpy(tmp, _config); // make a (modifiable) copy
const char* parts[8];
int n = mesh::Utils::parseTextParts(tmp, parts, 8, PROP_SEP_CHAR);
const char* parts[8];
int n = mesh::Utils::parseTextParts(tmp, parts, 8, PROP_SEP_CHAR);
// dynamically call def()'s
for (int i = 0; i < n; i++) {
char* item = (char *) parts[i];
char* eq = strchr(item, KEY_SEP_CHAR);
if (eq) {
*eq = 0; // replace separator with null terminator
def(item, eq + 1, MAX_DYNAMIC_CONFG/2); // maximum HALF of total for individual property value
for (int i = 0; i < n; i++) {
char* item = (char *) parts[i];
char* eq = strchr(item, KEY_SEP_CHAR);
if (eq) {
*eq = 0; // replace separator with null terminator
def(item, eq + 1, MAX_DYNAMIC_CONFG/2);
}
}
} else {
setByKeyPrv(_context->getKey(getDepth()), _context->getToken());
}
}
+3
View File
@@ -1,3 +1,4 @@
#pragma once
#include "ConfigSerializer.h"
#include "KeyValueStore.h"
@@ -9,6 +10,8 @@ class DynamicConfigSerializer : public ConfigSerializer, public KeyValueStore {
char _config[MAX_DYNAMIC_CONFG];
KeyValueStore* _fallback;
bool setByKeyPrv(const char* key, const char* value);
protected:
void structure() override;
@@ -1,13 +1,6 @@
#include <gtest/gtest.h>
#include "helpers/ConfigSerializer.h"
class NativeFileSystem {
public:
void mkdir(const char*) { }
};
#define FILESYSTEM NativeFileSystem
#include "helpers/CommonCLI.h"
#undef FILESYSTEM
#include "helpers/DynamicConfigSerializer.h"
#define TEST_INT_S "56"
#define TEST_INT 56
@@ -192,28 +185,90 @@ TEST(ConfigSerializer, LoadSerial_IgnoreUnknowns) {
EXPECT_TRUE(match);
}
TEST(NodePrefs, FemGainSettingsRoundTrip) {
NodePrefs saved;
saved.radio_fem_rxgain = 0;
saved.radio_fem_txgain = 1;
TEST(DynamicConfigSerializer, GetSet_Basic) {
DynamicConfigSerializer data;
MockPrintStream output;
ASSERT_TRUE(saved.saveSerial(output));
bool s1 = data.setByKey("age", "11");
bool s2 = data.setByKey("name", "Scott");
EXPECT_TRUE(s1 && s2);
std::string serialised(reinterpret_cast<const char*>(output.getBytes()), output.getLength());
EXPECT_NE(std::string::npos, serialised.find("fem_rxgain:0"));
EXPECT_NE(std::string::npos, serialised.find("fem_txgain:1"));
char tmp[32];
bool g1 = data.getByKey("age", tmp, 31);
EXPECT_TRUE(g1);
EXPECT_STREQ("11", tmp);
MockInputStream input(serialised.c_str());
NodePrefs loaded;
loaded.radio_fem_rxgain = 1;
loaded.radio_fem_txgain = 0;
ASSERT_TRUE(loaded.loadSerial(input));
EXPECT_EQ(0, loaded.radio_fem_rxgain);
EXPECT_EQ(1, loaded.radio_fem_txgain);
bool g2 = data.getByKey("name", tmp, 31);
EXPECT_TRUE(g2);
EXPECT_STREQ("Scott", tmp);
}
TEST(DynamicConfigSerializer, Set_Replaces) {
DynamicConfigSerializer data;
bool s1 = data.setByKey("age", "11");
bool s2 = data.setByKey("name", "Scott");
EXPECT_TRUE(s1 && s2);
bool s3 = data.setByKey("age", "333");
EXPECT_TRUE(s3);
char tmp[32];
bool g1 = data.getByKey("age", tmp, 31);
EXPECT_TRUE(g1);
EXPECT_STREQ("333", tmp);
bool g2 = data.getByKey("name", tmp, 31);
EXPECT_TRUE(g2);
EXPECT_STREQ("Scott", tmp);
}
TEST(DynamicConfigSerializer, GetUnknown_Fail) {
DynamicConfigSerializer data;
bool s1 = data.setByKey("age", "11");
EXPECT_TRUE(s1);
char tmp[32];
bool g2 = data.getByKey("name", tmp, 31);
EXPECT_FALSE(g2);
}
TEST(DynamicConfigSerializer, SaveCustom_Basic) {
MockPrintStream s;
DynamicConfigSerializer data;
bool s1 = data.setByKey("age", "11");
bool s2 = data.setByKey("name", "Scott");
EXPECT_TRUE(s1 && s2);
bool success = data.saveSerial(s);
EXPECT_TRUE(success);
auto l = s.getLength();
char tmp[128];
memcpy(tmp, s.getBytes(), l);
tmp[l] = 0;
const char* expect = "{age:\"11\",name:\"Scott\"}";
EXPECT_STREQ(expect, tmp);
}
TEST(DynamicConfigSerializer, LoadCustom_Basic) {
MockInputStream s("{age:\"" TEST_INT_S "\",name:\"Scott\"}");
DynamicConfigSerializer data;
bool success = data.loadSerial(s);
EXPECT_TRUE(success);
char tmp[32];
bool g1 = data.getByKey("age", tmp, 31);
EXPECT_TRUE(g1);
EXPECT_STREQ(TEST_INT_S, tmp);
bool g2 = data.getByKey("name", tmp, 31);
EXPECT_TRUE(g2);
EXPECT_STREQ("Scott", tmp);
}
// ── main ───────────────────────────────────────────────────────