diff --git a/common/util/BUILD b/common/util/BUILD index cf428731c..6a570f568 100644 --- a/common/util/BUILD +++ b/common/util/BUILD @@ -292,6 +292,11 @@ cc_library( ], ) +cc_library( + name = "container_proxy", + hdrs = ["container_proxy.h"], +) + cc_test( name = "algorithm_test", srcs = ["algorithm_test.cc"], @@ -547,3 +552,14 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "container_proxy_test", + srcs = ["container_proxy_test.cc"], + deps = [ + ":container_proxy", + ":type_traits", + "//common/strings:display_utils", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/common/util/container_proxy.h b/common/util/container_proxy.h new file mode 100644 index 000000000..553e2c6ad --- /dev/null +++ b/common/util/container_proxy.h @@ -0,0 +1,458 @@ +// Copyright 2017-2022 The Verible Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef VERIBLE_COMMON_UTIL_CONTAINER_PROXY_H_ +#define VERIBLE_COMMON_UTIL_CONTAINER_PROXY_H_ + +#include +#include +#include +#include + +namespace verible { + +// CRTP base class for creating modification tracking STL container proxies. +// +// The class allows safe exposition of a STL container to an user in cases where +// changes to the container must be pre-/post-processed. This is done through +// implementation of several optional event handling methods in a derived class. +// Event handling methods that are not implemented do not introduce overhead. +// +// Example use case: List of child nodes in a tree which sets parent when +// a child is inserted. +// +// Note that the proxy only tracks container changes. It does not track +// individual elements. +// +// Derived class interface +// ----------------------- +// +// - Mandatory: +// - `ContainerType& underlying_container()`, +// `const ContainerType& underlying_container() const`: +// Returns reference to the wrapped container. +// +// - Optional: +// - `void ElementsInserted(iterator first, iterator last)`: +// Called when new elements were inserted. `first` and `last` (exclusive) +// refer to inserted elements. +// - `void ElementsBeingRemoved(iterator first, iterator last)`: +// Called just before removing elements from `first` to `last` (exclusive). +// - `void ElementsBeingReplaced()`: +// Called when all elements are going to be replaced (due to assignment). +// - `void ElementsWereReplaced()`: +// Called just after all elements were replaced. +// +// Usage +// ----- +// +// 1. Subclass: +// +// using ContainerType = std::vector +// class MyProxy: private ContainerProxyBase { +// using Base = ContainerProxyBase; +// friend Base; +// // ... +// }; +// +// 2. Implement mandatory interface: +// +// private: +// ContainerType& underlying_container() { +// return container_; +// } +// const ContainerType& underlying_container() const { +// return container_; +// } +// // ... +// ContainerType container_; // not a part of the interface +// +// 3. Inherit public interface (all or only some members): +// +// public: +// using typename Base::container_type; +// using typename Base::value_type; +// using typename Base::reference; +// using typename Base::const_reference; +// // ... +// using Base::begin; +// using Base::end; +// // ... +// +// Note: inheriting (or implementing) `void swap(MyProxy&)` will also provide +// standalone function `void swap(MyProxy&, MyProxy&)`. +// +// 4. Implement optional interface: +// +// private: +// void ElementsInserted(iterator first, iterator last) { +// /* ... */ +// } +// void ElementsBeingRemoved(iterator first, iterator last) { +// /* ... */ +// } +// void ElementsBeingReplaced() { /* ... */ } +// void ElementsWereReplaced() { /* ... */ } +// +// 5. Optional: implement operator=(const MyProxy&) and operator=(MyProxy&&) +// (base class' `operator=` works only with `ContainerType`): +// +// public: +// MyProxy& operator=(const MyProxy& other) { +// *this = other.underlying_container(); +// return *this; +// } +// MyProxy& operator=(MyProxy&& other) { +// // You probably want to notify `other` about invalidation +// // of its **whole container** (not just elements). +// *this = std::move(other.underlying_container()); +// return *this; +// } +// +// TODO(mglb): The class name is not really conveying what the class does. +// Find a better one. More details: +// https://github.com/chipsalliance/verible/pull/1244#discussion_r821964959 +template +class ContainerProxyBase { + static_assert(!std::is_const_v); + static_assert(!std::is_reference_v); + static_assert(!std::is_pointer_v); + + public: + ContainerProxyBase() = default; + + // Copy/move of the base class doesn't make sense. + ContainerProxyBase(const ContainerProxyBase&) = delete; + ContainerProxyBase(ContainerProxyBase&&) = delete; + ContainerProxyBase& operator=(const ContainerProxyBase&) = delete; + ContainerProxyBase& operator=(ContainerProxyBase&&) = delete; + + using container_type = ContainerType; + + using value_type = typename ContainerType::value_type; + using reference = typename ContainerType::reference; + using const_reference = typename ContainerType::const_reference; + using iterator = typename ContainerType::iterator; + using const_iterator = typename ContainerType::const_iterator; + using difference_type = typename ContainerType::difference_type; + using size_type = typename ContainerType::size_type; + + using reverse_iterator = typename ContainerType::reverse_iterator; + using const_reverse_iterator = typename ContainerType::const_reverse_iterator; + + // Iteration + + iterator begin() { return container().begin(); } + const_iterator begin() const { return container().begin(); } + const_iterator cbegin() const { return container().cbegin(); } + + iterator end() { return container().end(); } + const_iterator end() const { return container().end(); } + const_iterator cend() const { return container().cend(); } + + reverse_iterator rbegin() { return container().rbegin(); } + const_reverse_iterator rbegin() const { return container().rbegin(); } + const_reverse_iterator crbegin() const { return container().crbegin(); } + + reverse_iterator rend() { return container().rend(); } + const_reverse_iterator rend() const { return container().rend(); } + const_reverse_iterator crend() const { return container().crend(); } + + // Element access + + reference front() { return container().front(); } + const_reference front() const { return container().front(); } + + reference back() { return container().back(); } + const_reference back() const { return container().back(); } + + reference operator[](size_type index) noexcept { return container()[index]; } + const_reference operator[](size_type index) const noexcept { + return container()[index]; + } + + reference at(size_type index) { return container().at(index); } + const_reference at(size_type index) const { return container().at(index); } + + // Modifiers (inserting) + + // TODO(mglb): provide methods taking `iterator` instead of `const_iterator`. + // In most cases `iterator` is convertible to `const_iterator` without any + // cost, but that is not guaranteed. + + template + iterator emplace(const_iterator pos, Args&&... args) { + const auto iter = container().emplace(pos, std::forward(args)...); + CallElementsInserted(iter); + return iter; + } + + template + void emplace_front(Args&&... args) { + container().emplace_front(std::forward(args)...); + CallElementsInserted(container().begin()); + } + + template + void emplace_back(Args&&... args) { + container().emplace_back(std::forward(args)...); + CallElementsInserted(std::prev(container().end())); + } + + iterator insert(const_iterator pos, const value_type& value) { + const auto iter = container().insert(pos, value); + CallElementsInserted(iter); + return iter; + } + + iterator insert(const_iterator pos, size_type count, + const value_type& value) { + const auto iter = container().insert(pos, count, value); + CallElementsInserted(iter, std::next(iter, count)); + return iter; + } + + iterator insert(const_iterator pos, value_type&& value) { + const auto iter = container().insert(pos, std::move(value)); + CallElementsInserted(iter); + return iter; + } + + template + iterator insert(const_iterator pos, InputIterator first, InputIterator last) { + const auto iter = container().insert(pos, first, last); + const auto end_iter = std::next(iter, std::distance(first, last)); + CallElementsInserted(iter, end_iter); + return iter; + } + + iterator insert(const_iterator pos, + std::initializer_list values) { + const auto iter = container().insert(pos, values); + const auto end_iter = std::next(iter, values.size()); + CallElementsInserted(iter, end_iter); + return iter; + } + + void push_front(const value_type& value) { + container().push_front(value); + CallElementsInserted(container().begin()); + } + void push_front(value_type&& value) { + container().push_front(std::move(value)); + CallElementsInserted(container().begin()); + } + + void push_back(const value_type& value) { + container().push_back(value); + CallElementsInserted(std::prev(container().end())); + } + void push_back(value_type&& value) { + container().push_back(std::move(value)); + CallElementsInserted(std::prev(container().end())); + } + + // Modifiers (removing) + + iterator erase(iterator pos) { + CallElementsBeingRemoved(pos); + return container().erase(pos); + } + + iterator erase(const_iterator pos) { + CallElementsBeingRemoved(ConvertToMutableIterator(pos)); + return container().erase(pos); + } + + iterator erase(iterator first, iterator last) { + CallElementsBeingRemoved(first, last); + return container().erase(first, last); + } + + iterator erase(const_iterator first, const_iterator last) { + CallElementsBeingRemoved(ConvertToMutableIterator(first), + ConvertToMutableIterator(last)); + return container().erase(first, last); + } + + void pop_front() { + CallElementsBeingRemoved(container().begin()); + container().pop_front(); + } + + void pop_back() { + CallElementsBeingRemoved(std::prev(container().end())); + container().pop_back(); + } + + void clear() { + CallElementsBeingRemoved(begin(), end()); + container().clear(); + } + + // Assignment + + template + void assign(InputIterator first, InputIterator last) { + CallElementsBeingReplaced(); + container().assign(first, last); + CallElementsWereReplaced(); + } + + void assign(std::initializer_list values) { + CallElementsBeingReplaced(); + container().assign(values); + CallElementsWereReplaced(); + } + + void assign(size_type count, const value_type& value) { + CallElementsBeingReplaced(); + container().assign(count, value); + CallElementsWereReplaced(); + } + + // Intended to be exposed via `using` in `DerivedType`. + // NOLINTNEXTLINE(misc-unconventional-assign-operator) + DerivedType& operator=(const ContainerType& other_container) { + CallElementsBeingReplaced(); + container() = other_container; + CallElementsWereReplaced(); + return *derived(); + } + + // Intended to be exposed via `using` in `DerivedType`. + // NOLINTNEXTLINE(misc-unconventional-assign-operator) + DerivedType& operator=(ContainerType&& other_container) noexcept { + CallElementsBeingReplaced(); + container() = std::move(other_container); + CallElementsWereReplaced(); + return *derived(); + } + + // Intended to be exposed via `using` in `DerivedType`. + // NOLINTNEXTLINE(misc-unconventional-assign-operator) + DerivedType& operator=(std::initializer_list values) { + CallElementsBeingReplaced(); + container() = values; + CallElementsWereReplaced(); + return *derived(); + } + + void swap(ContainerType& other) { + CallElementsBeingReplaced(); + container().swap(other); + CallElementsWereReplaced(); + } + + void swap(DerivedType& other) { + CallElementsBeingReplaced(); + other.CallElementsBeingReplaced(); + container().swap(other.container()); + CallElementsWereReplaced(); + other.CallElementsWereReplaced(); + } + + // Standalone `swap` function available only if `DerivedType` implements + // `swap(DerivedType&)` method (either through importing this class' method + // via `using` or implementing their own version). + template ().swap(std::declval()))> + friend void swap(DerivedType& a, DerivedType& b) { + a.swap(b); + } + + // Capacity + + size_type size() const { return container().size(); } + size_type max_size() const { return container().max_size(); } + bool empty() const { return container().empty(); } + + size_type capacity() const { return container().capacity(); } + + void reserve(size_type count) { container().reserve(count); } + + void resize(size_type count) { + const auto initial_size = container().size(); + if (count < initial_size) { + const iterator first_removed = std::next(container().begin(), count); + CallElementsBeingRemoved(first_removed, container().end()); + } + container().resize(count); + if (count > initial_size) { + const iterator first_inserted = + std::next(container().begin(), initial_size); + CallElementsInserted(first_inserted, container().end()); + } + } + + void resize(size_type count, const value_type& value) { + const auto initial_size = container().size(); + if (count < initial_size) { + const iterator first_removed = std::next(container().begin(), count); + CallElementsBeingRemoved(first_removed, container().end()); + } + container().resize(count, value); + if (count > initial_size) { + const iterator first_inserted = + std::next(container().begin(), initial_size); + CallElementsInserted(first_inserted, container().end()); + } + } + + protected: + // Derived class interface + + // Mandatory: + // ContainerType& underlying_container(); + // const ContainerType& underlying_container() const; + + // Optional: + void ElementsInserted(iterator first, iterator last) {} + void ElementsBeingRemoved(iterator first, iterator last) {} + void ElementsBeingReplaced() {} + void ElementsWereReplaced() {} + + private: + iterator ConvertToMutableIterator(const_iterator iter) { + return std::next(container().begin(), + std::distance(container().cbegin(), iter)); + } + + auto* derived() { return static_cast(this); } + const auto* derived() const { return static_cast(this); } + + ContainerType& container() { return derived()->underlying_container(); } + const ContainerType& container() const { + return derived()->underlying_container(); + } + + void CallElementsInserted(iterator element) { + derived()->ElementsInserted(element, std::next(element)); + } + void CallElementsInserted(iterator first, iterator last) { + derived()->ElementsInserted(first, last); + } + void CallElementsBeingRemoved(iterator element) { + derived()->ElementsBeingRemoved(element, std::next(element)); + } + void CallElementsBeingRemoved(iterator first, iterator last) { + derived()->ElementsBeingRemoved(first, last); + } + void CallElementsBeingReplaced() { derived()->ElementsBeingReplaced(); } + void CallElementsWereReplaced() { derived()->ElementsWereReplaced(); } +}; + +} // namespace verible + +#endif // VERIBLE_COMMON_UTIL_CONTAINER_PROXY_H_ diff --git a/common/util/container_proxy_test.cc b/common/util/container_proxy_test.cc new file mode 100644 index 000000000..833d2c31e --- /dev/null +++ b/common/util/container_proxy_test.cc @@ -0,0 +1,884 @@ +// Copyright 2017-2022 The Verible Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "common/util/container_proxy.h" + +#include +#include +#include + +#include "common/strings/display_utils.h" +#include "common/util/type_traits.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace verible { +namespace { + +using ::testing::ElementsAre; + +enum class ContainerProxyEvent { + kUnknown, + kInserted, + kBeingRemoved, + kBeingReplaced, + kWereReplaced, +}; + +template +struct TestTrace { + using value_type = typename Container::value_type; + using const_iterator = typename Container::const_iterator; + + ContainerProxyEvent triggered_method = ContainerProxyEvent::kUnknown; + // Snapshot of the container when the method has been called. + std::vector container_snapshot; + + int first_index = -1; + int last_index = -1; + std::vector values = {}; + + // Constructors for creating reference object in tests + + template + TestTrace(ContainerProxyEvent method, const SnapshotRange& snapshot, + int first_index, int last_index, const ValuesRange& values) + : triggered_method(method), + container_snapshot(std::begin(snapshot), std::end(snapshot)), + first_index(first_index), + last_index(last_index), + values(std::begin(values), std::end(values)) {} + + template + TestTrace(ContainerProxyEvent method, const SnapshotRange& snapshot, + int first_index, int last_index, + std::initializer_list values) + : triggered_method(method), + container_snapshot(std::begin(snapshot), std::end(snapshot)), + first_index(first_index), + last_index(last_index), + values(values) {} + + TestTrace(ContainerProxyEvent method, + std::initializer_list snapshot, int first_index, + int last_index, std::initializer_list values) + : triggered_method(method), + container_snapshot(snapshot), + first_index(first_index), + last_index(last_index), + values(values) {} + + TestTrace(ContainerProxyEvent method, + std::initializer_list snapshot) + : triggered_method(method), container_snapshot(snapshot) {} + + // Constructors for creating trace objects in a container implementation + + template + TestTrace(ContainerProxyEvent method, const Range& container) + : triggered_method(method), + container_snapshot(container.begin(), container.end()) {} + + template + TestTrace(ContainerProxyEvent method, const Range& container, + const_iterator first, const_iterator last) + : triggered_method(method), + container_snapshot(container.begin(), container.end()), + first_index(std::distance(container.begin(), first)), + last_index(std::distance(container.begin(), last)), + values(first, last) {} + + // Operators + + bool operator==(const TestTrace& other) const { + return triggered_method == other.triggered_method && + std::equal(container_snapshot.begin(), container_snapshot.end(), + other.container_snapshot.begin()) && + first_index == other.first_index && last_index == other.last_index && + last_index == other.last_index && + std::equal(values.begin(), values.end(), other.values.begin()); + } + + friend std::ostream& operator<<(std::ostream& s, TestTrace obj) { + switch (obj.triggered_method) { + case ContainerProxyEvent::kUnknown: + s << "UNKNOWN"; + break; + case ContainerProxyEvent::kInserted: + s << "inserted"; + break; + case ContainerProxyEvent::kBeingRemoved: + s << "being_removed"; + break; + case ContainerProxyEvent::kBeingReplaced: + s << "being_replaced"; + break; + case ContainerProxyEvent::kWereReplaced: + s << "were_replaced"; + break; + } + s << "("; + if (obj.first_index >= 0) { + s << "elements = [" << obj.first_index << ", " << obj.last_index << ")"; + s << " {" << SequenceFormatter(obj.values) << "}; "; + } + s << "snapshot = {" << SequenceFormatter(obj.container_snapshot) << "})"; + return s; + } +}; + +template +class ContainerProxy + : private ContainerProxyBase, Container> { + using ThisType = ContainerProxy; + using Base = ContainerProxyBase, Container>; + friend Base; + + public: + explicit ContainerProxy(Container& container) : container_(container) {} + + using typename Base::container_type; + + using typename Base::value_type; + + using typename Base::const_reference; + using typename Base::reference; + + using typename Base::const_iterator; + using typename Base::iterator; + + using typename Base::difference_type; + using typename Base::size_type; + + using typename Base::const_reverse_iterator; + using typename Base::reverse_iterator; + + using Base::begin; + using Base::cbegin; + using Base::cend; + using Base::end; + + using Base::crbegin; + using Base::crend; + using Base::rbegin; + using Base::rend; + + using Base::back; + using Base::front; + using Base::operator[]; + using Base::at; + + using Base::empty; + using Base::max_size; + using Base::size; + + using Base::emplace_back; + using Base::push_back; + + using Base::emplace_front; + using Base::push_front; + + using Base::emplace; + using Base::insert; + + using Base::clear; + using Base::erase; + using Base::pop_back; + using Base::pop_front; + + using Base::assign; + using Base::operator=; + using Base::swap; + + using Base::capacity; + using Base::reserve; + using Base::resize; + + // Tracing data for test purposes. + std::vector> trace_data; + + private: + Container& container_; + + Container& underlying_container() { return container_; } + const Container& underlying_container() const { return container_; } + + void ElementsInserted(const_iterator first, const_iterator last) { + trace_data.push_back(TestTrace(ContainerProxyEvent::kInserted, + container_, first, last)); + } + + void ElementsBeingRemoved(const_iterator first, const_iterator last) { + trace_data.push_back(TestTrace(ContainerProxyEvent::kBeingRemoved, + container_, first, last)); + } + + void ElementsBeingReplaced() { + trace_data.push_back( + TestTrace(ContainerProxyEvent::kBeingReplaced, container_)); + } + + void ElementsWereReplaced() { + trace_data.push_back( + TestTrace(ContainerProxyEvent::kWereReplaced, container_)); + } +}; + +template +class ContainerProxyTest : public ::testing::Test { + public: + ContainerProxyTest() {} + + using Container = typename Proxy::container_type; + Container container = {"zero", "one", "two"}; + Proxy proxy = Proxy(container); +}; + +using BidirectionalContainerProxyTestTypes = ::testing::Types< + // vector + ContainerProxy>, + const ContainerProxy>, + // list + ContainerProxy>, + const ContainerProxy> + // TODO: deque + // TODO: const array + >; +template +class BidirectionalContainerProxyTest : public ContainerProxyTest {}; +TYPED_TEST_SUITE(BidirectionalContainerProxyTest, + BidirectionalContainerProxyTestTypes); + +using MutableBidirectionalContainerProxyTestTypes = ::testing::Types< + // vector + ContainerProxy>, + // list + ContainerProxy> + // TODO: deque + >; +template +class MutableBidirectionalContainerProxyTest : public ContainerProxyTest {}; +TYPED_TEST_SUITE(MutableBidirectionalContainerProxyTest, + MutableBidirectionalContainerProxyTestTypes); + +using MutablePrependableContainerProxyTestTypes = ::testing::Types< + // list + ContainerProxy> + // TODO: deque + >; +template +class MutablePrependableContainerProxyTest : public ContainerProxyTest {}; +TYPED_TEST_SUITE(MutablePrependableContainerProxyTest, + MutablePrependableContainerProxyTestTypes); + +using RandomAccessContainerProxyTestTypes = ::testing::Types< + // vector + ContainerProxy>, + const ContainerProxy>>; +template +class RandomAccessContainerProxyTest : public ContainerProxyTest {}; +TYPED_TEST_SUITE(RandomAccessContainerProxyTest, + RandomAccessContainerProxyTestTypes); + +using MutableRandomAccessContainerProxyTestTypes = ::testing::Types< + // vector + ContainerProxy>>; +template +class MutableRandomAccessContainerProxyTest : public ContainerProxyTest {}; +TYPED_TEST_SUITE(MutableRandomAccessContainerProxyTest, + MutableRandomAccessContainerProxyTestTypes); + +// Access + +TYPED_TEST(BidirectionalContainerProxyTest, Access) { + EXPECT_EQ(this->proxy.front(), this->container.front()); + EXPECT_EQ(this->proxy.back(), this->container.back()); + EXPECT_EQ(&this->proxy.front(), &this->container.front()); + EXPECT_EQ(&this->proxy.back(), &this->container.back()); + EXPECT_EQ(*this->proxy.begin(), this->container.front()); +} + +TYPED_TEST(RandomAccessContainerProxyTest, Access) { + EXPECT_EQ(this->proxy[0], this->container[0]); + EXPECT_EQ(this->proxy[1], this->container[1]); + EXPECT_EQ(this->proxy[2], this->container[2]); + + EXPECT_EQ(this->proxy.at(0), this->container[0]); + EXPECT_EQ(this->proxy.at(1), this->container[1]); + EXPECT_EQ(this->proxy.at(2), this->container[2]); + + EXPECT_EQ(&this->proxy[0], &this->container[0]); + EXPECT_EQ(&this->proxy[1], &this->container[1]); + EXPECT_EQ(&this->proxy[2], &this->container[2]); + + EXPECT_EQ(&this->proxy.at(0), &this->container[0]); + EXPECT_EQ(&this->proxy.at(1), &this->container[1]); + EXPECT_EQ(&this->proxy.at(2), &this->container[2]); +} + +// Iteration + +TYPED_TEST(BidirectionalContainerProxyTest, Iteration) { + const int initial_size = this->container.size(); + + { + auto iter = this->proxy.begin(); + for (int i = 0; i < initial_size; ++i) { + EXPECT_EQ(*iter, *std::next(this->container.begin(), i)) << "i = " << i; + EXPECT_EQ(&*iter, &*std::next(this->container.begin(), i)) << "i = " << i; + ++iter; + } + EXPECT_EQ(iter, this->proxy.end()); + } + { + auto iter = this->proxy.cbegin(); + for (int i = 0; i < initial_size; ++i) { + EXPECT_EQ(*iter, *std::next(this->container.begin(), i)) << "i = " << i; + EXPECT_EQ(&*iter, &*std::next(this->container.begin(), i)) << "i = " << i; + ++iter; + } + EXPECT_EQ(iter, this->proxy.cend()); + } + { + int i = 0; + for (auto& elem : this->proxy) { + EXPECT_EQ(elem, *std::next(this->container.begin(), i)) << "i = " << i; + EXPECT_EQ(&elem, &*std::next(this->container.begin(), i)) << "i = " << i; + ++i; + } + } +} + +TYPED_TEST(BidirectionalContainerProxyTest, ReverseIteration) { + const int initial_size = this->container.size(); + + { + auto iter = this->proxy.rbegin(); + for (int i = initial_size - 1; i >= 0; --i) { + EXPECT_EQ(*iter, *std::next(this->container.begin(), i)) << "i = " << i; + EXPECT_EQ(&*iter, &*std::next(this->container.begin(), i)) << "i = " << i; + ++iter; + } + EXPECT_EQ(iter, this->proxy.rend()); + } + { + auto iter = this->proxy.crbegin(); + for (int i = initial_size - 1; i >= 0; --i) { + EXPECT_EQ(*iter, *std::next(this->container.begin(), i)) << "i = " << i; + EXPECT_EQ(&*iter, &*std::next(this->container.begin(), i)) << "i = " << i; + ++iter; + } + EXPECT_EQ(iter, this->proxy.crend()); + } +} + +// Modifiers (inserting) + +TYPED_TEST(MutableBidirectionalContainerProxyTest, ModifiersPushEmplaceBack) { + using Trace = TestTrace; + const int initial_size = this->proxy.size(); + + this->proxy.trace_data.clear(); + + this->proxy.push_back("three"); + EXPECT_EQ(this->proxy.size(), initial_size + 1); + EXPECT_EQ(this->proxy.back(), "three"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + initial_size, initial_size + 1, {"three"}))); + + this->proxy.trace_data.clear(); + + std::string four = std::string("four"); + // Make sure the string buffer is dynamically allocated. + four.reserve(1000); + const auto* four_data = four.data(); + this->proxy.push_back(std::move(four)); + EXPECT_EQ(this->proxy.size(), initial_size + 2); + EXPECT_EQ(this->proxy.back(), "four"); + // Verify that the string has been moved and not copied. + // TODO(mglb): make sure this is reliable. + EXPECT_EQ(this->proxy.back().data(), four_data); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + initial_size + 1, initial_size + 2, {"four"}))); + + this->proxy.trace_data.clear(); + + this->proxy.emplace_back(5, '5'); + EXPECT_EQ(this->proxy.size(), initial_size + 3); + EXPECT_EQ(this->proxy.back(), "55555"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + initial_size + 2, initial_size + 3, {"55555"}))); +} + +TYPED_TEST(MutablePrependableContainerProxyTest, ModifiersPushEmplaceFront) { + using Trace = TestTrace; + const int initial_size = this->proxy.size(); + + this->proxy.trace_data.clear(); + + this->proxy.push_front("minus_one"); + EXPECT_EQ(this->proxy.size(), initial_size + 1); + EXPECT_EQ(this->proxy.front(), "minus_one"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 0, 1, {"minus_one"}))); + + this->proxy.trace_data.clear(); + + std::string minus_two = std::string("minus_two"); + // Make sure the string buffer is dynamically allocated. + minus_two.reserve(1000); + const auto* minus_two_data = minus_two.data(); + this->proxy.push_front(std::move(minus_two)); + EXPECT_EQ(this->proxy.size(), initial_size + 2); + EXPECT_EQ(this->proxy.front(), "minus_two"); + // Verify that the string has been moved and not copied. + // TODO(mglb): make sure this is reliable. + EXPECT_EQ(this->proxy.front().data(), minus_two_data); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 0, 1, {"minus_two"}))); + + this->proxy.trace_data.clear(); + + this->proxy.emplace_front(3, '-'); + EXPECT_EQ(this->proxy.size(), initial_size + 3); + EXPECT_EQ(this->proxy.front(), "---"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 0, 1, {"---"}))); +} + +TYPED_TEST(MutableBidirectionalContainerProxyTest, ModifiersInsertEmplace) { + using Trace = TestTrace; + + const int initial_size = this->proxy.size(); + + this->proxy.trace_data.clear(); + + this->proxy.emplace(std::next(this->proxy.begin()), 3, 'a'); + EXPECT_EQ(this->proxy.size(), initial_size + 1); + EXPECT_EQ(*std::next(this->proxy.begin()), "aaa"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 1, 2, {"aaa"}))); + + this->proxy.trace_data.clear(); + + this->proxy.insert(this->proxy.begin(), "foo"); + EXPECT_EQ(this->proxy.size(), initial_size + 2); + EXPECT_EQ(this->proxy.front(), "foo"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 0, 1, {"foo"}))); + + this->proxy.trace_data.clear(); + + this->proxy.insert(std::next(this->proxy.begin(), 3), {"bbb", "ccc", "ddd"}); + EXPECT_EQ(this->proxy.size(), initial_size + 5); + EXPECT_EQ(*std::next(this->proxy.begin(), 3), "bbb"); + EXPECT_EQ(*std::next(this->proxy.begin(), 4), "ccc"); + EXPECT_EQ(*std::next(this->proxy.begin(), 5), "ddd"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 3, 6, {"bbb", "ccc", "ddd"}))); + + this->proxy.trace_data.clear(); + + static const std::string source[] = {"eee", "fff", "ggg"}; + this->proxy.insert(std::next(this->proxy.begin(), 6), std::begin(source), + std::end(source)); + EXPECT_EQ(this->proxy.size(), initial_size + 8); + EXPECT_EQ(*std::next(this->proxy.begin(), 6), "eee"); + EXPECT_EQ(*std::next(this->proxy.begin(), 7), "fff"); + EXPECT_EQ(*std::next(this->proxy.begin(), 8), "ggg"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 6, 9, {"eee", "fff", "ggg"}))); + + this->proxy.trace_data.clear(); + + std::string s = std::string("bar"); + // Make sure the string buffer is dynamically allocated. + s.reserve(1000); + const auto* s_data = s.data(); + this->proxy.insert(std::next(this->proxy.begin(), 1), std::move(s)); + EXPECT_EQ(this->proxy.size(), initial_size + 9); + EXPECT_EQ(*std::next(this->proxy.begin(), 1), "bar"); + // Verify that the string has been moved and not copied. + // TODO(mglb): make sure this is reliable. + EXPECT_EQ(std::next(this->proxy.begin(), 1)->data(), s_data); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 1, 2, {"bar"}))); + + this->proxy.trace_data.clear(); + + this->proxy.insert(std::next(this->proxy.begin(), 2), 3, "baz"); + EXPECT_EQ(this->proxy.size(), initial_size + 12); + EXPECT_EQ(*std::next(this->proxy.begin(), 2), "baz"); + EXPECT_EQ(*std::next(this->proxy.begin(), 3), "baz"); + EXPECT_EQ(*std::next(this->proxy.begin(), 4), "baz"); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, this->container, // + 2, 5, {"baz", "baz", "baz"}))); + + // Check whole container, just to be sure. + + EXPECT_THAT(this->container, ElementsAre("foo", "bar", "baz", "baz", "baz", + "zero", "aaa", "bbb", "ccc", "ddd", + "eee", "fff", "ggg", "one", "two")); +} + +// Modifiers (removing) + +TYPED_TEST(MutableBidirectionalContainerProxyTest, ModifiersErase) { + using Trace = TestTrace; + using iterator = typename TypeParam::iterator; + using const_iterator = typename TypeParam::const_iterator; + + // Add a few more elements + this->container.push_back("three"); + this->container.push_back("four"); + this->container.push_back("five"); + this->container.push_back("six"); + this->container.push_back("seven"); + this->container.push_back("eight"); + + { + this->proxy.trace_data.clear(); + + iterator pos = std::next(this->proxy.begin(), 2); // "two" + this->proxy.erase(pos); + EXPECT_THAT(this->container, ElementsAre("zero", "one", "three", "four", + "five", "six", "seven", "eight")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "two", "three", "four", + "five", "six", "seven", "eight"}, // + 2, 3, {"two"}))); + } + { + this->proxy.trace_data.clear(); + + const_iterator pos = std::next(this->proxy.begin(), 2); // "three" + this->proxy.erase(pos); + EXPECT_THAT(this->container, ElementsAre("zero", "one", "four", "five", + "six", "seven", "eight")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "three", "four", "five", + "six", "seven", "eight"}, // + 2, 3, {"three"}))); + } + { + this->proxy.trace_data.clear(); + + iterator first = std::next(this->proxy.begin(), 2); // "four" + iterator last = std::next(this->proxy.begin(), 5); // "seven" + this->proxy.erase(first, last); + EXPECT_THAT(this->container, ElementsAre("zero", "one", "seven", "eight")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "four", "five", "six", + "seven", "eight"}, // + 2, 5, {"four", "five", "six"}))); + } + { + this->proxy.trace_data.clear(); + + const_iterator first = std::next(this->proxy.begin(), 1); // "one" + const_iterator last = std::next(this->proxy.begin(), 3); // "eight" + this->proxy.erase(first, last); + EXPECT_THAT(this->container, ElementsAre("zero", "eight")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "seven", "eight"}, // + 1, 3, {"one", "seven"}))); + } +} + +TYPED_TEST(MutablePrependableContainerProxyTest, ModifiersPopFront) { + using Trace = TestTrace; + + this->proxy.trace_data.clear(); + + this->proxy.pop_front(); + EXPECT_THAT(this->container, ElementsAre("one", "two")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "two"}, // + 0, 1, {"zero"}))); +} + +TYPED_TEST(MutableRandomAccessContainerProxyTest, ModifiersPopBack) { + using Trace = TestTrace; + + this->proxy.trace_data.clear(); + + this->proxy.pop_back(); + EXPECT_THAT(this->container, ElementsAre("zero", "one")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "two"}, // + 2, 3, {"two"}))); +} + +TYPED_TEST(MutableRandomAccessContainerProxyTest, ModifiersClear) { + using Trace = TestTrace; + + this->proxy.trace_data.clear(); + + this->proxy.clear(); + EXPECT_TRUE(this->container.empty()); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "two"}, // + 0, 3, {"zero", "one", "two"}))); +} + +// Assignment + +TYPED_TEST(MutableBidirectionalContainerProxyTest, Assign) { + using Trace = TestTrace; + + static const std::vector other_container = {"foo", "bar", "baz"}; + + this->proxy.trace_data.clear(); + + this->proxy.assign(other_container.begin(), other_container.end()); + EXPECT_THAT(this->container, ElementsAre("foo", "bar", "baz")); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"zero", "one", "two"}), + Trace(ContainerProxyEvent::kWereReplaced, {"foo", "bar", "baz"}))); + + this->proxy.trace_data.clear(); + + this->proxy.assign({"x", "y"}); + EXPECT_THAT(this->container, ElementsAre("x", "y")); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"foo", "bar", "baz"}), + Trace(ContainerProxyEvent::kWereReplaced, {"x", "y"}))); + + this->proxy.trace_data.clear(); + + this->proxy.assign(4, "FOUR"); + EXPECT_THAT(this->container, ElementsAre("FOUR", "FOUR", "FOUR", "FOUR")); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingReplaced, {"x", "y"}), + Trace(ContainerProxyEvent::kWereReplaced, + {"FOUR", "FOUR", "FOUR", "FOUR"}))); +} + +TYPED_TEST(MutableBidirectionalContainerProxyTest, AssignmentOperator) { + using Trace = TestTrace; + using Container = typename TypeParam::container_type; + + Container other_container = {"foo", "bar", "baz"}; + + this->proxy.trace_data.clear(); + + auto& proxy_ref_0 = this->proxy = other_container; + static_assert(std::is_same_v); + EXPECT_THAT(this->container, ElementsAre("foo", "bar", "baz")); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"zero", "one", "two"}), + Trace(ContainerProxyEvent::kWereReplaced, {"foo", "bar", "baz"}))); + + this->proxy.trace_data.clear(); + + auto& proxy_ref_1 = this->proxy = {"x", "y"}; + static_assert(std::is_same_v); + EXPECT_THAT(this->container, ElementsAre("x", "y")); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"foo", "bar", "baz"}), + Trace(ContainerProxyEvent::kWereReplaced, {"x", "y"}))); + + this->proxy.trace_data.clear(); + + auto* const foo_ptr = &other_container.front(); + auto& proxy_ref_2 = this->proxy = std::move(other_container); + static_assert(std::is_same_v); + EXPECT_THAT(this->container, ElementsAre("foo", "bar", "baz")); + // Verify that the container has been moved and not copied. + EXPECT_EQ(&this->container.front(), foo_ptr); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"x", "y"}), + Trace(ContainerProxyEvent::kWereReplaced, {"foo", "bar", "baz"}))); +} + +TYPED_TEST(MutableBidirectionalContainerProxyTest, Swap) { + using Proxy = TypeParam; + using Container = typename Proxy::container_type; + using Trace = TestTrace; + + Container other_container = {"foo", "bar"}; + Proxy other_proxy = Proxy(other_container); + + this->proxy.trace_data.clear(); + other_proxy.trace_data.clear(); + + this->proxy.swap(other_proxy); + EXPECT_THAT(this->container, ElementsAre("foo", "bar")); + EXPECT_THAT(other_container, ElementsAre("zero", "one", "two")); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"zero", "one", "two"}), + Trace(ContainerProxyEvent::kWereReplaced, {"foo", "bar"}))); + EXPECT_THAT( + other_proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"foo", "bar"}), + Trace(ContainerProxyEvent::kWereReplaced, {"zero", "one", "two"}))); + + this->proxy.trace_data.clear(); + other_proxy.trace_data.clear(); + + swap(this->proxy, other_proxy); + EXPECT_THAT(this->container, ElementsAre("zero", "one", "two")); + EXPECT_THAT(other_container, ElementsAre("foo", "bar")); + EXPECT_THAT( + this->proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"foo", "bar"}), + Trace(ContainerProxyEvent::kWereReplaced, {"zero", "one", "two"}))); + EXPECT_THAT( + other_proxy.trace_data, + ElementsAre( + Trace(ContainerProxyEvent::kBeingReplaced, {"zero", "one", "two"}), + Trace(ContainerProxyEvent::kWereReplaced, {"foo", "bar"}))); + + Container container_without_proxy = {"qux", "quux", "quz", "quuz"}; + + this->proxy.trace_data.clear(); + + this->proxy.swap(container_without_proxy); + EXPECT_THAT(this->container, ElementsAre("qux", "quux", "quz", "quuz")); + EXPECT_THAT(container_without_proxy, ElementsAre("zero", "one", "two")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingReplaced, + {"zero", "one", "two"}), + Trace(ContainerProxyEvent::kWereReplaced, + {"qux", "quux", "quz", "quuz"}))); +} + +// Capacity + +TYPED_TEST(BidirectionalContainerProxyTest, Capacity) { + EXPECT_EQ(this->proxy.size(), this->container.size()); + EXPECT_EQ(this->proxy.empty(), this->container.empty()); + EXPECT_EQ(this->proxy.max_size(), this->container.max_size()); +} + +TYPED_TEST(RandomAccessContainerProxyTest, Capacity) { + EXPECT_EQ(this->proxy.capacity(), this->container.capacity()); +} + +TYPED_TEST(MutableBidirectionalContainerProxyTest, Capacity) { + using Trace = TestTrace; + + const int initial_size = this->proxy.size(); + const int resized_up_size = initial_size + 3; + const int resized_down_size = 2; + const int resized_down_again_size = 1; + + this->proxy.trace_data.clear(); + + this->proxy.resize(resized_up_size); + EXPECT_EQ(this->proxy.size(), resized_up_size); + EXPECT_THAT(this->container, ElementsAre("zero", "one", "two", "", "", "")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, + {"zero", "one", "two", "", "", ""}, // + initial_size, resized_up_size, // + {"", "", ""}))); + + this->proxy.trace_data.clear(); + + this->proxy.resize(resized_down_size); + EXPECT_EQ(this->proxy.size(), resized_down_size); + EXPECT_THAT(this->container, ElementsAre("zero", "one")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "two", "", "", ""}, // + resized_down_size, resized_up_size, // + {"two", "", "", ""}))); + + this->proxy.trace_data.clear(); + + this->proxy.resize(resized_up_size, "1"); + EXPECT_EQ(this->proxy.size(), resized_up_size); + EXPECT_THAT(this->container, ElementsAre("zero", "one", "1", "1", "1", "1")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kInserted, + {"zero", "one", "1", "1", "1", "1"}, // + resized_down_size, resized_up_size, // + {"1", "1", "1", "1"}))); + + this->proxy.trace_data.clear(); + + this->proxy.resize(resized_down_again_size, "whatever, will not be used"); + EXPECT_EQ(this->proxy.size(), resized_down_again_size); + EXPECT_THAT(this->container, ElementsAre("zero")); + EXPECT_THAT(this->proxy.trace_data, + ElementsAre(Trace(ContainerProxyEvent::kBeingRemoved, + {"zero", "one", "1", "1", "1", "1"}, // + resized_down_again_size, resized_up_size, // + {"one", "1", "1", "1", "1"}))); +} + +TYPED_TEST(MutableRandomAccessContainerProxyTest, Capacity) { + const int initial_capacity = this->proxy.capacity(); + EXPECT_EQ(this->proxy.capacity(), this->container.capacity()); + + const int new_capacity = initial_capacity + 42; + this->proxy.reserve(new_capacity); + EXPECT_EQ(this->proxy.capacity(), new_capacity); + EXPECT_EQ(this->container.capacity(), new_capacity); + + const int lower_capacity = 1; + this->proxy.reserve(lower_capacity); + EXPECT_EQ(this->proxy.capacity(), new_capacity); + EXPECT_EQ(this->container.capacity(), new_capacity); +} + +} // namespace +} // namespace verible