make table_eq check recursive equality

and add tests. to write test the table_eq function itself, I expose it
in the text env via expect_raw. if we don't want to do this,
alternatives could be:
1. add the test to main.lua itself
2. expose the expect.table_eq function via a test_hooks variable. then
   test.lua could require main.lua and access the function via the hook.
   may need to update main.lua a bit to ensure it does not run when it
   is "require"d
develop
myk002 2021-03-05 11:31:40 -08:00
parent b302289864
commit f176310bcd
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
2 changed files with 71 additions and 6 deletions

@ -79,21 +79,57 @@ end
function expect.ge(a, b, comment) function expect.ge(a, b, comment)
return a >= b, comment, ('%s < %s'):format(a, b) return a >= b, comment, ('%s < %s'):format(a, b)
end end
function expect.table_eq(a, b, comment) local function table_eq_recurse(a, b, keys, known_eq)
if a == b then return true end
local checked = {} local checked = {}
for k, v in pairs(a) do for k,v in pairs(a) do
if a[k] ~= b[k] then if type(a[k]) == 'table' then
return false, comment, ('key "%s": %s ~= %s'):format(k, a[k], b[k]) if known_eq[a[k]] and known_eq[a[k]][b[k]] then goto skip end
table.insert(keys, tostring(k))
if type(b[k]) ~= 'table' then
return false, keys, {tostring(a[k]), tostring(b[k])}
end
if not known_eq[a[k]] then known_eq[a[k]] = {} end
for eq_tab,_ in pairs(known_eq[a[k]]) do
known_eq[eq_tab][b[k]] = true
end
known_eq[a[k]][b[k]] = true
if not known_eq[b[k]] then known_eq[b[k]] = {} end
for eq_tab,_ in pairs(known_eq[b[k]]) do
known_eq[eq_tab][a[k]] = true
end
known_eq[b[k]][a[k]] = true
local matched, keys_at_diff, diff =
table_eq_recurse(a[k], b[k], keys, known_eq)
if not matched then return false, keys_at_diff, diff end
keys[#keys] = nil
elseif a[k] ~= b[k] then
table.insert(keys, tostring(k))
return false, keys, {tostring(a[k]), tostring(b[k])}
end end
::skip::
checked[k] = true checked[k] = true
end end
for k in pairs(b) do for k in pairs(b) do
if not checked[k] then if not checked[k] then
return false, comment, ('key "%s": %s ~= %s'):format(k, a[k], b[k]) table.insert(keys, tostring(k))
return false, keys, {tostring(a[k]), tostring(b[k])}
end end
end end
return true return true
end end
function expect.table_eq(a, b, comment)
if type(a) ~= 'table' or type(b) ~= 'table' then
return false, comment, 'both operands to table_eq must be tables'
end
local keys, known_eq = {}, {}
local matched, keys_at_diff, diff = table_eq_recurse(a, b, keys, known_eq)
if matched then return true end
local keystr = '['..keys_at_diff[1]..']'
for i=2,#keys_at_diff do keystr = keystr..'['..keys_at_diff[i]..']' end
return false, comment,
('key %s: "%s" ~= "%s"'):format(keystr, diff[1], diff[2])
end
function expect.error(func, ...) function expect.error(func, ...)
local ok, ret = pcall(func, ...) local ok, ret = pcall(func, ...)
if ok then if ok then
@ -199,6 +235,7 @@ function build_test_env()
mode = 'none', mode = 'none',
}, },
expect = {}, expect = {},
expect_raw = expect,
delay = delay, delay = delay,
require = clean_require, require = clean_require,
} }
@ -342,7 +379,7 @@ function main()
} }
local passed = true local passed = true
print('Loading tests') print('Loading tests from ' .. config.test_dir)
local tests = {} local tests = {}
for _, file in ipairs(files) do for _, file in ipairs(files) do
if not load_tests(file, tests) then if not load_tests(file, tests) then

@ -1,3 +1,31 @@
function test.internal_in_test() function test.internal_in_test()
expect.true_(dfhack.internal.IN_TEST) expect.true_(dfhack.internal.IN_TEST)
end end
function test.table_eq()
expect.true_(expect_raw.table_eq({}, {}))
expect.true_(expect_raw.table_eq({'a'}, {'a'}))
expect.true_(expect_raw.table_eq({{'a', k='val'}, 'b'},
{{'a', k='val'}, 'b'}))
expect.false_(expect_raw.table_eq({}, {''}))
expect.false_(expect_raw.table_eq({''}, {}))
expect.false_(expect_raw.table_eq({'a', {}}, {'a'}))
expect.false_(expect_raw.table_eq({{'a', k='val1'}, 'b'},
{{'a', k='val2'}, 'b'}))
local tab = {a='a', b='b'}
expect.true_(expect_raw.table_eq(tab, tab))
expect.true_(expect_raw.table_eq({tab}, {tab}))
local tab1, tab2 = {'a'}, {'a'}
tab1.self, tab2.self = tab1, tab2
expect.true_(expect_raw.table_eq(tab1, tab2))
tab1.other, tab2.other = tab2, tab1
expect.true_(expect_raw.table_eq(tab1, tab2))
local tabA, tabB, tabC, tabD = {k='a'}, {k='a'}, {k='a'}, {k='a'}
tabA.next, tabB.next, tabC.next, tabD.next = tabB, tabC, tabD, tabA
expect.true_(expect_raw.table_eq(tabA, tabB))
end