Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/tests/core/io/test_resource.h
21520 views
1
/**************************************************************************/
2
/* test_resource.h */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#pragma once
32
33
#include "core/io/resource.h"
34
#include "core/io/resource_loader.h"
35
#include "core/io/resource_saver.h"
36
#include "scene/main/node.h"
37
38
#include "thirdparty/doctest/doctest.h"
39
40
#include "tests/test_macros.h"
41
42
#include <functional>
43
44
namespace TestResource {
45
46
enum TestDuplicateMode {
47
TEST_MODE_RESOURCE_DUPLICATE_SHALLOW,
48
TEST_MODE_RESOURCE_DUPLICATE_DEEP,
49
TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE,
50
TEST_MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE,
51
TEST_MODE_VARIANT_DUPLICATE_SHALLOW,
52
TEST_MODE_VARIANT_DUPLICATE_DEEP,
53
TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE,
54
};
55
56
class DuplicateGuineaPigData : public Object {
57
GDSOFTCLASS(DuplicateGuineaPigData, Object)
58
59
public:
60
const Variant SENTINEL_1 = "A";
61
const Variant SENTINEL_2 = 645;
62
const Variant SENTINEL_3 = StringName("X");
63
const Variant SENTINEL_4 = true;
64
65
Ref<Resource> SUBRES_1 = memnew(Resource);
66
Ref<Resource> SUBRES_2 = memnew(Resource);
67
Ref<Resource> SUBRES_3 = memnew(Resource);
68
Ref<Resource> SUBRES_SL_1 = memnew(Resource);
69
Ref<Resource> SUBRES_SL_2 = memnew(Resource);
70
Ref<Resource> SUBRES_SL_3 = memnew(Resource);
71
72
Variant obj; // Variant helps with lifetime so duplicates pointing to the same don't try to double-free it.
73
Array arr;
74
Dictionary dict;
75
Variant packed; // A PackedByteArray, but using Variant to be able to tell if the array is shared or not.
76
Ref<Resource> subres;
77
Ref<Resource> subres_sl;
78
79
void set_defaults() {
80
SUBRES_1->set_name("juan");
81
SUBRES_2->set_name("you");
82
SUBRES_3->set_name("tree");
83
SUBRES_SL_1->set_name("maybe_scene_local");
84
SUBRES_SL_2->set_name("perhaps_local_to_scene");
85
SUBRES_SL_3->set_name("sometimes_locality_scenial");
86
87
// To try some cases of internal and external.
88
SUBRES_1->set_path_cache("");
89
SUBRES_2->set_path_cache("local://hehe");
90
SUBRES_3->set_path_cache("res://some.tscn::1");
91
DEV_ASSERT(SUBRES_1->is_built_in());
92
DEV_ASSERT(SUBRES_2->is_built_in());
93
DEV_ASSERT(SUBRES_3->is_built_in());
94
SUBRES_SL_1->set_path_cache("res://thing.scn");
95
SUBRES_SL_2->set_path_cache("C:/not/really/possible/but/still/external");
96
SUBRES_SL_3->set_path_cache("/this/neither");
97
DEV_ASSERT(!SUBRES_SL_1->is_built_in());
98
DEV_ASSERT(!SUBRES_SL_2->is_built_in());
99
DEV_ASSERT(!SUBRES_SL_3->is_built_in());
100
101
obj = memnew(Object);
102
103
// Construct enough cases to test deep recursion involving resources;
104
// we mix some primitive values with recurses nested in different ways,
105
// acting as array values and dictionary keys and values, some of those
106
// being marked as scene-local when for subcases where scene-local is relevant.
107
108
arr.push_back(SENTINEL_1);
109
arr.push_back(SUBRES_1);
110
arr.push_back(SUBRES_SL_1);
111
{
112
Dictionary d;
113
d[SENTINEL_2] = SENTINEL_3;
114
d[SENTINEL_4] = SUBRES_2;
115
d[SUBRES_3] = SUBRES_SL_2;
116
d[SUBRES_SL_3] = SUBRES_1;
117
arr.push_back(d);
118
}
119
120
dict[SENTINEL_4] = SENTINEL_1;
121
dict[SENTINEL_2] = SUBRES_2;
122
dict[SUBRES_3] = SUBRES_SL_1;
123
dict[SUBRES_SL_2] = SUBRES_1;
124
{
125
Array a;
126
a.push_back(SENTINEL_3);
127
a.push_back(SUBRES_2);
128
a.push_back(SUBRES_SL_3);
129
dict[SENTINEL_4] = a;
130
}
131
132
packed = PackedByteArray{ 0xaa, 0xbb, 0xcc };
133
134
subres = SUBRES_1;
135
subres_sl = SUBRES_SL_1;
136
}
137
138
void verify_empty() const {
139
CHECK(obj.get_type() == Variant::NIL);
140
CHECK(arr.size() == 0);
141
CHECK(dict.size() == 0);
142
CHECK(packed.get_type() == Variant::NIL);
143
CHECK(subres.is_null());
144
}
145
146
void verify_duplication(const DuplicateGuineaPigData *p_orig, uint32_t p_property_usage, TestDuplicateMode p_test_mode, ResourceDeepDuplicateMode p_deep_mode) const {
147
if (!(p_property_usage & PROPERTY_USAGE_STORAGE)) {
148
verify_empty();
149
return;
150
}
151
152
// To see if each resource involved is copied once at most,
153
// and then the reference to the duplicate reused.
154
HashMap<Resource *, Resource *> duplicates;
155
156
auto _verify_resource = [&](const Ref<Resource> &p_dupe_res, const Ref<Resource> &p_orig_res, bool p_is_property = false) {
157
bool expect_true_copy = (p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP && p_orig_res->is_built_in()) ||
158
(p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE && p_deep_mode == RESOURCE_DEEP_DUPLICATE_INTERNAL && p_orig_res->is_built_in()) ||
159
(p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE && p_deep_mode == RESOURCE_DEEP_DUPLICATE_ALL) ||
160
(p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE && p_orig_res->is_local_to_scene()) ||
161
(p_test_mode == TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE && p_deep_mode == RESOURCE_DEEP_DUPLICATE_INTERNAL && p_orig_res->is_built_in()) ||
162
(p_test_mode == TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE && p_deep_mode == RESOURCE_DEEP_DUPLICATE_ALL);
163
164
if (expect_true_copy) {
165
if (p_deep_mode == RESOURCE_DEEP_DUPLICATE_NONE) {
166
expect_true_copy = false;
167
} else if (p_deep_mode == RESOURCE_DEEP_DUPLICATE_INTERNAL) {
168
expect_true_copy = p_orig_res->is_built_in();
169
}
170
}
171
172
if (p_is_property) {
173
if ((p_property_usage & PROPERTY_USAGE_ALWAYS_DUPLICATE)) {
174
expect_true_copy = true;
175
} else if ((p_property_usage & PROPERTY_USAGE_NEVER_DUPLICATE)) {
176
expect_true_copy = false;
177
}
178
}
179
180
if (expect_true_copy) {
181
CHECK(p_dupe_res != p_orig_res);
182
CHECK(p_dupe_res->get_name() == p_orig_res->get_name());
183
if (duplicates.has(p_orig_res.ptr())) {
184
CHECK(duplicates[p_orig_res.ptr()] == p_dupe_res.ptr());
185
} else {
186
duplicates[p_orig_res.ptr()] = p_dupe_res.ptr();
187
}
188
} else {
189
CHECK(p_dupe_res == p_orig_res);
190
}
191
};
192
193
std::function<void(const Variant &p_a, const Variant &p_b)> _verify_deep_copied_variants = [&](const Variant &p_a, const Variant &p_b) {
194
CHECK(p_a.get_type() == p_b.get_type());
195
const Ref<Resource> &res_a = p_a;
196
const Ref<Resource> &res_b = p_b;
197
if (res_a.is_valid()) {
198
_verify_resource(res_a, res_b);
199
} else if (p_a.get_type() == Variant::ARRAY) {
200
const Array &arr_a = p_a;
201
const Array &arr_b = p_b;
202
CHECK(!arr_a.is_same_instance(arr_b));
203
CHECK(arr_a.size() == arr_b.size());
204
for (int i = 0; i < arr_a.size(); i++) {
205
_verify_deep_copied_variants(arr_a[i], arr_b[i]);
206
}
207
} else if (p_a.get_type() == Variant::DICTIONARY) {
208
const Dictionary &dict_a = p_a;
209
const Dictionary &dict_b = p_b;
210
CHECK(!dict_a.is_same_instance(dict_b));
211
CHECK(dict_a.size() == dict_b.size());
212
for (int i = 0; i < dict_a.size(); i++) {
213
_verify_deep_copied_variants(dict_a.get_key_at_index(i), dict_b.get_key_at_index(i));
214
_verify_deep_copied_variants(dict_a.get_value_at_index(i), dict_b.get_value_at_index(i));
215
}
216
} else {
217
CHECK(p_a == p_b);
218
}
219
};
220
221
CHECK(this != p_orig);
222
223
CHECK((Object *)obj == (Object *)p_orig->obj);
224
225
bool expect_true_copy = p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP ||
226
p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE ||
227
p_test_mode == TEST_MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE ||
228
p_test_mode == TEST_MODE_VARIANT_DUPLICATE_DEEP ||
229
p_test_mode == TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE;
230
if (expect_true_copy) {
231
_verify_deep_copied_variants(arr, p_orig->arr);
232
_verify_deep_copied_variants(dict, p_orig->dict);
233
CHECK(!packed.identity_compare(p_orig->packed));
234
} else {
235
CHECK(arr.is_same_instance(p_orig->arr));
236
CHECK(dict.is_same_instance(p_orig->dict));
237
CHECK(packed.identity_compare(p_orig->packed));
238
}
239
240
_verify_resource(subres, p_orig->subres, true);
241
_verify_resource(subres_sl, p_orig->subres_sl, true);
242
}
243
244
void enable_scene_local_subresources() {
245
SUBRES_SL_1->set_local_to_scene(true);
246
SUBRES_SL_2->set_local_to_scene(true);
247
SUBRES_SL_3->set_local_to_scene(true);
248
}
249
250
virtual ~DuplicateGuineaPigData() {
251
Object *obj_ptr = obj.get_validated_object();
252
if (obj_ptr) {
253
memdelete(obj_ptr);
254
}
255
}
256
};
257
258
#define DEFINE_DUPLICATE_GUINEA_PIG(m_class_name, m_property_usage) \
259
class m_class_name : public Resource { \
260
GDCLASS(m_class_name, Resource) \
261
\
262
DuplicateGuineaPigData data; \
263
\
264
public: \
265
void set_obj(Object *p_obj) { \
266
data.obj = p_obj; \
267
} \
268
Object *get_obj() const { \
269
return data.obj; \
270
} \
271
\
272
void set_arr(const Array &p_arr) { \
273
data.arr = p_arr; \
274
} \
275
Array get_arr() const { \
276
return data.arr; \
277
} \
278
\
279
void set_dict(const Dictionary &p_dict) { \
280
data.dict = p_dict; \
281
} \
282
Dictionary get_dict() const { \
283
return data.dict; \
284
} \
285
\
286
void set_packed(const Variant &p_packed) { \
287
data.packed = p_packed; \
288
} \
289
Variant get_packed() const { \
290
return data.packed; \
291
} \
292
\
293
void set_subres(const Ref<Resource> &p_subres) { \
294
data.subres = p_subres; \
295
} \
296
Ref<Resource> get_subres() const { \
297
return data.subres; \
298
} \
299
\
300
void set_subres_sl(const Ref<Resource> &p_subres) { \
301
data.subres_sl = p_subres; \
302
} \
303
Ref<Resource> get_subres_sl() const { \
304
return data.subres_sl; \
305
} \
306
\
307
void set_defaults() { \
308
data.set_defaults(); \
309
} \
310
\
311
Object *get_data() { \
312
return &data; \
313
} \
314
\
315
void verify_duplication(const Ref<Resource> &p_orig, int p_test_mode, int p_deep_mode) const { \
316
const DuplicateGuineaPigData *orig_data = Object::cast_to<DuplicateGuineaPigData>(p_orig->call("get_data")); \
317
data.verify_duplication(orig_data, m_property_usage, (TestDuplicateMode)p_test_mode, (ResourceDeepDuplicateMode)p_deep_mode); \
318
} \
319
\
320
protected: \
321
static void _bind_methods() { \
322
ClassDB::bind_method(D_METHOD("set_obj", "obj"), &m_class_name::set_obj); \
323
ClassDB::bind_method(D_METHOD("get_obj"), &m_class_name::get_obj); \
324
\
325
ClassDB::bind_method(D_METHOD("set_arr", "arr"), &m_class_name::set_arr); \
326
ClassDB::bind_method(D_METHOD("get_arr"), &m_class_name::get_arr); \
327
\
328
ClassDB::bind_method(D_METHOD("set_dict", "dict"), &m_class_name::set_dict); \
329
ClassDB::bind_method(D_METHOD("get_dict"), &m_class_name::get_dict); \
330
\
331
ClassDB::bind_method(D_METHOD("set_packed", "packed"), &m_class_name::set_packed); \
332
ClassDB::bind_method(D_METHOD("get_packed"), &m_class_name::get_packed); \
333
\
334
ClassDB::bind_method(D_METHOD("set_subres", "subres"), &m_class_name::set_subres); \
335
ClassDB::bind_method(D_METHOD("get_subres"), &m_class_name::get_subres); \
336
\
337
ClassDB::bind_method(D_METHOD("set_subres_sl", "subres"), &m_class_name::set_subres_sl); \
338
ClassDB::bind_method(D_METHOD("get_subres_sl"), &m_class_name::get_subres_sl); \
339
\
340
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "obj", PROPERTY_HINT_NONE, "", m_property_usage), "set_obj", "get_obj"); \
341
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "arr", PROPERTY_HINT_NONE, "", m_property_usage), "set_arr", "get_arr"); \
342
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "dict", PROPERTY_HINT_NONE, "", m_property_usage), "set_dict", "get_dict"); \
343
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packed", PROPERTY_HINT_NONE, "", m_property_usage), "set_packed", "get_packed"); \
344
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "subres", PROPERTY_HINT_NONE, "", m_property_usage), "set_subres", "get_subres"); \
345
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "subres_sl", PROPERTY_HINT_NONE, "", m_property_usage), "set_subres_sl", "get_subres_sl"); \
346
\
347
ClassDB::bind_method(D_METHOD("set_defaults"), &m_class_name::set_defaults); \
348
ClassDB::bind_method(D_METHOD("get_data"), &m_class_name::get_data); \
349
ClassDB::bind_method(D_METHOD("verify_duplication", "orig", "test_mode", "deep_mode"), &m_class_name::verify_duplication); \
350
} \
351
\
352
public: \
353
static m_class_name *register_and_instantiate() { \
354
static bool registered = false; \
355
if (!registered) { \
356
GDREGISTER_CLASS(m_class_name); \
357
registered = true; \
358
} \
359
return memnew(m_class_name); \
360
} \
361
};
362
363
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_None, PROPERTY_USAGE_NONE)
364
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Always, PROPERTY_USAGE_ALWAYS_DUPLICATE)
365
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage, PROPERTY_USAGE_STORAGE)
366
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage_Always, (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_ALWAYS_DUPLICATE))
367
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage_Never, (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_NEVER_DUPLICATE))
368
369
TEST_CASE("[Resource] Duplication") {
370
auto _run_test = [](
371
TestDuplicateMode p_test_mode,
372
ResourceDeepDuplicateMode p_deep_mode,
373
Ref<Resource> (*p_duplicate_fn)(const Ref<Resource> &)) -> void {
374
LocalVector<Ref<Resource>> resources = {
375
DuplicateGuineaPig_None::register_and_instantiate(),
376
DuplicateGuineaPig_Always::register_and_instantiate(),
377
DuplicateGuineaPig_Storage::register_and_instantiate(),
378
DuplicateGuineaPig_Storage_Always::register_and_instantiate(),
379
DuplicateGuineaPig_Storage_Never::register_and_instantiate(),
380
};
381
382
for (const Ref<Resource> &orig : resources) {
383
INFO(orig->get_class());
384
385
orig->call("set_defaults");
386
const Ref<Resource> &dupe = p_duplicate_fn(orig);
387
dupe->call("verify_duplication", orig, p_test_mode, p_deep_mode);
388
}
389
};
390
391
SUBCASE("Resource::duplicate(), shallow") {
392
_run_test(
393
TEST_MODE_RESOURCE_DUPLICATE_SHALLOW,
394
RESOURCE_DEEP_DUPLICATE_MAX,
395
[](const Ref<Resource> &p_res) -> Ref<Resource> {
396
return p_res->duplicate(false);
397
});
398
}
399
400
SUBCASE("Resource::duplicate(), deep") {
401
_run_test(
402
TEST_MODE_RESOURCE_DUPLICATE_DEEP,
403
RESOURCE_DEEP_DUPLICATE_MAX,
404
[](const Ref<Resource> &p_res) -> Ref<Resource> {
405
return p_res->duplicate(true);
406
});
407
}
408
409
SUBCASE("Resource::duplicate_deep()") {
410
static int deep_mode = 0;
411
for (deep_mode = 0; deep_mode < RESOURCE_DEEP_DUPLICATE_MAX; deep_mode++) {
412
_run_test(
413
TEST_MODE_RESOURCE_DUPLICATE_DEEP_WITH_MODE,
414
(ResourceDeepDuplicateMode)deep_mode,
415
[](const Ref<Resource> &p_res) -> Ref<Resource> {
416
return p_res->duplicate_deep((ResourceDeepDuplicateMode)deep_mode);
417
});
418
}
419
}
420
421
SUBCASE("Resource::duplicate_for_local_scene()") {
422
static int mark_main_as_local = 0;
423
static int mark_some_subs_as_local = 0;
424
for (mark_main_as_local = 0; mark_main_as_local < 2; ++mark_main_as_local) { // Whether main is local-to-scene shouldn't matter.
425
for (mark_some_subs_as_local = 0; mark_some_subs_as_local < 2; ++mark_some_subs_as_local) {
426
_run_test(
427
TEST_MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE,
428
RESOURCE_DEEP_DUPLICATE_MAX,
429
[](const Ref<Resource> &p_res) -> Ref<Resource> {
430
if (mark_main_as_local) {
431
p_res->set_local_to_scene(true);
432
}
433
if (mark_some_subs_as_local) {
434
Object::cast_to<DuplicateGuineaPigData>(p_res->call("get_data"))->enable_scene_local_subresources();
435
}
436
HashMap<Ref<Resource>, Ref<Resource>> remap_cache;
437
Node fake_scene;
438
return p_res->duplicate_for_local_scene(&fake_scene, remap_cache);
439
});
440
}
441
}
442
}
443
444
SUBCASE("Variant::duplicate(), shallow") {
445
_run_test(
446
TEST_MODE_VARIANT_DUPLICATE_SHALLOW,
447
RESOURCE_DEEP_DUPLICATE_MAX,
448
[](const Ref<Resource> &p_res) -> Ref<Resource> {
449
return Variant(p_res).duplicate(false);
450
});
451
}
452
453
SUBCASE("Variant::duplicate(), deep") {
454
_run_test(
455
TEST_MODE_VARIANT_DUPLICATE_DEEP,
456
RESOURCE_DEEP_DUPLICATE_MAX,
457
[](const Ref<Resource> &p_res) -> Ref<Resource> {
458
return Variant(p_res).duplicate(true);
459
});
460
}
461
462
SUBCASE("Variant::duplicate_deep()") {
463
static int deep_mode = 0;
464
for (deep_mode = 0; deep_mode < RESOURCE_DEEP_DUPLICATE_MAX; deep_mode++) {
465
_run_test(
466
TEST_MODE_VARIANT_DUPLICATE_DEEP_WITH_MODE,
467
(ResourceDeepDuplicateMode)deep_mode,
468
[](const Ref<Resource> &p_res) -> Ref<Resource> {
469
return Variant(p_res).duplicate_deep((ResourceDeepDuplicateMode)deep_mode);
470
});
471
}
472
}
473
474
SUBCASE("Via Variant, resource not being the root") {
475
// Variant controls the deep copy, recursing until resources are found, and then
476
// it's Resource who controls the deep copy from it onwards.
477
// Therefore, we have to test if Variant is able to track unique duplicates across
478
// multiple times Resource takes over.
479
// Since the other test cases already prove Resource's mechanism to have at most
480
// one duplicate per resource involved, the test for Variant is simple.
481
482
Ref<Resource> res;
483
res.instantiate();
484
res->set_name("risi");
485
Array a;
486
a.push_back(res);
487
{
488
Dictionary d;
489
d[res] = res;
490
a.push_back(d);
491
}
492
493
Array dupe_a;
494
Ref<Resource> dupe_res;
495
496
SUBCASE("Variant::duplicate(), shallow") {
497
dupe_a = Variant(a).duplicate(false);
498
// Ensure it's referencing the original.
499
dupe_res = dupe_a[0];
500
CHECK(dupe_res == res);
501
}
502
SUBCASE("Variant::duplicate(), deep") {
503
dupe_a = Variant(a).duplicate(true);
504
// Ensure it's referencing the original.
505
dupe_res = dupe_a[0];
506
CHECK(dupe_res == res);
507
}
508
SUBCASE("Variant::duplicate_deep(), no resources") {
509
dupe_a = Variant(a).duplicate_deep(RESOURCE_DEEP_DUPLICATE_NONE);
510
// Ensure it's referencing the original.
511
dupe_res = dupe_a[0];
512
CHECK(dupe_res == res);
513
}
514
SUBCASE("Variant::duplicate_deep(), with resources") {
515
dupe_a = Variant(a).duplicate_deep(RESOURCE_DEEP_DUPLICATE_ALL);
516
// Ensure it's a copy.
517
dupe_res = dupe_a[0];
518
CHECK(dupe_res != res);
519
CHECK(dupe_res->get_name() == "risi");
520
521
// Ensure the map is already gone so we get new instances.
522
Array dupe_a_2 = Variant(a).duplicate_deep(RESOURCE_DEEP_DUPLICATE_ALL);
523
CHECK(dupe_a_2[0] != dupe_a[0]);
524
}
525
526
// Ensure all the usages are of the same resource.
527
CHECK(((Dictionary)dupe_a[1]).get_key_at_index(0) == dupe_res);
528
CHECK(((Dictionary)dupe_a[1]).get_value_at_index(0) == dupe_res);
529
}
530
}
531
532
TEST_CASE("[Resource] Saving and loading") {
533
Ref<Resource> resource = memnew(Resource);
534
resource->set_name("Hello world");
535
resource->set_meta("ExampleMetadata", Vector2i(40, 80));
536
resource->set_meta("string", "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks");
537
Ref<Resource> child_resource = memnew(Resource);
538
child_resource->set_name("I'm a child resource");
539
resource->set_meta("other_resource", child_resource);
540
const String save_path_binary = TestUtils::get_temp_path("resource.res");
541
const String save_path_text = TestUtils::get_temp_path("resource.tres");
542
ResourceSaver::save(resource, save_path_binary);
543
ResourceSaver::save(resource, save_path_text);
544
545
const Ref<Resource> &loaded_resource_binary = ResourceLoader::load(save_path_binary);
546
CHECK_MESSAGE(
547
loaded_resource_binary->get_name() == "Hello world",
548
"The loaded resource name should be equal to the expected value.");
549
CHECK_MESSAGE(
550
loaded_resource_binary->get_meta("ExampleMetadata") == Vector2i(40, 80),
551
"The loaded resource metadata should be equal to the expected value.");
552
CHECK_MESSAGE(
553
loaded_resource_binary->get_meta("string") == "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks",
554
"The loaded resource metadata should be equal to the expected value.");
555
const Ref<Resource> &loaded_child_resource_binary = loaded_resource_binary->get_meta("other_resource");
556
CHECK_MESSAGE(
557
loaded_child_resource_binary->get_name() == "I'm a child resource",
558
"The loaded child resource name should be equal to the expected value.");
559
560
const Ref<Resource> &loaded_resource_text = ResourceLoader::load(save_path_text);
561
CHECK_MESSAGE(
562
loaded_resource_text->get_name() == "Hello world",
563
"The loaded resource name should be equal to the expected value.");
564
CHECK_MESSAGE(
565
loaded_resource_text->get_meta("ExampleMetadata") == Vector2i(40, 80),
566
"The loaded resource metadata should be equal to the expected value.");
567
CHECK_MESSAGE(
568
loaded_resource_text->get_meta("string") == "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks",
569
"The loaded resource metadata should be equal to the expected value.");
570
const Ref<Resource> &loaded_child_resource_text = loaded_resource_text->get_meta("other_resource");
571
CHECK_MESSAGE(
572
loaded_child_resource_text->get_name() == "I'm a child resource",
573
"The loaded child resource name should be equal to the expected value.");
574
}
575
576
TEST_CASE("[Resource] Breaking circular references on save") {
577
Ref<Resource> resource_a = memnew(Resource);
578
resource_a->set_name("A");
579
Ref<Resource> resource_b = memnew(Resource);
580
resource_b->set_name("B");
581
Ref<Resource> resource_c = memnew(Resource);
582
resource_c->set_name("C");
583
resource_a->set_meta("next", resource_b);
584
resource_b->set_meta("next", resource_c);
585
resource_c->set_meta("next", resource_b);
586
587
const String save_path_binary = TestUtils::get_temp_path("resource.res");
588
const String save_path_text = TestUtils::get_temp_path("resource.tres");
589
ResourceSaver::save(resource_a, save_path_binary);
590
// Suppress expected errors caused by the resources above being uncached.
591
ERR_PRINT_OFF;
592
ResourceSaver::save(resource_a, save_path_text);
593
594
const Ref<Resource> &loaded_resource_a_binary = ResourceLoader::load(save_path_binary);
595
ERR_PRINT_ON;
596
CHECK_MESSAGE(
597
loaded_resource_a_binary->get_name() == "A",
598
"The loaded resource name should be equal to the expected value.");
599
const Ref<Resource> &loaded_resource_b_binary = loaded_resource_a_binary->get_meta("next");
600
CHECK_MESSAGE(
601
loaded_resource_b_binary->get_name() == "B",
602
"The loaded child resource name should be equal to the expected value.");
603
const Ref<Resource> &loaded_resource_c_binary = loaded_resource_b_binary->get_meta("next");
604
CHECK_MESSAGE(
605
loaded_resource_c_binary->get_name() == "C",
606
"The loaded child resource name should be equal to the expected value.");
607
CHECK_MESSAGE(
608
!loaded_resource_c_binary->has_meta("next"),
609
"The loaded child resource circular reference should be NULL.");
610
611
const Ref<Resource> &loaded_resource_a_text = ResourceLoader::load(save_path_text);
612
CHECK_MESSAGE(
613
loaded_resource_a_text->get_name() == "A",
614
"The loaded resource name should be equal to the expected value.");
615
const Ref<Resource> &loaded_resource_b_text = loaded_resource_a_text->get_meta("next");
616
CHECK_MESSAGE(
617
loaded_resource_b_text->get_name() == "B",
618
"The loaded child resource name should be equal to the expected value.");
619
const Ref<Resource> &loaded_resource_c_text = loaded_resource_b_text->get_meta("next");
620
CHECK_MESSAGE(
621
loaded_resource_c_text->get_name() == "C",
622
"The loaded child resource name should be equal to the expected value.");
623
CHECK_MESSAGE(
624
!loaded_resource_c_text->has_meta("next"),
625
"The loaded child resource circular reference should be NULL.");
626
627
// Break circular reference to avoid memory leak
628
resource_c->remove_meta("next");
629
}
630
} // namespace TestResource
631
632