Skip to content

Commit

Permalink
Add tests for resource duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
RandomShaper committed Dec 20, 2024
1 parent e361e29 commit a7283a6
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 21 deletions.
4 changes: 4 additions & 0 deletions core/variant/dictionary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,10 @@ bool Dictionary::is_typed_value() const {
return _p->typed_value.type != Variant::NIL;
}

bool Dictionary::is_same_instance(const Dictionary &p_other) const {
return _p == p_other._p;
}

bool Dictionary::is_same_typed(const Dictionary &p_other) const {
return is_same_typed_key(p_other) && is_same_typed_value(p_other);
}
Expand Down
1 change: 1 addition & 0 deletions core/variant/dictionary.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class Dictionary {
bool is_typed() const;
bool is_typed_key() const;
bool is_typed_value() const;
bool is_same_instance(const Dictionary &p_other) const;
bool is_same_typed(const Dictionary &p_other) const;
bool is_same_typed_key(const Dictionary &p_other) const;
bool is_same_typed_value(const Dictionary &p_other) const;
Expand Down
225 changes: 204 additions & 21 deletions tests/core/io/test_resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,30 +43,213 @@

namespace TestResource {

struct DuplicateGuineaPigData {
Variant obj; // Variant helps with lifetime so duplicates pointing to the same don't try to double-free it.
Array arr;
Dictionary dict;
Variant packed; // A PackedByteArray, but using Variant to be able to tell if the array is shared or not.
Ref<Resource> res;

uint32_t property_usage = PROPERTY_USAGE_NONE;

void set_defaults() {
obj = memnew(Object);
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
dict["A"] = 'A';
dict["B"] = 'B';
dict["C"] = 'C';
packed = PackedByteArray{ 0xaa, 0xbb, 0xcc };
res.instantiate();
}

virtual ~DuplicateGuineaPigData() {
Object *obj_ptr = obj.get_validated_object();
if (obj_ptr) {
memdelete(obj_ptr);
}
}
};

#define DEFINE_DUPLICATE_GUINEA_PIG(m_class_name, m_property_usage) \
class m_class_name : public Resource, public DuplicateGuineaPigData { \
GDCLASS(m_class_name, Resource) \
\
public: \
void set_obj(Object *p_obj) { obj = p_obj; } \
Object *get_obj() const { return obj; } \
\
void set_arr(const Array &p_arr) { arr = p_arr; } \
Array get_arr() const { return arr; } \
\
void set_dict(const Dictionary &p_dict) { dict = p_dict; } \
Dictionary get_dict() const { return dict; } \
\
void set_packed(const Variant &p_packed) { packed = p_packed; } \
Variant get_packed() const { return packed; } \
\
void set_res(const Ref<Resource> &p_res) { res = p_res; } \
Ref<Resource> get_res() const { return res; } \
\
protected: \
static void _bind_methods() { \
ClassDB::bind_method(D_METHOD("set_obj", "obj"), &m_class_name::set_obj); \
ClassDB::bind_method(D_METHOD("get_obj"), &m_class_name::get_obj); \
\
ClassDB::bind_method(D_METHOD("set_arr", "arr"), &m_class_name::set_arr); \
ClassDB::bind_method(D_METHOD("get_arr"), &m_class_name::get_arr); \
\
ClassDB::bind_method(D_METHOD("set_dict", "dict"), &m_class_name::set_dict); \
ClassDB::bind_method(D_METHOD("get_dict"), &m_class_name::get_dict); \
\
ClassDB::bind_method(D_METHOD("set_packed", "packed"), &m_class_name::set_packed); \
ClassDB::bind_method(D_METHOD("get_packed"), &m_class_name::get_packed); \
\
ClassDB::bind_method(D_METHOD("set_res", "res"), &m_class_name::set_res); \
ClassDB::bind_method(D_METHOD("get_res"), &m_class_name::get_res); \
\
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "obj", PROPERTY_HINT_NONE, "", m_property_usage), "set_obj", "get_obj"); \
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "arr", PROPERTY_HINT_NONE, "", m_property_usage), "set_arr", "get_arr"); \
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "dict", PROPERTY_HINT_NONE, "", m_property_usage), "set_dict", "get_dict"); \
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packed", PROPERTY_HINT_NONE, "", m_property_usage), "set_packed", "get_packed"); \
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "res", PROPERTY_HINT_NONE, "", m_property_usage), "set_res", "get_res"); \
} \
\
public: \
static m_class_name *register_and_instantiate() { \
static bool registered = false; \
if (!registered) { \
GDREGISTER_INTERNAL_CLASS(m_class_name); \
registered = true; \
} \
return memnew(m_class_name); \
} \
\
m_class_name() { \
property_usage = m_property_usage; \
} \
};

DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_None, PROPERTY_USAGE_NONE)
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Always, PROPERTY_USAGE_ALWAYS_DUPLICATE)
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage, PROPERTY_USAGE_STORAGE)
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage_Always, (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_ALWAYS_DUPLICATE))
DEFINE_DUPLICATE_GUINEA_PIG(DuplicateGuineaPig_Storage_Never, (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_NEVER_DUPLICATE))

DuplicateGuineaPigData *_get_duplicate_guinea_pig_data(const Ref<Resource> &p_res) {
return dynamic_cast<DuplicateGuineaPigData *>(p_res.ptr());
}

TEST_CASE("[Resource] Duplication") {
Ref<Resource> resource = memnew(Resource);
resource->set_name("Hello world");
Ref<Resource> child_resource = memnew(Resource);
child_resource->set_name("I'm a child resource");
resource->set_meta("other_resource", child_resource);
enum Mode {
MODE_RESOURCE_DUPLICATE_SHALLOW,
MODE_RESOURCE_DUPLICATE_DEEP,
MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE,
};

Ref<Resource> resource_dupe = resource->duplicate();
const Ref<Resource> &resource_dupe_reference = resource_dupe;
resource_dupe->set_name("Changed name");
child_resource->set_name("My name was changed too");
auto _run_test = [](Mode p_mode, Ref<Resource> (*p_duplicate_fn)(const Ref<Resource> &)) -> void {
LocalVector<Ref<Resource>> resources = {
DuplicateGuineaPig_None::register_and_instantiate(),
DuplicateGuineaPig_Always::register_and_instantiate(),
DuplicateGuineaPig_Storage::register_and_instantiate(),
DuplicateGuineaPig_Storage_Always::register_and_instantiate(),
DuplicateGuineaPig_Storage_Never::register_and_instantiate(),
};

CHECK_MESSAGE(
resource_dupe->get_name() == "Changed name",
"Duplicated resource should have the new name.");
CHECK_MESSAGE(
resource_dupe_reference->get_name() == "Changed name",
"Reference to the duplicated resource should have the new name.");
CHECK_MESSAGE(
resource->get_name() == "Hello world",
"Original resource name should not be affected after editing the duplicate's name.");
CHECK_MESSAGE(
Ref<Resource>(resource_dupe->get_meta("other_resource"))->get_name() == "My name was changed too",
"Duplicated resource should share its child resource with the original.");
for (const Ref<Resource> &orig : resources) {
INFO(std::string(String(orig->get_class_name()).utf8().get_data()));

orig->set_name(vformat("resource_%d", Math::rand()));
DuplicateGuineaPigData *orig_data = _get_duplicate_guinea_pig_data(orig);
_get_duplicate_guinea_pig_data(orig)->set_defaults();

const Ref<Resource> &dupe = p_duplicate_fn(orig);
DuplicateGuineaPigData *dupe_data = _get_duplicate_guinea_pig_data(dupe);

bool is_deep = p_mode == MODE_RESOURCE_DUPLICATE_DEEP || p_mode == MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE;

if ((orig_data->property_usage & PROPERTY_USAGE_STORAGE)) {
bool expecting_true_copy = is_deep;

bool expecting_subres_true_copy = (p_mode == MODE_RESOURCE_DUPLICATE_DEEP ||
(p_mode == MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE && orig_data->res->is_local_to_scene()));
if ((orig_data->property_usage & PROPERTY_USAGE_ALWAYS_DUPLICATE)) {
expecting_subres_true_copy = true;
} else if ((orig_data->property_usage & PROPERTY_USAGE_NEVER_DUPLICATE)) {
expecting_subres_true_copy = false;
}

CHECK((Object *)dupe_data->obj == (Object *)orig_data->obj);

if (expecting_true_copy) {
CHECK(!dupe_data->arr.is_same_instance(orig_data->arr));
CHECK(dupe_data->arr == orig_data->arr);

CHECK(!dupe_data->dict.is_same_instance(orig_data->dict));
CHECK(dupe_data->dict == orig_data->dict);

CHECK(!dupe_data->packed.identity_compare(orig_data->packed));
} else {
CHECK(dupe_data->arr.is_same_instance(orig_data->arr));

CHECK(dupe_data->dict.is_same_instance(orig_data->dict));

CHECK(dupe_data->packed.identity_compare(orig_data->packed));
}

if (expecting_subres_true_copy) {
CHECK(dupe_data->res.ptr() != orig_data->res.ptr());
CHECK(dupe_data->res->get_name() == orig_data->res->get_name());
} else {
CHECK(dupe_data->res.ptr() == orig_data->res.ptr());
}
} else {
CHECK(!dupe_data->obj);
CHECK(dupe_data->arr.size() == 0);
CHECK(dupe_data->dict.size() == 0);
CHECK(dupe_data->packed.is_zero());
CHECK(dupe_data->res.is_null());
}
}
};

SUBCASE("Resource::duplicate(), shallow") {
_run_test(
MODE_RESOURCE_DUPLICATE_SHALLOW,
[](const Ref<Resource> &p_res) -> Ref<Resource> {
return p_res->duplicate(false);
});
}
SUBCASE("Resource::duplicate(), deep") {
_run_test(
MODE_RESOURCE_DUPLICATE_DEEP,
[](const Ref<Resource> &p_res) -> Ref<Resource> {
return p_res->duplicate(true);
});
}
SUBCASE("Resource::duplicate_for_local_scene()") {
static int mark_main_as_local;
static int mark_sub_as_local;
for (mark_main_as_local = 0; mark_main_as_local < 2; ++mark_main_as_local) { // Whether main is local-to-scene shouldn't matter.
for (mark_sub_as_local = 0; mark_sub_as_local < 2; ++mark_sub_as_local) {
_run_test(
MODE_RESOURCE_DUPLICATE_FOR_LOCAL_SCENE,
[](const Ref<Resource> &p_res) -> Ref<Resource> {
if (mark_main_as_local) {
p_res->set_local_to_scene(true);
}
if (mark_sub_as_local) {
DuplicateGuineaPigData *data = _get_duplicate_guinea_pig_data(p_res);
data->res->set_local_to_scene(true);
}
HashMap<Ref<Resource>, Ref<Resource>> remap_cache;
Node fake_scene;

Check failure on line 247 in tests/core/io/test_resource.h

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)

aggregate 'Node fake_scene' has incomplete type and cannot be defined
return p_res->duplicate_for_local_scene(&fake_scene, remap_cache);
});
}
}
}
}

TEST_CASE("[Resource] Saving and loading") {
Expand Down

0 comments on commit a7283a6

Please sign in to comment.