added debug features to the lua and python jrpc servers.

Added doc of lua func item
This commit is contained in:
2025-12-31 13:38:19 +01:00
parent f54e09098a
commit 2e9c5e6c52
17 changed files with 129 additions and 54 deletions

View File

@@ -166,7 +166,7 @@ The test items returning a value are:
* :ref:`dialog_value test items<sec_dialog_value_test_item>`
* :ref:`py_func test item<sec_func_item>`
* :ref:`py_func test item<sec_py_func_item>`
* :ref:`dialog_choices test item<sec_dialog_choices_test_item>`

View File

@@ -0,0 +1,65 @@
.. _sec_lua_func_item:
**lua_func** test item
============================================================
The ``lua_func`` test item is used to execute custom lua 5.4 scripts with the given
input parameters.
The ``lua_func`` test item is of the form:
.. code-block:: lua
:caption: ``lua_func`` python function example
:name: script_file.lua
local module = {}
function module.dummy_func(param1, param2, param4, param4):
...
return 10
return module
.. code-block:: yaml
:caption: corresponding ``lua_func`` tum extract
- lua_func:
name: lua function test item
file: script_file.lua
func_name: dummy_func
param:
- 123
- 0.123
- True
- $(global_dict_key)
expected_result: 10
**Attributes**
Beside common test items attributes, lua_func item has specific attribute, some of which being mandatory.
* ``file``: the script file name that contains the function to be executed.
Only python script format is supported.
* ``func_name``: The function name to be executed.
* ``param``: This is a list of parameters that are passed to the function
in the order they are presented in the script. These parameters are not
mandatory and are highly dependent of the function prototype.
.. code-block:: yaml
:caption: ``lua_func`` test item example of usage
- lua_func:
name: activity
file: script_name.lua
func_name: methodName
param:
- $(my_param)
The result of the function (after eventual post treatment) is stored in the global
variable named ``pfn_<func_name>``
(See :ref:`global variables<sec_global_variables>` for more detail
on how to access to global variables from test items and scripts).
In the example above, the global variable ``$(lfn_activity)``
would be created at the end of the item execution. It would contain the resulting
value of the funcToBeExecuted python function.

View File

@@ -1,4 +1,4 @@
.. _sec_func_item:
.. _sec_py_func_item:
**py_func** test item
============================================================
@@ -99,10 +99,10 @@ Beside common test items attributes, py_func item has specific attribute, some o
- $(my_param)
The result of the function (after eventual post treatment) is stored in the global
variable named ``fn_<func_name>``
variable named ``pfn_<func_name>``
(See :ref:`global variables<sec_global_variables>` for more detail
on how to access to global variables from test items and scripts).
In the example above, the global variable ``$(fn_function test item)``
In the example above, the global variable ``$(pfn_function test item)``
would be created at the end of the item execution. It would contain the resulting
value of the funcToBeExecuted python function.

View File

@@ -142,7 +142,8 @@ library API (see :ref:`helper library<sec_python_helper_library>`)
* ``ts_end_<item_name>``: timestamp at the end of test item execution (see :ref:`sec_item_common`),
* ``ts_duration_<item_name>``: duration of test item execution in seconds (see :ref:`sec_item_common`),
* ``cn_<test_name>``: console test item result (see section :ref:`sec_console_test_item`),
* ``fn_<func_name>``: py_func test item result (see section :ref:`sec_func_item`),
* ``pfn_<func_name>``: py_func test item result (see section :ref:`sec_py_func_item`),
* ``lfn_<func_name>``: lua_func test item result (see section :ref:`sec_lua_func_item`),
* ``cs_<test_name>``: dialog_choices test item result (see section :ref:`sec_dialog_choices_test_item`),
* ``loop_param``: loop iterator (available from within a loop item,
see :ref:`sec_loop_item`),
@@ -237,6 +238,7 @@ step list attributes.
test_items/json-rpc_test_item.rst
test_items/let_test_item.rst
test_items/loop_test_item.rst
test_items/lua_func_test_item.rst
test_items/plot_test_item.rst
test_items/report_test_item.rst
test_items/run_test_item.rst

Binary file not shown.

View File

@@ -8,7 +8,7 @@ from interpreter.utils.eval import evaluate
class TestItemCheckValue(TestItem):
"""check item usage.
check usage:{check: {name: check my func output, steps: ['$(fn_echo) < 5']}}
check usage:{check: {name: check my func output, steps: ['$(pfn_echo) < 5']}}
"""
def __init__(self, dict_item, parent = None, status_queue=None, filename=""):
self._name = cst.TYPE_CHECK.item_name

View File

@@ -50,6 +50,7 @@ class TestItemLuaFunc(TestItem):
if success == TestValue.SUCCESS:
self.result.set(TestValue.SUCCESS)
res, reported_values = ret
print(res)
reported_values = {**reported_values, "returned": res}
self.result.reported = ret[1]
@@ -58,7 +59,7 @@ class TestItemLuaFunc(TestItem):
tm.print_debug(textwrap.indent(pprint.pformat(res), " |"))
# The result of the func test item is put in global dir and result
tm.setgd("fn_" + self._name, res)
tm.setgd("lfn_" + self._name, res)
self.result.value = res
else:

View File

@@ -58,7 +58,7 @@ class TestItemPyFunc(TestItem):
tm.print_debug(textwrap.indent(pprint.pformat(res), " |"))
# The result of the func test item is put in global dir and result
tm.setgd("fn_" + self._name, res)
tm.setgd("pfn_" + self._name, res)
self.result.value = res
else:

View File

@@ -38,7 +38,7 @@ Usage example (server):
Usage example (client):
clt = JsonRpcClient(port)
clt = JsonRpcClient(host, port)
clt.start()
result = clt.call('method_name', {'foo': 'bar'})
@@ -236,8 +236,9 @@ class JsonRpcBase(threading.Thread):
- `call()` raises `ETUMRuntimeError` if no active connection exists.
"""
def __init__(self, port, req_handler: Callable[[dict], Any]=None, timeout=10, dbg_out=None):
def __init__(self, host, port, req_handler: Callable[[dict], Any]=None, timeout=10, dbg_out=None):
super().__init__()
self._host = host
self._port = port
self._timeout = timeout
self._rpc = None
@@ -306,8 +307,8 @@ class JsonRpcSrv(JsonRpcBase):
The server will raise `ETUMRuntimeError` on accept/connect timeout.
"""
def __init__(self, port, req_handler = None, timeout=10):
super().__init__(port, req_handler, timeout)
def __init__(self, host, port, req_handler = None, timeout=10):
super().__init__(host, port, req_handler, timeout)
self.name = f"JsonRpcSvr_{port}"
def run(self):
@@ -318,12 +319,13 @@ class JsonRpcSrv(JsonRpcBase):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Link of the socket at the configured port
sock.bind(("localhost", self._port))
sock.bind((self._host, self._port))
sock.settimeout(self._timeout)
# Listens incoming connections
sock.listen(1)
self.print_info(f"listening on {self._host}:{self._port}")
self.print_info("awaiting connection")
tslice = 0.2
@@ -331,6 +333,7 @@ class JsonRpcSrv(JsonRpcBase):
while True:
try:
conn, addr = sock.accept()
self.print_info("Client connected")
except socket.timeout:
if t >= 0:
sleep(tslice)
@@ -364,13 +367,13 @@ class JsonRpcClient(JsonRpcBase):
Typical usage::
clt = JsonRpcClient(port)
clt = JsonRpcClient(host, port)
clt.start()
resp = clt.call('method', {'a': 1})
"""
def __init__(self, port, req_handler = None, timeout=10):
super().__init__(port, req_handler, timeout)
def __init__(self, host, port, req_handler = None, timeout=10):
super().__init__(host, port, req_handler, timeout)
self.name = f"JsonRpcClt_{port}"
def run(self):
@@ -383,7 +386,7 @@ class JsonRpcClient(JsonRpcBase):
t = self._timeout
while True:
try:
sock.connect(("localhost", self._port))
sock.connect((self._host, self._port))
except OSError:
t -= tslice
if t >= 0:

View File

@@ -75,15 +75,20 @@ class LuaFuncExecEngine:
func_proc_path = os.path.join(tm.gd("testium_path"),"lua_func")
lua_env = tm.gd("lua_env", {})
params = [self._lpath, "main.lua", "--host", "127.0.0.1", "--port", f"{self._port}"]
if tm.debug_enabled():
params.append("--verbose")
self._process = subprocess.Popen(
[self._lpath, "main.lua", "--host", "127.0.0.1", "--port", f"{self._port}"], env=lua_env, cwd=func_proc_path
params, env=lua_env, cwd=func_proc_path
)
# Port was reserved until the sub-process is started. Now released.
if sock is not None:
sock.close()
self._rpc = JsonRpcClient(self._port, req_handler=self._req_handler)
self._rpc = JsonRpcClient("localhost", self._port, req_handler=self._req_handler)
self._rpc.start()
def join(self):

View File

@@ -78,15 +78,19 @@ class PyFuncExecEngine:
func_proc_path = tm.gd("testium_path")
params = [self._ppath, "-m", "py_func", "-p", f"{self._port}"]
if tm.debug_enabled():
params.append("-v")
self._process = subprocess.Popen(
[self._ppath, "-m", "py_func", "-p", f"{self._port}"], cwd=func_proc_path
params, cwd=func_proc_path
)
# Port was reserved until the sub-process is started. Now released.
if sock is not None:
sock.close()
self._rpc = JsonRpcClient(self._port, req_handler=self._req_handler)
self._rpc = JsonRpcClient("localhost", self._port, req_handler=self._req_handler)
self._rpc.start()
def join(self):

View File

@@ -27,7 +27,7 @@ local function _get_func_by_path(file_path, func_name)
local target_func = module[func_name]
if type(target_func) ~= "function" then
return nil, "Function '" .. func_name .. "' not found in " .. file_path
return nil, "Function '" .. func_name .. "' not found in '" .. file_path .. "'"
end
return target_func
@@ -52,9 +52,10 @@ function handle.func_call(params)
-- 3. Execute the function
if err == nil then
print(string.format("Function executed from '%s'", pfile))
utils.log("func_call function found '%s', '%s'", file, fname)
succ, ret = pcall(func, table.unpack(prms))
utils.log("func_call returned '%s'", tostring(ret))
utils.log("func_call returned '%s', '%s'", tostring(succ), tostring(ret))
if succ then
res = ret

View File

@@ -6,9 +6,9 @@ JSONRPC.__index = JSONRPC
function JSONRPC.new(sock)
local self = setmetatable({}, JSONRPC)
self.sock = sock -- Function to transmit string data to transport (TCP/Websocket)
self.methods = {} -- Methods the server provides to the client
self.pending = {} -- Requests sent to client waiting for response
self.sock = sock -- Function to transmit string data to transport (TCP/Websocket)
self.methods = {} -- Methods the server provides to the client
self.pending = {} -- Requests sent to client waiting for response
self.next_id = 1
self.sock:settimeout(0.2)
@@ -24,7 +24,7 @@ end
function JSONRPC:handle_message(raw_data)
utils.log("received: '%s'", raw_data)
local ok, msg = pcall(json.decode, raw_data)
if not ok then return self:_send_error(nil, -32700, "Parse error") end
if not ok then return self:_send_error(nil, "received message parse error in lua server") end
-- 1. Check if it's a Response (has 'result' or 'error' and 'id')
if (msg.id ~= nil) and (msg.result ~= nil or msg.error ~= nil) then
@@ -44,37 +44,27 @@ function JSONRPC:_handle_request(req)
local ok, ret
local res, err
if not method then
if req.id then self:_send_error(req.id, -32601, "Method not found") end
if req.id then self:_send_error(req.id, string.format("Method '%s' not registered in lua server")) end
return
end
utils.log("calling '%s'", method)
ok, ret = pcall(method, req.params)
utils.log("returned '%s', '%s'", tostring(ok), tostring(res))
ok, ret, err = pcall(method, req.params)
utils.log("Call returned ok='%s', ret='%s'", tostring(ok), tostring(ret))
-- Only send response if it's not a Notification (notifications have no ID)
if req.id then
if ok then
res, err = ret
res = ret
if res == nil then
self:_send_error(req.id, -32603, "Internal error: " .. tostring(err))
self:_send_error(req.id, tostring(err))
else
self:_send({ jsonrpc = "2.0", result = {returned = res}, id = req.id })
self:_send({ jsonrpc = "2.0", result = { returned_value = res }, id = req.id })
end
else
self:_send_error(req.id, -32603, "Internal error: " .. tostring(ret))
self:_send_error(req.id, tostring(err))
end
end
end
--- INTERNAL: Handle responses to requests WE sent
-- function JSONRPC:_handle_response(res)
-- local callback = self.pending[res.id]
-- if callback then
-- callback(res.error, res.result)
-- self.pending[res.id] = nil
-- end
-- end
--- Call a method on the client
function JSONRPC:call(method, params)
local id = self.next_id
@@ -109,10 +99,10 @@ function JSONRPC:_send(data)
return self.sock:send(j .. "\n")
end
function JSONRPC:_send_error(id, code, message)
function JSONRPC:_send_error(id, message)
self:_send({
jsonrpc = "2.0",
error = { code = code, message = message },
error = message,
id = id
})
end

View File

@@ -5,7 +5,7 @@ local config = {
host = "0.0.0.0",
port = 9000,
timeout = 60,
verbose = true,
verbose = false,
}
local function usage()

View File

@@ -22,15 +22,19 @@ def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--ip", type=str, help="Ip address or hostname to listen to",
default="localhost")
parser.add_argument("-p", "--port", type=int, help="port to listen to",
default="/etc/jsonrpc-echo.conf")
default=9000)
parser.add_argument("-v", "--verbose", action='store_true', help="port to listen to")
args = parser.parse_args()
thrd_api = _init_api(args.port)
thrd_api = _init_api(args.ip, args.port)
outstream = TcpStdOut()
stdio_redir.redirect(outstream)
# debug the server
# thrd_api.dbg_out = stdio_redir.ini_stdout
if args.verbose:
thrd_api.dbg_out = stdio_redir.ini_stdout
try:
while thrd_api.is_alive():
thrd_api.join(1)

View File

@@ -46,7 +46,7 @@ def func_exec(file: str, func_name: str, params: list, verbose: bool=True):
reported_values = {}
mod = func_module(file)
if verbose:
print("Function executed from {}".format(
print("Function executed from '{}'".format(
inspect.getabsfile(mod)))
# check of the FunctionItem descendants

View File

@@ -49,7 +49,7 @@ def _make_api(name):
for k in SUPPORTED_API:
setattr(thismodule, k, _make_api(k))
def _init_api(port):
def _init_api(host, port):
"""Start and initialize the remote function handler.
Starts a ``FuncHandler`` bound to ``port``, runs it and blocks until
@@ -63,7 +63,7 @@ def _init_api(port):
``_func_call_thread``.
"""
global _func_call_thread
_func_call_thread = FuncHandler(port)
_func_call_thread = FuncHandler(host, port)
_func_call_thread.start()
_func_call_thread.wait_ready()
return _func_call_thread