Blender V4.5
asset_library_service_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2020 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
7#include "BLI_fileops.h" /* For PATH_MAX (at least on Windows). */
8#include "BLI_path_utils.hh"
9#include "BLI_string.h"
10
11#include "BKE_appdir.hh"
12#include "BKE_callbacks.hh"
13#include "BKE_main.hh"
14
15#include "DNA_asset_types.h"
16
17#include "CLG_log.h"
18
19#include "testing/testing.h"
20
22
23const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
24
25class AssetLibraryServiceTest : public testing::Test {
26 public:
29
30 static void SetUpTestSuite()
31 {
32 CLG_init();
34 }
35 static void TearDownTestSuite()
36 {
37 CLG_exit();
39 }
40
41 void SetUp() override
42 {
43 const std::string test_files_dir = blender::tests::flags_test_asset_dir();
44 if (test_files_dir.empty()) {
45 FAIL();
46 }
47 asset_library_root_ = test_files_dir + SEP_STR + "asset_library";
49 }
50
51 void TearDown() override
52 {
54
55 if (!temp_library_path_.empty()) {
56 BLI_delete(temp_library_path_.c_str(), true, true);
58 }
59 }
60
61 /* Register a temporary path, which will be removed at the end of the test.
62 * The returned path ends in a slash. */
64 {
65 BKE_tempdir_init(nullptr);
66 const CatalogFilePath tempdir = BKE_tempdir_session();
67 temp_library_path_ = tempdir + "test-temporary-path" + SEP_STR;
68 return temp_library_path_;
69 }
70
72 {
74 BLI_dir_create_recursive(path.c_str());
75 return path;
76 }
77};
78
80{
83 << "Calling twice without destroying in between should return the same instance.";
84
85 /* This should not crash. */
88
89 /* NOTE: there used to be a test for the opposite here, that after a call to
90 * AssetLibraryService::destroy() the above calls should return freshly allocated objects. This
91 * cannot be reliably tested by just pointer comparison, though. */
92}
93
95{
97
98 AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
99 asset_library_root_);
100 AssetLibrary *const curfile_lib = service->get_asset_library_current_file();
101
102 EXPECT_EQ(lib, service->get_asset_library_on_disk_custom(__func__, asset_library_root_))
103 << "Calling twice without destroying in between should return the same instance.";
104 EXPECT_EQ(curfile_lib, service->get_asset_library_current_file())
105 << "Calling twice without destroying in between should return the same instance.";
106
107 /* NOTE: there used to be a test for the opposite here, that after a call to
108 * AssetLibraryService::destroy() the above calls should return freshly allocated objects. This
109 * cannot be reliably tested by just pointer comparison, though. */
110}
111
112TEST_F(AssetLibraryServiceTest, library_from_reference)
113{
115
116 AssetLibrary *const curfile_lib = service->get_asset_library_current_file();
117
120 EXPECT_EQ(curfile_lib, service->get_asset_library(nullptr, ref))
121 << "Getting the local (current file) reference without a main saved on disk should return "
122 "the current file library";
123
124 {
125 Main dummy_main{};
126 std::string dummy_filepath = asset_library_root_ + SEP + "dummy.blend";
127 STRNCPY(dummy_main.filepath, dummy_filepath.c_str());
128
129 AssetLibrary *custom_lib = service->get_asset_library_on_disk_custom(__func__,
130 asset_library_root_);
131 AssetLibrary *tmp_curfile_lib = service->get_asset_library(&dummy_main, ref);
132
133 /* Requested a current file library with a (fake) file saved in the same directory as a custom
134 * asset library. The resulting library should never match the custom asset library, even
135 * though the paths match. */
136
137 EXPECT_NE(custom_lib, tmp_curfile_lib)
138 << "Getting an asset library from a local (current file) library reference should never "
139 "match any custom asset library";
140 EXPECT_EQ(custom_lib->root_path(), tmp_curfile_lib->root_path());
141 }
142}
143
144TEST_F(AssetLibraryServiceTest, library_path_trailing_slashes)
145{
147
148 char asset_lib_no_slash[PATH_MAX];
149 char asset_lib_with_slash[PATH_MAX];
150 STRNCPY(asset_lib_no_slash, asset_library_root_.c_str());
151 STRNCPY(asset_lib_with_slash, asset_library_root_.c_str());
152
153 /* Ensure #asset_lib_no_slash has no trailing slash, regardless of what was passed on the CLI to
154 * the unit test. */
155 while (strlen(asset_lib_no_slash) &&
156 ELEM(asset_lib_no_slash[strlen(asset_lib_no_slash) - 1], SEP, ALTSEP))
157 {
158 asset_lib_no_slash[strlen(asset_lib_no_slash) - 1] = '\0';
159 }
160
161 BLI_path_slash_ensure(asset_lib_with_slash, PATH_MAX);
162
163 AssetLibrary *const lib_no_slash = service->get_asset_library_on_disk_custom(__func__,
164 asset_lib_no_slash);
165
166 EXPECT_EQ(lib_no_slash,
167 service->get_asset_library_on_disk_custom(__func__, asset_lib_with_slash))
168 << "With or without trailing slash shouldn't matter.";
169}
170
172{
174 AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
175 asset_library_root_);
176 AssetCatalogService &cat_service = lib->catalog_service();
177
178 const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
179 EXPECT_NE(nullptr, cat_service.find_catalog(UUID_POSES_ELLIE))
180 << "Catalogs should be loaded after getting an asset library from disk.";
181}
182
183TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs)
184{
186 EXPECT_FALSE(service->has_any_unsaved_catalogs())
187 << "Empty AssetLibraryService should have no unsaved catalogs";
188
189 AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
190 asset_library_root_);
191 AssetCatalogService &cat_service = lib->catalog_service();
192 EXPECT_FALSE(service->has_any_unsaved_catalogs())
193 << "Unchanged AssetLibrary should have no unsaved catalogs";
194
195 const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
197 EXPECT_FALSE(service->has_any_unsaved_catalogs())
198 << "Deletion of catalogs via AssetCatalogService should not automatically tag as 'unsaved "
199 "changes'.";
200
201 const bUUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed");
202 AssetCatalog *cat = cat_service.find_catalog(UUID_POSES_RUZENA);
203 ASSERT_NE(nullptr, cat) << "Catalog " << UUID_POSES_RUZENA << " should be known";
204
205 cat_service.tag_has_unsaved_changes(cat);
206 EXPECT_TRUE(service->has_any_unsaved_catalogs())
207 << "Tagging as having unsaved changes of a single catalog service should result in unsaved "
208 "changes being reported.";
209 EXPECT_TRUE(cat->flags.has_unsaved_changes);
210}
211
212TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs_after_write)
213{
214 const CatalogFilePath writable_dir = create_temp_path(); /* Has trailing slash. */
215 const CatalogFilePath original_cdf_file = asset_library_root_ + SEP_STR +
216 "blender_assets.cats.txt";
217 CatalogFilePath writable_cdf_file = writable_dir + AssetCatalogService::DEFAULT_CATALOG_FILENAME;
218 BLI_path_slash_native(writable_cdf_file.data());
219 ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
220
222 AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__, writable_dir);
223
224 EXPECT_FALSE(service->has_any_unsaved_catalogs())
225 << "Unchanged AssetLibrary should have no unsaved catalogs";
226
227 AssetCatalogService &cat_service = lib->catalog_service();
228 AssetCatalog *cat = cat_service.find_catalog(UUID_POSES_ELLIE);
229
230 cat_service.tag_has_unsaved_changes(cat);
231
232 EXPECT_TRUE(service->has_any_unsaved_catalogs())
233 << "Tagging as having unsaved changes of a single catalog service should result in unsaved "
234 "changes being reported.";
235 EXPECT_TRUE(cat->flags.has_unsaved_changes);
236
237 cat_service.write_to_disk(writable_dir + "dummy_path.blend");
238 EXPECT_FALSE(service->has_any_unsaved_catalogs())
239 << "Written AssetCatalogService should have no unsaved catalogs";
240 EXPECT_FALSE(cat->flags.has_unsaved_changes);
241}
242
245TEST_F(AssetLibraryServiceTest, move_runtime_current_file_into_on_disk_library__empty_directory)
246{
248
249 AssetLibrary *runtime_lib = service->get_asset_library_current_file();
250 AssetCatalogService &runtime_catservice = runtime_lib->catalog_service();
251
252 /* Catalog created in the runtime lib that should be moved to the on-disk lib. */
253 AssetCatalog *catalog = runtime_catservice.create_catalog("Some/Catalog/Path");
254 runtime_catservice.undo_push();
255
256 {
257 EXPECT_TRUE(catalog->flags.has_unsaved_changes);
258
259 EXPECT_EQ(nullptr, runtime_catservice.find_catalog(UUID_POSES_ELLIE))
260 << "Catalog not expected in the runtime asset library.";
261 }
262
263 {
264 Main dummy_main{};
265 std::string dummy_filepath = create_temp_path() + "dummy.blend";
266 STRNCPY(dummy_main.filepath, dummy_filepath.c_str());
267
269
272
273 /* Loads and merges the catalogs from disk. */
274 AssetLibrary *on_disk_lib = service->get_asset_library(&dummy_main, ref);
275 AssetCatalogService &on_disk_catservice = on_disk_lib->catalog_service();
276
277 /* Can only test the pointer equality here because the implementation keeps the runtime library
278 * alive until all its contents are moved to the on-disk library. Otherwise the allocator might
279 * choose the same address for the new on-disk library. Useful for testing, though not
280 * required. */
281 EXPECT_NE(on_disk_lib, runtime_lib);
282 EXPECT_EQ(on_disk_lib->root_path(), temp_library_path_);
283
284 /* Check if catalog was moved correctly. */
285 {
286 EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id)->path, catalog->path);
287 /* Compare catalog by pointer. #move_runtime_current_file_into_on_disk_library() doesn't
288 * guarantee publicly that catalog pointers remain unchanged, but practically code might rely
289 * on it. Good to know if this breaks. */
290 EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id), catalog);
291 /* No writing happened, just merging. */
292 EXPECT_TRUE(on_disk_catservice.find_catalog(catalog->catalog_id)->flags.has_unsaved_changes);
293 }
294
295 EXPECT_EQ(nullptr, runtime_catservice.find_catalog(UUID_POSES_ELLIE))
296 << "Catalog not expected in the on disk asset library.";
297
298 /* Check if undo stack was moved correctly. */
299 {
300 on_disk_catservice.undo();
301 const AssetCatalog *ellie_catalog = on_disk_catservice.find_catalog(UUID_POSES_ELLIE);
302 EXPECT_EQ(nullptr, ellie_catalog) << "This catalog should not be present after undo";
303 EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id)->path, catalog->path)
304 << "This catalog should still be present after undo";
305 }
306
307 /* Force a new current file runtime library to be created. */
308 EXPECT_NE(service->get_asset_library_current_file(), on_disk_lib);
309 }
310}
311
316 move_runtime_current_file_into_on_disk_library__directory_with_catalogs)
317{
319
320 AssetLibrary *runtime_lib = service->get_asset_library_current_file();
321 AssetCatalogService &runtime_catservice = runtime_lib->catalog_service();
322
323 /* Catalog created in the runtime lib that should be moved to the on-disk lib. */
324 AssetCatalog *catalog = runtime_catservice.create_catalog("Some/Catalog/Path");
325 runtime_catservice.undo_push();
326
327 {
328 EXPECT_TRUE(catalog->flags.has_unsaved_changes);
329
330 EXPECT_EQ(nullptr, runtime_catservice.find_catalog(UUID_POSES_ELLIE))
331 << "Catalog not expected in the runtime asset library.";
332 }
333
334 {
335 Main dummy_main{};
336 std::string dummy_filepath = asset_library_root_ + SEP + "dummy.blend";
337 STRNCPY(dummy_main.filepath, dummy_filepath.c_str());
338
340
343
344 /* Loads and merges the catalogs from disk. */
345 AssetLibrary *on_disk_lib = service->get_asset_library(&dummy_main, ref);
346 AssetCatalogService &on_disk_catservice = on_disk_lib->catalog_service();
347
348 EXPECT_NE(on_disk_lib, runtime_lib);
350 (asset_library_root_ + SEP).c_str()),
351 0);
352
353 /* Check if catalog was moved correctly. */
354 {
355 EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id)->path, catalog->path);
356 /* Compare catalog by pointer. #move_runtime_current_file_into_on_disk_library() doesn't
357 * guarantee publicly that catalog pointers remain unchanged, but practically code might rely
358 * on it. Good to know if this breaks. */
359 EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id), catalog);
360 /* No writing happened, just merging. */
361 EXPECT_TRUE(on_disk_catservice.find_catalog(catalog->catalog_id)->flags.has_unsaved_changes);
362 }
363
364 /* Check if catalogs have been merged in from disk correctly (by #get_asset_library()). */
365 {
366 const AssetCatalog *ellie_catalog = on_disk_catservice.find_catalog(UUID_POSES_ELLIE);
367 EXPECT_NE(nullptr, ellie_catalog)
368 << "Catalogs should be loaded after getting an asset library from disk.";
369 EXPECT_FALSE(ellie_catalog->flags.has_unsaved_changes);
370 }
371
372 /* Check if undo stack was moved correctly. */
373 {
374 on_disk_catservice.undo();
375 const AssetCatalog *ellie_catalog = on_disk_catservice.find_catalog(UUID_POSES_ELLIE);
376 EXPECT_EQ(nullptr, ellie_catalog) << "This catalog should not be present after undo";
377 EXPECT_EQ(on_disk_catservice.find_catalog(catalog->catalog_id)->path, catalog->path)
378 << "This catalog should still be present after undo";
379 }
380
381 /* Force a new current file runtime library to be created. */
382 EXPECT_NE(service->get_asset_library_current_file(), on_disk_lib);
383 }
384}
385
386} // namespace blender::asset_system::tests
void BKE_tempdir_init(const char *userdir)
Definition appdir.cc:1197
void BKE_callback_global_finalize()
Definition callbacks.cc:107
void BKE_callback_global_init()
Definition callbacks.cc:102
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
File and directory operations.
int BLI_copy(const char *path_src, const char *path_dst) ATTR_NONNULL()
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:391
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL()
#define PATH_MAX
Definition BLI_fileops.h:26
#define ALTSEP
void BLI_path_slash_native(char *path) ATTR_NONNULL(1)
#define SEP
int BLI_path_cmp_normalized(const char *p1, const char *p2) ATTR_NONNULL(1
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
#define ELEM(...)
void CLG_exit(void)
Definition clog.c:704
void CLG_init(void)
Definition clog.c:697
@ ASSET_LIBRARY_LOCAL
struct bUUID bUUID
Universally Unique Identifier according to RFC4122.
constexpr const char * c_str() const
AssetCatalog * find_catalog(CatalogID catalog_id) const
bool write_to_disk(const CatalogFilePath &blend_file_path)
AssetCatalog * create_catalog(const AssetCatalogPath &catalog_path)
void tag_has_unsaved_changes(AssetCatalog *edited_catalog=nullptr)
static const CatalogFilePath DEFAULT_CATALOG_FILENAME
struct blender::asset_system::AssetCatalog::Flags flags
AssetLibrary * get_asset_library(const Main *bmain, const AssetLibraryReference &library_reference)
static AssetLibrary * move_runtime_current_file_into_on_disk_library(const Main &bmain)
AssetLibrary * get_asset_library_on_disk_custom(StringRef name, StringRefNull root_path)
AssetCatalogService & catalog_service() const
TEST_F(AssetCatalogTest, load_single_file)
const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78")
const bUUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed")
char filepath[1024]
Definition BKE_main.hh:155
void * BKE_tempdir_session
Definition stubs.c:38
#define SEP_STR
Definition unit.cc:39
static DynamicLibrary lib