Merge remote-tracking branch 'lethosor/mock-observe-func' into develop

develop
lethosor 2022-05-27 01:46:24 -04:00
commit ca10cbc9e6
No known key found for this signature in database
GPG Key ID: 76A269552F4F58C1
3 changed files with 91 additions and 10 deletions

@ -240,7 +240,7 @@ end
-- output doesn't trigger its own dfhack.printerr usage detection (see -- output doesn't trigger its own dfhack.printerr usage detection (see
-- detect_printerr below) -- detect_printerr below)
local orig_printerr = dfhack.printerr local orig_printerr = dfhack.printerr
local function wrap_expect(func, private) local function wrap_expect(func, private, path)
return function(...) return function(...)
private.checks = private.checks + 1 private.checks = private.checks + 1
local ret = {func(...)} local ret = {func(...)}
@ -269,7 +269,7 @@ local function wrap_expect(func, private)
end end
-- Skip any frames corresponding to C calls, or Lua functions defined in another file -- Skip any frames corresponding to C calls, or Lua functions defined in another file
-- these could include pcall(), with_finalize(), etc. -- these could include pcall(), with_finalize(), etc.
if info.what == 'Lua' and info.short_src == caller_src then if info.what == 'Lua' and (info.short_src == caller_src or info.short_src == path) then
orig_printerr((' at %s:%d'):format(info.short_src, info.currentline)) orig_printerr((' at %s:%d'):format(info.short_src, info.currentline))
end end
frame = frame + 1 frame = frame + 1
@ -278,7 +278,7 @@ local function wrap_expect(func, private)
end end
end end
local function build_test_env() local function build_test_env(path)
local env = { local env = {
test = utils.OrderedTable(), test = utils.OrderedTable(),
-- config values can be overridden in the test file to define -- config values can be overridden in the test file to define
@ -309,7 +309,7 @@ local function build_test_env()
checks_ok = 0, checks_ok = 0,
} }
for name, func in pairs(expect) do for name, func in pairs(expect) do
env.expect[name] = wrap_expect(func, private) env.expect[name] = wrap_expect(func, private, path)
end end
setmetatable(env, {__index = _G}) setmetatable(env, {__index = _G})
return env, private return env, private
@ -345,9 +345,9 @@ local function finish_tests(done_command)
end end
local function load_tests(file, tests) local function load_tests(file, tests)
local short_filename = file:sub((file:find('test') or -4)+5, -1) local short_filename = file:sub((file:find('test') or -4) + 5, -1)
print('Loading file: ' .. short_filename) print('Loading file: ' .. short_filename)
local env, env_private = build_test_env() local env, env_private = build_test_env(file)
local code, err = loadfile(file, 't', env) local code, err = loadfile(file, 't', env)
if not code then if not code then
dfhack.printerr('Failed to load file: ' .. tostring(err)) dfhack.printerr('Failed to load file: ' .. tostring(err))

@ -32,12 +32,17 @@ function _patch_impl(patches_raw, callback, restore_only)
end end
--[[ --[[
Replaces `table[key]` with `value`, calls `callback()`, then restores the
original value of `table[key]`.
Usage: Usage:
patch(table, key, value, callback) patch(table, key, value, callback)
patch({ patch({
{table, key, value}, {table, key, value},
{table2, key2, value2}, {table2, key2, value2},
}, callback) }, callback)
]] ]]
function mock.patch(...) function mock.patch(...)
local args = {...} local args = {...}
@ -57,12 +62,18 @@ function mock.patch(...)
end end
--[[ --[[
Restores the original value of `table[key]` after calling `callback()`.
Equivalent to: patch(table, key, table[key], callback)
Usage: Usage:
restore(table, key, callback) restore(table, key, callback)
restore({ restore({
{table, key}, {table, key},
{table2, key2}, {table2, key2},
}, callback) }, callback)
]] ]]
function mock.restore(...) function mock.restore(...)
local args = {...} local args = {...}
@ -81,9 +92,19 @@ function mock.restore(...)
return _patch_impl(patches, callback, true) return _patch_impl(patches, callback, true)
end end
function mock.func(...) --[[
Returns a callable object that tracks the arguments it is called with, then
passes those arguments to `callback()`.
The returned object has the following properties:
- `call_count`: the number of times the object has been called
- `call_args`: a table of function arguments (shallow-copied) corresponding
to each time the object was called
]]
function mock.observe_func(callback)
local f = { local f = {
return_values = {...},
call_count = 0, call_count = 0,
call_args = {}, call_args = {},
} }
@ -101,11 +122,36 @@ function mock.func(...)
end end
end end
table.insert(self.call_args, args) table.insert(self.call_args, args)
return table.unpack(self.return_values) return callback(...)
end, end,
}) })
return f return f
end end
--[[
Returns a callable object similar to `mock.observe_func()`, but which when
called, only returns the given `return_value`(s) with no additional side effects.
Intended for use by `patch()`.
Usage:
func(return_value [, return_value2 ...])
See `observe_func()` for a description of the return value.
The return value also has an additional `return_values` field, which is a table
of values returned when the object is called. This can be modified.
]]
function mock.func(...)
local f
f = mock.observe_func(function()
return table.unpack(f.return_values)
end)
f.return_values = {...}
return f
end
return mock return mock

@ -208,9 +208,44 @@ function test.func_call_return_value()
end end
function test.func_call_return_multiple_values() function test.func_call_return_multiple_values()
local f = mock.func(7,5,{imatable='snarfsnarf'}) local f = mock.func(7, 5, {imatable='snarfsnarf'})
local a, b, c = f() local a, b, c = f()
expect.eq(7, a) expect.eq(7, a)
expect.eq(5, b) expect.eq(5, b)
expect.table_eq({imatable='snarfsnarf'}, c) expect.table_eq({imatable='snarfsnarf'}, c)
end end
function test.observe_func()
-- basic end-to-end test for common cases;
-- most edge cases are covered by mock.func() tests
local counter = 0
local function target()
counter = counter + 1
return counter
end
local observer = mock.observe_func(target)
expect.eq(observer(), 1)
expect.eq(counter, 1)
expect.eq(observer.call_count, 1)
expect.table_eq(observer.call_args, {{}})
expect.eq(observer('x', 'y'), 2)
expect.eq(counter, 2)
expect.eq(observer.call_count, 2)
expect.table_eq(observer.call_args, {{}, {'x', 'y'}})
end
function test.observe_func_error()
local function target()
error('asdf')
end
local observer = mock.observe_func(target)
expect.error_match('asdf', function()
observer('x')
end)
-- make sure the call was still tracked
expect.eq(observer.call_count, 1)
expect.table_eq(observer.call_args, {{'x'}})
end