/* SPDX-License-Identifier: Apache-2.0 */ #include "BLI_exception_safety_test_utils.hh" #include "BLI_map.hh" #include "BLI_rand.h" #include "BLI_set.hh" #include "BLI_strict_flags.h" #include "BLI_timeit.hh" #include "BLI_vector.hh" #include "testing/testing.h" #include namespace blender::tests { TEST(map, DefaultConstructor) { Map map; EXPECT_EQ(map.size(), 0); EXPECT_TRUE(map.is_empty()); } TEST(map, AddIncreasesSize) { Map map; EXPECT_EQ(map.size(), 0); EXPECT_TRUE(map.is_empty()); map.add(2, 5.0f); EXPECT_EQ(map.size(), 1); EXPECT_FALSE(map.is_empty()); map.add(6, 2.0f); EXPECT_EQ(map.size(), 2); EXPECT_FALSE(map.is_empty()); } TEST(map, Contains) { Map map; EXPECT_FALSE(map.contains(4)); map.add(5, 6.0f); EXPECT_FALSE(map.contains(4)); map.add(4, 2.0f); EXPECT_TRUE(map.contains(4)); } TEST(map, LookupExisting) { Map map; map.add(2, 6.0f); map.add(4, 1.0f); EXPECT_EQ(map.lookup(2), 6.0f); EXPECT_EQ(map.lookup(4), 1.0f); } TEST(map, LookupNotExisting) { Map map; map.add(2, 4.0f); map.add(1, 1.0f); EXPECT_EQ(map.lookup_ptr(0), nullptr); EXPECT_EQ(map.lookup_ptr(5), nullptr); } TEST(map, AddMany) { Map map; for (int i = 0; i < 100; i++) { map.add(i * 30, i); map.add(i * 31, i); } } TEST(map, PopItem) { Map map; map.add(2, 3.0f); map.add(1, 9.0f); EXPECT_TRUE(map.contains(2)); EXPECT_TRUE(map.contains(1)); EXPECT_EQ(map.pop(1), 9.0f); EXPECT_TRUE(map.contains(2)); EXPECT_FALSE(map.contains(1)); EXPECT_EQ(map.pop(2), 3.0f); EXPECT_FALSE(map.contains(2)); EXPECT_FALSE(map.contains(1)); } TEST(map, PopTry) { Map map; map.add(1, 5); map.add(2, 7); EXPECT_EQ(map.size(), 2); std::optional value = map.pop_try(4); EXPECT_EQ(map.size(), 2); EXPECT_FALSE(value.has_value()); value = map.pop_try(2); EXPECT_EQ(map.size(), 1); EXPECT_TRUE(value.has_value()); EXPECT_EQ(*value, 7); EXPECT_EQ(*map.pop_try(1), 5); EXPECT_EQ(map.size(), 0); } TEST(map, PopDefault) { Map map; map.add(1, 4); map.add(2, 7); map.add(3, 8); EXPECT_EQ(map.size(), 3); EXPECT_EQ(map.pop_default(4, 10), 10); EXPECT_EQ(map.size(), 3); EXPECT_EQ(map.pop_default(1, 10), 4); EXPECT_EQ(map.size(), 2); EXPECT_EQ(map.pop_default(2, 20), 7); EXPECT_EQ(map.size(), 1); EXPECT_EQ(map.pop_default(2, 20), 20); EXPECT_EQ(map.size(), 1); EXPECT_EQ(map.pop_default(3, 0), 8); EXPECT_EQ(map.size(), 0); } TEST(map, PopItemMany) { Map map; for (int i = 0; i < 100; i++) { map.add_new(i, i); } for (int i = 25; i < 80; i++) { EXPECT_EQ(map.pop(i), i); } for (int i = 0; i < 100; i++) { EXPECT_EQ(map.contains(i), i < 25 || i >= 80); } } TEST(map, ValueIterator) { Map map; map.add(3, 5.0f); map.add(1, 2.0f); map.add(7, -2.0f); blender::Set values; int iterations = 0; for (float value : map.values()) { values.add(value); iterations++; } EXPECT_EQ(iterations, 3); EXPECT_TRUE(values.contains(5.0f)); EXPECT_TRUE(values.contains(-2.0f)); EXPECT_TRUE(values.contains(2.0f)); } TEST(map, KeyIterator) { Map map; map.add(6, 3.0f); map.add(2, 4.0f); map.add(1, 3.0f); blender::Set keys; int iterations = 0; for (int key : map.keys()) { keys.add(key); iterations++; } EXPECT_EQ(iterations, 3); EXPECT_TRUE(keys.contains(1)); EXPECT_TRUE(keys.contains(2)); EXPECT_TRUE(keys.contains(6)); } TEST(map, ItemIterator) { Map map; map.add(5, 3.0f); map.add(2, 9.0f); map.add(1, 0.0f); blender::Set keys; blender::Set values; int iterations = 0; const Map &const_map = map; for (auto item : const_map.items()) { keys.add(item.key); values.add(item.value); iterations++; } EXPECT_EQ(iterations, 3); EXPECT_TRUE(keys.contains(5)); EXPECT_TRUE(keys.contains(2)); EXPECT_TRUE(keys.contains(1)); EXPECT_TRUE(values.contains(3.0f)); EXPECT_TRUE(values.contains(9.0f)); EXPECT_TRUE(values.contains(0.0f)); } TEST(map, MutableValueIterator) { Map map; map.add(3, 6); map.add(2, 1); for (int &value : map.values()) { value += 10; } EXPECT_EQ(map.lookup(3), 16); EXPECT_EQ(map.lookup(2), 11); } TEST(map, MutableItemIterator) { Map map; map.add(3, 6); map.add(2, 1); for (auto item : map.items()) { item.value += item.key; } EXPECT_EQ(map.lookup(3), 9.0f); EXPECT_EQ(map.lookup(2), 3.0f); } TEST(map, MutableItemToItemConversion) { Map map; map.add(3, 6); map.add(2, 1); Vector keys, values; for (Map::Item item : map.items()) { keys.append(item.key); values.append(item.value); } EXPECT_EQ(keys.size(), 2); EXPECT_EQ(values.size(), 2); EXPECT_TRUE(keys.contains(3)); EXPECT_TRUE(keys.contains(2)); EXPECT_TRUE(values.contains(6)); EXPECT_TRUE(values.contains(1)); } static float return_42() { return 42.0f; } TEST(map, LookupOrAddCB_SeparateFunction) { Map map; EXPECT_EQ(map.lookup_or_add_cb(0, return_42), 42.0f); EXPECT_EQ(map.lookup(0), 42); map.keys(); } TEST(map, LookupOrAddCB_Lambdas) { Map map; auto lambda1 = []() { return 11.0f; }; EXPECT_EQ(map.lookup_or_add_cb(0, lambda1), 11.0f); auto lambda2 = []() { return 20.0f; }; EXPECT_EQ(map.lookup_or_add_cb(1, lambda2), 20.0f); EXPECT_EQ(map.lookup_or_add_cb(0, lambda2), 11.0f); EXPECT_EQ(map.lookup_or_add_cb(1, lambda1), 20.0f); } TEST(map, AddOrModify) { Map map; auto create_func = [](float *value) { *value = 10.0f; return true; }; auto modify_func = [](float *value) { *value += 5; return false; }; EXPECT_TRUE(map.add_or_modify(1, create_func, modify_func)); EXPECT_EQ(map.lookup(1), 10.0f); EXPECT_FALSE(map.add_or_modify(1, create_func, modify_func)); EXPECT_EQ(map.lookup(1), 15.0f); } TEST(map, AddOrModifyReference) { Map> map; auto create_func = [](std::unique_ptr *value) -> int & { new (value) std::unique_ptr(new int{10}); return **value; }; auto modify_func = [](std::unique_ptr *value) -> int & { **value += 5; return **value; }; EXPECT_EQ(map.add_or_modify(1, create_func, modify_func), 10); int &a = map.add_or_modify(1, create_func, modify_func); EXPECT_EQ(a, 15); a = 100; EXPECT_EQ(*map.lookup(1), 100); } TEST(map, AddOverwrite) { Map map; EXPECT_FALSE(map.contains(3)); EXPECT_TRUE(map.add_overwrite(3, 6.0f)); EXPECT_EQ(map.lookup(3), 6.0f); EXPECT_FALSE(map.add_overwrite(3, 7.0f)); EXPECT_EQ(map.lookup(3), 7.0f); EXPECT_FALSE(map.add(3, 8.0f)); EXPECT_EQ(map.lookup(3), 7.0f); } TEST(map, LookupOrAddDefault) { Map map; map.lookup_or_add_default(3) = 6; EXPECT_EQ(map.lookup(3), 6); map.lookup_or_add_default(5) = 2; EXPECT_EQ(map.lookup(5), 2); map.lookup_or_add_default(3) += 4; EXPECT_EQ(map.lookup(3), 10); } TEST(map, LookupOrAdd) { Map map; EXPECT_EQ(map.lookup_or_add(6, 4), 4); EXPECT_EQ(map.lookup_or_add(6, 5), 4); map.lookup_or_add(6, 4) += 10; EXPECT_EQ(map.lookup(6), 14); } TEST(map, MoveConstructorSmall) { Map map1; map1.add(1, 2.0f); map1.add(4, 1.0f); Map map2(std::move(map1)); EXPECT_EQ(map2.size(), 2); EXPECT_EQ(map2.lookup(1), 2.0f); EXPECT_EQ(map2.lookup(4), 1.0f); EXPECT_EQ(map1.size(), 0); /* NOLINT: bugprone-use-after-move */ EXPECT_EQ(map1.lookup_ptr(4), nullptr); } TEST(map, MoveConstructorLarge) { Map map1; for (int i = 0; i < 100; i++) { map1.add_new(i, i); } Map map2(std::move(map1)); EXPECT_EQ(map2.size(), 100); EXPECT_EQ(map2.lookup(1), 1); EXPECT_EQ(map2.lookup(4), 4); EXPECT_EQ(map1.size(), 0); /* NOLINT: bugprone-use-after-move */ EXPECT_EQ(map1.lookup_ptr(4), nullptr); } TEST(map, MoveAssignment) { Map map1; map1.add(1, 2.0f); map1.add(4, 1.0f); Map map2; map2 = std::move(map1); EXPECT_EQ(map2.size(), 2); EXPECT_EQ(map2.lookup(1), 2.0f); EXPECT_EQ(map2.lookup(4), 1.0f); EXPECT_EQ(map1.size(), 0); /* NOLINT: bugprone-use-after-move */ EXPECT_EQ(map1.lookup_ptr(4), nullptr); } TEST(map, CopyAssignment) { Map map1; map1.add(1, 2.0f); map1.add(4, 1.0f); Map map2; map2 = map1; EXPECT_EQ(map2.size(), 2); EXPECT_EQ(map2.lookup(1), 2.0f); EXPECT_EQ(map2.lookup(4), 1.0f); EXPECT_EQ(map1.size(), 2); EXPECT_EQ(*map1.lookup_ptr(4), 1.0f); } TEST(map, Clear) { Map map; map.add(1, 1.0f); map.add(2, 5.0f); EXPECT_EQ(map.size(), 2); EXPECT_TRUE(map.contains(1)); EXPECT_TRUE(map.contains(2)); map.clear(); EXPECT_EQ(map.size(), 0); EXPECT_FALSE(map.contains(1)); EXPECT_FALSE(map.contains(2)); } TEST(map, UniquePtrValue) { auto value1 = std::make_unique(); auto value2 = std::make_unique(); auto value3 = std::make_unique(); int *value1_ptr = value1.get(); Map> map; map.add_new(1, std::move(value1)); map.add(2, std::move(value2)); map.add_overwrite(3, std::move(value3)); map.lookup_or_add_cb(4, []() { return std::make_unique(); }); map.add_new(5, std::make_unique()); map.add(6, std::make_unique()); map.add_overwrite(7, std::make_unique()); map.lookup_or_add(8, std::make_unique()); map.pop_default(9, std::make_unique()); EXPECT_EQ(map.lookup(1).get(), value1_ptr); EXPECT_EQ(map.lookup_ptr(100), nullptr); } TEST(map, Remove) { Map map; map.add(2, 4); EXPECT_EQ(map.size(), 1); EXPECT_FALSE(map.remove(3)); EXPECT_EQ(map.size(), 1); EXPECT_TRUE(map.remove(2)); EXPECT_EQ(map.size(), 0); } TEST(map, PointerKeys) { char a, b, c, d; Map map; EXPECT_TRUE(map.add(&a, 5)); EXPECT_FALSE(map.add(&a, 4)); map.add_new(&b, 1); map.add_new(&c, 1); EXPECT_EQ(map.size(), 3); EXPECT_TRUE(map.remove(&b)); EXPECT_TRUE(map.add(&b, 8)); EXPECT_FALSE(map.remove(&d)); EXPECT_TRUE(map.remove(&a)); EXPECT_TRUE(map.remove(&b)); EXPECT_TRUE(map.remove(&c)); EXPECT_TRUE(map.is_empty()); } TEST(map, ConstKeysAndValues) { Map map; map.reserve(10); map.add("45", "643"); EXPECT_TRUE(map.contains("45")); EXPECT_FALSE(map.contains("54")); } TEST(map, ForeachItem) { Map map; map.add(3, 4); map.add(1, 8); Vector keys; Vector values; map.foreach_item([&](int key, int value) { keys.append(key); values.append(value); }); EXPECT_EQ(keys.size(), 2); EXPECT_EQ(values.size(), 2); EXPECT_EQ(keys.first_index_of(3), values.first_index_of(4)); EXPECT_EQ(keys.first_index_of(1), values.first_index_of(8)); } TEST(map, CopyConstructorExceptions) { using MapType = Map; MapType map; map.add(2, 2); map.add(4, 4); map.lookup(2).throw_during_copy = true; EXPECT_ANY_THROW({ MapType map_copy(map); }); } TEST(map, MoveConstructorExceptions) { using MapType = Map; MapType map; map.add(1, 1); map.add(2, 2); map.lookup(1).throw_during_move = true; EXPECT_ANY_THROW({ MapType map_moved(std::move(map)); }); map.add(5, 5); /* NOLINT: bugprone-use-after-move */ } TEST(map, AddNewExceptions) { Map map; ExceptionThrower key1 = 1; key1.throw_during_copy = true; ExceptionThrower value1; EXPECT_ANY_THROW({ map.add_new(key1, value1); }); EXPECT_EQ(map.size(), 0); ExceptionThrower key2 = 2; ExceptionThrower value2; value2.throw_during_copy = true; EXPECT_ANY_THROW({ map.add_new(key2, value2); }); } TEST(map, ReserveExceptions) { Map map; map.add(3, 3); map.add(5, 5); map.add(2, 2); map.lookup(2).throw_during_move = true; EXPECT_ANY_THROW({ map.reserve(100); }); map.add(1, 1); map.add(5, 5); } TEST(map, PopExceptions) { Map map; map.add(3, 3); map.lookup(3).throw_during_move = true; EXPECT_ANY_THROW({ map.pop(3); }); /* NOLINT: bugprone-throw-keyword-missing */ EXPECT_EQ(map.size(), 1); map.add(1, 1); EXPECT_EQ(map.size(), 2); } TEST(map, AddOrModifyExceptions) { Map map; auto create_fn = [](ExceptionThrower *UNUSED(v)) { throw std::runtime_error(""); }; auto modify_fn = [](ExceptionThrower *UNUSED(v)) {}; EXPECT_ANY_THROW({ map.add_or_modify(3, create_fn, modify_fn); }); } namespace { enum class TestEnum { A = 0, B = 1, C = 2, D = 1, }; } TEST(map, EnumKey) { Map map; map.add(TestEnum::A, 4); map.add(TestEnum::B, 6); EXPECT_EQ(map.lookup(TestEnum::A), 4); EXPECT_EQ(map.lookup(TestEnum::B), 6); EXPECT_EQ(map.lookup(TestEnum::D), 6); EXPECT_FALSE(map.contains(TestEnum::C)); map.lookup(TestEnum::D) = 10; EXPECT_EQ(map.lookup(TestEnum::B), 10); } TEST(map, GenericAlgorithms) { Map map; map.add(5, 2); map.add(1, 4); map.add(2, 2); map.add(7, 1); map.add(8, 6); EXPECT_TRUE(std::any_of(map.keys().begin(), map.keys().end(), [](int v) { return v == 1; })); EXPECT_TRUE(std::any_of(map.values().begin(), map.values().end(), [](int v) { return v == 1; })); EXPECT_TRUE(std::any_of( map.items().begin(), map.items().end(), [](auto item) { return item.value == 1; })); EXPECT_EQ(std::count(map.values().begin(), map.values().end(), 2), 2); EXPECT_EQ(std::count(map.values().begin(), map.values().end(), 4), 1); EXPECT_EQ(std::count(map.keys().begin(), map.keys().end(), 7), 1); } TEST(map, AddAsVariadic) { Map map; map.add_as(3, "hello", 2); map.add_as(2, "test", 1); EXPECT_EQ(map.lookup(3), "he"); EXPECT_EQ(map.lookup(2), "t"); } TEST(map, RemoveDuringIteration) { Map map; map.add(2, 1); map.add(5, 2); map.add(1, 2); map.add(6, 0); map.add(3, 3); EXPECT_EQ(map.size(), 5); using Iter = Map::MutableItemIterator; Iter begin = map.items().begin(); Iter end = map.items().end(); for (Iter iter = begin; iter != end; ++iter) { Map::MutableItem item = *iter; if (item.value == 2) { map.remove(iter); } } EXPECT_EQ(map.size(), 3); EXPECT_EQ(map.lookup(2), 1); EXPECT_EQ(map.lookup(6), 0); EXPECT_EQ(map.lookup(3), 3); } TEST(map, LookupKey) { Map map; map.add("a", 0); map.add("b", 1); map.add("c", 2); EXPECT_EQ(map.lookup_key("a"), "a"); EXPECT_EQ(map.lookup_key_as("c"), "c"); EXPECT_EQ(map.lookup_key_ptr_as("d"), nullptr); EXPECT_EQ(map.lookup_key_ptr_as("b")->size(), 1); EXPECT_EQ(map.lookup_key_ptr("a"), map.lookup_key_ptr_as("a")); } /** * Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot. */ #if 0 template BLI_NOINLINE void benchmark_random_ints(StringRef name, int amount, int factor) { RNG *rng = BLI_rng_new(0); Vector values; for (int i = 0; i < amount; i++) { values.append(BLI_rng_get_int(rng) * factor); } BLI_rng_free(rng); MapT map; { SCOPED_TIMER(name + " Add"); for (int value : values) { map.add(value, value); } } int count = 0; { SCOPED_TIMER(name + " Contains"); for (int value : values) { count += map.contains(value); } } { SCOPED_TIMER(name + " Remove"); for (int value : values) { count += map.remove(value); } } /* Print the value for simple error checking and to avoid some compiler optimizations. */ std::cout << "Count: " << count << "\n"; } TEST(map, Benchmark) { for (int i = 0; i < 3; i++) { benchmark_random_ints>("blender::Map ", 1000000, 1); benchmark_random_ints>("std::unordered_map", 1000000, 1); } std::cout << "\n"; for (int i = 0; i < 3; i++) { uint32_t factor = (3 << 10); benchmark_random_ints>("blender::Map ", 1000000, factor); benchmark_random_ints>( "std::unordered_map", 1000000, factor); } } /** * Timer 'blender::Map Add' took 61.7616 ms * Timer 'blender::Map Contains' took 18.4989 ms * Timer 'blender::Map Remove' took 20.5864 ms * Count: 1999755 * Timer 'std::unordered_map Add' took 188.674 ms * Timer 'std::unordered_map Contains' took 44.3741 ms * Timer 'std::unordered_map Remove' took 169.52 ms * Count: 1999755 * Timer 'blender::Map Add' took 37.9196 ms * Timer 'blender::Map Contains' took 16.7361 ms * Timer 'blender::Map Remove' took 20.9568 ms * Count: 1999755 * Timer 'std::unordered_map Add' took 166.09 ms * Timer 'std::unordered_map Contains' took 40.6133 ms * Timer 'std::unordered_map Remove' took 142.85 ms * Count: 1999755 * Timer 'blender::Map Add' took 37.3053 ms * Timer 'blender::Map Contains' took 16.6731 ms * Timer 'blender::Map Remove' took 18.8304 ms * Count: 1999755 * Timer 'std::unordered_map Add' took 170.964 ms * Timer 'std::unordered_map Contains' took 38.1824 ms * Timer 'std::unordered_map Remove' took 140.263 ms * Count: 1999755 * * Timer 'blender::Map Add' took 50.1131 ms * Timer 'blender::Map Contains' took 25.0491 ms * Timer 'blender::Map Remove' took 32.4225 ms * Count: 1889920 * Timer 'std::unordered_map Add' took 150.129 ms * Timer 'std::unordered_map Contains' took 34.6999 ms * Timer 'std::unordered_map Remove' took 120.907 ms * Count: 1889920 * Timer 'blender::Map Add' took 50.4438 ms * Timer 'blender::Map Contains' took 25.2677 ms * Timer 'blender::Map Remove' took 32.3047 ms * Count: 1889920 * Timer 'std::unordered_map Add' took 144.015 ms * Timer 'std::unordered_map Contains' took 36.3387 ms * Timer 'std::unordered_map Remove' took 119.109 ms * Count: 1889920 * Timer 'blender::Map Add' took 48.6995 ms * Timer 'blender::Map Contains' took 25.1846 ms * Timer 'blender::Map Remove' took 33.0283 ms * Count: 1889920 * Timer 'std::unordered_map Add' took 143.494 ms * Timer 'std::unordered_map Contains' took 34.8905 ms * Timer 'std::unordered_map Remove' took 122.739 ms * Count: 1889920 */ #endif /* Benchmark */ } // namespace blender::tests