diff --git a/doc/examples/param.yaml b/doc/examples/param.yaml index 6516bb8..d8d2dc4 100644 --- a/doc/examples/param.yaml +++ b/doc/examples/param.yaml @@ -9,9 +9,21 @@ global_loop_param_num: [1, 2, 3] # Plot parameters plot_log_path: /tmp/testium_plot/$(testrun_date)/$(testrun_time)/ -# python_path: $(home)/tmp/tum_venv/bin/python3 +python_path_Windows: C:\Users\François\Applications\Python313\python.exe +python_path_Linux: $(home)/tmp/tum_venv/bin/python3 + +# lua_path_Windows: C:\Lua\5.1 +# lua_path_Linux: /usr/bin/lua + +LUA_PATH_Linux: /usr/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?/init.lua;/usr/share/lua/5.4/?/init.lua;/usr/local/lib/lua/5.4/?.lua;/usr/local/lib/lua/5.4/?/init.lua;/usr/lib/lua/5.4/?.lua;/usr/lib/lua/5.4/?/init.lua;./?.lua;./?/init.lua;/home/francois/.luarocks/share/lua/5.4/?.lua;/home/francois/.luarocks/share/lua/5.4/?/init.lua +LUA_CPATH_Linux: /usr/local/lib/lua/5.4/?.so;/usr/lib/lua/5.4/?.so;/usr/local/lib/lua/5.4/loadall.so;/usr/lib/lua/5.4/loadall.so;./?.so;/home/francois/.luarocks/lib/lua/5.4/?.so +PATH_Linux: "" + +LUA_PATH_Windows: ;.\?.lua;C:\Lua\5.1\lua\?.lua;C:\Lua\5.1\lua\?\init.lua;C:\Lua\5.1\?.lua;C:\Lua\5.1\?\init.lua;C:\Lua\5.1\lua\?.luac +LUA_CPATH_Windows: .\?.dll;C:\Lua\5.1\?.dll;C:\Lua\5.1\loadall.dll;C:\Lua\5.1\clibs\?.dll;C:\Lua\5.1\clibs\loadall.dll;.\?51.dll;C:\Lua\5.1\?51.dll;C:\Lua\5.1\clibs\?51.dll +PATH_Windows: "" -# lua_path: /usr/bin/lua lua_env: - LUA_PATH: /usr/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?/init.lua;/usr/share/lua/5.4/?/init.lua;/usr/local/lib/lua/5.4/?.lua;/usr/local/lib/lua/5.4/?/init.lua;/usr/lib/lua/5.4/?.lua;/usr/lib/lua/5.4/?/init.lua;./?.lua;./?/init.lua;/home/francois/.luarocks/share/lua/5.4/?.lua;/home/francois/.luarocks/share/lua/5.4/?/init.lua - LUA_CPATH: /usr/local/lib/lua/5.4/?.so;/usr/lib/lua/5.4/?.so;/usr/local/lib/lua/5.4/loadall.so;/usr/lib/lua/5.4/loadall.so;./?.so;/home/francois/.luarocks/lib/lua/5.4/?.so + PATH: $(PATH_$(os)) + LUA_PATH: $(LUA_PATH_$(os)) + LUA_CPATH: $(LUA_CPATH_$(os)) diff --git a/src/testium/interpreter/process.py b/src/testium/interpreter/process.py index 2c84ac2..2762ba5 100644 --- a/src/testium/interpreter/process.py +++ b/src/testium/interpreter/process.py @@ -122,7 +122,9 @@ python_path = {tm.gd("python_path", "no python path defined")}""" raise ETUMRuntimeError( f"""Impossible to start the external lua execution process. Is the lua path correct ? -lua_path = {tm.gd("lua_path", "no lua path defined")}""" + lua_path = {tm.gd("lua_path", "no lua path defined")} +Are "lua-sockets" and "lua-cjson" installed ? +Is the lua environnment well defined in the "LUA_PATH" and "LUA_CPATH" variables ?""" ) test_set.execute() finally: diff --git a/src/testium/interpreter/test_items/test_item_console.py b/src/testium/interpreter/test_items/test_item_console.py index af009e0..984c0bc 100644 --- a/src/testium/interpreter/test_items/test_item_console.py +++ b/src/testium/interpreter/test_items/test_item_console.py @@ -3,7 +3,7 @@ import os import importlib import traceback -from libs import testium as tm +import libs.testium as tm from interpreter.utils.tum_except import ETUMSyntaxError from interpreter.utils.stdout_redirect import stdio_redir from interpreter.test_items.test_item import test_run @@ -58,7 +58,7 @@ class TestItemConsoleOpen(TestItemConsoleAction): telnet_port = self._prms.getParam("telnet_port", default=69) elif self._protocol == "ssh": - if sys.platform.startswith("win"): + if tm.OS() == "Windows": self.result.set( TestValue.FAILURE, "SSH protocol not supported on Windows" ) @@ -112,7 +112,7 @@ class TestItemConsoleOpen(TestItemConsoleAction): ) elif self._protocol == "ssh": - if sys.platform.startswith("win"): + if tm.OS() == "Windows": raise ETUMSyntaxError( f"The '{self.cmd()}' test item named '{self.name()}' does not support SSH protocol on Windows", self.seqFilename() diff --git a/src/testium/interpreter/utils/jrpc.py b/src/testium/interpreter/utils/jrpc.py index 900b441..d1ffa35 100644 --- a/src/testium/interpreter/utils/jrpc.py +++ b/src/testium/interpreter/utils/jrpc.py @@ -380,23 +380,14 @@ class JsonRpcClient(JsonRpcBase): # TCP/IP socket creation try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - + sock.settimeout(self._timeout) # Link of the socket at the configured port - tslice = 0.2 - t = self._timeout - while True: - try: - sock.connect((self._host, self._port)) - except OSError: - t -= tslice - if t >= 0: - sleep(tslice) - continue - else: - raise ETUMRuntimeError(f"{self.name}: failed to connect") - break + try: + sock.connect((self._host, self._port)) + except OSError: + raise ETUMRuntimeError(f"{self.name}: failed to connect") + self.print_info("Connected to server") - self.connect(sock) while self._rpc.running: diff --git a/src/testium/interpreter/utils/lua_func_exec.py b/src/testium/interpreter/utils/lua_func_exec.py index dbe255d..9a190ed 100644 --- a/src/testium/interpreter/utils/lua_func_exec.py +++ b/src/testium/interpreter/utils/lua_func_exec.py @@ -3,6 +3,7 @@ import shutil import subprocess import socket import libs.testium as tm +from interpreter.utils.paths import sys_lua_path from interpreter.utils.tum_except import ETUMRuntimeError from interpreter.utils.jrpc import JsonRpcClient from interpreter.test_items.test_result import TestValue @@ -16,17 +17,6 @@ def lua_func_call_init(lua_path, request_handler): return function_call_process -def lua_version(path: str): - result = subprocess.run( - [path, "-v"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - vers = ((result.stdout.split(" "))[1]).split(".") - return (vers[0], vers[1], vers[2]) - - def is_lua_interpreter(path: str, timeout=2) -> bool: try: result = subprocess.run( @@ -55,7 +45,11 @@ class LuaFuncExecEngine: f"The passed executable is not a lua interpreter: '{lua_path}'" ) else: - lua_path = "/usr/bin/lua" + lua_path = sys_lua_path() + if lua_path == "": + raise ETUMRuntimeError( + f"No valid lua interpreter found" + ) tm.setgd("lua_path", lua_path) self._lpath = lua_path @@ -78,8 +72,9 @@ class LuaFuncExecEngine: func_proc_path = os.path.join(tm.gd("testium_path"),"lua_func") lua_env = tm.gd("lua_env", {}) + tm.print_debug(f"lua_env : {lua_env}") - params = [self._lpath, "main.lua", "--host", "127.0.0.1", "--port", f"{self._port}"] + params = [self._lpath, "main.lua", "--timeout", "10", "--host", "127.0.0.1", "--port", f"{self._port}"] if tm.debug_enabled(): params.append("--verbose") diff --git a/src/testium/interpreter/utils/paths.py b/src/testium/interpreter/utils/paths.py index 565b078..c8285c0 100644 --- a/src/testium/interpreter/utils/paths.py +++ b/src/testium/interpreter/utils/paths.py @@ -1,9 +1,10 @@ import os +import sys import inspect from pathlib import Path import testium from interpreter.utils.params import expanse - +import subprocess import libs.testium as tm @@ -34,3 +35,156 @@ def abs_path_from_file(file): abs_file_path = abs_file_path.resolve() return abs_file_path + +def sys_encoding(): + if tm.OS() == "Windows": + enc = 'oem' + else: + enc = 'utf-8' + return enc + + +def _python_version(path: str): + cmd = f'"{path}" -c "import sys; print(sys.version_info[:3])"' + try: + result = subprocess.run( + cmd, + shell=True, + capture_output=True, + text=True, + encoding=sys_encoding(), + timeout=10 + ) + data = result.stdout + except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired) as e: + tm.print_debug(str(e)) + data = "" + return eval(data) + + +def _lua_version(path: str): + cmd = f'"{path}" -v"' + try: + result = subprocess.run( + cmd, + shell=True, + capture_output=True, + text=True, + encoding=sys_encoding(), + timeout=10 + ) + # Under windows, the output is on stderr + data = result.stdout or result.stderr + except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired) as e: + data = "" + try: + vers = ((data.split(" "))[1]).split(".") + except: + vers = (0, 0, 0) + return (vers[0], vers[1], vers[2]) + + +def is_python3(python_path): + try: + v = _python_version(python_path) + if v[0] == 3: + res = True + except: + res = False + + return res + + +def is_lua51(lua_path): + res = False + v = _lua_version(lua_path) + if (v[0] == "5") and (v[1] >= "1"): + res = True + return res + + +def _sys_app_path_win(app_name): + try: + result = subprocess.run( + f"where {app_name}", + shell=True, + capture_output=True, + text=True, + encoding="oem", + timeout=10 + ) + data = result.stdout + except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired): + data = "" + sys_python_path = data.splitlines() + tm.print_debug("data = ", data) + for l in sys_python_path: + if f"{app_name}.exe" in l: + return l + return "" + + +def _sys_app_path_lin(app_name): + try: + result = subprocess.run( + f"which {app_name}", + shell=True, + capture_output=True, + text=True, + timeout=10 + ) + data = result.stdout + except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired): + data = "" + sys_python_path = data.splitlines() + for l in sys_python_path: + if ( + (f"{app_name}" in l) + and not l.startswith("which:") + ): + return l + return "" + + +def sys_python_path(): + sys_python_path = tm.gd("_sys_python_path", "") + if sys_python_path != "": + return sys_python_path + + cur_os = tm.OS() + if cur_os == "Windows": + func = _sys_app_path_win + else: + func = _sys_app_path_lin + + exe=["python3", "python"] + for e in exe: + sys_python_path = func(e) + if sys_python_path == "": + continue + if not is_python3(sys_python_path): + sys_python_path = "" + continue + + tm.setgd("_sys_python_path", sys_python_path) + return sys_python_path + + +def sys_lua_path(): + sys_lua_path = tm.gd("_sys_lua_path", "") + if sys_lua_path != "": + return sys_lua_path + + cur_os = tm.OS() + if cur_os == "Windows": + func = _sys_app_path_win + else: + func = _sys_app_path_lin + + sys_lua_path = func("lua") + if (sys_lua_path != "") and not is_lua51(sys_lua_path): + tm.print_debug(f"'{sys_lua_path}' not a lua 5.1 min.") + sys_lua_path = "" + + tm.setgd("_sys_lua_path", sys_lua_path) + return sys_lua_path diff --git a/src/testium/interpreter/utils/py_func_exec.py b/src/testium/interpreter/utils/py_func_exec.py index 3b70d28..9e04bf8 100644 --- a/src/testium/interpreter/utils/py_func_exec.py +++ b/src/testium/interpreter/utils/py_func_exec.py @@ -3,6 +3,7 @@ import shutil import subprocess import socket import libs.testium as tm +from interpreter.utils.paths import sys_python_path from interpreter.utils.tum_except import ETUMRuntimeError from interpreter.utils.jrpc import JsonRpcClient from interpreter.test_items.test_result import TestValue @@ -16,16 +17,6 @@ def py_func_call_init(python_path, request_handler): return function_call_process -def python_version(path: str): - result = subprocess.run( - [path, "-c", "import sys; print(sys.version_info[:3])"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - return eval(result.stdout.strip()) - - def is_python_interpreter(path: str, timeout=2) -> bool: try: result = subprocess.run( @@ -56,7 +47,11 @@ class PyFuncExecEngine: ) else: - python_path = sys.executable + python_path = sys_python_path() + if python_path == "": + raise ETUMRuntimeError( + f"No valid python interpreter found" + ) tm.setgd("python_path", python_path) self._ppath = python_path diff --git a/src/testium/libs/console.py b/src/testium/libs/console.py index 025184e..24aa038 100755 --- a/src/testium/libs/console.py +++ b/src/testium/libs/console.py @@ -56,6 +56,7 @@ class Console(object): def __init__(self, name, echoOn=False, write_delay=0): self.stream = sys.stdout self.name = name + self.encoding = "utf-8" self.echo_on = echoOn self.write_delay = write_delay self.string_buffer = '['+str(datetime.now()).split('.')[0].split(' ')[1]+' '+self.name+']' @@ -117,9 +118,9 @@ A {classname}.close() is missing somewhere in your code !'.format(classname=type return True def _compute_char(self, data): - c = data.decode('utf-8', errors='replace') - if not self._is_valid_character(c): - c = '' + c = data.decode(self.encoding, errors='replace') + # if not self._is_valid_character(c): + # c = '' return c def read_until(self, match, timeout=None, return_data=False, mute=False): @@ -234,11 +235,11 @@ A {classname}.close() is missing somewhere in your code !'.format(classname=type print(('[>' + self.name + '] : ' + characters), end=ech) if self.write_delay != 0: for char in characters: - self.port.write(char.encode('utf-8')) + self.port.write(char.encode(self.encoding)) sleep(self.write_delay) return len(characters) else: - return self.port.write(characters.encode('utf-8')) + return self.port.write(characters.encode(self.encoding)) if not sys.platform.startswith('win'): @@ -296,7 +297,7 @@ class TelnetConsole(Console): return self.read_until('\n', return_data=True)[1] def read_nowait(self, mute=False): - st = self.port.read_very_eager().decode('utf-8', errors='replace') + st = self.port.read_very_eager().decode(self.encoding, errors='replace') if not mute: date_str = str(datetime.now()).split('.')[0].split(' ')[1] self.stream.write('[{} {}]'.format(date_str, self.name)+st) @@ -455,13 +456,13 @@ class SerialConsole(Console): if not self._thd.is_alive() and not self.stop.isSet(): raise RuntimeError( "Impossible to read the serial console, it may be already openned") - st = self.rx_queue.getAll().decode('utf-8', errors='replace') + st = self.rx_queue.getAll().decode(self.encoding, errors='replace') if not mute: date_str = str(datetime.now()).split('.')[0].split(' ')[1] self.stream.write('[{} {}]'.format(date_str, self.name)+st) return st - st = self.port.read(self.port.inWaiting()).decode('utf-8', errors='replace') + st = self.port.read(self.port.inWaiting()).decode(self.encoding, errors='replace') if not mute: date_str = str(datetime.now()).split('.')[0].split(' ')[1] self.stream.write('[{} {}]'.format(date_str, self.name)+st) @@ -540,7 +541,7 @@ class LoggedConsole(Console): self.rx_queue.put(data) else: continue - data = data.decode('utf-8', errors='replace') + data = data.decode(self.encoding, errors='replace') # if valid char, write into the file if self._is_valid_character(data): # replace '\r' by '\n' and '\r\n' by '\n' @@ -583,7 +584,7 @@ class LoggedConsole(Console): raise ConnectionAbortedError chars = '' for _ in range(self.rx_queue.qsize()): - chars = chars + self.rx_queue.get().decode('utf-8', errors='replace') + chars = chars + self.rx_queue.get().decode(self.encoding, errors='replace') if not mute: date_str = str(datetime.now()).split('.')[0].split(' ')[1] diff --git a/src/testium/libs/raw_tcp_console.py b/src/testium/libs/raw_tcp_console.py index 42dfc16..d4b96c5 100644 --- a/src/testium/libs/raw_tcp_console.py +++ b/src/testium/libs/raw_tcp_console.py @@ -59,7 +59,7 @@ class RawTCPConsole(Console): self.sock.settimeout(0) self.stimeout = 0 s = self.sock.recv(4096) - st = s.decode('utf-8', errors='replace') + st = s.decode(self.encoding, errors='replace') if not mute: date_str = str(datetime.now()).split('.')[0].split(' ')[1] self.stream.write('[{} {}]'.format(date_str, self.name)+st) @@ -69,5 +69,5 @@ class RawTCPConsole(Console): if self.echo_on and not mute: ech = '' if s.strip(' ').endswith('\n') else '\n' print(('[>' + self.name + '] : ' + s), end=ech) - res = self.sock.sendall(s.encode('utf-8')) + res = self.sock.sendall(s.encode(self.encoding)) return res diff --git a/src/testium/libs/termconsole.py b/src/testium/libs/termconsole.py index 7244d43..ea6f753 100644 --- a/src/testium/libs/termconsole.py +++ b/src/testium/libs/termconsole.py @@ -1,5 +1,6 @@ from datetime import datetime import sys +import locale if sys.platform.startswith('win'): import subprocess else: @@ -16,6 +17,9 @@ class TermConsole(Console): def __init__(self, name, project_path=None, cust_shell=None, echoOn=False, write_delay=0): Console.__init__(self, name, echoOn, write_delay) + if sys.platform.startswith('win'): + self.encoding = 'cp850' + if not project_path: self.ppath = os.getcwd() else: @@ -111,7 +115,7 @@ class TermConsole(Console): s += self.q.getAll() - st = s.decode('utf-8', errors='replace') + st = s.decode(self.encoding, errors='replace') ls = st.splitlines() if (len(st) > 0) and (st[-1] != '\r') and (st[-1] !='\n'): @@ -129,7 +133,7 @@ class TermConsole(Console): ech = '' if s.strip(' ').endswith('\n') else '\n' print(('[>' + self.name + '] : ' + s), end=ech) if sys.platform.startswith('win'): - res = self.term.stdin.write(s.encode('utf-8')) + res = self.term.stdin.write(s.encode(self.encoding)) else: res = self.term.send(s) diff --git a/src/testium/lua_func/main.lua b/src/testium/lua_func/main.lua index b7dd3b9..5c51356 100644 --- a/src/testium/lua_func/main.lua +++ b/src/testium/lua_func/main.lua @@ -64,8 +64,17 @@ local handle = require("handle") utils.verbose = config.verbose -- Create the master socket -local server_sock = assert(socket.bind(config.host, config.port)) -utils.log("listening on %s:%d", config.host, config.port) +local server_sock = socket.tcp() + +local ok, err = server_sock:bind(config.host, config.port) +if not ok then + utils.log("error : %s", err) + os.exit(1) +end + +server_sock:listen(1) +local ip, port = server_sock:getsockname() +utils.log("listening on %s:%d", ip, port) server_sock:settimeout(config.timeout) -- Prevents hanging on dead connections diff --git a/src/testium/main_win/test_tree_items/test_tree_item.py b/src/testium/main_win/test_tree_items/test_tree_item.py index b9eb355..34e6748 100644 --- a/src/testium/main_win/test_tree_items/test_tree_item.py +++ b/src/testium/main_win/test_tree_items/test_tree_item.py @@ -56,7 +56,7 @@ class QTestTreeItem(QTreeWidgetItem): self._display_pause = False self.icon_pause = QIcon() self.icon_fake = QIcon() - self.icon_pause.addPixmap(QPixmap(icon_prefix() + "/stop.png")) + self.icon_pause.addPixmap(QPixmap(icon_prefix() + "/break.png")) self.nfailure = 0 self._timestamp = -1 self._is_skipped = False