Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle reference cycles in is_deep_equal to avoid stack overflow #4

Merged
merged 2 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions simple_test-1.0.1-0.rockspec
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package = 'simple_test'
version = '1.0.1-0'
version = '1.0.2-0'

source = {
url = 'git://github.com/evandrolg/simple_test.git',
tag = 'v1.0.1'
tag = 'v1.0.2'
}

description = {
Expand Down
14 changes: 12 additions & 2 deletions src/simple_test/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ function is_table(obj)
return type(obj) == 'table'
end

function is_deep_equal(a, b)
local function is_deep_equal_helper(a, b, table_pairs)
if is_table(a) and is_table(b) then
if (#a ~= #b) then return false end

if table_pairs[a] == b then
return true
end

table_pairs[a] = b

for k in pairs(a) do
if not is_deep_equal(a[k], b[k]) then return false end
if not is_deep_equal_helper(a[k], b[k], table_pairs) then return false end
end

return true
Expand All @@ -16,6 +22,10 @@ function is_deep_equal(a, b)
return a == b
end

function is_deep_equal(a, b)
return is_deep_equal_helper(a, b, {})
end

return {
is_deep_equal = is_deep_equal
}
46 changes: 46 additions & 0 deletions test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,49 @@ test('utils.is_deep_equal', function(a)
a.ok(is_deep_equal({ 'a', 'b', 'c', 'd' }, { 'a', 'b', 'c', 'd' }))
a.not_ok(is_deep_equal({ 'a', 'b', 'c', 'd' }, { 'a', 'b', 'c', 'e' }))
end)

test('utils.is_deep_equal (reference cycles, different graph shape)', function(a)
-- structurally equivalent but nested table t is referenced twice in obj1,
-- but copied in obj2
local t = { d = "foo" }

local obj1 = {
a = { c = t },
b = t
}

local obj2 = {
a = { c = { d = "foo" } },
b = { d = "foo" }
}

a.ok(is_deep_equal(obj1, obj2))
end)

test("utils.is_deep_equal (reference cycles, same graph shape)", function(a)
-- tables both contain a self reference to the starting table. they are completely
-- separated graphs
local table_a1 = {}
local table_b1 = {}
table_a1.b = table_b1
table_b1.a = table_a1

local table_a2 = {}
local table_b2 = {}
table_a2.b = table_b2
table_b2.a = table_a2

a.ok(is_deep_equal(table_a1, table_a2))
end)

test("utils.is_deep_equal (reference cycles, connected graphs)", function(a)
-- tables form a simple reference cycle and are part of the same connected
-- graph. because it is a symmetrical graph, they are still structurally
-- equivalent
local table_a = {}
local table_b = {}
table_a.ref = table_b
table_b.ref = table_a

a.ok(is_deep_equal(table_a, table_b))
end)