From 55265378f9407856aed2ded43541011289db37f2 Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Thu, 29 Dec 2016 15:33:43 +0100 Subject: [PATCH] Attempting more comprehensive coverage of types, including threads/coroutines and functions. --- pretty.lua | 112 ++++++++++++++++++++++++++-- test/TestSuite.lua | 2 +- test/test_pretty.lua | 173 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 277 insertions(+), 10 deletions(-) diff --git a/pretty.lua b/pretty.lua index 3f4c0b6..f48414c 100644 --- a/pretty.lua +++ b/pretty.lua @@ -1,4 +1,10 @@ +local ERROR_UNKNOWN_TYPE = [[ +[pretty]: Attempting to format unsupported value of type "%s". + A native formatting of the value is: %s + This is a bug, and should be reported. +]] + local TABLE_TYPE_EMPTY = 'EMPTY TABLE' local TABLE_TYPE_SEQUENCE = 'SEQUENCE' local TABLE_TYPE_STRING_MAP = 'STRING KEY MAP' @@ -173,6 +179,22 @@ local function escape_string (str) return table.concat(l, '') end +local function get_function_info (f) + -- Regarding get-info: + -- * No need to includ 'f'. Function is already known + -- * No need to include 'L' (active lines) option. Ignored + -- * No need to include 'n' (name and namewhat). Won't work. + local info = debug.getinfo(f, 'Su') + info.params = {} + info.ups = {} + info.env = debug.getfenv(f) + info.builtin = info.source == '=[C]' + for i = 1, info.nparams do info.params[i] = debug.getlocal(f, i) end + for i = 1, info.nups do local k, v = debug.getupvalue(f, i); info.ups[k] = v end + + return info +end + -------------------------------------------------------------------------------- -- Identifyer stuff @@ -408,7 +430,8 @@ local function format_string (str, options) return left .. str .. right end -local function format_number (value, shorthand) +local function format_number (value, options) + local shorthand = options.math_shorthand if value ~= value then return shorthand and 'nan' or '0/0' elseif value == 1/0 then return shorthand and 'inf' or '1/0' elseif value == -1/0 then return shorthand and '-inf' or '-1/0' @@ -416,13 +439,86 @@ local function format_number (value, shorthand) end end -function format_value (value, options, depth) - local type = type(value) +local function format_coroutine (value) + return coroutine.status(value) .. ' coroutine: ' .. tostring(value):sub(9) +end - if type == 'table' then return format_table(value, options, depth or 'max') - elseif type == 'string' then return format_string(value, options) - elseif type == 'number' then return format_number(value, options.math_shorthand) - else return tostring(value) +local function format_primitive (value) + return tostring(value) +end + +--[[ + + if not options.more_function_info or depth ~= 0 then return table.concat(l, '') end + + -- More info! -- + + -- source + l[#l+1] = '\n' + l[#l+1] = options.indent + l[#l+1] = 'source = ' + l[#l+1] = info.short_src -- Maybe change to a longer + + -- upvalues + if info.nups > 0 then + l[#l+1] = '\n' + l[#l+1] = options.indent + l[#l+1] = 'upvalues = ' + l[#l+1] = format_value(info.ups, options, depth + 1) + end + + + --if info.nups > 0 and not info.builtin and info.more_function_info then + l[#l+1] = '(' + l[#l+1] = tostring(value) + l[#l+1] = '): ' + l[#l+1] = format_value(info, options, depth + 1) + --end +]] + +local function format_function (value, options, depth) + local info = get_function_info(value) + + local l = {} + + -- Func def + if info.builtin then l[#l+1] = 'builtin ' end + l[#l+1] = 'function (' + -- List parameters + for _, param in ipairs(info.params) do + l[#l+1] = param + l[#l+1] = ', ' + end + -- Show varg + if info.isvararg then l[#l+1] = '...' end + + -- Cleanup and finish + if l[#l] == ', ' then l[#l] = nil end + l[#l+1] = ') ... end' + + return table.concat(l, '') +end + +local TYPE_TO_FORMAT_FUNC = { + ['nil'] = format_primitive, + ['boolean'] = format_primitive, + ['number'] = format_number, + ['string'] = format_string, + ['thread'] = format_coroutine, + ['table'] = format_table, + + -- TODO + ['function'] = format_function, + ['userdata'] = format_primitive, + ['cdata'] = format_primitive, -- Luajit exclusive ? +} + +function format_value (value, options, depth) + local format_func = TYPE_TO_FORMAT_FUNC[type(value)] + if format_func then + return format_func(value, options, depth) + else + error(ERROR_UNKNOWN_TYPE:format(type(value), tostring(value))) end end @@ -430,7 +526,7 @@ end local function pretty_format (value, options) local options = options or {} - options.max_depth = options.max_depth or math.huge + options.max_depth = options.max_depth or 3--math.huge options.indent = options.indent or '\t' return format_value(value, options, 0) end diff --git a/test/TestSuite.lua b/test/TestSuite.lua index 3cec5a9..67d3435 100644 --- a/test/TestSuite.lua +++ b/test/TestSuite.lua @@ -177,7 +177,7 @@ function TestSuite:runTests (parent_prefix, parent_indent) debug.sethook(nil, 'l') -- Write work (or not.) if success then - print_status(ext_name, 'SUCCESS!', TERM_COLOR_CODE_GREEN) + --print_status(ext_name, 'SUCCESS!', TERM_COLOR_CODE_GREEN) else print_status(ext_name, 'ERROR!', TERM_COLOR_CODE_RED) traceback = indent_string(traceback, '\t') diff --git a/test/test_pretty.lua b/test/test_pretty.lua index cdd6253..389d665 100644 --- a/test/test_pretty.lua +++ b/test/test_pretty.lua @@ -4,6 +4,12 @@ SUITE:setEnviroment{ format = require('pretty') } +local ASSERT_ERROR_APPROX = [[ +Approximate strings not similar enough: + Should match: %s + Gotten: %s +]] + -------------------------------------------------------------------------------- local function format_test (t) @@ -12,7 +18,13 @@ local function format_test (t) local input_options = t.options local expected_result = t.expect local actual_result = format(input_value, input_options) - assert_equal(actual_result, expected_result) + if not t.approx or type(actual_result) ~= 'string' then + assert_equal(actual_result, expected_result) + else + if not actual_result:match(expected_result) then + error(ASSERT_ERROR_APPROX:format(expected_result, actual_result)) + end + end end) end @@ -140,6 +152,140 @@ format_test { expect = 'nan', } +-------------------------------------------------------------------------------- +-- Primitive types + +format_test { + input = nil, + expect = 'nil', +} + +format_test { + input = true, + expect = 'true', +} + +format_test { + input = false, + expect = 'false', +} + +-------------------------------------------------------------------------------- +-- Function printing + +format_test { + input = function () end, + expect = 'function () ... end', +} + +format_test { + input = function (a) end, + expect = 'function (a) ... end', +} + +format_test { + input = function (a, b) end, + expect = 'function (a, b) ... end', +} + +format_test { + input = function (...) end, + expect = 'function (...) ... end', +} + +format_test { + input = function (a, b, ...) end, + expect = 'function (a, b, ...) ... end', +} + +do + local SOME_RANDOM_UPVALUE = false + + format_test { + input = function () l = SOME_RANDOM_UPVALUE end, + expect = 'function () ... end', + } + + format_test { + input = function () SOME_RANDOM_UPVALUE = true end, + expect = 'function () ... end', + } + + format_test { + -- More function info is ignored if not at depth 0. + input = { a = function () SOME_RANDOM_UPVALUE = true end }, + options = { more_function_info = true }, + expect = '{\n\ta = function () ... end\n}', + } + + format_test { + input = function () l = SOME_RANDOM_UPVALUE end, + options = { more_function_info = true }, + expect = 'function ()\n\t-- source_file = \'./test/test_pretty.lua\'\n\t-- up_values = { SOME_RANDOM_UPVALUE = false }\n\n\t...\nend' + } + + format_test { + input = function () SOME_RANDOM_UPVALUE = true end, + options = { more_function_info = true }, + expect = 'function ()\n\t-- source_file = \'./test/test_pretty.lua\'\n\t-- up_values = { SOME_RANDOM_UPVALUE = false }\n\n\t...\nend' + } +end + +format_test { + input = function () end, + options = { more_function_info = true }, + expect = 'function () ... end', +} + +do + local index = 0 + + format_test { + input = function () index = index + 1; return index end, + expect = 'function ()\n\t-- source_file = \'./test/test_pretty.lua\'\n\t-- up_values = { index = 0 }\n\n\t...\nend' + } +end + +format_test { + input = loadstring('return function () end')(), + expect = 'function () ... end', +} + +format_test { + input = pairs, + expect = 'builtin function (...) ... end', +} + +-------------------------------------------------------------------------------- +-- Userdata printing + +format_test { + input = 'DUNNO MAYBE USE LUAJIT IN SOME WAY?', + expect = 'TODO', +} + +-------------------------------------------------------------------------------- +-- 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 @@ -281,6 +427,31 @@ format_test { expect = '{\n\t[1] = 1,\n\t[\'whatever\'] = false\n}', } +-------------------------------------------------------------------------------- +-- 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