Skip to content

Commit 0c06b68

Browse files
server: restore exec() sparse output support (#351)
After [1], returning values after nil (for example, classic `return nil, err` scenario) is broken. This patch restores the behavior. Since server doesn't always has access to `require('luatest.utils')`, utility is provided through its dump. Since `net.box` already handles `nil` args input with `box.NULL`, everything is fine for exec arguments. 1. e6a2093 Closes #350
1 parent f37b353 commit 0c06b68

File tree

5 files changed

+128
-4
lines changed

5 files changed

+128
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

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

89
## 1.0.0
910

luatest/server.lua

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,10 @@ function Server:exec(fn, args, options)
686686
:format(utils.get_fn_location(fn)))
687687
end
688688

689+
local utils_dumps = {
690+
unpack_sparse_array = string.dump(utils.unpack_sparse_array),
691+
}
692+
689693
-- The function `fn` can return multiple values and we cannot use the
690694
-- classical approach to work with the `pcall`:
691695
--
@@ -694,14 +698,21 @@ function Server:exec(fn, args, options)
694698
-- `result` variable will contain only `1` value, not `1, 2, 3`.
695699
-- To solve this, we put everything from `pcall` in a table.
696700
return exec_tail(pcall(self.net_box.eval, self.net_box, [[
697-
local dump, args, passthrough_ups = ...
698-
local fn = loadstring(dump)
701+
local fn_dump, args, passthrough_ups, utils_dumps = ...
702+
703+
local fn = loadstring(fn_dump)
699704
for i = 1, debug.getinfo(fn, 'u').nups do
700705
local name, _ = debug.getupvalue(fn, i)
701706
if passthrough_ups[name] then
702707
debug.setupvalue(fn, i, require(passthrough_ups[name]))
703708
end
704709
end
710+
711+
local utils = {}
712+
for k, v in pairs(utils_dumps) do
713+
utils[k] = loadstring(v)
714+
end
715+
705716
local result
706717
if args == nil then
707718
result = {pcall(fn)}
@@ -714,8 +725,8 @@ function Server:exec(fn, args, options)
714725
end
715726
error(result[2], 0)
716727
end
717-
return unpack(result, 2)
718-
]], {string.dump(fn), args, passthrough_ups}, options))
728+
return utils.unpack_sparse_array(result, 2)
729+
]], {string.dump(fn), args, passthrough_ups, utils_dumps}, options))
719730
end
720731

721732
function Server:coverage(action)

luatest/utils.lua

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,26 @@ function utils.is_tarantool_binary(path)
191191
return path:find('^.*/tarantool[^/]*$') ~= nil
192192
end
193193

194+
function utils.unpack_sparse_array(array, index)
195+
-- Embed everything so no upvalues required on server.
196+
index = index or 1
197+
198+
local len = 0
199+
for k, _ in pairs(array) do
200+
if type(k) == 'number' then
201+
len = math.max(len, k)
202+
end
203+
end
204+
205+
local function unpack_sparse_array_tail(array, index, len) -- luacheck: ignore
206+
if index > len then
207+
return
208+
end
209+
210+
return array[index], unpack_sparse_array_tail(array, index + 1, len)
211+
end
212+
213+
return unpack_sparse_array_tail(array, index, len)
214+
end
215+
194216
return utils

test/autorequire_luatest_test.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,12 @@ end
104104
g.after_test('test_exec_when_luatest_not_found', function()
105105
g.bad_env_server:drop()
106106
end)
107+
108+
g.test_exec_with_sparse_output = function()
109+
local res1, res2 = g.server:exec(function()
110+
return nil, 'some error'
111+
end)
112+
113+
t.assert_equals(res1, nil)
114+
t.assert_equals(res2, 'some error')
115+
end

test/utils_test.lua

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
local json = require('json')
2+
13
local t = require('luatest')
24
local g = t.group()
35

@@ -22,3 +24,82 @@ g.test_is_tarantool_binary = function()
2224
("Unexpected result for %q"):format(path))
2325
end
2426
end
27+
28+
g.test_unpack_sparse_array_with_values = function()
29+
local non_sparse_input = {
30+
{{1, 2, 3, 4}, nil},
31+
{{1, 2, 3, 4}, 3},
32+
}
33+
34+
-- Test unpack_sparse_array() is unpack() if non-sparse input.
35+
local non_sparse_cases = {}
36+
for _, v in ipairs(non_sparse_input) do
37+
table.insert(non_sparse_cases, {v[1], v[2], {unpack(v[1], v[2])}})
38+
end
39+
40+
local sparse_cases = {
41+
{{1, nil, 3}, nil, {1, nil, 3}},
42+
{{1, nil, 3}, 2, {nil, 3}},
43+
{{nil, 2, nil}, nil, {nil, 2}},
44+
{{nil, 2, nil}, 2, {2}},
45+
{{nil, 2, box.NULL}, nil, {nil, 2, box.NULL}},
46+
{{nil, 2, box.NULL}, 3 ,{box.NULL}},
47+
{{nil, nil, nil, nil, 5}, 4, {nil, 5}},
48+
{{nil, nil, nil, nil, 5}, 5, {5}},
49+
}
50+
51+
local cases = {unpack(non_sparse_cases), unpack(sparse_cases)}
52+
53+
for _, case in ipairs(cases) do
54+
local array, index, result_packed = unpack(case)
55+
56+
local assert_msg
57+
if index ~= nil then
58+
assert_msg = ("Unexpected result for unpack_sparse_array(%q, %d)"):format(
59+
json.encode(array), index)
60+
else
61+
assert_msg = ("Unexpected result for unpack_sparse_array(%q)"):format(
62+
json.encode(array))
63+
end
64+
65+
t.assert_equals(
66+
{utils.unpack_sparse_array(array, index)},
67+
result_packed,
68+
assert_msg
69+
)
70+
end
71+
end
72+
73+
local function assert_return_no_values(func, ...)
74+
-- http://lua-users.org/lists/lua-l/2011-09/msg00312.html
75+
t.assert_error_msg_contains(
76+
"bad argument #1 to 'assert' (value expected)",
77+
function(...)
78+
assert(func(...))
79+
end,
80+
...
81+
)
82+
end
83+
84+
g.test_unpack_sparse_array_no_values = function()
85+
local non_sparse_cases = {
86+
{{1, 2, 3, 4}, 5},
87+
{{}, 1},
88+
}
89+
90+
local sparse_cases = {
91+
{{1, nil, 3}, 6},
92+
}
93+
94+
-- Assert built-in unpack() symmetric behavior.
95+
for _, case in ipairs(sparse_cases) do
96+
local array, index = unpack(case)
97+
assert_return_no_values(unpack, array, index)
98+
end
99+
100+
local cases = {unpack(non_sparse_cases), unpack(sparse_cases)}
101+
for _, case in ipairs(cases) do
102+
local array, index = unpack(case)
103+
assert_return_no_values(utils.unpack_sparse_array, array, index)
104+
end
105+
end

0 commit comments

Comments
 (0)