-- -- Error reporting -- local function PRINT(...) local str="" for i=1,select('#',...) do str= str..tostring(select(i,...)).."\t" end if io then io.stderr:write(str.."\n") end end local which_tests, remaining_tests = {}, {} for k,v in ipairs{...} do PRINT("got arg:", type(v), tostring(v)) which_tests[v] = true remaining_tests[v] = true end --################################################################################################## local lanes = require "lanes".configure{with_timers = false} local null = lanes.null --################################################################################################## -- Lua51 support local table_unpack = unpack or table.unpack --################################################################################################## local WR = function(...) local str="" for i=1,select('#',...) do local v = select(i,...) if type(v) == "function" then local infos = debug.getinfo(v) --[[for k,v in pairs(infos) do PRINT(k,v) end]] v = infos.source..":"..infos.linedefined end str= str..tostring(v).."\t" end if io then io.stderr:write(str.."\n") end end --################################################################################################## local lane_body = function(error_value_, finalizer_, finalizer_error_value_) WR( "In Lane body: EV: ", error_value_, " F: ", finalizer_, " FEV: ", finalizer_error_value_) if finalizer_ then local finalizer = function(err_, stack_) finalizer_(err_, stack_) if finalizer_error_value_ then WR ("Finalizer raises ", finalizer_error_value_) error(finalizer_error_value_, 0) -- 0 so that error() doesn't alter the error value end end set_finalizer(finalizer) end local subf = function() -- this so that we can see the call stack if error_value_ then error(error_value_, 0) -- 0 so that error() doesn't alter the error value end return "success" end local subf2 = function(b_) return b_ or subf() -- prevent tail call end local subf3 = function(b_) return b_ or subf2() -- prevent tail call end return subf3(false) end --################################################################################################## local lane_finalizer = function(err_, stack_) WR("In finalizer: ", err_, stack_) end --################################################################################################## local start_lane = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) return lanes.gen("*", { name = 'auto', error_trace_level = error_reporting_mode_ }, lane_body)(error_value_, finalizer_, finalizer_error_value_) end --################################################################################################## --################################################################################################## local make_table_error_mt = function() return { __tostring = function() return "{error as table}" end, __eq = function(t1_, t2_) -- because tables transfered through linda/error reporting are identical, but not equal -- however, due to metatable caching by Lanes, their metatables should be the same return getmetatable(t1_) == getmetatable(t2_) end } end local lane_error_as_string = "'lane error as string'" local lane_error_as_table = setmetatable({"lane error as table"}, make_table_error_mt()) local lane_error_as_linda = lanes.linda("'lane error'") local finalizer_error_as_string = "'finalizer error as string'" local finalizer_error_as_table = setmetatable({"finalizer error as table"}, make_table_error_mt()) local finalizer_error_as_linda = lanes.linda("'finalizer error'") local test_settings = {} local configure_tests = function() local append_test = function(level, lane_error, finalizer, finalizer_error) -- convert back null to nil lane_error = lane_error ~= null and lane_error or nil finalizer = finalizer ~= null and finalizer or nil finalizer_error = finalizer_error ~= null and finalizer_error or nil -- compose test description string local test_header = table.concat({ level .. " error reporting", (lane_error and tostring(lane_error) or "no error") .. " in lane", (finalizer and "with" or "without").. " finalizer" .. ((finalizer and finalizer_error) and " raising " .. tostring(finalizer_error) or "") }, ", ") PRINT(test_header) test_settings[test_header] = { level, lane_error, finalizer, finalizer_error } end -- can't store nil in tables, use null instead local levels = { "minimal", "basic", "extended", } local errors = { null, lane_error_as_string, lane_error_as_table, lane_error_as_linda, } local finalizers = { null, lane_finalizer, } local finalizer_errors = { null, finalizer_error_as_string, finalizer_error_as_table, finalizer_error_as_linda, } for _, level in ipairs(levels) do for _, lane_error in ipairs(errors) do for _, finalizer in ipairs(finalizers) do for _, finalizer_error in ipairs(finalizer_errors) do append_test(level, lane_error, finalizer, finalizer_error) end end end end WR "Tests configured" end configure_tests() -- do return end --################################################################################################## --################################################################################################## local do_error_catching_test = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) local h = start_lane(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) local ret,err,stack= h:join() -- wait for the lane (no automatic error propagation) WR("Processing results for {", error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_, "}") if err then assert(ret == nil) assert(error_reporting_mode_ == "minimal" or type(stack)=="table") -- only true if lane was configured with error_trace_level ~= "minimal" if err == error_value_ then WR("Lane regular error: ", err) elseif err == finalizer_error_value_ then WR("Lane finalizer error: ", err) else WR("Unknown error: ", type(err), err) assert(false) end if error_reporting_mode_ == "minimal" then assert(stack == nil, table.concat{"stack is a ", type(stack)}) elseif error_reporting_mode_ == "basic" then -- each stack line is a string in basic mode WR("STACK:\n", table.concat(stack,"\n\t")); else -- each stack line is a table in extended mode WR "STACK:" for line, details in pairs(stack) do WR("\t", line); for detail, value in pairs(details) do WR("\t\t", detail, ": ", value) end end end else -- no error assert(ret == "success") WR("No error in lane: ", ret) end WR "TEST OK" end --################################################################################################## local do_error_propagation_test = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) local wrapper = function() local raises_error = (error_value_ or finalizer_error_value_) and true or false local h = start_lane(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) local _ = h[0] -- if the lane is configured to raise an error, we should not get here assert(not raises_error, "should not get here") end local err, msg = pcall(wrapper) WR("Processing results for {", error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_, "}") if err then WR("Lane error: ", msg) end WR "TEST OK" end --################################################################################################## local perform_test = function(title_, test_) WR "###########################################################################" WR ("** " .. title_ .. " **") for desc, settings in pairs(test_settings) do WR "---------------------------------------------------------------------------" WR(desc) test_(table_unpack(settings)) end end if not next(which_tests) or which_tests.catch then remaining_tests.catch = nil perform_test("Error catching", do_error_catching_test) end --################################################################################################## if not next(which_tests) or which_tests.propagate then remaining_tests.propagate = nil perform_test("Error propagation", do_error_propagation_test) end -- ################################################################################################## local unknown_test, val = next(remaining_tests) assert(not unknown_test, tostring(unknown_test) .. " test is unknown") PRINT "\nTHE END"