microReticulum/test/test_bytes/test_main.cpp
2026-03-08 12:25:36 -06:00

766 lines
24 KiB
C++

#include <unity.h>
#include <Utilities/OS.h>
#include "Bytes.h"
#include "Log.h"
void testBytesDefault(const RNS::Bytes& bytes = {}) {
TEST_ASSERT_FALSE(bytes);
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
TEST_ASSERT_NULL(bytes.data());
}
RNS::Bytes ref("Test");
const RNS::Bytes& testBytesReference() {
// NOTE: Can NOT return local instance as reference!!!
//RNS::Bytes ref("Test");
TRACE("returning...");
return ref;
}
void testBytesMain() {
RNS::Bytes bytes;
TEST_ASSERT_FALSE(bytes);
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
TEST_ASSERT_TRUE(bytes.empty());
TEST_ASSERT_NULL(bytes.data());
const uint8_t prestr[] = "Hello";
const uint8_t poststr[] = " World";
const RNS::Bytes prebuf(prestr, 5);
TEST_ASSERT_TRUE(prebuf);
TEST_ASSERT_EQUAL_size_t(5, prebuf.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", prebuf.data(), prebuf.size());
TEST_ASSERT_FALSE(bytes == prebuf);
TEST_ASSERT_TRUE(bytes != prebuf);
TEST_ASSERT_TRUE(bytes < prebuf);
const RNS::Bytes postbuf(poststr, 6);
TEST_ASSERT_TRUE(postbuf);
TEST_ASSERT_EQUAL_size_t(6, postbuf.size());
TEST_ASSERT_EQUAL_MEMORY(" World", postbuf.data(), postbuf.size());
TEST_ASSERT_FALSE(postbuf == bytes);
TEST_ASSERT_TRUE(postbuf != bytes);
TEST_ASSERT_TRUE(postbuf > bytes);
TEST_ASSERT_FALSE(prebuf == postbuf);
TEST_ASSERT_TRUE(prebuf != postbuf);
if (prebuf == postbuf) {
TRACE("bytess are the same");
}
else {
TRACE("bytess are different");
}
// test string assignment
bytes = "Foo";
TEST_ASSERT_EQUAL_size_t(3, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Foo", bytes.data(), bytes.size());
// test string concat with addition
bytes += prebuf + postbuf;
TEST_ASSERT_EQUAL_size_t(14, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("FooHello World", bytes.data(), bytes.size());
TRACEF("assign bytes: %s", bytes.toString().c_str());
TRACEF("assign prebuf: %s", prebuf.toString().c_str());
TRACEF("assign postbuf: %s", postbuf.toString().c_str());
// test string assignment with addition
bytes = prebuf + postbuf;
TEST_ASSERT_EQUAL_size_t(11, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", bytes.data(), bytes.size());
// test string constructor
{
RNS::Bytes str("Foo");
TEST_ASSERT_EQUAL_size_t(3, str.size());
TEST_ASSERT_EQUAL_MEMORY("Foo", str.data(), str.size());
}
// test left in range
{
RNS::Bytes left(bytes.left(5));
TRACEF("left: %s", left.toString().c_str());
TEST_ASSERT_EQUAL_size_t(5, left.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", left.data(), left.size());
}
// test left oob
{
RNS::Bytes left(bytes.left(20));
TRACEF("oob left: %s", left.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, left.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", left.data(), left.size());
}
// test right in range
{
RNS::Bytes right(bytes.right(5));
TRACEF("right: %s", right.toString().c_str());
TEST_ASSERT_EQUAL_size_t(5, right.size());
TEST_ASSERT_EQUAL_MEMORY("World", right.data(), right.size());
}
// test right oob
{
RNS::Bytes right(bytes.right(20));
TRACEF("oob right: %s", right.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, right.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", right.data(), right.size());
}
// test mid in range
{
RNS::Bytes mid(bytes.mid(3, 5));
TRACEF("mid: %s", mid.toString().c_str());
TEST_ASSERT_EQUAL_size_t(5, mid.size());
TEST_ASSERT_EQUAL_MEMORY("lo Wo", mid.data(), mid.size());
}
// test mid oob pos
{
RNS::Bytes mid(bytes.mid(20, 5));
TRACEF("oob pos mid: %s", mid.toString().c_str());
TEST_ASSERT_FALSE(mid);
TEST_ASSERT_EQUAL_size_t(0, mid.size());
}
// test mid oob pos
{
RNS::Bytes mid(bytes.mid(3, 20));
TRACEF("oob len mid: %s", mid.toString().c_str());
TEST_ASSERT_EQUAL_size_t(8, mid.size());
TEST_ASSERT_EQUAL_MEMORY("lo World", mid.data(), mid.size());
}
// test mid to end variant
{
RNS::Bytes mid(bytes.mid(3));
TRACEF("end mid: %s", mid.toString().c_str());
TEST_ASSERT_EQUAL_size_t(8, mid.size());
TEST_ASSERT_EQUAL_MEMORY("lo World", mid.data(), mid.size());
}
// test resize
HEAD("TestBytes: resize", RNS::LOG_TRACE);
{
RNS::Bytes shrink(bytes);
shrink.resize(5);
TRACEF("shrink: %s", shrink.toString().c_str());
TEST_ASSERT_EQUAL_size_t(5, shrink.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", shrink.data(), shrink.size());
}
// test trim
HEAD("TestBytes: trim", RNS::LOG_TRACE);
{
RNS::Bytes bytes("Hello World");
TRACEF("orig: %s", bytes.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", bytes.data(), bytes.size());
bytes = bytes.left(5);
TRACEF("trim: %s", bytes.toString().c_str());
TEST_ASSERT_EQUAL_size_t(5, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", bytes.data(), bytes.size());
}
// test creating bytes from default
{
RNS::Bytes bytes;
TEST_ASSERT_FALSE(bytes);
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
TEST_ASSERT_NULL(bytes.data());
}
// test creating bytes from nullptr
{
RNS::Bytes bytes = nullptr;
TEST_ASSERT_FALSE(bytes);
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
TEST_ASSERT_NULL(bytes.data());
}
// test creating bytes from NONE
{
RNS::Bytes bytes(RNS::Bytes::NONE);
TEST_ASSERT_FALSE(bytes);
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
TEST_ASSERT_NULL(bytes.data());
}
// test creating bytes from empty
{
RNS::Bytes bytes = {};
TEST_ASSERT_FALSE(bytes);
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
TEST_ASSERT_NULL(bytes.data());
}
// function default argument
HEAD("TestBytes: function default argument", RNS::LOG_TRACE);
testBytesDefault();
// function reference return
HEAD("TestBytes: function reference return", RNS::LOG_TRACE);
{
RNS::Bytes test = testBytesReference();
TRACE("returned");
TEST_ASSERT_TRUE(test);
TEST_ASSERT_EQUAL_size_t(4, test.size());
TEST_ASSERT_EQUAL_MEMORY("Test", test.data(), test.size());
}
// TODO test comparison
}
void testCowBytes() {
RNS::Bytes bytes1("1");
TEST_ASSERT_EQUAL_size_t(1, bytes1.size());
TEST_ASSERT_EQUAL_MEMORY("1", bytes1.data(), bytes1.size());
RNS::Bytes bytes2(bytes1);
TEST_ASSERT_EQUAL_size_t(1, bytes2.size());
TEST_ASSERT_EQUAL_MEMORY("1", bytes2.data(), bytes2.size());
TEST_ASSERT_EQUAL_PTR(bytes1.data(), bytes2.data());
RNS::Bytes bytes3(bytes2);
TEST_ASSERT_EQUAL_size_t(1, bytes3.size());
TEST_ASSERT_EQUAL_MEMORY("1", bytes3.data(), bytes3.size());
TEST_ASSERT_EQUAL_PTR(bytes2.data(), bytes3.data());
TRACEF("pre bytes1 ptr: %p data: %s", (void*)bytes1.data(), bytes1.toString().c_str());
TRACEF("pre bytes2 ptr: %p data: %s", (void*)bytes2.data(), bytes2.toString().c_str());
TRACEF("pre bytes3 ptr: %p data: %s", (void*)bytes3.data(), bytes3.toString().c_str());
//bytes1.append("mississippi");
//assert(bytes1.size() == 12);
//assert(memcmp(bytes1.data(), "1mississippi", bytes1.size()) == 0);
//assert(bytes1.data() != bytes2.data());
bytes2.append("mississippi");
TEST_ASSERT_EQUAL_size_t(12, bytes2.size());
TEST_ASSERT_EQUAL_MEMORY("1mississippi", bytes2.data(), bytes2.size());
TEST_ASSERT_NOT_EQUAL(bytes1.data(), bytes2.data());
bytes3.assign("mississippi");
TEST_ASSERT_EQUAL_size_t(11, bytes3.size());
TEST_ASSERT_EQUAL_MEMORY("mississippi", bytes3.data(), bytes3.size());
TEST_ASSERT_NOT_EQUAL(bytes2.data(), bytes3.data());
TRACEF("post bytes1 ptr: %p data: %s", (void*)bytes1.data(), bytes1.toString().c_str());
TRACEF("post bytes2 ptr: %p data: %s", (void*)bytes2.data(), bytes2.toString().c_str());
TRACEF("post bytes3 ptr: %p data: %s", (void*)bytes3.data(), bytes3.toString().c_str());
}
void testBytesConversion() {
{
RNS::Bytes bytes("Hello World");
std::string hex = bytes.toHex(true);
TRACEF("text: \"%s\" upper hex: \"%s\"", bytes.toString().c_str(), hex.c_str());
TEST_ASSERT_EQUAL_size_t(22, hex.length());
TEST_ASSERT_EQUAL_STRING("48656C6C6F20576F726C64", hex.c_str());
}
{
RNS::Bytes bytes("Hello World");
std::string hex = bytes.toHex(false);
TRACEF("text: \"%s\" lower hex: \"%s\"", bytes.toString().c_str(), hex.c_str());
TEST_ASSERT_EQUAL_size_t(22, hex.length());
TEST_ASSERT_EQUAL_STRING("48656c6c6f20576f726c64", hex.c_str());
}
{
std::string hex("48656C6C6F20576F726C64");
RNS::Bytes bytes;
bytes.assignHex(hex.c_str());
std::string text = bytes.toString();
TRACEF("hex: \"%s\" text: \"%s\"", hex.c_str(), text.c_str());
TEST_ASSERT_EQUAL_size_t(11, text.length());
TEST_ASSERT_EQUAL_STRING("Hello World", text.c_str());
}
{
std::string hex("48656c6c6f20576f726c64");
RNS::Bytes bytes;
bytes.assignHex(hex.c_str());
std::string text = bytes.toString();
TRACEF("hex: \"%s\" text: \"%s\"", hex.c_str(), text.c_str());
TEST_ASSERT_EQUAL_size_t(11, text.length());
TEST_ASSERT_EQUAL_STRING("Hello World", text.c_str());
// overwrite
bytes.assignHex(hex.c_str());
text = bytes.toString();
TRACEF("hex: \"%s\" text: \"%s\"", hex.c_str(), text.c_str());
TEST_ASSERT_EQUAL_size_t(11, text.length());
TEST_ASSERT_EQUAL_STRING("Hello World", text.c_str());
// apend
bytes.appendHex(hex.c_str());
text = bytes.toString();
TRACEF("hex: \"%s\" text: \"%s\"", hex.c_str(), text.c_str());
TEST_ASSERT_EQUAL_size_t(22, text.length());
TEST_ASSERT_EQUAL_STRING("Hello WorldHello World", text.c_str());
}
}
void testBytesBase64() {
// encode "Hello World" → known base64 value
{
RNS::Bytes bytes("Hello World");
std::string b64 = bytes.toBase64();
TRACEF("text: \"%s\" base64: \"%s\"", bytes.toString().c_str(), b64.c_str());
TEST_ASSERT_EQUAL_STRING("SGVsbG8gV29ybGQ=", b64.c_str());
}
// decode known base64 string → "Hello World"
{
RNS::Bytes bytes;
bytes.assignBase64("SGVsbG8gV29ybGQ=");
std::string text = bytes.toString();
TRACEF("base64: \"SGVsbG8gV29ybGQ=\" text: \"%s\"", text.c_str());
TEST_ASSERT_EQUAL_size_t(11, text.length());
TEST_ASSERT_EQUAL_STRING("Hello World", text.c_str());
}
// round-trip stability
{
RNS::Bytes original("Hello World");
std::string b64 = original.toBase64();
RNS::Bytes restored;
restored.assignBase64(b64.c_str());
TEST_ASSERT_EQUAL_size_t(original.size(), restored.size());
TEST_ASSERT_EQUAL_MEMORY(original.data(), restored.data(), original.size());
}
// empty bytes → toBase64 returns ""
{
RNS::Bytes empty;
TEST_ASSERT_EQUAL_STRING("", empty.toBase64().c_str());
}
// assignBase64 then appendBase64 produces concatenated result
{
RNS::Bytes bytes;
bytes.assignBase64("SGVsbG8="); // "Hello"
bytes.appendBase64("IFdvcmxk"); // " World"
TEST_ASSERT_EQUAL_size_t(11, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", bytes.data(), bytes.size());
}
}
void testBytesResize() {
TRACE("Testing downsize of Bytes with initial capacity");
RNS::Bytes bytes(1024);
TEST_ASSERT_FALSE(bytes);
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
TEST_ASSERT_EQUAL_size_t(1024, bytes.capacity());
TEST_ASSERT_NOT_NULL(bytes.data());
uint8_t* buffer = bytes.writable(bytes.capacity());
TEST_ASSERT_TRUE(bytes);
TEST_ASSERT_EQUAL_size_t(1024, bytes.size());
TEST_ASSERT_EQUAL_size_t(1024, bytes.capacity());
TEST_ASSERT_NOT_NULL(buffer);
memcpy(buffer, "Hello World", 11);
bytes.resize(11);
TEST_ASSERT_EQUAL_size_t(11, bytes.size());
TEST_ASSERT_EQUAL_size_t(1024, bytes.capacity());
}
void testBytesStream() {
const uint8_t prestr[] = "Hello";
const uint8_t poststr[] = " World";
const RNS::Bytes prebuf(prestr, 5);
TEST_ASSERT_TRUE(prebuf);
TEST_ASSERT_EQUAL_size_t(5, prebuf.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", prebuf.data(), prebuf.size());
const RNS::Bytes postbuf(poststr, 6);
TEST_ASSERT_TRUE(postbuf);
TEST_ASSERT_EQUAL_size_t(6, postbuf.size());
TEST_ASSERT_EQUAL_MEMORY(" World", postbuf.data(), postbuf.size());
// stream into empty bytes
HEAD("TestBytes: stream into empty bytes", RNS::LOG_TRACE);
{
RNS::Bytes strmbuf;
strmbuf << prebuf << postbuf;
TRACE("Results:");
TRACEF("stream prebuf: %s", prebuf.toString().c_str());
TRACEF("stream postbuf: %s", postbuf.toString().c_str());
TRACEF("stream strmbuf: %s", strmbuf.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, strmbuf.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", strmbuf.data(), strmbuf.size());
TEST_ASSERT_EQUAL_size_t(5, prebuf.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", prebuf.data(), prebuf.size());
TEST_ASSERT_EQUAL_size_t(6, postbuf.size());
TEST_ASSERT_EQUAL_MEMORY(" World", postbuf.data(), postbuf.size());
}
// stream into reserved empty bytes
HEAD("TestBytes: stream into reserved empty bytes", RNS::LOG_TRACE);
{
RNS::Bytes strmbuf(256);
//strmbuf << prebuf << postbuf;
strmbuf << prebuf;
strmbuf << postbuf;
TRACE("Results:");
//TRACEF("stream prebuf: %s", prebuf.toString().c_str());
//TRACEF("stream postbuf: %s", postbuf.toString().c_str());
//TRACEF("stream strmbuf: %s", strmbuf.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, strmbuf.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", strmbuf.data(), strmbuf.size());
TEST_ASSERT_EQUAL_size_t(5, prebuf.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", prebuf.data(), prebuf.size());
TEST_ASSERT_EQUAL_size_t(6, postbuf.size());
TEST_ASSERT_EQUAL_MEMORY(" World", postbuf.data(), postbuf.size());
}
// stream into populated bytes
HEAD("TestBytes: stream into populated bytes", RNS::LOG_TRACE);
{
RNS::Bytes strmbuf("Stream ");
TEST_ASSERT_TRUE(strmbuf);
TEST_ASSERT_EQUAL_size_t(7, strmbuf.size());
TEST_ASSERT_EQUAL_MEMORY("Stream ", strmbuf.data(), strmbuf.size());
strmbuf << prebuf << postbuf;
TRACE("Results:");
TRACEF("stream prebuf: %s", prebuf.toString().c_str());
TRACEF("stream postbuf: %s", postbuf.toString().c_str());
TRACEF("stream strmbuf: %s", strmbuf.toString().c_str());
TEST_ASSERT_EQUAL_size_t(18, strmbuf.size());
TEST_ASSERT_EQUAL_MEMORY("Stream Hello World", strmbuf.data(), strmbuf.size());
TEST_ASSERT_EQUAL_size_t(5, prebuf.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", prebuf.data(), prebuf.size());
TEST_ASSERT_EQUAL_size_t(6, postbuf.size());
TEST_ASSERT_EQUAL_MEMORY(" World", postbuf.data(), postbuf.size());
}
// stream with assignment
HEAD("TestBytes: stream with assignment", RNS::LOG_TRACE);
// NOTE: This test demonstrates a side-effect of the stream insertion operator!!!
// When pre is volatile (non-const) then it gets updated in the process of inserting both pre and post.
// This is a known and correct but perhaps unexpected and non-intuitive side-effect of assignment with stream.
// To counter this side-effect, intermediate Bytes in a stream insertion chain must be const to avoid being modified.
{
// NOTE pre must be volatile in order for below stream with assignment to work (since it gets modified)
RNS::Bytes pre("Hello");
TEST_ASSERT_EQUAL_size_t(5, pre.size());
const RNS::Bytes post(" World");
TEST_ASSERT_EQUAL_size_t(6, post.size());
RNS::Bytes strmbuf = pre << post;
TRACE("Results:");
TRACEF("stream pre: %s", prebuf.toString().c_str());
TRACEF("stream post: %s", postbuf.toString().c_str());
TRACEF("stream strmbuf: %s", strmbuf.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, strmbuf.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", strmbuf.data(), strmbuf.size());
TEST_ASSERT_EQUAL_size_t(11, pre.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", pre.data(), pre.size());
TEST_ASSERT_EQUAL_size_t(6, post.size());
TEST_ASSERT_EQUAL_MEMORY(" World", post.data(), post.size());
}
}
void testBytesReserve() {
RNS::Bytes src("Hello");
uint8_t hops = 32;
{
RNS::Bytes dst = src.left(1);
dst << hops;
dst << src.mid(2);
TRACEF("non-reserve: %s", dst.toString().c_str());
TEST_ASSERT_EQUAL_size_t(5, dst.size());
TEST_ASSERT_EQUAL_MEMORY("H llo", dst.data(), dst.size());
}
{
RNS::Bytes dst(512);
dst << src.left(1);
dst << hops;
dst << src.mid(2);
TRACEF("reserve: %s", dst.toString().c_str());
TEST_ASSERT_EQUAL_size_t(5, dst.size());
TEST_ASSERT_EQUAL_MEMORY("H llo", dst.data(), dst.size());
}
}
void testFind() {
HEAD("testFind:", RNS::LOG_TRACE);
RNS::Bytes src("Hello");
TEST_ASSERT_EQUAL_INT(0, src.find("H"));
TEST_ASSERT_EQUAL_INT(-1, src.find(1, "H"));
TEST_ASSERT_EQUAL_INT(2, src.find("ll"));
TEST_ASSERT_EQUAL_INT(2, src.find(1, "ll"));
TEST_ASSERT_EQUAL_INT(2, src.find(2, "ll"));
TEST_ASSERT_EQUAL_INT(-1, src.find(3, "ll"));
TEST_ASSERT_EQUAL_INT(-1, src.find(32, "ll"));
TEST_ASSERT_EQUAL_INT(-1, src.find("foo"));
}
void testCompare() {
HEAD("testCompare:", RNS::LOG_TRACE);
RNS::Bytes bytes("Hello\x20World");
//RNS::Bytes bytes("Hello World");
TRACEF("testCompare: %s", bytes.toString().c_str());
TEST_ASSERT_TRUE(bytes == RNS::Bytes("Hello World"));
TEST_ASSERT_TRUE(bytes == RNS::Bytes("Hello\x20World"));
TEST_ASSERT_TRUE(bytes == "Hello World");
TEST_ASSERT_TRUE(bytes == "Hello\x20World");
}
void testConcat() {
HEAD("testConcat:", RNS::LOG_TRACE);
const RNS::Bytes bytes1("Hello");
const RNS::Bytes bytes2(" World");
{
TRACE("testConcat: prefix");
RNS::Bytes bytes("Prefix ");
}
// CBA NOTE: Addition-assignment operator is the only case in this test that involves
// an extra/intermediate object creation and should therfore be avoided.
{
TRACE("testConcat: prefix addition-assignment with addition");
RNS::Bytes bytes("Prefix ");
bytes += bytes1 + bytes2;
//TRACEF("testConcat: bytes: %s", bytes.toString().c_str());
TEST_ASSERT_EQUAL_size_t(18, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Prefix Hello World", bytes.data(), bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", bytes1.data(), bytes1.size());
TEST_ASSERT_EQUAL_MEMORY(" World", bytes2.data(), bytes2.size());
}
// CBA NOTE: Extreme care must be excercised when using stream insertion since intermediate
// Bytes that are volatile (non-const) will be modified in the process!!!
{
TRACE("testConcat: prefix stream");
RNS::Bytes bytes("Prefix ");
bytes << bytes1 << bytes2;
//TRACEF("testConcat: bytes: %s", bytes.toString().c_str());
TEST_ASSERT_EQUAL_size_t(18, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Prefix Hello World", bytes.data(), bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", bytes1.data(), bytes1.size());
TEST_ASSERT_EQUAL_MEMORY(" World", bytes2.data(), bytes2.size());
}
{
TRACE("testConcat: assignment with addition");
const RNS::Bytes bytes = bytes1 + bytes2;
//TRACEF("testConcat: bytes: %s", bytes.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", bytes.data(), bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", bytes1.data(), bytes1.size());
TEST_ASSERT_EQUAL_MEMORY(" World", bytes2.data(), bytes2.size());
}
// CBA NOTE: Following construct with assignment using stream insertion is NOT WORKING!!!
// Using volatile (non-const) Bytes instead does work in this fashion, but that
// has side-effects as stated above.
// CBA NOTE: Also curious why Bytes constructor with capacity is being invoked here.
/*
{
TRACE("testConcat: assignment with stream");
const RNS::Bytes bytes = bytes1 << bytes2;
//TRACEF("testConcat: bytes: %s", bytes.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", bytes.data(), bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", bytes1.data(), bytes1.size());
TEST_ASSERT_EQUAL_MEMORY(" World", bytes2.data(), bytes2.size());
}
*/
{
TRACE("testConcat: empty with separate stream");
RNS::Bytes bytes;
bytes << bytes1;
bytes << bytes2;
//TRACEF("testConcat: bytes: %s", bytes.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", bytes.data(), bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", bytes1.data(), bytes1.size());
TEST_ASSERT_EQUAL_MEMORY(" World", bytes2.data(), bytes2.size());
}
{
TRACE("testConcat: construct with addition");
const RNS::Bytes bytes(bytes1 + bytes2);
//TRACEF("testConcat: bytes: %s", bytes.toString().c_str());
TEST_ASSERT_EQUAL_size_t(11, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello World", bytes.data(), bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", bytes1.data(), bytes1.size());
TEST_ASSERT_EQUAL_MEMORY(" World", bytes2.data(), bytes2.size());
}
}
void testIndex() {
HEAD("testConcat:", RNS::LOG_TRACE);
const RNS::Bytes bytes("Hello");
TEST_ASSERT_EQUAL_UINT8('H', bytes[0]);
TEST_ASSERT_EQUAL_UINT8('e', bytes[1]);
TEST_ASSERT_EQUAL_UINT8('l', bytes[2]);
TEST_ASSERT_EQUAL_UINT8('l', bytes[3]);
TEST_ASSERT_EQUAL_UINT8('o', bytes[4]);
}
// ============================================================================
// Bytes edge case tests
// ============================================================================
void test_assignHex_even_length() {
// Normal case: even-length hex string
RNS::Bytes bytes;
bytes.assignHex("48656C6C6F"); // "Hello"
TEST_ASSERT_EQUAL_size_t(5, bytes.size());
TEST_ASSERT_EQUAL_MEMORY("Hello", bytes.data(), 5);
}
void test_assignHex_odd_length() {
// Odd-length hex should be truncated to even (drop trailing nibble)
RNS::Bytes bytes;
bytes.assignHex("ABC"); // 3 chars - only "AB" should be decoded
TEST_ASSERT_EQUAL_size_t(1, bytes.size());
TEST_ASSERT_EQUAL_UINT8(0xAB, bytes.data()[0]);
}
void test_assignHex_single_char() {
// Single char hex can't form a complete byte - should produce empty
RNS::Bytes bytes;
bytes.assignHex("A");
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
}
void test_assignHex_empty() {
RNS::Bytes bytes;
bytes.assignHex("");
TEST_ASSERT_EQUAL_size_t(0, bytes.size());
}
void test_appendHex_odd_length() {
// appendHex with odd length should also truncate to even
RNS::Bytes bytes;
bytes.assignHex("4142"); // "AB"
TEST_ASSERT_EQUAL_size_t(2, bytes.size());
bytes.appendHex("434"); // 3 chars - only "43" ('C') should be appended
TEST_ASSERT_EQUAL_size_t(3, bytes.size());
TEST_ASSERT_EQUAL_UINT8(0x43, bytes.data()[2]);
}
void test_hex_roundtrip_stability() {
// toHex always produces even length, so roundtrip should be safe
RNS::Bytes original("Hello World");
std::string hex = original.toHex();
TEST_ASSERT_TRUE(hex.length() % 2 == 0); // Must be even
RNS::Bytes restored;
restored.assignHex(hex.c_str());
TEST_ASSERT_EQUAL_size_t(original.size(), restored.size());
TEST_ASSERT_EQUAL_MEMORY(original.data(), restored.data(), original.size());
}
void test_mid_large_len() {
// mid() with len that extends past end should clamp
RNS::Bytes bytes("Hello World");
RNS::Bytes mid = bytes.mid(3, 100);
TEST_ASSERT_EQUAL_size_t(8, mid.size());
TEST_ASSERT_EQUAL_MEMORY("lo World", mid.data(), 8);
// SIZE_MAX len should also clamp, not integer-overflow and crash
RNS::Bytes mid2 = bytes.mid(3, SIZE_MAX);
TEST_ASSERT_EQUAL_size_t(8, mid2.size());
TEST_ASSERT_EQUAL_MEMORY("lo World", mid2.data(), 8);
}
void test_mid_zero_size_bytes() {
RNS::Bytes empty;
RNS::Bytes mid = empty.mid(0, 5);
TEST_ASSERT_FALSE(mid);
TEST_ASSERT_EQUAL_size_t(0, mid.size());
}
void setUp(void) {
// set stuff up here before each test
}
void tearDown(void) {
// clean stuff up here after each test
}
int runUnityTests(void) {
UNITY_BEGIN();
// Suite-level setup
RNS::Utilities::Memory::dump_heap_stats();
size_t pre_memory = RNS::Utilities::Memory::heap_available();
TRACEF("testBytes: pre-mem: %u", pre_memory);
// Run tests
RUN_TEST(testBytesMain);
RUN_TEST(testCowBytes);
RUN_TEST(testBytesConversion);
RUN_TEST(testBytesBase64);
RUN_TEST(testBytesResize);
RUN_TEST(testBytesStream);
RUN_TEST(testBytesReserve);
RUN_TEST(testFind);
RUN_TEST(testCompare);
RUN_TEST(testConcat);
RUN_TEST(testIndex);
// Bytes edge cases
RUN_TEST(test_assignHex_even_length);
RUN_TEST(test_assignHex_odd_length);
RUN_TEST(test_assignHex_single_char);
RUN_TEST(test_assignHex_empty);
RUN_TEST(test_appendHex_odd_length);
RUN_TEST(test_hex_roundtrip_stability);
RUN_TEST(test_mid_large_len);
RUN_TEST(test_mid_zero_size_bytes);
// Suite-level teardown
size_t post_memory = RNS::Utilities::Memory::heap_available();
size_t diff_memory = (int)pre_memory - (int)post_memory;
TRACEF("testBytes: post-mem: %u", post_memory);
TRACEF("testBytes: diff-mem: %u", diff_memory);
TEST_ASSERT_EQUAL_size_t(0, diff_memory);
return UNITY_END();
}
// For native dev-platform or for some embedded frameworks
int main(void) {
return runUnityTests();
}
#ifdef ARDUINO
// For Arduino framework
void setup() {
// Wait ~2 seconds before the Unity test runner
// establishes connection with a board Serial interface
delay(2000);
runUnityTests();
}
void loop() {}
#endif
// For ESP-IDF framework
void app_main() {
runUnityTests();
}