local SUITE = require 'TestSuite' 'analyze_structure' SUITE:setEnvironment { analyze_structure = require 'analyze_structure', TABLE_TYPE = require 'common' . TABLE_TYPE } -------------------------------------------------------------------------------- -- analyze_structure SUITE:addTest('Empty Table', function () local input = {} -- Empty! local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.EMPTY, 'Returned bad type: '..table_info.type) assert_equal(table_info.has_seq, false) assert_equal(table_info.has_map, false) end) SUITE:addTest('Sequence', function () local input = { 1, 2, 3 } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.SEQUENCE, 'Returned bad type: '..table_info.type) assert_equal(table_info.has_seq, true) assert_equal(table_info.has_map, false) end) SUITE:addTest('Sequence with holes', function () local input = { 1, nil, 3 } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.SEQUENCE, 'Returned bad type: '..table_info.type) assert_equal(table_info.has_seq, true) assert_equal(table_info.has_map, false) assert_equal(table_info.has_holes, true) end) SUITE:addTest('Sequence with hole on start', function () local input = { nil, 2, 3 } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.SEQUENCE, 'Returned bad type: '..table_info.type) assert_equal(table_info.has_seq, true) assert_equal(table_info.has_map, false) assert_equal(table_info.has_holes, true) end) SUITE:addTest('Pure Map', function () local input = { a = 1, [true] = 2, c = 3 } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.PURE_MAP, 'Returned bad type: '..table_info.type) assert_equal(table_info.has_seq, false) assert_equal(table_info.has_map, true) end) SUITE:addTest('Boolean set', function () local input = { [true] = true, [false] = false } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.SET, 'Returned bad type: '..table_info.type) assert_equal(table_info.has_seq, false) assert_equal(table_info.has_map, true) end) SUITE:addTest('A Mixed table', function () local input = { 300, [300] = 1 } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.has_seq, true) assert_equal(table_info.has_map, true) assert_equal(table_info.type, TABLE_TYPE.MIXED, 'Returned bad type: '..table_info.type) end) SUITE:addTest('String Map', function () local input = { a = 1, b = 2, c = 3 } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.STRING_MAP, 'Returned bad type: '..table_info.type) assert_equal(table_info.has_seq, false) assert_equal(table_info.has_map, true) end) SUITE:addTest('Set', function () local input = { a = true, b = true, c = true } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.SET, 'Returned bad type: '..table_info.type) assert_equal(table_info.is_set, true) end) SUITE:addTest('Tabular of sequences', function () local input = { a = {1, 2, 3}, b = {4, 5, 6}, c = {7, 8, 9} } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.is_tabular, true) assert_equal(table_info.child_keys, {3, 3, 3}) end) SUITE:addTest('Tabular of maps', function () local input = { a = {a = 1, b = 2}, b = {a = 3, b = 4}, c = {a = 2, b = 7} } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.is_tabular, true) assert_equal(table_info.child_keys, {a = 3, b = 3}) end) SUITE:addTest('Not Tabular, due to no-sub-tables', function () local input = { a = 1, b = {4}, c = 7 } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.is_tabular, false) end) SUITE:addTest('Not Tabular, due to not being identical sub-tables', function () local input = { a = { a = 1 }, b = { b = 2 }, c = { c = 3 } } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.is_tabular, false) end) SUITE:addTest('Not Tabular, due to varying lengths', function () local input = { { 1 }, { 2, 3 }, { 4, 5, 6 } } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.is_tabular, false) end) SUITE:addTest('Pseudo-Tabular, due to min one shared key', function () local input = { { 1 }, { 2, 3 }, { 4, 5, 6 } } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.is_pseudo_tabular, true) assert_equal(table_info.child_keys, {3, 2, 1}) end) SUITE:addTest('Not Pseudo-Tabular, due to no key shared by all', function () local input = { {}, { 1 }, { 2, 3 }, { 4, 5, 6 } } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.is_pseudo_tabular, false) end) -------------------------------------------------------------------------------- -- Stops at max_depth SUITE:addTest('Has info for root, and children', function () local input = { { 1 }, { 2, 3 }, { 4, 5, 6 } } local table_info = analyze_structure(input, 1) assert(table_info[input]) assert(table_info[input[1]]) assert(table_info[input[2]]) assert(table_info[input[3]]) end) SUITE:addTest('Has info for root, but not children', function () local input = { { 1 }, { 2, 3 }, { 4, 5, 6 } } local table_info = analyze_structure(input, 0) assert(table_info[input]) assert(not table_info[input[1]]) assert(not table_info[input[2]]) assert(not table_info[input[3]]) end) -------------------------------------------------------------------------------- -- Corner cases. SUITE:addTest('goto is special', function () local input = { ['goto'] = 'hi' } local table_info = analyze_structure(input, math.huge)[input] assert_equal(table_info.type, TABLE_TYPE.PURE_MAP, 'Returned bad type: '..table_info.type) end) -------------------------------------------------------------------------------- -- analyze_structure SUITE:addTest('Recursive Numeration, Simple', function () local input = {} input[1] = input local info = analyze_structure(input, math.huge) assert_equal(1, info[input].marker) end) SUITE:addTest('Recursive Numeration, Multiple-appear', function () local input = { {}, { {} } } input[1][1] = input[2][1] local info = analyze_structure(input, math.huge) assert_equal(1, info[input[1][1]].marker) end) SUITE:addTest('Recursive Numeration, Multiple at once', function () local input = { {}, { {} } } input[1][1] = input[2][1] input[1][2] = input local info = analyze_structure(input, math.huge) assert_equal('number', type(info[input[1][1]].marker)) assert_equal('number', type(info[input].marker)) end) SUITE:addTest('Recursive Numeration, Through Keys', function () local input = { } input[input] = input local info = analyze_structure(input, math.huge) assert_equal(1, info[input].marker) end) -------------------------------------------------------------------------------- -- Metatable shennanigens. SUITE:addTest('Wont crash, even though metatable.__index throws errors', function () local input = setmetatable({ 'hi', 'hello' }, {__index = function (_, k) error('Undefined access on key: '..k) end}) local info = analyze_structure(input, math.huge) assert(true) end) SUITE:addTest('Can count elements, even though metatable.__index throws errors', function () local input = setmetatable({ 'hi', 'hello' }, {__index = function (_, k) error('Undefined access on key: '..k) end}) local info = analyze_structure(input, math.huge) assert_equal(2, info[input].seq_elems) end) SUITE:addTest('Can count elements, even though metatable.__index throws errors and __metatable returns bogus', function () local input = setmetatable({ 'hi', 'hello' }, {__index = function (_, k) error('Undefined access on key: '..k) end, __metatable = {}}) local info = analyze_structure(input, math.huge) assert_equal(2, info[input].seq_elems) end) -------------------------------------------------------------------------------- -- Uniform structure SUITE:addTest('Can detect uniform structure', function () local input = { a = 'b', b = 'a' } local info = analyze_structure(input, math.huge) assert_equal(true, info[input].is_uniform) end) SUITE:addTest('Can detect uniform structure with different key value types', function () local input = { a = 1, b = 4 } local info = analyze_structure(input, math.huge) assert_equal(true, info[input].is_uniform) end) SUITE:addTest('Can detect basic non-uniform structure', function () local input = { a = 'b', b = 5 } local info = analyze_structure(input, math.huge) assert_equal(false, info[input].is_uniform) end) SUITE:addTest('Can detect pseudo-uniform structure with nested tables', function () -- It's pseudo-uniform because of the child having uniform structure with -- equal number of elements. local input = { a = { d = 7 }, b = { p = 3 } } local info = analyze_structure(input, math.huge) assert_equal(true, info[input].is_uniform) end) SUITE:addTest('Can detect non-uniform structure with nested tables', function () local input = { a = { 'a', 'b', 'c' }, b = { p = 3, 'hi' } } local info = analyze_structure(input, math.huge) assert_equal(false, info[input].is_uniform) end) -- TODO: Add predicate to check for pseudo-uniformness. -------------------------------------------------------------------------------- -- API stuff SUITE:addTest('next_mark does not escape', function () local info = analyze_structure( {}, math.huge ) assert(not info.next_mark) end) SUITE:addTest('We can extend an already existing info table', function () local tab1, tab2 = {}, {} local info = analyze_structure( tab1, math.huge ) analyze_structure( tab2, math.huge, info ) assert(info[tab1]) assert(info[tab2]) end) SUITE:addTest('When we extend an info table, we retain the previous root', function () local tab1, tab2 = {}, {} local info = analyze_structure( tab1, math.huge ) analyze_structure( tab2, math.huge, info ) assert(tab1 == info.root) end) -------------------------------------------------------------------------------- return SUITE