processes management refundation.

Evaluation removed from conditions, let and exit_conditional
This commit is contained in:
2026-01-25 15:03:52 +01:00
parent 929e75215b
commit 60636b4fd2
45 changed files with 863 additions and 752 deletions

View File

@@ -1,6 +1,6 @@
import random import random
import libs.testium as tm import py_func.tm as tm
from libs.testium import FunctionItem from py_func.tm import FunctionItem
def random_value(): def random_value():
return random.random() return random.random()

View File

@@ -10,12 +10,12 @@ main:
- let: - let:
name: Set test variables for Linux name: Set test variables for Linux
condition: "'$(os)' == 'Linux'" condition: $| "$(os)" == "Linux" |
values: values:
- terminal_prompt: $(linux_prompt) - terminal_prompt: $(linux_prompt)
- let: - let:
name: Set test variables for Windows name: Set test variables for Windows
condition: "'$(os)' == 'Windows'" condition: $| "$(os)" == "Windows" |
values: values:
- terminal_prompt: $(windows_prompt) - terminal_prompt: $(windows_prompt)
@@ -35,17 +35,17 @@ main:
no_fail: True no_fail: True
exit_condition: exit_condition:
value: "'$(last_test_result)' == 'PASS'" value: $| "$(last_test_result)" == "PASS" |
- let: - let:
name: let name: let
eval: values:
- conditional_exec: "random.randint(1, 4)" - conditional_exec: $| random.randint(1, 4) |
- console: - console:
name: Console creation name: Console creation
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
doc: Opening the console doc: Opening the console
steps: steps:
@@ -55,56 +55,56 @@ main:
- console: - console:
name: Console read_until with timeout name: Console read_until with timeout
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
steps: steps:
- read_until: {expected: "$(terminal_prompt)", timeout: 10} - read_until: {expected: "$(terminal_prompt)", timeout: 10}
- console: - console:
name: Console write name: Console write
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
steps: steps:
- writeln: echo 0 - writeln: echo 0
- sleep: - sleep:
name: sleep item name: sleep item
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
timeout: 5 timeout: 5
- console: - console:
name: Console read_until immediate name: Console read_until immediate
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
steps: steps:
- read_until: {expected: "0", timeout: 0} - read_until: {expected: "0", timeout: 0}
- console: - console:
name: Console read_until immediate (2) name: Console read_until immediate (2)
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
steps: steps:
- read_until: {expected: "$(terminal_prompt)", timeout: 0} - read_until: {expected: "$(terminal_prompt)", timeout: 0}
- console: - console:
name: Console closure name: Console closure
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
steps: steps:
- close: consname - close: consname
- sleep: - sleep:
name: sleep item name: sleep item
condition: "$(conditional_exec) == 2" condition: $| $(conditional_exec) == 2 |
timeout: 5 timeout: 5
- dialog_image: - dialog_image:
name: dialog image item name: dialog image item
condition: "$(conditional_exec) == 3" condition: $| $(conditional_exec) == 3 |
question: click ok if you see the image question: click ok if you see the image
filename: image.jpg filename: image.jpg
- dialog_value: - dialog_value:
name: dialog_value item name: dialog_value item
condition: "$(conditional_exec) == 4" condition: $| $(conditional_exec) == 4 |
question: enter something and click ok question: enter something and click ok

View File

@@ -139,7 +139,7 @@ main:
timeout: 0.2 timeout: 0.2
dialog: false dialog: false
exit_condition: exit_condition:
value: "$(variable) >= 10" value: $| $(variable) >= 10 |
# This loop must fail du to an exception in exit condition. # This loop must fail du to an exception in exit condition.
- loop: - loop:

View File

@@ -16,7 +16,7 @@ main:
- group: - group:
name: Set test variables for Linux name: Set test variables for Linux
condition: "'$(os)' == 'Linux'" condition: $| "$(os)" == "Linux" |
steps: steps:
- let: - let:
@@ -26,7 +26,7 @@ main:
- group: - group:
name: Set test variables for Windows name: Set test variables for Windows
condition: "'$(os)' == 'Windows'" condition: $| "$(os)" == "Windows" |
steps: steps:
- let: - let:

View File

@@ -22,6 +22,6 @@ main:
- *seq_sleep - *seq_sleep
exit_condition: exit_condition:
value: "$(variable) >= 3" value: $| $(variable) >= 3 |
- !include {file: seq2.tum, is_dialog: True, sleep_timeout: 12, func_para: truc} - !include {file: seq2.tum, is_dialog: True, sleep_timeout: 12, func_para: truc}

View File

@@ -23,5 +23,5 @@ main:
- sleep: - sleep:
name: sleep item name: sleep item
dialog: true dialog: true
timeout: 3600 timeout: $| 3600 + random.randint(1, 10) |
no_fail: true no_fail: true

View File

@@ -20,10 +20,10 @@ main:
- let: - let:
name: Extract data name: Extract data
eval: values:
- text_extract: "[l for l in '''$(rand_text)'''.splitlines() if '$(text_searched)' in l][0]" - text_extract: $| [l for l in '''$(rand_text)'''.splitlines() if '$(text_searched)' in l][0] |
- dialog_message: - dialog_message:
condition: len('$(text_extract)') > 0 condition: $| len('$(text_extract)') > 0 |
name: dialog value test item name: dialog value test item
question: Tataaaaa ! question: Tataaaaa !

View File

@@ -8,10 +8,10 @@ This element is of the following form:
- group: - group:
name: Group Item name: Group Item
condition: "'$(OS)' == 'Linux'" condition: $| "$(OS)" == "Linux" |
steps: steps:
- unittest_file: - unittest_file:
test_file: test_prod_rio6_8093.py test_file: test_prod_alpha_13.py
test_method: test_method:
... ...
- sleep: - sleep:
@@ -23,4 +23,4 @@ Attributes
-------------------- --------------------
* The ``steps`` list describes the sequence executed in the group. * The ``steps`` list describes the sequence executed in the group.
It is a list of any of the testium test items, It is a list of any of the `testium` test items,

View File

@@ -72,9 +72,9 @@ if not provided is given in the table as well.
|``report`` | / | This attribute defines values (a dictionary) which | |``report`` | / | This attribute defines values (a dictionary) which |
| | | will be added in the ``data`` field of the report. | | | | will be added in the ``data`` field of the report. |
+-----------------------+-------------------+-------------------------------------------------------+ +-----------------------+-------------------+-------------------------------------------------------+
| ``condition`` | / | The test item is not executed if its | | ``condition`` | / | The test item is executed if its |
| | | ``condition`` attribute content is | | | | ``condition`` attribute content is a boolean |
| | | evaluated as ``False``. | | | | ``True``. |
| | | see :ref:`Conditional | | | | see :ref:`Conditional |
| | | execution<sec_conditional_execution>`. | | | | execution<sec_conditional_execution>`. |
+-----------------------+-------------------+-------------------------------------------------------+ +-----------------------+-------------------+-------------------------------------------------------+
@@ -133,7 +133,8 @@ or in configuration file (see :ref:`config files<sec_configuration_files>`) as a
Conditional execution Conditional execution
----------------------------------------------- -----------------------------------------------
The ``condition`` attribute content is evaluated as a python string. The ``condition`` attribute content must be a boolean value (if not ``True``, the condition is considered
not met).
.. _sec_process_result: .. _sec_process_result:

View File

@@ -11,15 +11,12 @@ This element is of the following form:
values: values:
key1: value1 key1: value1
key2: value2 key2: value2
eval: key3: $| $(variable)[$(loop_index)] |
key3: $(variable)[$(loop_index)]
The ``let`` element is used to set values in the global directory. The ``let`` element is used to set values in the global directory.
Attributes Attributes
---------------- ----------------
* The values list gives the {<key>, <value>} couples to set in the * The ``values`` list gives the {<key>, <value>} couples to set in the
global directory, global directory,
* The eval list gives the strings to evaluate prior to its storage into
the <key> of global directory.

View File

@@ -39,12 +39,12 @@ Below are described loop test item specific attributes.
* ``Iterator``: giving the number of loop iteration (see dedicated chapter below). * ``Iterator``: giving the number of loop iteration (see dedicated chapter below).
* ``steps``: describes the sequence executed at each cycle; it is * ``steps``: describes the sequence executed at each cycle; it is
a list of any of the testium test items. a list of any of the testium test items.
* ``exit_condition``: allows to exit the loop. If False is returned, loop continues * ``exit_condition``: allows to exit the loop. If True is returned loop continues
else, it breaks. exit_condition attributes are: otherwise it breaks. exit_condition attributes are:
* ``time``: the loop stops after the time (in minutes) is elapsed (optional) * ``time``: the loop stops after the time (in minutes) is elapsed (optional)
* ``value``: the loop stops when the content of the value attribute is * ``value``: the loop stops when the content of the value attribute is
evaluated as True (optional) True (optional)
* ``file``: the loop the script file name that contains a function to be * ``file``: the loop the script file name that contains a function to be
executed on each loop. Only python script format is supported (optional executed on each loop. Only python script format is supported (optional
if another exit_condition attribute is defined) if another exit_condition attribute is defined)

View File

@@ -110,13 +110,13 @@ value of the funcToBeExecuted python function.
**Python Interpreter environment setup** **Python Interpreter environment setup**
Some global variables have an impact on the ``py_func`` test item behavior: Some global variables have an impact on the ``py_func`` test item behavior:
* ``python_path``: This optional global variable can be used to define * ``python_bin``: This optional global variable can be used to define
the python executable path. If not defined, the python interpreter is the python executable path. If not defined, the python interpreter is
searched in at the default places in the system. searched in at the default places in the system.
* ``python_env``: This global variable can be used to define * ``python_env``: This global variable can be used to define
environment variables for the lua script execution environment. environment variables for the lua script execution environment.
Only `PATH`, `LUA_PATH`, and `LUA_CPATH` are supported. Only `PATH` and `PYTHONPATH` are supported.
.. code-block:: yaml .. code-block:: yaml
:caption: example of configuration file: param.yaml :caption: example of configuration file: param.yaml

View File

@@ -9,7 +9,7 @@ This test item executes a new instance of testium.
- run: - run:
name: Execute TUM name: Execute TUM
tum_fime: example_cycle.tum tum_fime: example_cycle.tum
python_path: python3 python_bin: python3
testium_path: /home/francois/projets/testium-new-report/testium.pyw testium_path: /home/francois/projets/testium-new-report/testium.pyw
log_file: $(home)/reports/test.log log_file: $(home)/reports/test.log
report_file: $(home)/reports/test.rep report_file: $(home)/reports/test.rep
@@ -21,7 +21,7 @@ run test item has the following specific attributes:
* ``tum_fime``: mandatory the path of the file to execute, it can be relative to current execution folder, * ``tum_fime``: mandatory the path of the file to execute, it can be relative to current execution folder,
* ``param_file`` (optional) the path of the parameter file to use, otherwise default parameter file is used. * ``param_file`` (optional) the path of the parameter file to use, otherwise default parameter file is used.
* ``python_path`` (optional) the path of a specific python to run your scripts, * ``python_bin`` (optional) the path of a specific python to run your scripts,
* ``testium_path`` (optional) the path of a specific testium to run your scripts, * ``testium_path`` (optional) the path of a specific testium to run your scripts,
* ``log_file`` (optional) the path of log file to register, if not provided a file is created with timestamp at the location of TUM file. * ``log_file`` (optional) the path of log file to register, if not provided a file is created with timestamp at the location of TUM file.
* ``report_file`` (optional), the path of report file to create * ``report_file`` (optional), the path of report file to create

View File

@@ -7,7 +7,6 @@ from pathlib import Path
ourpath = Path(__file__) ourpath = Path(__file__)
ourpath = ourpath.resolve() ourpath = ourpath.resolve()
sys.path.append(os.path.abspath(ourpath.parent)) sys.path.append(os.path.abspath(ourpath.parent))
from interpreter.utils.eval import evaluate
import interpreter.utils.constants as cst import interpreter.utils.constants as cst
@@ -71,8 +70,7 @@ def main():
d = define.split('=', 1) d = define.split('=', 1)
if d[0].strip() != '': if d[0].strip() != '':
if len(d) > 1: if len(d) > 1:
_, edef = evaluate(d[1]) defines.update({d[0].strip(): d[1]})
defines.update({d[0].strip(): edef})
else: else:
defines.update({d[0].strip(): True}) defines.update({d[0].strip(): True})

View File

@@ -4,16 +4,18 @@ from multiprocessing import Process, Queue, Pipe
from queue import Empty from queue import Empty
from threading import Thread from threading import Thread
from time import sleep from time import sleep
import traceback import copy
import libs.testium as tm import libs.testium as tm
from interpreter.utils.params import expanse from interpreter.utils.params import expanse
from interpreter.utils.string_queue import StringQueue from interpreter.utils.string_queue import StringQueue
from interpreter.utils.tum_except import ETUMRuntimeError from interpreter.utils.tum_except import ETUMRuntimeError, ETUMSyntaxError
from interpreter.utils.test_ctrl import TestSetController from interpreter.utils.test_ctrl import TestSetController
from interpreter.utils.test_init import ( from interpreter.utils.test_init import (
env_init, env_init,
load_test, prepare_global,
update_global,
set_standard_gd_keys,
test_run_init, test_run_init,
test_run_header, test_run_header,
locate_report_file, locate_report_file,
@@ -22,10 +24,12 @@ from interpreter.utils.test_init import (
) )
from interpreter.utils.constants import TestItemType as cst_type from interpreter.utils.constants import TestItemType as cst_type
from interpreter.test_set import TestSet from interpreter.test_set import TestSet
from interpreter.utils.include import TUMLoader, TUMLoaderNoIncludes, TUMLoaderRawIncludes
from interpreter.utils.stdout_redirect import stdio_redir from interpreter.utils.stdout_redirect import stdio_redir
from interpreter.utils.template import template_to_test
from interpreter.utils.yaml_load import yaml_load
from interpreter.utils.tum_except import print_exception from interpreter.utils.tum_except import print_exception
from interpreter.utils.py_func_exec import py_func_call_init from interpreter.utils.py_eval import eval_process_init
from interpreter.utils.lua_func_exec import lua_func_call_init
from interpreter.utils.api_srv import api_request from interpreter.utils.api_srv import api_request
@@ -51,6 +55,145 @@ class TestProcess(Process):
self.__closed = False self.__closed = False
self.__pconn = self.redirect_stdout() self.__pconn = self.redirect_stdout()
def _check_test_dict(self, test_dict):
if not isinstance(test_dict, dict):
raise ETUMSyntaxError(
"The tum file has a major problem. Please check the documentation for syntax.")
if not 'main' in test_dict.keys():
raise ETUMSyntaxError(
"The tum file has a major problem. The 'main' section could not be found.")
def _locate_config_files(self, test_dir, config_files, silent=False):
ret = []
pf = []
if len(config_files) == 0:
for p in ['param.yaml', 'param.yml']:
param_filename = os.path.join(test_dir, p)
if os.path.exists(param_filename):
pf.append(param_filename)
if not silent:
tm.print_info(f"Configuration file loaded: {p}.")
else:
if not silent:
tm.print_info(f"Default param file \"{p}\" does not exist.")
else:
pf = config_files
for p in pf:
ret.append(p)
return ret
def _config_files_from_test(self, test_dict, config_files=None):
test_dir = tm.gd('test_directory')
pf = []
if isinstance(config_files, list) and len(config_files) == 0:
param_filename = test_dict.get('config_file', None)
if param_filename is None:
param_node = test_dict.get('param_file', None)
if param_node is not None:
if isinstance(param_node, dict):
p = param_node.get('file_name', None)
if p is not None:
param_filename = p
else:
param_filename = param_node
else:
param_filename = param_node
if param_filename is None:
pf = self._locate_config_files(test_dir, [])
elif isinstance(param_filename, str):
pf.append(param_filename)
elif isinstance(param_filename, (list)):
pf = []
for p in param_filename:
if isinstance(p, list):
for pp in p:
pf.append(pp)
elif p is not None:
pf.append(p)
else:
raise ETUMSyntaxError(
'Unrecognized tum "param_file" : {}'.format(param_filename))
elif isinstance(config_files, list):
pf = config_files
elif isinstance(config_files, str):
pf = [config_files]
else:
raise ETUMSyntaxError(
'Unrecognized config_files parameter : {}'.format(config_files))
return pf
def _load_test_dict(self, test_file, variables: dict, no_include: bool = False, raw_include: bool = False):
loader = TUMLoader
loader = TUMLoaderRawIncludes if raw_include else loader
loader = TUMLoaderNoIncludes if no_include else loader
# Jinja template processing
tmpf = template_to_test(test_file, variables)
try:
d = yaml_load(tmpf, test_file, loader)
finally:
tmpf.close()
return d
def _load_initial_params(self, test_dir):
# First step: populate config files without includes considered
test_dict = self._load_test_dict(self.__fname, {}, no_include=True)
self._check_test_dict(test_dict)
prepare_global()
# Define the global builtin variables
set_standard_gd_keys(test_dict["main"].get(
"name", "Unnamed"), test_dir, self.__fname, self.__cfgf)
# Include the content of the first config files into glob dict
old_pfs = self._config_files_from_test(test_dict, self.__cfgf)
# Variables updated
gd = update_global(old_pfs, self.__defs, self.__gui_defaults, silent=True)
return old_pfs, gd
def _load_test(self, init_param_files, glob_variables):
old_pfs = init_param_files
gd = glob_variables
while True:
# Loop to check param files until all param files are identified
test_dict = self._load_test_dict(self.__fname, gd, raw_include=True)
new_pfs = self._config_files_from_test(test_dict, self.__cfgf)
# Check if things have changed since previous evaluation of
# config files
new_stuff = False
if len(old_pfs) != len(new_pfs):
new_stuff = True
if not new_stuff:
for i in range(len(old_pfs)):
if old_pfs[i] != new_pfs[i]:
new_stuff = True
break
# If the param files are identical, we continue in loading process
if not new_stuff:
break
# Variables updated
gd = update_global(new_pfs, self.__defs, self.__gui_defaults, silent=False)
old_pfs = copy.copy(new_pfs)
# Processing (with includes) for complete file loading
test_dict = self._load_test_dict(self.__fname, gd)
return test_dict, new_pfs
def run(self): def run(self):
try: try:
try: try:
@@ -64,97 +207,75 @@ class TestProcess(Process):
env_init() env_init()
# Load the test file # Creation of the python evaluation process for loading of the complete test
test_dict, cfg_files = load_test( eval_proc = eval_process_init("", api_request, 10, test_dir)
self.__fname, eval_proc.start()
test_dir, if not eval_proc.wait_ready(10):
self.__cfgf, raise ETUMRuntimeError(
self.__defs, f"""Impossible to start the external python execution process.
self.__gui_defaults, Is the python exec path correct ?"""
) )
# Backup the global dict in case of restart of the test try:
gdict = backup_gd()
# The path of the test file is included in PYTHONPATH # Loading of the param files without inclusions (first level)
sys.path.append(os.path.dirname(self.__fname)) init_param_files, glob_variables = self._load_initial_params(test_dir)
# Now create the test structure and objects # Load the test file
test_set = TestSet(self.__fname, test_dict, self.__squeue) test_dict, param_files = self._load_test(init_param_files, glob_variables)
# Thread for incoming control commands # Backup the global dict in case of restart of the test
self.init_commands(test_set) gdict = backup_gd()
self.cmd_th = Thread(
target=self.process_control_commands,
args=[self.__tctrl],
daemon=True,
)
self.cmd_th.start()
test_set.report_path = locate_report_file(test_set.report_path) # Now create the test structure and objects
test_set = TestSet(self.__fname, test_dict, self.__squeue)
# Python & lua functions call subprocess initialization # Thread for incoming control commands
py_fproc = py_func_call_init(tm.gd("python_path", ""), api_request, 10) self.init_commands(test_set)
self.cmd_th = Thread(
target=self.process_control_commands,
args=[self.__tctrl],
daemon=True,
)
self.cmd_th.start()
# Lua functions call subprocess initialization # Set the report path
lua_fproc = None test_set.report_path = locate_report_file(test_set.report_path)
if test_set.isTestTypePresent(cst_type.TYPE_LUA_FUNCTION): self.__loaded = True
lua_fproc = lua_func_call_init(tm.gd("lua_path", ""), api_request, 10)
self.__loaded = True while True:
# waiting for a control command
while True: while (not self.__exec) and (not self.__closed):
# waiting for a control command sleep(0.2)
while (not self.__exec) and (not self.__closed): # if close is required
sleep(0.2) if self.__closed:
# if close is required break
if self.__closed: # Test is started
break
# Test is started
try:
try: try:
try: try:
test_run_init() try:
print(test_run_header()) test_run_init()
# start the process for executing external python print(test_run_header())
py_fproc.start() test_set.execute()
if not py_fproc.wait_ready(10): finally:
raise ETUMRuntimeError( if test_set.success():
f"""Impossible to start the external python execution process. print("Test run success.")
Is the python path correct ? else:
python_path = {tm.gd("python_path", "no python path defined")}""" print("Test run failed.")
)
if lua_fproc is not None:
lua_fproc.start()
if not lua_fproc.wait_ready(10):
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")}
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:
if test_set.success():
print("Test run success.")
else:
print("Test run failed.")
test_set.run_post_exec() test_set.run_post_exec()
finally: finally:
# Stop function execution process self.__exec = False
py_fproc.stop() # Sends signal to the GUI
py_fproc.join() self.send_finished()
if lua_fproc is not None: restore_gd(gdict)
lua_fproc.stop() except Exception as e:
lua_fproc.join() print_exception(e)
self.__exec = False
# Sends signal to the GUI finally:
self.send_finished() # Stop python eval execution process
restore_gd(gdict) eval_proc.stop()
except Exception as e: eval_proc.join()
print_exception(e)
except Exception as e: except Exception as e:
print_exception(e) print_exception(e)
@@ -238,7 +359,7 @@ Is the lua environnment well defined in the "LUA_PATH" and "LUA_CPATH" variables
continue continue
def redirect_stdout(self): def redirect_stdout(self):
pipe = pconn, cconn = Pipe() pconn, cconn = Pipe()
redir = Thread(target=self.capture_stdout, args=(cconn,)) redir = Thread(target=self.capture_stdout, args=(cconn,))
redir.daemon = True redir.daemon = True
redir.start() redir.start()

View File

@@ -33,21 +33,13 @@ def test_run(f):
c = self._prms.expanse(raw_condition) c = self._prms.expanse(raw_condition)
if isinstance(c, bool): if isinstance(c, bool):
condition = c condition = c
elif isinstance(c, (str, bytes)):
is_evaluated, condition = evaluate(c)
if not is_evaluated:
print("eval with c: {}".format(c))
raise ETUMSyntaxError(
f"The '{self.cmd()}' test item named '{self.name()}' has a 'condition' impossible to evaluate",
self.seqFilename(),
)
else: else:
raise ETUMSyntaxError( c = False
f"The '{self.cmd()}' test item named '{self.name()}' has a 'condition' result ({c}) which is not string or bool",
self.seqFilename(),
)
msg = '"{}" --> "{}"'.format(raw_condition, c) if raw_condition == c:
msg = f'"{c}"'
else:
msg = f'"{raw_condition}" --> "{c}"'
# Do we have to skip the test because of a true condition ? # Do we have to skip the test because of a true condition ?
if condition: if condition:

View File

@@ -1,13 +1,13 @@
import traceback import traceback
from interpreter.utils.tum_except import ETUMSyntaxError, ETUMRuntimeError from interpreter.utils.tum_except import ETUMSyntaxError, ETUMRuntimeError
from interpreter.utils.py_func_exec import py_func_exec from interpreter.utils.py_func_exec import PyFuncExecEngine
from interpreter.utils.api_srv import api_request
from interpreter.test_items.test_item import TestItem, test_run from interpreter.test_items.test_item import TestItem, test_run
from interpreter.test_items.test_result import TestResult, TestValue from interpreter.test_items.test_result import TestResult, TestValue
import libs.testium as tm import libs.testium as tm
from interpreter.utils.params import TestItemParams from interpreter.utils.params import TestItemParams
from interpreter.utils.constants import TestItemType as cst from interpreter.utils.constants import TestItemType as cst
from interpreter.utils.eval import evaluate
class TestItemCycle(TestItem): class TestItemCycle(TestItem):
@@ -97,10 +97,9 @@ class TestItemCycle(TestItem):
iter = self._prms.expanse(iter) iter = self._prms.expanse(iter)
if not isinstance(iter, (list, tuple, int)): if not isinstance(iter, (list, tuple, int)):
_, iter = evaluate(iter) self.result.set(TestValue.FAILURE, f"unrecognized type for iterator '{str(iter)}'")
if not isinstance(iter, (list, tuple, int)): return
self.result.set(TestValue.FAILURE, f"unrecognized type for iterator '{str(iter)}'")
return
if not isinstance(iter, int): if not isinstance(iter, int):
r = [] r = []
for i in iter: for i in iter:
@@ -174,8 +173,13 @@ class TestItemCycle(TestItem):
exit_val = self._prms.expanse( exit_val = self._prms.expanse(
self._exit_condition self._exit_condition
) )
_, exit_val = evaluate(exit_val) ev = False
if exit_val: if isinstance(exit_val, bool):
ev = exit_val
else:
tm.print_warn(f"""Loop 'exit_condition' is not a boolean value ({exit_val}),
then considered as 'False'""")
if ev:
# exit condition is True # exit condition is True
self.result.reported = { self.result.reported = {
"exit": "condition", "exit": "condition",
@@ -190,9 +194,7 @@ class TestItemCycle(TestItem):
break break
else: else:
print( print(
'Continuing. Condition "{}" not met.'.format( f"Continuing. Condition '{self._exit_condition}' not a 'True' boolean."
self._exit_condition
)
) )
if self._exit_func: if self._exit_func:
@@ -204,7 +206,21 @@ class TestItemCycle(TestItem):
pl = self._prms.expanse(param_list) pl = self._prms.expanse(param_list)
else: else:
pl = [self._currentLoop] pl = [self._currentLoop]
fsucc, res = py_func_exec(file, func, pl)
proc = PyFuncExecEngine(tm.gd("python_bin", ""), api_request, 10)
proc.start()
if not proc.wait_ready(10):
raise ETUMRuntimeError(
f"""Impossible to start the external python execution process.
Is the python path correct ?
python_bin = {tm.gd("python_bin", "no python path defined")}"""
)
try:
fsucc, res = proc.func_call(file, func, pl)
finally:
proc.stop()
proc.join()
if fsucc == TestValue.SUCCESS: if fsucc == TestValue.SUCCESS:
fres, _ = res fres, _ = res
if fres: if fres:

View File

@@ -8,7 +8,6 @@ from interpreter.test_items.test_result import (TestResult, TestValue)
from interpreter.utils.tum_except import ETUMSyntaxError from interpreter.utils.tum_except import ETUMSyntaxError
import libs.testium as tm import libs.testium as tm
from interpreter.utils.constants import TestItemType as cst from interpreter.utils.constants import TestItemType as cst
from interpreter.utils.eval import evaluate
class TestItemLet(TestItem): class TestItemLet(TestItem):
"""let item usage. """let item usage.
@@ -22,10 +21,9 @@ class TestItemLet(TestItem):
self.is_container = False self.is_container = False
try: try:
self._values_list = self._prms.getParamAll('values', default=[], required=False) self._values_list = self._prms.getParamAll('values', default=[], required=False)
self._eval_list = self._prms.getParamAll('eval', default=[], required=False) if len(self._values_list) <= 0:
if (len(self._values_list) <= 0) and (len(self._eval_list) <= 0):
raise ETUMSyntaxError( raise ETUMSyntaxError(
f"The '{self.cmd()}' test item named '{self.name()}' must have a 'values' or 'eval' parameter", f"The '{self.cmd()}' test item named '{self.name()}' must have a 'values' parameter",
self.seqFilename(), self.seqFilename(),
) )
except: except:
@@ -41,11 +39,6 @@ class TestItemLet(TestItem):
for k in self._values_list.keys(): for k in self._values_list.keys():
l.append({k: self._values_list[k]}) l.append({k: self._values_list[k]})
self._values_list = l self._values_list = l
if isinstance(self._eval_list, dict):
l = []
for k in self._eval_list.keys():
l.append({k: self._eval_list[k]})
self._eval_list = l
#test core function #test core function
for i in self._values_list: for i in self._values_list:
for k, v in i.items(): for k, v in i.items():
@@ -55,16 +48,4 @@ class TestItemLet(TestItem):
self.result.reported = {key: ev} self.result.reported = {key: ev}
print('global value "{}" set to "{}"'.format(key, ev)) print('global value "{}" set to "{}"'.format(key, ev))
for i in self._eval_list:
for k, v in i.items():
key = self._prms.expanse(k)
val = self._prms.expanse(v)
is_evaluated, ev = evaluate(val)
if not is_evaluated:
self.result.set(TestValue.FAILURE, "Error evaluating: '{}'".format(val))
return
tm.setgd(key, ev)
self.result.reported = {key: ev}
print('global value "{}" set to "{}"'.format(key, ev))
self.result.set(TestValue.SUCCESS, 'Variable set') self.result.set(TestValue.SUCCESS, 'Variable set')

View File

@@ -7,8 +7,9 @@ import textwrap
from interpreter.test_items.test_item import TestItem, test_run from interpreter.test_items.test_item import TestItem, test_run
from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_result import TestValue
import libs.testium as tm import libs.testium as tm
from interpreter.utils.lua_func_exec import lua_func_exec from interpreter.utils.lua_func_exec import lua_func_call_init, lua_func_exec
from interpreter.utils.tum_except import ETUMSyntaxError from interpreter.utils.api_srv import api_request
from interpreter.utils.tum_except import ETUMSyntaxError, ETUMRuntimeError
from interpreter.utils.constants import TestItemType as cst from interpreter.utils.constants import TestItemType as cst
@@ -31,6 +32,9 @@ class TestItemLuaFunc(TestItem):
f"The '{self.cmd()}' test item named '{self.name()}' (child of '{self.parent.name()}') has a missing or wrong parameter", f"The '{self.cmd()}' test item named '{self.name()}' (child of '{self.parent.name()}') has a missing or wrong parameter",
self.seqFilename(), self.seqFilename(),
) )
# Lua functions call subprocess initialization
self._proc = lua_func_call_init(tm.gd("lua_path", ""), api_request, 10)
@test_run @test_run
def execute(self): def execute(self):
@@ -45,7 +49,24 @@ class TestItemLuaFunc(TestItem):
if tm.debug_enabled(): if tm.debug_enabled():
tm.print_debug("Parameters list:") tm.print_debug("Parameters list:")
tm.print_debug(textwrap.indent(pprint.pformat(pl), " |")) tm.print_debug(textwrap.indent(pprint.pformat(pl), " |"))
success, ret = lua_func_exec(self.file_name, self.func_name, pl)
if self._proc is not None:
self._proc.start()
if not self._proc.wait_ready(10):
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")}
Are "lua-sockets" and "lua-cjson" installed ?
Is the lua environnment well defined in the "LUA_PATH" and "LUA_CPATH" variables ?"""
)
try:
success, ret = lua_func_exec(self.file_name, self.func_name, pl)
finally:
# Stops lua function execution process
self._proc.stop()
self._proc.join()
if success == TestValue.SUCCESS: if success == TestValue.SUCCESS:
self.result.set(TestValue.SUCCESS) self.result.set(TestValue.SUCCESS)

View File

@@ -7,8 +7,9 @@ import textwrap
from interpreter.test_items.test_item import TestItem, test_run from interpreter.test_items.test_item import TestItem, test_run
from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_result import TestValue
import libs.testium as tm import libs.testium as tm
from interpreter.utils.py_func_exec import py_func_exec from interpreter.utils.py_func_exec import PyFuncExecEngine
from interpreter.utils.tum_except import ETUMSyntaxError from interpreter.utils.api_srv import api_request
from interpreter.utils.tum_except import ETUMSyntaxError, ETUMRuntimeError
from interpreter.utils.constants import TestItemType as cst from interpreter.utils.constants import TestItemType as cst
@@ -31,6 +32,7 @@ class TestItemPyFunc(TestItem):
f"The '{self.cmd()}' test item named '{self.name()}' (child of '{self.parent.name()}') has a missing or wrong parameter", f"The '{self.cmd()}' test item named '{self.name()}' (child of '{self.parent.name()}') has a missing or wrong parameter",
self.seqFilename(), self.seqFilename(),
) )
self._proc = PyFuncExecEngine(tm.gd("python_bin", ""), api_request, 10)
@test_run @test_run
def execute(self): def execute(self):
@@ -45,7 +47,22 @@ class TestItemPyFunc(TestItem):
if tm.debug_enabled(): if tm.debug_enabled():
tm.print_debug("Parameters list:") tm.print_debug("Parameters list:")
tm.print_debug(textwrap.indent(pprint.pformat(pl), " |")) tm.print_debug(textwrap.indent(pprint.pformat(pl), " |"))
success, ret = py_func_exec(self.file_name, self.func_name, pl)
# start the process for executing external python
self._proc.start()
if not self._proc.wait_ready(10):
raise ETUMRuntimeError(
f"""Impossible to start the external python execution process.
Is the python path correct ?
python_bin = {tm.gd("python_bin", "no python path defined")}"""
)
try:
success, ret = self._proc.func_call(self.file_name, self.func_name, pl)
finally:
# Stops python function execution process
self._proc.stop()
self._proc.join()
if success == TestValue.SUCCESS: if success == TestValue.SUCCESS:
self.result.set(TestValue.SUCCESS) self.result.set(TestValue.SUCCESS)

View File

@@ -33,7 +33,7 @@ class TestItemRun(TestItem):
try: try:
self.tum_fime = self._prms.getParam('tum_fime', required=True) self.tum_fime = self._prms.getParam('tum_fime', required=True)
self.param_file = self._prms.getParam('param_file', default='') self.param_file = self._prms.getParam('param_file', default='')
self.python_path = self._prms.getParam('python_path', default='') self.python_bin = self._prms.getParam('python_bin', default='')
self.testium_path = self._prms.getParam('testium_path', default='') self.testium_path = self._prms.getParam('testium_path', default='')
self.log_path = self._prms.getParam('log_file', default='') self.log_path = self._prms.getParam('log_file', default='')
self.report_path = self._prms.getParam('report_file', default='') self.report_path = self._prms.getParam('report_file', default='')
@@ -58,7 +58,7 @@ class TestItemRun(TestItem):
'"{}" file could not be found'.format(file_path)) '"{}" file could not be found'.format(file_path))
self.tum_fime = file_path self.tum_fime = file_path
pf = self._prms.expanse(self.param_file) pf = self._prms.expanse(self.param_file)
pp = self._prms.expanse(self.python_path) pp = self._prms.expanse(self.python_bin)
sp = self._prms.expanse(self.testium_path) sp = self._prms.expanse(self.testium_path)
lp = self._prms.expanse(self.log_path) lp = self._prms.expanse(self.log_path)
rp = self._prms.expanse(self.report_path) rp = self._prms.expanse(self.report_path)

View File

@@ -8,7 +8,9 @@ from interpreter.utils.tum_except import (
) )
import interpreter.utils.settings as prefs import interpreter.utils.settings as prefs
from interpreter.test_report.test_report import TestReport from interpreter.test_report.test_report import TestReport
from interpreter.utils.py_func_exec import py_func_exec from interpreter.utils.py_func_exec import PyFuncExecEngine
from interpreter.utils.api_srv import api_request
from interpreter.utils.tum_except import ETUMRuntimeError
from interpreter.utils.constants import TestItemType as cst_type from interpreter.utils.constants import TestItemType as cst_type
import interpreter.utils.constants as cst import interpreter.utils.constants as cst
from interpreter.utils.constants import TEST_TYPE_LIST from interpreter.utils.constants import TEST_TYPE_LIST
@@ -342,21 +344,33 @@ class TestSet:
tm.print_debug(f' No file: "{post_exec_file}".') tm.print_debug(f' No file: "{post_exec_file}".')
return return
tm.print_debug(f'Post-execution from: "{post_exec_file}"') proc = PyFuncExecEngine(tm.gd("python_bin", ""), api_request, 10)
if self.rootItem().result.success: # start the process for executing external python
# tests backup is done here proc.start()
succ, res = py_func_exec(post_exec_file, "post_exec", []) try:
if not succ == TestValue.SUCCESS: if not proc.wait_ready(10):
tm.print_debug( raise ETUMRuntimeError(
f"Test success but the \"post_exec\" function failed: {res}" f"""Impossible to start the external python execution process.
Is the python path correct ?
python_bin = {tm.gd("python_bin", "no python path defined")}"""
) )
else: tm.print_debug(f'Post-execution from: "{post_exec_file}"')
succ, res = py_func_exec(post_exec_file, "post_exec_fail", []) if self.rootItem().result.success:
if not succ == TestValue.SUCCESS: # tests backup is done here
tm.print_debug( succ, res = proc.func_call(post_exec_file, "post_exec", [])
f"Test failed but the \"post_exec_fail\" function failed: {res}" if not succ == TestValue.SUCCESS:
) tm.print_debug(
f"Test success but the \"post_exec\" function failed: {res}"
)
else:
succ, res = proc.func_call(post_exec_file, "post_exec_fail", [])
if not succ == TestValue.SUCCESS:
tm.print_debug(
f"Test failed but the \"post_exec_fail\" function failed: {res}"
)
finally:
proc.stop()
proc.join()
def rootItem(self): def rootItem(self):
return self._rootItem return self._rootItem

View File

@@ -5,8 +5,9 @@ import libs.testium as tm
# Fill the api_dict with the function of tm # Fill the api_dict with the function of tm
api_dict = {k: getattr(tm, k) for k in SUPPORTED_API if hasattr(tm, k)} api_dict = {k: getattr(tm, k) for k in SUPPORTED_API if hasattr(tm, k)}
def api_request(method, params): def api_request(method, params):
global api_dict
if method in api_dict.keys(): if method in api_dict.keys():
if params is None: if params is None:
params = [] params = []

View File

@@ -1,12 +1,7 @@
import random
import os
import sys
import time
import platform
import math
import json
import libs.testium as tm import libs.testium as tm
from interpreter.utils.tum_except import (ETUMSyntaxError, ETUMRuntimeError) from interpreter.utils.py_eval import eval_exec
from interpreter.utils.tum_except import ETUMSyntaxError, ETUMRuntimeError
def evaluate(val, **replacement_dict): def evaluate(val, **replacement_dict):
v2 = val v2 = val
@@ -16,37 +11,52 @@ def evaluate(val, **replacement_dict):
for key, replacement in replacement_dict.items(): for key, replacement in replacement_dict.items():
val = val.replace(f"$({key})", str(replacement)) val = val.replace(f"$({key})", str(replacement))
try: try:
v2 = eval(val) v2 = eval_exec(val)
except Exception as e: except Exception as e:
# eval can crash # eval can crash
if tm.debug_enabled(): if tm.debug_enabled():
s=f"Evaluation of '{val}' failed with message:\n "+str(e) s = f"Evaluation of '{val}' failed with message:\n " + str(e)
tm.print_debug(s) tm.print_debug(s)
v2 = val v2 = val
evaluated = (val != v2) evaluated = val != v2
return evaluated, v2 return evaluated, v2
def eval_to_boolean(c): def eval_to_boolean(c):
if isinstance(c, bool): if isinstance(c, bool):
condition = c condition = c
elif isinstance(c, (str, bytes)): elif isinstance(c, (str, bytes)):
if c.lower() in ['true', 't', 'y', 'yes', 'ok', ]: if c.lower() in [
"true",
"t",
"y",
"yes",
"ok",
]:
condition = True condition = True
elif c.lower() in ['f', 'n', 'nok', 'ko', 'false', 'no',]: elif c.lower() in [
"f",
"n",
"nok",
"ko",
"false",
"no",
]:
condition = False condition = False
else: else:
try: try:
cond = eval(c) cond = eval_exec(c)
condition = eval_to_boolean(cond) condition = eval_to_boolean(cond)
except Exception as e: except Exception as e:
print("eval with c: {}".format(c)) print("eval with c: {}".format(c))
raise e raise e
elif type(c) is int: elif type(c) is int:
condition = (c > 0) condition = c > 0
else: else:
raise ETUMSyntaxError('c : {} not string, int or bool'.format(c)) raise ETUMSyntaxError("c : {} not string, int or bool".format(c))
return condition return condition
def post_evaluate(post_eval, res): def post_evaluate(post_eval, res):
"""This function is evaluating the result of a test, """This function is evaluating the result of a test,
therefore it may include a $(result) parameter. therefore it may include a $(result) parameter.

View File

@@ -1,4 +1,3 @@
import sys
import socket import socket
import json import json
import threading import threading

View File

@@ -4,13 +4,66 @@ import shutil
import subprocess import subprocess
import socket import socket
import libs.testium as tm import libs.testium as tm
from interpreter.utils.paths import sys_lua_path from interpreter.utils.paths import sys_app_path_lin, sys_app_path_win
from interpreter.utils.tum_except import ETUMRuntimeError from interpreter.utils.tum_except import ETUMRuntimeError
from interpreter.utils.jrpc import JsonRpcClient from interpreter.utils.jrpc import JsonRpcClient
from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_result import TestValue
function_call_process = None function_call_process = None
def _lua_version(path: str):
cmd = f'"{path}" -v'
try:
result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
encoding=tm.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(".")
if len(vers) != 3:
vers = (0,0,0)
except:
vers = (0,0,0)
return tuple(vers)
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_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
def lua_func_call_init(lua_path, request_handler, timeout): def lua_func_call_init(lua_path, request_handler, timeout):
""" """
@@ -87,7 +140,7 @@ class LuaFuncExecEngine:
f"The passed executable is not a lua interpreter: '{lua_path}'" f"The passed executable is not a lua interpreter: '{lua_path}'"
) )
else: else:
lua_path = sys_lua_path() lua_path = _sys_lua_path()
if lua_path == "": if lua_path == "":
raise ETUMRuntimeError( raise ETUMRuntimeError(
f"No valid lua interpreter found" f"No valid lua interpreter found"

View File

@@ -1,7 +1,7 @@
import interpreter.utils.globdict as globdict import interpreter.utils.globdict as globdict
from interpreter.utils.eval import evaluate
from interpreter.utils.tum_except import ETUMSyntaxError, ETUMRuntimeError from interpreter.utils.tum_except import ETUMSyntaxError, ETUMRuntimeError
glob_eval_func = None
class TestItemParams: class TestItemParams:
@@ -295,12 +295,6 @@ def _operate_param(glob, parent):
return treated, g return treated, g
# def _dummy_eval(val):
# bla = evaluate(val)
# print("******** evaluate(" + str(val) + ") = " + str(bla[1]))
# return bla
def _preprocess_string(value, parent=None): def _preprocess_string(value, parent=None):
"""This function parses a string value to check if patterns corresponding """This function parses a string value to check if patterns corresponding
to $(xxx) exists. to $(xxx) exists.
@@ -318,7 +312,8 @@ def _eval_param(value):
content is done. content is done.
If it is not evaluable, not replaced. If it is not evaluable, not replaced.
""" """
return _parse_and_process("$|", "|", value, evaluate) global glob_eval_func
return _parse_and_process("$|", "|", value, glob_eval_func)
def _process_recursively(func, param_value, *fparams): def _process_recursively(func, param_value, *fparams):
@@ -377,3 +372,9 @@ def expanse(param_value, parent=None):
result = tmp_res result = tmp_res
n += 1 n += 1
return result return result
def eval_func_init(eval_func):
global glob_eval_func
glob_eval_func = eval_func

View File

@@ -36,76 +36,7 @@ def abs_path_from_file(file):
return abs_file_path return abs_file_path
def sys_encoding(): def sys_app_path_win(app_name):
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(".")
if len(vers) != 3:
vers = (0,0,0)
except:
vers = (0,0,0)
return tuple(vers)
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: try:
result = subprocess.run( result = subprocess.run(
f"where {app_name}", f"where {app_name}",
@@ -113,79 +44,28 @@ def _sys_app_path_win(app_name):
capture_output=True, capture_output=True,
text=True, text=True,
encoding="oem", encoding="oem",
timeout=10 timeout=10,
) )
data = result.stdout data = result.stdout
except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired): except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired):
data = "" data = ""
sys_python_path = data.splitlines() sys_python_bin = data.splitlines()
for l in sys_python_path: for l in sys_python_bin:
if f"{app_name}.exe" in l: if f"{app_name}.exe" in l:
return l return l
return "" return ""
def _sys_app_path_lin(app_name): def sys_app_path_lin(app_name):
try: try:
result = subprocess.run( result = subprocess.run(
f"which {app_name}", f"which {app_name}", shell=True, capture_output=True, text=True, timeout=10
shell=True,
capture_output=True,
text=True,
timeout=10
) )
data = result.stdout data = result.stdout
except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired): except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired):
data = "" data = ""
sys_python_path = data.splitlines() sys_python_bin = data.splitlines()
for l in sys_python_path: for l in sys_python_bin:
if ( if (f"{app_name}" in l) and not l.startswith("which:"):
(f"{app_name}" in l)
and not l.startswith("which:")
):
return l return l
return "" 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

View File

@@ -0,0 +1,49 @@
from interpreter.utils.py_process import PyProcessBase
from interpreter.utils.tum_except import ETUMRuntimeError
import libs.testium as tm
eval_process = None
def eval_process_init(python_bin, request_handler, timeout, python_path):
global eval_process
eval_process = EvalExecEngine(python_bin, request_handler, timeout, python_path)
return eval_process
class EvalExecEngine(PyProcessBase):
def eval(self, value):
if (self._rpc is not None) and self._rpc.is_alive():
answer = self._rpc.call(
"eval",
{
"value": value,
},
)
if "result" in answer:
return answer["result"]
# In case an error was encountered in the called function
elif "error" in answer:
raise ETUMRuntimeError(answer["result"])
else:
raise ETUMRuntimeError(
"Unexepected eval call failure to be reported to testium support team."
)
else:
raise ETUMRuntimeError(
"No function execution process active. To be reported to testium support team."
)
def eval_exec(value):
global eval_process
if eval_process is not None and eval_process.is_alive():
result = eval_process.eval(value)
else:
raise ETUMRuntimeError(
"No function execution process active. To be reported to testium support team."
)
return result

View File

@@ -1,129 +1,10 @@
import os
import sys from interpreter.utils.py_process import PyProcessBase
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.tum_except import ETUMRuntimeError
from interpreter.utils.jrpc import JsonRpcClient
from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_result import TestValue
function_call_process = None
class PyFuncExecEngine(PyProcessBase):
def py_func_call_init(python_path, request_handler, timeout):
global function_call_process
function_call_process = PyFuncExecEngine(python_path, request_handler, timeout)
return function_call_process
def is_python_interpreter(path: str, timeout=2) -> bool:
try:
result = subprocess.run(
[path, "-c", "import sys; print(sys.executable)"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=timeout,
)
return result.returncode == 0
except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired):
return False
class PyFuncExecEngine:
def __init__(self, python_path="", request_handler=None, timeout=10):
if python_path != "":
if shutil.which(python_path) is None:
raise ETUMRuntimeError(
f"The passed python path is not pointing to an executable: '{python_path}'"
)
if not is_python_interpreter(python_path):
raise ETUMRuntimeError(
f"The passed executable is not a python interpreter: '{python_path}'"
)
else:
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
self._req_handler = request_handler
self._process = None
self._port = 0
self._timeout = timeout
self._rpc = None
def start(self):
"""
run the subprocess to execute the python functions of the test.
"""
# This thread is not closed until new test is loaded
if self._process is not None:
raise ETUMRuntimeError("The function subprocess has already been started.")
# POpen config
CUST_ENV = {
"PATH": {"replace": False},
"PYTHONPATH": {"replace": True},
}
py_env = tm.gd("python_env", {})
env = os.environ.copy()
for k, v in CUST_ENV.items():
e = py_env.get(k, "")
if e != "":
if v["replace"]:
env[k] = e
else:
env[k] = e + ";" + env.get(k, "")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("localhost", 0))
self._port = sock.getsockname()[1]
func_proc_path = tm.gd("testium_path")
params = [self._ppath, "-m", "py_func", "-p", f"{self._port}", "-t", f"{self._timeout}"]
if tm.debug_enabled() and tm.gd("debug_rpc", False):
params.append("-v")
self._process = subprocess.Popen(
params, env=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("localhost", self._port, req_handler=self._req_handler)
if tm.debug_enabled() and tm.gd("debug_rpc", False):
self._rpc.dbg_out = sys.stdout
self._rpc.start()
def join(self):
if self._rpc is not None:
self._rpc.join()
self._rpc = None
self._process = None
def wait_ready(self, timeout=None):
if self._rpc is not None and self._rpc.is_alive():
return self._rpc.wait_ready(timeout)
return False
def stop(self):
if self._rpc is not None:
self._rpc.stop()
def func_call(self, file: str, func_name: str, params: list, verbose: bool = True): def func_call(self, file: str, func_name: str, params: list, verbose: bool = True):
if (self._rpc is not None) and self._rpc.is_alive(): if (self._rpc is not None) and self._rpc.is_alive():
@@ -159,19 +40,3 @@ class PyFuncExecEngine:
raise ETUMRuntimeError( raise ETUMRuntimeError(
"No function execution process active. To be reported to testium support team." "No function execution process active. To be reported to testium support team."
) )
def py_func_exec(file: str, func_name: str, params: list, verbose: bool = True):
"""Executes a python function and returns its result and reported values"""
global function_call_process
if function_call_process is not None:
success, result = function_call_process.func_call(
file, func_name, params, verbose
)
else:
raise ETUMRuntimeError(
"No function execution process active. To be reported to testium support team."
)
return success, result

View File

@@ -0,0 +1,184 @@
import os
import shutil
import sys
import subprocess
import socket
import libs.testium as tm
from interpreter.utils.paths import sys_app_path_lin, sys_app_path_win
from interpreter.utils.tum_except import ETUMRuntimeError
from interpreter.utils.jrpc import JsonRpcClient
from interpreter.utils.paths import testium_path
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=tm.sys_encoding(),
timeout=10,
)
data = result.stdout
except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired) as e:
tm.print_debug(str(e))
data = ""
return eval(data)
def _is_python3(python_bin):
try:
v = _python_version(python_bin)
if v[0] == 3:
res = True
except:
res = False
return res
def _is_python_interpreter(path: str, timeout=2) -> bool:
try:
result = subprocess.run(
[path, "-c", "import sys; print(sys.executable)"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=timeout,
)
return result.returncode == 0
except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired):
return False
def _sys_python_bin():
sys_python_bin = tm.gd("_sys_python_bin", "")
if sys_python_bin != "":
return sys_python_bin
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_bin = func(e)
if sys_python_bin == "":
continue
if not _is_python3(sys_python_bin):
sys_python_bin = ""
continue
tm.setgd("_sys_python_bin", sys_python_bin)
return sys_python_bin
class PyProcessBase:
CUST_ENV = {
"PATH": {"replace": False},
"PYTHONPATH": {"replace": True},
}
def __init__(self, python_bin="", request_handler=None, timeout=10, python_path=""):
if (python_bin is not None) and (python_bin != ""):
if shutil.which(python_bin) is None:
raise ETUMRuntimeError(
f"The passed python path is not pointing to an executable: '{python_bin}'"
)
if not _is_python_interpreter(python_bin):
raise ETUMRuntimeError(
f"The passed executable is not a python interpreter: '{python_bin}'"
)
else:
python_bin = _sys_python_bin()
if python_bin == "":
raise ETUMRuntimeError(f"No valid python interpreter found")
tm.setgd("python_bin", python_bin)
self._pbin = python_bin
self._ppath = python_path
self._req_handler = request_handler
self._process = None
self._port = 0
self._timeout = timeout
self._rpc = None
def start(self):
"""
run the subprocess to execute the python functions of the test.
"""
# This thread is not closed until new test is loaded
if self._process is not None:
raise ETUMRuntimeError("The function subprocess has already been started.")
# POpen config
py_env = tm.gd("python_env", {})
env = os.environ.copy()
for k, v in self.CUST_ENV.items():
e = py_env.get(k, "")
if e != "":
if v["replace"]:
env[k] = e
else:
env[k] = e + os.pathsep + env.get(k, "")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("localhost", 0))
self._port = sock.getsockname()[1]
# Add the path of the subprocess (root sources of testium)
func_proc_path = testium_path()
env["PYTHONPATH"] = func_proc_path + os.pathsep + self._ppath + os.pathsep + env.get("PYTHONPATH", "")
params = [
self._pbin,
"-m",
"py_func",
"-p",
f"{self._port}",
"-t",
f"{self._timeout}",
]
if tm.debug_enabled() and tm.gd("debug_rpc", False):
params.append("-v")
self._process = subprocess.Popen(params, env=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(
"localhost", self._port, req_handler=self._req_handler
)
if tm.debug_enabled() and tm.gd("debug_rpc", False):
self._rpc.dbg_out = sys.stdout
self._rpc.start()
def join(self):
if self._rpc is not None:
self._rpc.join()
self._rpc = None
self._process = None
def wait_ready(self, timeout=None):
if self._rpc is not None and self._rpc.is_alive():
return self._rpc.wait_ready(timeout)
return False
def is_alive(self):
if self._rpc is not None:
return self._rpc.is_alive()
return False
def stop(self):
if self._rpc is not None:
self._rpc.stop()

View File

@@ -259,14 +259,14 @@ class TestiumSettings():
def git_supported(self, value): def git_supported(self, value):
self.set_value(self.SettingsGitSupported, value) self.set_value(self.SettingsGitSupported, value)
# SettingsPythonPath = 'pythonPath' # SettingsPythonPath = 'python_bin'
@property @property
def python_path(self): def python_bin(self):
r = self.value(self.SettingsPythonPath, "") r = self.value(self.SettingsPythonPath, "")
return r return r
@python_path.setter @python_bin.setter
def python_path(self, value): def python_bin(self, value):
self.set_value(self.SettingsPythonPath, value) self.set_value(self.SettingsPythonPath, value)
# SettingsLuaPath = 'luaPath' # SettingsLuaPath = 'luaPath'

View File

@@ -1,7 +1,7 @@
import os import os
from sys import exc_info from sys import exc_info
from jinja2 import Template from jinja2 import Template
from jinja2.exceptions import TemplateError, UndefinedError from jinja2.exceptions import TemplateSyntaxError, TemplateError, UndefinedError
from tempfile import TemporaryFile from tempfile import TemporaryFile
from interpreter.utils.yaml_load import print_yaml from interpreter.utils.yaml_load import print_yaml
from interpreter.utils.tum_except import ETUMSyntaxError from interpreter.utils.tum_except import ETUMSyntaxError
@@ -29,17 +29,17 @@ def template_to_test(filename: str, params: list):
params["include_directory"] = os.path.dirname(os.path.abspath(filename)) params["include_directory"] = os.path.dirname(os.path.abspath(filename))
tmpf.write(j2_template.render(params)) tmpf.write(j2_template.render(params))
except TemplateSyntaxError as e: except TemplateSyntaxError as e:
raise ECPTSyntaxError(f"""Template loading of file '{filename}' with following parameters '{str(params)}' raise ETUMSyntaxError(f"""Template loading of file '{filename}' with following parameters '{str(params)}'
Syntax error in template: {e.message}""") Syntax error in template: {e.message}""")
except UndefinedError as e: except UndefinedError as e:
raise ECPTSyntaxError(f"""Template loading of file '{filename}' with following parameters '{str(params)}' raise ETUMSyntaxError(f"""Template loading of file '{filename}' with following parameters '{str(params)}'
Undefined variable error: {e.message}""") Undefined variable error: {e.message}""")
except TemplateError as e: except TemplateError as e:
raise ECPTSyntaxError(f"""Template loading of file '{filename}' with following parameters '{str(params)}' raise ETUMSyntaxError(f"""Template loading of file '{filename}' with following parameters '{str(params)}'
Template rendering error: {e.message}""") Template rendering error: {e.message}""")
except Exception as e: except Exception as e:
# Catch any other unexpected errors # Catch any other unexpected errors
raise ECPTSyntaxError(f"""Template loading of file '{filename}' with following parameters '{str(params)}' raise ETUMSyntaxError(f"""Template loading of file '{filename}' with following parameters '{str(params)}'
Unexpected error: {str(e)}""") Unexpected error: {str(e)}""")
# return to begining of the temp file # return to begining of the temp file

View File

@@ -3,12 +3,9 @@ from pathlib import Path
import datetime import datetime
from socket import gethostname from socket import gethostname
import ast import ast
import json
import yaml import yaml
import copy import copy
import yaml
from interpreter.utils.constants import TestItemType as cst from interpreter.utils.constants import TestItemType as cst
import libs.testium as tm import libs.testium as tm
import interpreter.utils.globdict as globdict import interpreter.utils.globdict as globdict
@@ -16,13 +13,14 @@ import interpreter.utils.settings as prefs
from interpreter.utils.paths import testium_path from interpreter.utils.paths import testium_path
from interpreter.utils.yaml_load import yaml_load from interpreter.utils.yaml_load import yaml_load
from interpreter.utils import clear_recursively from interpreter.utils import clear_recursively
from interpreter.utils.include import TUMLoader, TUMLoaderNoIncludes, TUMLoaderRawIncludes
from interpreter.utils.tum_except import ETUMSyntaxError from interpreter.utils.tum_except import ETUMSyntaxError
from interpreter.utils.params import (expanse) from interpreter.utils.params import expanse, eval_func_init
from interpreter.utils.version import (
get_version, get_testium_version, get_modifications)
from interpreter.utils.eval import evaluate from interpreter.utils.eval import evaluate
from interpreter.utils.template import template_to_test from interpreter.utils.version import (
get_version,
get_testium_version,
get_modifications,
)
from interpreter.test_items.test_item import TestItem from interpreter.test_items.test_item import TestItem
from interpreter.test_items.test_item_sleep import TestItemSleep from interpreter.test_items.test_item_sleep import TestItemSleep
@@ -73,33 +71,11 @@ def _constants_init():
cst.TYPE_VALUE_DLG.item_class = TestItemValueDialog cst.TYPE_VALUE_DLG.item_class = TestItemValueDialog
def _locate_config_files(test_dir, config_files, silent=False):
ret = []
pf = []
if len(config_files) == 0:
for p in ['param.yaml', 'param.yml']:
param_filename = os.path.join(test_dir, p)
if os.path.exists(param_filename):
pf.append(param_filename)
if not silent:
tm.print_info(f"Configuration file loaded: {p}.")
else:
if not silent:
tm.print_info(f"Default param file \"{p}\" does not exist.")
else:
pf = config_files
for p in pf:
ret.append(p)
return ret
def locate_report_file(rep_file): def locate_report_file(rep_file):
# report file name treatment # report file name treatment
if rep_file != '': if rep_file != "":
if not os.path.isabs(rep_file): if not os.path.isabs(rep_file):
rep_file = os.path.join( rep_file = os.path.join(os.getcwd(), rep_file)
os.getcwd(), rep_file)
rep_file = os.path.normpath(rep_file) rep_file = os.path.normpath(rep_file)
if not os.path.exists(os.path.dirname(rep_file)): if not os.path.exists(os.path.dirname(rep_file)):
os.makedirs(os.path.dirname(rep_file)) os.makedirs(os.path.dirname(rep_file))
@@ -107,111 +83,9 @@ def locate_report_file(rep_file):
return rep_file return rep_file
def _config_files_from_test(test_dict, config_files=None):
test_dir = tm.gd('test_directory')
pf = []
if isinstance(config_files, list) and len(config_files) == 0:
param_filename = test_dict.get('config_file', None)
if param_filename is None:
param_node = test_dict.get('param_file', None)
if param_node is not None:
if isinstance(param_node, dict):
p = param_node.get('file_name', None)
if p is not None:
param_filename = p
else:
param_filename = param_node
else:
param_filename = param_node
if param_filename is None:
pf = _locate_config_files(test_dir, [])
elif isinstance(param_filename, str):
pf.append(param_filename)
elif isinstance(param_filename, (list)):
pf = []
for p in param_filename:
if isinstance(p, list):
for pp in p:
pf.append(pp)
elif p is not None:
pf.append(p)
else:
raise ETUMSyntaxError(
'Unrecognized tum "param_file" : {}'.format(param_filename))
elif isinstance(config_files, list):
pf = config_files
elif isinstance(config_files, str):
pf = [config_files]
else:
raise ETUMSyntaxError(
'Unrecognized config_files parameter : {}'.format(config_files))
return pf
def _load_test_dict(test_file, variables: dict, no_include: bool = False, raw_include: bool = False):
loader = TUMLoader
loader = TUMLoaderRawIncludes if raw_include else loader
loader = TUMLoaderNoIncludes if no_include else loader
# Jinja template processing
tmpf = template_to_test(test_file, variables)
try:
d = yaml_load(tmpf, test_file, loader)
finally:
tmpf.close()
return d
def load_test(test_file, test_dir, cmdline_pfs, cmdline_defs, gui_defaults):
# First step: populate config files without includes considered
test_dict = _load_test_dict(test_file, {}, no_include=True)
_check_test_dict(test_dict)
prepare_global()
# Define the global builtin variables
set_standard_gd_keys(test_dict["main"].get(
"name", "Unnamed"), test_dir, test_file, cmdline_pfs)
# Include the content of the first config files into glob dict
old_pfs = _config_files_from_test(test_dict, cmdline_pfs)
# Variables updated
gd = update_global(old_pfs, cmdline_defs, gui_defaults, silent=True)
while True:
# Loop to check param files until all param files are identified
test_dict = _load_test_dict(test_file, gd, raw_include=True)
new_pfs = _config_files_from_test(test_dict, cmdline_pfs)
# Check if things have changed since previous evaluation of
# config files
new_stuff = False
if len(old_pfs) != len(new_pfs):
new_stuff = True
if not new_stuff:
for i in range(len(old_pfs)):
if old_pfs[i] != new_pfs[i]:
new_stuff = True
break
# If the param files are identical, we continue in loading process
if not new_stuff:
break
# Variables updated
gd = update_global(new_pfs, cmdline_defs, gui_defaults, silent=False)
old_pfs = copy.copy(new_pfs)
# Processing (with includes) for complete file loading
test_dict = _load_test_dict(test_file, gd)
return test_dict, new_pfs
def yamltodict(param_file, silent=True): def yamltodict(param_file, silent=True):
# load of the file # load of the file
with open(param_file, 'r') as fd: with open(param_file, "r") as fd:
dp = yaml_load(fd, param_file, yaml.Loader) dp = yaml_load(fd, param_file, yaml.Loader)
if dp is None: if dp is None:
@@ -229,9 +103,9 @@ def yamltodict(param_file, silent=True):
if not silent: if not silent:
if not tm.debug_enabled(): if not tm.debug_enabled():
tm.print_info(f"\"{param_file}\" loaded.") tm.print_info(f'"{param_file}" loaded.')
else: else:
tm.print_debug(f"\"{param_file}\" loading:") tm.print_debug(f'"{param_file}" loading:')
for k, v in dp.items(): for k, v in dp.items():
tm.print_debug(f" {k}: {v}") tm.print_debug(f" {k}: {v}")
tm.print_debug(f"done.") tm.print_debug(f"done.")
@@ -241,7 +115,7 @@ def yamltodict(param_file, silent=True):
def _feed_gd_with_params(param_file, silent=True): def _feed_gd_with_params(param_file, silent=True):
test_dir = tm.gd('test_directory') test_dir = tm.gd("test_directory")
# param files pre-processing # param files pre-processing
files = [] files = []
for p in param_file: for p in param_file:
@@ -263,44 +137,35 @@ def _feed_gd_with_params(param_file, silent=True):
raise ETUMSyntaxError(f'Parameter file "{pf}" not found') raise ETUMSyntaxError(f'Parameter file "{pf}" not found')
ext = os.path.splitext(pf)[1] ext = os.path.splitext(pf)[1]
if (ext == '.yaml') or (ext == '.yml'): if (ext == ".yaml") or (ext == ".yml"):
yamltodict(pf, silent) yamltodict(pf, silent)
else: else:
raise ETUMSyntaxError( raise ETUMSyntaxError('config files must be "*.yaml" or "*.yml"')
'config files must be "*.yaml" or "*.yml"')
def set_standard_gd_keys(test_name, test_dir, test_file, config_files): def set_standard_gd_keys(test_name, test_dir, test_file, config_files):
tm.setgd('testium_version', get_testium_version()) tm.setgd("testium_version", get_testium_version())
tm.setgd('testium_path', testium_path()) tm.setgd("testium_path", testium_path())
tm.setgd('test_name', test_name) tm.setgd("test_name", test_name)
tm.setgd('test_directory', test_dir) tm.setgd("test_directory", test_dir)
tm.setgd('test_main_file', test_file) tm.setgd("test_main_file", test_file)
tm.setgd('config_files', config_files) tm.setgd("config_files", config_files)
tm.setgd('host_name', gethostname()) tm.setgd("host_name", gethostname())
tm.setgd('home', str(Path.home())) tm.setgd("home", str(Path.home()))
tm.setgd('os', tm.OS()) tm.setgd("os", tm.OS())
def env_init(): def env_init():
if not hasattr(prefs, "settings"): if not hasattr(prefs, "settings"):
prefs.init() prefs.init()
eval_func_init(evaluate)
_constants_init() _constants_init()
def _check_test_dict(test_dict):
if not isinstance(test_dict, dict):
raise ETUMSyntaxError(
"The tum file has a major problem. Please check the documentation for syntax.")
if not 'main' in test_dict.keys():
raise ETUMSyntaxError(
"The tum file has a major problem. The 'main' section could not be found.")
def update_global(config_files, defines, gui_defaults, silent=False): def update_global(config_files, defines, gui_defaults, silent=False):
'''Global dict updated with the content of the config file and a dict provided. """Global dict updated with the content of the config file and a dict provided.
this function returns the resulting dict. this function returns the resulting dict.
''' """
# GUI preferences applied first # GUI preferences applied first
for k, v in gui_defaults.items(): for k, v in gui_defaults.items():
try: try:
@@ -332,7 +197,9 @@ def update_global(config_files, defines, gui_defaults, silent=False):
conf_val = tm.gd(k) conf_val = tm.gd(k)
if val != conf_val: if val != conf_val:
if not silent: if not silent:
tm.print_info(f"Variable $({k}) overloaded by command line arg --> \"{val}\".") tm.print_info(
f'Variable $({k}) overloaded by command line arg --> "{val}".'
)
tm.setgd(k, val) tm.setgd(k, val)
return globdict.global_dict return globdict.global_dict
@@ -355,48 +222,52 @@ def restore_gd(dict):
def test_run_init(): def test_run_init():
tm.init_timestamp() tm.init_timestamp()
test_dir = tm.gd('test_directory') test_dir = tm.gd("test_directory")
tm.setgd('test_version', get_version(test_dir)) tm.setgd("test_version", get_version(test_dir))
tm.setgd('test_modifs', get_modifications(test_dir)) tm.setgd("test_modifs", get_modifications(test_dir))
start_test_date = datetime.datetime.now() start_test_date = datetime.datetime.now()
tm.setgd('start_test_date', start_test_date) tm.setgd("start_test_date", start_test_date)
tm.setgd('testrun_date', start_test_date.strftime("%Y-%m-%d")) tm.setgd("testrun_date", start_test_date.strftime("%Y-%m-%d"))
tm.setgd('testrun_time', start_test_date.strftime("%H:%M:%S")) tm.setgd("testrun_time", start_test_date.strftime("%H:%M:%S"))
def test_run_header(): def test_run_header():
tool_version = tm.gd('testium_version') tool_version = tm.gd("testium_version")
test_file = tm.gd('test_main_file', '') test_file = tm.gd("test_main_file", "")
has_test_file = (tm.gd('test_main_file') != '') has_test_file = tm.gd("test_main_file") != ""
s = '' s = ""
s += (80*'=') + '\n' s += (80 * "=") + "\n"
s += '====== Test overview' + '\n' s += "====== Test overview" + "\n"
s += (80*'=') + '\n' s += (80 * "=") + "\n"
if has_test_file: if has_test_file:
s += ('Executed test file : ' + test_file) + '\n' s += ("Executed test file : " + test_file) + "\n"
for cf in tm.gd('config_files'): for cf in tm.gd("config_files"):
s += ('With param file : {}'.format(cf)) + '\n' s += ("With param file : {}".format(cf)) + "\n"
s += ('Test started : ' + tm.gd('testrun_date') + ' ' + s += (
tm.gd('testrun_time')) + '\n' "Test started : "
+ tm.gd("testrun_date")
+ " "
+ tm.gd("testrun_time")
) + "\n"
s += (80*'=') + '\n' s += (80 * "=") + "\n"
s += ('====== Test configuration') + '\n' s += ("====== Test configuration") + "\n"
s += (80*'=') + '\n' s += (80 * "=") + "\n"
s += ('Test executed with testium : ' + s += ("Test executed with testium : " + tool_version.splitlines()[0]) + "\n"
tool_version.splitlines()[0]) + '\n'
for l in tool_version.splitlines()[1:]: for l in tool_version.splitlines()[1:]:
s += (32*' ' + ': ' + l) + '\n' s += (32 * " " + ": " + l) + "\n"
s += (' \n') s += " \n"
if has_test_file: if has_test_file:
test_version = tm.gd('test_version') test_version = tm.gd("test_version")
test_modifs = tm.gd('test_modifs') test_modifs = tm.gd("test_modifs")
s += ('Test scripts revision : ' + s += (
test_version.splitlines()[0]) + '\n' "Test scripts revision : " + test_version.splitlines()[0]
) + "\n"
for l in test_version.splitlines()[1:]: for l in test_version.splitlines()[1:]:
s += (32*' ' + ': ' + l) + '\n' s += (32 * " " + ": " + l) + "\n"
for l in test_modifs.splitlines(): for l in test_modifs.splitlines():
s += (' '+l) + '\n' s += (" " + l) + "\n"
return s return s

View File

@@ -16,9 +16,11 @@ import numpy as np
import matplotlib.dates as mdates import matplotlib.dates as mdates
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import libs.testium as tm
from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_result import TestValue
from interpreter.utils.tum_except import ETUMRuntimeError from interpreter.utils.tum_except import ETUMRuntimeError
from interpreter.utils.py_func_exec import py_func_exec from interpreter.utils.py_func_exec import PyFuncExecEngine
from interpreter.utils.api_srv import api_request
from interpreter.utils.eval import post_evaluate from interpreter.utils.eval import post_evaluate
from interpreter.utils.periodic_timer import PeriodicTimer from interpreter.utils.periodic_timer import PeriodicTimer
from interpreter.utils.paths import abs_path_from_file, prepare_file_to_save from interpreter.utils.paths import abs_path_from_file, prepare_file_to_save
@@ -268,11 +270,19 @@ class RuntimePlotPeriodic(PeriodicTimer):
self.func_name = func_name self.func_name = func_name
self.args = args self.args = args
self.post_eval = post_eval self.post_eval = post_eval
self.proc = PyFuncExecEngine(tm.gd("python_bin", ""), api_request, 10)
self.proc.start()
if not self.proc.wait_ready(10):
raise ETUMRuntimeError(
f"""Impossible to start the external python execution process.
Is the python path correct ?
python_bin = {tm.gd("python_bin", "no python path defined")}"""
)
self.start() self.start()
self.on_timer_event() self.on_timer_event()
def on_timer_event(self): def on_timer_event(self):
succ, ret = py_func_exec(self.file, self.func_name, self.args) succ, ret = self.proc.func_call(self.file, self.func_name, self.args)
if succ == TestValue.SUCCESS: if succ == TestValue.SUCCESS:
res, _ = ret res, _ = ret
res = post_evaluate(self.post_eval, res) res = post_evaluate(self.post_eval, res)
@@ -280,6 +290,11 @@ class RuntimePlotPeriodic(PeriodicTimer):
else: else:
print("Plot periodic timer function ({self.file}/{self.func_name}) failed: \"{ret}\"") print("Plot periodic timer function ({self.file}/{self.func_name}) failed: \"{ret}\"")
def stop(self):
self.proc.stop()
self.proc.join()
super().stop()
class RuntimePlot: class RuntimePlot:
EXPORTS = [".pdf", ".csv"] EXPORTS = [".pdf", ".csv"]

View File

@@ -143,21 +143,21 @@ def last_plot_value(name: str) -> dict:
############################################################################### ###############################################################################
class FunctionItem(): # class FunctionItem():
"""Class allowing extended capabilities of function.""" # """Class allowing extended capabilities of function."""
module_count = 0 # module_count = 0
def __init__(self): # def __init__(self):
self._reported_value = {} # self._reported_value = {}
def reportValue(self, key, value): # def reportValue(self, key, value):
self._reported_value[key] = value # self._reported_value[key] = value
def reportedValues(self): # def reportedValues(self):
return self._reported_value # return self._reported_value
def exec(self): # def exec(self):
pass # pass
def get_main_dir(): def get_main_dir():
@@ -209,6 +209,14 @@ def OS():
return platform.system() return platform.system()
def sys_encoding():
if OS() == "Windows":
enc = "oem"
else:
enc = "utf-8"
return enc
def line_number(phrase, filename): def line_number(phrase, filename):
with open(filename, 'r') as f: with open(filename, 'r') as f:
for (i, line) in enumerate(f): for (i, line) in enumerate(f):

View File

@@ -101,7 +101,7 @@ class PrefWindow(QDialog):
prefs.settings.SettingsPythonPath: { prefs.settings.SettingsPythonPath: {
"type": "text", "type": "text",
"widget": self.ui.editPythonPath, "widget": self.ui.editPythonPath,
"value": prefs.settings.python_path, "value": prefs.settings.python_bin,
"default": "", "default": "",
"changed": False, "changed": False,
}, },

View File

@@ -977,9 +977,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def defaults_for_process(self): def defaults_for_process(self):
d = {} d = {}
pp = prefs.settings.python_path pp = prefs.settings.python_bin
if pp != "": if pp != "":
d["python_path"] = pp d["python_bin"] = pp
pp = prefs.settings.lua_path pp = prefs.settings.lua_path
if pp != "": if pp != "":

View File

@@ -1,24 +1,21 @@
import os from pathlib import Path
import sys import sys
import logging
import traceback import traceback
logging.basicConfig(
level=logging.ERROR,
filename=os.path.join(os.path.normpath(os.getcwd()), "crash.txt"),
format="%(asctime)s - %(levelname)s - %(message)s"
)
def exception_handler(typ_exc, value, trbk): def exception_handler(typ_exc, value, trbk):
"""Testium Exception handling""" """Testium Exception handling"""
logging.error("An unmanaged exception occured", exc_info=(typ_exc, value, trbk)) print("An unmanaged exception occured", exc_info=(typ_exc, value, trbk))
print(f"Critical failure : '{value}'.") print(f"Critical failure : '{value}'.")
tb = traceback.format_exception(typ_exc, value, trbk) tb = traceback.format_exception(typ_exc, value, trbk)
print("".join(tb)) print("".join(tb))
sys.excepthook = exception_handler sys.excepthook = exception_handler
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) p = Path(__file__)
p = p.parent / ".."
p = p.resolve()
sys.path.append(p)
from py_func import main from py_func import main

View File

@@ -1,4 +1,10 @@
import random
import os
import sys import sys
import time
import platform
import math
import json
import traceback import traceback
from interpreter.utils.jrpc import JsonRpcSrv from interpreter.utils.jrpc import JsonRpcSrv
from interpreter.utils.tum_except import ETUMRuntimeError, print_exception from interpreter.utils.tum_except import ETUMRuntimeError, print_exception
@@ -35,6 +41,22 @@ class FuncHandler(JsonRpcSrv):
return { return {
"error": f"bad jrpc req handler 'func_call' arguments ({"\n".join(tb.splitlines())}). To be reported to testium support team." "error": f"bad jrpc req handler 'func_call' arguments ({"\n".join(tb.splitlines())}). To be reported to testium support team."
} }
if method == "eval":
try:
value = params["value"]
try:
res = eval(value)
return {"result": res}
except Exception as e:
# eval can crash
return {
"error": f"Evaluation of '{value}' failed with message:\n "+str(e)
}
except Exception as e:
tb = traceback.format_exc()
return {
"error": f"bad jrpc req handler 'eval' arguments ({"\n".join(tb.splitlines())}). To be reported to testium support team."
}
else: else:
return { return {
"error": f"unknown RPC request ({method}). To be reported to testium support team." "error": f"unknown RPC request ({method}). To be reported to testium support team."

View File

@@ -19,10 +19,10 @@
name: Check condition on existing variable (PASS) name: Check condition on existing variable (PASS)
key: $(test)_PASS key: $(test)_PASS
values: values:
- $(fn_Dummy_int) > 1 - $(pfn_Dummy_int) > 1
- check: - check:
name: Check condition on existing variable (FAIL) name: Check condition on existing variable (FAIL)
key: $(test)_FAIL key: $(test)_FAIL
values: values:
- '"tailor" in "$(fn_Dummy_str)"' - '"tailor" in "$(pfn_Dummy_str)"'

View File

@@ -16,17 +16,17 @@
- $(loop_param) - $(loop_param)
exit_condition: exit_condition:
value: "$(fn_Echo function) > 3" value: $| $(pfn_Echo function) > 3 |
- let: - let:
name: let name: let
key: $(test)_PASS key: $(test)_PASS
eval: values:
- conditional_exec: "random.randint(1, 2)" - conditional_exec: $| random.randint(1, 2) |
- console: - console:
name: Console creation name: Console creation
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
doc: Opening the console doc: Opening the console
key: $(test)_PASS key: $(test)_PASS
@@ -37,7 +37,7 @@
- console: - console:
name: Console read_until with timeout name: Console read_until with timeout
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
key: $(test)_PASS key: $(test)_PASS
steps: steps:
@@ -45,7 +45,7 @@
- console: - console:
name: Console write name: Console write
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
key: $(test)_PASS key: $(test)_PASS
steps: steps:
@@ -53,12 +53,12 @@
- sleep: - sleep:
name: sleep item name: sleep item
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
timeout: 1 timeout: 1
- console: - console:
name: Console read_until immediate name: Console read_until immediate
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
key: $(test)_PASS key: $(test)_PASS
steps: steps:
@@ -66,7 +66,7 @@
- console: - console:
name: Console read_until immediate (2) name: Console read_until immediate (2)
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
key: $(test)_PASS key: $(test)_PASS
steps: steps:
@@ -74,7 +74,7 @@
- console: - console:
name: Console closure name: Console closure
condition: "$(conditional_exec) == 1" condition: $| $(conditional_exec) == 1 |
console_name: consname console_name: consname
key: $(test)_PASS key: $(test)_PASS
steps: steps:
@@ -82,5 +82,5 @@
- sleep: - sleep:
name: sleep item name: sleep item
condition: "$(conditional_exec) == 2" condition: $| $(conditional_exec) == 2 |
timeout: 1 timeout: 1

View File

@@ -13,7 +13,7 @@
- group: - group:
name: jsonrpc tests name: jsonrpc tests
condition: "'/jrpces' in r'''$(cn_json rpc echo server)'''" condition: $| '/jrpces' in r'''$(cn_json rpc echo server)''' |
steps: steps:
- console: - console:
name: Start the json rpc echo server name: Start the json rpc echo server

View File

@@ -12,8 +12,7 @@
name: Let it be name: Let it be
values: values:
it: $(loop_param) it: $(loop_param)
eval: be: $| $(loop_param) == $(it) |
- be: "$(loop_param) == $(it)"
- loop: - loop:
name: Cycle iterating on list name: Cycle iterating on list
@@ -31,8 +30,7 @@
name: Let it be name: Let it be
values: values:
- it: $(loop_param) - it: $(loop_param)
eval: - be: $| $(loop_param) == $(it) |
- be: "$(loop_param) == $(it)"
- let: - let:
name: Get time name: Get time
@@ -45,8 +43,8 @@
- let: - let:
name: Get parameter file value name: Get parameter file value
key: $(test)_PASS key: $(test)_PASS
eval: values:
- test_overwrite_me: "$(overwrite_me) == True" - test_overwrite_me: $| $(overwrite_me) == True |
- py_func: - py_func:
name: Check global dic pass name: Check global dic pass
@@ -85,8 +83,8 @@
- let: - let:
name: Evaluate Overwriting parameter file value name: Evaluate Overwriting parameter file value
key: $(test)_PASS key: $(test)_PASS
eval: values:
- test_overwrite_me: '"$(overwrite_me)" == True' - test_overwrite_me: $| "$(overwrite_me)" == True |
- check: - check:
name: Check Overwriting parameter file value name: Check Overwriting parameter file value

View File

@@ -13,14 +13,14 @@ main:
- let: - let:
name: Set test variables for Linux name: Set test variables for Linux
condition: "'$(os)' == 'Linux'" condition: $| "$(os)" == "Linux" |
values: values:
- terminal_prompt: $(linux_prompt) - terminal_prompt: $(linux_prompt)
- psep: "/" - psep: "/"
- let: - let:
name: Set test variables for Windows name: Set test variables for Windows
condition: "'$(os)' == 'Windows'" condition: $| "$(os)" == "Windows" |
values: values:
- terminal_prompt: $(windows_prompt) - terminal_prompt: $(windows_prompt)
- psep: "\\" - psep: "\\"