diff --git a/doc/manual/sphinx/source/test_items/items_common_attributes.rst b/doc/manual/sphinx/source/test_items/items_common_attributes.rst index fa16c54..6ad9137 100644 --- a/doc/manual/sphinx/source/test_items/items_common_attributes.rst +++ b/doc/manual/sphinx/source/test_items/items_common_attributes.rst @@ -166,7 +166,7 @@ The test items returning a value are: * :ref:`dialog_value test items` -* :ref:`py_func test item` +* :ref:`py_func test item` * :ref:`dialog_choices test item` diff --git a/doc/manual/sphinx/source/test_items/lua_func_test_item.rst b/doc/manual/sphinx/source/test_items/lua_func_test_item.rst new file mode 100644 index 0000000..2ccd022 --- /dev/null +++ b/doc/manual/sphinx/source/test_items/lua_func_test_item.rst @@ -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_`` +(See :ref:`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. \ No newline at end of file diff --git a/doc/manual/sphinx/source/test_items/py_func_test_item.rst b/doc/manual/sphinx/source/test_items/py_func_test_item.rst index 619fd78..cf6d178 100644 --- a/doc/manual/sphinx/source/test_items/py_func_test_item.rst +++ b/doc/manual/sphinx/source/test_items/py_func_test_item.rst @@ -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_`` +variable named ``pfn_`` (See :ref:`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. \ No newline at end of file diff --git a/doc/manual/sphinx/source/tum_syntax.rst b/doc/manual/sphinx/source/tum_syntax.rst index 046f1a0..335f984 100644 --- a/doc/manual/sphinx/source/tum_syntax.rst +++ b/doc/manual/sphinx/source/tum_syntax.rst @@ -142,7 +142,8 @@ library API (see :ref:`helper library`) * ``ts_end_``: timestamp at the end of test item execution (see :ref:`sec_item_common`), * ``ts_duration_``: duration of test item execution in seconds (see :ref:`sec_item_common`), * ``cn_``: console test item result (see section :ref:`sec_console_test_item`), -* ``fn_``: py_func test item result (see section :ref:`sec_func_item`), +* ``pfn_``: py_func test item result (see section :ref:`sec_py_func_item`), +* ``lfn_``: lua_func test item result (see section :ref:`sec_lua_func_item`), * ``cs_``: 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 diff --git a/doc/manual/testium_manual.pdf b/doc/manual/testium_manual.pdf index 53e6201..06ab50e 100644 Binary files a/doc/manual/testium_manual.pdf and b/doc/manual/testium_manual.pdf differ diff --git a/src/testium/interpreter/test_items/test_item_check.py b/src/testium/interpreter/test_items/test_item_check.py index 30c1e19..3886cbb 100644 --- a/src/testium/interpreter/test_items/test_item_check.py +++ b/src/testium/interpreter/test_items/test_item_check.py @@ -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 diff --git a/src/testium/interpreter/test_items/test_item_lua_func.py b/src/testium/interpreter/test_items/test_item_lua_func.py index 75daa83..8e38ac1 100644 --- a/src/testium/interpreter/test_items/test_item_lua_func.py +++ b/src/testium/interpreter/test_items/test_item_lua_func.py @@ -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: diff --git a/src/testium/interpreter/test_items/test_item_py_func.py b/src/testium/interpreter/test_items/test_item_py_func.py index 12a9101..8a687cc 100644 --- a/src/testium/interpreter/test_items/test_item_py_func.py +++ b/src/testium/interpreter/test_items/test_item_py_func.py @@ -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: diff --git a/src/testium/interpreter/utils/jrpc.py b/src/testium/interpreter/utils/jrpc.py index 3850059..900b441 100644 --- a/src/testium/interpreter/utils/jrpc.py +++ b/src/testium/interpreter/utils/jrpc.py @@ -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: diff --git a/src/testium/interpreter/utils/lua_func_exec.py b/src/testium/interpreter/utils/lua_func_exec.py index 0b773e9..cc8d690 100644 --- a/src/testium/interpreter/utils/lua_func_exec.py +++ b/src/testium/interpreter/utils/lua_func_exec.py @@ -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): diff --git a/src/testium/interpreter/utils/py_func_exec.py b/src/testium/interpreter/utils/py_func_exec.py index 1e2970f..81b70f6 100644 --- a/src/testium/interpreter/utils/py_func_exec.py +++ b/src/testium/interpreter/utils/py_func_exec.py @@ -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): diff --git a/src/testium/lua_func/handle.lua b/src/testium/lua_func/handle.lua index fae3e13..1bd0220 100644 --- a/src/testium/lua_func/handle.lua +++ b/src/testium/lua_func/handle.lua @@ -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 diff --git a/src/testium/lua_func/json-rpc.lua b/src/testium/lua_func/json-rpc.lua index 2d31ab4..1cfead2 100644 --- a/src/testium/lua_func/json-rpc.lua +++ b/src/testium/lua_func/json-rpc.lua @@ -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 @@ -137,4 +127,4 @@ function JSONRPC:loop() end end -return JSONRPC \ No newline at end of file +return JSONRPC diff --git a/src/testium/lua_func/main.lua b/src/testium/lua_func/main.lua index a7b2cb5..b7dd3b9 100644 --- a/src/testium/lua_func/main.lua +++ b/src/testium/lua_func/main.lua @@ -5,7 +5,7 @@ local config = { host = "0.0.0.0", port = 9000, timeout = 60, - verbose = true, + verbose = false, } local function usage() diff --git a/src/testium/py_func/__init__.py b/src/testium/py_func/__init__.py index 62a26aa..b8414d4 100755 --- a/src/testium/py_func/__init__.py +++ b/src/testium/py_func/__init__.py @@ -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) diff --git a/src/testium/py_func/func_call.py b/src/testium/py_func/func_call.py index 20c4f36..e2c1f7c 100644 --- a/src/testium/py_func/func_call.py +++ b/src/testium/py_func/func_call.py @@ -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 diff --git a/src/testium/py_func/tm.py b/src/testium/py_func/tm.py index c41ee1d..4c3010b 100644 --- a/src/testium/py_func/tm.py +++ b/src/testium/py_func/tm.py @@ -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