Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server: restore exec() sparse output support #351

Merged
merged 1 commit into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Fixed incorrent unix socket path length check (gh-341).
* Now net_box_uri can be accepted as table (gh-342).
* Fixed returning values from `Server:exec()` if some of them are nil (gh-350).

## 1.0.0

Expand Down
19 changes: 15 additions & 4 deletions luatest/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,10 @@ function Server:exec(fn, args, options)
:format(utils.get_fn_location(fn)))
end

local utils_dumps = {
unpack_sparse_array = string.dump(utils.unpack_sparse_array),
}

-- The function `fn` can return multiple values and we cannot use the
-- classical approach to work with the `pcall`:
--
Expand All @@ -694,14 +698,21 @@ function Server:exec(fn, args, options)
-- `result` variable will contain only `1` value, not `1, 2, 3`.
-- To solve this, we put everything from `pcall` in a table.
return exec_tail(pcall(self.net_box.eval, self.net_box, [[
local dump, args, passthrough_ups = ...
local fn = loadstring(dump)
local fn_dump, args, passthrough_ups, utils_dumps = ...

local fn = loadstring(fn_dump)
for i = 1, debug.getinfo(fn, 'u').nups do
local name, _ = debug.getupvalue(fn, i)
if passthrough_ups[name] then
debug.setupvalue(fn, i, require(passthrough_ups[name]))
end
end

local utils = {}
for k, v in pairs(utils_dumps) do
utils[k] = loadstring(v)
end

local result
if args == nil then
result = {pcall(fn)}
Expand All @@ -714,8 +725,8 @@ function Server:exec(fn, args, options)
end
error(result[2], 0)
end
return unpack(result, 2)
]], {string.dump(fn), args, passthrough_ups}, options))
return utils.unpack_sparse_array(result, 2)
]], {string.dump(fn), args, passthrough_ups, utils_dumps}, options))
end

function Server:coverage(action)
Expand Down
22 changes: 22 additions & 0 deletions luatest/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,26 @@ function utils.is_tarantool_binary(path)
return path:find('^.*/tarantool[^/]*$') ~= nil
end

function utils.unpack_sparse_array(array, index)
-- Embed everything so no upvalues required on server.
index = index or 1

local len = 0
for k, _ in pairs(array) do
ochaplashkin marked this conversation as resolved.
Show resolved Hide resolved
if type(k) == 'number' then
len = math.max(len, k)
end
end

local function unpack_sparse_array_tail(array, index, len) -- luacheck: ignore
if index > len then
return
end

return array[index], unpack_sparse_array_tail(array, index + 1, len)
end

return unpack_sparse_array_tail(array, index, len)
end

return utils
9 changes: 9 additions & 0 deletions test/autorequire_luatest_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,12 @@ end
g.after_test('test_exec_when_luatest_not_found', function()
g.bad_env_server:drop()
end)

g.test_exec_with_sparse_output = function()
local res1, res2 = g.server:exec(function()
return nil, 'some error'
end)

t.assert_equals(res1, nil)
t.assert_equals(res2, 'some error')
end
81 changes: 81 additions & 0 deletions test/utils_test.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
local json = require('json')

local t = require('luatest')
local g = t.group()

Expand All @@ -22,3 +24,82 @@ g.test_is_tarantool_binary = function()
("Unexpected result for %q"):format(path))
end
end

g.test_unpack_sparse_array_with_values = function()
local non_sparse_input = {
{{1, 2, 3, 4}, nil},
{{1, 2, 3, 4}, 3},
}

-- Test unpack_sparse_array() is unpack() if non-sparse input.
local non_sparse_cases = {}
for _, v in ipairs(non_sparse_input) do
table.insert(non_sparse_cases, {v[1], v[2], {unpack(v[1], v[2])}})
end

local sparse_cases = {
{{1, nil, 3}, nil, {1, nil, 3}},
{{1, nil, 3}, 2, {nil, 3}},
{{nil, 2, nil}, nil, {nil, 2}},
{{nil, 2, nil}, 2, {2}},
{{nil, 2, box.NULL}, nil, {nil, 2, box.NULL}},
{{nil, 2, box.NULL}, 3 ,{box.NULL}},
{{nil, nil, nil, nil, 5}, 4, {nil, 5}},
{{nil, nil, nil, nil, 5}, 5, {5}},
}

local cases = {unpack(non_sparse_cases), unpack(sparse_cases)}

for _, case in ipairs(cases) do
local array, index, result_packed = unpack(case)

local assert_msg
if index ~= nil then
assert_msg = ("Unexpected result for unpack_sparse_array(%q, %d)"):format(
json.encode(array), index)
else
assert_msg = ("Unexpected result for unpack_sparse_array(%q)"):format(
json.encode(array))
end

t.assert_equals(
{utils.unpack_sparse_array(array, index)},
result_packed,
assert_msg
)
end
end

local function assert_return_no_values(func, ...)
-- http://lua-users.org/lists/lua-l/2011-09/msg00312.html
t.assert_error_msg_contains(
"bad argument #1 to 'assert' (value expected)",
function(...)
assert(func(...))
end,
...
)
end

g.test_unpack_sparse_array_no_values = function()
local non_sparse_cases = {
{{1, 2, 3, 4}, 5},
{{}, 1},
}

local sparse_cases = {
{{1, nil, 3}, 6},
}

-- Assert built-in unpack() symmetric behavior.
for _, case in ipairs(sparse_cases) do
local array, index = unpack(case)
assert_return_no_values(unpack, array, index)
end

local cases = {unpack(non_sparse_cases), unpack(sparse_cases)}
for _, case in ipairs(cases) do
local array, index = unpack(case)
assert_return_no_values(utils.unpack_sparse_array, array, index)
end
end
Loading