diff --git a/src/box/lua/console.lua b/src/box/lua/console.lua index 69d9d992109d..bd8d331fbc16 100644 --- a/src/box/lua/console.lua +++ b/src/box/lua/console.lua @@ -606,7 +606,10 @@ local function local_read(self) if buf:sub(1, 1) == '\\' then break end - if delim == "" then + if delim == "" and line:endswith("\\") then + buf = buf:sub(1, -2) + -- Continue reading. + elseif delim == "" then local lang = box.session.language if not lang or lang == 'lua' then -- stop once a complete Lua statement is entered @@ -642,9 +645,12 @@ local function local_print(self, output) end -- --- Read command from connected client console.listen() +-- Read a line or a chunk from connected client console.listen(). -- -local function client_read(self) +-- The value is returned without delimiter (if any) and trailing +-- newline character. +-- +local function client_read_line(self) -- -- Byte sequences that come over the network and come from -- the local client console may have a different terminal @@ -665,6 +671,30 @@ local function client_read(self) return buf:sub(1, -#self.delimiter-2) end +-- +-- Read a command from connected client console.listen(). +-- +local function client_read(self) + local buf = "" + while true do + local line = client_read_line(self) + if line == nil then + -- EOF or error. + -- + -- Note: Even if `buf` is not empty, skip unfinished + -- command. + return nil + end + buf = buf .. line + if self.delimiter == "" and line:endswith("\\") then + buf = buf:sub(1, -2) + else + break + end + end + return buf +end + -- -- Print result to connected client from console.listen() -- diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 729baa93889a..5d030e3e2559 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -68,6 +68,14 @@ add_subdirectory(box-tap) add_subdirectory(box-luatest) add_subdirectory(sql-tap) add_subdirectory(sql-luatest) +# Build a library with isatty() that always returns 1. +# +# It is needed to test the local tarantool console. Consider an +# example: +# +# echo 42 | LD_PRELOAD=test/libisatty.so ./src/tarantool +add_library(isatty SHARED ./app-tap/isatty.c) + if(ENABLE_FUZZER) add_subdirectory(fuzz) endif() diff --git a/test/app-luatest/gh_4317_console_backslash_test.lua b/test/app-luatest/gh_4317_console_backslash_test.lua new file mode 100644 index 000000000000..cea6586fa334 --- /dev/null +++ b/test/app-luatest/gh_4317_console_backslash_test.lua @@ -0,0 +1,57 @@ +local t = require('luatest') +local g = t.group() +local console = require('console') +local socket = require('socket') +local tap = require('tap') +local fio = require('fio') +local log = require('log') + +local result_str = [[tarantool> local a = 0 \ + > for i = 1, 10 do + > a = a + i + > end \ + > print(a) +55 +--- +... + +tarantool> ]] + +g.test_local_console = function() + t.skip_if(jit.os == 'OSX', "LD_PRELOAD doesn't work on OSX") + + -- libisatty.so provides isatty() that always returns 1. + local binary = arg[-1] + local isatty_lib_path = binary:gsub('tarantool$', '../test/libisatty.so') + assert(isatty_lib_path:match('libisatty.so$'), 'Failed to locate libisatty.so') + local tarantool_command = "local a = 0 \\\nfor i = 1, 10 do\na = a + i\nend \\\nprint(a)" + + local cmd = ([[printf '%s\n' | LD_PRELOAD='%s' tarantool 2>/dev/null]]):format(tarantool_command, isatty_lib_path) + local fh = io.popen(cmd, 'r') + + -- Readline on CentOS 7 produces \e[?1034h escape sequence before tarantool> prompt, remove it. + local result = fh:read('*a'):gsub('\x1b%[%?1034h', '') + + fh:close() + t.assert_equals(result, result_str) +end + +g.test_remote_console = function() + -- Suppress console log messages + log.level(4) + local CONSOLE_SOCKET = fio.pathjoin(fio.cwd(), 'tarantool-test-console.sock') + local IPROTO_SOCKET = fio.pathjoin(fio.cwd(), 'tarantool-test-iproto.sock') + local EOL = "\n...\n" + local server = console.listen(CONSOLE_SOCKET) + local client = socket.tcp_connect("unix/", CONSOLE_SOCKET) + local cmd = 'local a = 0 \\\nfor i = 1, 10 do\\\n a = a + i \\\n end \\\n print(a)' + + client = socket.tcp_connect("unix/", CONSOLE_SOCKET) + client:read(128) + client:write(('%s\n'):format(cmd)) + local result = client:read(EOL) + t.assert_not_str_contains(result, 'error') + client:close() + os.remove(CONSOLE_SOCKET) + os.remove(IPROTO_SOCKET) +end diff --git a/test/app-tap/isatty.c b/test/app-tap/isatty.c new file mode 100644 index 000000000000..f716d2903739 --- /dev/null +++ b/test/app-tap/isatty.c @@ -0,0 +1,5 @@ +int +isatty(int fd) +{ + return 1; +}