diff --git a/platformio.ini b/platformio.ini index f3ada1338..17f6c7734 100644 --- a/platformio.ini +++ b/platformio.ini @@ -167,5 +167,6 @@ build_src_filter = -<*> +<../src/Utils.cpp> +<../src/Packet.cpp> + +<../src/helpers/ConfigSerializer.cpp> lib_deps = google/googletest @ 1.17.0 diff --git a/src/helpers/ConfigSerializer.cpp b/src/helpers/ConfigSerializer.cpp index 775ef2202..9d1323e43 100644 --- a/src/helpers/ConfigSerializer.cpp +++ b/src/helpers/ConfigSerializer.cpp @@ -111,7 +111,7 @@ bool ConfigSerializer::loadSerial(Stream& s) { if (sp < CONFIG_MAX_DEPTH - 1) { sp++; } else { - Serial.printf("Error: max nesting reached"); // TODO: debug logging + //Serial.printf("Error: max nesting reached"); // TODO: debug logging context.success = false; break; } @@ -119,7 +119,7 @@ bool ConfigSerializer::loadSerial(Stream& s) { if (sp > 0) { sp--; } else { - Serial.printf("Error: too many closing '}'"); // TODO: debug logging + //Serial.printf("Error: too many closing '}'"); // TODO: debug logging context.success = false; break; } diff --git a/test/mocks/Arduino.h b/test/mocks/Arduino.h new file mode 100644 index 000000000..f0821bf4f --- /dev/null +++ b/test/mocks/Arduino.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/test/mocks/Stream.h b/test/mocks/Stream.h index 195a30297..9b41c54d3 100644 --- a/test/mocks/Stream.h +++ b/test/mocks/Stream.h @@ -1,10 +1,71 @@ #pragma once +#include +#include +#include + // Mock Stream class for native testing // Provides minimal interface needed by Utils.h -class Stream { +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + +class Print +{ public: - virtual void print(char c) {} - virtual void print(const char* str) {} + virtual size_t write(uint8_t b) { return 1; } + size_t write(const char *str) + { + if(str == NULL) { + return 0; + } + return write((const uint8_t *) str, strlen(str)); + } + virtual size_t write(const uint8_t *buffer, size_t size) { + size_t t = 0; + for (int i = 0; i < size; i++) { t += write(buffer[i]); } + return t; + } + size_t write(const char *buffer, size_t size) + { + return write((const uint8_t *) buffer, size); + } + + virtual size_t print(unsigned char b, int r = DEC) { return 0; } + virtual size_t print(int v, int r = DEC) { return 0; } + virtual size_t print(unsigned int v, int r = DEC) { return 0; } + virtual size_t print(long v, int r = DEC) { return 0; } + virtual size_t print(unsigned long v, int r = DEC) { return 0; } + virtual size_t print(long long v, int r = DEC) { return 0; } + virtual size_t print(unsigned long long v, int r = DEC) { return 0; } + virtual size_t print(double v, int p = 2) { return 0; } + + size_t print(char c) { return write(c); } + size_t print(const char* str) { return write(str); } + + //size_t println(void) { return 0; } + + virtual void flush() { /* Empty implementation for backward compatibility */ } }; + +class Stream: public Print +{ +public: + virtual int available() { return 0; } + virtual int read() { return -1; } + virtual int peek() { return 0; } + + virtual size_t readBytes(char *buffer, size_t length) { + size_t i = 0; + while (i < length && available()) { + buffer[i++] = read(); + } + return i; + } + virtual size_t readBytes(uint8_t *buffer, size_t length) + { + return readBytes((char *) buffer, length); + } +}; \ No newline at end of file diff --git a/test/test_config_serializer/test_config_serializer.cpp b/test/test_config_serializer/test_config_serializer.cpp new file mode 100644 index 000000000..d6ce68135 --- /dev/null +++ b/test/test_config_serializer/test_config_serializer.cpp @@ -0,0 +1,136 @@ +#include +#include "helpers/ConfigSerializer.h" + +#define TEST_INT_S "56" +#define TEST_INT 56 +#define TEST_FLOAT_S "-6.123" +#define TEST_FLOAT -6.1230f +#define TEST_DOUBLE_S "12.123456" +#define TEST_DOUBLE 12.123456 + +class MockInputStream : public Stream { + const char* _text; + int pos, len; +public: + MockInputStream(const char* text) : _text(text) { pos = 0; len = strlen(text); } + int available() override { return len - pos; } + int read() override { if (pos < len) { return _text[pos++]; } return -1; } + int peek() override { if (pos < len) { return _text[pos]; } return -1; } +}; + +class MockPrintStream : public Stream { + int len = 0; + uint8_t _buf[1024]; +public: + size_t write(uint8_t b) override { + if (len < sizeof(_buf)) { + _buf[len++] = b; + return 1; + } + return 0; + } + + size_t print(unsigned char b, int r) override { if (b == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(int v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(unsigned int v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(unsigned long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(long long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(unsigned long long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(double v, int p = 2) override { + if (p == 6) return Print::print(TEST_DOUBLE_S); + if (p == 4) return Print::print(TEST_FLOAT_S); + return 0; + } + + int getLength() const { return len; } + const uint8_t* getBytes() const { return _buf; } +}; + +class TestStruct : public ConfigSerializer { + protected: + void structure() override { + def("age", age); + def("flags", flags); + def("name", name, sizeof(name)); + } + public: + int32_t age; + char name[16]; + uint8_t flags; +}; + +// ── saveSerial: basic ─────────────────────────────────────────────────────── + +TEST(ConfigSerializer, SaveSerial_Basic) { + MockPrintStream s; + TestStruct data; + + data.age = TEST_INT; + data.flags = TEST_INT; + strcpy(data.name, "Scott"); + + bool success = data.saveSerial(s); + EXPECT_TRUE(success); + + auto l = s.getLength(); + const char* expect = "{age:" TEST_INT_S ",flags:" TEST_INT_S ",name:\"Scott\"}"; + EXPECT_EQ(strlen(expect), l); + + bool match = memcmp(s.getBytes(), expect, l) == 0; + EXPECT_TRUE(match); +} + +// ── loadSerial: basic ─────────────────────────────────────────────────────── + +TEST(ConfigSerializer, LoadSerial_Basic) { + MockInputStream s("{age:" TEST_INT_S ",flags:" TEST_INT_S ",name:\"Scott\"}"); + TestStruct data; + + bool success = data.loadSerial(s); + EXPECT_TRUE(success); + + EXPECT_EQ(TEST_INT, data.age); + EXPECT_EQ(TEST_INT, data.flags); + bool match = strcmp("Scott", data.name) == 0; + EXPECT_TRUE(match); +} + +TEST(ConfigSerializer, LoadSerial_UnmatchedBraces) { + MockInputStream s("{age:" TEST_INT_S ",flags:" TEST_INT_S ",name:\"Scott\""); + TestStruct data; + + bool success = data.loadSerial(s); + EXPECT_FALSE(success); +} + +TEST(ConfigSerializer, LoadSerial_MissingCommas) { + MockInputStream s("{age:" TEST_INT_S " flags:" TEST_INT_S " name:\"Scott\"}"); + TestStruct data; + + bool success = data.loadSerial(s); + EXPECT_FALSE(success); +} + +TEST(ConfigSerializer, LoadSerial_IgnoreUnknowns) { + MockInputStream s("{age:" TEST_INT_S ",xxx:" TEST_INT_S ",name:\"Scott\"}"); + TestStruct data; + data.flags = 1; + + // should ignore the 'xxx' property + bool success = data.loadSerial(s); + EXPECT_TRUE(success); + + EXPECT_EQ(TEST_INT, data.age); + EXPECT_EQ(1, data.flags); // flags should be unmodified + bool match = strcmp("Scott", data.name) == 0; + EXPECT_TRUE(match); +} + + +// ── main ─────────────────────────────────────────────────────── + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}