From 22c46ffb06aba4f143deeccb30c3dd90bf139adc Mon Sep 17 00:00:00 2001 From: Ussama Naal Date: Wed, 19 Jul 2023 16:55:12 -0700 Subject: [PATCH] Add ring_buffer_test + enable building and running within docker --- CMakeLists.txt | 6 ++ Dockerfile | 7 +- tests/ring_buffer_test.cpp | 181 +++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 tests/ring_buffer_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d4a3d903..fc2c605b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,12 @@ add_library(nodelets_os target_link_libraries(nodelets_os ouster_ros ${catkin_LIBRARIES}) add_dependencies(nodelets_os ${PROJECT_NAME}_gencpp) +# ==== Test ==== +if(CATKIN_ENABLE_TESTING) + catkin_add_gtest(${PROJECT_NAME}_test tests/ring_buffer_test.cpp) + target_link_libraries(${PROJECT_NAME}_test ${catkin_LIBRARIES}) +endif() + # ==== Install ==== install( TARGETS diff --git a/Dockerfile b/Dockerfile index c703175c..fd859545 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,8 +52,11 @@ RUN set -xe \ FROM build-env ENV CXXFLAGS="-Werror -Wno-deprecated-declarations" -RUN /opt/ros/$ROS_DISTRO/env.sh catkin_make -DCMAKE_BUILD_TYPE=Release \ -&& /opt/ros/$ROS_DISTRO/env.sh catkin_make install +RUN /opt/ros/$ROS_DISTRO/env.sh catkin_make \ + -DCMAKE_BUILD_TYPE=Release --make-args tests \ + && /opt/ros/$ROS_DISTRO/env.sh catkin_make install + +RUN source /opt/ros/$ROS_DISTRO/setup.bash && rosrun ouster_ros ouster_ros_test # Entrypoint for running Ouster ros: # diff --git a/tests/ring_buffer_test.cpp b/tests/ring_buffer_test.cpp new file mode 100644 index 00000000..8d8bd7f4 --- /dev/null +++ b/tests/ring_buffer_test.cpp @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include "../src/thread_safe_ring_buffer.h" + +using namespace std::chrono_literals; + +class ThreadSafeRingBufferTest : public ::testing::Test { + protected: + static const int ITEM_SIZE = 4; // predefined size for all items used in + static const int ITEM_COUNT = 3; // number of item the buffer could hold + + void SetUp() override { + buffer = std::make_unique(ITEM_SIZE, ITEM_COUNT); + } + + void TearDown() override { + buffer.reset(); + } + + std::string rand_str(int size) { + const std::string characters = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dist(0, characters.size() - 1); + + std::string result; + for (int i = 0; i < size; ++i) { + result += characters[dist(gen)]; + } + return result; + } + + std::vector rand_vector_str(int vec_size, int str_size) { + std::vector output(vec_size); + for (auto i = 0; i < vec_size; ++i) + output[i] = rand_str(str_size); + return output; + } + + std::vector known_vector_str(int vec_size, const std::string& known) { + std::vector output(vec_size); + for (auto i = 0; i < vec_size; ++i) + output[i] = known; + return output; + } + + std::unique_ptr buffer; +}; + +TEST_F(ThreadSafeRingBufferTest, ReadWriteToBufferSimple) { + + assert (ITEM_COUNT > 1 && "or this test can't run"); + + const int TOTAL_ITEMS = 10; // total items to process + const std::vector source = rand_vector_str(TOTAL_ITEMS, ITEM_SIZE); + std::vector target = known_vector_str(TOTAL_ITEMS, "0000"); + + EXPECT_TRUE(buffer->empty()); + EXPECT_FALSE(buffer->full()); + + for (int i = 0; i < ITEM_COUNT; ++i) { + buffer->write([i, &source](uint8_t* buffer) { + std::memcpy(buffer, &source[i][0], ITEM_SIZE); + }); + } + + EXPECT_FALSE(buffer->empty()); + EXPECT_TRUE(buffer->full()); + + // remove one item + buffer->read([&target](uint8_t* buffer){ + std::memcpy(&target[0][0], buffer, ITEM_SIZE); + }); + + EXPECT_FALSE(buffer->empty()); + EXPECT_FALSE(buffer->full()); + + for (int i = 1; i < ITEM_COUNT; ++i) { + buffer->read([i, &target](uint8_t* buffer){ + std::memcpy(&target[i][0], buffer, ITEM_SIZE); + }); + } + + EXPECT_TRUE(buffer->empty()); + EXPECT_FALSE(buffer->full()); + + for (int i = 0; i < ITEM_COUNT; ++i) { + std::cout << "source " << source[i] << ", target " << target[i] << std::endl; + EXPECT_EQ(target[i], source[i]); + } +} + +TEST_F(ThreadSafeRingBufferTest, ReadWriteToBuffer) { + + const int TOTAL_ITEMS = 10; // total items to process + const std::vector source = rand_vector_str(TOTAL_ITEMS, ITEM_SIZE); + std::vector target = known_vector_str(TOTAL_ITEMS, "0000"); + + EXPECT_TRUE(buffer->empty()); + EXPECT_FALSE(buffer->full()); + + std::thread producer([this, &source]() { + for (int i = 0; i < TOTAL_ITEMS; ++i) { + buffer->write([i, &source](uint8_t* buffer){ + std::memcpy(buffer, &source[i][0], ITEM_SIZE); + }); + } + }); + + std::thread consumer([this, &target]() { + for (int i = 0; i < TOTAL_ITEMS; ++i) { + buffer->read([i, &target](uint8_t* buffer){ + std::memcpy(&target[i][0], buffer, ITEM_SIZE); + }); + } + }); + + producer.join(); + consumer.join(); + + for (int i = 0; i < TOTAL_ITEMS; ++i) { + std::cout << "source " << source[i] << ", target " << target[i] << std::endl; + EXPECT_EQ(target[i], source[i]); + } +} + +TEST_F(ThreadSafeRingBufferTest, ReadWriteToBufferWithOverwrite) { + + const int TOTAL_ITEMS = 10; // total items to process + const std::vector source = rand_vector_str(TOTAL_ITEMS, ITEM_SIZE); + std::vector target = known_vector_str(TOTAL_ITEMS, "0000"); + + EXPECT_TRUE(buffer->empty()); + EXPECT_FALSE(buffer->full()); + + std::thread producer([this, &source]() { + for (int i = 0; i < TOTAL_ITEMS; ++i) { + buffer->write_overwrite([i, &source](uint8_t* buffer){ + std::memcpy(buffer, &source[i][0], ITEM_SIZE); + }); + } + }); + + // wait for 1 second before starting the consumer thread + // allowing sufficient time for the producer thread to be + // completely done + std::this_thread::sleep_for(1s); + std::thread consumer([this, &target]() { + for (int i = 0; i < TOTAL_ITEMS; ++i) { + buffer->read_timeout([i, &target](uint8_t* buffer){ + if (buffer != nullptr) + std::memcpy(&target[i][0], buffer, ITEM_SIZE); + }, 1s); + } + }); + + producer.join(); + consumer.join(); + + // Since our buffer can host only up to ITEM_COUNT simultanously only the + // last ITEM_COUNT items would have remained in the buffer by the time + // the consumer started processing. + for (int i = 0; i < ITEM_COUNT; ++i) { + std::cout << "source " << source[i] << ", target " << target[i] << std::endl; + EXPECT_EQ(target[i], source[TOTAL_ITEMS-ITEM_COUNT+i]); + } + + for (int i = ITEM_COUNT; i < TOTAL_ITEMS; ++i) { + std::cout << "source " << source[i] << ", target " << target[i] << std::endl; + EXPECT_EQ(target[i], "0000"); + } +} + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file