diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4205d75 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.10) +project(CircularBuffer) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -pthread") + +find_package(GTest REQUIRED) +include_directories(include test ${GTEST_INCLUDE_DIRS}) +add_executable(CircularBuffer test/circular_buffer_test.cpp) +target_link_libraries(CircularBuffer ${GTEST_LIBRARIES} pthread gtest gtest_main) +add_test(CircularBuffer CircularBuffer) +add_custom_target(circular_buffer ALL DEPENDS CircularBuffer COMMAND CircularBuffer) \ No newline at end of file diff --git a/README.md b/README.md index 7fb2175..33b36c7 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,33 @@ 3. Generic data type (e.g. uint8_t) 4. Two put(data) overflow behaviors: overwrite and discard data. 2. Write tests showing your circular buffer in action. + +## Install CMake and GTest +### CMake +https://cmake.org/install/ + +### GTest +Linux: +https://github.com/smistad/GTest +Windows: +https://github.com/iat-cener/tonatiuh/wiki/Installing-Google-Test-For-Windows + +Note: Compiler used to compiling GTest and this Circular buffer project needs to be same + +Use clang(clang 6) or gcc(gcc8) which supports C++17 + +## Build +Go to circular_buffer path. Execute the following commands. +1. cmake CMakeLists.txt +2. make + +CircularBuffer is built and run, an executable 'CircularBuffer' will be created. + +CMakeLists.txt has command to run the CircularBuffer Testcases for each build to encourage Test driven development. + +## Run + +In the circular_buffer path, 'CircularBuffer' executable will be created after successful build. + +Execute the following command to run the testcases. +./CircularBuffer \ No newline at end of file diff --git a/include/circular_buffer.h b/include/circular_buffer.h new file mode 100644 index 0000000..cbc2996 --- /dev/null +++ b/include/circular_buffer.h @@ -0,0 +1,61 @@ +#ifndef CIRCULAR_BUFFER_H +#define CIRCULAR_BUFFER_H + +#include +#include +namespace Circular { +namespace Buffer { + +constexpr auto kMaxBufferSize{50}; +constexpr auto kOverRide{true}; +template +class CircularBuffer { + public: + CircularBuffer() = default; + CircularBuffer(const CircularBuffer&) = delete; + CircularBuffer(CircularBuffer&&) = delete; + CircularBuffer& operator=(CircularBuffer&&) = delete; + CircularBuffer& operator=(const CircularBuffer&) = delete; + constexpr auto Capacity() const { return kMaxBufferSize; } + constexpr auto Empty() const { return (!full_ && (head_ == tail_)); } + constexpr auto Full() const { return full_; } + constexpr auto Size() const { + size_t buffer_size{kMaxBufferSize}; + if (full_) { + buffer_size = kMaxBufferSize + head_ - tail_; + } else { + buffer_size = head_ - tail_; + } + return buffer_size; + } + constexpr void PushBack(const T& data, bool override = kOverRide) { + std::lock_guard lock(circular_buffer_mutex_); + if (full_) { + if (override) { + PutDataFull(data); + } + } else { + PutData(data); + } + full_ = head_ == tail_; + } + constexpr T operator[](const size_t& index) const { return buffer_[index]; } + + private: + constexpr void PutData(const T& data) { + buffer_[head_] = data; + head_ = (head_ + 1) % kMaxBufferSize; + } + constexpr void PutDataFull(const T& data) { + PutData(data); + tail_ = (tail_ + 1) % kMaxBufferSize; + } + T buffer_[kMaxBufferSize]; + std::mutex circular_buffer_mutex_; + size_t head_{0}; + size_t tail_{0}; + bool full_{false}; +}; +} // namespace Buffer +} // namespace Circular +#endif \ No newline at end of file diff --git a/test/circular_buffer_test.cpp b/test/circular_buffer_test.cpp new file mode 100644 index 0000000..d0c3611 --- /dev/null +++ b/test/circular_buffer_test.cpp @@ -0,0 +1,84 @@ +#include +#include +namespace Circular { +namespace Buffer { +namespace { +using Type = std::int64_t; +class CircularBufferTest : public ::testing::Test { + public: + void SetUp() override {} + void TearDown() override {} + CircularBuffer circular_buffer_; +}; + +TEST_F(CircularBufferTest, CapacityTest) { + EXPECT_EQ(kMaxBufferSize, circular_buffer_.Capacity()); +} + +class BufferSizeTest : public ::testing::TestWithParam { + public: + void SetUp() override { + result_size_ = GetParam() % kMaxBufferSize; + for (auto index{0u}; index < result_size_; ++index) { + circular_buffer_.PushBack(index); + } + if (0 == result_size_) { + result_empty_ = true; + } + if (kMaxBufferSize == result_size_) { + result_full_ = true; + } + } + void TearDown() override {} + size_t result_size_{0}; + bool result_empty_{false}; + bool result_full_{false}; + CircularBuffer circular_buffer_; +}; + +TEST_P(BufferSizeTest, SizeTest) { + EXPECT_EQ(result_size_, circular_buffer_.Size()); +} +TEST_P(BufferSizeTest, EmptyTest) { + EXPECT_EQ(result_empty_, circular_buffer_.Empty()); +} +TEST_P(BufferSizeTest, FullTest) { + EXPECT_EQ(result_full_, circular_buffer_.Full()); +} +INSTANTIATE_TEST_CASE_P(SizeParam, BufferSizeTest, + ::testing::Values(0, kMaxBufferSize + 1)); + +class BufferDataTest : public ::testing::TestWithParam> { + public: + void SetUp() override { + auto buffer_size = std::get<0>(GetParam()); + auto override_flag = std::get<1>(GetParam()); + for (auto index{0}; index < buffer_size; ++index) { + circular_buffer_.PushBack(index, override_flag); + } + if (override_flag) { + result_start_index_ = buffer_size - kMaxBufferSize; + result_end_index_ = buffer_size; + } else { + result_end_index_ = buffer_size % kMaxBufferSize; + } + } + void TearDown() override {} + size_t result_start_index_{0}; + size_t result_end_index_{0}; + CircularBuffer circular_buffer_; +}; + +TEST_P(BufferDataTest, VerifyBuffer) { + for (auto index{result_start_index_}; index < result_end_index_; ++index) { + EXPECT_EQ(index, circular_buffer_[(index) % kMaxBufferSize]); + } +} +INSTANTIATE_TEST_CASE_P(OverrideDiscardParam, BufferDataTest, + ::testing::Combine(::testing::Values(0, kMaxBufferSize + + 1), + ::testing::Bool())); + +} // namespace +} // namespace Buffer +} // namespace Circular \ No newline at end of file