From 11dddb65d7ea1344d36f3239fc3422fd2792e6fe Mon Sep 17 00:00:00 2001
From: Gleb Kashkin <g.kashkin@tarantool.org>
Date: Wed, 19 Jul 2023 11:20:02 +0000
Subject: [PATCH] test: update interactive_tarantool for dbgr tests

The following changes were applied to `interactive_tarantool` helper
to adapt it for debugger tests:
* compared commands are now stripped from tabs and color codes too
* now each command independent of control symbols is being stripped
  before comparison in `execute_command()`
* created internal new function that is called from both `new()` and
  `new_debugger()`
* now user defined prompt can be set up from `new` and `new_debugger()`
* now there are several less typos in comments

Part of #7738

NO_CHANGELOG=test helper update
NO_DOC=test helper update
NO_TEST=test helper update
---
 test/interactive_tarantool.lua | 100 +++++++++++++++++++++++++++------
 1 file changed, 83 insertions(+), 17 deletions(-)

diff --git a/test/interactive_tarantool.lua b/test/interactive_tarantool.lua
index 801c769ffd..91cd9ec2b6 100644
--- a/test/interactive_tarantool.lua
+++ b/test/interactive_tarantool.lua
@@ -6,6 +6,7 @@ local fiber = require('fiber')
 local log = require('log')
 local yaml = require('yaml')
 local popen = require('popen')
+local tnt = require('tarantool')
 
 -- Default timeout for expecting an input on child's stdout.
 --
@@ -18,6 +19,8 @@ local M = {}
 local mt = {}
 mt.__index = mt
 
+local dbg_header = tnt.package .. " debugger " .. tnt.version
+
 -- {{{ Instance methods
 
 function mt._start_stderr_logger(self)
@@ -45,7 +48,14 @@ function mt._start_stderr_logger(self)
 end
 
 function mt._stop_stderr_logger(self)
-    self._stderr_logger:cancel()
+    if self._stderr_logger == nil then
+        return
+    end
+    -- The server could have finished already, thus
+    -- kill only what needs to be killed.
+    if self._stderr_logger:status() ~= 'dead' then
+        self._stderr_logger:cancel()
+    end
     self._stderr_logger = nil
 end
 
@@ -79,7 +89,7 @@ function mt.read_line(self, opts)
     return line
 end
 
--- Returns all readed lines (including expected one).
+-- Returns all read lines (including expected one).
 function mt.read_until_line(self, exp_line, opts)
     local opts = opts or {}
     local deadline = opts.deadline or (fiber.clock() + TIMEOUT)
@@ -94,6 +104,31 @@ function mt.read_until_line(self, exp_line, opts)
     return res
 end
 
+-- Returns all read lines (excluding one with expected prompt, with deadline).
+-- Checks if stderr is empty.
+function mt.read_until_prompt(self, opts)
+    local opts = opts or {}
+    local deadline = opts.deadline or (fiber.clock() + TIMEOUT)
+
+    while not self._readahead_buffer:find(self._prompt) do
+        self:read_chunk({deadline = deadline})
+    end
+
+    local res, new_buffer = unpack(self._readahead_buffer:split(self._prompt,
+                                                                1))
+    new_buffer = self._prompt .. new_buffer
+
+    self._readahead_buffer = new_buffer
+
+    local stderr, err = self.ph:read({timeout = 0.05, stderr = true})
+    if stderr ~= "" and not (stderr == nil and
+                             tostring(err) == "timed out") then
+        error(("Unexpected stderr output: %s"):format(stderr))
+    end
+
+    return res
+end
+
 function mt.assert_line(self, exp_line, opts)
     local opts = opts or {}
     local deadline = opts.deadline or (fiber.clock() + TIMEOUT)
@@ -119,6 +154,14 @@ function mt.assert_data(self, exp_data, opts)
     end
 end
 
+local function decolor(string)
+    assert(type(string) == 'string')
+    -- The gsub is meant to clean ANSI color codes, for more details on
+    -- the format see the doc:
+    -- https://www.xfree86.org/current/ctlseqs.html
+    return string:gsub(M.ESC .. '%[[0-9;]*m', '')
+end
+
 -- ReadLine echoes commands to stdout. It is easier to match the
 -- echo against the original command, when the command is a
 -- one-liner. Let's replace newlines with spaces.
@@ -138,11 +181,16 @@ end
 -- * ReadLine 7: x<CR><duplicate x>, extra spaces.
 -- * ReadLine 6: <space><CR>.
 --
--- This function drop duplicates that goes in row, strips <CR> and
--- spaces. Applying of this function to a source command and
--- readline's echoed command allows to compare them for equality.
+-- This function drop duplicates that goes in row, strips <CR>,
+-- spaces, tabs and ANSI color codes. Applying of this function
+-- to a source command and readline's echoed command allows to
+-- compare them for equality.
 local function _prepare_command_for_compare(x)
-    x = x:gsub(' ', ''):gsub(M.CR, '')
+    x = x:gsub(' ', '')
+         :gsub(M.CR, '')
+         :gsub(M.TAB, '')
+
+    x = decolor(x)
     local acc = fun.iter(x):reduce(function(acc, c)
         if acc[#acc] ~= c then
             table.insert(acc, c)
@@ -161,12 +209,9 @@ function mt._assert_command_echo(self, prepared_command, opts)
 
     -- If readline wraps the line, prepare the commands for
     -- compare.
-    local comment = ''
-    if echo:find(M.CR) ~= nil then
-        exp_echo = _prepare_command_for_compare(exp_echo)
-        echo = _prepare_command_for_compare(echo)
-        comment = ' (the commands are mangled for the comparison)'
-    end
+    exp_echo = _prepare_command_for_compare(exp_echo)
+    echo = _prepare_command_for_compare(echo)
+    local comment = ' (the commands are mangled for the comparison)'
 
     if echo ~= exp_echo then
         error(('Unexpected command echo %q, expected %q%s'):format(
@@ -236,9 +281,10 @@ function M.escape_control(str)
         :gsub(M.ESC, '<ESC>')
 end
 
-function M.new(opts)
+function M._new_internal(opts)
     local opts = opts or {}
     local args = opts.args or {}
+    local prompt = opts.prompt
 
     local tarantool_exe = arg[-1]
     local ph = popen.new(fun.chain({tarantool_exe, '-i'}, args):totable(), {
@@ -263,18 +309,35 @@ function M.new(opts)
     local res = setmetatable({
         ph = ph,
         _readahead_buffer = '',
-        _prompt = 'tarantool> ',
+        _prompt = prompt or 'tarantool> ',
     }, mt)
 
-    -- Log child's stderr.
-    res:_start_stderr_logger()
+    return res
+end
+
+function M.new_debugger(opts)
+    opts = opts or {}
+    opts.prompt = opts.prompt or 'luadebug> '
+    local debugger = M._new_internal(opts)
+    if opts.expect_header then
+        local stderr, err = debugger.ph:read({timeout = TIMEOUT, stderr = true})
+        if (stderr == nil) then
+            error(err)
+        end
+        assert(stderr:find(dbg_header, 0, true))
+    end
+    return debugger
+end
+
+function M.new(opts)
+    local res = M._new_internal(opts)
 
     -- Write a command and ignore the echoed output.
     --
     -- ReadLine 6 writes <ESC>[?1034h at beginning, it may hit
     -- assertions on the child's output.
     --
-    -- This sequence of charcters is smm ('set meta mode')
+    -- This sequence of characters is smm ('set meta mode')
     -- terminal capacity value. It has relation to writing
     -- characters out of the ASCII range -- ones with 8th bit set,
     -- but its description is vague. See terminfo(5).
@@ -286,6 +349,9 @@ function M.new(opts)
     res:execute_command("io.stdout:setvbuf('no')")
     assert(res:read_response(), true)
 
+    -- Log child's stderr.
+    res:_start_stderr_logger()
+
     return res
 end
 
-- 
GitLab