// ArduinoJson - https://arduinojson.org // Copyright © 2014-2025, Benoit BLANCHON // MIT License #include #include #include "Allocators.hpp" #include "Literals.hpp" 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.size() == sizeofString("")); REQUIRE(resources.overflowed() == false); REQUIRE(spyingAllocator.log() == AllocatorLog{ Allocate(sizeofStringBuffer()), Reallocate(sizeofStringBuffer(), sizeofString("")), }); } 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 const char* saveString(StringBuilder& builder, const char* s) { VariantData data; builder.startString(); builder.append(s); builder.save(&data); return data.asString().c_str(); } 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"_s); REQUIRE(s2 == "world"_s); REQUIRE(+s1 == +s3); // 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"_s); REQUIRE(s2 == "hello"_s); REQUIRE(+s2 != +s1); // 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, "wor"); REQUIRE(s1 == "hello world"_s); REQUIRE(s2 == "wor"_s); REQUIRE(s2 != s1); REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofStringBuffer()), Reallocate(sizeofStringBuffer(), sizeofString("hello world")), Allocate(sizeofStringBuffer()), Reallocate(sizeofStringBuffer(), sizeofString("wor")), }); } }