Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user