diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..8b052ac
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.14)
+project(BytePack VERSION 0.1.0 LANGUAGES CXX)
+
+# Create an INTERFACE library.
+add_library(BytePack INTERFACE)
+
+# Alias with author prefix. Consumers link "alkonosst::BytePack".
+add_library(alkonosst::BytePack ALIAS BytePack)
+
+# Specify the include directories for the library.
+target_include_directories(BytePack INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/src)
+
+# Set a C++ standard requirement for the library. This will also propagate to consumers of the library.
+target_compile_features(BytePack INTERFACE cxx_std_17)
\ No newline at end of file
diff --git a/README.md b/README.md
index 1123a17..cc09fb3 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,9 @@
+
+
+
@@ -35,6 +38,7 @@
- [Installation](#installation)
- [PlatformIO](#platformio)
- [Arduino IDE](#arduino-ide)
+ - [CMake](#cmake)
- [Usage](#usage)
- [Including the library](#including-the-library)
- [Namespace](#namespace)
@@ -58,7 +62,7 @@
# Description
-**BytePack** is a header-only C++17 Arduino library for serializing plain C++ structs into compact, portable byte buffers. A message is any struct that lists its fields in a single `io()` member function; that one function drives serialization, deserialization and compile-time size counting, so the field list is written once and can never get out of sync.
+**BytePack** is a header-only C++17 Arduino/Native library for serializing plain C++ structs into compact, portable byte buffers. A message is any struct that lists its fields in a single `io()` member function; that one function drives serialization, deserialization and compile-time size counting, so the field list is written once and can never get out of sync.
The wire format is explicit little-endian with no padding, making it safe to exchange between different architectures (ESP32, ARM, a PC on the other end of a link, etc.). All buffers are caller-provided and statically sized; there is no dynamic memory allocation.
@@ -151,6 +155,23 @@ lib_deps =
3. Search for **"BytePack"**.
4. Click **Install**.
+## CMake
+
+For desktop C++ projects, pull the library with `FetchContent` and link the `alkonosst::BytePack`
+target:
+
+```cmake
+include(FetchContent)
+FetchContent_Declare(
+ BytePack
+ GIT_REPOSITORY https://github.com/alkonosst/BytePack.git
+ GIT_TAG vx.y.z # pin a release tag (recommended), or a branch/commit
+)
+FetchContent_MakeAvailable(BytePack)
+
+target_link_libraries(your_app PRIVATE alkonosst::BytePack)
+```
+
# Usage
## Including the library
diff --git a/examples/BasicNative/BasicNative.cpp b/examples/BasicNative/BasicNative.cpp
new file mode 100644
index 0000000..24ea362
--- /dev/null
+++ b/examples/BasicNative/BasicNative.cpp
@@ -0,0 +1,81 @@
+/**
+ * SPDX-FileCopyrightText: 2026 Maximiliano Ramirez
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * Basic Native Example Overview:
+ * - Mirrors the Basic Arduino example, swapping Serial for printf. Useful for native builds.
+ * - Build and run locally:
+ * PowerShell: $env:EXAMPLE="examples/BasicNative"; pio run -e native-example -t exec
+ * bash/WSL : export EXAMPLE="examples/BasicNative"; pio run -e native-example -t exec
+ */
+
+#include
+
+#include "BytePack.h"
+using namespace BytePack;
+
+// A message is any struct that lists its fields in io()
+struct Telemetry {
+ uint32_t uptime_ms = 0;
+ int16_t temperature = 0; // centi-degrees Celsius
+ bool relay_on = false;
+
+ template
+ constexpr void io(Archive& ar) {
+ ar(uptime_ms, temperature, relay_on);
+ }
+};
+
+static void printHex(const uint8_t* data, const size_t len) {
+ for (size_t i = 0; i < len; ++i)
+ printf("%02X ", data[i]);
+ printf("\n");
+}
+
+int main() {
+ printf("-------------------------------\n");
+ printf("BytePack - Basic Native Example\n");
+ printf("-------------------------------\n");
+
+ // Buffer sized at compile time: 4 (uptime) + 2 (temperature) + 1 (relay) = 7 bytes
+ uint8_t buffer[getMaxPackedSize()] = {};
+
+ // Fill and serialize (e.g. on the transmitting device)
+ Telemetry tx;
+ tx.uptime_ms = 123456;
+ tx.temperature = 2350; // 23.50 C
+ tx.relay_on = true;
+
+ const size_t written = serialize(tx, buffer, sizeof(buffer));
+ if (written == 0) {
+ printf("Serialization failed: buffer too small\n");
+ return 1;
+ }
+
+ printf("Serialized %zu bytes (little-endian):\n", written);
+ printHex(buffer, written);
+ printf("\n");
+
+ printf("Tx:\n");
+ printf("- uptime_ms: %u\n", tx.uptime_ms);
+ printf("- temperature: %d\n", tx.temperature);
+ printf("- relay_on: %s\n", tx.relay_on ? "true" : "false");
+ printf("\n");
+
+ // Deserialize into a fresh struct (e.g. on the receiving device)
+ Telemetry rx;
+ if (!deserialize(rx, buffer, written)) {
+ printf("Deserialization failed: truncated or invalid input\n");
+ return 1;
+ }
+
+ printf("Rx:\n");
+ printf("- uptime_ms: %u\n", rx.uptime_ms);
+ printf("- temperature: %d\n", rx.temperature);
+ printf("- relay_on: %s\n", rx.relay_on ? "true" : "false");
+
+ return 0;
+}
diff --git a/platformio.ini b/platformio.ini
index 6eeb690..4155d46 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -8,37 +8,35 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
-[platformio]
-lib_dir = .
-src_dir = examples/Basic
-; src_dir = examples/CustomTypes
-; src_dir = examples/Dispatch
-; src_dir = examples/MessageHeader
-; src_dir = examples/NestedMessages
-; src_dir = examples/SizeBudget
-; src_dir = examples/WriterReader
+; ------------------------------------------------------------------------------------------------ ;
+; Common configurations ;
+; ------------------------------------------------------------------------------------------------ ;
+; Common configuration for all environments.
[common]
-; Tests
-; Ignore Unity library to avoid scanning .pio/libdeps/Unity/examples/ as part of lib_dir = .
-; Add Unity src path manually so unity.h is still found during test compilation
-lib_ignore = Unity
+build_src_flags = -Wall -Wextra -Werror
-; Flags
-build_flags =
- ; All warning as errors
- -Wall
- -Wextra
- -Werror
-
-[env:stm32f103c8]
+; Common configuration for compiling examples.
+; It's necessary to set the EXAMPLE env variable when running. Example:
+; - Windows: $env:EXAMPLE="examples/basic"; pio run -e native-example
+; - Linux : export EXAMPLE="examples/basic"; pio run -e native-example
+[example]
extends = common
-platform = ststm32@19.6.0
-framework = arduino
-board = bluepill_f103c8
+build_src_filter = +<*> +<../${sysenv.EXAMPLE}>
+extra_scripts = pre:scripts/require-example.py
-[env:esp32-s3]
+; Common configuration for compiling tests.
+[test]
extends = common
+test_build_src = yes
+build_flags = -DUNITY_INCLUDE_DOUBLE
+
+; Common configuration for native builds.
+[native]
+platform = native
+
+; Common configuration for esp32-s3.
+[esp32-s3]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
@@ -57,23 +55,68 @@ upload_speed = 921600
monitor_echo = true
monitor_filters =
esp32_exception_decoder
- send_on_enter
log2file
-; Flags
build_flags =
- ${common.build_flags}
-
- ; Unity include path (lib_ignore = Unity prevents it from being auto-added)
- -I.pio/libdeps/esp32-s3/Unity/src
-
; Enable debug (ESP-IDF logs)
; -DUSE_ESP_IDF_LOG
; -DCORE_DEBUG_LEVEL=5
; -DCONFIG_LOG_COLORS
; Enable PSRAM
- -DBOARD_HAS_PSRAM
+ ; -DBOARD_HAS_PSRAM
; Enable USB CDC on boot
-DARDUINO_USB_CDC_ON_BOOT=1
+
+; ------------------------------------------------------------------------------------------------ ;
+; Environments ;
+; ------------------------------------------------------------------------------------------------ ;
+
+; Native example compilation.
+; Usage: export EXAMPLE=examples/; pio run -e native-example -t exec
+[env:native-example]
+extends = native, example
+
+; Native tests without sanitizers or code coverage for quick tests.
+; Usage: pio test -e native-test
+[env:native-test]
+extends = native, test
+
+; Native tests with sanitizers (ASan + UBSan).
+; Usage: pio test -e native-san-test
+[env:native-san-test]
+extends = native, test
+build_type = debug
+build_flags =
+ ${test.build_flags}
+ -fsanitize=address,undefined
+ -fno-sanitize-recover=all
+ -fno-omit-frame-pointer
+
+; Native tests with code coverage (gcov).
+; Usage: pio test -e native-cov-test
+; - coverage.py passes --coverage to the linker (SCons does not forward it) and adds a
+; custom "coverage" target that runs the tests and builds the gcovr report
+; (pio run -e native-cov-test -t coverage).
+[env:native-cov-test]
+extends = native, test
+build_type = debug
+build_flags =
+ ${test.build_flags}
+ -fno-inline
+ --coverage
+extra_scripts = pre:scripts/coverage.py
+
+; ESP32-S3 example compilation.
+; Usage: export EXAMPLE=examples/; pio run -e esp32-s3-example -t upload -t monitor
+[env:esp32-s3-example]
+extends = esp32-s3, example
+
+; ESP32-S3 tests.
+; Usage: pio test -e esp32-s3-test
+[env:esp32-s3-test]
+extends = esp32-s3, test
+build_flags =
+ ${esp32-s3.build_flags}
+ ${test.build_flags}
diff --git a/scripts/coverage.py b/scripts/coverage.py
new file mode 100644
index 0000000..a197f3a
--- /dev/null
+++ b/scripts/coverage.py
@@ -0,0 +1,38 @@
+# SPDX-FileCopyrightText: 2026 Maximiliano Ramirez
+# SPDX-License-Identifier: MIT
+
+# Coverage setup for the native-cov-test environment. Does two things:
+# 1) Pass --coverage to the linker. PlatformIO/SCons (4.8.1) forwards -fsanitize to the linker
+# automatically, but NOT --coverage (it has no special case in SCons ParseFlags, so it only reaches
+# the compiler). Without this, the gcov runtime symbols (__gcov_*) are undefined at link time.
+# 2) Add a custom "coverage" target that runs the tests and builds the report with gcovr.
+# Use: pio run -e native-cov-test -t coverage (output in coverage/ as XML and HTML).
+import platform
+
+Import("env")
+
+env.Append(LINKFLAGS=["--coverage"])
+
+env_name = env["PIOENV"]
+
+coverage_dir_cmd = ""
+platform_name = platform.system()
+if platform_name == "Windows":
+ coverage_dir_cmd = "if not exist coverage mkdir coverage"
+else:
+ coverage_dir_cmd = "mkdir -p coverage"
+
+env.AddCustomTarget(
+ name="coverage",
+ dependencies=None,
+ actions=[
+ "pio test -e " + env_name,
+ coverage_dir_cmd,
+ "gcovr --root . --filter src/ .pio/build/"
+ + env_name
+ + " --print-summary"
+ + " --xml coverage/coverage.xml --html-details coverage/index.html",
+ ],
+ title="Local coverage report",
+ description="Unit tests + gcovr report (XML/HTML) in coverage/",
+)
diff --git a/scripts/require-example.py b/scripts/require-example.py
new file mode 100644
index 0000000..0b7c109
--- /dev/null
+++ b/scripts/require-example.py
@@ -0,0 +1,36 @@
+# SPDX-FileCopyrightText: 2026 Maximiliano Ramirez
+# SPDX-License-Identifier: MIT
+
+# Check that the EXAMPLE environment variable is defined and points to an existing folder. This
+# variable is used to compile an example from the examples/ directory, and it needs to know which
+# one to compile. If the variable is not defined or points to a non-existing folder, an error
+# message is printed, and the build process is terminated.
+import os
+import sys
+
+Import("env")
+
+example = os.environ.get("EXAMPLE")
+
+if not example:
+ sys.stderr.write(
+ "\n"
+ "==================================================================================\n"
+ " ERROR: the EXAMPLE environment variable is not defined.\n"
+ " This env compiles an example from examples/ and needs to know which one.\n"
+ " Define EXAMPLE before 'pio run', for example:\n"
+ ' PowerShell: $env:EXAMPLE="examples/"\n'
+ ' bash/WSL: export EXAMPLE="examples/"\n'
+ "==================================================================================\n"
+ )
+ env.Exit(1)
+elif not os.path.isdir(os.path.join(env.subst("$PROJECT_DIR"), example)):
+ sys.stderr.write(
+ "\n"
+ "==================================================================================\n"
+ " ERROR: the example folder does not exist: EXAMPLE=" + example + "\n"
+ " Check the value (relative path to the project root, e.g., examples/)\n"
+ " and make sure the folder exists.\n"
+ "==================================================================================\n"
+ )
+ env.Exit(1)
diff --git a/test/test_bytepack.cpp b/test/test_bytepack.cpp
index 8b836b7..f25790e 100644
--- a/test/test_bytepack.cpp
+++ b/test/test_bytepack.cpp
@@ -31,9 +31,10 @@
* truncation of valid messages and single-byte corruption.
*/
-#include
+#ifdef ARDUINO
+# include
+#endif
-#define UNITY_INCLUDE_DOUBLE
#include
#include
@@ -133,6 +134,25 @@ struct MsgNested {
}
};
+// Named dispatch handler reused by every dispatch test. Each distinct lambda type is its own
+// dispatch<>/tryDispatch<> instantiation, so funneling the tests through a single handler type
+// keeps the template's branch coverage tractable (one instantiation per message, fully exercised).
+struct DispatchRecorder {
+ bool ping_handled = false;
+ bool data_handled = false;
+ uint8_t ping_counter = 0;
+ uint16_t data_value = 0;
+
+ void operator()(const MsgPing& msg) {
+ ping_handled = true;
+ ping_counter = msg.counter;
+ }
+ void operator()(const MsgData& msg) {
+ data_handled = true;
+ data_value = msg.value;
+ }
+};
+
/* ---------------------------------------------------------------------------------------------- */
/* Custom types */
/* ---------------------------------------------------------------------------------------------- */
@@ -206,6 +226,10 @@ void test_type_quant_saturation() {
q = -400.0f;
TEST_ASSERT_EQUAL_INT16(-32768, q.getRaw());
+ // Negative, non-saturating value: exercises the "round half away from zero" negative branch
+ q = -12.34f;
+ TEST_ASSERT_EQUAL_INT16(-1234, q.getRaw());
+
// NaN -> 0
q = NAN;
TEST_ASSERT_EQUAL_INT16(0, q.getRaw());
@@ -399,6 +423,10 @@ void test_writer_custom_types() {
w.reset();
TEST_ASSERT_TRUE(w.isOk());
TEST_ASSERT_EQUAL(0, w.getSize());
+
+ // set() rejects a payload larger than this capacity (Bytes)
+ uint8_t oversized[6] = {};
+ TEST_ASSERT_FALSE(msg.b.set(oversized, sizeof(oversized)));
}
void test_writer_overflow() {
@@ -427,6 +455,24 @@ void test_writer_overflow() {
TEST_ASSERT_EQUAL(0, w.getSize());
}
+void test_writer_bytes_overflow() {
+ // A Bytes payload whose length prefix fits but whose data does not must fail without writing a
+ // partial payload; and the writer must stay in error if it is already failed when it reaches the
+ // Bytes field.
+ Msg2 msg{};
+ msg.q = 1.0f;
+ uint8_t src[5] = {1, 2, 3, 4, 5};
+ TEST_ASSERT_TRUE(msg.b.set(src, sizeof(src)));
+
+ // Quant (2) + length prefix (1) fit in 4 bytes, but the 5 data bytes overflow
+ uint8_t small[4] = {};
+ TEST_ASSERT_EQUAL(0, serialize(msg, small, sizeof(small)));
+
+ // Buffer too small even for the Quant: the writer is already in error when it reaches the Bytes
+ uint8_t tiny[1] = {};
+ TEST_ASSERT_EQUAL(0, serialize(msg, tiny, sizeof(tiny)));
+}
+
/* ---------------------------------------------------------------------------------------------- */
/* Reader */
/* ---------------------------------------------------------------------------------------------- */
@@ -614,6 +660,26 @@ void test_serialize() {
TEST_ASSERT_EQUAL(0, serialize(big, small, sizeof(small)));
}
+void test_serialize_overflow() {
+ // serialize() must report 0 when the destination cannot hold the message. Exercising it for every
+ // message type also covers the failure side of serialize()'s "isOk() ? size : 0" per
+ // instantiation.
+ uint8_t buffer[1] = {};
+
+ MsgPing ping{};
+ TEST_ASSERT_EQUAL(0, serialize(ping, buffer, 0)); // needs 1 byte
+
+ Msg2 custom{};
+ custom.q = 1.0f;
+ TEST_ASSERT_EQUAL(0, serialize(custom, buffer, sizeof(buffer))); // needs >= 3 bytes
+
+ MsgArray arr{};
+ TEST_ASSERT_EQUAL(0, serialize(arr, buffer, sizeof(buffer))); // needs 6 bytes
+
+ MsgNested nested{};
+ TEST_ASSERT_EQUAL(0, serialize(nested, buffer, sizeof(buffer))); // needs 2 bytes
+}
+
void test_deserialize() {
const uint8_t buffer[1] = {77};
@@ -655,6 +721,22 @@ void test_serialize_with_header() {
TEST_ASSERT_EQUAL(expected[i], buffer[i]);
}
+ // deserializeWithHeader round-trip and every header-mismatch path (covers the MsgData header
+ // check)
+ MsgData out{};
+ TEST_ASSERT_TRUE(deserializeWithHeader(out, buffer, written));
+ TEST_ASSERT_EQUAL_UINT16(0x1234, out.value);
+
+ buffer[0] = 0x00; // wrong ID
+ TEST_ASSERT_FALSE(deserializeWithHeader(out, buffer, written));
+ buffer[0] = MsgData::ID;
+
+ buffer[1] = 0x00; // wrong VERSION
+ TEST_ASSERT_FALSE(deserializeWithHeader(out, buffer, written));
+ buffer[1] = MsgData::VERSION;
+
+ TEST_ASSERT_FALSE(deserializeWithHeader(out, buffer, 1)); // too short for the header
+
// Buffer too small for header + fields -> returns 0
TEST_ASSERT_EQUAL(0, serializeWithHeader(in, buffer, 3));
}
@@ -700,54 +782,69 @@ void test_peek_id_and_version() {
TEST_ASSERT_FALSE(peekVersion(buffer, 1, version));
}
+// Runs every dispatch path for a given handler: each candidate matching and not matching, unknown
+// ID, wrong VERSION (per message), truncated body (per message), the fold short-circuit and the
+// too-short frame. After it returns, a MsgPing and a MsgData frame have each been routed once.
+template
+void exerciseAllDispatchBranches(Handler& handler) {
+ uint8_t ping_frame[3] = {MsgPing::ID, MsgPing::VERSION, 7};
+ uint8_t data_frame[8] = {MsgData::ID, MsgData::VERSION, 0x09, 0x03, 0, 0, 0, 0}; // value = 777
+
+ // MsgData matches (first candidate fails the ID check, second matches and deserializes)
+ TEST_ASSERT_TRUE((dispatch(data_frame, sizeof(data_frame), handler)));
+
+ // MsgPing matches (first candidate matches, short-circuiting the fold before MsgData is tried)
+ TEST_ASSERT_TRUE((dispatch(ping_frame, sizeof(ping_frame), handler)));
+
+ // Unknown ID -> no candidate matches
+ data_frame[0] = 0x00;
+ TEST_ASSERT_FALSE((dispatch(data_frame, sizeof(data_frame), handler)));
+ data_frame[0] = MsgData::ID;
+
+ // Right ID, wrong VERSION (MsgData) -> rejected
+ data_frame[1] = 0xFF;
+ TEST_ASSERT_FALSE((dispatch(data_frame, sizeof(data_frame), handler)));
+ data_frame[1] = MsgData::VERSION;
+
+ // Matching header but truncated body (MsgData needs 6 body bytes) -> rejected
+ TEST_ASSERT_FALSE((dispatch(data_frame, 3, handler)));
+
+ // Right ID, wrong VERSION (MsgPing) -> rejected
+ ping_frame[1] = MsgPing::VERSION + 1;
+ TEST_ASSERT_FALSE((dispatch(ping_frame, sizeof(ping_frame), handler)));
+ ping_frame[1] = MsgPing::VERSION;
+
+ // Matching header but truncated body (MsgPing needs 1 body byte) -> rejected
+ TEST_ASSERT_FALSE((dispatch(ping_frame, 2, handler)));
+
+ // Too short for the header -> rejected
+ TEST_ASSERT_FALSE((dispatch(data_frame, 1, handler)));
+}
+
void test_dispatch() {
- FloatInt f_union;
- f_union.f = 0.5f;
+ DispatchRecorder rec;
+ exerciseAllDispatchBranches(rec);
- // [ID][VERSION][value LE][ratio LE]
- uint8_t buffer[8] = {
- MsgData::ID,
- MsgData::VERSION,
- 0x09, // value = 777 (LSB)
- 0x03, // value (MSB)
- uint8_t(f_union.u & 0xFF),
- uint8_t((f_union.u >> 8) & 0xFF),
- uint8_t((f_union.u >> 16) & 0xFF),
- uint8_t((f_union.u >> 24) & 0xFF),
- };
+ TEST_ASSERT_TRUE(rec.ping_handled);
+ TEST_ASSERT_TRUE(rec.data_handled);
+ TEST_ASSERT_EQUAL_UINT8(7, rec.ping_counter);
+ TEST_ASSERT_EQUAL_UINT16(777, rec.data_value);
+}
+void test_dispatch_overloaded() {
+ // Overloaded is the ergonomic public handler; exercise every dispatch branch through it so both
+ // the API and its template instantiation are fully covered.
bool ping_handled = false;
bool data_handled = false;
-
- auto handler = Overloaded{
+ auto handler = Overloaded{
[&](const MsgPing&) { ping_handled = true; },
- [&](const MsgData& m) { data_handled = (m.value == 777); },
+ [&](const MsgData&) { data_handled = true; },
};
- // Matching ID and VERSION -> the right handler runs
- TEST_ASSERT_TRUE((dispatch(buffer, sizeof(buffer), handler)));
- TEST_ASSERT_FALSE(ping_handled);
- TEST_ASSERT_TRUE(data_handled);
+ exerciseAllDispatchBranches(handler);
- // Unknown ID -> no handler runs
- data_handled = false;
- buffer[0] = 0xFF;
- TEST_ASSERT_FALSE((dispatch(buffer, sizeof(buffer), handler)));
- TEST_ASSERT_FALSE(ping_handled);
- TEST_ASSERT_FALSE(data_handled);
- buffer[0] = MsgData::ID;
-
- // Wrong VERSION -> no handler runs
- buffer[1] = MsgData::VERSION + 1;
- TEST_ASSERT_FALSE((dispatch(buffer, sizeof(buffer), handler)));
- buffer[1] = MsgData::VERSION;
-
- // Known header but truncated body -> rejected
- TEST_ASSERT_FALSE((dispatch(buffer, sizeof(buffer) - 1, handler)));
-
- // Empty / header-only input -> rejected
- TEST_ASSERT_FALSE((dispatch(buffer, 0, handler)));
- TEST_ASSERT_FALSE((dispatch(buffer, 2, handler)));
+ TEST_ASSERT_TRUE(ping_handled);
+ TEST_ASSERT_TRUE(data_handled);
}
/* ---------------------------------------------------------------------------------------------- */
@@ -856,15 +953,11 @@ void test_round_trip_with_header_and_dispatch() {
TEST_ASSERT_EQUAL_UINT16(in.value, out.value);
TEST_ASSERT_EQUAL_FLOAT(in.ratio, out.ratio);
- // Dispatch to the right handler
- bool handled = false;
- TEST_ASSERT_TRUE((dispatch(buffer,
- written,
- Overloaded{
- [&](const MsgPing&) {},
- [&](const MsgData& m) { handled = (m.value == 777); },
- })));
- TEST_ASSERT_TRUE(handled);
+ // Dispatch to the right handler (shared recorder type, see test_dispatch)
+ DispatchRecorder rec;
+ TEST_ASSERT_TRUE((dispatch(buffer, written, rec)));
+ TEST_ASSERT_TRUE(rec.data_handled);
+ TEST_ASSERT_EQUAL_UINT16(777, rec.data_value);
}
/* ---------------------------------------------------------------------------------------------- */
@@ -887,6 +980,7 @@ void test_stress_garbage_input() {
stress_state = 0xA5A5A5A5;
uint8_t buffer[24];
+ DispatchRecorder rec;
for (uint32_t i = 0; i < STRESS_ITERATIONS; i++) {
const size_t len = nextRandom() % (sizeof(buffer) + 1); // 0..24
@@ -900,12 +994,7 @@ void test_stress_garbage_input() {
deserialize(msg, buffer, len);
TEST_ASSERT_TRUE(msg.b.getLength() <= msg.b.getCapacity());
- dispatch(buffer,
- len,
- Overloaded{
- [](const MsgPing&) {},
- [](const MsgData&) {},
- });
+ dispatch(buffer, len, rec);
}
}
@@ -1014,13 +1103,18 @@ void test_stress_truncate_and_corrupt() {
}
/* ---------------------------------------------------------------------------------------------- */
-/* setup / loop */
+/* Runners */
/* ---------------------------------------------------------------------------------------------- */
-void setup() {
- Serial.begin(115200);
- delay(2000);
+void setUp(void) {
+ // set stuff up here
+}
+
+void tearDown(void) {
+ // clean stuff up here
+}
+int runUnityTests(void) {
UNITY_BEGIN();
// Custom types
@@ -1034,6 +1128,7 @@ void setup() {
RUN_TEST(test_writer_common_types);
RUN_TEST(test_writer_custom_types);
RUN_TEST(test_writer_overflow);
+ RUN_TEST(test_writer_bytes_overflow);
// Reader
RUN_TEST(test_reader_initial_state);
@@ -1046,11 +1141,13 @@ void setup() {
// Helpers / dispatch
RUN_TEST(test_serialize);
+ RUN_TEST(test_serialize_overflow);
RUN_TEST(test_deserialize);
RUN_TEST(test_serialize_with_header);
RUN_TEST(test_deserialize_with_header);
RUN_TEST(test_peek_id_and_version);
RUN_TEST(test_dispatch);
+ RUN_TEST(test_dispatch_overloaded);
// Round-trip
RUN_TEST(test_round_trip_common_types);
@@ -1063,7 +1160,17 @@ void setup() {
RUN_TEST(test_stress_round_trip);
RUN_TEST(test_stress_truncate_and_corrupt);
- UNITY_END();
+ return UNITY_END();
}
-void loop() {}
\ No newline at end of file
+// For native
+int main(void) { return runUnityTests(); }
+
+// For Arduino framework
+#ifdef ARDUINO
+void setup() {
+ delay(2000);
+ runUnityTests();
+}
+void loop() {}
+#endif
\ No newline at end of file