1
0

Improved assert messages for certain expressions.

This commit is contained in:
Jon Michael Aanes 2017-10-28 13:15:57 +02:00
parent aec232efcb
commit c9842a2b29
2 changed files with 117 additions and 27 deletions

View File

@ -1,9 +1,6 @@
local lexer = require 'lua_lang'
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function get_assert_body_text (call_info)
@ -72,6 +69,57 @@ local function get_value_of_string (string_str)
assert(false)
end
local function fmt_val (val)
if type(val) == 'string' then
return string.format('%q', val)
else
return tostring(val)
end
end
local function get_variable_and_prefix (gotten_name, level)
assert(type(gotten_name) == 'string')
assert(type(level) == 'number')
--
local gotten_val, var_scope, in_func = get_variable(gotten_name, level + 1)
local func_name = (in_func == '' and ' to anonymous function') or in_func and (' to \''..in_func..'\'') or ''
return gotten_val, ('assertion failed! bad %s \'%s\'%s'):format(var_scope, gotten_name, func_name)
end
local CONSTANT_VALUE_TOKEN = {
NUMBER = tonumber,
STRING = get_value_of_string,
TRUE = function() return true end,
FALSE = function() return false end,
NIL = function() return nil end
}
local PRIMITIVE_VALUES = {
['nil'] = true,
['boolean'] = true,
}
local COMPLEX_TYPES = {
['table'] = true,
['userdata'] = true,
['cdata'] = true,
}
local function get_value_of_const_token (token)
assert(CONSTANT_VALUE_TOKEN[token.token])
return CONSTANT_VALUE_TOKEN[token.token](token.text)
end
local function fmt_with_type (val)
-- Primitive values ARE their type, and don't need the annotation.
if PRIMITIVE_VALUES[type(val)] then return tostring(val) end
-- Complex types are already formatted with some type information.
if COMPLEX_TYPES[type(val)] then return tostring(val) end
-- Numbers and string should have their types with them.
return type(val) .. ' ' .. fmt_val(val)
end
--------------------------------------------------------------------------------
return function (condition)
@ -79,12 +127,27 @@ return function (condition)
local call_info = debug.getinfo(2)
local tokens, body_text = get_assert_body(call_info)
if tokens[1].text == 'type' and tokens[2].token == 'LPAR' and tokens[3].token == 'IDENTIFIER' and tokens[4].token == 'RPAR'and tokens[5].token == 'EQ'and tokens[6].token == 'STRING' then
local var_name = tokens[3].text
local var_val, var_scope, is_func = get_variable(var_name, 2)
local func_name = (is_func == '' and ' to anonymous function') or is_func and (' to \''..is_func..'\'') or ''
error(('assertion failed! bad %s \'%s\'%s (%s expected, but got %s: %s)'):format(var_scope, var_name, func_name, get_value_of_string(tokens[6].text), type(var_val), var_val), 2)
if #tokens == 6 and tokens[1].text == 'type' and tokens[2].token == 'LPAR' and tokens[3].token == 'IDENTIFIER' and tokens[4].token == 'RPAR'and tokens[5].token == 'EQ'and tokens[6].token == 'STRING' then
local gotten_val, prefix = get_variable_and_prefix(tokens[3].text, 2)
error(('%s (%s expected, but got %s: %s)'):format(prefix, get_value_of_string(tokens[6].text), type(gotten_val), fmt_val(gotten_val)), 2)
elseif #tokens == 3 and tokens[1].token == 'IDENTIFIER' and tokens[2].token == 'EQ' and CONSTANT_VALUE_TOKEN[tokens[3].token] then
local gotten_val, prefix = get_variable_and_prefix(tokens[1].text, 2)
local expected_value = get_value_of_const_token(tokens[3])
local type_annotation = (type(expected_value) == type(gotten_val)) and '' or (' '..type(gotten_val))
error(('%s (%s expected, but got%s: %s)'):format(prefix, fmt_with_type(expected_value), type_annotation, fmt_val(gotten_val)), 2)
elseif #tokens == 3 and tokens[1].token == 'IDENTIFIER' and tokens[2].token == 'NEQ' and CONSTANT_VALUE_TOKEN[tokens[3].token] then
local gotten_val, prefix = get_variable_and_prefix(tokens[1].text, 2)
local expected_value = get_value_of_const_token(tokens[3])
error(('%s (expected anything other than %s, but got %s)'):format(prefix, fmt_with_type(expected_value), fmt_val(gotten_val)), 2)
elseif #tokens == 1 and tokens[1].token == 'IDENTIFIER' then
local gotten_val, prefix = get_variable_and_prefix(tokens[1].text, 2)
error(('%s (truthy expected, but got %s)'):format(prefix, fmt_val(gotten_val)), 2)
else
print(require'pretty'(tokens))
error(('assertion failed! expression `%s` evaluated to %s'):format(body_text, condition), 2)
end
end

View File

@ -45,25 +45,6 @@ SUITE:addTest('argument to named function', function ()
assert_equal('./test/test_assert-gooder.lua:'..curline(-3)..': '..'assertion failed! bad argument #1 \'a\' to \'f\' (string expected, but got number: 2)', msg)
end)
--------------------------------------------------------------------------------
-- Other assert types
SUITE:addTest('compare values', function ()
local _, msg = pcall(function ()
local a = 2
assert(a == 4)
end)
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (value of 4 expected, but got: 2)', msg)
end)
SUITE:addTest('compare values across types', function ()
local _, msg = pcall(function ()
local a = "hi"
assert(a == 4)
end)
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (value of 4 expected, but got string: "hi")', msg)
end)
--------------------------------------------------------------------------------
SUITE:addTest('can improve asserts in loaded strings too', function ()
@ -80,6 +61,52 @@ SUITE:addTest('really complicated expression', function ()
assert_equal('./test/test_assert-gooder.lua:'..curline(-3)..': '..'assertion failed! expression `a == 3 and math.floor(2.522) == 2 or 5 == n` evaluated to false', msg)
end)
--------------------------------------------------------------------------------
-- Other assert types
SUITE:addTest('compare values', function ()
local _, msg = pcall(function ()
local a = 2
assert(a == 4)
end)
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (number 4 expected, but got: 2)', msg)
end)
SUITE:addTest('compare values across types', function ()
local _, msg = pcall(function ()
local a = "hi"
assert(a == 4)
end)
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (number 4 expected, but got string: "hi")', msg)
end)
SUITE:addTest('is nil', function ()
local _, msg = pcall(function ()
local a = "hi"
assert(a == nil)
end)
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (nil expected, but got string: "hi")', msg)
end)
SUITE:addTest('truthy', function ()
local _, msg = pcall(function ()
local a = false
assert(a)
end)
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (truthy expected, but got false)', msg)
end)
SUITE:addTest('not equal', function (constant_value, msg_in_pars)
local func = loadstring (("return function() local a = %s; assert(a ~= %s) end"):format(constant_value, constant_value)) ()
local _, msg = pcall(setfenv(func, getfenv()))
assert_equal('[string "return function() ..."]:1: assertion failed! bad local \'a\' ('..msg_in_pars..')', msg)
end, { data = {
{ 'true' , 'expected anything other than true, but got true' },
{ 'nil', 'expected anything other than nil, but got nil' },
{ '"hi"', 'expected anything other than string "hi", but got "hi"' },
{ '42', 'expected anything other than number 42, but got 42' },
}})
--------------------------------------------------------------------------------
return SUITE