bitwes / Gut

Godot Unit Test. Unit testing tool for Godot Game Engine.
1.88k stars 104 forks source link

Add Object support for compare_deep() #662

Open neth392 opened 2 months ago

neth392 commented 2 months ago

Versions

What versions of Godot do you want to use this feature in? Latest

The Feature

I am writing tests for a serialization library. The tests involve comparing original object instances to their deserialized counterparts. Godot's == does not work with this as the objects are two different instances, but I need to test to see if their property values match. If compare_deep supported objects this would be great, as right now I have to write some really hacky code to make these tests work & be verbose enough to isolate which properties do not match. Recursive support would be needed too as objects can have properties whose values are other objects

bitwes commented 2 months ago

This seems out of scope for compare_deep, even though it feels very similar. If you have a dictionary or array of of things to compare then maybe GUT could get a compare_deep_custom that would allow you to specify a Callable to compare two elements.

If you only have two objects you want to compare you might be able to use compare_deep with the results of inst_to_dict. I'm not sure if inst_to_dict handles sub-objects but it shouldn't be too difficult to add that. You can also use get_property_list and get to go through each property.

If I'm misunderstanding the request, add some sample code.

neth392 commented 2 months ago

That's a good point. I didn't think of trying inst_to_dict, maybe it could've worked. A compare_deep_custom(callable) could work but it'd have to account for properties that are other objects and so on, and be able to isolate at what level there was an issue, say if "MyObject.other_object_property.another_object_property" didn't match.

I wrote some functions to use in a GutTest (link below) where I needed to compare objects & arrays/dictionaries possibly containing objects, so that'd be a good sample of why I needed it. The initial code I had written didn't really isolate what failed the comparison so that's why I recursively return a string, and concatenate it back up that recursive chain until it's printed as 1 string containing all the info I need to see what failed. May be something to consider if it's decided to implement this

https://github.com/neth392/godot-improved-json/blob/main/tests/test_custom_objects.gd#L160

christo8989 commented 2 days ago

There are problems with this implementation but if anyone wants a quick and dirty approach:

USE WITH CAUTION!!!! (I haven't tested)

func deep(v1, v2):
    var result =  null

    if(GutUtils.are_datatypes_same(v1, v2)):
        if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]):
            result = GutUtils.DiffTool.new(v1, v2, GutUtils.DIFF.DEEP)
        elif (
            typeof(v1) == TYPE_OBJECT and 
            typeof(v2) == TYPE_OBJECT and 
            v1.get_class() == v2.get_class()
        ):
            if v1.has_method('equal'):
                result = simple(v1.equal(v2), true)
            else:
                for property in v1.get_property_list():
                    if property.usage != PROPERTY_USAGE_SCRIPT_VARIABLE:
                        continue
                    result = deep(v1[property.name], v2[property.name])
        else:
            result = simple(v1, v2)
    else:
        result = simple(v1, v2)

    return result

Maybe it'd be best to first check for an .equal function on the object, and if it's not found, perform the following. So, they can override this default behavior that is somewhat arbitrary.

Also, I haven't tested this too deeply. So, I don't know how well the recursiveness works.

EDIT:

I cleaned up the code a bit and added the .equal part.