moved code for coherence
This commit is contained in:
46
src/py_func/__init__.py
Executable file
46
src/py_func/__init__.py
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python
|
||||
import multiprocessing
|
||||
from py_func.tm import _init_api, _remote_print
|
||||
from testium.interpreter.utils.stdout_redirect import stdio_redir
|
||||
|
||||
|
||||
class TcpStdOut:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def write(self, s: str) -> None:
|
||||
_remote_print(s)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
# This line sets the method for the "Process" function. It is required for Linux
|
||||
# support of the test dialogs.
|
||||
multiprocessing.set_start_method('spawn')
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-i", "--ip", type=str, help="Ip address or hostname to listen to",
|
||||
default="localhost")
|
||||
parser.add_argument("-p", "--port", type=int, help="port to listen to",
|
||||
default=9000)
|
||||
parser.add_argument("-t", "--timeout", type=float, help="Timeout waiting for connection",
|
||||
default=10)
|
||||
parser.add_argument("-v", "--verbose", action='store_true', help="port to listen to")
|
||||
args = parser.parse_args()
|
||||
|
||||
thrd_api = _init_api(args.ip, args.port, args.timeout)
|
||||
# redirect I/O
|
||||
outstream = TcpStdOut()
|
||||
stdio_redir.redirect(outstream)
|
||||
# debug the server
|
||||
if args.verbose:
|
||||
thrd_api.dbg_out = stdio_redir.ini_stdout
|
||||
thrd_api.start()
|
||||
try:
|
||||
while thrd_api.is_alive():
|
||||
thrd_api.join(1)
|
||||
finally:
|
||||
stdio_redir.restore()
|
||||
23
src/py_func/__main__.py
Normal file
23
src/py_func/__main__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
def exception_handler(typ_exc, value, trbk):
|
||||
"""Testium Exception handling"""
|
||||
print("An unmanaged exception occured", exc_info=(typ_exc, value, trbk))
|
||||
print(f"Critical failure : '{value}'.")
|
||||
tb = traceback.format_exception(typ_exc, value, trbk)
|
||||
print("".join(tb))
|
||||
|
||||
sys.excepthook = exception_handler
|
||||
|
||||
p = Path(__file__)
|
||||
p = p.parent / ".."
|
||||
p = p.resolve()
|
||||
|
||||
sys.path.append(p)
|
||||
|
||||
from py_func import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
73
src/py_func/func_call.py
Normal file
73
src/py_func/func_call.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import sys
|
||||
import importlib.util
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
import importlib
|
||||
import traceback
|
||||
|
||||
from testium.interpreter.utils.tum_except import ETUMRuntimeError, ETUMSyntaxError
|
||||
from py_func import tm
|
||||
|
||||
|
||||
def abs_path_from_file(file):
|
||||
abs_file_path = Path(file)
|
||||
if not abs_file_path.is_absolute():
|
||||
tdir = tm.gd("test_directory")
|
||||
abs_file_path = Path(tdir) / abs_file_path
|
||||
abs_file_path = abs_file_path.resolve()
|
||||
return abs_file_path
|
||||
|
||||
|
||||
def func_module(file):
|
||||
abs_file_path = abs_path_from_file(file)
|
||||
|
||||
if not abs_file_path.is_file():
|
||||
raise ETUMSyntaxError(f'"{abs_file_path}" file could not be found')
|
||||
|
||||
try:
|
||||
sys.path.append(str(abs_file_path.parent))
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
abs_file_path.stem,
|
||||
abs_file_path
|
||||
)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
except:
|
||||
tb = traceback.format_exc()
|
||||
raise ETUMRuntimeError("Error importing file.\n" + "\n".join(tb.splitlines()))
|
||||
|
||||
return module
|
||||
|
||||
|
||||
def func_exec(file: str, func_name: str, params: list, verbose: bool=True):
|
||||
"""Executes a python function and returns its result and reported values
|
||||
"""
|
||||
reported_values = {}
|
||||
mod = func_module(file)
|
||||
if verbose:
|
||||
print("Function executed from '{}'".format(
|
||||
inspect.getabsfile(mod)))
|
||||
|
||||
# check of the FunctionItem descendants
|
||||
fitems = []
|
||||
for name, cls in inspect.getmembers(mod):
|
||||
if inspect.isclass(cls):
|
||||
if issubclass(cls, tm.FunctionItem):
|
||||
fitems.append(cls)
|
||||
|
||||
oldstyle = True
|
||||
if len(fitems) > 0:
|
||||
for fitem in fitems:
|
||||
if fitem.__name__ == func_name:
|
||||
oldstyle = False
|
||||
o = fitem()
|
||||
res = o.exec(*params)
|
||||
reported_values = o.reportedValues()
|
||||
|
||||
if oldstyle:
|
||||
res = getattr(mod, func_name)(*params)
|
||||
|
||||
reported_values.update({'returned': res})
|
||||
|
||||
return res, reported_values
|
||||
68
src/py_func/handle.py
Normal file
68
src/py_func/handle.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import random
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
import math
|
||||
import json
|
||||
import traceback
|
||||
from testium.interpreter.utils.jrpc import JsonRpcSrv
|
||||
from testium.interpreter.utils.tum_except import ETUMRuntimeError, print_exception
|
||||
from py_func.func_call import func_exec
|
||||
|
||||
|
||||
class FuncHandler(JsonRpcSrv):
|
||||
|
||||
def handle_request(self, method, params):
|
||||
try:
|
||||
if method == "func_call":
|
||||
try:
|
||||
file = params["file"]
|
||||
fname = params["fname"]
|
||||
args = params["params"]
|
||||
verb = params["verbose"]
|
||||
try:
|
||||
res, reported_values = func_exec(file, fname, args, verb)
|
||||
return {
|
||||
"result": {
|
||||
"returned_value": res,
|
||||
"reported_values": reported_values,
|
||||
}
|
||||
}
|
||||
except TypeError as e:
|
||||
return {
|
||||
"error": f'In file "{file}",\ncalling function "{fname}" with bad arguments ({args}).\nMessage is "{str(e)}"'
|
||||
}
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
return {"error": "\n".join(tb.splitlines())}
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
return {
|
||||
"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:
|
||||
return {
|
||||
"error": f"unknown RPC request ({method}). To be reported to testium support team."
|
||||
}
|
||||
except:
|
||||
print_exception(str(*sys.exc_info()))
|
||||
raise ETUMRuntimeError(
|
||||
"python Function item execution error. To be reported to testium support team."
|
||||
)
|
||||
101
src/py_func/tm.py
Normal file
101
src/py_func/tm.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""tm — proxy module exposing remote-callable API functions.
|
||||
|
||||
This module dynamically exposes functions listed in
|
||||
``interpreter.utils.api.SUPPORTED_API``. Each exposed function is a
|
||||
thin wrapper that forwards the call to a running ``FuncHandler``
|
||||
instance (stored in ``_func_call_thread``).
|
||||
|
||||
Typical usage:
|
||||
>>> from testium.py_func import tm
|
||||
>>> handler = tm._init_api(port)
|
||||
>>> tm.some_api_function(args)
|
||||
|
||||
Only after ``_init_api`` has been called will API functions be able to
|
||||
send requests to the remote handler; otherwise an ``ETUMRuntimeError``
|
||||
is raised.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from py_func.handle import FuncHandler
|
||||
from testium.interpreter.utils.tum_except import ETUMRuntimeError
|
||||
from testium.interpreter.utils.api import SUPPORTED_API
|
||||
|
||||
thismodule = sys.modules[__name__]
|
||||
# Shared FuncHandler instance used to forward API calls. Remains None
|
||||
# until `_init_api` is invoked.
|
||||
_func_call_thread = None
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Dynamically create module-level functions for each supported API name.
|
||||
# Each generated function shares the implementation of `api_call` but
|
||||
# has a distinct name used as the remote action identifier.
|
||||
def _make_api(name):
|
||||
def _wrapper(*params):
|
||||
if _func_call_thread is not None:
|
||||
res = _func_call_thread.call(name, params)
|
||||
if "result" in res:
|
||||
ret_val = res["result"]
|
||||
elif "error" in res:
|
||||
raise ETUMRuntimeError(f"api call to 'tm.{name}' failed with error '{res["error"]}'")
|
||||
else:
|
||||
raise ETUMRuntimeError("api call failure in jrpc client to be reported to testium support team.")
|
||||
return ret_val
|
||||
else:
|
||||
raise ETUMRuntimeError("api not initialized")
|
||||
_wrapper.__name__ = name
|
||||
return _wrapper
|
||||
|
||||
for k in SUPPORTED_API:
|
||||
setattr(thismodule, k, _make_api(k))
|
||||
|
||||
def _init_api(host, port, timeout):
|
||||
"""Start and initialize the remote function handler.
|
||||
|
||||
Starts a ``FuncHandler`` bound to ``port``, runs it and blocks until
|
||||
it signals readiness.
|
||||
|
||||
Args:
|
||||
port: port number or identifier passed to ``FuncHandler``.
|
||||
|
||||
Returns:
|
||||
The initialized ``FuncHandler`` instance assigned to
|
||||
``_func_call_thread``.
|
||||
"""
|
||||
global _func_call_thread
|
||||
_func_call_thread = FuncHandler(host, port, timeout=timeout)
|
||||
return _func_call_thread
|
||||
|
||||
|
||||
###############################################################################
|
||||
def _remote_print(*values):
|
||||
"""Forward print-like output to the remote handler.
|
||||
|
||||
If a ``_func_call_thread`` is available, this function calls the
|
||||
handler with action name ``"print"`` and the provided values. Errors
|
||||
during forwarding are ignored because printing is best-effort.
|
||||
"""
|
||||
if _func_call_thread is not None:
|
||||
try:
|
||||
_func_call_thread.call("print", values)
|
||||
except:
|
||||
# Best-effort: ignore forwarding failures
|
||||
pass
|
||||
|
||||
|
||||
###############################################################################
|
||||
class FunctionItem():
|
||||
"""Class allowing extended capabilities of function."""
|
||||
module_count = 0
|
||||
|
||||
def __init__(self):
|
||||
self._reported_value = {}
|
||||
|
||||
def reportValue(self, key, value):
|
||||
self._reported_value[key] = value
|
||||
|
||||
def reportedValues(self):
|
||||
return self._reported_value
|
||||
|
||||
def exec(self):
|
||||
pass
|
||||
Reference in New Issue
Block a user