aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/core
diff options
context:
space:
mode:
authorGravatar Gil <mcg@google.com>2018-01-31 14:28:20 -0800
committerGravatar GitHub <noreply@github.com>2018-01-31 14:28:20 -0800
commitac3052223465ce654457fec17b34f27de1706e57 (patch)
tree64acd16a6d963ba8a429ed6025e64c2e8221f411 /Firestore/core
parent729b8d176c75ecc0cbbd137cc6811116a64e310a (diff)
Start on ArraySortedMap in C++ (#721)
* Implement ArraySortedMap.remove * Implement ArraySortedMap.insert * Ensure ArraySortedMap.insert avoids copying on duplicates * Port more ArraySortedMapTests
Diffstat (limited to 'Firestore/core')
-rw-r--r--Firestore/core/CMakeLists.txt2
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt21
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/array_sorted_map.cc30
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h318
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/map_entry.h62
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt22
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc328
7 files changed, 783 insertions, 0 deletions
diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt
index f143a71..2fc88c6 100644
--- a/Firestore/core/CMakeLists.txt
+++ b/Firestore/core/CMakeLists.txt
@@ -14,12 +14,14 @@
add_subdirectory(src/firebase/firestore)
add_subdirectory(src/firebase/firestore/core)
+add_subdirectory(src/firebase/firestore/immutable)
add_subdirectory(src/firebase/firestore/model)
add_subdirectory(src/firebase/firestore/remote)
add_subdirectory(src/firebase/firestore/util)
add_subdirectory(test/firebase/firestore)
add_subdirectory(test/firebase/firestore/core)
+add_subdirectory(test/firebase/firestore/immutable)
add_subdirectory(test/firebase/firestore/model)
add_subdirectory(test/firebase/firestore/remote)
add_subdirectory(test/firebase/firestore/util)
diff --git a/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt
new file mode 100644
index 0000000..e8a95cd
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Copyright 2018 Google
+#
+# 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.
+
+cc_library(
+ firebase_firestore_immutable
+ SOURCES
+ array_sorted_map.cc
+ array_sorted_map.h
+ map_entry.h
+)
diff --git a/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.cc b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.cc
new file mode 100644
index 0000000..48e7553
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.cc
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Google
+ *
+ * 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 "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+namespace impl {
+
+// Define external storage for constants:
+constexpr ArraySortedMapBase::size_type ArraySortedMapBase::kFixedSize;
+
+} // namespace impl
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h
new file mode 100644
index 0000000..d0210a8
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2018 Google
+ *
+ * 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 FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <functional>
+#include <memory>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/immutable/map_entry.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+namespace impl {
+
+/**
+ * A base class for implementing ArraySortedMap, containing types and constants
+ * that don't depend upon the template parameters to the main class.
+ *
+ * Note that this exists as a base class rather than as just a namespace in
+ * order to make it possible for users of ArraySortedMap to avoid needing to
+ * declare storage for each instantiation of the template.
+ */
+class ArraySortedMapBase {
+ public:
+ /**
+ * The type of size() methods on immutable collections. Note that this is not
+ * size_t specifically to save space in the TreeSortedMap implementation.
+ */
+ using size_type = uint32_t;
+
+ /**
+ * The maximum size of an ArraySortedMap.
+ *
+ * This is the size threshold where we use a tree backed sorted map instead of
+ * an array backed sorted map. This is a more or less arbitrary chosen value,
+ * that was chosen to be large enough to fit most of object kind of Firebase
+ * data, but small enough to not notice degradation in performance for
+ * inserting and lookups. Feel free to empirically determine this constant,
+ * but don't expect much gain in real world performance.
+ */
+ // TODO(wilhuff): actually use this for switching implementations.
+ static constexpr size_type kFixedSize = 25;
+};
+
+/**
+ * A bounded-size array that allocates its contents directly in itself. This
+ * saves a heap allocation when compared with std::vector (though std::vector
+ * can resize itself while FixedArray cannot).
+ *
+ * Unlike std::array, FixedArray keeps track of its size and grows up to the
+ * fixed_size limit. Inserting more elements than fixed_size will trigger an
+ * assertion failure.
+ *
+ * ArraySortedMap does not actually contain its array: it contains a shared_ptr
+ * to a FixedArray.
+ *
+ * @tparam T The type of an element in the array.
+ * @tparam fixed_size the fixed size to use in creating the FixedArray.
+ */
+template <typename T, ArraySortedMapBase::size_type fixed_size>
+class FixedArray {
+ public:
+ using size_type = ArraySortedMapBase::size_type;
+ using array_type = std::array<T, fixed_size>;
+ using iterator = typename array_type::iterator;
+ using const_iterator = typename array_type::const_iterator;
+
+ FixedArray() {
+ }
+
+ template <typename SourceIterator>
+ FixedArray(SourceIterator src_begin, SourceIterator src_end) {
+ append(src_begin, src_end);
+ }
+
+ /**
+ * Appends to this array, copying from the given src_begin up to but not
+ * including the src_end.
+ */
+ template <typename SourceIterator>
+ void append(SourceIterator src_begin, SourceIterator src_end) {
+ size_type appending = static_cast<size_type>(src_end - src_begin);
+ size_type new_size = size_ + appending;
+ assert(new_size <= fixed_size);
+
+ std::copy(src_begin, src_end, end());
+ size_ = new_size;
+ }
+
+ /**
+ * Appends a single value to the array.
+ */
+ void append(T&& value) {
+ size_type new_size = size_ + 1;
+ assert(new_size <= fixed_size);
+
+ *end() = std::move(value);
+ size_ = new_size;
+ }
+
+ const_iterator begin() const {
+ return contents_.begin();
+ }
+
+ const_iterator end() const {
+ return begin() + size_;
+ }
+
+ size_type size() const {
+ return size_;
+ }
+
+ private:
+ iterator begin() {
+ return contents_.begin();
+ }
+
+ iterator end() {
+ return begin() + size_;
+ }
+
+ array_type contents_;
+ size_type size_ = 0;
+};
+
+} // namespace impl
+
+/**
+ * ArraySortedMap is a value type containing a map. It is immutable, but has
+ * methods to efficiently create new maps that are mutations of it.
+ */
+template <typename K, typename V, typename C = std::less<K>>
+class ArraySortedMap : public impl::ArraySortedMapBase {
+ public:
+ using key_comparator_type = KeyComparator<K, V, C>;
+
+ /**
+ * The type of the entries stored in the map.
+ */
+ using value_type = std::pair<K, V>;
+
+ /**
+ * The type of the fixed-size array containing entries of value_type.
+ */
+ using array_type = impl::FixedArray<value_type, kFixedSize>;
+ using const_iterator = typename array_type::const_iterator;
+
+ using array_pointer = std::shared_ptr<const array_type>;
+
+ /**
+ * Creates an empty ArraySortedMap.
+ */
+ explicit ArraySortedMap(const C& comparator = C())
+ : array_(EmptyArray()), key_comparator_(comparator) {
+ }
+
+ /**
+ * Creates an ArraySortedMap containing the given entries.
+ */
+ ArraySortedMap(std::initializer_list<value_type> entries,
+ const C& comparator = C())
+ : array_(std::make_shared<array_type>(entries.begin(), entries.end())),
+ key_comparator_(comparator) {
+ }
+
+ /**
+ * Creates a new map identical to this one, but with a key-value pair added or
+ * updated.
+ *
+ * @param key The key to insert/update.
+ * @param value The value to associate with the key.
+ * @return A new dictionary with the added/updated value.
+ */
+ ArraySortedMap insert(const K& key, const V& value) const {
+ const_iterator current_end = end();
+ const_iterator pos = LowerBound(key);
+ bool replacing_entry = false;
+
+ if (pos != current_end) {
+ // LowerBound found an entry where pos->first >= pair.first. Reversing the
+ // argument order here tests pair.first < pos->first.
+ replacing_entry = !key_comparator_(key, *pos);
+ if (replacing_entry && value == pos->second) {
+ return *this;
+ }
+ }
+
+ // Copy the segment before the found position. If not found, this is
+ // everything.
+ auto copy = std::make_shared<array_type>(begin(), pos);
+
+ // Copy the value to be inserted.
+ copy->append(value_type(key, value));
+
+ if (replacing_entry) {
+ // Skip the thing at pos because it compares the same as the pair above.
+ copy->append(pos + 1, current_end);
+ } else {
+ copy->append(pos, current_end);
+ }
+ return wrap(copy);
+ }
+
+ /**
+ * Creates a new map identical to this one, but with a key removed from it.
+ *
+ * @param key The key to remove.
+ * @return A new dictionary without that value.
+ */
+ ArraySortedMap erase(const K& key) const {
+ const_iterator current_end = end();
+ const_iterator pos = find(key);
+ if (pos == current_end) {
+ return *this;
+ } else if (size() <= 1) {
+ // If the key was found and it's the last entry, removing it would make
+ // the result empty.
+ return wrap(EmptyArray());
+ } else {
+ auto copy = std::make_shared<array_type>(begin(), pos);
+ copy->append(pos + 1, current_end);
+ return wrap(copy);
+ }
+ }
+
+ /**
+ * Finds a value in the map.
+ *
+ * @param key The key to look up.
+ * @return An iterator pointing to the entry containing the key, or end() if
+ * not found.
+ */
+ const_iterator find(const K& key) const {
+ const_iterator not_found = end();
+ const_iterator lower_bound = LowerBound(key);
+ if (lower_bound != not_found && !key_comparator_(key, *lower_bound)) {
+ return lower_bound;
+ } else {
+ return not_found;
+ }
+ }
+
+ // TODO(wilhuff): indexof
+
+ /** Returns true if the map contains no elements. */
+ bool empty() const {
+ return size() == 0;
+ }
+
+ /** Returns the number of items in this map. */
+ size_type size() const {
+ return array_->size();
+ }
+
+ /**
+ * Returns an iterator pointing to the first entry in the map. If there are
+ * no entries in the map, begin() == end().
+ */
+ const_iterator begin() const {
+ return array_->begin();
+ }
+
+ /**
+ * Returns an iterator pointing past the last entry in the map.
+ */
+ const_iterator end() const {
+ return array_->end();
+ }
+
+ private:
+ static array_pointer EmptyArray() {
+ static const array_pointer kEmptyArray =
+ std::make_shared<const array_type>();
+ return kEmptyArray;
+ }
+
+ ArraySortedMap(const array_pointer& array,
+ const key_comparator_type& key_comparator) noexcept
+ : array_(array), key_comparator_(key_comparator) {
+ }
+
+ ArraySortedMap wrap(const array_pointer& array) const noexcept {
+ return ArraySortedMap(array, key_comparator_);
+ }
+
+ const_iterator LowerBound(const K& key) const {
+ return std::lower_bound(begin(), end(), key, key_comparator_);
+ }
+
+ array_pointer array_;
+ key_comparator_type key_comparator_;
+};
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_
diff --git a/Firestore/core/src/firebase/firestore/immutable/map_entry.h b/Firestore/core/src/firebase/firestore/immutable/map_entry.h
new file mode 100644
index 0000000..2130b5b
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/map_entry.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 Google
+ *
+ * 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 FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_
+
+#include <functional>
+#include <utility>
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+/**
+ * Compares two keys out of a map entry.
+ *
+ * @tparam K The type of the first value in the pair.
+ * @tparam V The type of the second value in the pair.
+ * @tparam C The comparator for use for values of type K
+ */
+template <typename K, typename V, typename C = std::less<K>>
+struct KeyComparator {
+ typedef std::pair<K, V> pair_type;
+
+ explicit KeyComparator(const C& comparator = C())
+ : key_comparator_(comparator) {
+ }
+
+ bool operator()(const K& lhs, const pair_type& rhs) const noexcept {
+ return key_comparator_(lhs, rhs.first);
+ }
+
+ bool operator()(const pair_type& lhs, const K& rhs) const noexcept {
+ return key_comparator_(lhs.first, rhs);
+ }
+
+ bool operator()(const pair_type& lhs, const pair_type& rhs) const noexcept {
+ return key_comparator_(lhs.first, rhs.first);
+ }
+
+ private:
+ C key_comparator_;
+};
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_
diff --git a/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt
new file mode 100644
index 0000000..e7b0b6e
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright 2018 Google
+#
+# 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.
+
+cc_test(
+ firebase_firestore_immutable_test
+ SOURCES
+ array_sorted_map_test.cc
+ DEPENDS
+ firebase_firestore_immutable
+ firebase_firestore_util
+)
diff --git a/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc b/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc
new file mode 100644
index 0000000..4dc6a4b
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2018 Google
+ *
+ * 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 "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h"
+
+#include <numeric>
+#include <random>
+
+#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+typedef ArraySortedMap<int, int> IntMap;
+constexpr IntMap::size_type kFixedSize = IntMap::kFixedSize;
+
+template <typename K, typename V>
+testing::AssertionResult NotFound(const ArraySortedMap<K, V>& set,
+ const K& key) {
+ auto found = set.find(key);
+ if (found == set.end()) {
+ return testing::AssertionSuccess();
+ } else {
+ return testing::AssertionFailure()
+ << "Should not have found (" << found->first << ", " << found->second
+ << ") @ " << found;
+ }
+}
+
+template <typename K, typename V>
+testing::AssertionResult Found(const ArraySortedMap<K, V>& map,
+ const K& key,
+ const V& expected) {
+ auto found = map.find(key);
+ if (found == map.end()) {
+ return testing::AssertionFailure() << "Did not find key " << key;
+ }
+ if (found->second == expected) {
+ return testing::AssertionSuccess();
+ } else {
+ return testing::AssertionFailure() << "Found entry was (" << found->first
+ << ", " << found->second << ")";
+ }
+}
+
+/**
+ * Creates a vector containing a sequence of integers from the given starting
+ * element up to, but not including, the given end element, with values
+ * incremented by the given step.
+ *
+ * If step is negative the sequence is in descending order (but still starting
+ * at start and ending before end).
+ */
+std::vector<int> Sequence(int start, int end, int step = 1) {
+ std::vector<int> result;
+ if (step > 0) {
+ for (int i = start; i < end; i += step) {
+ result.push_back(i);
+ }
+ } else {
+ for (int i = start; i > end; i += step) {
+ result.push_back(i);
+ }
+ }
+ return result;
+}
+
+/**
+ * Creates a vector containing a sequence of integers with the given number of
+ * elements, from zero up to, but not including the given value.
+ */
+std::vector<int> Sequence(int num_elements) {
+ return Sequence(0, num_elements);
+}
+
+/**
+ * Creates a copy of the given vector with contents shuffled randomly.
+ */
+std::vector<int> Shuffled(const std::vector<int>& values) {
+ std::vector<int> result(values);
+ util::SecureRandom rng;
+ std::shuffle(result.begin(), result.end(), rng);
+ return result;
+}
+
+/**
+ * Creates a copy of the given vector with contents sorted.
+ */
+std::vector<int> Sorted(const std::vector<int>& values) {
+ std::vector<int> result(values);
+ std::sort(result.begin(), result.end());
+ return result;
+}
+
+/**
+ * Creates a vector of pairs where each pair has the same first and second
+ * corresponding to an element in the given vector.
+ */
+std::vector<std::pair<int, int>> Pairs(const std::vector<int>& values) {
+ std::vector<std::pair<int, int>> result;
+ for (auto&& value : values) {
+ result.emplace_back(value, value);
+ }
+ return result;
+}
+
+/**
+ * Creates an ArraySortedMap by inserting a pair for each value in the vector.
+ * Each pair will have the same key and value.
+ */
+IntMap ToMap(const std::vector<int>& values) {
+ IntMap result;
+ for (auto&& value : values) {
+ result = result.insert(value, value);
+ }
+ return result;
+}
+
+/**
+ * Appends the contents of the given container to a new vector.
+ */
+template <typename Container>
+std::vector<typename Container::value_type> Append(const Container& container) {
+ std::vector<typename Container::value_type> result;
+ result.insert(result.begin(), container.begin(), container.end());
+ return result;
+}
+
+// TODO(wilhuff): ReverseTraversal
+
+#define ASSERT_SEQ_EQ(x, y) ASSERT_EQ((x), Append(y));
+#define EXPECT_SEQ_EQ(x, y) EXPECT_EQ((x), Append(y));
+
+TEST(ArraySortedMap, SearchForSpecificKey) {
+ IntMap map{{1, 3}, {2, 4}};
+
+ ASSERT_TRUE(Found(map, 1, 3));
+ ASSERT_TRUE(Found(map, 2, 4));
+ ASSERT_TRUE(NotFound(map, 3));
+}
+
+TEST(ArraySortedMap, RemoveKeyValuePair) {
+ IntMap map{{1, 3}, {2, 4}};
+
+ IntMap new_map = map.erase(1);
+ ASSERT_TRUE(Found(new_map, 2, 4));
+ ASSERT_TRUE(NotFound(new_map, 1));
+
+ // Make sure the original one is not mutated
+ ASSERT_TRUE(Found(map, 1, 3));
+ ASSERT_TRUE(Found(map, 2, 4));
+}
+
+TEST(ArraySortedMap, MoreRemovals) {
+ IntMap map = IntMap()
+ .insert(1, 1)
+ .insert(50, 50)
+ .insert(3, 3)
+ .insert(4, 4)
+ .insert(7, 7)
+ .insert(9, 9)
+ .insert(1, 20)
+ .insert(18, 18)
+ .insert(3, 2)
+ .insert(4, 71)
+ .insert(7, 42)
+ .insert(9, 88);
+
+ ASSERT_TRUE(Found(map, 7, 42));
+ ASSERT_TRUE(Found(map, 3, 2));
+ ASSERT_TRUE(Found(map, 1, 20));
+
+ IntMap s1 = map.erase(7);
+ IntMap s2 = map.erase(3);
+ IntMap s3 = map.erase(1);
+
+ ASSERT_TRUE(NotFound(s1, 7));
+ ASSERT_TRUE(Found(s1, 3, 2));
+ ASSERT_TRUE(Found(s1, 1, 20));
+
+ ASSERT_TRUE(Found(s2, 7, 42));
+ ASSERT_TRUE(NotFound(s2, 3));
+ ASSERT_TRUE(Found(s2, 1, 20));
+
+ ASSERT_TRUE(Found(s3, 7, 42));
+ ASSERT_TRUE(Found(s3, 3, 2));
+ ASSERT_TRUE(NotFound(s3, 1));
+}
+
+TEST(ArraySortedMap, RemovesMiddle) {
+ IntMap map{{1, 1}, {2, 2}, {3, 3}};
+ ASSERT_TRUE(Found(map, 1, 1));
+ ASSERT_TRUE(Found(map, 2, 2));
+ ASSERT_TRUE(Found(map, 3, 3));
+
+ IntMap s1 = map.erase(2);
+ ASSERT_TRUE(Found(s1, 1, 1));
+ ASSERT_TRUE(NotFound(s1, 2));
+ ASSERT_TRUE(Found(s1, 3, 3));
+}
+
+TEST(ArraySortedMap, Increasing) {
+ auto total = static_cast<int>(kFixedSize);
+ IntMap map;
+
+ for (int i = 0; i < total; i++) {
+ map = map.insert(i, i);
+ }
+ ASSERT_EQ(kFixedSize, map.size());
+
+ for (int i = 0; i < total; i++) {
+ map = map.erase(i);
+ }
+ ASSERT_EQ(0u, map.size());
+}
+
+TEST(ArraySortedMap, Override) {
+ IntMap map = IntMap().insert(10, 10).insert(10, 8);
+
+ ASSERT_TRUE(Found(map, 10, 8));
+ ASSERT_FALSE(Found(map, 10, 10));
+}
+
+TEST(ArraySortedMap, ChecksSize) {
+ std::vector<int> to_insert = Sequence(kFixedSize);
+ IntMap map = ToMap(to_insert);
+
+ // Replacing an existing entry should not hit increase size
+ map = map.insert(5, 10);
+
+ int next = kFixedSize;
+ ASSERT_DEATH_IF_SUPPORTED(map.insert(next, next), "new_size <= fixed_size");
+}
+
+TEST(ArraySortedMap, Empty) {
+ IntMap map = IntMap().insert(10, 10).erase(10);
+ EXPECT_TRUE(map.empty());
+ EXPECT_EQ(0u, map.size());
+ EXPECT_TRUE(NotFound(map, 1));
+ EXPECT_TRUE(NotFound(map, 10));
+}
+
+TEST(ArraySortedMap, EmptyGet) {
+ IntMap map;
+ EXPECT_TRUE(NotFound(map, 10));
+}
+
+TEST(ArraySortedMap, EmptySize) {
+ IntMap map;
+ EXPECT_TRUE(map.empty());
+ EXPECT_EQ(0u, map.size());
+}
+
+TEST(ArraySortedMap, EmptyRemoval) {
+ IntMap map;
+ IntMap new_map = map.erase(1);
+ EXPECT_TRUE(new_map.empty());
+ EXPECT_EQ(0u, new_map.size());
+ EXPECT_TRUE(NotFound(new_map, 1));
+}
+
+TEST(ArraySortedMap, InsertionAndRemovalOfMaxItems) {
+ auto expected_size = kFixedSize;
+ int n = static_cast<int>(expected_size);
+ std::vector<int> to_insert = Shuffled(Sequence(n));
+ std::vector<int> to_remove = Shuffled(to_insert);
+
+ // Add them to the map
+ IntMap map = ToMap(to_insert);
+ ASSERT_EQ(expected_size, map.size())
+ << "Check if all N objects are in the map";
+
+ // check the order is correct
+ ASSERT_SEQ_EQ(Pairs(Sorted(to_insert)), map);
+
+ for (int i : to_remove) {
+ map = map.erase(i);
+ }
+ ASSERT_EQ(0u, map.size()) << "Check we removed all of the items";
+}
+
+TEST(ArraySortedMap, BalanceProblem) {
+ std::vector<int> to_insert{1, 7, 8, 5, 2, 6, 4, 0, 3};
+
+ IntMap map = ToMap(to_insert);
+ ASSERT_SEQ_EQ(Pairs(Sorted(to_insert)), map);
+}
+
+// TODO(wilhuff): PredecessorKey
+
+// TODO(wilhuff): Iterators
+
+// TODO(wilhuff): IndexOf
+
+TEST(ArraySortedMap, AvoidsCopying) {
+ IntMap map = IntMap().insert(10, 20);
+ auto found = map.find(10);
+ ASSERT_NE(found, map.end());
+ EXPECT_EQ(20, found->second);
+
+ // Verify that inserting something with equal keys and values just returns
+ // the same underlying array.
+ IntMap duped = map.insert(10, 20);
+ auto duped_found = duped.find(10);
+
+ // If everything worked correctly, the backing array should not have been
+ // copied and the pointer to the entry with 10 as key should be the same.
+ EXPECT_EQ(found, duped_found);
+}
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase