M module-utils/CMakeLists.txt => module-utils/CMakeLists.txt +3 -0
@@ 38,6 38,9 @@ set (SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/log/Logger.cpp
${CMAKE_CURRENT_SOURCE_DIR}/log/log.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/log/LoggerBuffer.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/circular_buffer/StringCircularBuffer.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/generators/RandomStringGenerator.cpp
)
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${BOARD_SOURCES})
A module-utils/circular_buffer/StringCircularBuffer.cpp => module-utils/circular_buffer/StringCircularBuffer.cpp +55 -0
@@ 0,0 1,55 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "StringCircularBuffer.hpp"
+
+std::pair<bool, std::string> StringCircularBuffer::get()
+{
+ if (isEmpty()) {
+ return {false, ""};
+ }
+
+ const std::string val = buffer[tail];
+ full = false;
+ tail = (tail + 1) % capacity;
+ --size;
+
+ return {true, val};
+}
+
+void StringCircularBuffer::put(const std::string &item)
+{
+ updateMembersBeforePut();
+ buffer[head] = item;
+ updateMembersAfterPut();
+}
+
+void StringCircularBuffer::put(std::string &&item)
+{
+ updateMembersBeforePut();
+ buffer[head] = std::move(item);
+ updateMembersAfterPut();
+}
+
+void StringCircularBuffer::reset()
+{
+ head = tail;
+ full = false;
+ size = 0;
+}
+
+void StringCircularBuffer::updateMembersAfterPut()
+{
+ head = (head + 1) % capacity;
+ full = head == tail;
+}
+
+void StringCircularBuffer::updateMembersBeforePut()
+{
+ if (full) {
+ tail = (tail + 1) % capacity;
+ }
+ else {
+ ++size;
+ }
+}
A module-utils/circular_buffer/StringCircularBuffer.hpp => module-utils/circular_buffer/StringCircularBuffer.hpp +46 -0
@@ 0,0 1,46 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+class StringCircularBuffer
+{
+ public:
+ explicit StringCircularBuffer(size_t size) : buffer(std::make_unique<std::string[]>(size)), capacity(size)
+ {}
+ virtual ~StringCircularBuffer() = default;
+ [[nodiscard]] size_t getCapacity() const noexcept
+ {
+ return capacity;
+ }
+ [[nodiscard]] bool isEmpty() const noexcept
+ {
+ return size == 0;
+ }
+ [[nodiscard]] virtual std::pair<bool, std::string> get();
+ [[nodiscard]] size_t getSize() const noexcept
+ {
+ return size;
+ }
+ [[nodiscard]] bool isFull() const noexcept
+ {
+ return full;
+ }
+ virtual void put(const std::string &item);
+ virtual void put(std::string &&item);
+ void reset();
+
+ private:
+ void updateMembersAfterPut();
+ void updateMembersBeforePut();
+
+ std::unique_ptr<std::string[]> buffer;
+ bool full{false};
+ size_t head{0};
+ size_t capacity;
+ size_t size{0};
+ size_t tail{0};
+};
A module-utils/generators/RandomStringGenerator.cpp => module-utils/generators/RandomStringGenerator.cpp +20 -0
@@ 0,0 1,20 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <algorithm>
+#include "RandomStringGenerator.hpp"
+
+std::string RandomStringGenerator::getRandomString()
+{
+ const size_t length = lengthDist(rng);
+ std::string str(length, 0);
+ std::generate_n(str.begin(), length, [this]() { return charSet[charDist(rng)]; });
+ return str;
+}
+
+std::vector<std::string> RandomStringGenerator::createRandomStringVector(size_t size)
+{
+ std::vector<std::string> vec(size);
+ std::generate_n(vec.begin(), size, [this]() { return getRandomString(); });
+ return vec;
+}
A module-utils/generators/RandomStringGenerator.hpp => module-utils/generators/RandomStringGenerator.hpp +33 -0
@@ 0,0 1,33 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <random>
+#include <string>
+#include <vector>
+
+class RandomStringGenerator
+{
+ public:
+ RandomStringGenerator(size_t minLength = minRandomStringLength, size_t maxLength = maxRandomStringLength)
+ : minLength(minLength), maxLength(maxLength), lengthDist(minLength, maxLength)
+ {}
+
+ std::string getRandomString();
+ std::vector<std::string> createRandomStringVector(size_t size);
+
+ private:
+ std::uniform_int_distribution<> charDist{0, sizeof(charSet) - 1};
+ std::default_random_engine rng{std::random_device{}()};
+ const size_t minLength;
+ const size_t maxLength;
+ std::uniform_int_distribution<> lengthDist;
+
+ static constexpr auto minRandomStringLength = 1;
+ static constexpr auto maxRandomStringLength = 25;
+ static constexpr char charSet[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+ 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
+};
A module-utils/log/LoggerBuffer.cpp => module-utils/log/LoggerBuffer.cpp +37 -0
@@ 0,0 1,37 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "LoggerBuffer.hpp"
+
+std::pair<bool, std::string> LoggerBuffer::get()
+{
+ auto [result, logMsg] = StringCircularBuffer::get();
+ if (!result) {
+ return {result, logMsg};
+ }
+ if (numOfLostBytes > 0) {
+ logMsg += "\r\n" + std::to_string(numOfLostBytes) + " " + lostBytesMessage;
+ numOfLostBytes = 0;
+ }
+ return {true, logMsg};
+}
+
+void LoggerBuffer::put(const std::string &logMsg)
+{
+ updateNumOfLostBytes();
+ StringCircularBuffer::put(logMsg);
+}
+
+void LoggerBuffer::put(std::string &&logMsg)
+{
+ updateNumOfLostBytes();
+ StringCircularBuffer::put(std::move(logMsg));
+}
+
+void LoggerBuffer::updateNumOfLostBytes()
+{
+ if (StringCircularBuffer::isFull()) {
+ auto [_, lostMsg] = StringCircularBuffer::get();
+ numOfLostBytes += lostMsg.length();
+ }
+}
A module-utils/log/LoggerBuffer.hpp => module-utils/log/LoggerBuffer.hpp +23 -0
@@ 0,0 1,23 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "circular_buffer/StringCircularBuffer.hpp"
+
+class LoggerBuffer : public StringCircularBuffer
+{
+ public:
+ using StringCircularBuffer::StringCircularBuffer;
+
+ [[nodiscard]] std::pair<bool, std::string> get() override;
+ void put(const std::string &logMsg) override;
+ void put(std::string &&logMsg) override;
+
+ static constexpr auto lostBytesMessage = "bytes was lost.";
+
+ private:
+ void updateNumOfLostBytes();
+
+ size_t numOfLostBytes{0};
+};
M module-utils/test/CMakeLists.txt => module-utils/test/CMakeLists.txt +10 -0
@@ 75,6 75,16 @@ add_catch2_executable(
module-utils
)
+# Logger buffer tests
+add_catch2_executable(
+ NAME
+ utils-loggerbuffer
+ SRCS
+ test_LoggerBuffer.cpp
+ LIBS
+ module-utils
+)
+
# ParserICS tests
#add_catch2_executable(
# NAME
A module-utils/test/test_LoggerBuffer.cpp => module-utils/test/test_LoggerBuffer.cpp +192 -0
@@ 0,0 1,192 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+#include <catch2/catch.hpp>
+
+#include "generators/RandomStringGenerator.hpp"
+#include "log/LoggerBuffer.hpp"
+#include <string>
+#include <vector>
+
+using namespace std;
+
+void TestBuffer(const LoggerBuffer &buffer, size_t capacity, size_t numOfMsgs)
+{
+ const bool isEmpty = buffer.isEmpty();
+ REQUIRE((numOfMsgs == 0 ? isEmpty : !isEmpty));
+ const bool isFull = buffer.isFull();
+ REQUIRE((capacity > 0 && capacity == numOfMsgs ? isFull : !isFull));
+ REQUIRE(buffer.getCapacity() == capacity);
+ REQUIRE(buffer.getSize() == numOfMsgs);
+}
+
+size_t GetNumOfBytes(vector<string>::const_iterator startIt, vector<string>::const_iterator endIt)
+{
+ size_t numOfBytes = 0;
+ for (; startIt != endIt; ++startIt) {
+ numOfBytes += startIt->length();
+ }
+ return numOfBytes;
+}
+
+TEST_CASE("LoggerBuffer tests")
+{
+ const size_t capacity = 100;
+ LoggerBuffer buffer(capacity);
+
+ RandomStringGenerator randomStringGenerator;
+
+ auto putMsgFunc = [&](const auto &msg) { buffer.put(msg); };
+ auto getMsgFunc = [&](const auto &originalMsg) {
+ const auto [result, msg] = buffer.get();
+ REQUIRE(result);
+ REQUIRE(msg == originalMsg);
+ };
+ auto putAllMsgsFunc = [&](const vector<string> &msgs) { for_each(msgs.begin(), msgs.end(), putMsgFunc); };
+ auto getAllMsgsFunc = [&](const vector<string> &msgs) { for_each(msgs.begin(), msgs.end(), getMsgFunc); };
+ auto checkLostBytes = [&](size_t numOfBytes, const string &originalMsg) {
+ const auto [result, msg] = buffer.get();
+ REQUIRE(result);
+ REQUIRE(msg.find(originalMsg) != string::npos);
+ REQUIRE(msg.find(to_string(numOfBytes)) != string::npos);
+ REQUIRE(msg.find(LoggerBuffer::lostBytesMessage) != string::npos);
+ };
+
+ SECTION("calling get on empty buffer should return false")
+ {
+ const auto [result, _] = buffer.get();
+ REQUIRE(!result);
+ TestBuffer(buffer, capacity, 0);
+ }
+
+ SECTION("after putting one msg in buffer, get should return this msg")
+ {
+ const string originalMsg = randomStringGenerator.getRandomString();
+ buffer.put(originalMsg);
+ TestBuffer(buffer, capacity, 1);
+ const auto [result, msg] = buffer.get();
+ REQUIRE(result);
+ REQUIRE(msg == originalMsg);
+ TestBuffer(buffer, capacity, 0);
+ }
+
+ SECTION("after filling whole buffer with msgs, caliling get repeatedly should return all these msgs back")
+ {
+ const auto msgs = randomStringGenerator.createRandomStringVector(capacity);
+ putAllMsgsFunc(msgs);
+ TestBuffer(buffer, capacity, msgs.size());
+ getAllMsgsFunc(msgs);
+ TestBuffer(buffer, capacity, 0);
+ }
+
+ SECTION("buffer should be empty after resetting it")
+ {
+ const auto msgs = randomStringGenerator.createRandomStringVector(capacity);
+ putAllMsgsFunc(msgs);
+ TestBuffer(buffer, capacity, msgs.size());
+ buffer.reset();
+ TestBuffer(buffer, capacity, 0);
+ }
+
+ SECTION("when more msgs are put into buffer than its capacity, only last msgs should be returned with get and "
+ "first msg should contain lost bytes message")
+ {
+ const size_t numOfMsgsAboveCapacity = 13;
+ const auto msgs = randomStringGenerator.createRandomStringVector(capacity + numOfMsgsAboveCapacity);
+ putAllMsgsFunc(msgs);
+ auto firstLostMsgIt = msgs.begin();
+ auto firstMsgInBufferIt = firstLostMsgIt + numOfMsgsAboveCapacity;
+ const auto numOfLostBytes = GetNumOfBytes(firstLostMsgIt, firstMsgInBufferIt);
+ checkLostBytes(numOfLostBytes, *firstMsgInBufferIt);
+ for_each(firstMsgInBufferIt + 1, msgs.end(), getMsgFunc);
+ TestBuffer(buffer, capacity, 0);
+ }
+
+ SECTION("each get should reduce number of msgs in buffer")
+ {
+ size_t numOfMsgsInBuffer = capacity;
+ const auto msgs = randomStringGenerator.createRandomStringVector(capacity);
+
+ auto getStartIt = msgs.begin();
+
+ putAllMsgsFunc(msgs);
+ TestBuffer(buffer, capacity, capacity);
+
+ size_t numOfMsgsToGet = 15;
+ for_each(getStartIt, getStartIt + numOfMsgsToGet, getMsgFunc);
+ numOfMsgsInBuffer -= numOfMsgsToGet;
+ TestBuffer(buffer, capacity, numOfMsgsInBuffer);
+ getStartIt += numOfMsgsToGet;
+
+ numOfMsgsToGet = 34;
+ for_each(getStartIt, getStartIt + numOfMsgsToGet, getMsgFunc);
+ numOfMsgsInBuffer -= numOfMsgsToGet;
+ TestBuffer(buffer, capacity, numOfMsgsInBuffer);
+ getStartIt += numOfMsgsToGet;
+
+ for_each(getStartIt, msgs.end(), getMsgFunc);
+ TestBuffer(buffer, capacity, 0);
+ }
+
+ SECTION("put get put get")
+ {
+ const auto msgs = randomStringGenerator.createRandomStringVector(capacity);
+
+ auto putStartIt = msgs.begin();
+ auto getStartIt = msgs.begin();
+
+ size_t numOfMsgsToPut = 37;
+ for_each(putStartIt, putStartIt + numOfMsgsToPut, putMsgFunc);
+ putStartIt += numOfMsgsToPut;
+ size_t numOfMsgsInBuffer = numOfMsgsToPut;
+ TestBuffer(buffer, capacity, numOfMsgsInBuffer);
+
+ size_t numOfMsgsToGet = 25;
+ for_each(getStartIt, getStartIt + numOfMsgsToGet, getMsgFunc);
+ getStartIt += numOfMsgsToGet;
+ numOfMsgsInBuffer -= numOfMsgsToGet;
+ TestBuffer(buffer, capacity, numOfMsgsInBuffer);
+
+ numOfMsgsToPut = msgs.end() - putStartIt;
+ for_each(putStartIt, msgs.end(), putMsgFunc);
+ numOfMsgsInBuffer += numOfMsgsToPut;
+ TestBuffer(buffer, capacity, numOfMsgsInBuffer);
+
+ for_each(getStartIt, msgs.end(), getMsgFunc);
+ TestBuffer(buffer, capacity, 0);
+ }
+
+ SECTION("put get put get - with buffer overflow")
+ {
+ const size_t numOfMsgsAboveCapacity = 43;
+ const auto msgs = randomStringGenerator.createRandomStringVector(capacity + numOfMsgsAboveCapacity);
+
+ auto putStartIt = msgs.begin();
+ auto getStartIt = msgs.begin();
+
+ size_t numOfMsgsToPut = 77;
+ for_each(putStartIt, putStartIt + numOfMsgsToPut, putMsgFunc);
+ putStartIt += numOfMsgsToPut;
+ size_t numOfMsgsInBuffer = numOfMsgsToPut;
+ TestBuffer(buffer, capacity, numOfMsgsInBuffer);
+
+ size_t numOfMsgsToGet = 15;
+ for_each(getStartIt, getStartIt + numOfMsgsToGet, getMsgFunc);
+ getStartIt += numOfMsgsToGet;
+ numOfMsgsInBuffer -= numOfMsgsToGet;
+ TestBuffer(buffer, capacity, numOfMsgsInBuffer);
+
+ numOfMsgsToPut = msgs.end() - putStartIt; // put rest of msgs - more than buffer can hold
+ for_each(putStartIt, msgs.end(), putMsgFunc);
+ numOfMsgsInBuffer = capacity;
+ TestBuffer(buffer, capacity, numOfMsgsInBuffer);
+
+ auto firstLostMsgIt = getStartIt;
+ auto firstMsgInBufferIt = firstLostMsgIt + numOfMsgsAboveCapacity - numOfMsgsToGet;
+ const auto numOfLostBytes = GetNumOfBytes(firstLostMsgIt, firstMsgInBufferIt);
+ checkLostBytes(numOfLostBytes, *firstMsgInBufferIt);
+ for_each(firstMsgInBufferIt + 1, msgs.end(), getMsgFunc);
+ TestBuffer(buffer, capacity, 0);
+ }
+}