~aleteoryx/muditaos

3fd237c1e47e3b4a081e57c87ac47e984ffcbf2d — Mateusz Grzegorzek 5 years ago fc5ab3a
[EGD-5181] Add Circular and Logger buffers

Add RandomStringGenerator class.
Implement Circular and Logger buffers.
Add UTs for LoggerBuffer.
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);
    }
}