From ea9bd9ee328d9bb54adc92ea1b4edb7d716ac1fe Mon Sep 17 00:00:00 2001 From: Roland Bock Date: Sun, 1 Oct 2023 14:49:02 +0200 Subject: [PATCH] Add tests for circular buffer --- include/sqlpp11/detail/circular_buffer.h | 2 +- tests/core/CMakeLists.txt | 1 + tests/core/helpers/CMakeLists.txt | 32 +++ tests/core/helpers/circular_buffer.cpp | 345 +++++++++++++++++++++++ 4 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 tests/core/helpers/CMakeLists.txt create mode 100644 tests/core/helpers/circular_buffer.cpp diff --git a/include/sqlpp11/detail/circular_buffer.h b/include/sqlpp11/detail/circular_buffer.h index 2737715b..df540e81 100644 --- a/include/sqlpp11/detail/circular_buffer.h +++ b/include/sqlpp11/detail/circular_buffer.h @@ -35,7 +35,7 @@ namespace sqlpp { namespace detail { - // This class is modelled after boost::circular_buffer + // This class is modeled after boost::circular_buffer template class circular_buffer { diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 37e5b30e..10b4fe37 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -27,3 +27,4 @@ add_subdirectory(constraints) add_subdirectory(static_asserts) add_subdirectory(types) add_subdirectory(compat) +add_subdirectory(helpers) diff --git a/tests/core/helpers/CMakeLists.txt b/tests/core/helpers/CMakeLists.txt new file mode 100644 index 00000000..e531c5db --- /dev/null +++ b/tests/core/helpers/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (c) 2013-2015, Roland Bock +# Copyright (c) 2023, Vesselin Atanasov +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +function(test_compile name) + set(target sqlpp11_helpers_${name}) + add_executable(${target} ${name}.cpp) + target_link_libraries(${target} PRIVATE sqlpp11::sqlpp11 sqlpp11_testing) +endfunction() + +test_compile(circular_buffer) diff --git a/tests/core/helpers/circular_buffer.cpp b/tests/core/helpers/circular_buffer.cpp new file mode 100644 index 00000000..eaffb8dd --- /dev/null +++ b/tests/core/helpers/circular_buffer.cpp @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2023, Roland Bock + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +namespace sqlpp +{ + namespace + { + template + void assert_equal(int lineNo, const Result& result, const Expected& expected) + { + if (result != expected) + { + std::cerr << __FILE__ << " " << lineNo << '\n' + << "Expected: -->|" << expected << "|<--\n" + << "Received: -->|" << result << "|<--\n"; + throw std::runtime_error("unexpected result"); + } + } + + template + void assert_not_equal(int lineNo, const Result& result, const Expected& expected) + { + if (result == expected) + { + std::cerr << __FILE__ << " " << lineNo << '\n' + << "Expected: -->|" << expected << "|<--\n" + << "Received: -->|" << result << "|<--\n"; + throw std::runtime_error("unexpected equality"); + } + } + + template + inline void assert_true(int lineNo, Result result) + { + assert_equal(lineNo, result, true); + } + + template + inline void assert_false(int lineNo, Result result) + { + assert_equal(lineNo, result, false); + } + + template + void assert_runtime_error(int lineNo, Callable callable) + { + try + { + callable(); + } + catch (const std::runtime_error& ex) + { + return; + } + catch (...) + { + std::cerr << __FILE__ << " " << lineNo << '\n' << "Unexpected exception caught\n"; + throw std::runtime_error("unexpected exception"); + } + std::cerr << __FILE__ << " " << lineNo << '\n' << "Expected exception not thrown\n"; + throw std::runtime_error("Missing exception"); + } + + template + void assert_no_except(int lineNo, Callable callable) + { + try + { + callable(); + } + catch (...) + { + std::cerr << __FILE__ << " " << lineNo << '\n' << "Unexpected exception caught\n"; + throw std::runtime_error("unexpected exception"); + } + } + + using ::sqlpp::detail::circular_buffer; + + void test_no_capacity() + { + auto cb = circular_buffer(0); + assert_true(__LINE__, cb.empty()); + assert_true(__LINE__, cb.full()); + assert_runtime_error(__LINE__, [&cb] { cb.front(); }); + assert_runtime_error(__LINE__, [&cb] { cb.push_back(42); }); + assert_runtime_error(__LINE__, [&cb] { cb.pop_front(); }); + cb.set_capacity(1); + } + + void test_empty() + { + auto cb = circular_buffer(10); + assert_true(__LINE__, cb.empty()); + assert_false(__LINE__, cb.full()); + assert_runtime_error(__LINE__, [&cb] { cb.front(); }); + assert_runtime_error(__LINE__, [&cb] { cb.pop_front(); }); + assert_no_except(__LINE__, [&cb] { cb.push_back(42); }); + cb.set_capacity(1); + } + + void test_push_back() + { + constexpr int capacity = 10; + auto cb = circular_buffer(capacity); + + // We can push as many times as we have capacity. + for (int i = 0; i < capacity; ++i) + { + cb.push_back(42); + assert_equal(__LINE__, cb.size(), std::size_t(i + 1)); + } + assert_true(__LINE__, cb.full()); + + // Cannot push more than the capacity. + assert_runtime_error(__LINE__, [&cb] { cb.push_back(42); }); + } + + void test_pop_front() + { + constexpr int capacity = 10; + auto cb = circular_buffer(capacity); + + for (int i = 0; i < capacity; ++i) + { + cb.push_back(42); + } + + // We can pop as many times as we pushed before. + for (int i = 0; i < capacity; ++i) + { + cb.pop_front(); + assert_equal(__LINE__, cb.size(), std::size_t(capacity - i - 1)); + } + assert_true(__LINE__, cb.empty()); + + // Cannot pop from empty buffer. + assert_runtime_error(__LINE__, [&cb] { cb.pop_front(); }); + } + + void test_front() + { + constexpr int capacity = 10; + auto cb = circular_buffer(capacity); + + // Pushing back does not change the front. + for (int i = 0; i < capacity; ++i) + { + cb.push_back(int(i)); + assert_equal(__LINE__, cb.front(), 0); + } + + // Popping from the front moves `front` to the next entry. + for (int i = 0; i < capacity; ++i) + { + assert_equal(__LINE__, cb.front(), i); + cb.pop_front(); + } + + // Cannot pop from empty buffer. + assert_runtime_error(__LINE__, [&cb] { cb.pop_front(); }); + } + + void test_increase_capacity() + { + constexpr int old_capacity = 11; + constexpr int old_size = 7; + constexpr int new_capacity = 17; + for (int iterations = 0; iterations < old_capacity; ++iterations) + { + auto cb = circular_buffer(old_capacity); + + // Pre-fill with `size` items. + int current_back = 0; + int current_front = 0; + for (int i = 0; i < old_size; ++i) + { + cb.push_back(int(current_back)); + ++current_back; + } + + // Move through the buffer. + for (int i = 0; i < iterations; ++i) + { + cb.push_back(int(current_back)); + assert_equal(__LINE__, cb.front(), current_front); + cb.pop_front(); + ++current_back; + ++current_front; + } + + assert_equal(__LINE__, cb.size(), std::size_t(old_size)); + + // Increasing the capacity above the current size should have no effect on the current items. + cb.set_capacity(new_capacity); + + // Popping from the front moves `front` to the next entry. Since not items were touched, we can value continue to be in ithe same order they were pushed. + for (int i = 0; i < new_capacity; ++i) + { + assert_equal(__LINE__, cb.front(), current_front); + cb.pop_front(); + ++current_front; + cb.push_back(int(current_back)); + ++current_back; + } + } + } + + void test_reduce_capacity() + { + constexpr int old_capacity = 17; + constexpr int old_size = 11; + constexpr int new_capacity = 7; + for (int iterations = 0; iterations < old_capacity; ++iterations) + { + auto cb = circular_buffer(old_capacity); + + // Pre-fill with `size` items. + int current_back = 0; + int current_front = 0; + for (int i = 0; i < old_size; ++i) + { + cb.push_back(int(current_back)); + ++current_back; + } + + // Move through the buffer. + for (int i = 0; i < iterations; ++i) + { + cb.push_back(int(current_back)); + assert_equal(__LINE__, cb.front(), current_front); + cb.pop_front(); + ++current_back; + ++current_front; + } + + assert_equal(__LINE__, cb.size(), std::size_t(old_size)); + + // Reducing the capacity below the current size implictly drops the last push_back items. The remaining items at + // the front() should remain unchanged, though. + cb.set_capacity(new_capacity); + + // Popping from the front moves `front` to the next entry. + for (int i = 0; i < new_capacity; ++i) + { + assert_equal(__LINE__, cb.front(), current_front); + cb.pop_front(); + ++current_front; + cb.push_back(int(current_back)); + ++current_back; + } + + // We can observe that a few push_back items were dropped. + assert_not_equal(__LINE__, cb.front(), current_front); + + } + } + + void test_reduce_capacity_to_size() + { + constexpr int old_capacity = 17; + constexpr int old_size = 11; + constexpr int new_capacity = old_size; + for (int iterations = 0; iterations < old_capacity; ++iterations) + { + auto cb = circular_buffer(old_capacity); + + // Pre-fill with `size` items. + int current_back = 0; + int current_front = 0; + for (int i = 0; i < old_size; ++i) + { + cb.push_back(int(current_back)); + ++current_back; + } + + // Move through the buffer. + for (int i = 0; i < iterations; ++i) + { + cb.push_back(int(current_back)); + assert_equal(__LINE__, cb.front(), current_front); + cb.pop_front(); + ++current_back; + ++current_front; + } + + assert_equal(__LINE__, cb.size(), std::size_t(old_size)); + + // Reducing the capacity to the current size drops no items. + cb.set_capacity(new_capacity); + + // Popping from the front moves `front` to the next entry. + for (int i = 0; i < old_capacity; ++i) + { + assert_equal(__LINE__, cb.front(), current_front); + cb.pop_front(); + ++current_front; + cb.push_back(int(current_back)); + ++current_back; + } + } + } + + } // namespace +} // namespace sqlpp11 + +int main(int, char*[]) +{ + sqlpp::test_no_capacity(); + sqlpp::test_empty(); + sqlpp::test_push_back(); + sqlpp::test_pop_front(); + sqlpp::test_front(); + sqlpp::test_increase_capacity(); + sqlpp::test_reduce_capacity(); + sqlpp::test_reduce_capacity_to_size(); + + return 0; +}