bluebird75 / luaunit

LuaUnit is a popular unit-testing framework for Lua, with an interface typical of xUnit libraries (Python unittest, Junit, NUnit, ...). It supports several output formats (Text, TAP, JUnit, ...) to be used directly or work with Continuous Integration platforms (Jenkins, Maven, ...).
Other
572 stars 137 forks source link

assertEquals failed for two tables with the same structure but with the circual links #116

Closed anryoshi closed 5 years ago

anryoshi commented 6 years ago

In example below, the assertion fails however tables have the same structure

local a={}
local b={}
a.x = b; b.x = a
local a1={}
local b1={}
a1.x = b1; b1.x = a1
lu.assertEquals(a, a1)

I am not familiar with code base of luaunit and decisions that was made during the development of this project, but possibly assertEquals can be extended with approach correlated with code below. This is some kind of prototype that works but maybe it is not optimal. The idea beyound this is if two tables have the same structure and we maintain current path to subtable that currenlty compared, we can check a new table gotten as a value from current subtable on existance in this path. If this table is in the path we asume that the table in the table we compare against should have correlated table on the same position in the path, if not assert fails.

local function assertEqualsWithCycles(op1, op2)
    local compared = {}
    local function check_in_compared(v1)
        for _, v in ipairs(compared) do
            if v.v1 == v1 then
                return v.v2
            end
        end
    end

    local function recursive_compare(op1, op2)
        table.insert(compared, {v1=op1, v2=op2})
        print(op1, op2)
        for k, v1 in pairs(op1) do
            local v2 = op2[k]
            if type(v1) == "table" and type(v2) == "table" then
                local v2_old = check_in_compared(v1)
                if v2_old ~= nil then
                    if v2_old ~= v2 then
                        return false
                    end
                else
                    if not recursive_compare(v1, v2) then
                        return false
                    end
                end
            else
                if v1 ~= v2 then
                    return false
                end
            end
        end
        table.remove(compared)
        return true
    end
    return recursive_compare(op1, op2)
end
bluebird75 commented 6 years ago

Hi. LuaUnit is supposed to deal with cycles. There were several rounds of commits / review to handle this but it seems like we missed one.

I will look into your test case and how to solve it, although not immediatly. Be patient...

bluebird75 commented 5 years ago

The last time this was discussed is in #48 and #57 .

After revisiting the topic, I believe the final implementation made an incorrect choice : table with cycles and similar structure do not compare equally. I think that they should compare equally.

Examples :

local A, B, C, D = {1}, {1}, {2}, {2}
lu.assertEquals(A, B)
lu.assertEquals(C, D)
A.ref = C
B.ref = D
lu.assertEquals(A, B)
A.self = A
B.self = B
lu.assertEquals(A, B)
local A, B, C = {}, {}, {}
A.circular = C
B.circular = A
C.circular = B
lu.assertEquals(A, B)
lu.assertEquals(C, C)

I will make the requested changes to make these example succeed in addition to the code you proposed.

bluebird75 commented 5 years ago

Your example shall now work successfully with the latest master. I included an explicit test for it.