test_resource.h 35 KB

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