Initial commit of Arduino libraries

This commit is contained in:
Sam
2025-05-23 10:47:41 +10:00
commit 5bfce5fc3e
2476 changed files with 1108481 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
# ArduinoJson - https://arduinojson.org
# Copyright © 2014-2025, Benoit BLANCHON
# MIT License
add_executable(ResourceManagerTests
allocVariant.cpp
clear.cpp
saveString.cpp
shrinkToFit.cpp
size.cpp
StringBuffer.cpp
StringBuilder.cpp
swap.cpp
)
add_compile_definitions(ResourceManagerTests
ARDUINOJSON_SLOT_ID_SIZE=1 # require less RAM for overflow tests
ARDUINOJSON_POOL_CAPACITY=16
)
add_test(ResourceManager ResourceManagerTests)
set_tests_properties(ResourceManager
PROPERTIES
LABELS "Catch"
)

View File

@@ -0,0 +1,50 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/StringBuffer.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
#include "Literals.hpp"
using namespace ArduinoJson::detail;
TEST_CASE("StringBuffer") {
SpyingAllocator spy;
ResourceManager resources(&spy);
StringBuffer sb(&resources);
VariantData variant;
SECTION("Tiny string") {
auto ptr = sb.reserve(3);
strcpy(ptr, "hi!");
sb.save(&variant);
REQUIRE(variant.type() == VariantType::TinyString);
REQUIRE(variant.asString() == "hi!");
}
SECTION("Tiny string can't contain NUL") {
auto ptr = sb.reserve(3);
memcpy(ptr, "a\0b", 3);
sb.save(&variant);
REQUIRE(variant.type() == VariantType::OwnedString);
auto str = variant.asString();
REQUIRE(str.size() == 3);
REQUIRE(str.c_str()[0] == 'a');
REQUIRE(str.c_str()[1] == 0);
REQUIRE(str.c_str()[2] == 'b');
}
SECTION("Tiny string can't have 4 characters") {
auto ptr = sb.reserve(4);
strcpy(ptr, "alfa");
sb.save(&variant);
REQUIRE(variant.type() == VariantType::OwnedString);
REQUIRE(variant.asString() == "alfa");
}
}

View File

@@ -0,0 +1,184 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/StringBuilder.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson;
using namespace ArduinoJson::detail;
TEST_CASE("StringBuilder") {
KillswitchAllocator killswitch;
SpyingAllocator spyingAllocator(&killswitch);
ResourceManager resources(&spyingAllocator);
SECTION("Empty string") {
StringBuilder str(&resources);
VariantData data;
str.startString();
str.save(&data);
REQUIRE(resources.overflowed() == false);
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofStringBuffer()),
});
REQUIRE(data.type() == VariantType::TinyString);
}
SECTION("Tiny string") {
StringBuilder str(&resources);
str.startString();
str.append("url");
REQUIRE(str.isValid() == true);
REQUIRE(str.str() == "url");
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofStringBuffer()),
});
VariantData data;
str.save(&data);
REQUIRE(resources.overflowed() == false);
REQUIRE(data.type() == VariantType::TinyString);
REQUIRE(data.asString() == "url");
}
SECTION("Short string fits in first allocation") {
StringBuilder str(&resources);
str.startString();
str.append("hello");
REQUIRE(str.isValid() == true);
REQUIRE(str.str() == "hello");
REQUIRE(resources.overflowed() == false);
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofStringBuffer()),
});
}
SECTION("Long string needs reallocation") {
StringBuilder str(&resources);
const char* lorem =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
"eiusmod tempor incididunt ut labore et dolore magna aliqua.";
str.startString();
str.append(lorem);
REQUIRE(str.isValid() == true);
REQUIRE(str.str() == lorem);
REQUIRE(resources.overflowed() == false);
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer(1)),
Reallocate(sizeofStringBuffer(1), sizeofStringBuffer(2)),
Reallocate(sizeofStringBuffer(2), sizeofStringBuffer(3)),
});
}
SECTION("Realloc fails") {
StringBuilder str(&resources);
str.startString();
killswitch.on();
str.append(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
"eiusmod tempor incididunt ut labore et dolore magna aliqua.");
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
ReallocateFail(sizeofStringBuffer(), sizeofStringBuffer(2)),
Deallocate(sizeofStringBuffer()),
});
REQUIRE(str.isValid() == false);
REQUIRE(resources.overflowed() == true);
}
SECTION("Initial allocation fails") {
StringBuilder str(&resources);
killswitch.on();
str.startString();
REQUIRE(str.isValid() == false);
REQUIRE(resources.overflowed() == true);
REQUIRE(spyingAllocator.log() == AllocatorLog{
AllocateFail(sizeofStringBuffer()),
});
}
}
static JsonString saveString(StringBuilder& builder, const char* s) {
VariantData data;
builder.startString();
builder.append(s);
builder.save(&data);
return data.asString();
}
TEST_CASE("StringBuilder::save() deduplicates strings") {
SpyingAllocator spy;
ResourceManager resources(&spy);
StringBuilder builder(&resources);
SECTION("Basic") {
auto s1 = saveString(builder, "hello");
auto s2 = saveString(builder, "world");
auto s3 = saveString(builder, "hello");
REQUIRE(s1 == "hello");
REQUIRE(s2 == "world");
REQUIRE(+s1.c_str() == +s3.c_str()); // same address
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("world")),
Allocate(sizeofStringBuffer()),
});
}
SECTION("Requires terminator") {
auto s1 = saveString(builder, "hello world");
auto s2 = saveString(builder, "hello");
REQUIRE(s1 == "hello world");
REQUIRE(s2 == "hello");
REQUIRE(+s2.c_str() != +s1.c_str()); // different address
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
});
}
SECTION("Don't overrun") {
auto s1 = saveString(builder, "hello world");
auto s2 = saveString(builder, "worl");
REQUIRE(s1 == "hello world");
REQUIRE(s2 == "worl");
REQUIRE(s2.c_str() != s1.c_str()); // different address
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("worl")),
});
}
}

View File

@@ -0,0 +1,92 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail;
TEST_CASE("ResourceManager::allocVariant()") {
SECTION("Returns different pointer") {
ResourceManager resources;
auto s1 = resources.allocVariant();
REQUIRE(s1.ptr() != nullptr);
auto s2 = resources.allocVariant();
REQUIRE(s2.ptr() != nullptr);
REQUIRE(s1.ptr() != s2.ptr());
}
SECTION("Returns the same slot after calling freeVariant()") {
ResourceManager resources;
auto s1 = resources.allocVariant();
auto s2 = resources.allocVariant();
resources.freeVariant(s1);
resources.freeVariant(s2);
auto s3 = resources.allocVariant();
auto s4 = resources.allocVariant();
auto s5 = resources.allocVariant();
REQUIRE(s2.id() != s1.id());
REQUIRE(s3.id() == s2.id());
REQUIRE(s4.id() == s1.id());
REQUIRE(s5.id() != s1.id());
REQUIRE(s5.id() != s2.id());
}
SECTION("Returns aligned pointers") {
ResourceManager resources;
REQUIRE(isAligned(resources.allocVariant().ptr()));
REQUIRE(isAligned(resources.allocVariant().ptr()));
}
SECTION("Returns null if pool list allocation fails") {
ResourceManager resources(FailingAllocator::instance());
auto variant = resources.allocVariant();
REQUIRE(variant.id() == NULL_SLOT);
REQUIRE(variant.ptr() == nullptr);
}
SECTION("Returns null if pool allocation fails") {
ResourceManager resources(FailingAllocator::instance());
resources.allocVariant();
auto variant = resources.allocVariant();
REQUIRE(variant.id() == NULL_SLOT);
REQUIRE(variant.ptr() == nullptr);
}
SECTION("Try overflow pool counter") {
ResourceManager resources;
// this test assumes SlotId is 8-bit; otherwise it consumes a lot of memory
// tyhe GitHub Workflow gets killed
REQUIRE(NULL_SLOT == 255);
// fill all the pools
for (SlotId i = 0; i < NULL_SLOT; i++) {
auto slot = resources.allocVariant();
REQUIRE(slot.id() == i); // or != NULL_SLOT
REQUIRE(slot.ptr() != nullptr);
}
REQUIRE(resources.overflowed() == false);
// now all allocations should fail
for (int i = 0; i < 10; i++) {
auto slot = resources.allocVariant();
REQUIRE(slot.id() == NULL_SLOT);
REQUIRE(slot.ptr() == nullptr);
}
REQUIRE(resources.overflowed() == true);
}
}

View File

@@ -0,0 +1,30 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <catch.hpp>
using namespace ArduinoJson::detail;
TEST_CASE("ResourceManager::clear()") {
ResourceManager resources;
SECTION("Discards allocated variants") {
resources.allocVariant();
resources.clear();
REQUIRE(resources.size() == 0);
}
SECTION("Discards allocated strings") {
resources.saveString(adaptString("123456789"));
REQUIRE(resources.size() == sizeofString(9));
resources.clear();
REQUIRE(resources.size() == 0);
}
}

View File

@@ -0,0 +1,70 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail;
static StringNode* saveString(ResourceManager& resources, const char* s) {
return resources.saveString(adaptString(s));
}
static StringNode* saveString(ResourceManager& resources, const char* s,
size_t n) {
return resources.saveString(adaptString(s, n));
}
TEST_CASE("ResourceManager::saveString()") {
ResourceManager resources;
SECTION("Duplicates different strings") {
auto a = saveString(resources, "hello");
auto b = saveString(resources, "world");
REQUIRE(+a->data != +b->data);
REQUIRE(a->length == 5);
REQUIRE(b->length == 5);
REQUIRE(a->references == 1);
REQUIRE(b->references == 1);
REQUIRE(resources.size() == sizeofString("hello") + sizeofString("world"));
}
SECTION("Deduplicates identical strings") {
auto a = saveString(resources, "hello");
auto b = saveString(resources, "hello");
REQUIRE(a == b);
REQUIRE(a->length == 5);
REQUIRE(a->references == 2);
REQUIRE(resources.size() == sizeofString("hello"));
}
SECTION("Deduplicates identical strings that contain NUL") {
auto a = saveString(resources, "hello\0world", 11);
auto b = saveString(resources, "hello\0world", 11);
REQUIRE(a == b);
REQUIRE(a->length == 11);
REQUIRE(a->references == 2);
REQUIRE(resources.size() == sizeofString("hello world"));
}
SECTION("Don't stop on first NUL") {
auto a = saveString(resources, "hello");
auto b = saveString(resources, "hello\0world", 11);
REQUIRE(a != b);
REQUIRE(a->length == 5);
REQUIRE(b->length == 11);
REQUIRE(a->references == 1);
REQUIRE(b->references == 1);
REQUIRE(resources.size() ==
sizeofString("hello") + sizeofString("hello world"));
}
SECTION("Returns NULL when allocation fails") {
ResourceManager pool2(FailingAllocator::instance());
REQUIRE(saveString(pool2, "a") == nullptr);
}
}

View File

@@ -0,0 +1,57 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail;
TEST_CASE("ResourceManager::shrinkToFit()") {
SpyingAllocator spyingAllocator;
ResourceManager resources(&spyingAllocator);
SECTION("empty") {
resources.shrinkToFit();
REQUIRE(spyingAllocator.log() == AllocatorLog{});
}
SECTION("only one pool") {
resources.allocVariant();
resources.shrinkToFit();
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(1)),
});
}
SECTION("more pools than initial count") {
for (size_t i = 0;
i < ARDUINOJSON_POOL_CAPACITY * ARDUINOJSON_INITIAL_POOL_COUNT + 1;
i++)
resources.allocVariant();
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()) * ARDUINOJSON_INITIAL_POOL_COUNT,
Allocate(sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2)),
Allocate(sizeofPool()),
});
spyingAllocator.clearLog();
resources.shrinkToFit();
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Reallocate(sizeofPool(), sizeofPool(1)),
Reallocate(sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2),
sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT + 1)),
});
}
}

View File

@@ -0,0 +1,31 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail;
TEST_CASE("ResourceManager::size()") {
TimebombAllocator timebomb(0);
ResourceManager resources(&timebomb);
SECTION("Initial size is 0") {
REQUIRE(0 == resources.size());
}
SECTION("Doesn't grow when allocation of second pool fails") {
timebomb.setCountdown(1);
for (size_t i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
resources.allocVariant();
size_t size = resources.size();
resources.allocVariant();
REQUIRE(size == resources.size());
}
}

View File

@@ -0,0 +1,96 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/Alignment.hpp>
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail;
static void fullPreallocatedPools(ResourceManager& resources) {
for (int i = 0;
i < ARDUINOJSON_INITIAL_POOL_COUNT * ARDUINOJSON_POOL_CAPACITY; i++)
resources.allocVariant();
}
TEST_CASE("ResourceManager::swap()") {
SECTION("Both using preallocated pool list") {
SpyingAllocator spy;
ResourceManager a(&spy);
ResourceManager b(&spy);
auto a1 = a.allocVariant();
auto b1 = b.allocVariant();
swap(a, b);
REQUIRE(a1.ptr() == b.getVariant(a1.id()));
REQUIRE(b1.ptr() == a.getVariant(b1.id()));
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()) * 2,
});
}
SECTION("Only left using preallocated pool list") {
SpyingAllocator spy;
ResourceManager a(&spy);
ResourceManager b(&spy);
fullPreallocatedPools(b);
auto a1 = a.allocVariant();
auto b1 = b.allocVariant();
swap(a, b);
REQUIRE(a1.ptr() == b.getVariant(a1.id()));
REQUIRE(b1.ptr() == a.getVariant(b1.id()));
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofPool()) * (ARDUINOJSON_INITIAL_POOL_COUNT + 1),
Allocate(sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2)),
Allocate(sizeofPool()),
});
}
SECTION("Only right using preallocated pool list") {
SpyingAllocator spy;
ResourceManager a(&spy);
fullPreallocatedPools(a);
ResourceManager b(&spy);
auto a1 = a.allocVariant();
auto b1 = b.allocVariant();
swap(a, b);
REQUIRE(a1.ptr() == b.getVariant(a1.id()));
REQUIRE(b1.ptr() == a.getVariant(b1.id()));
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofPool()) * ARDUINOJSON_INITIAL_POOL_COUNT,
Allocate(sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2)),
Allocate(sizeofPool()) * 2,
});
}
SECTION("None is using preallocated pool list") {
SpyingAllocator spy;
ResourceManager a(&spy);
fullPreallocatedPools(a);
ResourceManager b(&spy);
fullPreallocatedPools(b);
auto a1 = a.allocVariant();
auto b1 = b.allocVariant();
swap(a, b);
REQUIRE(a1.ptr() == b.getVariant(a1.id()));
REQUIRE(b1.ptr() == a.getVariant(b1.id()));
}
}