| //===----------------------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // UNSUPPORTED: c++98, c++03 |
| |
| // <deque> |
| |
| // Test how deque manages the spare blocks it keeps. The exact values it tests |
| // for are not always important, but are sometimes needed to ensure the container |
| // resizes or shrinks at the correct time. |
| |
| #include <deque> |
| #include <iostream> |
| #include <memory> |
| #include <stack> |
| #include <queue> |
| |
| #include "min_allocator.h" |
| #include "rapid-cxx-test.hpp" |
| |
| template <class Adaptor> |
| struct ContainerAdaptor : public Adaptor { |
| using Adaptor::Adaptor; |
| typename Adaptor::container_type& GetContainer() { return Adaptor::c; } |
| }; |
| |
| template <class Deque> |
| static void print(const Deque& d) { |
| std::cout << d.size() |
| << " : __front_spare() == " << d.__front_spare() |
| << " : __back_spare() == " << d.__back_spare() |
| << " : __capacity() == " << d.__capacity() |
| << " : bytes allocated == " |
| << malloc_allocator_base::outstanding_bytes << '\n'; |
| } |
| |
| template <class T> |
| using Deque = std::deque<T, malloc_allocator<T> >; |
| |
| template <class T> |
| using BlockSize = std::__deque_block_size<T, std::ptrdiff_t>; |
| |
| struct LargeT { |
| LargeT() = default; |
| char buff[256] = {}; |
| }; |
| static_assert(BlockSize<LargeT>::value == 16, ""); |
| |
| const auto& AllocBytes = malloc_allocator_base::outstanding_bytes; |
| |
| template <class DT> |
| struct PrintOnFailure { |
| explicit PrintOnFailure(DT const& d) : d(&d) {} |
| |
| ~PrintOnFailure() { |
| if (::rapid_cxx_test::get_reporter().current_failure().type |
| != ::rapid_cxx_test::failure_type::none) { |
| print(*d); |
| } |
| } |
| private: |
| const DT* d; |
| |
| PrintOnFailure(PrintOnFailure const&) = delete; |
| }; |
| |
| TEST_SUITE(deque_spare_tests) |
| |
| TEST_CASE(push_back) { |
| const auto BS = BlockSize<LargeT>::value; |
| std::unique_ptr<Deque<LargeT>> dp(new Deque<LargeT>); |
| auto& d = *dp; |
| PrintOnFailure<Deque<LargeT>> on_fail(d); |
| |
| // Test nothing is allocated after default construction. |
| { |
| TEST_REQUIRE(d.size() == 0); |
| TEST_REQUIRE(d.__capacity() == 0); |
| TEST_REQUIRE(d.__block_count() == 0); |
| } |
| // First push back allocates one block. |
| d.push_back({}); |
| { |
| TEST_REQUIRE(d.size() == 1); |
| TEST_REQUIRE(d.__front_spare() == 0); |
| TEST_REQUIRE(d.__back_spare() == 14); |
| TEST_REQUIRE(d.__back_spare_blocks() == 0); |
| TEST_REQUIRE(d.__capacity() == BS - 1); |
| TEST_REQUIRE(d.__block_count() == 1); |
| } |
| |
| d.push_back({}); |
| { |
| TEST_REQUIRE(d.size() == 2); |
| TEST_REQUIRE(d.__front_spare() == 0); |
| TEST_REQUIRE(d.__back_spare() == 13); |
| TEST_REQUIRE(d.__back_spare_blocks() == 0); |
| } |
| // Push back until we need a new block. |
| for (int RemainingCap = d.__capacity() - d.size(); RemainingCap >= 0; --RemainingCap) |
| d.push_back({}); |
| { |
| TEST_REQUIRE(d.__block_count() == 2); |
| TEST_REQUIRE(d.__front_spare_blocks() == 0); |
| TEST_REQUIRE(d.__back_spare_blocks() == 0); |
| TEST_REQUIRE(d.__back_spare() == 15); |
| } |
| |
| // Remove the only element in the new block. Test that we keep the empty |
| // block as a spare. |
| d.pop_back(); |
| { |
| TEST_REQUIRE(d.__block_count() == 2); |
| TEST_REQUIRE(d.__front_spare_blocks() == 0); |
| TEST_REQUIRE(d.__back_spare_blocks() == 1); |
| TEST_REQUIRE(d.__back_spare() == 16); |
| } |
| |
| // Pop back again, keep the spare. |
| d.pop_back(); |
| { |
| TEST_REQUIRE(d.__block_count() == 2); |
| TEST_REQUIRE(d.__front_spare() == 0); |
| TEST_REQUIRE(d.__back_spare() == 17); |
| TEST_REQUIRE(d.__back_spare_blocks() == 1); |
| } |
| |
| dp.reset(); |
| TEST_REQUIRE(AllocBytes == 0); |
| } |
| |
| TEST_CASE(push_front) { |
| std::unique_ptr<Deque<LargeT>> dp(new Deque<LargeT>); |
| auto& d = *dp; |
| PrintOnFailure<Deque<LargeT>> on_fail(d); |
| |
| // Test nothing is allocated after default construction. |
| { |
| TEST_REQUIRE(d.size() == 0); |
| TEST_REQUIRE(d.__capacity() == 0); |
| TEST_REQUIRE(d.__block_count() == 0); |
| } |
| // First push front allocates one block, and we start the sequence in the |
| // middle. |
| d.push_front({}); |
| { |
| TEST_REQUIRE(d.size() == 1); |
| TEST_REQUIRE(d.__front_spare() == 7); |
| TEST_REQUIRE(d.__back_spare() == 7); |
| TEST_REQUIRE(d.__front_spare_blocks() == 0); |
| TEST_REQUIRE(d.__back_spare_blocks() == 0); |
| TEST_REQUIRE(d.__block_count() == 1); |
| } |
| |
| d.push_front({}); |
| { |
| TEST_REQUIRE(d.size() == 2); |
| TEST_REQUIRE(d.__front_spare() == 6); |
| TEST_REQUIRE(d.__back_spare() == 7); |
| TEST_REQUIRE(d.__front_spare_blocks() == 0); |
| TEST_REQUIRE(d.__back_spare_blocks() == 0); |
| } |
| // Push front until we need a new block. |
| for (int RemainingCap = d.__front_spare(); RemainingCap >= 0; --RemainingCap) |
| d.push_front({}); |
| { |
| TEST_REQUIRE(d.__block_count() == 2); |
| TEST_REQUIRE(d.__front_spare() == 15); |
| TEST_REQUIRE(d.__back_spare() == 7); |
| TEST_REQUIRE(d.__front_spare_blocks() == 0); |
| TEST_REQUIRE(d.__back_spare_blocks() == 0); |
| } |
| |
| // Remove the only element in the new block. Test that we keep the empty |
| // block as a spare. |
| d.pop_front(); |
| { |
| TEST_REQUIRE(d.__block_count() == 2); |
| TEST_REQUIRE(d.__front_spare_blocks() == 1); |
| TEST_REQUIRE(d.__back_spare_blocks() == 0); |
| TEST_REQUIRE(d.__back_spare() == 7); |
| } |
| |
| // Pop back again, keep the spare. |
| d.pop_front(); |
| { |
| TEST_REQUIRE(d.__block_count() == 2); |
| TEST_REQUIRE(d.__front_spare_blocks() == 1); |
| TEST_REQUIRE(d.__back_spare() == 7); |
| } |
| |
| dp.reset(); |
| TEST_REQUIRE(AllocBytes == 0); |
| } |
| |
| TEST_CASE(std_queue) { |
| using D = Deque<LargeT>; |
| using Queue = std::queue<LargeT, D>; |
| ContainerAdaptor<Queue> CA; |
| const D& d = CA.GetContainer(); |
| Queue &q = CA; |
| PrintOnFailure<Deque<LargeT>> on_fail(d); |
| |
| while (d.__block_count() < 4) |
| q.push({}); |
| { |
| TEST_REQUIRE(d.__block_count() == 4); |
| TEST_REQUIRE(d.__front_spare() == 0); |
| TEST_REQUIRE(d.__back_spare() == 15); |
| TEST_REQUIRE(d.__back_spare_blocks() == 0); |
| } |
| while (d.__back_spare()) { |
| q.push({}); |
| } |
| { |
| TEST_REQUIRE(d.__block_count() == 4); |
| TEST_REQUIRE(d.__front_spare() == 0); |
| TEST_REQUIRE(d.__back_spare() == 0); |
| } |
| q.pop(); |
| { |
| TEST_REQUIRE(d.__block_count() == 4); |
| TEST_REQUIRE(d.__front_spare() == 1); |
| TEST_REQUIRE(d.__back_spare() == 0); |
| } |
| |
| // Pop until we create a spare block at the front. |
| while (d.__front_spare() <= 15) |
| q.pop(); |
| |
| { |
| TEST_REQUIRE(d.__block_count() == 4); |
| TEST_REQUIRE(d.__front_spare_blocks() == 1); |
| TEST_REQUIRE(d.__front_spare() == 16); |
| TEST_REQUIRE(d.__back_spare() == 0); |
| } |
| |
| // Push at the end -- should re-use new spare block at front |
| q.push({}); |
| |
| { |
| TEST_REQUIRE(d.__block_count() == 4); |
| TEST_REQUIRE(d.__front_spare_blocks() == 0); |
| TEST_REQUIRE(d.__front_spare() == 0); |
| TEST_REQUIRE(d.__back_spare() == 15); |
| } |
| while (!q.empty()) { |
| q.pop(); |
| TEST_REQUIRE(d.__front_spare_blocks() + d.__back_spare_blocks() <= 2); |
| } |
| |
| // The empty state has two blocks |
| { |
| TEST_REQUIRE(d.__front_spare() == 16); |
| TEST_REQUIRE(d.__back_spare() == 15); |
| TEST_REQUIRE(d.__capacity() == 31); |
| } |
| } |
| |
| TEST_CASE(pop_front_push_back) { |
| Deque<char> d(32 * 1024, 'a'); |
| bool take_from_front = true; |
| while (d.size() > 0) { |
| if (take_from_front) { |
| d.pop_front(); |
| take_from_front = false; |
| } else { |
| d.pop_back(); |
| take_from_front = true; |
| } |
| if (d.size() % 1000 == 0 || d.size() < 50) { |
| print(d); |
| } |
| } |
| } |
| |
| TEST_SUITE_END() |
| |
| |