This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenlib/tests/BLI_map_test.cc
Jacques Lucke 313a1c072d BLI: support removing Map elements during iteration
While it was technically safe to call Map.remove while iterating over
a map, it wasn't really designed to work. Also it wasn't very efficient,
because to remove the element, the map would have to search it
again. Now it is possible to remove an element given an iterator
into the map. It is safe to remove the element while iterating over
the map. Obviously, the removed element must not be accessed
anymore after it has been removed.
2021-04-30 16:23:02 +02:00

752 lines
18 KiB
C++

/* Apache License, Version 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 <memory>
namespace blender::tests {
TEST(map, DefaultConstructor)
{
Map<int, float> map;
EXPECT_EQ(map.size(), 0);
EXPECT_TRUE(map.is_empty());
}
TEST(map, AddIncreasesSize)
{
Map<int, float> 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<int, float> 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<int, float> 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<int, float> 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<int, int> map;
for (int i = 0; i < 100; i++) {
map.add(i * 30, i);
map.add(i * 31, i);
}
}
TEST(map, PopItem)
{
Map<int, float> 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<int, int> map;
map.add(1, 5);
map.add(2, 7);
EXPECT_EQ(map.size(), 2);
std::optional<int> 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<int, int> 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<int, int> 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<int, float> map;
map.add(3, 5.0f);
map.add(1, 2.0f);
map.add(7, -2.0f);
blender::Set<float> 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<int, float> map;
map.add(6, 3.0f);
map.add(2, 4.0f);
map.add(1, 3.0f);
blender::Set<int> 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<int, float> map;
map.add(5, 3.0f);
map.add(2, 9.0f);
map.add(1, 0.0f);
blender::Set<int> keys;
blender::Set<float> values;
int iterations = 0;
const Map<int, float> &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<int, int> 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<int, int> 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<int, int> map;
map.add(3, 6);
map.add(2, 1);
Vector<int> keys, values;
for (Map<int, int>::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<int, float> 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<int, float> 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<int, float> 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<int, std::unique_ptr<int>> map;
auto create_func = [](std::unique_ptr<int> *value) -> int & {
new (value) std::unique_ptr<int>(new int{10});
return **value;
};
auto modify_func = [](std::unique_ptr<int> *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<int, float> 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<int, float> 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<int, int> 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<int, float> map1;
map1.add(1, 2.0f);
map1.add(4, 1.0f);
Map<int, float> 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<int, int> map1;
for (int i = 0; i < 100; i++) {
map1.add_new(i, i);
}
Map<int, int> 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<int, float> map1;
map1.add(1, 2.0f);
map1.add(4, 1.0f);
Map<int, float> 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<int, float> map1;
map1.add(1, 2.0f);
map1.add(4, 1.0f);
Map<int, float> 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<int, float> 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<int>();
auto value2 = std::make_unique<int>();
auto value3 = std::make_unique<int>();
int *value1_ptr = value1.get();
Map<int, std::unique_ptr<int>> 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<int>(); });
map.add_new(5, std::make_unique<int>());
map.add(6, std::make_unique<int>());
map.add_overwrite(7, std::make_unique<int>());
map.lookup_or_add(8, std::make_unique<int>());
map.pop_default(9, std::make_unique<int>());
EXPECT_EQ(map.lookup(1).get(), value1_ptr);
EXPECT_EQ(map.lookup_ptr(100), nullptr);
}
TEST(map, Remove)
{
Map<int, int> 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<char *, int> 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<const std::string, const std::string> map;
map.reserve(10);
map.add("45", "643");
EXPECT_TRUE(map.contains("45"));
EXPECT_FALSE(map.contains("54"));
}
TEST(map, ForeachItem)
{
Map<int, int> map;
map.add(3, 4);
map.add(1, 8);
Vector<int> keys;
Vector<int> 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<ExceptionThrower, ExceptionThrower>;
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<ExceptionThrower, ExceptionThrower>;
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<ExceptionThrower, ExceptionThrower> 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<ExceptionThrower, ExceptionThrower> 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<ExceptionThrower, ExceptionThrower> 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<ExceptionThrower, ExceptionThrower> 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<TestEnum, int> 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<int, int> 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<int, StringRef> 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<int, int> 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<int, int>::MutableItemIterator;
Iter begin = map.items().begin();
Iter end = map.items().end();
for (Iter iter = begin; iter != end; ++iter) {
Map<int, int>::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);
}
/**
* Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot.
*/
#if 0
template<typename MapT>
BLI_NOINLINE void benchmark_random_ints(StringRef name, int amount, int factor)
{
RNG *rng = BLI_rng_new(0);
Vector<int> 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<int, int>>("blender::Map ", 1000000, 1);
benchmark_random_ints<blender::StdUnorderedMapWrapper<int, int>>("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<int, int>>("blender::Map ", 1000000, factor);
benchmark_random_ints<blender::StdUnorderedMapWrapper<int, int>>(
"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