Files
test2/source/blender/blenkernel/intern/lib_id_test.cc
Bastien Montagne 16e552298c Refactor: Core: ID's 'namemap' used to generate unique ID names.
Note: This commit is essentially non-behavioral change, expect in some
fairly rare edge cases.

This commit does a few things:
* Move the whole BKE_main_namemap code to modern C++.
* Split API calls to work with the global namemap, or the local ones.
* Simplify and make the code easier to follow and understand.
* Reduce 'default' memory usage by using growing BitVector for numeric
  suffix management, instead of a fixed 1K items.
* Fix inconsistent handling of 'same base name and numeric suffix,
  different name' issues (e.g. 'Foo.1' and 'Foo.001'), see
  `re_create_equivalent_numeric_suffixes` new unittest.
* Fix completely broken handling of `global` namemaps. This was
  (probably!) OK so far because of their currently very limited
  use-cases.

It also adds a few minor improvements to existing behavior (essentially
in exotic rare edge cases):
* Names that get too long are now only shortened by one char at a time,
  trying to modify the requested base name as little as possible.
* Names that are short, but for which all the manageable numeric suffixes
  are already in use, are extended with an (increasing)  number, instead
  of being shortened.

This work also allowed to detect a few (apparently harmless?) bugs in
existing code, which have been fixed already in 4.4 and main, or in this
commit as well when they depend on changes in namemap code itself.

About performances: This commit introduces a minor slow-down. Some tests
heavily relying on this code (like `bl_id_management` and `blendkernel`
e.g.) get slightly slower (resp. about 1% and 5%). This seems to come
mostly from the added complexity to handle correctly multiple different
names with the same base and numeric suffix value ('Foo.1' and
'Foo.001', but also in the global namemap context where IDs from
different libraries can have the same name).

Pull Request: https://projects.blender.org/blender/blender/pulls/135199
2025-02-28 17:58:58 +01:00

739 lines
28 KiB
C++

/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "testing/testing.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_main_namemap.hh"
#include "DNA_ID.h"
namespace blender::bke::tests {
struct LibIDMainSortTestContext {
Main *bmain = nullptr;
LibIDMainSortTestContext()
{
BKE_idtype_init();
bmain = BKE_main_new();
}
~LibIDMainSortTestContext()
{
BKE_main_free(bmain);
}
};
static void test_lib_id_main_sort_check_order(std::initializer_list<ID *> list)
{
ID *prev_id = nullptr;
for (ID *id : list) {
EXPECT_EQ(id->prev, prev_id);
if (prev_id != nullptr) {
EXPECT_EQ(prev_id->next, id);
}
prev_id = id;
}
EXPECT_EQ(prev_id->next, nullptr);
}
TEST(lib_id_main_sort, local_ids_1)
{
LibIDMainSortTestContext ctx;
EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
EXPECT_TRUE(ctx.bmain->objects.first == id_a);
EXPECT_TRUE(ctx.bmain->objects.last == id_c);
test_lib_id_main_sort_check_order({id_a, id_b, id_c});
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
static void change_lib(Main *bmain, ID *id, Library *lib)
{
if (id->lib == lib) {
return;
}
BKE_main_namemap_remove_id(*bmain, *id);
id->lib = lib;
BKE_main_namemap_get_unique_name(*bmain, *id, id->name + 2);
}
static IDNewNameResult change_name(Main *bmain, ID *id, const char *name, const IDNewNameMode mode)
{
return BKE_libblock_rename(*bmain, *id, name, mode);
}
TEST(lib_id_main_sort, linked_ids_1)
{
LibIDMainSortTestContext ctx;
EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
Library *lib_a = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_A"));
Library *lib_b = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_B"));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
change_lib(ctx.bmain, id_a, lib_a);
id_sort_by_name(&ctx.bmain->objects, id_a, nullptr);
change_lib(ctx.bmain, id_b, lib_a);
id_sort_by_name(&ctx.bmain->objects, id_b, nullptr);
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_c, id_a, id_b});
change_lib(ctx.bmain, id_a, lib_b);
id_sort_by_name(&ctx.bmain->objects, id_a, nullptr);
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_a);
test_lib_id_main_sort_check_order({id_c, id_b, id_a});
change_lib(ctx.bmain, id_b, lib_b);
id_sort_by_name(&ctx.bmain->objects, id_b, nullptr);
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_c, id_a, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, local_ids_rename_existing_never)
{
LibIDMainSortTestContext ctx;
EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
test_lib_id_main_sort_check_order({id_a, id_b, id_c});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
IDNewNameResult result;
/* Rename to different root name. */
result = change_name(ctx.bmain, id_c, "OB_A", IDNewNameMode::RenameExistingNever);
EXPECT_EQ(result.action, IDNewNameResult::Action::RENAMED_COLLISION_ADJUSTED);
// EXPECT_EQ(result.other_id, id_a); /* other_id purposely not looked-up currently. */
EXPECT_EQ(result.other_id, nullptr);
EXPECT_STREQ(id_c->name + 2, "OB_A.001");
EXPECT_STREQ(id_a->name + 2, "OB_A");
EXPECT_TRUE(ctx.bmain->objects.first == id_a);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_a, id_c, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
/* Rename to same root name. */
result = change_name(ctx.bmain, id_c, "OB_A", IDNewNameMode::RenameExistingNever);
EXPECT_EQ(result.action, IDNewNameResult::Action::UNCHANGED_COLLISION);
// EXPECT_EQ(result.other_id, id_a); /* other_id purposely not looked-up currently. */
EXPECT_EQ(result.other_id, nullptr);
EXPECT_STREQ(id_c->name + 2, "OB_A.001");
EXPECT_STREQ(id_a->name + 2, "OB_A");
EXPECT_TRUE(ctx.bmain->objects.first == id_a);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_a, id_c, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
/* Test lower-level #BKE_main_namemap_get_unique_name itself. */
/* Name already in use, needs additional numeric suffix. */
char future_name[MAX_ID_NAME - 2];
STRNCPY(future_name, "OB_B");
EXPECT_TRUE(BKE_main_namemap_get_unique_name(*ctx.bmain, *id_c, future_name));
EXPECT_STREQ(future_name, "OB_B.001");
/* Name not already in use, no need to alter it. */
STRNCPY(future_name, "OB_BBBB");
EXPECT_FALSE(BKE_main_namemap_get_unique_name(*ctx.bmain, *id_c, future_name));
EXPECT_STREQ(future_name, "OB_BBBB");
/* Name too long, needs to be truncated. */
STRNCPY(future_name, "OB_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
change_name(ctx.bmain, id_a, future_name, IDNewNameMode::RenameExistingNever);
EXPECT_STREQ(id_a->name + 2, future_name);
EXPECT_STREQ(future_name, "OB_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
EXPECT_TRUE(BKE_main_namemap_get_unique_name(*ctx.bmain, *id_c, future_name));
EXPECT_STREQ(future_name, "OB_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
}
TEST(lib_id_main_unique_name, local_ids_rename_existing_always)
{
LibIDMainSortTestContext ctx;
EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
test_lib_id_main_sort_check_order({id_a, id_b, id_c});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
IDNewNameResult result;
/* Rename to different root name. */
result = change_name(ctx.bmain, id_c, "OB_A", IDNewNameMode::RenameExistingAlways);
EXPECT_EQ(result.action, IDNewNameResult::Action::RENAMED_COLLISION_FORCED);
EXPECT_EQ(result.other_id, id_a);
EXPECT_STREQ(id_c->name + 2, "OB_A");
EXPECT_STREQ(id_a->name + 2, "OB_A.001");
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_c, id_a, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
/* Rename to same root name. */
result = change_name(ctx.bmain, id_a, "OB_A", IDNewNameMode::RenameExistingAlways);
EXPECT_EQ(result.action, IDNewNameResult::Action::RENAMED_COLLISION_FORCED);
EXPECT_EQ(result.other_id, id_c);
EXPECT_STREQ(id_c->name + 2, "OB_A.001");
EXPECT_STREQ(id_a->name + 2, "OB_A");
EXPECT_TRUE(ctx.bmain->objects.first == id_a);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_a, id_c, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, local_ids_rename_existing_same_root)
{
LibIDMainSortTestContext ctx;
EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
test_lib_id_main_sort_check_order({id_a, id_b, id_c});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
IDNewNameResult result;
/* Rename to different root name. */
result = change_name(ctx.bmain, id_c, "OB_A", IDNewNameMode::RenameExistingSameRoot);
EXPECT_EQ(result.action, IDNewNameResult::Action::RENAMED_COLLISION_ADJUSTED);
// EXPECT_EQ(result.other_id, id_a); /* other_id purposely not looked-up currently. */
EXPECT_EQ(result.other_id, nullptr);
EXPECT_STREQ(id_c->name + 2, "OB_A.001");
EXPECT_STREQ(id_a->name + 2, "OB_A");
EXPECT_TRUE(ctx.bmain->objects.first == id_a);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_a, id_c, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
/* Rename to same root name. */
result = change_name(ctx.bmain, id_c, "OB_A", IDNewNameMode::RenameExistingSameRoot);
EXPECT_EQ(result.action, IDNewNameResult::Action::RENAMED_COLLISION_FORCED);
EXPECT_EQ(result.other_id, id_a);
EXPECT_STREQ(id_c->name + 2, "OB_A");
EXPECT_STREQ(id_a->name + 2, "OB_A.001");
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_c, id_a, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, linked_ids_1)
{
LibIDMainSortTestContext ctx;
EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
Library *lib_a = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_A"));
Library *lib_b = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_B"));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
change_lib(ctx.bmain, id_a, lib_a);
id_sort_by_name(&ctx.bmain->objects, id_a, nullptr);
change_lib(ctx.bmain, id_b, lib_a);
id_sort_by_name(&ctx.bmain->objects, id_b, nullptr);
change_name(ctx.bmain, id_b, "OB_A", IDNewNameMode::RenameExistingNever);
EXPECT_STREQ(id_b->name + 2, "OB_A.001");
EXPECT_STREQ(id_a->name + 2, "OB_A");
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_c, id_a, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
change_lib(ctx.bmain, id_b, lib_b);
id_sort_by_name(&ctx.bmain->objects, id_b, nullptr);
change_name(ctx.bmain, id_b, "OB_A", IDNewNameMode::RenameExistingNever);
EXPECT_STREQ(id_b->name + 2, "OB_A");
EXPECT_STREQ(id_a->name + 2, "OB_A");
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_c, id_a, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
static void change_name_global(Main *bmain, ID *id, const char *name)
{
BKE_main_namemap_remove_id(*bmain, *id);
BLI_strncpy(id->name + 2, name, MAX_NAME);
BKE_main_global_namemap_get_unique_name(*bmain, *id, id->name + 2);
id_sort_by_name(&bmain->objects, id, nullptr);
}
TEST(lib_id_main_global_unique_name, linked_ids_1)
{
LibIDMainSortTestContext ctx;
EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
Library *lib_a = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_A"));
Library *lib_b = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_B"));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
change_lib(ctx.bmain, id_a, lib_a);
id_sort_by_name(&ctx.bmain->objects, id_a, nullptr);
change_lib(ctx.bmain, id_b, lib_b);
id_sort_by_name(&ctx.bmain->objects, id_b, nullptr);
change_name_global(ctx.bmain, id_b, "OB_A");
EXPECT_NE(ctx.bmain->name_map_global, nullptr);
EXPECT_STREQ(id_b->name + 2, "OB_A.001");
EXPECT_STREQ(id_a->name + 2, "OB_A");
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_b);
test_lib_id_main_sort_check_order({id_c, id_a, id_b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
change_lib(ctx.bmain, id_b, lib_a);
id_sort_by_name(&ctx.bmain->objects, id_b, nullptr);
change_name_global(ctx.bmain, id_b, "OB_C");
EXPECT_STREQ(id_b->name + 2, "OB_C.001");
EXPECT_STREQ(id_a->name + 2, "OB_A");
EXPECT_STREQ(id_c->name + 2, "OB_C");
change_name_global(ctx.bmain, id_a, "OB_C");
EXPECT_STREQ(id_b->name + 2, "OB_C.001");
EXPECT_STREQ(id_a->name + 2, "OB_C.002");
EXPECT_STREQ(id_c->name + 2, "OB_C");
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_a);
test_lib_id_main_sort_check_order({id_c, id_b, id_a});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
change_name(ctx.bmain, id_b, "OB_C", IDNewNameMode::RenameExistingNever);
EXPECT_STREQ(id_b->name + 2, "OB_C");
EXPECT_STREQ(id_a->name + 2, "OB_C.002");
EXPECT_STREQ(id_c->name + 2, "OB_C");
EXPECT_TRUE(ctx.bmain->objects.first == id_c);
EXPECT_TRUE(ctx.bmain->objects.last == id_a);
test_lib_id_main_sort_check_order({id_c, id_b, id_a});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
}
TEST(lib_id_main_unique_name, ids_sorted_by_default)
{
LibIDMainSortTestContext ctx;
ID *id_foo = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
ID *id_bar = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Bar"));
ID *id_baz = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Baz"));
ID *id_yes = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Yes"));
test_lib_id_main_sort_check_order({id_bar, id_baz, id_foo, id_yes});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
static ID *add_id_in_library(Main *bmain, const char *name, Library *lib)
{
ID *id = static_cast<ID *>(BKE_id_new(bmain, ID_OB, name));
change_lib(bmain, id, lib);
id_sort_by_name(&bmain->objects, id, nullptr);
return id;
}
TEST(lib_id_main_unique_name, ids_sorted_by_default_with_libraries)
{
LibIDMainSortTestContext ctx;
Library *lib_one = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LibOne"));
Library *lib_two = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LibTwo"));
ID *id_foo = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
ID *id_bar = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Bar"));
ID *id_l1c = add_id_in_library(ctx.bmain, "C", lib_one);
ID *id_l2b = add_id_in_library(ctx.bmain, "B", lib_two);
ID *id_l1a = add_id_in_library(ctx.bmain, "A", lib_one);
ID *id_baz = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Baz"));
ID *id_yes = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Yes"));
test_lib_id_main_sort_check_order({id_bar, id_baz, id_foo, id_yes, id_l1a, id_l1c, id_l2b});
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, name_too_long_handling)
{
LibIDMainSortTestContext ctx;
const char *name_a = "Long_Name_That_Does_Not_Fit_Into_Max_Name_Limit_And_Should_Get_Truncated";
const char *name_b = "Another_Long_Name_That_Does_Not_Fit_And_Has_A_Number_Suffix.123456";
const char *name_c = "Name_That_Has_Too_Long_Number_Suffix.1234567890";
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, name_a));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, name_b));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, name_c));
EXPECT_STREQ(id_a->name + 2, "Long_Name_That_Does_Not_Fit_Into_Max_Name_Limit_And_Should_Get_");
EXPECT_STREQ(id_b->name + 2, "Another_Long_Name_That_Does_Not_Fit_And_Has_A_Number_Suffix.123");
EXPECT_STREQ(id_c->name + 2, "Name_That_Has_Too_Long_Number_Suffix.1234567890"); /* Unchanged */
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, create_equivalent_numeric_suffixes)
{
LibIDMainSortTestContext ctx;
/* Create names where many of their numeric suffixes are
* the same number, yet the names are different and thus
* should be allowed as-is. */
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.123"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.000"));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.003"));
ID *id_d = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.3"));
ID *id_e = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.0"));
ID *id_f = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo."));
ID *id_g = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.0123"));
ID *id_h = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
ID *id_i = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.."));
ID *id_j = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo..001"));
ID *id_k = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo..000"));
EXPECT_STREQ(id_a->name + 2, "Foo.123");
EXPECT_STREQ(id_b->name + 2, "Foo.000");
EXPECT_STREQ(id_c->name + 2, "Foo.003");
EXPECT_STREQ(id_d->name + 2, "Foo.3");
EXPECT_STREQ(id_e->name + 2, "Foo.0");
EXPECT_STREQ(id_f->name + 2, "Foo.");
EXPECT_STREQ(id_g->name + 2, "Foo.0123");
EXPECT_STREQ(id_h->name + 2, "Foo");
EXPECT_STREQ(id_i->name + 2, "Foo..");
EXPECT_STREQ(id_j->name + 2, "Foo..001");
EXPECT_STREQ(id_k->name + 2, "Foo..000");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
/* Now create their exact duplicates again, and check what happens. */
id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.123"));
id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.000"));
id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.003"));
id_d = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.3"));
id_e = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.0"));
id_f = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo."));
id_g = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.0123"));
id_h = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
id_i = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.."));
id_j = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo..001"));
id_k = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo..000"));
EXPECT_STREQ(id_a->name + 2, "Foo.001");
EXPECT_STREQ(id_b->name + 2, "Foo.002");
EXPECT_STREQ(id_c->name + 2, "Foo.004");
EXPECT_STREQ(id_d->name + 2, "Foo.005");
EXPECT_STREQ(id_e->name + 2, "Foo.006");
EXPECT_STREQ(id_f->name + 2, "Foo..002");
EXPECT_STREQ(id_g->name + 2, "Foo.007");
EXPECT_STREQ(id_h->name + 2, "Foo.008");
EXPECT_STREQ(id_i->name + 2, "Foo...001");
EXPECT_STREQ(id_j->name + 2, "Foo..003");
EXPECT_STREQ(id_k->name + 2, "Foo..004");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, re_create_equivalent_numeric_suffixes)
{
LibIDMainSortTestContext ctx;
/* Create names where many of their numeric suffixes are
* the same number, yet the names are different and thus
* should be allowed as-is. */
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.123"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.001"));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.01"));
ID *id_d = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.1"));
ID *id_e = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(BKE_id_name(*id_a), "Foo.123");
EXPECT_STREQ(BKE_id_name(*id_b), "Foo.001");
EXPECT_STREQ(BKE_id_name(*id_c), "Foo.01");
EXPECT_STREQ(BKE_id_name(*id_d), "Foo.1");
EXPECT_STREQ(BKE_id_name(*id_e), "Foo");
/* Deleting 'Foo.1' will _not_ mark number `1` as available, since its internal multi-usages
* counter will still be at `2`, for the 'Foo.01' and 'Foo.001' IDs still present.
*
* So the number `1` is not available, and since `123` is also used, the next free value is `2`.
*/
BKE_id_delete(ctx.bmain, id_d);
id_d = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.123"));
EXPECT_STREQ(BKE_id_name(*id_d), "Foo.002");
/* However, while deleting 'Foo.001' will _not_ mark number `1` as available, it _will_ remove
* the exact name from the full names map.
*
* So adding again 'Foo.001' will succeed and not modify the name at all. */
BKE_id_delete(ctx.bmain, id_b);
id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.001"));
EXPECT_STREQ(BKE_id_name(*id_b), "Foo.001");
/* Finally, removing the last two users of number `1` makes it available again. */
BKE_id_delete(ctx.bmain, id_b);
BKE_id_delete(ctx.bmain, id_c);
id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(BKE_id_name(*id_b), "Foo.001");
id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.01"));
EXPECT_STREQ(BKE_id_name(*id_c), "Foo.01");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, zero_suffix_is_never_assigned)
{
LibIDMainSortTestContext ctx;
/* Creating these should assign 002 to the first one, but the next
* ones should start numbers starting from 1: 001 and 003. */
ID *id_002 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.002"));
ID *id_001 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.002"));
ID *id_003 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.002"));
EXPECT_STREQ(id_002->name + 2, "Foo.002");
EXPECT_STREQ(id_001->name + 2, "Foo.001");
EXPECT_STREQ(id_003->name + 2, "Foo.003");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, remove_after_dup_get_original_name)
{
LibIDMainSortTestContext ctx;
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(id_a->name + 2, "Foo");
EXPECT_STREQ(id_b->name + 2, "Foo.001");
BKE_id_free(ctx.bmain, id_a);
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(id_a->name + 2, "Foo");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, name_number_suffix_assignment)
{
LibIDMainSortTestContext ctx;
/* Create <1k objects first. */
const int total_object_count = 1200;
ID *ids[total_object_count] = {};
for (int i = 0; i < total_object_count / 2; ++i) {
ids[i] = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
}
/* They should get assigned sequential numeric suffixes. */
EXPECT_STREQ(ids[0]->name + 2, "Foo");
EXPECT_STREQ(ids[1]->name + 2, "Foo.001");
EXPECT_STREQ(ids[total_object_count / 2 - 1]->name + 2, "Foo.599");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
/* Free some of the objects. */
BKE_id_free(ctx.bmain, ids[10]);
BKE_id_free(ctx.bmain, ids[20]);
BKE_id_free(ctx.bmain, ids[30]);
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
/* Create objects again; they should get suffixes that were just freed up. */
ID *id_010 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(id_010->name + 2, "Foo.010");
ID *id_020 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.123"));
EXPECT_STREQ(id_020->name + 2, "Foo.020");
/* Suffixes >1k do not get the "use the most proper free one" treatment. */
ID *id_2000 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.2000"));
EXPECT_STREQ(id_2000->name + 2, "Foo.2000");
/* But smaller than 1k suffixes do get proper empty spots. */
ID *id_030 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(id_030->name + 2, "Foo.030");
ID *id_600 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(id_600->name + 2, "Foo.600");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
/* Max possible numeric suffix. */
ID *id_max = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999"));
EXPECT_STREQ(id_max->name + 2, "Foo.999999999");
/* Try with max. possible suffix again: will assign free suffix under 1k. */
ID *id_max1 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999"));
EXPECT_STREQ(id_max1->name + 2, "Foo.601");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
/* Now create the rest of objects, to use all the suffixes up to 1k.
* Once all the ones up to 1k are used, the logic will fall back to
* "use largest number seen + 1", but the largest one is already the max
* possible. So it will modify the name part and restart the counter,
* i.e. "Foo_001.001". */
for (int i = total_object_count / 2; i < total_object_count; ++i) {
ids[i] = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
}
/* At this point creating "Foo" based objects will always result in names extended with a 3 or
* more digits numeric suffix, e.g. "Foo_001.001".
*
* NOTE: The random 3-digits suffix added to the base name is expected to be stable, as the
* requested base name remains the same. This is why the added numeric suffixes can be predicted
* here. */
ID *id_foo_001_178 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(BKE_id_name(*id_foo_001_178), "Foo_001.178");
ID *id_foo_001_179 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.2000"));
EXPECT_STREQ(BKE_id_name(*id_foo_001_179), "Foo_001.179");
ID *id_foo_001_180 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999"));
EXPECT_STREQ(BKE_id_name(*id_foo_001_180), "Foo_001.180");
/* Longer names will be shortened, when no more numeric suffixes are available. */
for (int i = 0; i < total_object_count; ++i) {
ids[i] = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "ALongerName"));
}
/* Max possible numeric suffix. */
id_max = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "ALongerName.999999999"));
EXPECT_STREQ(BKE_id_name(*id_max), "ALongerName.999999999");
ID *id_alongernam = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "ALongerName"));
EXPECT_STREQ(BKE_id_name(*id_alongernam), "ALongerNam");
ID *id_alongernam001 = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "ALongerName"));
EXPECT_STREQ(BKE_id_name(*id_alongernam001), "ALongerNam.001");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, renames_with_duplicates)
{
LibIDMainSortTestContext ctx;
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Bar"));
EXPECT_STREQ(id_a->name + 2, "Foo");
EXPECT_STREQ(id_b->name + 2, "Foo.001");
EXPECT_STREQ(id_c->name + 2, "Bar");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
BKE_libblock_rename(*ctx.bmain, *id_a, "Foo.002");
EXPECT_STREQ(id_a->name + 2, "Foo.002");
BKE_libblock_rename(*ctx.bmain, *id_b, "Bar");
EXPECT_STREQ(id_b->name + 2, "Bar.001");
BKE_libblock_rename(*ctx.bmain, *id_c, "Foo");
EXPECT_STREQ(id_c->name + 2, "Foo");
BKE_libblock_rename(*ctx.bmain, *id_b, "Bar");
EXPECT_STREQ(id_b->name + 2, "Bar");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, names_are_unique_per_id_type)
{
LibIDMainSortTestContext ctx;
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_CA, "Foo"));
ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "Foo"));
EXPECT_STREQ(id_a->name + 2, "Foo");
EXPECT_STREQ(id_b->name + 2, "Foo"); /* Different types (OB & CA) can have the same name. */
EXPECT_STREQ(id_c->name + 2, "Foo.001");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
TEST(lib_id_main_unique_name, name_huge_number_suffix)
{
LibIDMainSortTestContext ctx;
/* Use numeric suffix that is really large: should come through
* fine, since no duplicates with other names. */
ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "SuperLong.1234567890"));
EXPECT_STREQ(id_a->name + 2, "SuperLong.1234567890");
/* Now create with the same name again: should get 001 suffix. */
ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "SuperLong.1234567890"));
EXPECT_STREQ(id_b->name + 2, "SuperLong.001");
EXPECT_TRUE(BKE_main_namemap_validate(*ctx.bmain));
EXPECT_EQ(ctx.bmain->name_map_global, nullptr);
}
} // namespace blender::bke::tests