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