// ArduinoJson - https://arduinojson.org // Copyright © 2014-2025, Benoit BLANCHON // MIT License #include #include #include "Allocators.hpp" #include "Literals.hpp" using namespace ArduinoJson::detail; enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 }; TEST_CASE("JsonVariant::set() when there is enough memory") { SpyingAllocator spy; JsonDocument doc(&spy); JsonVariant variant = doc.to(); SECTION("string literal") { bool result = variant.set("hello\0world"); REQUIRE(result == true); CHECK(variant == "hello"_s); // linked string cannot contain '\0' at the moment CHECK(spy.log() == AllocatorLog{ Allocate(sizeofStaticStringPool()), }); } SECTION("const char*") { char str[16]; strcpy(str, "hello"); bool result = variant.set(static_cast(str)); strcpy(str, "world"); REQUIRE(result == true); REQUIRE(variant == "hello"); // stores by copy REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofString("hello")), }); } SECTION("(const char*)0") { bool result = variant.set(static_cast(0)); REQUIRE(result == true); REQUIRE(variant.isNull()); REQUIRE(variant.as() == nullptr); REQUIRE(spy.log() == AllocatorLog{}); } SECTION("char*") { char str[16]; strcpy(str, "hello"); bool result = variant.set(str); strcpy(str, "world"); REQUIRE(result == true); REQUIRE(variant == "hello"); // stores by copy REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofString("hello")), }); } SECTION("char* (tiny string optimization)") { char str[16]; strcpy(str, "abc"); bool result = variant.set(str); strcpy(str, "def"); REQUIRE(result == true); REQUIRE(variant == "abc"); // stores by copy REQUIRE(spy.log() == AllocatorLog{}); } SECTION("(char*)0") { bool result = variant.set(static_cast(0)); REQUIRE(result == true); REQUIRE(variant.isNull()); REQUIRE(spy.log() == AllocatorLog{}); } SECTION("unsigned char*") { char str[16]; strcpy(str, "hello"); bool result = variant.set(reinterpret_cast(str)); strcpy(str, "world"); REQUIRE(result == true); REQUIRE(variant == "hello"); // stores by copy REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofString("hello")), }); } SECTION("signed char*") { char str[16]; strcpy(str, "hello"); bool result = variant.set(reinterpret_cast(str)); strcpy(str, "world"); REQUIRE(result == true); REQUIRE(variant == "hello"); // stores by copy REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofString("hello")), }); } #ifdef HAS_VARIABLE_LENGTH_ARRAY SECTION("VLA") { size_t n = 16; char str[n]; strcpy(str, "hello"); bool result = variant.set(str); strcpy(str, "world"); REQUIRE(result == true); REQUIRE(variant == "hello"); // stores by copy REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofString("hello")), }); } #endif SECTION("std::string") { std::string str = "hello\0world"_s; bool result = variant.set(str); str.replace(0, 5, "world"); REQUIRE(result == true); REQUIRE(variant == "hello\0world"_s); // stores by copy REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofString("hello?world")), }); } SECTION("static JsonString") { char str[16]; strcpy(str, "hello"); bool result = variant.set(JsonString(str, true)); strcpy(str, "world"); REQUIRE(result == true); REQUIRE(variant == "world"); // stores by pointer REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofStaticStringPool()), }); } SECTION("non-static JsonString") { char str[16]; strcpy(str, "hello"); bool result = variant.set(JsonString(str)); strcpy(str, "world"); REQUIRE(result == true); REQUIRE(variant == "hello"); // stores by copy REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofString("hello")), }); } SECTION("enum") { ErrorCode code = ERROR_10; bool result = variant.set(code); REQUIRE(result == true); REQUIRE(variant.is() == true); REQUIRE(variant.as() == 10); REQUIRE(spy.log() == AllocatorLog{}); } SECTION("float") { bool result = variant.set(1.2f); REQUIRE(result == true); REQUIRE(variant.is() == true); REQUIRE(variant.as() == 1.2f); REQUIRE(spy.log() == AllocatorLog{}); } SECTION("double") { bool result = variant.set(1.2); doc.shrinkToFit(); REQUIRE(result == true); REQUIRE(variant.is() == true); REQUIRE(variant.as() == 1.2); REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofPool()), Reallocate(sizeofPool(), sizeofPool(1)), }); } SECTION("int32_t") { bool result = variant.set(int32_t(42)); REQUIRE(result == true); REQUIRE(variant.is() == true); REQUIRE(variant.as() == 42); REQUIRE(spy.log() == AllocatorLog{}); } SECTION("int64_t") { bool result = variant.set(int64_t(-2147483649LL)); doc.shrinkToFit(); REQUIRE(result == true); REQUIRE(variant.is() == true); REQUIRE(variant.as() == -2147483649LL); REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofPool()), Reallocate(sizeofPool(), sizeofPool(1)), }); } SECTION("uint32_t") { bool result = variant.set(uint32_t(42)); REQUIRE(result == true); REQUIRE(variant.is() == true); REQUIRE(variant.as() == 42); REQUIRE(spy.log() == AllocatorLog{}); } SECTION("uint64_t") { bool result = variant.set(uint64_t(4294967296)); doc.shrinkToFit(); REQUIRE(result == true); REQUIRE(variant.is() == true); REQUIRE(variant.as() == 4294967296); REQUIRE(spy.log() == AllocatorLog{ Allocate(sizeofPool()), Reallocate(sizeofPool(), sizeofPool(1)), }); } SECTION("JsonDocument") { JsonDocument doc1; doc1["hello"] = "world"; // Should copy the doc variant.set(doc1); doc1.clear(); std::string json; serializeJson(doc, json); REQUIRE(json == "{\"hello\":\"world\"}"); } } TEST_CASE("JsonVariant::set() with not enough memory") { JsonDocument doc(FailingAllocator::instance()); JsonVariant v = doc.to(); SECTION("string literal") { bool result = v.set("hello world"); REQUIRE(result == false); REQUIRE(v.isNull()); } SECTION("static JsonString") { bool result = v.set(JsonString("hello world", true)); REQUIRE(result == false); REQUIRE(v.isNull()); } SECTION("std::string") { bool result = v.set("hello world!!"_s); REQUIRE(result == false); REQUIRE(v.isNull()); } SECTION("Serialized") { bool result = v.set(serialized("hello world!!"_s)); REQUIRE(result == false); REQUIRE(v.isNull()); } SECTION("char*") { char s[] = "hello world!!"; bool result = v.set(s); REQUIRE(result == false); REQUIRE(v.isNull()); } SECTION("float") { bool result = v.set(1.2f); REQUIRE(result == true); REQUIRE(v.is()); } SECTION("double") { bool result = v.set(1.2); REQUIRE(result == false); REQUIRE(v.isNull()); } SECTION("int32_t") { bool result = v.set(-42); REQUIRE(result == true); REQUIRE(v.is()); } SECTION("int64_t") { bool result = v.set(-2147483649LL); REQUIRE(result == false); REQUIRE(v.isNull()); } SECTION("uint32_t") { bool result = v.set(42); REQUIRE(result == true); REQUIRE(v.is()); } SECTION("uint64_t") { bool result = v.set(4294967296U); REQUIRE(result == false); REQUIRE(v.isNull()); } } TEST_CASE("JsonVariant::set() releases the previous value") { SpyingAllocator spy; JsonDocument doc(&spy); doc["hello"] = "world"_s; spy.clearLog(); JsonVariant v = doc["hello"]; SECTION("int") { v.set(42); REQUIRE(spy.log() == AllocatorLog{ Deallocate(sizeofString("world")), }); } SECTION("bool") { v.set(false); REQUIRE(spy.log() == AllocatorLog{ Deallocate(sizeofString("world")), }); } SECTION("const char*") { v.set("hello"); REQUIRE(spy.log() == AllocatorLog{ Deallocate(sizeofString("world")), }); } SECTION("float") { v.set(1.2f); REQUIRE(spy.log() == AllocatorLog{ Deallocate(sizeofString("world")), }); } SECTION("Serialized") { v.set(serialized("[]")); REQUIRE(spy.log() == AllocatorLog{ Deallocate(sizeofString("world")), Allocate(sizeofString("[]")), }); } } TEST_CASE("JsonVariant::set() reuses 8-bit slot") { SpyingAllocator spy; JsonDocument doc(&spy); JsonVariant variant = doc.to(); variant.set(1.2); doc.shrinkToFit(); spy.clearLog(); SECTION("double") { bool result = variant.set(3.4); REQUIRE(result == true); REQUIRE(spy.log() == AllocatorLog{}); } SECTION("int64_t") { bool result = variant.set(-2147483649LL); REQUIRE(result == true); REQUIRE(spy.log() == AllocatorLog{}); } SECTION("uint64_t") { bool result = variant.set(4294967296U); REQUIRE(result == true); REQUIRE(spy.log() == AllocatorLog{}); } }