diff options
| author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-17 15:34:44 +0200 |
|---|---|---|
| committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-17 15:34:44 +0200 |
| commit | 823d3d570236e32bdbe270306df5602ff586c4a7 (patch) | |
| tree | 88e60e92f4c5bf618588ec0b64f295540390ae8f /tests | |
| parent | d359e8cf7d71c2f80a1706fef38e603478ac003f (diff) | |
| download | lanes-823d3d570236e32bdbe270306df5602ff586c4a7.tar.gz lanes-823d3d570236e32bdbe270306df5602ff586c4a7.tar.bz2 lanes-823d3d570236e32bdbe270306df5602ff586c4a7.zip | |
Error reporting revamp
* set_error_reporting() is gone
* new lane generator setting error_reporting_level
* basic/extended stack trace is selectable at runtime instead of compile-time
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/error.lua | 264 |
1 files changed, 224 insertions, 40 deletions
diff --git a/tests/error.lua b/tests/error.lua index 91ccebc..126161e 100644 --- a/tests/error.lua +++ b/tests/error.lua | |||
| @@ -1,58 +1,242 @@ | |||
| 1 | -- | 1 | -- |
| 2 | -- Error reporting | 2 | -- Error reporting |
| 3 | -- | 3 | -- |
| 4 | -- Note: this code is supposed to end in errors; not included in 'make test' | 4 | local which_tests, remaining_tests = {}, {} |
| 5 | -- | 5 | for k,v in ipairs{...} do |
| 6 | print("got arg:", type(v), tostring(v)) | ||
| 7 | which_tests[v] = true | ||
| 8 | remaining_tests[v] = true | ||
| 9 | end | ||
| 10 | |||
| 11 | --################################################################################################## | ||
| 12 | |||
| 13 | local lanes = require "lanes".configure{with_timers = false} | ||
| 14 | local null = lanes.null | ||
| 15 | |||
| 16 | --################################################################################################## | ||
| 17 | |||
| 18 | -- Lua51 support | ||
| 19 | local table_unpack = unpack or table.unpack | ||
| 6 | 20 | ||
| 7 | local lanes = require "lanes".configure{ with_timers = false} | 21 | --################################################################################################## |
| 8 | 22 | ||
| 9 | local function lane( mode_) | 23 | local WR = function(...) |
| 10 | set_error_reporting( mode_) | 24 | local str="" |
| 11 | local subf= function() -- this so that we can see the call stack | 25 | for i=1,select('#',...) do |
| 12 | error "aa" | 26 | local v = select(i,...) |
| 13 | --error({}) | 27 | if type(v) == "function" then |
| 14 | --error(error) | 28 | local infos = debug.getinfo(v) |
| 29 | --[[for k,v in pairs(infos) do | ||
| 30 | print(k,v) | ||
| 31 | end]] | ||
| 32 | v = infos.source..":"..infos.linedefined | ||
| 33 | end | ||
| 34 | str= str..tostring(v).."\t" | ||
| 15 | end | 35 | end |
| 16 | local subf2= function() | 36 | if io then |
| 17 | subf() | 37 | io.stderr:write(str.."\n") |
| 18 | end | 38 | end |
| 19 | subf2() | ||
| 20 | end | 39 | end |
| 21 | 40 | ||
| 22 | local function cleanup(err) | 41 | --################################################################################################## |
| 42 | |||
| 43 | local lane_body = function(error_value_, finalizer_, finalizer_error_value_) | ||
| 44 | WR( "In Lane body: EV: ", error_value_, " F: ", finalizer_, " FEV: ", finalizer_error_value_) | ||
| 45 | if finalizer_ then | ||
| 46 | local finalizer = function(err_, stack_) | ||
| 47 | finalizer_(err_, stack_) | ||
| 48 | if finalizer_error_value_ then | ||
| 49 | WR ("Finalizer raises ", finalizer_error_value_) | ||
| 50 | error(finalizer_error_value_, 0) -- 0 so that error() doesn't alter the error value | ||
| 51 | end | ||
| 52 | end | ||
| 53 | set_finalizer(finalizer) | ||
| 54 | end | ||
| 55 | |||
| 56 | local subf = function() -- this so that we can see the call stack | ||
| 57 | if error_value_ then | ||
| 58 | error(error_value_, 0) -- 0 so that error() doesn't alter the error value | ||
| 59 | end | ||
| 60 | return "success" | ||
| 61 | end | ||
| 62 | local subf2 = function(b_) | ||
| 63 | return b_ or subf() -- prevent tail call | ||
| 64 | end | ||
| 65 | local subf3 = function(b_) | ||
| 66 | return b_ or subf2() -- prevent tail call | ||
| 67 | end | ||
| 68 | return subf3(false) | ||
| 23 | end | 69 | end |
| 24 | 70 | ||
| 25 | local lgen = lanes.gen("*", { --[[finalizer=cleanup]] }, lane) | 71 | --################################################################################################## |
| 26 | 72 | ||
| 27 | --- | 73 | local lane_finalizer = function(err_, stack_) |
| 28 | io.stderr:write( "\n** Error catching **\n" ) | 74 | WR("In finalizer: ", err_, stack_) |
| 29 | -- | 75 | end |
| 30 | 76 | ||
| 31 | local error_reporting_mode = "extended" | 77 | --################################################################################################## |
| 32 | local h= lgen( error_reporting_mode) | 78 | |
| 33 | local _,err,stack= h:join() -- wait for the lane (no automatic error propagation) | 79 | local start_lane = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) |
| 80 | return lanes.gen("*", {error_trace_level = error_reporting_mode_}, lane_body)(error_value_, finalizer_, finalizer_error_value_) | ||
| 81 | end | ||
| 34 | 82 | ||
| 35 | if err then | 83 | --################################################################################################## |
| 36 | assert( type(stack)=="table" ) -- only true if lanes was compiled with ERROR_FULL_STACK == 1 | 84 | --################################################################################################## |
| 37 | io.stderr:write( "Lane error: "..tostring(err).."\n" ) | ||
| 38 | 85 | ||
| 39 | if error_reporting_mode == "basic" then -- each stack line is a string in basic mode | 86 | local make_table_error_mt = function() |
| 40 | io.stderr:write( "\t", table.concat(stack,"\n\t"), "\n" ); | 87 | return { |
| 41 | else -- each stack line is a table in extended mode | 88 | __tostring = function() return "{error as table}" end, |
| 42 | for line, details in pairs( stack) do | 89 | __eq = function(t1_, t2_) |
| 43 | io.stderr:write( "\t", tostring( line), "\n" ); | 90 | -- because tables transfered through linda/error reporting are identical, but not equal |
| 44 | for detail, value in pairs( details) do | 91 | -- however, due to metatable caching by Lanes, their metatables should be the same |
| 45 | io.stderr:write( "\t\t", tostring( detail), ":", tostring( value), "\n") | 92 | return getmetatable(t1_) == getmetatable(t2_) |
| 46 | end | 93 | end |
| 47 | end | 94 | } |
| 48 | end | ||
| 49 | end | 95 | end |
| 50 | 96 | ||
| 51 | --- | 97 | local lane_error_as_string = "'lane error as string'" |
| 52 | io.stderr:write( "\n** Error propagation **\n" ) | 98 | local lane_error_as_table = setmetatable({}, make_table_error_mt()) |
| 53 | -- | 99 | local lane_error_as_linda = lanes.linda("'lane error'") |
| 54 | local h2= lgen( error_reporting_mode) | 100 | |
| 55 | local _= h2[0] | 101 | local finalizer_error_as_string = "'lane error as string'" |
| 56 | assert(false) -- does NOT get here | 102 | local finalizer_error_as_table = setmetatable({}, make_table_error_mt()) |
| 103 | local finalizer_error_as_linda = lanes.linda("'lane error'") | ||
| 104 | |||
| 105 | local test_settings = {} | ||
| 106 | local configure_tests = function() | ||
| 107 | local append_test = function(level, lane_error, finalizer, finalizer_error) | ||
| 108 | -- convert back null to nil | ||
| 109 | lane_error = lane_error ~= null and lane_error or nil | ||
| 110 | finalizer = finalizer ~= null and finalizer or nil | ||
| 111 | finalizer_error = finalizer_error ~= null and finalizer_error or nil | ||
| 112 | -- compose test description string | ||
| 113 | local test_header = table.concat({ | ||
| 114 | level .. " error reporting", | ||
| 115 | (lane_error and tostring(lane_error) or "no error") .. " in lane", | ||
| 116 | (finalizer and "with" or "without").. " finalizer" .. ((finalizer and finalizer_error) and " raising " .. tostring(finalizer_error) or "") | ||
| 117 | }, ", ") | ||
| 118 | print(test_header) | ||
| 119 | test_settings[test_header] = { level, lane_error, finalizer, finalizer_error } | ||
| 120 | end | ||
| 121 | -- can't store nil in tables, use null instead | ||
| 122 | local levels = { | ||
| 123 | "minimal", | ||
| 124 | "basic", | ||
| 125 | "extended", | ||
| 126 | } | ||
| 127 | local errors = { | ||
| 128 | null, | ||
| 129 | lane_error_as_string, | ||
| 130 | lane_error_as_table, | ||
| 131 | lane_error_as_linda, | ||
| 132 | } | ||
| 133 | local finalizers = { | ||
| 134 | null, | ||
| 135 | lane_finalizer, | ||
| 136 | } | ||
| 137 | local finalizer_errors = { | ||
| 138 | null, | ||
| 139 | finalizer_error_as_string, | ||
| 140 | finalizer_error_as_table, | ||
| 141 | finalizer_error_as_linda, | ||
| 142 | } | ||
| 143 | |||
| 144 | for _, level in ipairs(levels) do | ||
| 145 | for _, lane_error in ipairs(errors) do | ||
| 146 | for _, finalizer in ipairs(finalizers) do | ||
| 147 | for _, finalizer_error in ipairs(finalizer_errors) do | ||
| 148 | append_test(level, lane_error, finalizer, finalizer_error) | ||
| 149 | end | ||
| 150 | end | ||
| 151 | end | ||
| 152 | end | ||
| 153 | end | ||
| 154 | |||
| 155 | configure_tests() | ||
| 156 | -- do return end | ||
| 157 | |||
| 158 | --################################################################################################## | ||
| 159 | --################################################################################################## | ||
| 160 | |||
| 161 | local do_error_catching_test = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) | ||
| 162 | local h = start_lane(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) | ||
| 163 | local ret,err,stack= h:join() -- wait for the lane (no automatic error propagation) | ||
| 164 | WR("Processing results for {", error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_, "}") | ||
| 165 | if err then | ||
| 166 | assert(ret == nil) | ||
| 167 | assert(error_reporting_mode_ == "minimal" or type(stack)=="table") -- only true if lane was configured with error_trace_level ~= "minimal" | ||
| 168 | if err == error_value_ then | ||
| 169 | WR("Lane regular error: ", err) | ||
| 170 | elseif err == finalizer_error_value_ then | ||
| 171 | WR("Lane finalizer error: ", err) | ||
| 172 | else | ||
| 173 | WR("Unknown error: ", type(err), err) | ||
| 174 | assert(false) | ||
| 175 | end | ||
| 176 | if error_reporting_mode_ == "minimal" then | ||
| 177 | assert(stack == nil, table.concat{"stack is a ", type(stack)}) | ||
| 178 | elseif error_reporting_mode_ == "basic" then -- each stack line is a string in basic mode | ||
| 179 | WR("STACK:\n", table.concat(stack,"\n\t")); | ||
| 180 | else -- each stack line is a table in extended mode | ||
| 181 | WR "STACK:" | ||
| 182 | for line, details in pairs(stack) do | ||
| 183 | WR("\t", line); | ||
| 184 | for detail, value in pairs(details) do | ||
| 185 | WR("\t\t", detail, ": ", value) | ||
| 186 | end | ||
| 187 | end | ||
| 188 | end | ||
| 189 | else -- no error | ||
| 190 | assert(ret == "success") | ||
| 191 | WR("No error in lane: ", ret) | ||
| 192 | end | ||
| 193 | WR "TEST OK" | ||
| 194 | end | ||
| 195 | |||
| 196 | --################################################################################################## | ||
| 197 | |||
| 198 | local do_error_propagation_test = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) | ||
| 199 | local wrapper = function() | ||
| 200 | local raises_error = (error_value_ or finalizer_error_value_) and true or false | ||
| 201 | local h = start_lane(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) | ||
| 202 | local _ = h[0] | ||
| 203 | -- if the lane is configured to raise an error, we should not get here | ||
| 204 | assert(not raises_error, "should not get here") | ||
| 205 | end | ||
| 206 | local err, msg = pcall(wrapper) | ||
| 207 | WR("Processing results for {", error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_, "}") | ||
| 208 | if err then | ||
| 209 | WR("Lane error: ", msg) | ||
| 210 | end | ||
| 211 | WR "TEST OK" | ||
| 212 | end | ||
| 213 | |||
| 214 | --################################################################################################## | ||
| 215 | |||
| 216 | local perform_test = function(title_, test_) | ||
| 217 | WR "###########################################################################" | ||
| 218 | WR ("** " .. title_ .. " **") | ||
| 219 | for desc, settings in pairs(test_settings) do | ||
| 220 | WR "---------------------------------------------------------------------------" | ||
| 221 | WR(desc) | ||
| 222 | test_(table_unpack(settings)) | ||
| 223 | end | ||
| 224 | end | ||
| 225 | |||
| 226 | if not next(which_tests) or which_tests.catch then | ||
| 227 | remaining_tests.catch = nil | ||
| 228 | perform_test("Error catching", do_error_catching_test) | ||
| 229 | end | ||
| 230 | |||
| 231 | --################################################################################################## | ||
| 232 | if not next(which_tests) or which_tests.propagate then | ||
| 233 | remaining_tests.propagate = nil | ||
| 234 | perform_test("Error propagation", do_error_propagation_test) | ||
| 235 | end | ||
| 236 | |||
| 237 | -- ################################################################################################## | ||
| 238 | |||
| 239 | local unknown_test, val = next(remaining_tests) | ||
| 240 | assert(not unknown_test, tostring(unknown_test) .. " test is unknown") | ||
| 57 | 241 | ||
| 58 | --never ends | 242 | print "\nTHE END" |
