gfe-relnote: (n/a) Add QuicCircularDeque to third_party/quic/core. Code not used yet.
The implementation is based on Chromium's circular_deque(http://shortn/_RLFU0Zrxza), the backing type of Chromium's QuicDeque. The new class can be used by all existing uses of QuicDeque except the one in PacketNumberQueue.
PiperOrigin-RevId: 278627694
Change-Id: If52cd23cd38c173b14ccefad57b86c4bcf405392
diff --git a/quic/core/quic_circular_deque.h b/quic/core/quic_circular_deque.h
new file mode 100644
index 0000000..398448f
--- /dev/null
+++ b/quic/core/quic_circular_deque.h
@@ -0,0 +1,687 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QUIC_CIRCULAR_DEQUE_H_
+#define QUICHE_QUIC_CORE_QUIC_CIRCULAR_DEQUE_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <ostream>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+
+namespace quic {
+
+// QuicCircularDeque is a STL-style container that is similar to std deque in
+// API and std::vector in capacity management. The goal is to optimize a common
+// QUIC use case where we keep adding new elements to the end and removing old
+// elements from the beginning, under such scenarios, if the container's size()
+// remain relatively stable, QuicCircularDeque requires little to no memory
+// allocations or deallocations.
+//
+// The implementation, as the name suggests, uses a flat circular buffer to hold
+// all elements. At any point in time, either
+// a) All elements are placed in a contiguous portion of this buffer, like a
+// c-array, or
+// b) Elements are phycially divided into two parts: the first part occupies the
+// end of the buffer and the second part occupies the beginning of the
+// buffer.
+//
+// Currently, elements can only be pushed or poped from either ends, it can't be
+// inserted or erased in the middle.
+//
+// TODO(wub): Make memory grow/shrink strategies customizable.
+template <typename T,
+ size_t MinCapacityIncrement = 3,
+ typename Allocator = std::allocator<T>>
+class QUIC_NO_EXPORT QuicCircularDeque {
+ using AllocatorTraits = std::allocator_traits<Allocator>;
+
+ // Pointee is either T or const T.
+ template <typename Pointee>
+ class QUIC_NO_EXPORT basic_iterator {
+ using size_type = typename AllocatorTraits::size_type;
+
+ public:
+ using iterator_category = std::random_access_iterator_tag;
+ using value_type = typename AllocatorTraits::value_type;
+ using difference_type = typename AllocatorTraits::difference_type;
+ using pointer = Pointee*;
+ using reference = Pointee&;
+
+ basic_iterator() = default;
+
+ // A copy constructor if Pointee is T.
+ // A conversion from iterator to const_iterator if Pointee is const T.
+ basic_iterator(
+ const basic_iterator<value_type>& it) // NOLINT(runtime/explicit)
+ : deque_(it.deque_), index_(it.index_) {}
+
+ reference operator*() const { return *deque_->index_to_address(index_); }
+ pointer operator->() const { return deque_->index_to_address(index_); }
+ reference operator[](difference_type i) { return *(*this + i); }
+
+ basic_iterator& operator++() {
+ Increment();
+ return *this;
+ }
+
+ basic_iterator operator++(int) {
+ basic_iterator result = *this;
+ Increment();
+ return result;
+ }
+
+ basic_iterator operator--() {
+ Decrement();
+ return *this;
+ }
+
+ basic_iterator operator--(int) {
+ basic_iterator result = *this;
+ Decrement();
+ return result;
+ }
+
+ friend basic_iterator operator+(const basic_iterator& it,
+ difference_type delta) {
+ basic_iterator result = it;
+ result.IncrementBy(delta);
+ return result;
+ }
+
+ basic_iterator& operator+=(difference_type delta) {
+ IncrementBy(delta);
+ return *this;
+ }
+
+ friend basic_iterator operator-(const basic_iterator& it,
+ difference_type delta) {
+ basic_iterator result = it;
+ result.IncrementBy(-delta);
+ return result;
+ }
+
+ basic_iterator& operator-=(difference_type delta) {
+ IncrementBy(-delta);
+ return *this;
+ }
+
+ friend difference_type operator-(const basic_iterator& lhs,
+ const basic_iterator& rhs) {
+ return lhs.ExternalPosition() - rhs.ExternalPosition();
+ }
+
+ friend bool operator==(const basic_iterator& lhs,
+ const basic_iterator& rhs) {
+ return lhs.index_ == rhs.index_;
+ }
+
+ friend bool operator!=(const basic_iterator& lhs,
+ const basic_iterator& rhs) {
+ return !(lhs == rhs);
+ }
+
+ friend bool operator<(const basic_iterator& lhs,
+ const basic_iterator& rhs) {
+ return lhs.ExternalPosition() < rhs.ExternalPosition();
+ }
+
+ friend bool operator<=(const basic_iterator& lhs,
+ const basic_iterator& rhs) {
+ return !(lhs > rhs);
+ }
+
+ friend bool operator>(const basic_iterator& lhs,
+ const basic_iterator& rhs) {
+ return lhs.ExternalPosition() > rhs.ExternalPosition();
+ }
+
+ friend bool operator>=(const basic_iterator& lhs,
+ const basic_iterator& rhs) {
+ return !(lhs < rhs);
+ }
+
+ private:
+ basic_iterator(const QuicCircularDeque* deque, size_type index)
+ : deque_(deque), index_(index) {}
+
+ void Increment() { index_ = deque_->index_next(index_); }
+
+ void Decrement() { index_ = deque_->index_prev(index_); }
+
+ void IncrementBy(difference_type delta) {
+ if (delta == 0) {
+ return;
+ }
+
+ index_ = (deque_->begin_ + ExternalPosition() + delta) %
+ deque_->data_capacity();
+ }
+
+ size_type ExternalPosition() const {
+ if (index_ >= deque_->begin_) {
+ return index_ - deque_->begin_;
+ }
+ return index_ + deque_->data_capacity() - deque_->begin_;
+ }
+
+ friend class QuicCircularDeque;
+ const QuicCircularDeque* deque_ = nullptr;
+ size_type index_ = 0;
+ };
+
+ public:
+ using allocator_type = typename AllocatorTraits::allocator_type;
+ using value_type = typename AllocatorTraits::value_type;
+ using size_type = typename AllocatorTraits::size_type;
+ using difference_type = typename AllocatorTraits::difference_type;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using pointer = typename AllocatorTraits::pointer;
+ using const_pointer = typename AllocatorTraits::const_pointer;
+ using iterator = basic_iterator<T>;
+ using const_iterator = basic_iterator<const T>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ QuicCircularDeque() : QuicCircularDeque(allocator_type()) {}
+ explicit QuicCircularDeque(const allocator_type& alloc)
+ : allocator_and_data_(alloc) {}
+
+ QuicCircularDeque(size_type count,
+ const T& value,
+ const Allocator& alloc = allocator_type())
+ : allocator_and_data_(alloc) {
+ resize(count, value);
+ }
+
+ explicit QuicCircularDeque(size_type count,
+ const Allocator& alloc = allocator_type())
+ : allocator_and_data_(alloc) {
+ resize(count);
+ }
+
+ template <class InputIt,
+ typename = std::enable_if_t<std::is_base_of_v<
+ std::input_iterator_tag,
+ typename std::iterator_traits<InputIt>::iterator_category>>>
+ QuicCircularDeque(InputIt first,
+ InputIt last,
+ const Allocator& alloc = allocator_type())
+ : allocator_and_data_(alloc) {
+ AssignRange(first, last);
+ }
+
+ QuicCircularDeque(const QuicCircularDeque& other)
+ : QuicCircularDeque(
+ other,
+ AllocatorTraits::select_on_container_copy_construction(
+ other.allocator_and_data_.allocator())) {}
+
+ QuicCircularDeque(const QuicCircularDeque& other, const allocator_type& alloc)
+ : allocator_and_data_(alloc) {
+ assign(other.begin(), other.end());
+ }
+
+ QuicCircularDeque(QuicCircularDeque&& other)
+ : begin_(other.begin_),
+ end_(other.end_),
+ allocator_and_data_(std::move(other.allocator_and_data_)) {
+ other.begin_ = other.end_ = 0;
+ other.allocator_and_data_.data = nullptr;
+ other.allocator_and_data_.data_capacity = 0;
+ }
+
+ QuicCircularDeque(QuicCircularDeque&& other, const allocator_type& alloc)
+ : allocator_and_data_(alloc) {
+ MoveRetainAllocator(std::move(other));
+ }
+
+ QuicCircularDeque(std::initializer_list<T> init,
+ const allocator_type& alloc = allocator_type())
+ : QuicCircularDeque(init.begin(), init.end(), alloc) {}
+
+ QuicCircularDeque& operator=(const QuicCircularDeque& other) {
+ if (this == &other) {
+ return *this;
+ }
+ if (AllocatorTraits::propagate_on_container_copy_assignment::value &&
+ (allocator_and_data_.allocator() !=
+ other.allocator_and_data_.allocator())) {
+ // Destroy all current elements and blocks with the current allocator,
+ // before switching this to use the allocator propagated from "other".
+ DestroyAndDeallocateAll();
+ begin_ = end_ = 0;
+ allocator_and_data_ =
+ AllocatorAndData(other.allocator_and_data_.allocator());
+ }
+ assign(other.begin(), other.end());
+ return *this;
+ }
+
+ QuicCircularDeque& operator=(QuicCircularDeque&& other) {
+ if (this == &other) {
+ return *this;
+ }
+ if (AllocatorTraits::propagate_on_container_move_assignment::value) {
+ // Take over the storage of "other", along with its allocator.
+ this->~QuicCircularDeque();
+ new (this) QuicCircularDeque(std::move(other));
+ } else {
+ MoveRetainAllocator(std::move(other));
+ }
+ return *this;
+ }
+
+ ~QuicCircularDeque() { DestroyAndDeallocateAll(); }
+
+ void assign(size_type count, const T& value) {
+ ClearRetainCapacity();
+ reserve(count);
+ for (size_t i = 0; i < count; ++i) {
+ emplace_back(value);
+ }
+ }
+
+ template <class InputIt,
+ typename = std::enable_if_t<std::is_base_of_v<
+ std::input_iterator_tag,
+ typename std::iterator_traits<InputIt>::iterator_category>>>
+ void assign(InputIt first, InputIt last) {
+ AssignRange(first, last);
+ }
+
+ void assign(std::initializer_list<T> ilist) {
+ assign(ilist.begin(), ilist.end());
+ }
+
+ reference at(size_type pos) {
+ DCHECK(pos < size()) << "pos:" << pos << ", size():" << size();
+ size_type index = begin_ + pos;
+ if (index < data_capacity()) {
+ return *index_to_address(index);
+ }
+ return *index_to_address(index - data_capacity());
+ }
+
+ const_reference at(size_type pos) const {
+ return const_cast<QuicCircularDeque*>(this)->at(pos);
+ }
+
+ reference operator[](size_type pos) { return at(pos); }
+
+ const_reference operator[](size_type pos) const { return at(pos); }
+
+ reference front() {
+ DCHECK(!empty());
+ return *index_to_address(begin_);
+ }
+
+ const_reference front() const {
+ return const_cast<QuicCircularDeque*>(this)->front();
+ }
+
+ reference back() {
+ DCHECK(!empty());
+ return *(index_to_address(end_ == 0 ? data_capacity() - 1 : end_ - 1));
+ }
+
+ const_reference back() const {
+ return const_cast<QuicCircularDeque*>(this)->back();
+ }
+
+ iterator begin() { return iterator(this, begin_); }
+ const_iterator begin() const { return const_iterator(this, begin_); }
+ const_iterator cbegin() const { return const_iterator(this, begin_); }
+
+ iterator end() { return iterator(this, end_); }
+ const_iterator end() const { return const_iterator(this, end_); }
+ const_iterator cend() const { return const_iterator(this, end_); }
+
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(end());
+ }
+ const_reverse_iterator crbegin() const { return rbegin(); }
+
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator(begin());
+ }
+ const_reverse_iterator crend() const { return rend(); }
+
+ size_type capacity() const {
+ return data_capacity() == 0 ? 0 : data_capacity() - 1;
+ }
+
+ void reserve(size_type new_cap) {
+ if (new_cap > capacity()) {
+ Relocate(new_cap);
+ }
+ }
+
+ // Remove all elements. Leave capacity unchanged.
+ void clear() { ClearRetainCapacity(); }
+
+ bool empty() const { return begin_ == end_; }
+
+ size_type size() const {
+ if (begin_ <= end_) {
+ return end_ - begin_;
+ }
+ return data_capacity() + end_ - begin_;
+ }
+
+ void resize(size_type count) { ResizeInternal(count); }
+
+ void resize(size_type count, const value_type& value) {
+ ResizeInternal(count, value);
+ }
+
+ void push_front(const T& value) { emplace_front(value); }
+ void push_front(T&& value) { emplace_front(std::move(value)); }
+
+ template <class... Args>
+ reference emplace_front(Args&&... args) {
+ MaybeExpandCapacity(1);
+ begin_ = index_prev(begin_);
+ new (index_to_address(begin_)) T(std::forward<Args>(args)...);
+ return front();
+ }
+
+ void push_back(const T& value) { emplace_back(value); }
+ void push_back(T&& value) { emplace_back(std::move(value)); }
+
+ template <class... Args>
+ reference emplace_back(Args&&... args) {
+ MaybeExpandCapacity(1);
+ new (index_to_address(end_)) T(std::forward<Args>(args)...);
+ end_ = index_next(end_);
+ return back();
+ }
+
+ void pop_front() {
+ DCHECK(!empty());
+ DestroyByIndex(begin_);
+ begin_ = index_next(begin_);
+ MaybeShrinkCapacity();
+ }
+
+ void pop_back() {
+ DCHECK(!empty());
+ end_ = index_prev(end_);
+ DestroyByIndex(end_);
+ MaybeShrinkCapacity();
+ }
+
+ void swap(QuicCircularDeque& other) {
+ using std::swap;
+ swap(begin_, other.begin_);
+ swap(end_, other.end_);
+
+ if (AllocatorTraits::propagate_on_container_swap::value) {
+ swap(allocator_and_data_, other.allocator_and_data_);
+ } else {
+ // When propagate_on_container_swap is false, it is undefined behavior, by
+ // c++ standard, to swap between two AllocatorAwareContainer(s) with
+ // unequal allocators.
+ DCHECK(get_allocator() == other.get_allocator())
+ << "Undefined swap behavior";
+ swap(allocator_and_data_.data, other.allocator_and_data_.data);
+ swap(allocator_and_data_.data_capacity,
+ other.allocator_and_data_.data_capacity);
+ }
+ }
+
+ friend void swap(QuicCircularDeque& lhs, QuicCircularDeque& rhs) {
+ lhs.swap(rhs);
+ }
+
+ allocator_type get_allocator() const {
+ return allocator_and_data_.allocator();
+ }
+
+ friend bool operator==(const QuicCircularDeque& lhs,
+ const QuicCircularDeque& rhs) {
+ return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+ }
+
+ friend bool operator!=(const QuicCircularDeque& lhs,
+ const QuicCircularDeque& rhs) {
+ return !(lhs == rhs);
+ }
+
+ friend QUIC_NO_EXPORT std::ostream& operator<<(std::ostream& os,
+ const QuicCircularDeque& dq) {
+ os << "{";
+ for (size_type pos = 0; pos != dq.size(); ++pos) {
+ if (pos != 0) {
+ os << ",";
+ }
+ os << " " << dq[pos];
+ }
+ os << " }";
+ return os;
+ }
+
+ private:
+ void MoveRetainAllocator(QuicCircularDeque&& other) {
+ if (get_allocator() == other.get_allocator()) {
+ // Take over the storage of "other", with which we share an allocator.
+ DestroyAndDeallocateAll();
+
+ begin_ = other.begin_;
+ end_ = other.end_;
+ allocator_and_data_.data = other.allocator_and_data_.data;
+ allocator_and_data_.data_capacity =
+ other.allocator_and_data_.data_capacity;
+
+ other.begin_ = other.end_ = 0;
+ other.allocator_and_data_.data = nullptr;
+ other.allocator_and_data_.data_capacity = 0;
+ } else {
+ // We cannot take over of the storage from "other", since it has a
+ // different allocator; we're stuck move-assigning elements individually.
+ ClearRetainCapacity();
+ for (auto& elem : other) {
+ push_back(std::move(elem));
+ }
+ other.clear();
+ }
+ }
+
+ template <typename InputIt,
+ typename = std::enable_if_t<std::is_base_of_v<
+ std::input_iterator_tag,
+ typename std::iterator_traits<InputIt>::iterator_category>>>
+ void AssignRange(InputIt first, InputIt last) {
+ ClearRetainCapacity();
+ if constexpr (std::is_base_of_v<std::random_access_iterator_tag,
+ typename std::iterator_traits<
+ InputIt>::iterator_category>) {
+ reserve(std::distance(first, last));
+ }
+ for (; first != last; ++first) {
+ emplace_back(*first);
+ }
+ }
+
+ // WARNING: begin_, end_ and allocator_and_data_ are not modified.
+ void DestroyAndDeallocateAll() {
+ DestroyRange(begin_, end_);
+
+ if (data_capacity() > 0) {
+ DCHECK_NE(nullptr, allocator_and_data_.data);
+ AllocatorTraits::deallocate(allocator_and_data_.allocator(),
+ allocator_and_data_.data, data_capacity());
+ }
+ }
+
+ void ClearRetainCapacity() {
+ DestroyRange(begin_, end_);
+ begin_ = end_ = 0;
+ }
+
+ void MaybeShrinkCapacity() {
+ // TODO(wub): Implement a storage policy that actually shrinks.
+ }
+
+ void MaybeExpandCapacity(size_t num_additional_elements) {
+ size_t new_size = size() + num_additional_elements;
+ if (capacity() >= new_size) {
+ return;
+ }
+
+ // The minimum amount of additional capacity to grow.
+ size_t min_additional_capacity =
+ std::max(MinCapacityIncrement, capacity() / 4);
+ size_t new_capacity =
+ std::max(new_size, capacity() + min_additional_capacity);
+
+ Relocate(new_capacity);
+ }
+
+ void Relocate(size_t new_capacity) {
+ const size_t num_elements = size();
+ DCHECK_GT(new_capacity, num_elements)
+ << "new_capacity:" << new_capacity << ", num_elements:" << num_elements;
+
+ size_t new_data_capacity = new_capacity + 1;
+ pointer new_data = AllocatorTraits::allocate(
+ allocator_and_data_.allocator(), new_data_capacity);
+
+ if (begin_ <= end_) {
+ // Not wrapped.
+ RelocateUnwrappedRange(begin_, end_, new_data);
+ } else {
+ // Wrapped.
+ const size_t num_elements_before_wrap = data_capacity() - begin_;
+ RelocateUnwrappedRange(begin_, data_capacity(), new_data);
+ RelocateUnwrappedRange(0, end_, new_data + num_elements_before_wrap);
+ }
+
+ if (data_capacity()) {
+ AllocatorTraits::deallocate(allocator_and_data_.allocator(),
+ allocator_and_data_.data, data_capacity());
+ }
+
+ allocator_and_data_.data = new_data;
+ allocator_and_data_.data_capacity = new_data_capacity;
+ begin_ = 0;
+ end_ = num_elements;
+ }
+
+ void RelocateUnwrappedRange(size_type begin,
+ size_type end,
+ pointer dest) const {
+ DCHECK_LE(begin, end) << "begin:" << begin << ", end:" << end;
+ if constexpr (std::is_trivially_copyable_v<T>) {
+ memcpy(dest, index_to_address(begin), sizeof(T) * (end - begin));
+ DestroyRange(begin, end);
+ } else {
+ pointer src = index_to_address(begin);
+ pointer src_end = index_to_address(end);
+ while (src != src_end) {
+ if constexpr (std::is_move_constructible_v<T>) {
+ new (dest) T(std::move(*src));
+ } else {
+ new (dest) T(*src);
+ }
+ DestroyByAddress(src);
+ ++dest;
+ ++src;
+ }
+ }
+ }
+
+ template <class... U>
+ void ResizeInternal(size_type count, U&&... u) {
+ if (count > size()) {
+ // Expanding.
+ MaybeExpandCapacity(count - size());
+ while (size() < count) {
+ emplace_back(std::forward<U>(u)...);
+ }
+ } else {
+ // Most likely shrinking. No-op if count == size().
+ size_type new_end = (begin_ + count) % data_capacity();
+ DestroyRange(new_end, end_);
+ end_ = new_end;
+
+ MaybeShrinkCapacity();
+ }
+ }
+
+ void DestroyRange(size_type begin, size_type end) const {
+ if constexpr (std::is_trivially_destructible_v<T>) {
+ return;
+ }
+ if (end >= begin) {
+ DestroyUnwrappedRange(begin, end);
+ } else {
+ DestroyUnwrappedRange(begin, data_capacity());
+ DestroyUnwrappedRange(0, end);
+ }
+ }
+
+ // Should only be called from DestroyRange.
+ void DestroyUnwrappedRange(size_type begin, size_type end) const {
+ DCHECK_LE(begin, end) << "begin:" << begin << ", end:" << end;
+ for (; begin != end; ++begin) {
+ DestroyByIndex(begin);
+ }
+ }
+
+ void DestroyByIndex(size_type index) const {
+ DestroyByAddress(index_to_address(index));
+ }
+
+ void DestroyByAddress(pointer address) const {
+ if constexpr (std::is_trivially_destructible_v<T>) {
+ return;
+ }
+ address->~T();
+ }
+
+ size_type data_capacity() const { return allocator_and_data_.data_capacity; }
+
+ pointer index_to_address(size_type index) const {
+ return allocator_and_data_.data + index;
+ }
+
+ size_type index_prev(size_type index) const {
+ return index == 0 ? data_capacity() - 1 : index - 1;
+ }
+
+ size_type index_next(size_type index) const {
+ return index == data_capacity() - 1 ? 0 : index + 1;
+ }
+
+ // Empty base-class optimization: bundle storage for our allocator together
+ // with the fields we had to store anyway, via inheriting from the allocator,
+ // so this allocator instance doesn't consume any storage when its type has no
+ // data members.
+ struct AllocatorAndData : private allocator_type {
+ explicit AllocatorAndData(const allocator_type& alloc)
+ : allocator_type(alloc) {}
+
+ const allocator_type& allocator() const { return *this; }
+ allocator_type& allocator() { return *this; }
+
+ pointer data = nullptr;
+ size_type data_capacity = 0;
+ };
+
+ size_type begin_ = 0;
+ size_type end_ = 0;
+ AllocatorAndData allocator_and_data_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_QUIC_CIRCULAR_DEQUE_H_
diff --git a/quic/core/quic_circular_deque_test.cc b/quic/core/quic_circular_deque_test.cc
new file mode 100644
index 0000000..19f8ca2
--- /dev/null
+++ b/quic/core/quic_circular_deque_test.cc
@@ -0,0 +1,757 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/quic_circular_deque.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <type_traits>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+using testing::ElementsAre;
+
+namespace quic {
+namespace test {
+
+template <typename T, template <typename> class BaseAllocator = std::allocator>
+class CountingAllocator : public BaseAllocator<T> {
+ typedef BaseAllocator<T> BaseType;
+
+ public:
+ using propagate_on_container_copy_assignment = std::true_type;
+ using propagate_on_container_move_assignment = std::true_type;
+ using propagate_on_container_swap = std::true_type;
+
+ T* allocate(std::size_t n) {
+ ++shared_counts_->allocate_count;
+ return BaseType::allocate(n);
+ }
+
+ void deallocate(T* ptr, std::size_t n) {
+ ++shared_counts_->deallocate_count;
+ return BaseType::deallocate(ptr, n);
+ }
+
+ size_t allocate_count() const { return shared_counts_->allocate_count; }
+
+ size_t deallocate_count() const { return shared_counts_->deallocate_count; }
+
+ friend bool operator==(const CountingAllocator& lhs,
+ const CountingAllocator& rhs) {
+ return lhs.shared_counts_ == rhs.shared_counts_;
+ }
+
+ friend bool operator!=(const CountingAllocator& lhs,
+ const CountingAllocator& rhs) {
+ return !(lhs == rhs);
+ }
+
+ private:
+ struct Counts {
+ size_t allocate_count = 0;
+ size_t deallocate_count = 0;
+ };
+
+ std::shared_ptr<Counts> shared_counts_ = std::make_shared<Counts>();
+};
+
+template <typename T,
+ typename propagate_on_copy_assignment,
+ typename propagate_on_move_assignment,
+ typename propagate_on_swap,
+ bool equality_result,
+ template <typename> class BaseAllocator = std::allocator>
+struct ConfigurableAllocator : public BaseAllocator<T> {
+ using propagate_on_container_copy_assignment = propagate_on_copy_assignment;
+ using propagate_on_container_move_assignment = propagate_on_move_assignment;
+ using propagate_on_container_swap = propagate_on_swap;
+
+ friend bool operator==(const ConfigurableAllocator& /*lhs*/,
+ const ConfigurableAllocator& /*rhs*/) {
+ return equality_result;
+ }
+
+ friend bool operator!=(const ConfigurableAllocator& lhs,
+ const ConfigurableAllocator& rhs) {
+ return !(lhs == rhs);
+ }
+};
+
+// [1, 2, 3, 4] ==> [4, 1, 2, 3]
+template <typename Deque>
+void ShiftRight(Deque* dq, bool emplace) {
+ auto back = *(&dq->back());
+ dq->pop_back();
+ if (emplace) {
+ dq->emplace_front(back);
+ } else {
+ dq->push_front(back);
+ }
+}
+
+// [1, 2, 3, 4] ==> [2, 3, 4, 1]
+template <typename Deque>
+void ShiftLeft(Deque* dq, bool emplace) {
+ auto front = *(&dq->front());
+ dq->pop_front();
+ if (emplace) {
+ dq->emplace_back(front);
+ } else {
+ dq->push_back(front);
+ }
+}
+
+TEST(QuicCircularDeque, Empty) {
+ QuicCircularDeque<int> dq;
+ EXPECT_TRUE(dq.empty());
+ EXPECT_EQ(0u, dq.size());
+ dq.clear();
+ dq.push_back(10);
+ EXPECT_FALSE(dq.empty());
+ EXPECT_EQ(1u, dq.size());
+ EXPECT_EQ(10, dq.front());
+ EXPECT_EQ(10, dq.back());
+ dq.pop_front();
+ EXPECT_TRUE(dq.empty());
+ EXPECT_EQ(0u, dq.size());
+
+ EXPECT_DEBUG_DEATH(dq.front(), "");
+ EXPECT_DEBUG_DEATH(dq.back(), "");
+ EXPECT_DEBUG_DEATH(dq.at(0), "");
+ EXPECT_DEBUG_DEATH(dq[0], "");
+}
+
+TEST(QuicCircularDeque, Constructor) {
+ QuicCircularDeque<int> dq;
+ EXPECT_TRUE(dq.empty());
+
+ std::allocator<int> alloc;
+ QuicCircularDeque<int> dq1(alloc);
+ EXPECT_TRUE(dq1.empty());
+
+ QuicCircularDeque<int> dq2(8, 100, alloc);
+ EXPECT_THAT(dq2, ElementsAre(100, 100, 100, 100, 100, 100, 100, 100));
+
+ QuicCircularDeque<int> dq3(5, alloc);
+ EXPECT_THAT(dq3, ElementsAre(0, 0, 0, 0, 0));
+
+ QuicCircularDeque<int> dq4_rand_iter(dq3.begin(), dq3.end(), alloc);
+ EXPECT_THAT(dq4_rand_iter, ElementsAre(0, 0, 0, 0, 0));
+ EXPECT_EQ(dq4_rand_iter, dq3);
+
+ std::list<int> dq4_src = {4, 4, 4, 4};
+ QuicCircularDeque<int> dq4_bidi_iter(dq4_src.begin(), dq4_src.end());
+ EXPECT_THAT(dq4_bidi_iter, ElementsAre(4, 4, 4, 4));
+
+ QuicCircularDeque<int> dq5(dq4_bidi_iter);
+ EXPECT_THAT(dq5, ElementsAre(4, 4, 4, 4));
+ EXPECT_EQ(dq5, dq4_bidi_iter);
+
+ QuicCircularDeque<int> dq6(dq5, alloc);
+ EXPECT_THAT(dq6, ElementsAre(4, 4, 4, 4));
+ EXPECT_EQ(dq6, dq5);
+
+ QuicCircularDeque<int> dq7(std::move(*&dq6));
+ EXPECT_THAT(dq7, ElementsAre(4, 4, 4, 4));
+ EXPECT_TRUE(dq6.empty());
+
+ QuicCircularDeque<int> dq8_equal_allocator(std::move(*&dq7), alloc);
+ EXPECT_THAT(dq8_equal_allocator, ElementsAre(4, 4, 4, 4));
+ EXPECT_TRUE(dq7.empty());
+
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq8_temp = {5, 6, 7, 8, 9};
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq8_unequal_allocator(
+ std::move(*&dq8_temp), CountingAllocator<int>());
+ EXPECT_THAT(dq8_unequal_allocator, ElementsAre(5, 6, 7, 8, 9));
+ EXPECT_TRUE(dq8_temp.empty());
+
+ QuicCircularDeque<int> dq9({3, 4, 5, 6, 7}, alloc);
+ EXPECT_THAT(dq9, ElementsAre(3, 4, 5, 6, 7));
+}
+
+TEST(QuicCircularDeque, Assign) {
+ // assign()
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq;
+ dq.assign(7, 1);
+ EXPECT_THAT(dq, ElementsAre(1, 1, 1, 1, 1, 1, 1));
+ EXPECT_EQ(1u, dq.get_allocator().allocate_count());
+
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq2;
+ dq2.assign(dq.begin(), dq.end());
+ EXPECT_THAT(dq2, ElementsAre(1, 1, 1, 1, 1, 1, 1));
+ EXPECT_EQ(1u, dq2.get_allocator().allocate_count());
+ EXPECT_TRUE(std::equal(dq.begin(), dq.end(), dq2.begin(), dq2.end()));
+
+ dq2.assign({2, 2, 2, 2, 2, 2});
+ EXPECT_THAT(dq2, ElementsAre(2, 2, 2, 2, 2, 2));
+
+ // Assign from a non random access iterator.
+ std::list<int> dq3_src = {3, 3, 3, 3, 3};
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq3;
+ dq3.assign(dq3_src.begin(), dq3_src.end());
+ EXPECT_THAT(dq3, ElementsAre(3, 3, 3, 3, 3));
+ EXPECT_LT(1u, dq3.get_allocator().allocate_count());
+
+ // Copy assignment
+ dq3 = dq3;
+ EXPECT_THAT(dq3, ElementsAre(3, 3, 3, 3, 3));
+
+ QuicCircularDeque<
+ int, 3,
+ ConfigurableAllocator<int,
+ /*propagate_on_copy_assignment=*/std::true_type,
+ /*propagate_on_move_assignment=*/std::true_type,
+ /*propagate_on_swap=*/std::true_type,
+ /*equality_result=*/false>>
+ dq4, dq5;
+ dq4.assign(dq3.begin(), dq3.end());
+ dq5 = dq4;
+ EXPECT_THAT(dq5, ElementsAre(3, 3, 3, 3, 3));
+
+ QuicCircularDeque<
+ int, 3,
+ ConfigurableAllocator<int,
+ /*propagate_on_copy_assignment=*/std::false_type,
+ /*propagate_on_move_assignment=*/std::true_type,
+ /*propagate_on_swap=*/std::true_type,
+ /*equality_result=*/true>>
+ dq6, dq7;
+ dq6.assign(dq3.begin(), dq3.end());
+ dq7 = dq6;
+ EXPECT_THAT(dq7, ElementsAre(3, 3, 3, 3, 3));
+
+ // Move assignment
+ dq3 = std::move(*&dq3);
+ EXPECT_THAT(dq3, ElementsAre(3, 3, 3, 3, 3));
+
+ ASSERT_TRUE(decltype(
+ dq3.get_allocator())::propagate_on_container_move_assignment::value);
+ decltype(dq3) dq8;
+ dq8 = std::move(*&dq3);
+ EXPECT_THAT(dq8, ElementsAre(3, 3, 3, 3, 3));
+ EXPECT_TRUE(dq3.empty());
+
+ QuicCircularDeque<
+ int, 3,
+ ConfigurableAllocator<int,
+ /*propagate_on_copy_assignment=*/std::true_type,
+ /*propagate_on_move_assignment=*/std::false_type,
+ /*propagate_on_swap=*/std::true_type,
+ /*equality_result=*/true>>
+ dq9, dq10;
+ dq9.assign(dq8.begin(), dq8.end());
+ dq10.assign(dq2.begin(), dq2.end());
+ dq9 = std::move(*&dq10);
+ EXPECT_THAT(dq9, ElementsAre(2, 2, 2, 2, 2, 2));
+ EXPECT_TRUE(dq10.empty());
+
+ QuicCircularDeque<
+ int, 3,
+ ConfigurableAllocator<int,
+ /*propagate_on_copy_assignment=*/std::true_type,
+ /*propagate_on_move_assignment=*/std::false_type,
+ /*propagate_on_swap=*/std::true_type,
+ /*equality_result=*/false>>
+ dq11, dq12;
+ dq11.assign(dq8.begin(), dq8.end());
+ dq12.assign(dq2.begin(), dq2.end());
+ dq11 = std::move(*&dq12);
+ EXPECT_THAT(dq11, ElementsAre(2, 2, 2, 2, 2, 2));
+ EXPECT_TRUE(dq12.empty());
+}
+
+TEST(QuicCircularDeque, Access) {
+ // at()
+ // operator[]
+ // front()
+ // back()
+
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq;
+ dq.push_back(10);
+ EXPECT_EQ(dq.front(), 10);
+ EXPECT_EQ(dq.back(), 10);
+ EXPECT_EQ(dq.at(0), 10);
+ EXPECT_EQ(dq[0], 10);
+ dq.front() = 12;
+ EXPECT_EQ(dq.front(), 12);
+ EXPECT_EQ(dq.back(), 12);
+ EXPECT_EQ(dq.at(0), 12);
+ EXPECT_EQ(dq[0], 12);
+
+ const auto& dqref = dq;
+ EXPECT_EQ(dqref.front(), 12);
+ EXPECT_EQ(dqref.back(), 12);
+ EXPECT_EQ(dqref.at(0), 12);
+ EXPECT_EQ(dqref[0], 12);
+
+ dq.pop_front();
+ EXPECT_TRUE(dqref.empty());
+
+ // Push to capacity.
+ dq.push_back(15);
+ dq.push_front(5);
+ dq.push_back(25);
+ EXPECT_EQ(dq.size(), dq.capacity());
+ EXPECT_THAT(dq, ElementsAre(5, 15, 25));
+ EXPECT_LT(&dq.front(), &dq.back());
+ EXPECT_EQ(dq.front(), 5);
+ EXPECT_EQ(dq.back(), 25);
+ EXPECT_EQ(dq.at(0), 5);
+ EXPECT_EQ(dq.at(1), 15);
+ EXPECT_EQ(dq.at(2), 25);
+ EXPECT_EQ(dq[0], 5);
+ EXPECT_EQ(dq[1], 15);
+ EXPECT_EQ(dq[2], 25);
+
+ // Shift right such that begin=1 and end=0. Data is still not wrapped.
+ dq.pop_front();
+ dq.push_back(35);
+ EXPECT_THAT(dq, ElementsAre(15, 25, 35));
+ EXPECT_LT(&dq.front(), &dq.back());
+ EXPECT_EQ(dq.front(), 15);
+ EXPECT_EQ(dq.back(), 35);
+ EXPECT_EQ(dq.at(0), 15);
+ EXPECT_EQ(dq.at(1), 25);
+ EXPECT_EQ(dq.at(2), 35);
+ EXPECT_EQ(dq[0], 15);
+ EXPECT_EQ(dq[1], 25);
+ EXPECT_EQ(dq[2], 35);
+
+ // Shift right such that data is wrapped.
+ dq.pop_front();
+ dq.push_back(45);
+ EXPECT_THAT(dq, ElementsAre(25, 35, 45));
+ EXPECT_GT(&dq.front(), &dq.back());
+ EXPECT_EQ(dq.front(), 25);
+ EXPECT_EQ(dq.back(), 45);
+ EXPECT_EQ(dq.at(0), 25);
+ EXPECT_EQ(dq.at(1), 35);
+ EXPECT_EQ(dq.at(2), 45);
+ EXPECT_EQ(dq[0], 25);
+ EXPECT_EQ(dq[1], 35);
+ EXPECT_EQ(dq[2], 45);
+
+ // Shift right again, data is still wrapped.
+ dq.pop_front();
+ dq.push_back(55);
+ EXPECT_THAT(dq, ElementsAre(35, 45, 55));
+ EXPECT_GT(&dq.front(), &dq.back());
+ EXPECT_EQ(dq.front(), 35);
+ EXPECT_EQ(dq.back(), 55);
+ EXPECT_EQ(dq.at(0), 35);
+ EXPECT_EQ(dq.at(1), 45);
+ EXPECT_EQ(dq.at(2), 55);
+ EXPECT_EQ(dq[0], 35);
+ EXPECT_EQ(dq[1], 45);
+ EXPECT_EQ(dq[2], 55);
+
+ // Shift right one last time. begin returns to 0. Data is no longer wrapped.
+ dq.pop_front();
+ dq.push_back(65);
+ EXPECT_THAT(dq, ElementsAre(45, 55, 65));
+ EXPECT_LT(&dq.front(), &dq.back());
+ EXPECT_EQ(dq.front(), 45);
+ EXPECT_EQ(dq.back(), 65);
+ EXPECT_EQ(dq.at(0), 45);
+ EXPECT_EQ(dq.at(1), 55);
+ EXPECT_EQ(dq.at(2), 65);
+ EXPECT_EQ(dq[0], 45);
+ EXPECT_EQ(dq[1], 55);
+ EXPECT_EQ(dq[2], 65);
+
+ EXPECT_EQ(1u, dq.get_allocator().allocate_count());
+}
+
+TEST(QuicCircularDeque, Iterate) {
+ QuicCircularDeque<int> dq;
+ EXPECT_EQ(dq.begin(), dq.end());
+ EXPECT_EQ(dq.cbegin(), dq.cend());
+ EXPECT_EQ(dq.rbegin(), dq.rend());
+ EXPECT_EQ(dq.crbegin(), dq.crend());
+
+ dq.emplace_back(2);
+ QuicCircularDeque<int>::const_iterator citer = dq.begin();
+ EXPECT_NE(citer, dq.end());
+ EXPECT_EQ(*citer, 2);
+ ++citer;
+ EXPECT_EQ(citer, dq.end());
+
+ EXPECT_EQ(*dq.begin(), 2);
+ EXPECT_EQ(*dq.cbegin(), 2);
+ EXPECT_EQ(*dq.rbegin(), 2);
+ EXPECT_EQ(*dq.crbegin(), 2);
+
+ dq.emplace_front(1);
+ QuicCircularDeque<int>::const_reverse_iterator criter = dq.rbegin();
+ EXPECT_NE(criter, dq.rend());
+ EXPECT_EQ(*criter, 2);
+ ++criter;
+ EXPECT_NE(criter, dq.rend());
+ EXPECT_EQ(*criter, 1);
+ ++criter;
+ EXPECT_EQ(criter, dq.rend());
+
+ EXPECT_EQ(*dq.begin(), 1);
+ EXPECT_EQ(*dq.cbegin(), 1);
+ EXPECT_EQ(*dq.rbegin(), 2);
+ EXPECT_EQ(*dq.crbegin(), 2);
+
+ dq.push_back(3);
+
+ // Forward iterate.
+ int expected_value = 1;
+ for (QuicCircularDeque<int>::iterator it = dq.begin(); it != dq.end(); ++it) {
+ EXPECT_EQ(expected_value++, *it);
+ }
+
+ expected_value = 1;
+ for (QuicCircularDeque<int>::const_iterator it = dq.cbegin(); it != dq.cend();
+ ++it) {
+ EXPECT_EQ(expected_value++, *it);
+ }
+
+ // Reverse iterate.
+ expected_value = 3;
+ for (QuicCircularDeque<int>::reverse_iterator it = dq.rbegin();
+ it != dq.rend(); ++it) {
+ EXPECT_EQ(expected_value--, *it);
+ }
+
+ expected_value = 3;
+ for (QuicCircularDeque<int>::const_reverse_iterator it = dq.crbegin();
+ it != dq.crend(); ++it) {
+ EXPECT_EQ(expected_value--, *it);
+ }
+}
+
+TEST(QuicCircularDeque, Iterator) {
+ // Default constructed iterators of the same type compare equal.
+ EXPECT_EQ(QuicCircularDeque<int>::iterator(),
+ QuicCircularDeque<int>::iterator());
+ EXPECT_EQ(QuicCircularDeque<int>::const_iterator(),
+ QuicCircularDeque<int>::const_iterator());
+ EXPECT_EQ(QuicCircularDeque<int>::reverse_iterator(),
+ QuicCircularDeque<int>::reverse_iterator());
+ EXPECT_EQ(QuicCircularDeque<int>::const_reverse_iterator(),
+ QuicCircularDeque<int>::const_reverse_iterator());
+
+ QuicCircularDeque<QuicCircularDeque<int>, 3> dqdq = {
+ {1, 2}, {10, 20, 30}, {100, 200, 300, 400}};
+
+ // iter points to {1, 2}
+ decltype(dqdq)::iterator iter = dqdq.begin();
+ EXPECT_EQ(iter->size(), 2u);
+ EXPECT_THAT(*iter, ElementsAre(1, 2));
+
+ // citer points to {10, 20, 30}
+ decltype(dqdq)::const_iterator citer = dqdq.cbegin() + 1;
+ EXPECT_NE(*iter, *citer);
+ EXPECT_EQ(citer->size(), 3u);
+ int x = 10;
+ for (auto it = citer->begin(); it != citer->end(); ++it) {
+ EXPECT_EQ(*it, x);
+ x += 10;
+ }
+
+ EXPECT_LT(iter, citer);
+ EXPECT_LE(iter, iter);
+ EXPECT_GT(citer, iter);
+ EXPECT_GE(citer, citer);
+
+ // iter points to {100, 200, 300, 400}
+ iter += 2;
+ EXPECT_NE(*iter, *citer);
+ EXPECT_EQ(iter->size(), 4u);
+ for (int i = 1; i <= 4; ++i) {
+ EXPECT_EQ(iter->begin()[i - 1], i * 100);
+ }
+
+ EXPECT_LT(citer, iter);
+ EXPECT_LE(iter, iter);
+ EXPECT_GT(iter, citer);
+ EXPECT_GE(citer, citer);
+
+ // iter points to {10, 20, 30}. (same as citer)
+ iter -= 1;
+ EXPECT_EQ(*iter, *citer);
+ EXPECT_EQ(iter->size(), 3u);
+ x = 10;
+ for (auto it = iter->begin(); it != iter->end();) {
+ EXPECT_EQ(*(it++), x);
+ x += 10;
+ }
+ x = 30;
+ for (auto it = iter->begin() + 2; it != iter->begin();) {
+ EXPECT_EQ(*(it--), x);
+ x -= 10;
+ }
+}
+
+TEST(QuicCircularDeque, Resize) {
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq;
+ dq.resize(8);
+ EXPECT_THAT(dq, ElementsAre(0, 0, 0, 0, 0, 0, 0, 0));
+ EXPECT_EQ(1u, dq.get_allocator().allocate_count());
+
+ dq.resize(10, 5);
+ EXPECT_THAT(dq, ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 5, 5));
+
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq2 = dq;
+
+ for (size_t new_size = dq.size(); new_size != 0; --new_size) {
+ dq.resize(new_size);
+ EXPECT_TRUE(
+ std::equal(dq.begin(), dq.end(), dq2.begin(), dq2.begin() + new_size));
+ }
+
+ dq.resize(0);
+ EXPECT_TRUE(dq.empty());
+
+ // Resize when data is wrapped.
+ ASSERT_EQ(dq2.size(), dq2.capacity());
+ while (dq2.size() < dq2.capacity()) {
+ dq2.push_back(5);
+ }
+
+ // Shift left once such that data is wrapped.
+ ASSERT_LT(&dq2.front(), &dq2.back());
+ dq2.pop_back();
+ dq2.push_front(-5);
+ ASSERT_GT(&dq2.front(), &dq2.back());
+
+ EXPECT_EQ(-5, dq2.front());
+ EXPECT_EQ(5, dq2.back());
+ dq2.resize(dq2.size() + 1, 10);
+
+ // Data should be unwrapped after the resize.
+ ASSERT_LT(&dq2.front(), &dq2.back());
+ EXPECT_EQ(-5, dq2.front());
+ EXPECT_EQ(10, dq2.back());
+ EXPECT_EQ(5, *(dq2.rbegin() + 1));
+}
+
+namespace {
+class Foo {
+ public:
+ Foo() : Foo(0xF00) {}
+
+ explicit Foo(int i) : i_(new int(i)) {}
+
+ ~Foo() {
+ if (i_ != nullptr) {
+ delete i_;
+ // Do not set i_ to nullptr such that if the container calls destructor
+ // multiple times, asan can detect it.
+ }
+ }
+
+ Foo(const Foo& other) : i_(new int(*other.i_)) {}
+
+ Foo(Foo&& other) = delete;
+
+ void Set(int i) { *i_ = i; }
+
+ int i() const { return *i_; }
+
+ friend bool operator==(const Foo& lhs, const Foo& rhs) {
+ return lhs.i() == rhs.i();
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const Foo& foo) {
+ return os << "Foo(" << foo.i() << ")";
+ }
+
+ private:
+ // By pointing i_ to a dynamically allocated integer, a memory leak will be
+ // reported if the container forget to properly destruct this object.
+ int* i_ = nullptr;
+};
+} // namespace
+
+TEST(QuicCircularDeque, RelocateNonTriviallyCopyable) {
+ // When relocating non-trivially-copyable objects:
+ // - Move constructor is preferred, if available.
+ // - Copy constructor is used otherwise.
+
+ {
+ // Move construct in Relocate.
+ typedef std::unique_ptr<Foo> MoveConstructible;
+ ASSERT_FALSE(std::is_trivially_copyable_v<MoveConstructible>);
+ ASSERT_TRUE(std::is_move_constructible_v<MoveConstructible>);
+ QuicCircularDeque<MoveConstructible, 3,
+ CountingAllocator<MoveConstructible>>
+ dq1;
+ dq1.resize(3);
+ EXPECT_EQ(dq1.size(), dq1.capacity());
+ EXPECT_EQ(1u, dq1.get_allocator().allocate_count());
+
+ dq1.emplace_back(new Foo(0xF1)); // Cause existing elements to relocate.
+ EXPECT_EQ(4u, dq1.size());
+ EXPECT_EQ(2u, dq1.get_allocator().allocate_count());
+ EXPECT_EQ(dq1[0], nullptr);
+ EXPECT_EQ(dq1[1], nullptr);
+ EXPECT_EQ(dq1[2], nullptr);
+ EXPECT_EQ(dq1[3]->i(), 0xF1);
+ }
+
+ {
+ // Copy construct in Relocate.
+ typedef Foo NonMoveConstructible;
+ ASSERT_FALSE(std::is_trivially_copyable_v<NonMoveConstructible>);
+ ASSERT_FALSE(std::is_move_constructible_v<NonMoveConstructible>);
+ QuicCircularDeque<NonMoveConstructible, 3,
+ CountingAllocator<NonMoveConstructible>>
+ dq2;
+ dq2.resize(3);
+ EXPECT_EQ(dq2.size(), dq2.capacity());
+ EXPECT_EQ(1u, dq2.get_allocator().allocate_count());
+
+ dq2.emplace_back(0xF1); // Cause existing elements to relocate.
+ EXPECT_EQ(4u, dq2.size());
+ EXPECT_EQ(2u, dq2.get_allocator().allocate_count());
+ EXPECT_EQ(dq2[0].i(), 0xF00);
+ EXPECT_EQ(dq2[1].i(), 0xF00);
+ EXPECT_EQ(dq2[2].i(), 0xF00);
+ EXPECT_EQ(dq2[3].i(), 0xF1);
+ }
+}
+
+TEST(QuicCircularDeque, PushPop) {
+ // (push|pop|emplace)_(back|front)
+
+ {
+ QuicCircularDeque<Foo, 4, CountingAllocator<Foo>> dq(4);
+ for (size_t i = 0; i < dq.size(); ++i) {
+ dq[i].Set(i + 1);
+ }
+ QUIC_LOG(INFO) << "dq initialized to " << dq;
+ EXPECT_THAT(dq, ElementsAre(Foo(1), Foo(2), Foo(3), Foo(4)));
+
+ ShiftLeft(&dq, false);
+ QUIC_LOG(INFO) << "shift left once : " << dq;
+ EXPECT_THAT(dq, ElementsAre(Foo(2), Foo(3), Foo(4), Foo(1)));
+
+ ShiftLeft(&dq, true);
+ QUIC_LOG(INFO) << "shift left twice: " << dq;
+ EXPECT_THAT(dq, ElementsAre(Foo(3), Foo(4), Foo(1), Foo(2)));
+ ASSERT_GT(&dq.front(), &dq.back());
+ // dq destructs with wrapped data.
+ }
+
+ {
+ QuicCircularDeque<Foo, 4, CountingAllocator<Foo>> dq1(4);
+ for (size_t i = 0; i < dq1.size(); ++i) {
+ dq1[i].Set(i + 1);
+ }
+ QUIC_LOG(INFO) << "dq1 initialized to " << dq1;
+ EXPECT_THAT(dq1, ElementsAre(Foo(1), Foo(2), Foo(3), Foo(4)));
+
+ ShiftRight(&dq1, false);
+ QUIC_LOG(INFO) << "shift right once : " << dq1;
+ EXPECT_THAT(dq1, ElementsAre(Foo(4), Foo(1), Foo(2), Foo(3)));
+
+ ShiftRight(&dq1, true);
+ QUIC_LOG(INFO) << "shift right twice: " << dq1;
+ EXPECT_THAT(dq1, ElementsAre(Foo(3), Foo(4), Foo(1), Foo(2)));
+ ASSERT_GT(&dq1.front(), &dq1.back());
+ // dq1 destructs with wrapped data.
+ }
+}
+
+TEST(QuicCircularDeque, Allocation) {
+ CountingAllocator<int> alloc;
+
+ {
+ QuicCircularDeque<int, 3, CountingAllocator<int>> dq(alloc);
+ EXPECT_EQ(alloc, dq.get_allocator());
+ EXPECT_EQ(0u, dq.size());
+ EXPECT_EQ(0u, dq.capacity());
+ EXPECT_EQ(0u, alloc.allocate_count());
+ EXPECT_EQ(0u, alloc.deallocate_count());
+
+ for (int i = 1; i <= 18; ++i) {
+ SCOPED_TRACE(testing::Message()
+ << "i=" << i << ", capacity_b4_push=" << dq.capacity());
+ dq.push_back(i);
+ EXPECT_EQ(i, static_cast<int>(dq.size()));
+
+ const size_t capacity = 3 + (i - 1) / 3 * 3;
+ EXPECT_EQ(capacity, dq.capacity());
+ EXPECT_EQ(capacity / 3, alloc.allocate_count());
+ EXPECT_EQ(capacity / 3 - 1, alloc.deallocate_count());
+ }
+
+ dq.push_back(19);
+ EXPECT_EQ(22u, dq.capacity()); // 18 + 18 / 4
+ EXPECT_EQ(7u, alloc.allocate_count());
+ EXPECT_EQ(6u, alloc.deallocate_count());
+ }
+
+ EXPECT_EQ(7u, alloc.deallocate_count());
+}
+
+} // namespace test
+} // namespace quic
+
+// Use a non-quic namespace to make sure swap can be used via ADL.
+namespace {
+
+template <typename T>
+using SwappableAllocator = quic::test::ConfigurableAllocator<
+ T,
+ /*propagate_on_copy_assignment=*/std::true_type,
+ /*propagate_on_move_assignment=*/std::true_type,
+ /*propagate_on_swap=*/std::true_type,
+ /*equality_result=*/true>;
+
+template <typename T>
+using UnswappableEqualAllocator = quic::test::ConfigurableAllocator<
+ T,
+ /*propagate_on_copy_assignment=*/std::true_type,
+ /*propagate_on_move_assignment=*/std::true_type,
+ /*propagate_on_swap=*/std::false_type,
+ /*equality_result=*/true>;
+
+template <typename T>
+using UnswappableUnequalAllocator = quic::test::ConfigurableAllocator<
+ T,
+ /*propagate_on_copy_assignment=*/std::true_type,
+ /*propagate_on_move_assignment=*/std::true_type,
+ /*propagate_on_swap=*/std::false_type,
+ /*equality_result=*/false>;
+
+TEST(QuicCircularDeque, Swap) {
+ using std::swap;
+
+ quic::QuicCircularDeque<int64_t, 3, SwappableAllocator<int64_t>> dq1, dq2;
+ dq1.push_back(10);
+ dq1.push_back(11);
+ dq2.push_back(20);
+ swap(dq1, dq2);
+ EXPECT_THAT(dq1, ElementsAre(20));
+ EXPECT_THAT(dq2, ElementsAre(10, 11));
+
+ quic::QuicCircularDeque<char, 3, UnswappableEqualAllocator<char>> dq3, dq4;
+ dq3 = {1, 2, 3, 4, 5};
+ dq4 = {6, 7, 8, 9, 0};
+ swap(dq3, dq4);
+ EXPECT_THAT(dq3, ElementsAre(6, 7, 8, 9, 0));
+ EXPECT_THAT(dq4, ElementsAre(1, 2, 3, 4, 5));
+
+ quic::QuicCircularDeque<int, 3, UnswappableUnequalAllocator<int>> dq5, dq6;
+ dq6.push_front(4);
+
+ // Using UnswappableUnequalAllocator is ok as long as swap is not called.
+ dq5.assign(dq6.begin(), dq6.end());
+ EXPECT_THAT(dq5, ElementsAre(4));
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+ // Undefined behavior to swap between two containers with unequal allocators.
+ EXPECT_DEBUG_DEATH(swap(dq5, dq6), "Undefined swap behavior");
+#endif
+}
+} // namespace