Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,273 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "AS_asset_catalog_path.hh"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "BKE_gtest_base.hh"
#include <set>
#include <sstream>
#include "testing/testing.h"
namespace blender::asset_system::tests {
class AssetCatalogPathTest : public bke::BlenderGTestBase {};
TEST_F(AssetCatalogPathTest, construction)
{
AssetCatalogPath default_constructed;
/* Use `.str()` to use `std:string`'s comparison operators here, not our own (which are tested
* later). */
EXPECT_EQ(default_constructed.str(), "");
/* C++ considers this construction special, it doesn't call the default constructor but does
* recursive, member-wise value initialization. See https://stackoverflow.com/a/4982720. */
AssetCatalogPath value_initialized = AssetCatalogPath();
EXPECT_EQ(value_initialized.str(), "");
AssetCatalogPath from_char_literal("the/path");
const std::string str_const = "the/path";
AssetCatalogPath from_string_constant(str_const);
std::string str_variable = "the/path";
AssetCatalogPath from_string_variable(str_variable);
std::string long_string = "this is a long/string/with/a/path in the middle";
StringRef long_string_ref(long_string);
StringRef middle_bit = long_string_ref.substr(10, 23);
AssetCatalogPath from_string_ref(middle_bit);
EXPECT_EQ(from_string_ref, "long/string/with/a/path");
}
TEST_F(AssetCatalogPathTest, length)
{
const AssetCatalogPath one("1");
EXPECT_EQ(1, one.length());
const AssetCatalogPath empty("");
EXPECT_EQ(0, empty.length());
const AssetCatalogPath utf8("some/родитель");
EXPECT_EQ(21, utf8.length()) << "13 characters should be 21 bytes.";
}
TEST_F(AssetCatalogPathTest, name)
{
EXPECT_EQ(StringRefNull(""), AssetCatalogPath("").name());
EXPECT_EQ(StringRefNull("word"), AssetCatalogPath("word").name());
EXPECT_EQ(StringRefNull("Пермь"), AssetCatalogPath("дорога/в/Пермь").name());
EXPECT_EQ(StringRefNull("windows\\paths"),
AssetCatalogPath("these/are/not/windows\\paths").name());
}
TEST_F(AssetCatalogPathTest, comparison_operators)
{
const AssetCatalogPath empty("");
const AssetCatalogPath the_path("the/path");
const AssetCatalogPath the_path_child("the/path/child");
const AssetCatalogPath unrelated_path("unrelated/path");
const AssetCatalogPath other_instance_same_path("the/path");
EXPECT_LT(empty, the_path);
EXPECT_LT(the_path, the_path_child);
EXPECT_LT(the_path, unrelated_path);
EXPECT_EQ(empty, empty) << "Identical empty instances should compare equal.";
EXPECT_EQ(empty, "") << "Comparison to empty string should be possible.";
EXPECT_EQ(the_path, the_path) << "Identical non-empty instances should compare equal.";
EXPECT_EQ(the_path, "the/path") << "Comparison to string should be possible.";
EXPECT_EQ(the_path, other_instance_same_path)
<< "Different instances with equal path should compare equal.";
EXPECT_NE(the_path, the_path_child);
EXPECT_NE(the_path, unrelated_path);
EXPECT_NE(the_path, empty);
EXPECT_FALSE(empty);
EXPECT_TRUE(the_path);
}
TEST_F(AssetCatalogPathTest, move_semantics)
{
AssetCatalogPath source_path("source/path");
EXPECT_TRUE(source_path);
AssetCatalogPath dest_path = std::move(source_path);
EXPECT_FALSE(source_path); /* NOLINT: bugprone-use-after-move */
EXPECT_TRUE(dest_path);
}
TEST_F(AssetCatalogPathTest, concatenation)
{
AssetCatalogPath some_parent("some/родитель");
AssetCatalogPath child = some_parent / "ребенок";
EXPECT_EQ(some_parent, "some/родитель")
<< "Appending a child path should not modify the parent.";
EXPECT_EQ(child, "some/родитель/ребенок");
AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
EXPECT_EQ(appended_compound_path, "some/родитель/ребенок/внук");
AssetCatalogPath empty("");
AssetCatalogPath child_of_the_void = empty / "child";
EXPECT_EQ(child_of_the_void, "child")
<< "Appending to an empty path should not create an initial slash.";
AssetCatalogPath parent_of_the_void = some_parent / empty;
EXPECT_EQ(parent_of_the_void, "some/родитель")
<< "Prepending to an empty path should not create a trailing slash.";
std::string subpath = "child";
AssetCatalogPath concatenated_with_string = some_parent / subpath;
EXPECT_EQ(concatenated_with_string, "some/родитель/child");
}
TEST_F(AssetCatalogPathTest, hashable)
{
AssetCatalogPath path("heyyyyy");
std::set<AssetCatalogPath> path_std_set;
path_std_set.insert(path);
Set<AssetCatalogPath> path_blender_set;
path_blender_set.add(path);
}
TEST_F(AssetCatalogPathTest, stream_operator)
{
AssetCatalogPath path("путь/в/Пермь");
std::stringstream sstream;
sstream << path;
EXPECT_EQ("путь/в/Пермь", sstream.str());
}
TEST_F(AssetCatalogPathTest, is_contained_in)
{
const AssetCatalogPath catpath("simple/path/child");
EXPECT_FALSE(catpath.is_contained_in("unrelated"));
EXPECT_FALSE(catpath.is_contained_in("sim"));
EXPECT_FALSE(catpath.is_contained_in("simple/pathx"));
EXPECT_FALSE(catpath.is_contained_in("simple/path/c"));
EXPECT_FALSE(catpath.is_contained_in("simple/path/child/grandchild"));
EXPECT_FALSE(catpath.is_contained_in("simple/path/"))
<< "Non-normalized paths are not expected to work.";
EXPECT_TRUE(catpath.is_contained_in(""));
EXPECT_TRUE(catpath.is_contained_in("simple"));
EXPECT_TRUE(catpath.is_contained_in("simple/path"));
/* Test with some UTF8 non-ASCII characters. */
AssetCatalogPath some_parent("some/родитель");
AssetCatalogPath child = some_parent / "ребенок";
EXPECT_TRUE(child.is_contained_in(some_parent));
EXPECT_TRUE(child.is_contained_in("some"));
AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
EXPECT_TRUE(appended_compound_path.is_contained_in(some_parent));
EXPECT_TRUE(appended_compound_path.is_contained_in(child));
/* Test "going up" directory-style. */
AssetCatalogPath child_with_dotdot = some_parent / "../../other/hierarchy/part";
EXPECT_TRUE(child_with_dotdot.is_contained_in(some_parent))
<< "dotdot path components should have no meaning";
}
TEST_F(AssetCatalogPathTest, cleanup)
{
{
AssetCatalogPath ugly_path("/ some / родитель / ");
AssetCatalogPath clean_path = ugly_path.cleanup();
EXPECT_EQ(AssetCatalogPath("/ some / родитель / "), ugly_path)
<< "cleanup should not modify the path instance itself";
EXPECT_EQ(AssetCatalogPath("some/родитель"), clean_path);
}
{
AssetCatalogPath double_slashed("some//родитель");
EXPECT_EQ(AssetCatalogPath("some/родитель"), double_slashed.cleanup());
}
{
AssetCatalogPath with_colons("some/key:subkey=value/path");
EXPECT_EQ(AssetCatalogPath("some/key-subkey=value/path"), with_colons.cleanup());
}
{
const AssetCatalogPath with_backslashes("windows\\for\\life");
EXPECT_EQ(AssetCatalogPath("windows/for/life"), with_backslashes.cleanup());
}
{
const AssetCatalogPath with_mixed("windows\\for/life");
EXPECT_EQ(AssetCatalogPath("windows/for/life"), with_mixed.cleanup());
}
{
const AssetCatalogPath with_punctuation("is!/this?/¿valid?");
EXPECT_EQ(AssetCatalogPath("is!/this?/¿valid?"), with_punctuation.cleanup());
}
}
TEST_F(AssetCatalogPathTest, iterate_components)
{
AssetCatalogPath path("путь/в/Пермь");
Vector<std::pair<std::string, bool>> seen_components;
path.iterate_components([&seen_components](StringRef component_name, bool is_last_component) {
std::pair<std::string, bool> parameter_pair = std::make_pair<std::string, bool>(
component_name, bool(is_last_component));
seen_components.append(parameter_pair);
});
ASSERT_EQ(3, seen_components.size());
EXPECT_EQ("путь", seen_components[0].first);
EXPECT_EQ("в", seen_components[1].first);
EXPECT_EQ("Пермь", seen_components[2].first);
EXPECT_FALSE(seen_components[0].second);
EXPECT_FALSE(seen_components[1].second);
EXPECT_TRUE(seen_components[2].second);
}
TEST_F(AssetCatalogPathTest, rebase)
{
AssetCatalogPath path("some/path/to/some/catalog");
EXPECT_EQ(path.rebase("some/path", "new/base"), "new/base/to/some/catalog");
EXPECT_EQ(path.rebase("", "new/base"), "new/base/some/path/to/some/catalog");
EXPECT_EQ(path.rebase("some/path/to/some/catalog", "some/path/to/some/catalog"),
"some/path/to/some/catalog")
<< "Rebasing to itself should not change the path.";
EXPECT_EQ(path.rebase("path/to", "new/base"), "")
<< "Non-matching base path should return empty string to indicate 'NO'.";
/* Empty strings should be handled without crashing or other nasty side-effects. */
AssetCatalogPath empty("");
EXPECT_EQ(empty.rebase("path/to", "new/base"), "");
EXPECT_EQ(empty.rebase("", "new/base"), "new/base");
EXPECT_EQ(empty.rebase("", ""), "");
}
TEST_F(AssetCatalogPathTest, parent)
{
const AssetCatalogPath ascii_path("path/with/missing/parents");
EXPECT_EQ(ascii_path.parent(), "path/with/missing");
const AssetCatalogPath path("путь/в/Пермь/долог/и/далек");
EXPECT_EQ(path.parent(), "путь/в/Пермь/долог/и");
EXPECT_EQ(path.parent().parent(), "путь/в/Пермь/долог");
EXPECT_EQ(path.parent().parent().parent(), "путь/в/Пермь");
const AssetCatalogPath one_level("one");
EXPECT_EQ(one_level.parent(), "");
const AssetCatalogPath empty("");
EXPECT_EQ(empty.parent(), "");
}
} // namespace blender::asset_system::tests

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,161 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "AS_asset_catalog.hh"
#include "AS_asset_catalog_tree.hh"
#include "BLI_path_utils.hh"
#include "testing/testing.h"
#include "asset_library_test_common.hh"
namespace blender::asset_system::tests {
class AssetCatalogTreeTest : public AssetLibraryTestBase, public AssetCatalogTreeTestFunctions {};
TEST_F(AssetCatalogTreeTest, insert_item_into_tree)
{
{
AssetCatalogTree tree;
std::unique_ptr<AssetCatalog> catalog_empty_path = AssetCatalog::from_path("");
tree.insert_item(*catalog_empty_path);
expect_tree_items(tree, {});
}
{
AssetCatalogTree tree;
std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item");
tree.insert_item(*catalog);
expect_tree_items(tree, {"item"});
/* Insert child after parent already exists. */
std::unique_ptr<AssetCatalog> child_catalog = AssetCatalog::from_path("item/child");
tree.insert_item(*catalog);
expect_tree_items(tree, {"item", "item/child"});
std::vector<AssetCatalogPath> expected_paths;
/* Test inserting multi-component sub-path. */
std::unique_ptr<AssetCatalog> grandgrandchild_catalog = AssetCatalog::from_path(
"item/child/grandchild/grandgrandchild");
tree.insert_item(*catalog);
expected_paths = {
"item", "item/child", "item/child/grandchild", "item/child/grandchild/grandgrandchild"};
expect_tree_items(tree, expected_paths);
std::unique_ptr<AssetCatalog> root_level_catalog = AssetCatalog::from_path("root level");
tree.insert_item(*catalog);
expected_paths = {"item",
"item/child",
"item/child/grandchild",
"item/child/grandchild/grandgrandchild",
"root level"};
expect_tree_items(tree, expected_paths);
}
{
AssetCatalogTree tree;
std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item/child");
tree.insert_item(*catalog);
expect_tree_items(tree, {"item", "item/child"});
}
{
AssetCatalogTree tree;
std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("white space");
tree.insert_item(*catalog);
expect_tree_items(tree, {"white space"});
}
{
AssetCatalogTree tree;
std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("/item/white space");
tree.insert_item(*catalog);
expect_tree_items(tree, {"item", "item/white space"});
}
{
AssetCatalogTree tree;
std::unique_ptr<AssetCatalog> catalog_unicode_path = AssetCatalog::from_path("Ružena");
tree.insert_item(*catalog_unicode_path);
expect_tree_items(tree, {"Ružena"});
catalog_unicode_path = AssetCatalog::from_path("Ružena/Ružena");
tree.insert_item(*catalog_unicode_path);
expect_tree_items(tree, {"Ružena", "Ružena/Ružena"});
}
}
TEST_F(AssetCatalogTreeTest, load_single_file_into_tree)
{
AssetCatalogService service(asset_library_root_);
service.load_from_disk(asset_library_root_ + SEP_STR + "blender_assets.cats.txt");
/* Contains not only paths from the CDF but also the missing parents (implicitly defined
* catalogs). */
std::vector<AssetCatalogPath> expected_paths{
"character",
"character/Ellie",
"character/Ellie/backslashes",
"character/Ellie/poselib",
"character/Ellie/poselib/tailslash",
"character/Ellie/poselib/white space",
"character/Ružena",
"character/Ružena/poselib",
"character/Ružena/poselib/face",
"character/Ružena/poselib/hand",
"path", /* Implicit. */
"path/without", /* Implicit. */
"path/without/simplename", /* From CDF. */
};
const std::shared_ptr<const AssetCatalogTree> tree = service.catalog_tree();
expect_tree_items(*tree, expected_paths);
}
TEST_F(AssetCatalogTreeTest, foreach_in_tree)
{
{
AssetCatalogTree tree{};
const std::vector<AssetCatalogPath> no_catalogs{};
expect_tree_items(tree, no_catalogs);
expect_tree_root_items(tree, no_catalogs);
/* Need a root item to check child items. */
std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("something");
tree.insert_item(*catalog);
tree.foreach_root_item([&no_catalogs](const AssetCatalogTreeItem &item) {
expect_tree_item_child_items(item, no_catalogs);
});
}
AssetCatalogService service(asset_library_root_);
service.load_from_disk(asset_library_root_ + SEP_STR + "blender_assets.cats.txt");
std::vector<AssetCatalogPath> expected_root_items{{"character", "path"}};
const std::shared_ptr<const AssetCatalogTree> tree = service.catalog_tree();
expect_tree_root_items(*tree, expected_root_items);
/* Test if the direct children of the root item are what's expected. */
std::vector<std::vector<AssetCatalogPath>> expected_root_child_items = {
/* Children of the "character" root item. */
{"character/Ellie", "character/Ružena"},
/* Children of the "path" root item. */
{"path/without"},
};
int i = 0;
tree->foreach_root_item([&expected_root_child_items, &i](const AssetCatalogTreeItem &item) {
expect_tree_item_child_items(item, expected_root_child_items[i]);
i++;
});
}
} // namespace blender::asset_system::tests

View File

@@ -0,0 +1,380 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "asset_library_service.hh"
#include "BLI_fileops.h" /* For PATH_MAX (at least on Windows). */
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BKE_appdir.hh"
#include "BKE_callbacks.hh"
#include "BKE_gtest_base.hh"
#include "BKE_main.hh"
#include "DNA_asset_types.h"
#include "CLG_log.h"
#include "testing/testing.h"
namespace blender::asset_system::tests {
const UUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
class AssetLibraryServiceTest : public bke::BlenderGTestBase {
public:
CatalogFilePath asset_library_root_;
CatalogFilePath temp_library_path_;
void SetUp() override
{
const std::string test_files_dir = blender::tests::flags_test_asset_dir();
if (test_files_dir.empty()) {
FAIL();
}
asset_library_root_ = test_files_dir + SEP_STR + "asset_library";
temp_library_path_ = "";
}
void TearDown() override
{
AssetLibraryService::destroy();
if (!temp_library_path_.empty()) {
BLI_delete(temp_library_path_.c_str(), true, true);
temp_library_path_ = "";
}
}
/* Register a temporary path, which will be removed at the end of the test.
* The returned path ends in a slash. */
CatalogFilePath use_temp_path()
{
BKE_tempdir_init(nullptr);
const CatalogFilePath tempdir = BKE_tempdir_session();
temp_library_path_ = tempdir + "test-temporary-path" + SEP_STR;
return temp_library_path_;
}
CatalogFilePath create_temp_path()
{
CatalogFilePath path = use_temp_path();
BLI_dir_create_recursive(path.c_str());
return path;
}
};
TEST_F(AssetLibraryServiceTest, get_destroy)
{
AssetLibraryService *const service = AssetLibraryService::get();
EXPECT_EQ(service, AssetLibraryService::get())
<< "Calling twice without destroying in between should return the same instance.";
/* This should not crash. */
AssetLibraryService::destroy();
AssetLibraryService::destroy();
/* NOTE: there used to be a test for the opposite here, that after a call to
* AssetLibraryService::destroy() the above calls should return freshly allocated objects. This
* cannot be reliably tested by just pointer comparison, though. */
}
TEST_F(AssetLibraryServiceTest, library_pointers)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetLibrary *const curfile_lib = service->get_asset_library_current_file();
EXPECT_EQ(lib, service->get_asset_library_on_disk_custom(__func__, asset_library_root_))
<< "Calling twice without destroying in between should return the same instance.";
EXPECT_EQ(curfile_lib, service->get_asset_library_current_file())
<< "Calling twice without destroying in between should return the same instance.";
/* NOTE: there used to be a test for the opposite here, that after a call to
* AssetLibraryService::destroy() the above calls should return freshly allocated objects. This
* cannot be reliably tested by just pointer comparison, though. */
}
TEST_F(AssetLibraryServiceTest, library_from_reference)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const curfile_lib = service->get_asset_library_current_file();
AssetLibraryReference ref{};
ref.type = ASSET_LIBRARY_LOCAL;
EXPECT_EQ(curfile_lib, service->get_asset_library(nullptr, ref))
<< "Getting the local (current file) reference without a main saved on disk should return "
"the current file library";
{
Main dummy_main{};
std::string dummy_filepath = asset_library_root_ + SEP + "dummy.blend";
STRNCPY(dummy_main.filepath, dummy_filepath.c_str());
AssetLibrary *custom_lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetLibrary *tmp_curfile_lib = service->get_asset_library(&dummy_main, ref);
/* Requested a current file library with a (fake) file saved in the same directory as a custom
* asset library. The resulting library should never match the custom asset library, even
* though the paths match. */
EXPECT_NE(custom_lib, tmp_curfile_lib)
<< "Getting an asset library from a local (current file) library reference should never "
"match any custom asset library";
EXPECT_EQ(custom_lib->root_path(), tmp_curfile_lib->root_path());
}
}
TEST_F(AssetLibraryServiceTest, library_path_trailing_slashes)
{
AssetLibraryService *service = AssetLibraryService::get();
char asset_lib_no_slash[PATH_MAX];
char asset_lib_with_slash[PATH_MAX];
STRNCPY(asset_lib_no_slash, asset_library_root_.c_str());
STRNCPY(asset_lib_with_slash, asset_library_root_.c_str());
/* Ensure #asset_lib_no_slash has no trailing slash, regardless of what was passed on the CLI to
* the unit test. */
while (strlen(asset_lib_no_slash) &&
ELEM(asset_lib_no_slash[strlen(asset_lib_no_slash) - 1], SEP, ALTSEP))
{
asset_lib_no_slash[strlen(asset_lib_no_slash) - 1] = '\0';
}
BLI_path_slash_ensure(asset_lib_with_slash, PATH_MAX);
AssetLibrary *const lib_no_slash = service->get_asset_library_on_disk_custom(__func__,
asset_lib_no_slash);
EXPECT_EQ(lib_no_slash,
service->get_asset_library_on_disk_custom(__func__, asset_lib_with_slash))
<< "With or without trailing slash shouldn't matter.";
}
TEST_F(AssetLibraryServiceTest, catalogs_loaded)
{
AssetLibraryService *const service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetCatalogService &cat_service = lib->catalog_service();
const UUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
EXPECT_NE(nullptr, cat_service.find_catalog(UUID_POSES_ELLIE))
<< "Catalogs should be loaded after getting an asset library from disk.";
}
TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs)
{
AssetLibraryService *const service = AssetLibraryService::get();
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Empty AssetLibraryService should have no unsaved catalogs";
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetCatalogService &cat_service = lib->catalog_service();
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Unchanged AssetLibrary should have no unsaved catalogs";
const UUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
cat_service.prune_catalogs_by_id(UUID_POSES_ELLIE);
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Deletion of catalogs via AssetCatalogService should not automatically tag as 'unsaved "
"changes'.";
const UUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed");
AssetCatalog *cat = cat_service.find_catalog(UUID_POSES_RUZENA);
ASSERT_NE(nullptr, cat) << "Catalog " << UUID_POSES_RUZENA << " should be known";
cat_service.tag_has_unsaved_changes(cat);
EXPECT_TRUE(service->has_any_unsaved_catalogs())
<< "Tagging as having unsaved changes of a single catalog service should result in unsaved "
"changes being reported.";
EXPECT_TRUE(cat->flags.has_unsaved_changes);
}
TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs_after_write)
{
const CatalogFilePath writable_dir = create_temp_path(); /* Has trailing slash. */
const CatalogFilePath original_cdf_file = asset_library_root_ + SEP_STR +
"blender_assets.cats.txt";
CatalogFilePath writable_cdf_file = writable_dir + AssetCatalogService::DEFAULT_CATALOG_FILENAME;
BLI_path_slash_native(writable_cdf_file.data());
ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
AssetLibraryService *const service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__, writable_dir);
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Unchanged AssetLibrary should have no unsaved catalogs";
AssetCatalogService &cat_service = lib->catalog_service();
AssetCatalog *cat = cat_service.find_catalog(UUID_POSES_ELLIE);
cat_service.tag_has_unsaved_changes(cat);
EXPECT_TRUE(service->has_any_unsaved_catalogs())
<< "Tagging as having unsaved changes of a single catalog service should result in unsaved "
"changes being reported.";
EXPECT_TRUE(cat->flags.has_unsaved_changes);
cat_service.write_to_disk(writable_dir + "dummy_path.blend");
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Written AssetCatalogService should have no unsaved catalogs";
EXPECT_FALSE(cat->flags.has_unsaved_changes);
}
/**
* Call #AssetLibraryService::move_runtime_current_file_into_on_disk_library() with an on disk
* location that contains no existing asset catalog definition file.
*/
TEST_F(AssetLibraryServiceTest, move_runtime_current_file_into_on_disk_library__empty_directory)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *runtime_lib = service->get_asset_library_current_file();
AssetCatalogService &runtime_catservice = runtime_lib->catalog_service();
/* Catalog created in the runtime lib that should be moved to the on-disk lib. */
AssetCatalog *catalog = runtime_catservice.create_catalog("Some/Catalog/Path");
runtime_catservice.undo_push();
{
EXPECT_TRUE(catalog->flags.has_unsaved_changes);
EXPECT_EQ(nullptr, runtime_catservice.find_catalog(UUID_POSES_ELLIE))
<< "Catalog not expected in the runtime asset library.";
}
{
Main dummy_main{};
std::string dummy_filepath = create_temp_path() + "dummy.blend";
STRNCPY(dummy_main.filepath, dummy_filepath.c_str());
AssetLibraryService::move_runtime_current_file_into_on_disk_library(dummy_main);
AssetLibraryReference ref{};
ref.type = ASSET_LIBRARY_LOCAL;
/* Loads and merges the catalogs from disk. */
AssetLibrary *on_disk_lib = service->get_asset_library(&dummy_main, ref);
AssetCatalogService &on_disk_catservice = on_disk_lib->catalog_service();
/* Can only test the pointer equality here because the implementation keeps the runtime library
* alive until all its contents are moved to the on-disk library. Otherwise the allocator might
* choose the same address for the new on-disk library. Useful for testing, though not
* required. */
EXPECT_NE(on_disk_lib, runtime_lib);
EXPECT_EQ(on_disk_lib->root_path(), temp_library_path_);
/* Check if catalog was moved correctly. */
{
EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id)->path, catalog->path);
/* Compare catalog by pointer. #move_runtime_current_file_into_on_disk_library() doesn't
* guarantee publicly that catalog pointers remain unchanged, but practically code might rely
* on it. Good to know if this breaks. */
EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id), catalog);
/* No writing happened, just merging. */
EXPECT_TRUE(on_disk_catservice.find_catalog(catalog->catalog_id)->flags.has_unsaved_changes);
}
EXPECT_EQ(nullptr, runtime_catservice.find_catalog(UUID_POSES_ELLIE))
<< "Catalog not expected in the on disk asset library.";
/* Check if undo stack was moved correctly. */
{
on_disk_catservice.undo();
const AssetCatalog *ellie_catalog = on_disk_catservice.find_catalog(UUID_POSES_ELLIE);
EXPECT_EQ(nullptr, ellie_catalog) << "This catalog should not be present after undo";
EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id)->path, catalog->path)
<< "This catalog should still be present after undo";
}
/* Force a new current file runtime library to be created. */
EXPECT_NE(service->get_asset_library_current_file(), on_disk_lib);
}
}
/**
* Call #AssetLibraryService::move_runtime_current_file_into_on_disk_library() with an on disk
* location that contains an existing asset catalog definition file.
* Result should be merged libraries.
*/
TEST_F(AssetLibraryServiceTest,
move_runtime_current_file_into_on_disk_library__directory_with_catalogs)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *runtime_lib = service->get_asset_library_current_file();
AssetCatalogService &runtime_catservice = runtime_lib->catalog_service();
/* Catalog created in the runtime lib that should be moved to the on-disk lib. */
AssetCatalog *catalog = runtime_catservice.create_catalog("Some/Catalog/Path");
runtime_catservice.undo_push();
{
EXPECT_TRUE(catalog->flags.has_unsaved_changes);
EXPECT_EQ(nullptr, runtime_catservice.find_catalog(UUID_POSES_ELLIE))
<< "Catalog not expected in the runtime asset library.";
}
{
Main dummy_main{};
std::string dummy_filepath = asset_library_root_ + SEP + "dummy.blend";
STRNCPY(dummy_main.filepath, dummy_filepath.c_str());
AssetLibraryService::move_runtime_current_file_into_on_disk_library(dummy_main);
AssetLibraryReference ref{};
ref.type = ASSET_LIBRARY_LOCAL;
/* Loads and merges the catalogs from disk. */
AssetLibrary *on_disk_lib = service->get_asset_library(&dummy_main, ref);
AssetCatalogService &on_disk_catservice = on_disk_lib->catalog_service();
EXPECT_NE(on_disk_lib, runtime_lib);
EXPECT_EQ(BLI_path_cmp_normalized(on_disk_lib->root_path().c_str(),
(asset_library_root_ + SEP).c_str()),
0);
/* Check if catalog was moved correctly. */
{
EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id)->path, catalog->path);
/* Compare catalog by pointer. #move_runtime_current_file_into_on_disk_library() doesn't
* guarantee publicly that catalog pointers remain unchanged, but practically code might rely
* on it. Good to know if this breaks. */
EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id), catalog);
/* No writing happened, just merging. */
EXPECT_TRUE(on_disk_catservice.find_catalog(catalog->catalog_id)->flags.has_unsaved_changes);
}
/* Check if catalogs have been merged in from disk correctly (by #get_asset_library()). */
{
const AssetCatalog *ellie_catalog = on_disk_catservice.find_catalog(UUID_POSES_ELLIE);
EXPECT_NE(nullptr, ellie_catalog)
<< "Catalogs should be loaded after getting an asset library from disk.";
EXPECT_FALSE(ellie_catalog->flags.has_unsaved_changes);
}
/* Check if undo stack was moved correctly. */
{
on_disk_catservice.undo();
const AssetCatalog *ellie_catalog = on_disk_catservice.find_catalog(UUID_POSES_ELLIE);
EXPECT_EQ(nullptr, ellie_catalog) << "This catalog should not be present after undo";
EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id)->path, catalog->path)
<< "This catalog should still be present after undo";
}
/* Force a new current file runtime library to be created. */
EXPECT_NE(service->get_asset_library_current_file(), on_disk_lib);
}
}
} // namespace blender::asset_system::tests

View File

@@ -0,0 +1,67 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "AS_asset_catalog.hh"
#include "AS_asset_library.hh"
#include "BKE_gtest_base.hh"
#include "asset_library_service.hh"
#include "testing/testing.h"
namespace blender::asset_system::tests {
class AssetLibraryTest : public bke::BlenderGTestBase {
public:
void TearDown() override
{
asset_system::AssetLibraryService::destroy();
}
};
TEST_F(AssetLibraryTest, AS_asset_library_load_from_directory)
{
const std::string test_files_dir = blender::tests::flags_test_asset_dir();
if (test_files_dir.empty()) {
FAIL();
}
/* Load the asset library. */
const std::string library_dirpath = test_files_dir + "/" + "asset_library";
AssetLibrary *library = AS_asset_library_load_from_directory(__func__, library_dirpath.data());
ASSERT_NE(nullptr, library);
/* Check that it can be cast to the C++ type and has a Catalog Service. */
const AssetCatalogService &service = library->catalog_service();
/* Check that the catalogs defined in the library are actually loaded. This just tests one single
* catalog, as that indicates the file has been loaded. Testing that loading went OK is for
* the asset catalog service tests. */
const UUID uuid_poses_ellie("df60e1f6-2259-475b-93d9-69a1b4a8db78");
AssetCatalog *poses_ellie = service.find_catalog(uuid_poses_ellie);
ASSERT_NE(nullptr, poses_ellie) << "unable to find POSES_ELLIE catalog";
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
}
TEST_F(AssetLibraryTest, load_nonexistent_directory)
{
const std::string test_files_dir = blender::tests::flags_test_asset_dir();
if (test_files_dir.empty()) {
FAIL();
}
/* Load the asset library. */
const std::string library_dirpath = test_files_dir + "/" +
"asset_library/this/subdir/does/not/exist";
AssetLibrary *library = AS_asset_library_load_from_directory(__func__, library_dirpath.data());
ASSERT_NE(nullptr, library);
/* Check that it can be cast to the C++ type and has a Catalog Service. */
AssetCatalogService &service = library->catalog_service();
/* Check that the catalog service doesn't have any catalogs. */
EXPECT_TRUE(service.is_empty());
}
} // namespace blender::asset_system::tests

View File

@@ -0,0 +1,166 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include <string>
#include <vector>
#include "AS_asset_catalog.hh"
#include "AS_asset_catalog_tree.hh"
#include "asset_library_service.hh"
#include "BKE_appdir.hh"
#include "BKE_gtest_base.hh"
#include "BLI_fileops.h"
#include "BLI_path_utils.hh"
#include "testing/testing.h"
namespace blender {
namespace asset_system {
class AssetCatalogTree;
class AssetCatalogTreeItem;
class AssetCatalogPath;
} // namespace asset_system
namespace asset_system::tests {
/**
* Functionality to setup and access directories on disk within which asset library related testing
* can be done.
*/
class AssetLibraryTestBase : public bke::BlenderGTestBase {
protected:
std::string asset_library_root_;
std::string temp_library_path_;
void SetUp() override
{
const std::string test_files_dir = blender::tests::flags_test_asset_dir();
if (test_files_dir.empty()) {
FAIL();
}
asset_library_root_ = test_files_dir + SEP_STR + "asset_library";
temp_library_path_ = "";
}
void TearDown() override
{
AssetLibraryService::destroy();
if (!temp_library_path_.empty()) {
BLI_delete(temp_library_path_.c_str(), true, true);
temp_library_path_ = "";
}
}
/* Register a temporary path, which will be removed at the end of the test.
* The returned path ends in a slash. */
std::string use_temp_path()
{
BKE_tempdir_init(nullptr);
const std::string tempdir = BKE_tempdir_session();
temp_library_path_ = tempdir + "test-temporary-path" + SEP_STR;
return temp_library_path_;
}
std::string create_temp_path()
{
std::string path = use_temp_path();
BLI_dir_create_recursive(path.c_str());
return path;
}
};
class AssetCatalogTreeTestFunctions {
public:
/**
* Recursively iterate over all tree items using #AssetCatalogTree::foreach_item() and check if
* the items map exactly to \a expected_paths.
*/
static void expect_tree_items(const AssetCatalogTree &tree,
const std::vector<AssetCatalogPath> &expected_paths);
/**
* Iterate over the root items of \a tree and check if the items map exactly to \a
* expected_paths. Similar to #assert_expected_tree_items() but calls
* #AssetCatalogTree::foreach_root_item() instead of #AssetCatalogTree::foreach_item().
*/
static void expect_tree_root_items(const AssetCatalogTree &tree,
const std::vector<AssetCatalogPath> &expected_paths);
/**
* Iterate over the child items of \a parent_item and check if the items map exactly to \a
* expected_paths. Similar to #assert_expected_tree_items() but calls
* #AssetCatalogTreeItem::foreach_child() instead of #AssetCatalogTree::foreach_item().
*/
static void expect_tree_item_child_items(const AssetCatalogTreeItem &parent_item,
const std::vector<AssetCatalogPath> &expected_paths);
};
static inline void compare_item_with_path(const AssetCatalogPath &expected_path,
const AssetCatalogTreeItem &actual_item)
{
if (expected_path != actual_item.catalog_path().str()) {
/* This will fail, but with a nicer error message than just calling FAIL(). */
EXPECT_EQ(expected_path, actual_item.catalog_path());
return;
}
/* Is the catalog name as expected? "character", "Ellie", ... */
EXPECT_EQ(expected_path.name(), actual_item.get_name());
/* Does the computed number of parents match? */
const std::string expected_path_str = expected_path.str();
const size_t expected_parent_count = std::count(
expected_path_str.begin(), expected_path_str.end(), AssetCatalogPath::SEPARATOR);
EXPECT_EQ(expected_parent_count, actual_item.count_parents());
}
inline void AssetCatalogTreeTestFunctions::expect_tree_items(
const AssetCatalogTree &tree, const std::vector<AssetCatalogPath> &expected_paths)
{
int i = 0;
tree.foreach_item([&](const AssetCatalogTreeItem &actual_item) {
ASSERT_LT(i, expected_paths.size())
<< "More catalogs in tree than expected; did not expect " << actual_item.catalog_path();
compare_item_with_path(expected_paths[i], actual_item);
i++;
});
}
inline void AssetCatalogTreeTestFunctions::expect_tree_root_items(
const AssetCatalogTree &tree, const std::vector<AssetCatalogPath> &expected_paths)
{
int i = 0;
tree.foreach_root_item([&](const AssetCatalogTreeItem &actual_item) {
ASSERT_LT(i, expected_paths.size())
<< "More catalogs in tree root than expected; did not expect "
<< actual_item.catalog_path();
compare_item_with_path(expected_paths[i], actual_item);
i++;
});
}
inline void AssetCatalogTreeTestFunctions::expect_tree_item_child_items(
const AssetCatalogTreeItem &parent_item, const std::vector<AssetCatalogPath> &expected_paths)
{
int i = 0;
parent_item.foreach_child([&](const AssetCatalogTreeItem &actual_item) {
ASSERT_LT(i, expected_paths.size())
<< "More catalogs in tree item than expected; did not expect "
<< actual_item.catalog_path();
compare_item_with_path(expected_paths[i], actual_item);
i++;
});
}
} // namespace asset_system::tests
} // namespace blender

View File

@@ -0,0 +1,360 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "asset_library_service.hh"
#include "asset_library_test_common.hh"
#include "AS_asset_representation.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#if defined(WIN32)
# include "BLI_string.h"
#endif
#include "DNA_asset_types.h"
#include "DNA_object_types.h"
#include "ED_asset_mark_clear.hh"
#include "../intern/utils.hh"
#include "testing/testing.h"
namespace blender::asset_system::tests {
/**
* Sets up asset library loading so we have a library to load asset representations into (required
* for some functionality to perform work).
*/
class AssetRepresentationTest : public AssetLibraryTestBase {
public:
AssetLibrary *get_builtin_library_from_type(eAssetLibraryType type)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibraryReference ref{};
ref.type = type;
return service->get_asset_library(nullptr, ref);
}
AssetRepresentation &add_dummy_asset(AssetLibrary &library, StringRef relative_path)
{
std::unique_ptr<AssetMetaData> dummy_metadata = std::make_unique<AssetMetaData>();
return *library
.add_external_on_disk_asset(
relative_path, "Some asset name", 0, std::move(dummy_metadata))
.lock();
}
AssetRepresentation &add_dummy_id_asset(AssetLibrary &library, ID &id)
{
/* Ensure ID is marked as asset (no-op if already marked). */
ed::asset::mark_id(&id);
return *library.add_local_id_asset(id).lock();
}
};
TEST_F(AssetRepresentationTest, library_relative_identifier__id_name_change)
{
Main *bmain = BKE_main_new();
Object *object = BKE_id_new<Object>(bmain, "Before rename");
AssetLibrary *library = get_builtin_library_from_type(ASSET_LIBRARY_LOCAL);
AssetRepresentation &asset = add_dummy_id_asset(*library, object->id);
EXPECT_EQ(asset.library_relative_identifier(), "Object" SEP_STR "Before rename");
BKE_id_rename(*bmain, object->id, "Renamed!");
EXPECT_EQ(asset.library_relative_identifier(), "Object" SEP_STR "Renamed!");
BKE_id_rename(*bmain, object->id, "Name/With\\Slashes/");
EXPECT_EQ(asset.library_relative_identifier(), "Object" SEP_STR "Name/With\\Slashes/");
BKE_main_free(bmain);
}
TEST_F(AssetRepresentationTest, weak_reference__current_file)
{
AssetLibrary *library = get_builtin_library_from_type(ASSET_LIBRARY_LOCAL);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
{
AssetWeakReference weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref.asset_library_type, ASSET_LIBRARY_LOCAL);
EXPECT_EQ(weak_ref.asset_library_identifier, nullptr);
EXPECT_STREQ(weak_ref.relative_asset_identifier, "path/to/an/asset");
}
}
TEST_F(AssetRepresentationTest, weak_reference__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
{
AssetWeakReference weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref.asset_library_type, ASSET_LIBRARY_CUSTOM);
EXPECT_STREQ(weak_ref.asset_library_identifier, "My custom lib");
EXPECT_STREQ(weak_ref.relative_asset_identifier, "path/to/an/asset");
}
}
/* Test if new weak references the ID name changes. */
TEST_F(AssetRepresentationTest, weak_reference__id_name_change)
{
Main *bmain = BKE_main_new();
Object *object = BKE_id_new<Object>(bmain, "Before rename");
AssetLibrary *library = get_builtin_library_from_type(ASSET_LIBRARY_LOCAL);
AssetRepresentation &asset = add_dummy_id_asset(*library, object->id);
{
AssetWeakReference weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref.asset_library_type, ASSET_LIBRARY_LOCAL);
EXPECT_STREQ(weak_ref.asset_library_identifier, nullptr);
EXPECT_STREQ(weak_ref.relative_asset_identifier, "Object" SEP_STR "Before rename");
}
BKE_id_rename(*bmain, object->id, "Renamed!");
{
AssetWeakReference weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref.asset_library_type, ASSET_LIBRARY_LOCAL);
EXPECT_STREQ(weak_ref.asset_library_identifier, nullptr);
EXPECT_STREQ(weak_ref.relative_asset_identifier, "Object" SEP_STR "Renamed!");
}
BKE_id_rename(*bmain, object->id, "Name/With\\Slashes/");
{
AssetWeakReference weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref.asset_library_type, ASSET_LIBRARY_LOCAL);
EXPECT_STREQ(weak_ref.asset_library_identifier, nullptr);
EXPECT_STREQ(weak_ref.relative_asset_identifier, "Object" SEP_STR "Name/With\\Slashes/");
}
BKE_main_free(bmain);
}
TEST_F(AssetRepresentationTest, weak_reference__compare)
{
{
AssetWeakReference a;
AssetWeakReference b;
EXPECT_EQ(a, b);
/* Arbitrary individual member changes to test how it affects the comparison. */
b.asset_library_identifier = "My lib";
/* Asset library identifier should be ignored unless the type is #ASSET_LIBRARY_CUSTOM. */
EXPECT_EQ(a, b);
a.asset_library_identifier = "My lib";
EXPECT_EQ(a, b);
a.asset_library_type = ASSET_LIBRARY_ESSENTIALS;
EXPECT_NE(a, b);
b.asset_library_type = ASSET_LIBRARY_LOCAL;
EXPECT_NE(a, b);
b.asset_library_type = ASSET_LIBRARY_ESSENTIALS;
EXPECT_EQ(a, b);
a.relative_asset_identifier = "Foo";
EXPECT_NE(a, b);
b.relative_asset_identifier = "Bar";
EXPECT_NE(a, b);
a.relative_asset_identifier = "Bar";
EXPECT_EQ(a, b);
/* Make the destructor work. */
a.asset_library_identifier = b.asset_library_identifier = nullptr;
a.relative_asset_identifier = b.relative_asset_identifier = nullptr;
}
{
AssetWeakReference a;
a.asset_library_type = ASSET_LIBRARY_LOCAL;
a.asset_library_identifier = "My custom lib";
a.relative_asset_identifier = "path/to/an/asset";
AssetWeakReference b;
EXPECT_NE(a, b);
b.asset_library_type = ASSET_LIBRARY_LOCAL;
b.asset_library_identifier = "My custom lib";
b.relative_asset_identifier = "path/to/an/asset";
EXPECT_EQ(a, b);
/* Make the destructor work. */
a.asset_library_identifier = b.asset_library_identifier = nullptr;
a.relative_asset_identifier = b.relative_asset_identifier = nullptr;
}
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
AssetWeakReference weak_ref = asset.make_weak_reference();
AssetWeakReference other;
other.asset_library_type = ASSET_LIBRARY_CUSTOM;
other.asset_library_identifier = "My custom lib";
other.relative_asset_identifier = "path/to/an/asset";
EXPECT_EQ(weak_ref, other);
other.relative_asset_identifier = "";
EXPECT_NE(weak_ref, other);
other.relative_asset_identifier = nullptr;
EXPECT_NE(weak_ref, other);
/* Make the destructor work. */
other.asset_library_identifier = nullptr;
other.relative_asset_identifier = nullptr;
}
/* Same but comparing windows and unix style paths. */
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
AssetWeakReference weak_ref = asset.make_weak_reference();
AssetWeakReference other;
other.asset_library_type = ASSET_LIBRARY_CUSTOM;
other.asset_library_identifier = "My custom lib";
other.relative_asset_identifier = "path\\to\\an\\asset";
EXPECT_EQ(weak_ref, other);
other.relative_asset_identifier = "";
EXPECT_NE(weak_ref, other);
other.relative_asset_identifier = nullptr;
EXPECT_NE(weak_ref, other);
/* Make the destructor work. */
other.asset_library_identifier = nullptr;
other.relative_asset_identifier = nullptr;
}
}
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_full_path__current_file)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *library = get_builtin_library_from_type(ASSET_LIBRARY_LOCAL);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
AssetWeakReference weak_ref = asset.make_weak_reference();
std::string resolved_path = service->resolve_asset_weak_reference_to_full_path(weak_ref);
EXPECT_EQ(resolved_path, "");
}
/* #AssetLibraryService::resolve_asset_weak_reference_to_full_path(). */
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_full_path__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
AssetWeakReference weak_ref = asset.make_weak_reference();
std::string expected_path = utils::normalize_path(asset_library_root_ + "/" + "path/") +
"to/an/asset";
std::string resolved_path = service->resolve_asset_weak_reference_to_full_path(weak_ref);
EXPECT_EQ(BLI_path_cmp(resolved_path.c_str(), expected_path.c_str()), 0);
}
TEST_F(AssetRepresentationTest,
weak_reference__resolve_to_full_path__custom_library__windows_pathsep)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path\\to\\an\\asset");
AssetWeakReference weak_ref = asset.make_weak_reference();
std::string expected_path = utils::normalize_path(asset_library_root_ + "\\" + "path\\") +
"to\\an\\asset";
std::string resolved_path = service->resolve_asset_weak_reference_to_full_path(weak_ref);
EXPECT_EQ(BLI_path_cmp(resolved_path.c_str(), expected_path.c_str()), 0);
}
/* #AssetLibraryService::resolve_asset_weak_reference_to_exploded_path(). */
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_exploded_path__current_file)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *library = get_builtin_library_from_type(ASSET_LIBRARY_LOCAL);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
AssetWeakReference weak_ref = asset.make_weak_reference();
std::string expected_full_path = utils::normalize_path("path/to/an/asset", 5);
std::optional<AssetLibraryService::ExplodedPath> resolved_path =
service->resolve_asset_weak_reference_to_exploded_path(weak_ref);
EXPECT_EQ(*resolved_path->full_path, expected_full_path);
EXPECT_EQ(resolved_path->dir_component, "");
EXPECT_EQ(resolved_path->group_component, "path");
/* ID names may contain slashes. */
EXPECT_EQ(resolved_path->name_component, "to/an/asset");
}
/* #AssetLibraryService::resolve_asset_weak_reference_to_exploded_path(). */
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_exploded_path__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "some.blend/Material/asset/name");
AssetWeakReference weak_ref = asset.make_weak_reference();
std::string expected_full_path = utils::normalize_path(asset_library_root_ +
"/some.blend/Material/") +
"asset/name";
std::optional<AssetLibraryService::ExplodedPath> resolved_path =
service->resolve_asset_weak_reference_to_exploded_path(weak_ref);
EXPECT_EQ(BLI_path_cmp(resolved_path->full_path->c_str(), expected_full_path.c_str()), 0);
EXPECT_EQ(BLI_path_cmp_normalized(std::string(resolved_path->dir_component).c_str(),
std::string(asset_library_root_ + "/some.blend").c_str()),
0);
EXPECT_EQ(resolved_path->group_component, "Material");
/* ID names may contain slashes. */
EXPECT_EQ(resolved_path->name_component, "asset/name");
}
/* #AssetLibraryService::resolve_asset_weak_reference_to_exploded_path(). */
TEST_F(AssetRepresentationTest,
weak_reference__resolve_to_exploded_path__custom_library__windows_pathsep)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "some.blend\\Material\\asset/name");
AssetWeakReference weak_ref = asset.make_weak_reference();
std::string expected_full_path = utils::normalize_path(asset_library_root_ +
"\\some.blend\\Material\\") +
"asset/name";
std::optional<AssetLibraryService::ExplodedPath> resolved_path =
service->resolve_asset_weak_reference_to_exploded_path(weak_ref);
EXPECT_EQ(BLI_path_cmp(resolved_path->full_path->c_str(), expected_full_path.c_str()), 0);
EXPECT_EQ(BLI_path_cmp_normalized(std::string(resolved_path->dir_component).c_str(),
std::string(asset_library_root_ + "\\some.blend").c_str()),
0);
EXPECT_EQ(resolved_path->group_component, "Material");
/* ID names may contain slashes. */
EXPECT_EQ(resolved_path->name_component, "asset/name");
}
} // namespace blender::asset_system::tests

View File

@@ -0,0 +1,31 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "testing/testing.h"
#include "AS_essentials_library.hh"
namespace blender::asset_system::tests {
TEST(EssentialsLibraryTest, is_online_essentials_url)
{
EXPECT_FALSE(is_online_essentials_url(""));
EXPECT_FALSE(is_online_essentials_url("https://www.blender.org/asset-library/"));
EXPECT_TRUE(
is_online_essentials_url("https://cdn.extensions.blender.org/asset-libraries/essentials/"));
EXPECT_TRUE(is_online_essentials_url(
"https://cdn.extensions.blender.org/asset-libraries/essentials/_asset-library-meta.json"));
EXPECT_FALSE(
is_online_essentials_url("https://cdn.extensions.blender.org/asset-libraries/essentials"));
EXPECT_FALSE(is_online_essentials_url(
"https://cdn.extensions.blender.org/asset-libraries/essentials_asset-library-meta.json"));
/* http instead of https. */
EXPECT_FALSE(
is_online_essentials_url("http://cdn.extensions.blender.org/asset-libraries/essentials/"));
}
} // namespace blender::asset_system::tests

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "testing/testing.h"
#include "AS_remote_library.hh"
namespace blender::asset_system::tests {
TEST(RemoteLibraryTest, url_ends_with_top_meta_file_name)
{
EXPECT_FALSE(remote_library_url_ends_with_top_meta_file_name(""));
EXPECT_FALSE(remote_library_url_ends_with_top_meta_file_name("_asset-library-meta.json"));
EXPECT_TRUE(remote_library_url_ends_with_top_meta_file_name(
"https://example.com/_asset-library-meta.json"));
EXPECT_FALSE(remote_library_url_ends_with_top_meta_file_name("https://example.com/"));
EXPECT_FALSE(remote_library_url_ends_with_top_meta_file_name("https://example.com/abc"));
EXPECT_FALSE(remote_library_url_ends_with_top_meta_file_name("https://example.com/abc/"));
EXPECT_FALSE(remote_library_url_ends_with_top_meta_file_name(
"https://example.com/_asset-library-meta.json/"));
/* Missing slash. */
EXPECT_FALSE(remote_library_url_ends_with_top_meta_file_name(
"https://example.com_asset-library-meta.json"));
}
} // namespace blender::asset_system::tests