diff --git a/module-utils/CMakeLists.txt b/module-utils/CMakeLists.txt index 092a226bf..c697ddedf 100644 --- a/module-utils/CMakeLists.txt +++ b/module-utils/CMakeLists.txt @@ -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}) diff --git a/module-utils/circular_buffer/StringCircularBuffer.cpp b/module-utils/circular_buffer/StringCircularBuffer.cpp new file mode 100644 index 000000000..5acab2390 --- /dev/null +++ b/module-utils/circular_buffer/StringCircularBuffer.cpp @@ -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 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; + } +} diff --git a/module-utils/circular_buffer/StringCircularBuffer.hpp b/module-utils/circular_buffer/StringCircularBuffer.hpp new file mode 100644 index 000000000..a6f760336 --- /dev/null +++ b/module-utils/circular_buffer/StringCircularBuffer.hpp @@ -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 +#include + +class StringCircularBuffer +{ + public: + explicit StringCircularBuffer(size_t size) : buffer(std::make_unique(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 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 buffer; + bool full{false}; + size_t head{0}; + size_t capacity; + size_t size{0}; + size_t tail{0}; +}; diff --git a/module-utils/generators/RandomStringGenerator.cpp b/module-utils/generators/RandomStringGenerator.cpp new file mode 100644 index 000000000..6b99e439e --- /dev/null +++ b/module-utils/generators/RandomStringGenerator.cpp @@ -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 +#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 RandomStringGenerator::createRandomStringVector(size_t size) +{ + std::vector vec(size); + std::generate_n(vec.begin(), size, [this]() { return getRandomString(); }); + return vec; +} diff --git a/module-utils/generators/RandomStringGenerator.hpp b/module-utils/generators/RandomStringGenerator.hpp new file mode 100644 index 000000000..8a74d1fd9 --- /dev/null +++ b/module-utils/generators/RandomStringGenerator.hpp @@ -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 +#include +#include + +class RandomStringGenerator +{ + public: + RandomStringGenerator(size_t minLength = minRandomStringLength, size_t maxLength = maxRandomStringLength) + : minLength(minLength), maxLength(maxLength), lengthDist(minLength, maxLength) + {} + + std::string getRandomString(); + std::vector 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'}; +}; diff --git a/module-utils/log/LoggerBuffer.cpp b/module-utils/log/LoggerBuffer.cpp new file mode 100644 index 000000000..f81401da4 --- /dev/null +++ b/module-utils/log/LoggerBuffer.cpp @@ -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 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(); + } +} diff --git a/module-utils/log/LoggerBuffer.hpp b/module-utils/log/LoggerBuffer.hpp new file mode 100644 index 000000000..6511018e8 --- /dev/null +++ b/module-utils/log/LoggerBuffer.hpp @@ -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 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}; +}; diff --git a/module-utils/test/CMakeLists.txt b/module-utils/test/CMakeLists.txt index 666ce96fe..83f3b5122 100644 --- a/module-utils/test/CMakeLists.txt +++ b/module-utils/test/CMakeLists.txt @@ -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 diff --git a/module-utils/test/test_LoggerBuffer.cpp b/module-utils/test/test_LoggerBuffer.cpp new file mode 100644 index 000000000..9d60844d9 --- /dev/null +++ b/module-utils/test/test_LoggerBuffer.cpp @@ -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 + +#include "generators/RandomStringGenerator.hpp" +#include "log/LoggerBuffer.hpp" +#include +#include + +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::const_iterator startIt, vector::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 &msgs) { for_each(msgs.begin(), msgs.end(), putMsgFunc); }; + auto getAllMsgsFunc = [&](const vector &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); + } +}