Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.o
*.vscode
*tests/output
*googletest
tests/circular_buffer_tests
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@
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.

## Build instructions

This solution is using the googletest framework for C++ unit tests.

1. Clone googletest into the root folder of this project via
`git clone https://github.com/google/googletest.git`
2. Run `make check` inside the `tests` folder in order to compile and run the tests.
110 changes: 110 additions & 0 deletions include/circularbuffer/circular_buffer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2020 by Alexander Dahmen
*/
#ifndef CIRCULARBUFFER_CIRCULAR_BUFFER_HPP_
#define CIRCULARBUFFER_CIRCULAR_BUFFER_HPP_

#include <mutex>

namespace circularbuffer {

/**
* CircularBuffer class
*
* Declaration and definition and of a ring buffer with a fixed size.
*
*/
template <typename T, std::size_t buffer_size> class CircularBuffer {
public:
/**
* Reads the next item from the buffer if not empty.
* Returns true if successfully.
*
*/
bool get(T *item);
/**
* Method to write a new item into the buffer.
* If the buffer is full the oldest item will be overwritten.
*
*/
void putOverwrite(T const &item);
/**
* Method to write a new item into the buffer.
* If the buffer is full the new item won't be written.
*
*/
void putDiscard(T const &item);
/**
* Returns true if the buffer is full or false if not.
*
*/
bool full() const;
/**
* Returns true if the buffer is empty or false if not.
*/
bool empty() const;

private:
void put(T const &item);

size_t read_index_;
size_t write_index_;
size_t data_available_;
std::array<T, buffer_size> buf_;
std::mutex mutex_;
};

template <typename T, std::size_t buffer_size>
void CircularBuffer<T, buffer_size>::put(T const &item) {
buf_[write_index_] = item;
write_index_ = (write_index_ + 1) % buffer_size;
}

template <typename T, std::size_t buffer_size>
void CircularBuffer<T, buffer_size>::putOverwrite(T const &item) {
std::lock_guard<std::mutex> lock(mutex_);

put(item);
// Since this method overrides existing data
// the maximum data which can be available is buffer_size.
// Only increase data_available_ when it is smaller than buffer_size.
if (!full())
data_available_++;
}

template <typename T, std::size_t buffer_size>
void CircularBuffer<T, buffer_size>::putDiscard(T const &item) {
std::lock_guard<std::mutex> lock(mutex_);

if (full())
return;
put(item);
data_available_++;
}

template <typename T, std::size_t buffer_size>
bool CircularBuffer<T, buffer_size>::get(T *item) {
std::lock_guard<std::mutex> lock(mutex_);

if (empty())
return false;
// read one item, return it and move index
*item = buf_[read_index_];
read_index_ = (read_index_ + 1) % buffer_size;
data_available_--;
return true;
}

template <typename T, std::size_t buffer_size>
bool CircularBuffer<T, buffer_size>::full() const {
return data_available_ == buffer_size;
}

template <typename T, std::size_t buffer_size>
bool CircularBuffer<T, buffer_size>::empty() const {
return data_available_ == 0;
}

} // namespace circularbuffer

#endif // CIRCULARBUFFER_CIRCULAR_BUFFER_HPP_
56 changes: 56 additions & 0 deletions tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# A Makefile for fusing Google Test and building a sample test against it.
#
# SYNOPSIS:
#
# make [all] - makes everything.
# make TARGET - makes the given target.
# make check - makes everything and runs the built test.
# make clean - removes all files generated by make.

# googletest root dir
GOOGLE_TEST_ROOT = ../googletest/googletest

# Points to the root of fused Google Test, relative to where this file is.
FUSED_GTEST_DIR = output

# Paths to the fused gtest files.
FUSED_GTEST_H = $(FUSED_GTEST_DIR)/gtest/gtest.h
FUSED_GTEST_ALL_CC = $(FUSED_GTEST_DIR)/gtest/gtest-all.cc

# Where to find gtest_main.cc.
GTEST_MAIN_CC = $(GOOGLE_TEST_ROOT)/src/gtest_main.cc

# Flags passed to the preprocessor.
# We have no idea here whether pthreads is available in the system, so
# disable its use.
CPPFLAGS += -I$(FUSED_GTEST_DIR) -I../include -DGTEST_HAS_PTHREAD=0

# Flags passed to the C++ compiler.
CXXFLAGS += -g

all : circular_buffer_tests

check : all
./circular_buffer_tests

clean :
rm -rf $(FUSED_GTEST_DIR) circular_buffer_tests *.o

$(FUSED_GTEST_H) :
$(GOOGLE_TEST_ROOT)/scripts/fuse_gtest_files.py $(FUSED_GTEST_DIR)

$(FUSED_GTEST_ALL_CC) :
$(GOOGLE_TEST_ROOT)/scripts/fuse_gtest_files.py $(FUSED_GTEST_DIR)

gtest-all.o : $(FUSED_GTEST_H) $(FUSED_GTEST_ALL_CC)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(FUSED_GTEST_DIR)/gtest/gtest-all.cc

gtest_main.o : $(FUSED_GTEST_H) $(GTEST_MAIN_CC)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(GTEST_MAIN_CC)

circular_buffer_tests.o : circular_buffer_tests.cpp \
../include/circularbuffer/circular_buffer.hpp $(FUSED_GTEST_H)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c circular_buffer_tests.cpp

circular_buffer_tests : circular_buffer_tests.o gtest-all.o gtest_main.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
91 changes: 91 additions & 0 deletions tests/circular_buffer_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2020 by Alexander Dahmen
*/
#include <circularbuffer/circular_buffer.hpp>
#include <gtest/gtest.h>

using circularbuffer::CircularBuffer;

TEST(CircularBufferTest, overwriteTest) {
CircularBuffer<std::uint8_t, 3> circular_buffer{};

// check if buffer is empty
ASSERT_TRUE(circular_buffer.empty());
ASSERT_FALSE(circular_buffer.full());

// fill the buffer with data till it is full
circular_buffer.putOverwrite(10);
circular_buffer.putOverwrite(8);
circular_buffer.putOverwrite(6);

// check if full and empty are set correctly
ASSERT_TRUE(circular_buffer.full());
ASSERT_FALSE(circular_buffer.empty());

circular_buffer.putOverwrite(4);

uint8_t read_item;
bool result = circular_buffer.get(&read_item);
ASSERT_EQ(read_item, 4);
ASSERT_FALSE(circular_buffer.full());
ASSERT_FALSE(circular_buffer.empty());
}

TEST(CircularBufferTest, discardTest) {
CircularBuffer<std::uint8_t, 3> circular_buffer{};

// check if buffer is empty
ASSERT_TRUE(circular_buffer.empty());
ASSERT_FALSE(circular_buffer.full());

// fill the buffer with data till it is full
circular_buffer.putDiscard(10);
circular_buffer.putDiscard(8);
circular_buffer.putDiscard(6);

// check if full and empty are set correctly
ASSERT_TRUE(circular_buffer.full());
ASSERT_FALSE(circular_buffer.empty());

circular_buffer.putDiscard(4);

// read first item
uint8_t read_item;
bool result = circular_buffer.get(&read_item);
ASSERT_EQ(read_item, 10);
ASSERT_FALSE(circular_buffer.full());
ASSERT_FALSE(circular_buffer.empty());
}

TEST(CircularBufferTest, getTest) {
CircularBuffer<std::uint8_t, 3> circular_buffer{};

// check if buffer is empty
ASSERT_TRUE(circular_buffer.empty());
ASSERT_FALSE(circular_buffer.full());

uint8_t read_value;
// try to read from an empty buffer
bool result = circular_buffer.get(&read_value);
ASSERT_FALSE(result);

// fill buffer with data
for (int i = 0; i < 3; ++i) {
circular_buffer.putDiscard(i);
}

// check if buffer is full
ASSERT_FALSE(circular_buffer.empty());
ASSERT_TRUE(circular_buffer.full());

// read from buffer
for (int i = 0; i < 3; ++i) {
bool result = circular_buffer.get(&read_value);
ASSERT_TRUE(result);
ASSERT_EQ(read_value, i) << "read item differs at " << i;
}

// now buffer should be empty
ASSERT_TRUE(circular_buffer.empty());
ASSERT_FALSE(circular_buffer.full());
}