Human-readable representation of Lua tables
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
inspect.lua/spec/inspect_spec.lua

441 lines
14 KiB

10 years ago
local inspect = require 'inspect'
local unindent = require 'spec.unindent'
local is_luajit, ffi = pcall(require, 'ffi')
local has_rawlen = type(_G.rawlen) == 'function'
14 years ago
describe( 'inspect', function()
14 years ago
describe('numbers', function()
it('works', function()
assert.equals("1", inspect(1))
assert.equals("1.5", inspect(1.5))
assert.equals("-3.14", inspect(-3.14))
end)
14 years ago
end)
describe('strings', function()
it('puts quotes around regular strings', function()
assert.equals('"hello"', inspect("hello"))
end)
it('puts apostrophes around strings with quotes', function()
assert.equals("'I have \"quotes\"'", inspect('I have "quotes"'))
end)
it('uses regular quotes if the string has both quotes and apostrophes', function()
assert.equals('"I have \\"quotes\\" and \'apostrophes\'"', inspect("I have \"quotes\" and 'apostrophes'"))
end)
it('escapes newlines properly', function()
9 years ago
assert.equals('"I have \\n new \\n lines"', inspect('I have \n new \n lines'))
end)
it('escapes tabs properly', function()
9 years ago
assert.equals('"I have \\t a tab character"', inspect('I have \t a tab character'))
end)
it('escapes backspaces properly', function()
9 years ago
assert.equals('"I have \\b a back space"', inspect('I have \b a back space'))
end)
it('escapes unnamed control characters with 1 or 2 digits', function()
assert.equals('"Here are some control characters: \\0 \\1 \\6 \\17 \\27 \\31"',
inspect('Here are some control characters: \0 \1 \6 \17 \27 \31'))
end)
it('escapes unnamed control characters with 3 digits when they are followed by numbers', function()
assert.equals('"Control chars followed by digits \\0001 \\0011 \\0061 \\0171 \\0271 \\0311"',
inspect('Control chars followed by digits \0001 \0011 \0061 \0171 \0271 \0311'))
end)
it('backslashes its backslashes', function()
9 years ago
assert.equals('"I have \\\\ a backslash"', inspect('I have \\ a backslash'))
assert.equals('"I have \\\\\\\\ two backslashes"', inspect('I have \\\\ two backslashes'))
assert.equals('"I have \\\\\\n a backslash followed by a newline"', inspect('I have \\\n a backslash followed by a newline'))
end)
end)
it('works with nil', function()
assert.equals('nil', inspect(nil))
end)
it('works with functions', function()
assert.equals('{ <function 1>, <function 2>, <function 1> }', inspect({ print, type, print }))
end)
14 years ago
it('works with booleans', function()
assert.equals('true', inspect(true))
assert.equals('false', inspect(false))
end)
if is_luajit then
it('works with luajit cdata', function()
assert.equals('{ cdata<int>: PTR, ctype<int>, cdata<int>: PTR }',
inspect({ ffi.new("int", 1), ffi.typeof("int"), ffi.typeof("int")(1) }):gsub('(0x%x+)','PTR'))
end)
end
describe('tables', function()
it('works with simple array-like tables', function()
assert.equals("{ 1, 2, 3 }", inspect({1,2,3}))
end)
it('works with nested arrays', function()
assert.equals('{ "a", "b", "c", { "d", "e" }, "f" }', inspect({'a','b','c', {'d','e'}, 'f'}))
end)
if has_rawlen then
it('handles arrays with a __len metatable correctly (ignoring the __len metatable and using rawlen)', function()
local arr = setmetatable({1,2,3}, {__len = function() return nil end})
assert.equals("{ 1, 2, 3,\n <metatable> = {\n __len = <function 1>\n }\n}", inspect(arr))
end)
end
it('works with simple dictionary tables', function()
assert.equals("{\n a = 1,\n b = 2\n}", inspect({a = 1, b = 2}))
end)
it('identifies tables with no number 1 as struct-like', function()
assert.equals(unindent([[{
[2] = 1,
[25] = 1,
id = 1
}
]]), inspect({[2]=1,[25]=1,id=1}))
end)
it('identifies numeric non-array keys as dictionary keys', function()
assert.equals("{ 1, 2,\n [-1] = true\n}", inspect({1, 2, [-1] = true}))
assert.equals("{ 1, 2,\n [1.5] = true\n}", inspect({1, 2, [1.5] = true}))
end)
it('sorts keys in dictionary tables', function()
local t = { 1,2,3,
[print] = 1, ["buy more"] = 1, a = 1,
10 years ago
[coroutine.create(function() end)] = 1,
[14] = 1, [{c=2}] = 1, [true]= 1
}
assert.equals(unindent([[
10 years ago
{ 1, 2, 3,
[14] = 1,
[true] = 1,
a = 1,
["buy more"] = 1,
[{
c = 2
}] = 1,
[<function 1>] = 1,
[<thread 1>] = 1
}
]]), inspect(t))
end)
it('works with nested dictionary tables', function()
assert.equals(unindent([[{
10 years ago
a = 1,
b = {
c = 2
},
d = 3
}]]), inspect( {d=3, b={c=2}, a=1} ))
end)
it('works with hybrid tables', function()
assert.equals(unindent([[
10 years ago
{ "a", {
b = 1
}, 2,
["ahoy you"] = 4,
c = 3
}
]]), inspect({ 'a', {b = 1}, 2, c = 3, ['ahoy you'] = 4 }))
end)
it('displays <table x> instead of repeating an already existing table', function()
local a = { 1, 2, 3 }
local b = { 'a', 'b', 'c', a }
a[4] = b
a[5] = a
a[6] = b
assert.equals('<1>{ 1, 2, 3, <2>{ "a", "b", "c", <table 1> }, <table 1>, <table 2> }', inspect(a))
end)
describe('The depth parameter', function()
local level5 = { 1,2,3, a = { b = { c = { d = { e = 5 } } } } }
local keys = { [level5] = true }
it('has infinite depth by default', function()
assert.equals(unindent([[
10 years ago
{ 1, 2, 3,
a = {
b = {
c = {
d = {
e = 5
}
}
}
}
}
]]), inspect(level5))
end)
it('is modifiable by the user', function()
assert.equals(unindent([[
10 years ago
{ 1, 2, 3,
a = {
b = {...}
}
}
]]), inspect(level5, {depth = 2}))
10 years ago
assert.equals(unindent([[
10 years ago
{ 1, 2, 3,
a = {...}
}
]]), inspect(level5, {depth = 1}))
10 years ago
assert.equals(unindent([[
10 years ago
{ 1, 2, 3,
a = {
b = {
c = {
d = {...}
}
}
}
}
]]), inspect(level5, {depth = 4}))
assert.equals("{...}", inspect(level5, {depth = 0}))
end)
it('respects depth on keys', function()
assert.equals(unindent([[
10 years ago
{
[{ 1, 2, 3,
a = {
b = {
c = {...}
}
}
}] = true
}
]]), inspect(keys, {depth = 4}))
end)
end)
describe('the newline option', function()
it('changes the substring used for newlines', function()
local t = {a={b=1}}
assert.equal("{@ a = {@ b = 1@ }@}", inspect(t, {newline='@'}))
end)
end)
describe('the indent option', function()
it('changes the substring used for indenting', function()
local t = {a={b=1}}
assert.equal("{\n>>>a = {\n>>>>>>b = 1\n>>>}\n}", inspect(t, {indent='>>>'}))
end)
end)
describe('the process option', function()
10 years ago
it('removes one element', function()
local names = {'Andrew', 'Peter', 'Ann' }
local removeAnn = function(item) if item ~= 'Ann' then return item end end
assert.equals('{ "Andrew", "Peter" }', inspect(names, {process = removeAnn}))
end)
10 years ago
it('uses the path', function()
local names = {'Andrew', 'Peter', 'Ann' }
local removeThird = function(item, path) if path[1] ~= 3 then return item end end
assert.equals('{ "Andrew", "Peter" }', inspect(names, {process = removeThird}))
end)
10 years ago
it('replaces items', function()
local names = {'Andrew', 'Peter', 'Ann' }
local filterAnn = function(item) return item == 'Ann' and '<filtered>' or item end
assert.equals('{ "Andrew", "Peter", "<filtered>" }', inspect(names, {process = filterAnn}))
end)
10 years ago
it('nullifies metatables', function()
local mt = {'world'}
local t = setmetatable({'hello'}, mt)
local removeMt = function(item) if item ~= mt then return item end end
assert.equals('{ "hello" }', inspect(t, {process = removeMt}))
end)
10 years ago
it('nullifies metatables using their paths', function()
local mt = {'world'}
local t = setmetatable({'hello'}, mt)
local removeMt = function(item, path) if path[#path] ~= inspect.METATABLE then return item end end
assert.equals('{ "hello" }', inspect(t, {process = removeMt}))
end)
10 years ago
it('nullifies the root object', function()
local names = {'Andrew', 'Peter', 'Ann' }
local removeNames = function(item) if item ~= names then return item end end
assert.equals('nil', inspect(names, {process = removeNames}))
end)
it('changes keys', function()
local dict = {a = 1}
local changeKey = function(item) return item == 'a' and 'x' or item end
assert.equals('{\n x = 1\n}', inspect(dict, {process = changeKey}))
end)
it('nullifies keys', function()
local dict = {a = 1, b = 2}
local removeA = function(item) return item ~= 'a' and item or nil end
assert.equals('{\n b = 2\n}', inspect(dict, {process = removeA}))
end)
it('prints inspect.KEY & inspect.METATABLE', function()
local t = {inspect.KEY, inspect.METATABLE}
assert.equals("{ inspect.KEY, inspect.METATABLE }", inspect(t))
end)
it('marks key paths with inspect.KEY and metatables with inspect.METATABLE', function()
local t = { [{a=1}] = setmetatable({b=2}, {c=3}) }
local items = {}
local addItem = function(item, path)
items[#items + 1] = {item = item, path = path}
return item
end
inspect(t, {process = addItem})
assert.same({
{item = t, path = {}},
{item = {a=1}, path = {{a=1}, inspect.KEY}},
{item = 'a', path = {{a=1}, inspect.KEY, 'a', inspect.KEY}},
{item = 1, path = {{a=1}, inspect.KEY, 'a'}},
{item = setmetatable({b=2}, {c=3}), path = {{a=1}}},
{item = 'b', path = {{a=1}, 'b', inspect.KEY}},
{item = 2, path = {{a=1}, 'b'}},
{item = {c=3}, path = {{a=1}, inspect.METATABLE}},
{item = 'c', path = {{a=1}, inspect.METATABLE, 'c', inspect.KEY}},
{item = 3, path = {{a=1}, inspect.METATABLE, 'c'}}
}, items)
end)
9 years ago
it('handles recursive tables correctly', function()
local tbl = { 1,2,3}
tbl.loop = tbl
inspect(tbl, { process=function(x) return x end})
end)
end)
describe('metatables', function()
it('includes the metatable as an extra hash attribute', function()
local foo = { foo = 1, __mode = 'v' }
local bar = setmetatable({a = 1}, foo)
assert.equals(unindent([[
10 years ago
{
a = 1,
<metatable> = {
__mode = "v",
foo = 1
}
}
]]), inspect(bar))
end)
it('includes the __tostring metamethod if it exists', function()
local foo = { foo = 1, __tostring = function() return 'hello\nworld' end }
local bar = setmetatable({a = 1}, foo)
assert.equals(unindent([[
10 years ago
{ -- hello\nworld
a = 1,
<metatable> = {
__tostring = <function 1>,
foo = 1
}
}
]]), inspect(bar))
end)
it('includes an error string if __tostring metamethod throws an error', function()
local foo = { foo = 1, __tostring = function() error('hello', 0) end }
local bar = setmetatable({a = 1}, foo)
assert.equals(unindent([[
10 years ago
{ -- error: hello
a = 1,
<metatable> = {
__tostring = <function 1>,
foo = 1
}
}
]]), inspect(bar))
end)
it('does not allow collecting weak tables while they are being inspected', function()
collectgarbage('stop')
finally(function() collectgarbage('restart') end)
local shimMetatable = {
__mode = 'v',
__index = function() return {} end,
__tostring = function() collectgarbage() return 'shim' end
}
local function shim() return setmetatable({}, shimMetatable) end
local t = shim()
t.key = shim()
assert.equals(unindent([[
{ -- shim
key = { -- shim
<metatable> = <1>{
__index = <function 1>,
__mode = "v",
__tostring = <function 2>
}
},
<metatable> = <table 1>
}
]]), inspect(t))
end)
describe('When a table is its own metatable', function()
it('accepts a table that is its own metatable without stack overflowing', function()
local x = {}
setmetatable(x,x)
assert.equals(unindent([[
10 years ago
<1>{
<metatable> = <table 1>
}
]]), inspect(x))
end)
it('can invoke the __tostring method without stack overflowing', function()
local t = {}
t.__index = t
setmetatable(t,t)
assert.equals(unindent([[
10 years ago
<1>{
__index = <table 1>,
<metatable> = <table 1>
}
]]), inspect(t))
end)
end)
end)
14 years ago
end)
it('allows changing the global tostring', function()
local save = _G.tostring
9 years ago
_G.tostring = inspect
local s = tostring({1, 2, 3})
9 years ago
_G.tostring = save
assert.equals("{ 1, 2, 3 }", s)
9 years ago
end)
14 years ago
end)