Preallocate pool list

This commit is contained in:
Benoit Blanchon
2023-07-21 10:38:35 +02:00
parent f427706e06
commit 1a14499612
19 changed files with 183 additions and 143 deletions

View File

@ -32,38 +32,60 @@ struct FailingAllocator : ArduinoJson::Allocator {
class AllocatorLog { class AllocatorLog {
public: public:
static std::string Allocate(size_t s) { class Entry {
public:
Entry(std::string s, size_t n = 1) : str_(s), count_(n) {}
const std::string& str() const {
return str_;
}
size_t count() const {
return count_;
}
Entry operator*(size_t n) const {
return Entry(str_, n);
}
private:
std::string str_;
size_t count_;
};
static Entry Allocate(size_t s) {
char buffer[32]; char buffer[32];
sprintf(buffer, "allocate(%zu)", s); sprintf(buffer, "allocate(%zu)", s);
return buffer; return Entry(buffer);
} }
static std::string AllocateFail(size_t s) { static Entry AllocateFail(size_t s) {
char buffer[32]; char buffer[32];
sprintf(buffer, "allocate(%zu) -> nullptr", s); sprintf(buffer, "allocate(%zu) -> nullptr", s);
return buffer; return Entry(buffer);
} }
static std::string Reallocate(size_t s1, size_t s2) { static Entry Reallocate(size_t s1, size_t s2) {
char buffer[32]; char buffer[32];
sprintf(buffer, "reallocate(%zu, %zu)", s1, s2); sprintf(buffer, "reallocate(%zu, %zu)", s1, s2);
return buffer; return Entry(buffer);
}; };
static std::string ReallocateFail(size_t s1, size_t s2) { static Entry ReallocateFail(size_t s1, size_t s2) {
char buffer[32]; char buffer[32];
sprintf(buffer, "reallocate(%zu, %zu) -> nullptr", s1, s2); sprintf(buffer, "reallocate(%zu, %zu) -> nullptr", s1, s2);
return buffer; return Entry(buffer);
}; };
static std::string Deallocate(size_t s) { static Entry Deallocate(size_t s) {
char buffer[32]; char buffer[32];
sprintf(buffer, "deallocate(%zu)", s); sprintf(buffer, "deallocate(%zu)", s);
return buffer; return Entry(buffer);
}; };
AllocatorLog& operator<<(const std::string& s) { AllocatorLog& operator<<(const Entry& entry) {
log_ << s << "\n"; for (size_t i = 0; i < entry.count(); i++)
log_ << entry.str() << "\n";
return *this; return *this;
} }

View File

@ -39,10 +39,7 @@ TEST_CASE("JsonArray::clear()") {
for (int i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++) for (int i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
array.add(i); array.add(i);
REQUIRE( REQUIRE(allocator.log() == AllocatorLog()
allocator.log() == << AllocatorLog::Allocate(sizeofPool()));
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()) // only one pool
);
} }
} }

View File

@ -105,9 +105,7 @@ TEST_CASE("Removed elements are recycled") {
// add one element; it should use the free slot // add one element; it should use the free slot
array.add(42); array.add(42);
REQUIRE( REQUIRE(allocator.log() == AllocatorLog() << AllocatorLog::Allocate(
allocator.log() == sizeofPool()) // only one pool
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()) // only one pool
); );
} }

View File

@ -281,7 +281,7 @@ TEST_CASE("deserialize JSON array under memory constraints") {
} }
SECTION("allocation of pool fails") { SECTION("allocation of pool fails") {
allocator.setCountdown(1); allocator.setCountdown(0);
char input[] = "[1]"; char input[] = "[1]";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
@ -291,7 +291,7 @@ TEST_CASE("deserialize JSON array under memory constraints") {
} }
SECTION("allocation of string fails in array") { SECTION("allocation of string fails in array") {
allocator.setCountdown(2); allocator.setCountdown(1);
char input[] = "[0,\"hi!\"]"; char input[] = "[0,\"hi!\"]";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);

View File

@ -344,7 +344,7 @@ TEST_CASE("deserialize JSON object under memory constraints") {
REQUIRE(doc.as<std::string>() == "{}"); REQUIRE(doc.as<std::string>() == "{}");
} }
SECTION("pool list allocation fails") { SECTION("pool allocation fails") {
allocator.setCountdown(2); allocator.setCountdown(2);
char input[] = "{\"a\":1}"; char input[] = "{\"a\":1}";
@ -354,18 +354,8 @@ TEST_CASE("deserialize JSON object under memory constraints") {
REQUIRE(doc.as<std::string>() == "{}"); REQUIRE(doc.as<std::string>() == "{}");
} }
SECTION("pool allocation fails") {
allocator.setCountdown(3);
char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{}");
}
SECTION("string allocation fails") { SECTION("string allocation fails") {
allocator.setCountdown(4); allocator.setCountdown(3);
char input[] = "{\"a\":\"b\"}"; char input[] = "{\"a\":\"b\"}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);

View File

@ -111,14 +111,13 @@ TEST_CASE("Allocation of the key fails") {
} }
SECTION("Quoted string, second member") { SECTION("Quoted string, second member") {
timebombAllocator.setCountdown(4); timebombAllocator.setCountdown(3);
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(sizeofString(31)) AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Reallocate(sizeofString(31), << AllocatorLog::Reallocate(sizeofString(31),
sizeofString(5)) sizeofString(5))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::AllocateFail(sizeofString(31))); << AllocatorLog::AllocateFail(sizeofString(31)));
} }
@ -131,14 +130,13 @@ TEST_CASE("Allocation of the key fails") {
} }
SECTION("Non-Quoted string, second member") { SECTION("Non-Quoted string, second member") {
timebombAllocator.setCountdown(4); timebombAllocator.setCountdown(3);
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(sizeofString(31)) AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Reallocate(sizeofString(31), << AllocatorLog::Reallocate(sizeofString(31),
sizeofString(5)) sizeofString(5))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::AllocateFail(sizeofString(31))); << AllocatorLog::AllocateFail(sizeofString(31)));
} }

View File

@ -24,7 +24,6 @@ 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(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // world << AllocatorLog::Allocate(sizeofString(5)) // world
); );
@ -40,8 +39,7 @@ 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::Allocate(sizeofPoolList()) AllocatorLog() << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // hello << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofString(5)) // world << AllocatorLog::Allocate(sizeofString(5)) // world
); );
@ -58,7 +56,6 @@ 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::Allocate(sizeofString(5)) // hello AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // world << AllocatorLog::Allocate(sizeofString(5)) // world
); );
@ -78,13 +75,11 @@ TEST_CASE("JsonDocument assignment") {
REQUIRE( REQUIRE(
spyingAllocator.log() == spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // world << AllocatorLog::Allocate(sizeofString(5)) // world
<< AllocatorLog::Deallocate(sizeofString(5)) // hello << AllocatorLog::Deallocate(sizeofString(5)) // hello
<< AllocatorLog::Deallocate(sizeofString(5)) // world << AllocatorLog::Deallocate(sizeofString(5)) // world
<< AllocatorLog::Deallocate(sizeofPool()) << AllocatorLog::Deallocate(sizeofPool()));
<< AllocatorLog::Deallocate(sizeofPoolList()));
} }
SECTION("Assign from JsonObject") { SECTION("Assign from JsonObject") {

View File

@ -59,8 +59,7 @@ 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(sizeofPoolList()) AllocatorLog() << AllocatorLog::Allocate(sizeofPool()));
<< AllocatorLog::Allocate(sizeofPool()));
} }
SECTION("Construct from JsonArray") { SECTION("Construct from JsonArray") {
@ -72,8 +71,7 @@ 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(sizeofPoolList()) AllocatorLog() << AllocatorLog::Allocate(sizeofPool()));
<< AllocatorLog::Allocate(sizeofPool()));
} }
SECTION("Construct from JsonVariant") { SECTION("Construct from JsonVariant") {

View File

@ -30,11 +30,9 @@ 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::Allocate(sizeofString(7)) AllocatorLog() << AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Deallocate(sizeofString(7)) << AllocatorLog::Deallocate(sizeofString(7))
<< AllocatorLog::Deallocate(sizeofPool()) << AllocatorLog::Deallocate(sizeofPool()));
<< AllocatorLog::Deallocate(sizeofPoolList()));
} }
SECTION("when allocation fails") { SECTION("when allocation fails") {

View File

@ -47,13 +47,13 @@ TEST_CASE("JsonDocument::overflowed()") {
} }
SECTION("returns true after a failed deserialization") { SECTION("returns true after a failed deserialization") {
allocator.setCountdown(1); allocator.setCountdown(0);
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") {
allocator.setCountdown(4); allocator.setCountdown(3);
deserializeJson(doc, "[\"example\"]"); deserializeJson(doc, "[\"example\"]");
CHECK(doc.overflowed() == false); CHECK(doc.overflowed() == false);
} }

View File

@ -105,13 +105,10 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":42}"); REQUIRE(doc.as<std::string>() == "{\"key\":42}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() == AllocatorLog()
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Reallocate(
<< AllocatorLog::Reallocate(sizeofPool(), sizeofPool(), sizeofObject(1)));
sizeofObject(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("owned key") { SECTION("owned key") {
@ -122,12 +119,9 @@ 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(sizeofString(7)) AllocatorLog() << AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(), << AllocatorLog::Reallocate(sizeofPool(),
sizeofObject(1)) sizeofObject(1)));
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("linked string in array") { SECTION("linked string in array") {
@ -136,13 +130,10 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[\"hello\"]"); REQUIRE(doc.as<std::string>() == "[\"hello\"]");
REQUIRE( REQUIRE(spyingAllocator.log() == AllocatorLog()
spyingAllocator.log() == << AllocatorLog::Allocate(sizeofPool())
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Reallocate(
<< AllocatorLog::Allocate(sizeofPool()) sizeofPool(), sizeofArray(1)));
<< AllocatorLog::Reallocate(sizeofPool(), sizeofArray(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("owned string in array") { SECTION("owned string in array") {
@ -151,14 +142,11 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[\"abcdefg\"]"); REQUIRE(doc.as<std::string>() == "[\"abcdefg\"]");
REQUIRE( REQUIRE(spyingAllocator.log() ==
spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(sizeofPool())
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Reallocate(sizeofPool(),
<< AllocatorLog::Allocate(sizeofString(7)) sizeofArray(1)));
<< AllocatorLog::Reallocate(sizeofPool(), sizeofArray(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("linked string in object") { SECTION("linked string in object") {
@ -167,13 +155,10 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"hello\"}"); REQUIRE(doc.as<std::string>() == "{\"key\":\"hello\"}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() == AllocatorLog()
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Reallocate(
<< AllocatorLog::Reallocate(sizeofPool(), sizeofPool(), sizeofObject(1)));
sizeofObject(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
SECTION("owned string in object") { SECTION("owned string in object") {
@ -182,13 +167,10 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}"); REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}");
REQUIRE( REQUIRE(spyingAllocator.log() ==
spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(sizeofPool())
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Reallocate(sizeofPool(),
<< AllocatorLog::Allocate(sizeofString(7)) sizeofPool(1)));
<< AllocatorLog::Reallocate(sizeofPool(), sizeofPool(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
} }
} }

View File

@ -74,7 +74,7 @@ TEST_CASE("JsonObject::set()") {
} }
SECTION("copy fails in the middle of an object") { SECTION("copy fails in the middle of an object") {
TimebombAllocator allocator(3); TimebombAllocator allocator(2);
JsonDocument doc3(&allocator); JsonDocument doc3(&allocator);
JsonObject obj3 = doc3.to<JsonObject>(); JsonObject obj3 = doc3.to<JsonObject>();
@ -88,7 +88,7 @@ TEST_CASE("JsonObject::set()") {
} }
SECTION("copy fails in the middle of an array") { SECTION("copy fails in the middle of an array") {
TimebombAllocator allocator(2); TimebombAllocator allocator(1);
JsonDocument doc3(&allocator); JsonDocument doc3(&allocator);
JsonObject obj3 = doc3.to<JsonObject>(); JsonObject obj3 = doc3.to<JsonObject>();

View File

@ -178,23 +178,19 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
checkError(0, "\x91\x01", checkError(0, "\x91\x01",
DeserializationError::NoMemory); // [1] DeserializationError::NoMemory); // [1]
checkError(1, "\x91\x01", checkError(1, "\x91\x01",
DeserializationError::NoMemory); // [1]
checkError(2, "\x91\x01",
DeserializationError::Ok); // [1] DeserializationError::Ok); // [1]
} }
SECTION("array 16") { SECTION("array 16") {
checkError(0, "\xDC\x00\x00", DeserializationError::Ok); checkError(0, "\xDC\x00\x00", DeserializationError::Ok);
checkError(0, "\xDC\x00\x01\x01", DeserializationError::NoMemory); checkError(0, "\xDC\x00\x01\x01", DeserializationError::NoMemory);
checkError(1, "\xDC\x00\x01\x01", DeserializationError::NoMemory); checkError(1, "\xDC\x00\x01\x01", DeserializationError::Ok);
checkError(2, "\xDC\x00\x01\x01", DeserializationError::Ok);
} }
SECTION("array 32") { SECTION("array 32") {
checkError(0, "\xDD\x00\x00\x00\x00", DeserializationError::Ok); checkError(0, "\xDD\x00\x00\x00\x00", DeserializationError::Ok);
checkError(0, "\xDD\x00\x00\x00\x01\x01", DeserializationError::NoMemory); checkError(0, "\xDD\x00\x00\x00\x01\x01", DeserializationError::NoMemory);
checkError(1, "\xDD\x00\x00\x00\x01\x01", DeserializationError::NoMemory); checkError(1, "\xDD\x00\x00\x00\x01\x01", DeserializationError::Ok);
checkError(2, "\xDD\x00\x00\x00\x01\x01", DeserializationError::Ok);
} }
SECTION("fixmap") { SECTION("fixmap") {
@ -203,11 +199,11 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
} }
SECTION("{H:1}") { SECTION("{H:1}") {
checkError(0, "\x81\xA1H\x01", DeserializationError::NoMemory); checkError(0, "\x81\xA1H\x01", DeserializationError::NoMemory);
checkError(4, "\x81\xA1H\x01", DeserializationError::Ok); checkError(3, "\x81\xA1H\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
checkError(4, "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory); checkError(3, "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory);
checkError(6, "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok); checkError(5, "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok);
} }
} }
@ -217,12 +213,12 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
} }
SECTION("{H:1}") { SECTION("{H:1}") {
checkError(2, "\xDE\x00\x01\xA1H\x01", DeserializationError::NoMemory); checkError(2, "\xDE\x00\x01\xA1H\x01", DeserializationError::NoMemory);
checkError(4, "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok); checkError(3, "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
checkError(4, "\xDE\x00\x02\xA1H\x01\xA1W\x02", checkError(3, "\xDE\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(6, "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); checkError(5, "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
} }
} }
@ -233,12 +229,12 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
SECTION("{H:1}") { SECTION("{H:1}") {
checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01", checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(4, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok); checkError(3, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
checkError(4, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", checkError(3, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(6, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", checkError(5, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::Ok); DeserializationError::Ok);
} }
} }

View File

@ -6,6 +6,7 @@ add_executable(ResourceManagerTests
allocVariant.cpp allocVariant.cpp
clear.cpp clear.cpp
saveString.cpp saveString.cpp
shrinkToFit.cpp
size.cpp size.cpp
StringBuilder.cpp StringBuilder.cpp
) )

View File

@ -57,8 +57,7 @@ TEST_CASE("ResourceManager::allocSlot()") {
} }
SECTION("Returns null if pool allocation fails") { SECTION("Returns null if pool allocation fails") {
TimebombAllocator allocator(1); ResourceManager resources(FailingAllocator::instance());
ResourceManager resources(&allocator);
resources.allocSlot(); resources.allocSlot();

View File

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

View File

@ -19,7 +19,7 @@ TEST_CASE("ResourceManager::size()") {
} }
SECTION("Doesn't grow when allocation of second pool fails") { SECTION("Doesn't grow when allocation of second pool fails") {
allocator.setCountdown(2); allocator.setCountdown(1);
for (size_t i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++) for (size_t i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
resources.allocSlot(); resources.allocSlot();
size_t size = resources.size(); size_t size = resources.size();

View File

@ -105,7 +105,7 @@
# endif # endif
#endif #endif
// Capacity of each variant pool (in slots) // Initial capacity of the pool list
#ifndef ARDUINOJSON_INITIAL_POOL_COUNT #ifndef ARDUINOJSON_INITIAL_POOL_COUNT
# define ARDUINOJSON_INITIAL_POOL_COUNT 4 # define ARDUINOJSON_INITIAL_POOL_COUNT 4
#endif #endif

View File

@ -16,15 +16,21 @@ class VariantPoolList {
VariantPoolList() = default; VariantPoolList() = default;
~VariantPoolList() { ~VariantPoolList() {
ARDUINOJSON_ASSERT(pools_ == nullptr); ARDUINOJSON_ASSERT(count_ == 0);
} }
VariantPoolList& operator=(VariantPoolList&& src) { VariantPoolList& operator=(VariantPoolList&& src) {
ARDUINOJSON_ASSERT(pools_ == nullptr); ARDUINOJSON_ASSERT(count_ == 0);
pools_ = src.pools_; if (src.pools_ == src.preallocatedPools_) {
memcpy(preallocatedPools_, src.preallocatedPools_,
sizeof(preallocatedPools_));
pools_ = preallocatedPools_;
} else {
pools_ = src.pools_;
src.pools_ = nullptr;
}
count_ = src.count_; count_ = src.count_;
capacity_ = src.capacity_; capacity_ = src.capacity_;
src.pools_ = nullptr;
src.count_ = 0; src.count_ = 0;
src.capacity_ = 0; src.capacity_ = 0;
return *this; return *this;
@ -37,7 +43,7 @@ class VariantPoolList {
} }
// try to allocate from last pool (other pools are full) // try to allocate from last pool (other pools are full)
if (pools_) { if (count_) {
auto slot = allocFromLastPool(); auto slot = allocFromLastPool();
if (slot) if (slot)
return slot; return slot;
@ -63,14 +69,14 @@ class VariantPoolList {
} }
void clear(Allocator* allocator) { void clear(Allocator* allocator) {
if (!pools_)
return;
for (PoolCount i = 0; i < count_; i++) for (PoolCount i = 0; i < count_; i++)
pools_[i].destroy(allocator); pools_[i].destroy(allocator);
allocator->deallocate(pools_);
pools_ = nullptr;
count_ = 0; count_ = 0;
capacity_ = 0; if (pools_ != preallocatedPools_) {
allocator->deallocate(pools_);
pools_ = preallocatedPools_;
capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT;
}
} }
SlotCount usage() const { SlotCount usage() const {
@ -81,9 +87,9 @@ class VariantPoolList {
} }
void shrinkToFit(Allocator* allocator) { void shrinkToFit(Allocator* allocator) {
if (pools_) if (count_ > 0)
pools_[count_ - 1].shrinkToFit(allocator); pools_[count_ - 1].shrinkToFit(allocator);
if (count_ != capacity_) { if (pools_ != preallocatedPools_ && count_ != capacity_) {
pools_ = static_cast<VariantPool*>( pools_ = static_cast<VariantPool*>(
allocator->reallocate(pools_, count_ * sizeof(VariantPool))); allocator->reallocate(pools_, count_ * sizeof(VariantPool)));
ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail
@ -95,10 +101,9 @@ class VariantPoolList {
SlotWithId allocFromFreeList(); SlotWithId allocFromFreeList();
SlotWithId allocFromLastPool() { SlotWithId allocFromLastPool() {
ARDUINOJSON_ASSERT(pools_ != nullptr); ARDUINOJSON_ASSERT(count_ > 0);
auto poolIndex = SlotId(count_ - 1); auto poolIndex = SlotId(count_ - 1);
auto lastPool = count_ ? &pools_[poolIndex] : nullptr; auto slot = pools_[poolIndex].allocSlot();
auto slot = lastPool->allocSlot();
if (!slot) if (!slot)
return {}; return {};
return {slot, SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())}; return {slot, SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())};
@ -116,28 +121,32 @@ class VariantPoolList {
} }
bool increaseCapacity(Allocator* allocator) { bool increaseCapacity(Allocator* allocator) {
if (count_ == maxPools) if (capacity_ == maxPools)
return false; return false;
void* newPools; void* newPools;
PoolCount newCapacity; auto newCapacity = PoolCount(capacity_ * 2);
if (pools_) {
newCapacity = PoolCount(capacity_ * 2); if (pools_ == preallocatedPools_) {
newPools = allocator->allocate(newCapacity * sizeof(VariantPool));
if (!newPools)
return false;
memcpy(newPools, preallocatedPools_, sizeof(preallocatedPools_));
} else {
newPools = newPools =
allocator->reallocate(pools_, newCapacity * sizeof(VariantPool)); allocator->reallocate(pools_, newCapacity * sizeof(VariantPool));
} else { if (!newPools)
newCapacity = ARDUINOJSON_INITIAL_POOL_COUNT; return false;
newPools = allocator->allocate(newCapacity * sizeof(VariantPool));
} }
if (!newPools)
return false;
pools_ = static_cast<VariantPool*>(newPools); pools_ = static_cast<VariantPool*>(newPools);
capacity_ = newCapacity; capacity_ = newCapacity;
return true; return true;
} }
VariantPool* pools_ = nullptr; VariantPool preallocatedPools_[ARDUINOJSON_INITIAL_POOL_COUNT];
VariantPool* pools_ = preallocatedPools_;
PoolCount count_ = 0; PoolCount count_ = 0;
PoolCount capacity_ = 0; PoolCount capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT;
SlotId freeList_ = NULL_SLOT; SlotId freeList_ = NULL_SLOT;
public: public: