From f176310bcdc52e134c00c067bd827b7b12a49543 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Mar 2021 11:31:40 -0800 Subject: [PATCH] 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 --- test/main.lua | 49 +++++++++++++++++++++++++++++++++++++++++++------ test/test.lua | 28 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/test/main.lua b/test/main.lua index fc61442fa..46e19aba3 100644 --- a/test/main.lua +++ b/test/main.lua @@ -79,21 +79,57 @@ end function expect.ge(a, b, comment) return a >= b, comment, ('%s < %s'):format(a, b) 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 = {} - for k, v in pairs(a) do - if a[k] ~= b[k] then - return false, comment, ('key "%s": %s ~= %s'):format(k, a[k], b[k]) + for k,v in pairs(a) do + if type(a[k]) == 'table' then + 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 + ::skip:: checked[k] = true end for k in pairs(b) do 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 return true 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, ...) local ok, ret = pcall(func, ...) if ok then @@ -199,6 +235,7 @@ function build_test_env() mode = 'none', }, expect = {}, + expect_raw = expect, delay = delay, require = clean_require, } @@ -342,7 +379,7 @@ function main() } local passed = true - print('Loading tests') + print('Loading tests from ' .. config.test_dir) local tests = {} for _, file in ipairs(files) do if not load_tests(file, tests) then diff --git a/test/test.lua b/test/test.lua index 11f038d66..6b188e766 100644 --- a/test/test.lua +++ b/test/test.lua @@ -1,3 +1,31 @@ function test.internal_in_test() expect.true_(dfhack.internal.IN_TEST) 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