/* * Copyright 2020 The Android Open Source Project * * 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 #include #include #include #include #include using namespace std::string_literals; using namespace std::string_view_literals; namespace android::test { using ftl::SmallMap; // Keep in sync with example usage in header file. TEST(SmallMap, Example) { ftl::SmallMap map; EXPECT_TRUE(map.empty()); EXPECT_FALSE(map.dynamic()); map = ftl::init::map(123, "abc")(-1)(42, 3u, '?'); EXPECT_EQ(map.size(), 3u); EXPECT_FALSE(map.dynamic()); EXPECT_TRUE(map.contains(123)); EXPECT_EQ(map.get(42).transform([](const std::string& s) { return s.size(); }), 3u); const auto opt = map.get(-1); ASSERT_TRUE(opt); std::string& ref = *opt; EXPECT_TRUE(ref.empty()); ref = "xyz"; map.emplace_or_replace(0, "vanilla", 2u, 3u); EXPECT_TRUE(map.dynamic()); EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz"sv)(0, "nil"sv)(42, "???"sv)(123, "abc"sv))); } TEST(SmallMap, Construct) { { // Default constructor. SmallMap map; EXPECT_TRUE(map.empty()); EXPECT_FALSE(map.dynamic()); } { // In-place constructor with same types. SmallMap map = ftl::init::map(123, "abc")(456, "def")(789, "ghi"); EXPECT_EQ(map.size(), 3u); EXPECT_EQ(map.max_size(), 5u); EXPECT_FALSE(map.dynamic()); EXPECT_EQ(map, SmallMap(ftl::init::map(123, "abc"sv)(456, "def"sv)(789, "ghi"sv))); } { // In-place constructor with different types. SmallMap map = ftl::init::map(123, "abc")(-1)(42, 3u, '?'); EXPECT_EQ(map.size(), 3u); EXPECT_EQ(map.max_size(), 5u); EXPECT_FALSE(map.dynamic()); EXPECT_EQ(map, SmallMap(ftl::init::map(42, "???"sv)(123, "abc"sv)(-1, ""sv))); } { // In-place constructor with implicit size. SmallMap map = ftl::init::map(123, "abc")(-1)(42, 3u, '?'); static_assert(std::is_same_v>); EXPECT_EQ(map.size(), 3u); EXPECT_EQ(map.max_size(), 3u); EXPECT_FALSE(map.dynamic()); EXPECT_EQ(map, SmallMap(ftl::init::map(-1, ""sv)(42, "???"sv)(123, "abc"sv))); } } TEST(SmallMap, Assign) { { // Same types; smaller capacity. SmallMap map1 = ftl::init::map('k', "kilo")('M', "mega")('G', "giga"); const SmallMap map2 = ftl::init::map('T', "tera"s)('P', "peta"s); map1 = map2; EXPECT_EQ(map1, map2); } { // Convertible types; same capacity. SmallMap map1 = ftl::init::map('M', "mega")('G', "giga"); const SmallMap map2 = ftl::init::map('T', "tera"sv)('P', "peta"sv); map1 = map2; EXPECT_EQ(map1, map2); } { // Convertible types; zero capacity. SmallMap map1 = ftl::init::map('M', "mega")('G', "giga"); const SmallMap map2 = ftl::init::map('T', "tera")('P', "peta"); map1 = map2; EXPECT_EQ(map1, map2); } } TEST(SmallMap, UniqueKeys) { { // Duplicate mappings are discarded. const SmallMap map = ftl::init::map(1)(2)(3)(2)(3)(1)(3)(2)(1); EXPECT_EQ(map.size(), 3u); EXPECT_EQ(map.max_size(), 9u); using Map = decltype(map); EXPECT_EQ(map, Map(ftl::init::map(1, 0.f)(2, 0.f)(3, 0.f))); } { // Duplicate mappings may be reordered. const SmallMap map = ftl::init::map('a', 'A')( 'b', 'B')('b')('b')('c', 'C')('a')('d')('c')('e', 'E')('d', 'D')('a')('f', 'F'); EXPECT_EQ(map.size(), 6u); EXPECT_EQ(map.max_size(), 12u); using Map = decltype(map); EXPECT_EQ(map, Map(ftl::init::map('a', 'A')('b', 'B')('c', 'C')('d', 'D')('e', 'E')('f', 'F'))); } } TEST(SmallMap, Get) { { // Constant reference. const SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C'); const auto opt = map.get('b'); EXPECT_EQ(opt, 'B'); const char d = 'D'; const auto ref = map.get('d').value_or(std::cref(d)); EXPECT_EQ(ref.get(), 'D'); } { // Mutable reference. SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C'); const auto opt = map.get('c'); EXPECT_EQ(opt, 'C'); char d = 'd'; const auto ref = map.get('d').value_or(std::ref(d)); ref.get() = 'D'; EXPECT_EQ(d, 'D'); } { // Immutable transform operation. const SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); EXPECT_EQ(map.get('c').transform([](char c) { return std::toupper(c); }), 'Z'); } { // Mutable transform operation. SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); EXPECT_EQ(map.get('c').transform(ftl::unit_fn([](char& c) { c = std::toupper(c); })), ftl::unit); EXPECT_EQ(map, SmallMap(ftl::init::map('c', 'Z')('b', 'y')('a', 'x'))); } } template struct SmallMapTest : testing::Test { static constexpr std::size_t kCapacity = Capacity{}(); }; template using Capacity = std::integral_constant; using Capacities = testing::Types, Capacity<0>>; TYPED_TEST_SUITE(SmallMapTest, Capacities, ); TYPED_TEST(SmallMapTest, TryEmplace) { SmallMap map; using Pair = typename decltype(map)::value_type; { const auto [it, ok] = map.try_emplace(123, "abc"); ASSERT_TRUE(ok); EXPECT_EQ(*it, Pair(123, "abc"s)); } { const auto [it, ok] = map.try_emplace(42, 3u, '?'); ASSERT_TRUE(ok); EXPECT_EQ(*it, Pair(42, "???"s)); } { const auto [it, ok] = map.try_emplace(-1); ASSERT_TRUE(ok); EXPECT_EQ(*it, Pair(-1, std::string())); if constexpr (map.static_capacity() > 0) { EXPECT_FALSE(map.dynamic()); } else { EXPECT_TRUE(map.dynamic()); } } { // Insertion fails if mapping exists. const auto [it, ok] = map.try_emplace(42, "!!!"); EXPECT_FALSE(ok); EXPECT_EQ(*it, Pair(42, "???")); if constexpr (map.static_capacity() > 0) { EXPECT_FALSE(map.dynamic()); } else { EXPECT_TRUE(map.dynamic()); } } { // Insertion at capacity promotes the map. const auto [it, ok] = map.try_emplace(999, "xyz"); ASSERT_TRUE(ok); EXPECT_EQ(*it, Pair(999, "xyz")); EXPECT_TRUE(map.dynamic()); } EXPECT_EQ(map, SmallMap(ftl::init::map(-1, ""s)(42, "???"s)(123, "abc"s)(999, "xyz"s))); } namespace { // The mapped type does not require a copy/move assignment operator. struct String { template String(Args... args) : str(args...) {} const std::string str; bool operator==(const String& other) const { return other.str == str; } }; } // namespace TYPED_TEST(SmallMapTest, TryReplace) { SmallMap map = ftl::init::map(1, "a")(2, "B"); using Pair = typename decltype(map)::value_type; { // Replacing fails unless mapping exists. const auto it = map.try_replace(3, "c"); EXPECT_EQ(it, map.end()); } { // Replacement arguments can refer to the replaced mapping. const auto ref = map.get(2).transform([](const String& s) { return s.str[0]; }); ASSERT_TRUE(ref); // Construct std::string from one character. const auto it = map.try_replace(2, 1u, static_cast(std::tolower(*ref))); ASSERT_NE(it, map.end()); EXPECT_EQ(*it, Pair(2, "b")); } if constexpr (map.static_capacity() > 0) { EXPECT_FALSE(map.dynamic()); } else { EXPECT_TRUE(map.dynamic()); } EXPECT_TRUE(map.try_emplace(3, "abc").second); EXPECT_TRUE(map.try_emplace(4, "d").second); EXPECT_TRUE(map.dynamic()); { // Replacing fails unless mapping exists. const auto it = map.try_replace(5, "e"); EXPECT_EQ(it, map.end()); } { // Replacement arguments can refer to the replaced mapping. const auto ref = map.get(3); ASSERT_TRUE(ref); // Construct std::string from substring. const auto it = map.try_replace(3, ref->get().str, 2u, 1u); ASSERT_NE(it, map.end()); EXPECT_EQ(*it, Pair(3, "c")); } EXPECT_EQ(map, SmallMap(ftl::init::map(4, "d"s)(3, "c"s)(2, "b"s)(1, "a"s))); } TYPED_TEST(SmallMapTest, EmplaceOrReplace) { SmallMap map = ftl::init::map(1, "a")(2, "B"); using Pair = typename decltype(map)::value_type; { // New mapping is emplaced. const auto [it, emplace] = map.emplace_or_replace(3, "c"); EXPECT_TRUE(emplace); EXPECT_EQ(*it, Pair(3, "c")); } { // Replacement arguments can refer to the replaced mapping. const auto ref = map.get(2).transform([](const String& s) { return s.str[0]; }); ASSERT_TRUE(ref); // Construct std::string from one character. const auto [it, emplace] = map.emplace_or_replace(2, 1u, static_cast(std::tolower(*ref))); EXPECT_FALSE(emplace); EXPECT_EQ(*it, Pair(2, "b")); } if constexpr (map.static_capacity() > 0) { EXPECT_FALSE(map.dynamic()); } else { EXPECT_TRUE(map.dynamic()); } EXPECT_FALSE(map.emplace_or_replace(3, "abc").second); // Replace. EXPECT_TRUE(map.emplace_or_replace(4, "d").second); // Emplace. EXPECT_TRUE(map.dynamic()); { // New mapping is emplaced. const auto [it, emplace] = map.emplace_or_replace(5, "e"); EXPECT_TRUE(emplace); EXPECT_EQ(*it, Pair(5, "e")); } { // Replacement arguments can refer to the replaced mapping. const auto ref = map.get(3); ASSERT_TRUE(ref); // Construct std::string from substring. const auto [it, emplace] = map.emplace_or_replace(3, ref->get().str, 2u, 1u); EXPECT_FALSE(emplace); EXPECT_EQ(*it, Pair(3, "c")); } EXPECT_EQ(map, SmallMap(ftl::init::map(5, "e"s)(4, "d"s)(3, "c"s)(2, "b"s)(1, "a"s))); } TEST(SmallMap, Erase) { { SmallMap map = ftl::init::map(1, '1')(2, '2')(3, '3')(4, '4'); EXPECT_FALSE(map.dynamic()); EXPECT_FALSE(map.erase(0)); // Key not found. EXPECT_TRUE(map.erase(2)); EXPECT_EQ(map, SmallMap(ftl::init::map(1, '1')(3, '3')(4, '4'))); EXPECT_TRUE(map.erase(1)); EXPECT_EQ(map, SmallMap(ftl::init::map(3, '3')(4, '4'))); EXPECT_TRUE(map.erase(4)); EXPECT_EQ(map, SmallMap(ftl::init::map(3, '3'))); EXPECT_TRUE(map.erase(3)); EXPECT_FALSE(map.erase(3)); // Key not found. EXPECT_TRUE(map.empty()); EXPECT_FALSE(map.dynamic()); } { SmallMap map = ftl::init::map(1, '1')(2, '2')(3, '3'); map.try_emplace(4, '4'); EXPECT_TRUE(map.dynamic()); EXPECT_FALSE(map.erase(0)); // Key not found. EXPECT_TRUE(map.erase(2)); EXPECT_EQ(map, SmallMap(ftl::init::map(1, '1')(3, '3')(4, '4'))); EXPECT_TRUE(map.erase(1)); EXPECT_EQ(map, SmallMap(ftl::init::map(3, '3')(4, '4'))); EXPECT_TRUE(map.erase(4)); EXPECT_EQ(map, SmallMap(ftl::init::map(3, '3'))); EXPECT_TRUE(map.erase(3)); EXPECT_FALSE(map.erase(3)); // Key not found. EXPECT_TRUE(map.empty()); EXPECT_TRUE(map.dynamic()); } } TEST(SmallMap, Clear) { SmallMap map = ftl::init::map(1, '1')(2, '2')(3, '3'); map.clear(); EXPECT_TRUE(map.empty()); EXPECT_FALSE(map.dynamic()); map = ftl::init::map(1, '1')(2, '2')(3, '3'); map.try_emplace(4, '4'); map.clear(); EXPECT_TRUE(map.empty()); EXPECT_TRUE(map.dynamic()); } TEST(SmallMap, KeyEqual) { struct KeyEqual { bool operator()(int lhs, int rhs) const { return lhs % 10 == rhs % 10; } }; SmallMap map; EXPECT_TRUE(map.try_emplace(3, '3').second); EXPECT_FALSE(map.try_emplace(13, '3').second); EXPECT_TRUE(map.try_emplace(22, '2').second); EXPECT_TRUE(map.contains(42)); EXPECT_TRUE(map.try_emplace(111, '1').second); EXPECT_EQ(map.get(321), '1'); map.erase(123); EXPECT_EQ(map, SmallMap(ftl::init::map(1, '1')(2, '2'))); } } // namespace android::test