-- Some primitives for tests. function run_tests() local sorted_names = {} for name,binding in pairs(_G) do if name:find('test_') == 1 then table.insert(sorted_names, name) end end table.sort(sorted_names) Test_errors = {} for _,name in ipairs(sorted_names) do xpcall(_G[name], function(err) prepend_debug_info_to_test_failure(name, err) end) end if #Test_errors > 0 then error(('There were %d test failures:\n%s'):format(#Test_errors, table.concat(Test_errors))) end end function prepend_debug_info_to_test_failure(test_name, err) local err_without_line_number = err:gsub('^[^:]*:[^:]*: ', '') local stack_trace = debug.traceback('', --[[stack frame]]5) -- most likely to be useful, but set to 0 for a complete stack trace local file_and_line_number = stack_trace:gsub('stack traceback:\n', ''):gsub(': .*', '') local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number -- uncomment this line for a complete stack trace --? local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number..'\t\t'..stack_trace:gsub('\n', '\n\t\t') table.insert(Test_errors, full_error) end function check(x, msg) if not x then error(msg) end end function check_nil(x, msg) if x ~= nil then error(msg..'; should be nil but got "'..x..'"') end end function check_eq(x, expected, msg) if not eq(x, expected) then error(msg..'; should be "'..expected..'" but got "'..x..'"') end end function eq(a, b) if type(a) ~= type(b) then return false end if type(a) == 'table' then if #a ~= #b then return false end for k, v in pairs(a) do if not eq(b[k], v) then return false end end for k, v in pairs(b) do if not eq(a[k], v) then return false end end return true end return a == b end