local SUITE = require('TestSuite').new('pretty') SUITE:setEnviroment{ format = require('pretty') } local ASSERT_ERROR_APPROX = [[ Approximate strings not similar enough: Should match: %s Gotten: %s ]] -------------------------------------------------------------------------------- -- Lua 5.1 compat: local HAS_ADV_GETLOCAL = not not debug.getinfo(1, 'u').nparams -- Lua 5.3 compat: if not loadstring then loadstring = load end local function format_test (t) if t.longterm then return end if t.adv_getlocal and not HAS_ADV_GETLOCAL then return end SUITE:addTest(t.expect, function () local input_value = t.input local input_options = t.options local expected_result = t.expect local actual_result = format(input_value, input_options) if not t.approx or type(actual_result) ~= 'string' then assert_equal(expected_result, actual_result) else if not actual_result:match(expected_result) then error(ASSERT_ERROR_APPROX:format(expected_result, actual_result)) end end end) end -------------------------------------------------------------------------------- -- Strings format_test { input = 'Hello World', expect = '\'Hello World\'', } format_test { input = 'Hello \'World\'', expect = '\"Hello \'World\'\"', } format_test { input = 'Hello \"World\"', expect = '\'Hello \"World\"\'', } format_test { input = 'Hello [[World]]', expect = '\'Hello [[World]]\'', } format_test { input = '\'Hello\' [[World]]', expect = '\"\'Hello\' [[World]]\"', } format_test { input = '\'Hello\' \"there\" [[World]]', expect = '[=[\'Hello\' \"there\" [[World]]]=]', } format_test { input = '\'Hello\' \"there\" [=[World]=]', expect = '[[\'Hello\' \"there\" [=[World]=]]]', } format_test { input = '\nHello World', expect = '\'\\nHello World\'', } format_test { input = '\'\"\n', expect = '[[\n\'\"\n]]', } format_test { input = '\n', expect = '\'\\n\'', } format_test { input = '\\', expect = '\'\\\\\'', } format_test { input = '\000', expect = '\'\\000\'', } format_test { input = '\a\b\v\r\f', expect = '\'\\a\\b\\v\\r\\f\'', } format_test { input = 'ø', expect = '\'ø\'', } -------------------------------------------------------------------------------- -- Numbers format_test { input = 0, expect = '0', } format_test { input = 2000, expect = '2000', } format_test { input = -2000, expect = '-2000', } -------------------------------------------------------------------------------- -- Numbers, Other formats format_test { input = 10^5, expect = '10^5', } format_test { input = 10^-6, expect = '10^-6', } format_test { input = 2^17, expect = '2^17', } format_test { input = 2^-7, expect = '2^-7', } format_test { input = 1/3, expect = '1/3', } format_test { input = 9/17, expect = '9/17', } format_test { input = math.sqrt(2), expect = 'math.sqrt(2)', } format_test { input = 1/math.sqrt(2), expect = '1/math.sqrt(2)', } format_test { input = math.log(2), expect = 'math.log(2)', } -------------------------------------------------------------------------------- -- Numbers, Constants format_test { input = math.pi, expect = 'math.pi', } format_test { input = math.exp(1), expect = 'math.exp(1)', } format_test { input = 1/0, -- Same as math.huge expect = '1/0', } format_test { input = -1/0, -- Same as -math.huge expect = '-1/0', } format_test { input = 0/0, -- Same as nan expect = '0/0', } format_test { input = 1/0, -- Same as math.huge options = { math_shorthand = true }, expect = '∞', } format_test { input = -1/0, -- Same as -math.huge options = { math_shorthand = true }, expect = '-∞', } format_test { input = 0/0, -- Same as nan options = { math_shorthand = true }, expect = 'nan', } format_test { input = math.pi, options = { math_shorthand = true }, expect = 'π', } format_test { input = math.exp(1), options = { math_shorthand = true }, expect = 'e', } format_test { input = { 1/2, 1/3, 1/4, 1/5 }, expect = '{ 1/2, 1/3, 1/4, 1/5 }', } -------------------------------------------------------------------------------- -- Primitive types format_test { input = nil, expect = 'nil', } format_test { input = true, expect = 'true', } format_test { input = false, expect = 'false', } -------------------------------------------------------------------------------- -- Function printing format_test { adv_getlocal = true, input = function () end, expect = 'function () ... end', } format_test { adv_getlocal = true, input = function (a) end, expect = 'function (a) ... end', } format_test { adv_getlocal = true, input = function (a, b) end, expect = 'function (a, b) ... end', } format_test { adv_getlocal = true, input = function (...) end, expect = 'function (...) ... end', } format_test { adv_getlocal = true, input = function (a, b, ...) end, expect = 'function (a, b, ...) ... end', } do local SOME_RANDOM_UPVALUE = false format_test { adv_getlocal = true, input = function () l = SOME_RANDOM_UPVALUE end, expect = 'function () ... end', } format_test { adv_getlocal = true, input = function () SOME_RANDOM_UPVALUE = true end, expect = 'function () ... end', } format_test { -- More function info is ignored if not at depth 0. adv_getlocal = true, input = { a = function () SOME_RANDOM_UPVALUE = true end }, options = { more_function_info = true }, expect = '{\n\ta = function () ... end\n}', } local func_line = debug.getinfo(1).currentline + 2 -- Must be exactly 2 lines above function format_test { input = function () l = SOME_RANDOM_UPVALUE end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_pretty.lua\' [Line: '..func_line..']\n\t-- up_values: { SOME_RANDOM_UPVALUE = false }\n\n\t...\nend' } local func_line = debug.getinfo(1).currentline + 2 -- Must be exactly 2 lines above function format_test { input = function () SOME_RANDOM_UPVALUE = true end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_pretty.lua\' [Line: '..func_line..']\n\t-- up_values: { SOME_RANDOM_UPVALUE = false }\n\n\t...\nend' } end do local func_line = debug.getinfo(1).currentline + 2 -- Must be exactly 2 lines above function format_test { input = function () end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_pretty.lua\' [Line: '..func_line..']\n\n\t...\nend' } end do local index = 0 format_test { input = function () index = index + 1; return index end, adv_getlocal = true, expect = 'function () ... end' } local func_line = debug.getinfo(1).currentline + 2 -- Must be exactly 2 lines above function format_test { input = function () index = index + 1; return index end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_pretty.lua\' [Line: '..func_line..']\n\t-- up_values: { index = 0 }\n\n\t...\nend' } end format_test { adv_getlocal = true, input = loadstring('return function () end')(), expect = 'function () end', } format_test { adv_getlocal = true, input = loadstring('return function () return function () end end')(), expect = 'function () return function () end end', } format_test { longterm = true, adv_getlocal = true, input = loadstring('return function () return function () end\nend')()(), expect = 'function () end', } format_test { -- NOTE: This is HARD to fix. It's thus longerterm longterm = true, adv_getlocal = true, input = loadstring('return function () return function () end end')()(), expect = 'function () end', } format_test { -- More function info allows one to even get the function whole, if it was defined in a string. input = loadstring('return function (a, b) return a + b end')(), options = { more_function_info = true }, expect = 'function (a, b) return a + b end', } format_test { -- More function info allows one to even get the function whole, if it was defined in a string. input = loadstring('return function (a, b)\n\treturn a + b\nend')(), options = { more_function_info = true }, expect = 'function (a, b)\n\treturn a + b\nend', } do local func_line = debug.getinfo(1).currentline + 2 -- Must be exactly 2 lines above function format_test { input = function () -- NOTE: This function must cover 3 lines of code! end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_pretty.lua\' [Lines: '..func_line..' - '..(func_line+2)..']\n\n\t...\nend', } local func_line = debug.getinfo(1).currentline + 2 -- Must be exactly 2 lines above function format_test { input = function () --[[ NOTE: This function must cover a single line of code! ]] end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_pretty.lua\' [Line: '..func_line..']\n\n\t...\nend', } end format_test { input = math.abs, expect = 'builtin function (x) ... end', } format_test { input = math.abs, options = { more_function_info = true }, expect = 'builtin function (x)\n\t-- math.abs\n\t-- Returns the absolute value of x.\n\n\t...\nend', } format_test { input = math.random, expect = 'builtin function ([m [, n]) ... end', } format_test { input = math.random, options = { more_function_info = true }, expect = 'builtin function ([m [, n])\n\t-- math.random\n\t-- When called without arguments, returns a uniform pseudo-random real number in the range [0,1). When called with an integer number m, math.random returns a uniform pseudo-random integer in the range [1, m]. When called with two integer numbers m and n, math.random returns a uniform pseudo-random integer in the range [m, n].\n\n\t...\nend', } format_test { input = string.byte, options = { more_function_info = true }, expect = 'builtin function (s [, i [, j]])\n\t-- string.byte\n\t-- Returns the internal numerical codes of the characters s[i], s[i+1], ..., s[j]. The default value for i is 1; the default value for j is i.\n\t-- Note that numerical codes are not necessarily portable across platforms.\n\n\t...\nend', } -- Indent functions nicely format_test { -- The tail part should align, letting people focus on the important aspects. input = { random = math.random, abs = math.abs }, expect = '{\n\tabs = builtin function (x) ... end,\n\trandom = builtin function ([m [, n]) ... end\n}', } format_test { -- The function part should align, if some are builtin and some are not. adv_getlocal = true, input = { random = math.random, abs = function (x) return x < 0 and -x or x end }, expect = '{\n\tabs = function (x) ... end,\n\trandom = builtin function ([m [, n]) ... end\n}', } format_test { -- No special indent if no special function modifier. adv_getlocal = true, input = { max = function(a, b) return a > b and a or b end, abs = function (x) return x < 0 and -x or x end }, expect = '{\n\tabs = function (x) ... end,\n\tmax = function (a, b) ... end\n}', } -------------------------------------------------------------------------------- -- Userdata printing -- TODO. First off figure out a way to test this stuff. -- Maybe look into using the one available debug.getupvalue(pairs, 1) -------------------------------------------------------------------------------- -- Thread printing do local suspended_coroutine = coroutine.create(function () end) format_test { input = suspended_coroutine, approx = true, expect = 'suspended coroutine: 0x%x+', } end do local dead_coroutine = coroutine.create(function () end) coroutine.resume(dead_coroutine) format_test { input = dead_coroutine, approx = true, expect = 'dead coroutine: 0x%x+', } end -------------------------------------------------------------------------------- -- Single-line tables format_test { input = {}, expect = '{}', } format_test { input = {1, 2, 3}, expect = '{ 1, 2, 3 }', } format_test { input = { 'Hello', 'World' }, expect = '{ \'Hello\', \'World\' }', } format_test { input = { a = 1, b = 2 }, expect = '{ a = 1, b = 2 }', } format_test { input = { __hello = true }, expect = '{ __hello = true }', } format_test { input = { [']]'] = true }, expect = '{ [\']]\'] = true }', } format_test { input = { ['and'] = true }, expect = '{ [\'and\'] = true }', } format_test { input = { [false] = false, [true] = true }, expect = '{ [false] = false, [true] = true }', } format_test { input = { [100] = 'Hi', [300] = 'Hello' }, expect = '{ [100] = \'Hi\', [300] = \'Hello\' }', } format_test { -- Order does not matter input = { b = 1, a = 2 }, expect = '{ a = 2, b = 1 }', } format_test { -- Can include empty tables input = { {}, {}, {} }, expect = '{ {}, {}, {} }', } format_test { -- Can include very small tables input = { {1}, {2}, {3} }, expect = '{ { 1 }, { 2 }, { 3 } }', } -------------------------------------------------------------------------------- -- Multi-line tables format_test { input = { {1, 2, 3}, {4, 5, 6} }, expect = '{\n\t{ 1, 2, 3 },\n\t{ 4, 5, 6 }\n}', } format_test { input = { a = {1, 2, 3}, b = {4, 5, 6} }, expect = '{\n\ta = { 1, 2, 3 },\n\tb = { 4, 5, 6 }\n}', } format_test { input = { 'Hi', [300] = 'Hello' }, expect = '{\n\t[1] = \'Hi\',\n\t[300] = \'Hello\'\n}', } format_test { input = { { {} } }, expect = '{\n\t{ {} }\n}', } format_test { input = { [{ 1, 2 }] = { 2, 1 } }, expect = '{\n\t[{ 1, 2 }] = { 2, 1 }\n}', } format_test { input = { { {1, 2}, {3, 4} }, {5, 6} }, expect = '{\n\t{\n\t\t{ 1, 2 },\n\t\t{ 3, 4 }\n\t},\n\t{ 5, 6 }\n}', } format_test { input = { { {1, 2}, {3, 4} }, {5, 6} }, options = { max_depth = 0 }, expect = '{...}', } format_test { input = { { {1, 2}, {3, 4} }, {5, 6} }, options = { max_depth = 1 }, expect = '{\n\t{...},\n\t{...}\n}', } format_test { input = { { {1, 2}, {3, 4} }, {5, 6} }, options = { max_depth = 2 }, expect = '{\n\t{\n\t\t{...},\n\t\t{...}\n\t},\n\t{ 5, 6 }\n}', } format_test { input = { { {1, 2}, {3, 4} }, {5, 6} }, options = { max_depth = 3 }, expect = '{\n\t{\n\t\t{ 1, 2 },\n\t\t{ 3, 4 }\n\t},\n\t{ 5, 6 }\n}', } format_test { input = { [{ {1,2}, {3,4} }] = 'Hello World' }, expect = '{\n\t[{...}] = \'Hello World\'\n}', } format_test { input = { a = {1,2}, bcdefg = {3,4} }, expect = '{\n\ta = { 1, 2 },\n\tbcdefg = { 3, 4 }\n}', } format_test { input = { [true] = 1, [1] = false }, expect = '{\n\t[true] = 1,\n\t[1] = false\n}', } format_test { -- Proper indent input = { [1] = 1, ['whatever'] = false }, expect = '{\n\t[1] = 1,\n\t[\'whatever\'] = false\n}', } format_test { -- Table view, with indent. input = { { a = 'hello', b = 'hi' }, { a = 'hi', b = 'hello' } }, expect = '{\n\t{ a = \'hello\', b = \'hi\' },\n\t{ a = \'hi\', b = \'hello\' }\n}', } -------------------------------------------------------------------------------- -- Table recursion do local recursive = {} recursive[1] = recursive format_test { input = recursive, options = { max_depth = 5 }, expect = '{ {...} }', } format_test { input = recursive, options = { max_depth = 5, recursion = 'ignore' }, expect = '{ {...} }', } format_test { input = recursive, options = { max_depth = 5, recursion = 'marked' }, expect = '<1>{ <1>{...} }', } end -------------------------------------------------------------------------------- -- Table Sorting format_test { input = { 'c', 'b', 'a' }, expect = '{ \'c\', \'b\', \'a\' }', } format_test { input = { a = 1, a1 = 0 }, expect = '{ a = 1, a1 = 0 }', } format_test { input = { a10 = 1, a1 = 0 }, expect = '{ a1 = 0, a10 = 1 }', } format_test { input = { a00 = 0, a1 = 1 }, expect = '{ a00 = 0, a1 = 1 }', } format_test { input = { a = {}, b = true }, expect = '{ b = true, a = {} }', } format_test { input = { a = {}, b = true, b1 = false }, expect = '{ b = true, b1 = false, a = {} }', } format_test { input = { {}, true, false, 5 }, expect = '{ {}, true, false, 5 }', } -- TODO: Add more tests for sorting. -------------------------------------------------------------------------------- -- CDATA -- TODO: Add more advanced understanding of cdata. if type(jit) == 'table' then local ffi = require('ffi') ffi.cdef[[ int poll(struct pollfd *fds, unsigned long nfds, int timeout); ]] format_test { input = ffi.C.poll, approx = true, expect = 'cdata<.+>: 0x%x+', } format_test { input = ffi.new('int[10]'), approx = true, expect = 'cdata<.+>: 0x%x+', } end -------------------------------------------------------------------------------- return SUITE