Create more memory pools as needed (resolves #1074)

This commit is contained in:
Benoit Blanchon
2023-07-17 14:39:57 +02:00
parent 65c67d317a
commit 42b2840009
22 changed files with 396 additions and 312 deletions

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <ArduinoJson/Memory/Allocator.hpp> #include <ArduinoJson/Memory/Allocator.hpp>
#include <ArduinoJson/Memory/VariantPool.hpp>
#include <sstream> #include <sstream>
@ -218,3 +219,12 @@ class TimebombAllocator : public ArduinoJson::Allocator {
size_t countdown_ = 0; size_t countdown_ = 0;
Allocator* upstream_; Allocator* upstream_;
}; };
inline size_t sizeofPoolList(size_t n = ARDUINOJSON_INITIAL_POOL_COUNT) {
return sizeof(ArduinoJson::detail::VariantPool) * n;
}
inline size_t sizeofPool(
ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) {
return ArduinoJson::detail::VariantPool::slotsToBytes(n);
}

View File

@ -5,6 +5,8 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray; using ArduinoJson::detail::sizeofArray;
TEST_CASE("copyArray()") { TEST_CASE("copyArray()") {
@ -109,17 +111,12 @@ TEST_CASE("copyArray()") {
} }
SECTION("int[] -> JsonArray, but not enough memory") { SECTION("int[] -> JsonArray, but not enough memory") {
const size_t SIZE = sizeofArray(2); JsonDocument doc(0, FailingAllocator::instance());
JsonDocument doc(SIZE);
JsonArray array = doc.to<JsonArray>(); JsonArray array = doc.to<JsonArray>();
char json[32];
int source[] = {1, 2, 3}; int source[] = {1, 2, 3};
bool ok = copyArray(source, array); bool ok = copyArray(source, array);
REQUIRE_FALSE(ok); REQUIRE_FALSE(ok);
serializeJson(array, json);
CHECK(std::string("[1,2]") == json);
} }
SECTION("int[][] -> JsonArray") { SECTION("int[][] -> JsonArray") {
@ -160,20 +157,12 @@ TEST_CASE("copyArray()") {
} }
SECTION("int[][] -> JsonArray, but not enough memory") { SECTION("int[][] -> JsonArray, but not enough memory") {
const size_t SIZE = sizeofArray(2) + sizeofArray(3) + sizeofArray(2); JsonDocument doc(0, FailingAllocator::instance());
JsonDocument doc(SIZE);
JsonArray array = doc.to<JsonArray>(); JsonArray array = doc.to<JsonArray>();
char json[32] = "";
int source[][3] = {{1, 2, 3}, {4, 5, 6}}; int source[][3] = {{1, 2, 3}, {4, 5, 6}};
CAPTURE(SIZE);
bool ok = copyArray(source, array); bool ok = copyArray(source, array);
CAPTURE(doc.memoryUsage()); REQUIRE(ok == false);
CHECK_FALSE(ok);
serializeJson(array, json);
CHECK(std::string("[[1,2,3],[4,5]]") == json);
} }
SECTION("JsonArray -> int[], with more space than needed") { SECTION("JsonArray -> int[], with more space than needed") {

View File

@ -5,6 +5,8 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray; using ArduinoJson::detail::sizeofArray;
using ArduinoJson::detail::sizeofObject; using ArduinoJson::detail::sizeofObject;
using ArduinoJson::detail::sizeofString; using ArduinoJson::detail::sizeofString;
@ -257,8 +259,11 @@ TEST_CASE("deserialize JSON array") {
} }
TEST_CASE("deserialize JSON array under memory constraints") { TEST_CASE("deserialize JSON array under memory constraints") {
SECTION("buffer of the right size for an empty array") { TimebombAllocator allocator(100);
JsonDocument doc(sizeofArray(0)); JsonDocument doc(0, &allocator);
SECTION("empty array requires no allocation") {
allocator.setCountdown(0);
char input[] = "[]"; char input[] = "[]";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
@ -266,73 +271,39 @@ TEST_CASE("deserialize JSON array under memory constraints") {
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::Ok);
} }
SECTION("buffer too small for an array with one element") { SECTION("allocation of pool list fails") {
JsonDocument doc(sizeofArray(0)); allocator.setCountdown(0);
char input[] = "[1]"; char input[] = "[1]";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory); REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "[]");
} }
SECTION("buffer of the right size for an array with one element") { SECTION("allocation of pool fails") {
JsonDocument doc(sizeofArray(1)); allocator.setCountdown(1);
char input[] = "[1]"; char input[] = "[1]";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "[]");
} }
SECTION("buffer too small for an array with a nested object") { SECTION("allocation of string fails in array") {
JsonDocument doc(sizeofArray(0) + sizeofObject(0)); allocator.setCountdown(2);
char input[] = "[{}]"; char input[] = "[0,\"hi!\"]";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory); REQUIRE(err == DeserializationError::NoMemory);
} REQUIRE(doc.as<std::string>() == "[0,null]");
SECTION("buffer of the right size for an array with a nested object") {
JsonDocument doc(sizeofArray(1) + sizeofObject(0));
char input[] = "[{}]";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok);
} }
SECTION("don't store space characters") { SECTION("don't store space characters") {
JsonDocument doc(100);
deserializeJson(doc, " [ \"1234567\" ] "); deserializeJson(doc, " [ \"1234567\" ] ");
REQUIRE(sizeofArray(1) + sizeofString(7) == doc.memoryUsage()); REQUIRE(sizeofArray(1) + sizeofString(7) == doc.memoryUsage());
} }
SECTION("Should clear the JsonArray") {
JsonDocument doc(sizeofArray(4));
char input[] = "[1,2,3,4]";
deserializeJson(doc, input);
deserializeJson(doc, "[]");
JsonArray arr = doc.as<JsonArray>();
REQUIRE(arr.size() == 0);
REQUIRE(doc.memoryUsage() == sizeofArray(0));
}
SECTION("buffer of the right size for an array with two element") {
JsonDocument doc(sizeofArray(2));
char input[] = "[1,2]";
DeserializationError err = deserializeJson(doc, input);
JsonArray arr = doc.as<JsonArray>();
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<JsonArray>());
REQUIRE(doc.memoryUsage() == sizeofArray(2));
REQUIRE(arr[0] == 1);
REQUIRE(arr[1] == 2);
}
} }

View File

@ -5,6 +5,8 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray; using ArduinoJson::detail::sizeofArray;
using ArduinoJson::detail::sizeofObject; using ArduinoJson::detail::sizeofObject;
using ArduinoJson::detail::sizeofString; using ArduinoJson::detail::sizeofString;
@ -320,59 +322,56 @@ TEST_CASE("deserialize JSON object") {
} }
TEST_CASE("deserialize JSON object under memory constraints") { TEST_CASE("deserialize JSON object under memory constraints") {
SECTION("buffer for the right size for an empty object") { TimebombAllocator allocator(1024);
JsonDocument doc(sizeofObject(0)); JsonDocument doc(0, &allocator);
SECTION("empty object requires no allocation") {
allocator.setCountdown(0);
char input[] = "{}"; char input[] = "{}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == "{}");
} }
SECTION("buffer too small for an empty object") { SECTION("key allocation fails") {
JsonDocument doc(sizeofObject(0)); allocator.setCountdown(0);
char input[] = "{\"a\":1}"; char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory); REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{}");
} }
SECTION("buffer of the right size for an object with one member") { SECTION("pool list allocation fails") {
JsonDocument doc(sizeofObject(1)); allocator.setCountdown(2);
char input[] = "{\"a\":1}"; char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{}");
} }
SECTION("buffer too small for an object with a nested array") { SECTION("pool allocation fails") {
JsonDocument doc(sizeofObject(0) + sizeofArray(0)); allocator.setCountdown(3);
char input[] = "{\"a\":[]}"; char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory); REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{}");
} }
SECTION("buffer of the right size for an object with a nested array") { SECTION("string allocation fails") {
JsonDocument doc(sizeofObject(1) + sizeofArray(0)); allocator.setCountdown(4);
char input[] = "{\"a\":[]}"; char input[] = "{\"a\":\"b\"}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::NoMemory);
} REQUIRE(doc.as<std::string>() == "{\"a\":null}");
SECTION("Should clear the JsonObject") {
JsonDocument doc(sizeofObject(1));
char input[] = "{\"hello\":\"world\"}";
deserializeJson(doc, input);
deserializeJson(doc, "{}");
REQUIRE(doc.as<JsonObject>().size() == 0);
REQUIRE(doc.memoryUsage() == sizeofObject(0));
} }
} }

View File

@ -99,7 +99,7 @@ TEST_CASE("Invalid JSON string") {
} }
TEST_CASE("Allocation of the key fails") { TEST_CASE("Allocation of the key fails") {
TimebombAllocator timebombAllocator(1); TimebombAllocator timebombAllocator(0);
SpyingAllocator spyingAllocator(&timebombAllocator); SpyingAllocator spyingAllocator(&timebombAllocator);
JsonDocument doc(1024, &spyingAllocator); JsonDocument doc(1024, &spyingAllocator);
@ -107,19 +107,19 @@ TEST_CASE("Allocation of the key fails") {
REQUIRE(deserializeJson(doc, "{\"example\":1}") == REQUIRE(deserializeJson(doc, "{\"example\":1}") ==
DeserializationError::NoMemory); DeserializationError::NoMemory);
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(1024) AllocatorLog() << AllocatorLog::AllocateFail(sizeofString(31)));
<< AllocatorLog::AllocateFail(sizeofString(31)));
} }
SECTION("Quoted string, second member") { SECTION("Quoted string, second member") {
timebombAllocator.setCountdown(2); timebombAllocator.setCountdown(4);
REQUIRE(deserializeJson(doc, "{\"hello\":1,\"world\"}") == REQUIRE(deserializeJson(doc, "{\"hello\":1,\"world\"}") ==
DeserializationError::NoMemory); DeserializationError::NoMemory);
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(1024) AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Reallocate(sizeofString(31), << AllocatorLog::Reallocate(sizeofString(31),
sizeofString(5)) sizeofString(5))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::AllocateFail(sizeofString(31))); << AllocatorLog::AllocateFail(sizeofString(31)));
} }
@ -127,19 +127,19 @@ TEST_CASE("Allocation of the key fails") {
REQUIRE(deserializeJson(doc, "{example:1}") == REQUIRE(deserializeJson(doc, "{example:1}") ==
DeserializationError::NoMemory); DeserializationError::NoMemory);
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(1024) AllocatorLog() << AllocatorLog::AllocateFail(sizeofString(31)));
<< AllocatorLog::AllocateFail(sizeofString(31)));
} }
SECTION("Non-Quoted string, second member") { SECTION("Non-Quoted string, second member") {
timebombAllocator.setCountdown(2); timebombAllocator.setCountdown(4);
REQUIRE(deserializeJson(doc, "{hello:1,world}") == REQUIRE(deserializeJson(doc, "{hello:1,world}") ==
DeserializationError::NoMemory); DeserializationError::NoMemory);
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(1024) AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Reallocate(sizeofString(31), << AllocatorLog::Reallocate(sizeofString(31),
sizeofString(5)) sizeofString(5))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::AllocateFail(sizeofString(31))); << AllocatorLog::AllocateFail(sizeofString(31)));
} }
} }

View File

@ -26,6 +26,8 @@ TEST_CASE("JsonDocument assignment") {
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // world << AllocatorLog::Allocate(sizeofString(5)) // world
); );
} }
@ -41,8 +43,8 @@ TEST_CASE("JsonDocument assignment") {
REQUIRE(doc2.as<std::string>() == "[{\"hello\":\"world\"}]"); REQUIRE(doc2.as<std::string>() == "[{\"hello\":\"world\"}]");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Deallocate(sizeofArray(1)) AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(capacity) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // hello << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofString(5)) // world << AllocatorLog::Allocate(sizeofString(5)) // world
); );
@ -59,9 +61,9 @@ TEST_CASE("JsonDocument assignment") {
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}"); REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Deallocate(4096) AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(capacity1) << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofString(5)) // hello << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // world << AllocatorLog::Allocate(sizeofString(5)) // world
); );
} }
@ -69,21 +71,24 @@ TEST_CASE("JsonDocument assignment") {
SECTION("Move assign") { SECTION("Move assign") {
{ {
JsonDocument doc1(4096, &spyingAllocator); JsonDocument doc1(4096, &spyingAllocator);
doc1.set(std::string("The size of this string is 32!!")); doc1[std::string("hello")] = std::string("world");
JsonDocument doc2(128, &spyingAllocator); JsonDocument doc2(128, &spyingAllocator);
doc2 = std::move(doc1); doc2 = std::move(doc1);
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!"); REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(doc1.as<std::string>() == "null"); REQUIRE(doc1.as<std::string>() == "null");
} }
REQUIRE(spyingAllocator.log() == REQUIRE(
AllocatorLog() << AllocatorLog::Allocate(4096) spyingAllocator.log() ==
<< AllocatorLog::Allocate(sizeofString(31)) AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(128) << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Deallocate(128) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Deallocate(sizeofString(31)) << AllocatorLog::Allocate(sizeofString(5)) // world
<< AllocatorLog::Deallocate(4096)); << AllocatorLog::Deallocate(sizeofString(5)) // hello
<< AllocatorLog::Deallocate(sizeofString(5)) // world
<< AllocatorLog::Deallocate(sizeofPool())
<< AllocatorLog::Deallocate(sizeofPoolList()));
} }
SECTION("Assign from JsonObject") { SECTION("Assign from JsonObject") {

View File

@ -16,9 +16,7 @@ TEST_CASE("JsonDocument constructor") {
SECTION("JsonDocument(size_t)") { SECTION("JsonDocument(size_t)") {
{ JsonDocument doc(4096, &spyingAllocator); } { JsonDocument doc(4096, &spyingAllocator); }
REQUIRE(spyingAllocator.log() == AllocatorLog() REQUIRE(spyingAllocator.log() == AllocatorLog());
<< AllocatorLog::Allocate(4096)
<< AllocatorLog::Deallocate(4096));
} }
SECTION("JsonDocument(const JsonDocument&)") { SECTION("JsonDocument(const JsonDocument&)") {
@ -33,14 +31,10 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!"); REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
} }
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(capacity) AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Allocate(capacity)
<< AllocatorLog::Allocate(sizeofString(31)) << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Deallocate(sizeofString(31)) << AllocatorLog::Deallocate(sizeofString(31))
<< AllocatorLog::Deallocate(capacity) << AllocatorLog::Deallocate(sizeofString(31)));
<< AllocatorLog::Deallocate(sizeofString(31))
<< AllocatorLog::Deallocate(capacity));
} }
SECTION("JsonDocument(JsonDocument&&)") { SECTION("JsonDocument(JsonDocument&&)") {
@ -54,10 +48,8 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc1.as<std::string>() == "null"); REQUIRE(doc1.as<std::string>() == "null");
} }
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096) AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Allocate(sizeofString(31)) << AllocatorLog::Deallocate(sizeofString(31)));
<< AllocatorLog::Deallocate(sizeofString(31))
<< AllocatorLog::Deallocate(4096));
} }
SECTION("JsonDocument(JsonObject)") { SECTION("JsonDocument(JsonObject)") {
@ -69,7 +61,8 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}"); REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofObject(1))); AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()));
} }
SECTION("Construct from JsonArray") { SECTION("Construct from JsonArray") {
@ -80,8 +73,9 @@ TEST_CASE("JsonDocument constructor") {
JsonDocument doc2(arr, &spyingAllocator); JsonDocument doc2(arr, &spyingAllocator);
REQUIRE(doc2.as<std::string>() == "[\"hello\"]"); REQUIRE(doc2.as<std::string>() == "[\"hello\"]");
REQUIRE(spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate( REQUIRE(spyingAllocator.log() ==
addPadding(doc1.memoryUsage()))); AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()));
} }
SECTION("Construct from JsonVariant") { SECTION("Construct from JsonVariant") {
@ -92,8 +86,6 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "hello"); REQUIRE(doc2.as<std::string>() == "hello");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate( AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)));
sizeofString(5)) // TODO: remove
<< AllocatorLog::Allocate(sizeofString(5)));
} }
} }

View File

@ -30,10 +30,12 @@ TEST_CASE("JsonDocument::garbageCollect()") {
REQUIRE(doc.memoryUsage() == sizeofObject(1) + sizeofString(7)); REQUIRE(doc.memoryUsage() == sizeofObject(1) + sizeofString(7));
REQUIRE(doc.as<std::string>() == "{\"dancing\":2}"); REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(capacity) AllocatorLog() << AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Allocate(sizeofString(7)) << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Deallocate(sizeofString(7)) << AllocatorLog::Deallocate(sizeofString(7))
<< AllocatorLog::Deallocate(capacity)); << AllocatorLog::Deallocate(sizeofPool())
<< AllocatorLog::Deallocate(sizeofPoolList()));
} }
SECTION("when allocation fails") { SECTION("when allocation fails") {
@ -50,7 +52,6 @@ TEST_CASE("JsonDocument::garbageCollect()") {
REQUIRE(doc.as<std::string>() == "{\"dancing\":2}"); REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::AllocateFail(capacity) AllocatorLog() << AllocatorLog::AllocateFail(sizeofString(7)));
<< AllocatorLog::AllocateFail(sizeofString(7)));
} }
} }

View File

@ -10,80 +10,81 @@
using ArduinoJson::detail::sizeofArray; using ArduinoJson::detail::sizeofArray;
TEST_CASE("JsonDocument::overflowed()") { TEST_CASE("JsonDocument::overflowed()") {
TimebombAllocator allocator(10);
JsonDocument doc(0, &allocator);
SECTION("returns false on a fresh object") { SECTION("returns false on a fresh object") {
JsonDocument doc(0); allocator.setCountdown(0);
CHECK(doc.overflowed() == false); CHECK(doc.overflowed() == false);
} }
SECTION("returns true after a failed insertion") { SECTION("returns true after a failed insertion") {
JsonDocument doc(0); allocator.setCountdown(0);
doc.add(0); doc.add(0);
CHECK(doc.overflowed() == true); CHECK(doc.overflowed() == true);
} }
SECTION("returns false after successful insertion") { SECTION("returns false after successful insertion") {
JsonDocument doc(sizeofArray(1)); allocator.setCountdown(2);
doc.add(0); doc.add(0);
CHECK(doc.overflowed() == false); CHECK(doc.overflowed() == false);
} }
SECTION("returns true after a failed string copy") { SECTION("returns true after a failed string copy") {
ControllableAllocator allocator; allocator.setCountdown(0);
JsonDocument doc(sizeofArray(1), &allocator);
allocator.disable();
doc.add(std::string("example")); doc.add(std::string("example"));
CHECK(doc.overflowed() == true); CHECK(doc.overflowed() == true);
} }
SECTION("returns false after a successful string copy") { SECTION("returns false after a successful string copy") {
JsonDocument doc(sizeofArray(1)); allocator.setCountdown(3);
doc.add(std::string("example")); doc.add(std::string("example"));
CHECK(doc.overflowed() == false); CHECK(doc.overflowed() == false);
} }
SECTION("returns true after a failed member add") { SECTION("returns true after a failed member add") {
JsonDocument doc(1); allocator.setCountdown(0);
doc["example"] = true; doc["example"] = true;
CHECK(doc.overflowed() == true); CHECK(doc.overflowed() == true);
} }
SECTION("returns true after a failed deserialization") { SECTION("returns true after a failed deserialization") {
JsonDocument doc(sizeofArray(1)); allocator.setCountdown(1);
deserializeJson(doc, "[1, 2]"); deserializeJson(doc, "[1, 2]");
CHECK(doc.overflowed() == true); CHECK(doc.overflowed() == true);
} }
SECTION("returns false after a successful deserialization") { SECTION("returns false after a successful deserialization") {
JsonDocument doc(sizeofArray(1)); allocator.setCountdown(4);
deserializeJson(doc, "[\"example\"]"); deserializeJson(doc, "[\"example\"]");
CHECK(doc.overflowed() == false); CHECK(doc.overflowed() == false);
} }
SECTION("returns false after clear()") { SECTION("returns false after clear()") {
JsonDocument doc(0); allocator.setCountdown(0);
doc.add(0); doc.add(0);
doc.clear(); doc.clear();
CHECK(doc.overflowed() == false); CHECK(doc.overflowed() == false);
} }
SECTION("remains false after shrinkToFit()") { SECTION("remains false after shrinkToFit()") {
JsonDocument doc(sizeofArray(1)); allocator.setCountdown(2);
doc.add(0); doc.add(0);
allocator.setCountdown(2);
doc.shrinkToFit(); doc.shrinkToFit();
CHECK(doc.overflowed() == false); CHECK(doc.overflowed() == false);
} }
SECTION("remains true after shrinkToFit()") { SECTION("remains true after shrinkToFit()") {
JsonDocument doc(sizeofArray(1)); allocator.setCountdown(0);
doc.add(0);
doc.add(0); doc.add(0);
allocator.setCountdown(2);
doc.shrinkToFit(); doc.shrinkToFit();
CHECK(doc.overflowed() == true); CHECK(doc.overflowed() == true);
} }
SECTION("return false after garbageCollect()") { SECTION("return false after garbageCollect()") {
JsonDocument doc(sizeofArray(1)); allocator.setCountdown(0);
doc.add(0);
doc.add(0); doc.add(0);
doc.garbageCollect(); doc.garbageCollect();
CHECK(doc.overflowed() == false); CHECK(doc.overflowed() == false);

View File

@ -48,9 +48,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "null"); REQUIRE(doc.as<std::string>() == "null");
REQUIRE(spyingAllocator.log() == AllocatorLog() REQUIRE(spyingAllocator.log() == AllocatorLog());
<< AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, 0));
} }
SECTION("empty object") { SECTION("empty object") {
@ -59,9 +57,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{}"); REQUIRE(doc.as<std::string>() == "{}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() == AllocatorLog());
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, sizeofObject(0)));
} }
SECTION("empty array") { SECTION("empty array") {
@ -70,9 +66,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[]"); REQUIRE(doc.as<std::string>() == "[]");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() == AllocatorLog());
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, sizeofArray(0)));
} }
SECTION("linked string") { SECTION("linked string") {
@ -81,9 +75,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "hello"); REQUIRE(doc.as<std::string>() == "hello");
REQUIRE(spyingAllocator.log() == AllocatorLog() REQUIRE(spyingAllocator.log() == AllocatorLog());
<< AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, 0));
} }
SECTION("owned string") { SECTION("owned string") {
@ -94,9 +86,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "abcdefg"); REQUIRE(doc.as<std::string>() == "abcdefg");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096) AllocatorLog() << AllocatorLog::Allocate(sizeofString(7)));
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(4096, 0));
} }
SECTION("raw string") { SECTION("raw string") {
@ -106,9 +96,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "[{},12]"); REQUIRE(doc.as<std::string>() == "[{},12]");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096) AllocatorLog() << AllocatorLog::Allocate(sizeofString(7)));
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(4096, 0));
} }
SECTION("linked key") { SECTION("linked key") {
@ -118,8 +106,12 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "{\"key\":42}"); REQUIRE(doc.as<std::string>() == "{\"key\":42}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096) AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Reallocate(4096, sizeofObject(1))); << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(),
sizeofObject(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("owned key") { SECTION("owned key") {
@ -129,9 +121,13 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "{\"abcdefg\":42}"); REQUIRE(doc.as<std::string>() == "{\"abcdefg\":42}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096) AllocatorLog() << AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Allocate(sizeofString(7)) << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Reallocate(4096, sizeofObject(1))); << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(),
sizeofObject(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("linked string in array") { SECTION("linked string in array") {
@ -140,9 +136,13 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[\"hello\"]"); REQUIRE(doc.as<std::string>() == "[\"hello\"]");
REQUIRE(spyingAllocator.log() == REQUIRE(
AllocatorLog() << AllocatorLog::Allocate(4096) spyingAllocator.log() ==
<< AllocatorLog::Reallocate(4096, sizeofArray(1))); AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(), sizeofArray(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("owned string in array") { SECTION("owned string in array") {
@ -151,10 +151,14 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[\"abcdefg\"]"); REQUIRE(doc.as<std::string>() == "[\"abcdefg\"]");
REQUIRE(spyingAllocator.log() == REQUIRE(
AllocatorLog() << AllocatorLog::Allocate(4096) spyingAllocator.log() ==
<< AllocatorLog::Allocate(sizeofString(7)) AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Reallocate(4096, sizeofArray(1))); << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(sizeofPool(), sizeofArray(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("linked string in object") { SECTION("linked string in object") {
@ -164,8 +168,12 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "{\"key\":\"hello\"}"); REQUIRE(doc.as<std::string>() == "{\"key\":\"hello\"}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096) AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Reallocate(4096, sizeofObject(1))); << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(),
sizeofObject(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("owned string in object") { SECTION("owned string in object") {
@ -174,9 +182,13 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}"); REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}");
REQUIRE(spyingAllocator.log() == REQUIRE(
AllocatorLog() << AllocatorLog::Allocate(4096) spyingAllocator.log() ==
<< AllocatorLog::Allocate(sizeofString(7)) AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Reallocate(4096, sizeofObject(1))); << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(sizeofPool(), sizeofPool(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
} }

View File

@ -5,6 +5,8 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofObject; using ArduinoJson::detail::sizeofObject;
TEST_CASE("JsonObject::set()") { TEST_CASE("JsonObject::set()") {
@ -73,12 +75,13 @@ TEST_CASE("JsonObject::set()") {
REQUIRE(obj2["hello"] == std::string("world")); REQUIRE(obj2["hello"] == std::string("world"));
} }
SECTION("destination too small to store the key") { SECTION("copy fails in the middle of an object") {
JsonDocument doc3(sizeofObject(1)); TimebombAllocator allocator(3);
JsonDocument doc3(0, &allocator);
JsonObject obj3 = doc3.to<JsonObject>(); JsonObject obj3 = doc3.to<JsonObject>();
obj1["a"] = 1; obj1[std::string("a")] = 1;
obj1["b"] = 2; obj1[std::string("b")] = 2;
bool success = obj3.set(obj1); bool success = obj3.set(obj1);
@ -86,16 +89,17 @@ TEST_CASE("JsonObject::set()") {
REQUIRE(doc3.as<std::string>() == "{\"a\":1}"); REQUIRE(doc3.as<std::string>() == "{\"a\":1}");
} }
SECTION("destination too small to store the value") { SECTION("copy fails in the middle of an array") {
JsonDocument doc3(sizeofObject(1)); TimebombAllocator allocator(2);
JsonDocument doc3(0, &allocator);
JsonObject obj3 = doc3.to<JsonObject>(); JsonObject obj3 = doc3.to<JsonObject>();
obj1["hello"][1] = "world"; obj1["hello"][0] = std::string("world");
bool success = obj3.set(obj1); bool success = obj3.set(obj1);
REQUIRE(success == false); REQUIRE(success == false);
REQUIRE(doc3.as<std::string>() == "{\"hello\":[]}"); REQUIRE(doc3.as<std::string>() == "{\"hello\":[null]}");
} }
SECTION("destination is null") { SECTION("destination is null") {

View File

@ -43,14 +43,6 @@ TEST_CASE("serializeJson(JsonArray)") {
check(array, "[\"hello\",\"world\"]"); check(array, "[\"hello\",\"world\"]");
} }
SECTION("OneStringOverCapacity") {
array.add("hello");
array.add("world");
array.add("lost");
check(array, "[\"hello\",\"world\"]");
}
SECTION("One double") { SECTION("One double") {
array.add(3.1415927); array.add(3.1415927);
check(array, "[3.1415927]"); check(array, "[3.1415927]");
@ -82,14 +74,6 @@ TEST_CASE("serializeJson(JsonArray)") {
check(array, "[{\"key\":\"value\"}]"); check(array, "[{\"key\":\"value\"}]");
} }
SECTION("OneIntegerOverCapacity") {
array.add(1);
array.add(2);
array.add(3);
check(array, "[1,2]");
}
SECTION("OneTrue") { SECTION("OneTrue") {
array.add(true); array.add(true);
@ -109,14 +93,6 @@ TEST_CASE("serializeJson(JsonArray)") {
check(array, "[false,true]"); check(array, "[false,true]");
} }
SECTION("OneBooleanOverCapacity") {
array.add(false);
array.add(true);
array.add(false);
check(array, "[false,true]");
}
SECTION("OneEmptyNestedArray") { SECTION("OneEmptyNestedArray") {
array.createNestedArray(); array.createNestedArray();

View File

@ -178,33 +178,33 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
} }
SECTION("fixarray") { SECTION("fixarray") {
checkError(sizeofArray(0), 1, "\x90", DeserializationError::Ok); // [] checkError(sizeofArray(0), 0, "\x90", DeserializationError::Ok); // []
checkError(sizeofArray(0), 0, "\x91\x01",
DeserializationError::NoMemory); // [1]
checkError(sizeofArray(0), 1, "\x91\x01", checkError(sizeofArray(0), 1, "\x91\x01",
DeserializationError::NoMemory); // [1] DeserializationError::NoMemory); // [1]
checkError(sizeofArray(1), 1, "\x91\x01", checkError(sizeofArray(1), 2, "\x91\x01",
DeserializationError::Ok); // [1] DeserializationError::Ok); // [1]
checkError(sizeofArray(1), 1, "\x92\x01\x02",
DeserializationError::NoMemory); // [1,2]
} }
SECTION("array 16") { SECTION("array 16") {
checkError(sizeofArray(0), 1, "\xDC\x00\x00", DeserializationError::Ok); checkError(sizeofArray(0), 0, "\xDC\x00\x00", DeserializationError::Ok);
checkError(sizeofArray(0), 0, "\xDC\x00\x01\x01",
DeserializationError::NoMemory);
checkError(sizeofArray(0), 1, "\xDC\x00\x01\x01", checkError(sizeofArray(0), 1, "\xDC\x00\x01\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofArray(1), 1, "\xDC\x00\x01\x01", DeserializationError::Ok); checkError(sizeofArray(1), 2, "\xDC\x00\x01\x01", DeserializationError::Ok);
checkError(sizeofArray(1), 1, "\xDC\x00\x02\x01\x02",
DeserializationError::NoMemory);
} }
SECTION("array 32") { SECTION("array 32") {
checkError(sizeofArray(0), 1, "\xDD\x00\x00\x00\x00", checkError(sizeofArray(0), 0, "\xDD\x00\x00\x00\x00",
DeserializationError::Ok); DeserializationError::Ok);
checkError(sizeofArray(0), 1, "\xDD\x00\x00\x00\x01\x01", checkError(sizeofArray(0), 0, "\xDD\x00\x00\x00\x01\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofArray(1), 1, "\xDD\x00\x00\x00\x01\x01", checkError(sizeofArray(1), 1, "\xDD\x00\x00\x00\x01\x01",
DeserializationError::Ok);
checkError(sizeofArray(1), 1, "\xDD\x00\x00\x00\x02\x01\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofArray(1), 2, "\xDD\x00\x00\x00\x01\x01",
DeserializationError::Ok);
} }
SECTION("fixmap") { SECTION("fixmap") {
@ -214,13 +214,13 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
SECTION("{H:1}") { SECTION("{H:1}") {
checkError(sizeofObject(0), 0, "\x81\xA1H\x01", checkError(sizeofObject(0), 0, "\x81\xA1H\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofObject(1) + sizeofString(2), 3, "\x81\xA1H\x01", checkError(sizeofObject(1) + sizeofString(2), 4, "\x81\xA1H\x01",
DeserializationError::Ok); DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
checkError(sizeofObject(1) + sizeofString(2), 3, "\x82\xA1H\x01\xA1W\x02", checkError(sizeofObject(1) + sizeofString(2), 4, "\x82\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofObject(2) + 2 * sizeofString(2), 5, checkError(sizeofObject(2) + 2 * sizeofString(2), 6,
"\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok); "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok);
} }
} }
@ -230,16 +230,16 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
checkError(sizeofObject(0), 0, "\xDE\x00\x00", DeserializationError::Ok); checkError(sizeofObject(0), 0, "\xDE\x00\x00", DeserializationError::Ok);
} }
SECTION("{H:1}") { SECTION("{H:1}") {
checkError(sizeofObject(1) + sizeofString(2), 1, "\xDE\x00\x01\xA1H\x01", checkError(sizeofObject(1) + sizeofString(2), 2, "\xDE\x00\x01\xA1H\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofObject(1) + sizeofString(2), 3, "\xDE\x00\x01\xA1H\x01", checkError(sizeofObject(1) + sizeofString(2), 4, "\xDE\x00\x01\xA1H\x01",
DeserializationError::Ok); DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
checkError(sizeofObject(1) + sizeofString(2), 3, checkError(sizeofObject(1) + sizeofString(2), 4,
"\xDE\x00\x02\xA1H\x01\xA1W\x02", "\xDE\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofObject(2) + 2 * sizeofObject(1), 5, checkError(sizeofObject(2) + 2 * sizeofObject(1), 6,
"\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
} }
} }
@ -250,17 +250,17 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
DeserializationError::Ok); DeserializationError::Ok);
} }
SECTION("{H:1}") { SECTION("{H:1}") {
checkError(sizeofObject(1) + sizeofString(2), 1, checkError(sizeofObject(1) + sizeofString(2), 2,
"\xDF\x00\x00\x00\x01\xA1H\x01", "\xDF\x00\x00\x00\x01\xA1H\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofObject(1) + sizeofString(2), 3, checkError(sizeofObject(1) + sizeofString(2), 4,
"\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok); "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
checkError(sizeofObject(1) + 2 * sizeofString(2), 3, checkError(sizeofObject(1) + 2 * sizeofString(2), 4,
"\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(sizeofObject(2) + 2 * sizeofObject(1), 5, checkError(sizeofObject(2) + 2 * sizeofObject(1), 6,
"\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::Ok); DeserializationError::Ok);
} }

View File

@ -30,15 +30,7 @@ TEST_CASE("ResourceManager::allocSlot()") {
REQUIRE(isAligned(resources.allocSlot().operator VariantSlot*())); REQUIRE(isAligned(resources.allocSlot().operator VariantSlot*()));
} }
SECTION("Returns zero if capacity is 0") { SECTION("Returns null if pool list allocation fails") {
ResourceManager resources(0);
auto variant = resources.allocSlot();
REQUIRE(variant.id() == NULL_SLOT);
REQUIRE(static_cast<VariantSlot*>(variant) == nullptr);
}
SECTION("Returns zero if buffer is null") {
ResourceManager resources(4096, FailingAllocator::instance()); ResourceManager resources(4096, FailingAllocator::instance());
auto variant = resources.allocSlot(); auto variant = resources.allocSlot();
@ -46,8 +38,9 @@ TEST_CASE("ResourceManager::allocSlot()") {
REQUIRE(static_cast<VariantSlot*>(variant) == nullptr); REQUIRE(static_cast<VariantSlot*>(variant) == nullptr);
} }
SECTION("Returns zero if capacity is insufficient") { SECTION("Returns null if pool allocation fails") {
ResourceManager resources(sizeof(VariantSlot)); TimebombAllocator allocator(1);
ResourceManager resources(4096, &allocator);
resources.allocSlot(); resources.allocSlot();

View File

@ -6,25 +6,21 @@
#include <ArduinoJson/Memory/VariantPoolImpl.hpp> #include <ArduinoJson/Memory/VariantPoolImpl.hpp>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail; using namespace ArduinoJson::detail;
TEST_CASE("ResourceManager::capacity()") {
const size_t capacity = 4 * sizeof(VariantSlot);
ResourceManager resources(capacity);
REQUIRE(capacity == resources.capacity());
}
TEST_CASE("ResourceManager::size()") { TEST_CASE("ResourceManager::size()") {
ResourceManager resources(4096); TimebombAllocator allocator(0);
ResourceManager resources(4096, &allocator);
SECTION("Initial size is 0") { SECTION("Initial size is 0") {
REQUIRE(0 == resources.size()); REQUIRE(0 == resources.size());
} }
SECTION("Doesn't grow when memory pool is full") { SECTION("Doesn't grow when allocation of second pool fails") {
const size_t variantCount = resources.capacity() / sizeof(VariantSlot); allocator.setCountdown(2);
for (size_t i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
for (size_t i = 0; i < variantCount; i++)
resources.allocSlot(); resources.allocSlot();
size_t size = resources.size(); size_t size = resources.size();

View File

@ -90,6 +90,26 @@
# endif # endif
#endif #endif
// Capacity of each variant pool (in slots)
#ifndef ARDUINOJSON_POOL_CAPACITY
# if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ <= 2
// Address space == 16-bit
# define ARDUINOJSON_POOL_CAPACITY 16 // 128 bytes
# elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ >= 8 || \
defined(_WIN64) && _WIN64
// Address space == 64-bit
# define ARDUINOJSON_POOL_CAPACITY 128 // 3072 bytes
# else
// Address space == 32-bit
# define ARDUINOJSON_POOL_CAPACITY 64 // 1024 bytes
# endif
#endif
// Capacity of each variant pool (in slots)
#ifndef ARDUINOJSON_INITIAL_POOL_COUNT
# define ARDUINOJSON_INITIAL_POOL_COUNT 4
#endif
#ifdef ARDUINO #ifdef ARDUINO
// Enable support for Arduino's String class // Enable support for Arduino's String class

View File

@ -94,7 +94,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
bool garbageCollect() { bool garbageCollect() {
// make a temporary clone and move assign // make a temporary clone and move assign
JsonDocument tmp(*this); JsonDocument tmp(*this);
if (!tmp.resources_.capacity()) if (tmp.overflowed())
return false; return false;
moveAssignFrom(tmp); moveAssignFrom(tmp);
return true; return true;

View File

@ -6,7 +6,7 @@
#include <ArduinoJson/Memory/Allocator.hpp> #include <ArduinoJson/Memory/Allocator.hpp>
#include <ArduinoJson/Memory/StringPool.hpp> #include <ArduinoJson/Memory/StringPool.hpp>
#include <ArduinoJson/Memory/VariantPool.hpp> #include <ArduinoJson/Memory/VariantPoolList.hpp>
#include <ArduinoJson/Polyfills/assert.hpp> #include <ArduinoJson/Polyfills/assert.hpp>
#include <ArduinoJson/Polyfills/utility.hpp> #include <ArduinoJson/Polyfills/utility.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp> #include <ArduinoJson/Strings/StringAdapters.hpp>
@ -18,15 +18,13 @@ class VariantPool;
class ResourceManager { class ResourceManager {
public: public:
ResourceManager(size_t capa, ResourceManager(size_t /*capa*/,
Allocator* allocator = DefaultAllocator::instance()) Allocator* allocator = DefaultAllocator::instance())
: allocator_(allocator), overflowed_(false) { : allocator_(allocator), overflowed_(false) {}
variantPool_.create(capa, allocator);
}
~ResourceManager() { ~ResourceManager() {
stringPool_.clear(allocator_); stringPool_.clear(allocator_);
variantPool_.destroy(allocator_); variantPools_.clear(allocator_);
} }
ResourceManager(const ResourceManager&) = delete; ResourceManager(const ResourceManager&) = delete;
@ -34,9 +32,9 @@ class ResourceManager {
ResourceManager& operator=(ResourceManager&& src) { ResourceManager& operator=(ResourceManager&& src) {
stringPool_.clear(allocator_); stringPool_.clear(allocator_);
variantPool_.destroy(allocator_); variantPools_.clear(allocator_);
allocator_ = src.allocator_; allocator_ = src.allocator_;
variantPool_ = detail::move(src.variantPool_); variantPools_ = detail::move(src.variantPools_);
overflowed_ = src.overflowed_; overflowed_ = src.overflowed_;
stringPool_ = detail::move(src.stringPool_); stringPool_ = detail::move(src.stringPool_);
return *this; return *this;
@ -48,19 +46,19 @@ class ResourceManager {
void reallocPool(size_t requiredSize) { void reallocPool(size_t requiredSize) {
size_t capa = VariantPool::bytesToSlots(requiredSize); size_t capa = VariantPool::bytesToSlots(requiredSize);
if (capa == variantPool_.capacity()) if (capa == variantPools_.capacity())
return; return;
variantPool_.destroy(allocator_); variantPools_.clear(allocator_);
variantPool_.create(requiredSize, allocator_);
} }
// Gets the capacity of the memoryPool in bytes // Gets the capacity of the memoryPool in bytes
size_t capacity() const { size_t capacity() const {
return VariantPool::slotsToBytes(variantPool_.capacity()); return VariantPool::slotsToBytes(variantPools_.capacity());
} }
size_t size() const { size_t size() const {
return VariantPool::slotsToBytes(variantPool_.usage()) + stringPool_.size(); return VariantPool::slotsToBytes(variantPools_.usage()) +
stringPool_.size();
} }
bool overflowed() const { bool overflowed() const {
@ -68,14 +66,14 @@ class ResourceManager {
} }
SlotWithId allocSlot() { SlotWithId allocSlot() {
auto p = variantPool_.allocSlot(); auto p = variantPools_.allocSlot(allocator_);
if (!p) if (!p)
overflowed_ = true; overflowed_ = true;
return p; return p;
} }
VariantSlot* getSlot(SlotId id) const { VariantSlot* getSlot(SlotId id) const {
return variantPool_.getSlot(id); return variantPools_.getSlot(id);
} }
template <typename TAdaptedString> template <typename TAdaptedString>
@ -122,20 +120,20 @@ class ResourceManager {
} }
void clear() { void clear() {
variantPool_.clear(); variantPools_.clear(allocator_);
overflowed_ = false; overflowed_ = false;
stringPool_.clear(allocator_); stringPool_.clear(allocator_);
} }
void shrinkToFit() { void shrinkToFit() {
variantPool_.shrinkToFit(allocator_); variantPools_.shrinkToFit(allocator_);
} }
private: private:
Allocator* allocator_; Allocator* allocator_;
bool overflowed_; bool overflowed_;
StringPool stringPool_; StringPool stringPool_;
VariantPool variantPool_; VariantPoolList variantPools_;
}; };
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -31,6 +31,7 @@ class StringBuilder {
StringNode* node = resources_->getString(adaptString(node_->data, size_)); StringNode* node = resources_->getString(adaptString(node_->data, size_));
if (!node) { if (!node) {
node = resources_->resizeString(node_, size_); node = resources_->resizeString(node_, size_);
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
resources_->saveString(node); resources_->saveString(node);
node_ = nullptr; // next time we need a new string node_ = nullptr; // next time we need a new string
} else { } else {

View File

@ -42,21 +42,7 @@ class SlotWithId {
class VariantPool { class VariantPool {
public: public:
~VariantPool() { void create(SlotCount cap, Allocator* allocator);
ARDUINOJSON_ASSERT(slots_ == nullptr);
}
VariantPool& operator=(VariantPool&& src) {
capacity_ = src.capacity_;
src.capacity_ = 0;
usage_ = src.usage_;
src.usage_ = 0;
slots_ = src.slots_;
src.slots_ = nullptr;
return *this;
}
void create(size_t cap, Allocator* allocator);
void destroy(Allocator* allocator); void destroy(Allocator* allocator);
SlotWithId allocSlot(); SlotWithId allocSlot();
@ -70,9 +56,9 @@ class VariantPool {
static size_t slotsToBytes(SlotCount); static size_t slotsToBytes(SlotCount);
private: private:
SlotCount capacity_ = 0; SlotCount capacity_;
SlotCount usage_ = 0; SlotCount usage_;
VariantSlot* slots_ = nullptr; VariantSlot* slots_;
}; };
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -9,15 +9,12 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline void VariantPool::create(size_t cap, Allocator* allocator) { inline void VariantPool::create(SlotCount cap, Allocator* allocator) {
ARDUINOJSON_ASSERT(slots_ == nullptr); ARDUINOJSON_ASSERT(cap > 0);
if (!cap) slots_ =
return; reinterpret_cast<VariantSlot*>(allocator->allocate(slotsToBytes(cap)));
slots_ = reinterpret_cast<VariantSlot*>(allocator->allocate(cap)); capacity_ = slots_ ? cap : 0;
if (slots_) { usage_ = 0;
capacity_ = bytesToSlots(cap);
usage_ = 0;
}
} }
inline void VariantPool::destroy(Allocator* allocator) { inline void VariantPool::destroy(Allocator* allocator) {

View File

@ -0,0 +1,133 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2023, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Memory/VariantPool.hpp>
#include <ArduinoJson/Polyfills/assert.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class VariantPoolList {
public:
VariantPoolList() = default;
~VariantPoolList() {
ARDUINOJSON_ASSERT(pools_ == nullptr);
}
VariantPoolList& operator=(VariantPoolList&& src) {
ARDUINOJSON_ASSERT(pools_ == nullptr);
pools_ = src.pools_;
count_ = src.count_;
capacity_ = src.capacity_;
src.pools_ = nullptr;
src.count_ = 0;
src.capacity_ = 0;
return *this;
}
SlotWithId allocSlot(Allocator* allocator) {
// try to allocate from last pool (other pools are full)
if (pools_) {
auto slot = allocFromLastPool();
if (slot)
return slot;
}
// create a new pool and try again
auto pool = addPool(allocator);
if (!pool)
return {};
return allocFromLastPool();
}
VariantSlot* getSlot(SlotId id) const {
auto poolIndex = SlotId(id / ARDUINOJSON_POOL_CAPACITY);
auto indexInPool = SlotId(id % ARDUINOJSON_POOL_CAPACITY);
if (poolIndex >= count_)
return nullptr;
return pools_[poolIndex].getSlot(indexInPool);
}
void clear(Allocator* allocator) {
if (!pools_)
return;
for (size_t i = 0; i < count_; i++)
pools_[i].destroy(allocator);
allocator->deallocate(pools_);
pools_ = nullptr;
count_ = 0;
capacity_ = 0;
}
SlotCount capacity() const {
SlotCount total = 0;
for (size_t i = 0; i < count_; i++)
total = SlotCount(total + pools_[i].capacity());
return total;
}
SlotCount usage() const {
SlotCount total = 0;
for (size_t i = 0; i < count_; i++)
total = SlotCount(total + pools_[i].usage());
return total;
}
void shrinkToFit(Allocator* allocator) {
if (pools_)
pools_[count_ - 1].shrinkToFit(allocator);
if (count_ != capacity_) {
pools_ = static_cast<VariantPool*>(
allocator->reallocate(pools_, count_ * sizeof(VariantPool)));
ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail
capacity_ = count_;
}
}
private:
SlotWithId allocFromLastPool() {
ARDUINOJSON_ASSERT(pools_ != nullptr);
auto poolIndex = SlotId(count_ - 1);
auto lastPool = count_ ? &pools_[poolIndex] : nullptr;
auto slot = lastPool->allocSlot();
if (!slot)
return {};
return {slot, SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())};
}
VariantPool* addPool(Allocator* allocator) {
if (count_ == capacity_ && !increaseCapacity(allocator))
return nullptr;
auto pool = &pools_[count_++];
pool->create(ARDUINOJSON_POOL_CAPACITY, allocator);
return pool;
}
bool increaseCapacity(Allocator* allocator) {
void* newPools;
size_t newCapacity;
if (pools_) {
newCapacity = capacity_ * 2;
newPools =
allocator->reallocate(pools_, newCapacity * sizeof(VariantPool));
} else {
newCapacity = ARDUINOJSON_INITIAL_POOL_COUNT;
newPools = allocator->allocate(newCapacity * sizeof(VariantPool));
}
if (!newPools)
return false;
pools_ = static_cast<VariantPool*>(newPools);
capacity_ = newCapacity;
return true;
}
VariantPool* pools_ = nullptr;
size_t count_ = 0;
size_t capacity_ = 0;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE