From b1a7dac0f32607133b000d2b106cc82ed0d9d0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 29 May 2026 11:14:42 +0200 Subject: [PATCH] item params: migrate every structured item to declarative PARAMS Migrates the remaining test items to the ParamSet/Param declaration introduced in d0721af: - dialogs: image, question, value, choices, tested_references - actions: check, run, report - console: parent + open/read_until actions - py_func / lua_func - containers: group, parallel + parallel_branch, unittest - complex: cycle (sub-block exit_condition documented in EXIT_CONDITION_PARAMS), git - runtime_plot: parent + open/close/periodic/last_value actions - json_rpc: parent + query/receive actions Items intentionally without PARAMS (and therefore not validated) are those whose body is the unstructured user value: console write/writeln, plot add/export, and the json_rpc/console open & close actions. Same for the internally-instantiated TestItemUnittestElement which passes dict_item=None. Behavior on valid .tum files is unchanged (validation suite source mode: SUCCESS). Typos on declared params now surface as warnings listing the accepted names; missing required params surface as load- time errors with file context. Co-Authored-By: Claude Sonnet 4.6 --- .../interpreter/test_items/test_item_check.py | 11 ++++ .../test_items/test_item_choices_dialog.py | 14 +++++ .../test_items/test_item_console.py | 52 +++++++++++++++++++ .../interpreter/test_items/test_item_cycle.py | 27 ++++++++++ .../interpreter/test_items/test_item_git.py | 8 +++ .../interpreter/test_items/test_item_group.py | 7 +++ .../test_items/test_item_image_dialog.py | 12 +++++ .../test_items/test_item_json_rpc/__init__.py | 38 ++++++++++++++ .../test_items/test_item_lua_func.py | 16 ++++++ .../test_items/test_item_parallel.py | 16 ++++++ .../test_items/test_item_py_func.py | 16 ++++++ .../test_items/test_item_question_dialog.py | 9 ++++ .../test_items/test_item_report.py | 8 +++ .../interpreter/test_items/test_item_run.py | 20 +++++++ .../test_items/test_item_runtime_plot.py | 44 ++++++++++++++++ .../test_items/test_item_tested_references.py | 12 +++++ .../test_items/test_item_unittest.py | 10 ++++ .../test_items/test_item_value_dialog.py | 14 +++++ 18 files changed, 334 insertions(+) diff --git a/src/testium/interpreter/test_items/test_item_check.py b/src/testium/interpreter/test_items/test_item_check.py index a7bae10..159bf9a 100644 --- a/src/testium/interpreter/test_items/test_item_check.py +++ b/src/testium/interpreter/test_items/test_item_check.py @@ -5,11 +5,22 @@ from runtime.tum_except import ETUMSyntaxError, item_load_context import api.testium as tm from interpreter.utils.constants import TestItemType as cst from interpreter.utils.eval import evaluate +from interpreter.utils.param_decl import Param, ParamSet, LIST class TestItemCheckValue(TestItem): """check item usage. check usage:{check: {name: check my func output, steps: ['$(pfn_echo) < 5']}} """ + + PARAMS = ParamSet( + Param("values", kind=LIST, required=True, + doc="List of expressions to evaluate. Each is expanded then " + "evaluated; non-truthy results fail the check."), + # 'steps' is intentionally not redeclared here — it's the deprecated + # alias of 'values' and is already accepted by COMMON_PARAMS for + # container items. A runtime warning is emitted when 'steps' is used. + ) + def __init__(self, dict_item, parent = None, status_queue=None, filename=""): self._name = cst.TYPE_CHECK.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_choices_dialog.py b/src/testium/interpreter/test_items/test_item_choices_dialog.py index 32d32e0..e5f92b4 100644 --- a/src/testium/interpreter/test_items/test_item_choices_dialog.py +++ b/src/testium/interpreter/test_items/test_item_choices_dialog.py @@ -2,11 +2,25 @@ from interpreter.test_items.test_item import test_run from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_item_dialog_base import TestItemDialogBase, _is_text_mode, _is_interactive from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet, BLOCK from runtime.tum_except import item_load_context import api.testium as tm class TestItemChoicesDialog(TestItemDialogBase): + + PARAMS = ParamSet( + Param("question", required=True, + doc="Prompt shown above the list of choices."), + Param("choices", kind=BLOCK, required=True, + doc="Tree of choices: either a list of strings, or a nested " + "mapping {label: subchoices, ...} to build a multi-level menu."), + Param("icon", default=None, + doc="Default icon name shown next to each choice."), + Param("auto_result", default=None, + doc="Batch-mode selection (path or label). None ⇒ FAILURE."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): self._name = cst.TYPE_CHOICES_DLG.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_console.py b/src/testium/interpreter/test_items/test_item_console.py index f924d97..dfdba53 100644 --- a/src/testium/interpreter/test_items/test_item_console.py +++ b/src/testium/interpreter/test_items/test_item_console.py @@ -10,6 +10,7 @@ from interpreter.test_items.test_item import test_run from interpreter.test_items.item_actions import TestItemActions from interpreter.test_items.item_actions.action import TestItemAction from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet from interpreter.test_items.test_result import TestResult, TestValue @@ -21,6 +22,38 @@ class TestItemConsoleAction(TestItemAction): class TestItemConsoleOpen(TestItemConsoleAction): + + PARAMS = ParamSet( + Param("protocol", required=True, + doc="Transport: 'telnet', 'ssh', 'rawtcp', 'serial' or 'terminal'."), + Param("write_delay", default=0, + doc="Inter-character write delay in ms (slow devices)."), + Param("log", doc="Path to a log file capturing the console traffic."), + Param("overwrite_log", default=True, + doc="If true, truncate the log file at open; else append."), + # telnet + Param("telnet_host", doc="Hostname/IP for the telnet target."), + Param("telnet_port", default=69, doc="TCP port for telnet."), + # ssh + Param("ssh_host", doc="Hostname/IP for the SSH target."), + Param("ssh_user", doc="SSH login user."), + Param("ssh_pwd", doc="SSH password (if key-based auth is not used)."), + # rawtcp + Param("tcp_host", doc="Hostname/IP for a raw-TCP connection."), + Param("tcp_port", doc="TCP port for a raw-TCP connection."), + # serial + Param("serial_port", doc="Serial device path (e.g. /dev/ttyUSB0 or COM3)."), + Param("serial_baudrate", doc="Serial baudrate."), + Param("buffered", default=True, + doc="If true, the serial console buffers received bytes between reads."), + # terminal + Param("terminal_path", + doc="Working directory for the local terminal protocol."), + Param("shell", + doc="Shell command used for the local terminal protocol " + "(default: 'cmd.exe' on Windows, '/usr/bin/env bash' elsewhere)."), + ) + def __init__( self, action_name, dict_item, parent=None, status_queue=None, filename="" ): @@ -283,6 +316,17 @@ class TestItemConsoleWriteLn(TestItemConsoleAction): class TestItemConsoleReadUntil(TestItemConsoleAction): + + PARAMS = ParamSet( + Param("expected", required=True, + doc="Regex matched against incoming console output until found " + "or until timeout."), + Param("timeout", default=-1, + doc="Seconds before giving up. Negative means infinite."), + Param("mute", default=False, + doc="If true, don't echo received bytes to testium's stdout/log."), + ) + def __init__( self, action_name, dict_item, parent=None, status_queue=None, filename="" ): @@ -336,6 +380,14 @@ class TestItemConsoleReadUntil(TestItemConsoleAction): class TestItemConsole(TestItemActions): + + PARAMS = ParamSet( + Param("console_name", required=True, + doc="Identifier of the console — used by every nested action to " + "reach back the same transport. Multiple consoles can coexist " + "as long as their names differ."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): super().__init__( cst.TYPE_CONSOLE, dict_item, parent, status_queue, filename=filename diff --git a/src/testium/interpreter/test_items/test_item_cycle.py b/src/testium/interpreter/test_items/test_item_cycle.py index c04fc8e..bc8ccb8 100644 --- a/src/testium/interpreter/test_items/test_item_cycle.py +++ b/src/testium/interpreter/test_items/test_item_cycle.py @@ -8,9 +8,36 @@ from interpreter.test_items.test_result import TestResult, TestValue import api.testium as tm from interpreter.utils.params import TestItemParams from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet, BLOCK + + +# Sub-block validation: 'cycle' accepts an 'exit_condition:' mapping whose +# own params are reported here so unknown keys inside it can be flagged +# during a future Block-aware diagnostic pass. For now the parent only +# declares that 'exit_condition' is an accepted top-level key. +EXIT_CONDITION_PARAMS = ParamSet( + Param("time", doc="HH:MM time of day after which the loop exits."), + Param("value", doc="Expression; when truthy the loop exits."), + Param("file", doc="Python file containing the exit-condition function."), + Param("func_name", doc="Function name in 'file' returning the exit value."), + Param("param", doc="Arguments passed to the exit function."), + Param("eval", default="", + doc="Post-evaluation expression applied to the function's return."), +) class TestItemCycle(TestItem): + + PARAMS = ParamSet( + Param("iterator", + doc="Iterable (or string expanding to one) driving the loop. " + "The current value is exposed as $(loop_param)."), + Param("exit_condition", kind=BLOCK, + doc="Optional block stopping the loop early: combine 'time', " + "'value', or a 'file'+'func_name' pair (with optional " + "'param' and 'eval')."), + ) + def __init__(self, dict_cycle, parent=None, status_queue=None, filename=""): self._name = cst.TYPE_CYCLE.item_name super().__init__(dict_cycle, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_git.py b/src/testium/interpreter/test_items/test_item_git.py index 7486777..e3ec57d 100644 --- a/src/testium/interpreter/test_items/test_item_git.py +++ b/src/testium/interpreter/test_items/test_item_git.py @@ -1,6 +1,7 @@ from interpreter.test_items.test_item import (TestItem, test_run) from interpreter.test_items.test_result import (TestValue) from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet, LIST from runtime.tum_except import ETUMParamError, ETUMSyntaxError import interpreter.utils.version as git @@ -8,6 +9,13 @@ class TestItemGit(TestItem): """ This item expect only one parameter which is a string or list of string being the path to the git folder """ + + PARAMS = ParamSet( + Param("repo", kind=LIST, required=True, + doc="Path to a git checkout, or list of such paths. Each is " + "reported with its current version (tag + dirty state)."), + ) + def __init__(self, dict_item, parent = None, status_queue=None, filename=""): self._name = cst.TYPE_GIT.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_group.py b/src/testium/interpreter/test_items/test_item_group.py index e288945..5a8d2f0 100644 --- a/src/testium/interpreter/test_items/test_item_group.py +++ b/src/testium/interpreter/test_items/test_item_group.py @@ -1,10 +1,17 @@ from interpreter.test_items.test_item import (TestItem, test_run) from interpreter.test_items.test_result import (TestResult, TestValue) from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import ParamSet from runtime.tum_except import ETUMSyntaxError import api.testium as tm class TestItemGroup(TestItem): + + # 'group' has no item-specific parameters; 'steps' is handled by COMMON_PARAMS. + # Declaring an empty ParamSet still opts in to unknown-param validation + # (e.g. typo 'stop_on_failures'). + PARAMS = ParamSet() + def __init__(self, dict_cycle, parent = None, status_queue=None, filename=""): self._name = cst.TYPE_GROUP.item_name super().__init__(dict_cycle, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_image_dialog.py b/src/testium/interpreter/test_items/test_item_image_dialog.py index ea726e4..e519d02 100644 --- a/src/testium/interpreter/test_items/test_item_image_dialog.py +++ b/src/testium/interpreter/test_items/test_item_image_dialog.py @@ -4,6 +4,7 @@ from interpreter.test_items.test_item import test_run from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_item_dialog_base import TestItemDialogBase, _is_text_mode, _is_interactive from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet from runtime.tum_except import item_load_context import api.testium as tm @@ -12,6 +13,17 @@ class TestItemImageDialog(TestItemDialogBase): """dialog_image item usage. dialog_image name: Nice image, question: could you press the red button, filename: img.jpg """ + + PARAMS = ParamSet( + Param("question", required=True, + doc="Prompt shown above the image."), + Param("filename", required=True, + doc="Path to the image file (relative to the test directory or absolute)."), + Param("auto_result", default=None, + doc="Outcome used in batch/non-interactive mode. Truthy ⇒ SUCCESS, " + "None ⇒ FAILURE."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): self._name = cst.TYPE_IMAGE_DLG.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_json_rpc/__init__.py b/src/testium/interpreter/test_items/test_item_json_rpc/__init__.py index 38281d5..2ef6732 100644 --- a/src/testium/interpreter/test_items/test_item_json_rpc/__init__.py +++ b/src/testium/interpreter/test_items/test_item_json_rpc/__init__.py @@ -11,6 +11,7 @@ from interpreter.test_items.item_actions.action import TestItemAction from interpreter.utils.constants import TestItemType as cst from interpreter.utils.eval import evaluate +from interpreter.utils.param_decl import Param, ParamSet, BLOCK from interpreter.test_items.test_item_json_rpc.jsonrpc_adapters import ( JrpcAdapter, @@ -76,6 +77,20 @@ class TestItemJSRPCActionClose(TestItemAction): class TestItemJSRPCActionQuery(TestItemAction): + PARAMS = ParamSet( + Param("method", required=True, + doc="JSON-RPC method name to call."), + Param("params", + doc="Parameters payload (list, dict or scalar) sent to the method."), + Param("id", default="rand", + doc="JSON-RPC request id. 'rand' (default) ⇒ a random integer is used."), + Param("no_wait", default=False, + doc="If true, send the request without waiting for a response."), + Param("timeout", default=None, + doc="Seconds to wait for a response. None ⇒ inherits the transport " + "default."), + ) + def __init__( self, action_name, dict_item, parent=None, status_queue=None, filename="" ): @@ -129,6 +144,13 @@ class TestItemJSRPCActionQuery(TestItemAction): class TestItemJSRPCActionReceive(TestItemAction): + PARAMS = ParamSet( + Param("id", required=True, + doc="JSON-RPC request id whose response we expect."), + Param("timeout", default=None, + doc="Seconds to wait for the response. None ⇒ transport default."), + ) + def __init__( self, action_name, dict_item, parent=None, status_queue=None, filename="" ): @@ -172,6 +194,22 @@ class TestItemJSON_RPC(TestItemActions): This item TBD """ + PARAMS = ParamSet( + Param("console", kind=BLOCK, + doc="Console-transport block: {console_name, …}. Either 'console' " + "or 'udp' must be set."), + Param("udp", kind=BLOCK, + doc="UDP-transport block: {host, port, …}. Either 'console' or " + "'udp' must be set."), + Param("version", default="1.0", + doc="JSON-RPC protocol version ('1.0' or '2.0')."), + Param("timeout", required=True, + doc="Default seconds to wait for a JSON-RPC response across all " + "child query/receive actions."), + Param("mute", default=False, + doc="If true, don't echo wire traffic to the log."), + ) + def __init__( self, dict_item: dict, parent: TestItem = None, status_queue=None, filename="" ): diff --git a/src/testium/interpreter/test_items/test_item_lua_func.py b/src/testium/interpreter/test_items/test_item_lua_func.py index b6d227e..885aef6 100644 --- a/src/testium/interpreter/test_items/test_item_lua_func.py +++ b/src/testium/interpreter/test_items/test_item_lua_func.py @@ -11,6 +11,7 @@ import api.testium as tm from interpreter.utils.lua_func_exec import LuaFuncExecEngine from interpreter.utils.api_srv import api_request from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet, LIST _LUA_FUNC_CONTEXTS_KEY = "_lua_func_contexts" @@ -21,6 +22,21 @@ class TestItemLuaFunc(TestItem): Optional: context_id: — share a persistent process with other lua_func items using the same id. """ + PARAMS = ParamSet( + Param("file", required=True, + doc="Path to the .lua file containing the function."), + Param("func_name", required=True, + doc="Name of the function to call in the file."), + Param("param", kind=LIST, + doc="Arguments passed to the function. Each entry is expanded " + "before the call. Special tokens $(loop_param) / $(loop_index) " + "resolve from the surrounding cycle."), + Param("context_id", default=None, + doc="If set, the lua_func subprocess is kept alive and reused by " + "every other lua_func item with the same context_id — enables " + "shared in-memory state between successive calls."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): self._name = cst.TYPE_LUA_FUNCTION.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_parallel.py b/src/testium/interpreter/test_items/test_item_parallel.py index c7c0163..e0b202c 100644 --- a/src/testium/interpreter/test_items/test_item_parallel.py +++ b/src/testium/interpreter/test_items/test_item_parallel.py @@ -6,6 +6,7 @@ from interpreter.test_items.test_item import test_run from interpreter.test_items.test_result import TestResult, TestValue from interpreter.utils.constants import TestItemType as cst from interpreter.utils.eval import eval_to_boolean +from interpreter.utils.param_decl import Param, ParamSet, LIST, BLOCK, Enum from runtime.tum_except import ETUMSyntaxError from runtime.string_queue import StringQueue from runtime.stdout_redirect import stdio_redir @@ -15,6 +16,12 @@ class TestItemParallelBranch(TestItemContainer): """One branch of a parallel item. Runs its children sequentially, optionally waiting for a condition before starting.""" + PARAMS = ParamSet( + Param("wait_for", kind=BLOCK, + doc="Optional block {condition, timeout} that defers the branch " + "start until the condition is truthy (or the timeout elapses)."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): super().__init__(cst.TYPE_PARALLEL_BRANCH, dict_item, parent, status_queue, filename=filename) self._wait_condition = None @@ -87,6 +94,15 @@ class TestItemParallel(TestItemContainer): - ... """ + PARAMS = ParamSet( + Param("branches", kind=LIST, required=True, + doc="List of branch blocks (each branch holds its own 'steps' " + "and optional 'wait_for')."), + Param("sync", kind=Enum("all", "any"), default="all", + doc="'all' (default) waits for every branch; 'any' returns as " + "soon as the first branch completes."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): branches = dict_item.get("branches", []) if not branches: diff --git a/src/testium/interpreter/test_items/test_item_py_func.py b/src/testium/interpreter/test_items/test_item_py_func.py index 7394439..ccd90c8 100644 --- a/src/testium/interpreter/test_items/test_item_py_func.py +++ b/src/testium/interpreter/test_items/test_item_py_func.py @@ -11,6 +11,7 @@ import api.testium as tm from interpreter.utils.py_func_exec import PyFuncExecEngine from interpreter.utils.api_srv import api_request from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet, LIST _PY_FUNC_CONTEXTS_KEY = "_py_func_contexts" @@ -21,6 +22,21 @@ class TestItemPyFunc(TestItem): Optional: context_id: — share a persistent process with other py_func items using the same id. """ + PARAMS = ParamSet( + Param("file", required=True, + doc="Path to the .py file containing the function."), + Param("func_name", required=True, + doc="Name of the function to call in the file."), + Param("param", kind=LIST, + doc="Arguments passed to the function. Each entry is expanded " + "before the call. Special tokens $(loop_param) / $(loop_index) " + "resolve from the surrounding cycle."), + Param("context_id", default=None, + doc="If set, the py_func subprocess is kept alive and reused by " + "every other py_func item with the same context_id — enables " + "shared in-memory state between successive calls."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): self._name = cst.TYPE_PY_FUNCTION.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_question_dialog.py b/src/testium/interpreter/test_items/test_item_question_dialog.py index 19a393f..ac20adc 100644 --- a/src/testium/interpreter/test_items/test_item_question_dialog.py +++ b/src/testium/interpreter/test_items/test_item_question_dialog.py @@ -2,6 +2,7 @@ from interpreter.test_items.test_item import test_run from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_item_dialog_base import TestItemDialogBase, _is_text_mode, _is_interactive from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet from runtime.tum_except import item_load_context @@ -9,6 +10,14 @@ class TestItemQuestionDialog(TestItemDialogBase): """dialog_question item usage. dialog_question name: Nice question, question: "If OK, press OK, If not, press cancel" """ + + PARAMS = ParamSet( + Param("question", required=True, + doc="Yes/No prompt presented to the user."), + Param("auto_result", default=None, + doc="Batch-mode answer ('yes'/'no' or truthy/falsy). None ⇒ FAILURE."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): self._name = cst.TYPE_QUESTION_DLG.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_report.py b/src/testium/interpreter/test_items/test_item_report.py index ebe7afb..55592ba 100644 --- a/src/testium/interpreter/test_items/test_item_report.py +++ b/src/testium/interpreter/test_items/test_item_report.py @@ -3,9 +3,17 @@ from interpreter.test_items.test_item import (TestItem, test_run) from interpreter.test_items.test_result import (TestValue) from runtime.tum_except import ETUMSyntaxError from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet, LIST from interpreter.test_report.test_report import Export class TestItemReport(TestItem): + + PARAMS = ParamSet( + Param("export", kind=LIST, required=True, + doc="List of exporters to run (junit, sqlite, …). Each entry is a " + "mapping describing the exporter type and its parameters."), + ) + def __init__(self, dict_item, parent = None, status_queue=None, filename=""): self._name = cst.TYPE_REPORT.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_run.py b/src/testium/interpreter/test_items/test_item_run.py index b53b37c..17f470a 100644 --- a/src/testium/interpreter/test_items/test_item_run.py +++ b/src/testium/interpreter/test_items/test_item_run.py @@ -10,6 +10,7 @@ from interpreter.test_items.test_item import (TestItem, test_run) from interpreter.test_items.test_result import (TestValue) import api.testium as tm from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet from runtime.tum_except import ETUMSyntaxError, ETUMRuntimeError, item_load_context @@ -57,6 +58,25 @@ def nowInBetween(start, end): class TestItemRun(TestItem): + + PARAMS = ParamSet( + Param("tum", required=True, + doc="Path to the .tum file launched in a fresh testium instance."), + Param("param_file", default="", + doc="Optional path to a param.yaml passed to the sub-instance."), + Param("log_file", default="", + doc="Path where the sub-instance writes its log."), + Param("report_file", default="", + doc="Path where the sub-instance writes its report."), + Param("start_time", + doc="HH:MM time of day after which the sub-instance may run."), + Param("end_time", + doc="HH:MM time of day after which the sub-instance no longer runs."), + Param("wait_for_exec", + doc="If true, block until the time window opens. Requires both " + "start_time and end_time."), + ) + def __init__(self, dict_item, parent = None, status_queue=None, filename=""): self._name = cst.TYPE_RUN.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_runtime_plot.py b/src/testium/interpreter/test_items/test_item_runtime_plot.py index e6257a2..a25db69 100644 --- a/src/testium/interpreter/test_items/test_item_runtime_plot.py +++ b/src/testium/interpreter/test_items/test_item_runtime_plot.py @@ -11,6 +11,7 @@ from interpreter.test_items.item_actions import TestItemActions from interpreter.test_items.item_actions.action import TestItemAction from interpreter.utils.constants import TestItemType as cst from interpreter.utils.eval import evaluate +from interpreter.utils.param_decl import Param, ParamSet, LIST class TestItemPlotAction(TestItemAction): @@ -21,6 +22,12 @@ class TestItemPlotAction(TestItemAction): class TestItemPlotActionOpen(TestItemPlotAction): + + PARAMS = ParamSet( + Param("log_path", default=None, + doc="Optional file to which the plot data are appended."), + ) + def __init__( self, action_name, dict_item, parent=None, status_queue=None, filename="" ): @@ -57,6 +64,15 @@ class TestItemPlotActionOpen(TestItemPlotAction): class TestItemPlotActionClose(TestItemPlotAction): + + PARAMS = ParamSet( + Param("wait_dialog_exit", default=False, + doc="If true, the close action blocks until the user closes the " + "plot window (or timeout)."), + Param("timeout", default=-1, + doc="Seconds to wait when wait_dialog_exit is true. Negative ⇒ infinite."), + ) + def __init__( self, action_name, dict_item, parent=None, status_queue=None, filename="" ): @@ -96,6 +112,20 @@ class TestItemPlotActionClose(TestItemPlotAction): class TestItemPlotActionPeriodic(TestItemPlotAction): + + PARAMS = ParamSet( + Param("period", required=True, + doc="Seconds between two calls of the periodic function."), + Param("file", required=True, + doc="Path to the .py file holding the periodic function."), + Param("func_name", required=True, + doc="Name of the periodic function."), + Param("param", kind=LIST, + doc="Arguments passed to the periodic function on each call."), + Param("eval", default="", + doc="Post-evaluation applied to the function's return value."), + ) + def __init__( self, action_name, dict_item, parent=None, status_queue=None, filename="" ): @@ -169,6 +199,13 @@ class TestItemPlotActionAdd(TestItemPlotAction): class TestItemPlotActionLastValues(TestItemPlotAction): + + PARAMS = ParamSet( + Param("name", kind=LIST, + doc="List of plot variable names whose last sample is returned. " + "Result is stored in $(plv_) as a dict."), + ) + def __init__(self, action_name, dict_item, parent=None, status_queue=None, filename=""): super().__init__( action_name, cst.TYPE_GRAPH_ACTION, dict_item, parent, status_queue, filename=filename @@ -219,6 +256,13 @@ class TestItemPlotActionExport(TestItemPlotAction): class TestItemPlot(TestItemActions): + + PARAMS = ParamSet( + Param("plot_name", required=True, + doc="Identifier of the plot window — referenced by every nested " + "action and by $(plv_) for last-values output."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): super().__init__( cst.TYPE_GRAPH, dict_item, parent, status_queue, filename=filename diff --git a/src/testium/interpreter/test_items/test_item_tested_references.py b/src/testium/interpreter/test_items/test_item_tested_references.py index 2130e96..5dbf982 100644 --- a/src/testium/interpreter/test_items/test_item_tested_references.py +++ b/src/testium/interpreter/test_items/test_item_tested_references.py @@ -2,11 +2,23 @@ from interpreter.test_items.test_item import test_run from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_item_dialog_base import TestItemDialogBase, _is_text_mode, _is_interactive from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet, LIST from runtime.tum_except import item_load_context import api.testium as tm class TestItemTestedRefsDialog(TestItemDialogBase): + + PARAMS = ParamSet( + Param("question", required=True, + doc="Prompt asking the operator to enter the tested references."), + Param("reference", kind=LIST, + doc="Pre-filled list of references shown in the dialog."), + Param("auto_result", default=None, + doc="Batch-mode outcome: None ⇒ FAILURE, truthy ⇒ SUCCESS with " + "the pre-filled references."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): self._name = cst.TYPE_REFERENCE_DLG.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_unittest.py b/src/testium/interpreter/test_items/test_item_unittest.py index 4300988..53c0fa4 100644 --- a/src/testium/interpreter/test_items/test_item_unittest.py +++ b/src/testium/interpreter/test_items/test_item_unittest.py @@ -11,6 +11,7 @@ from interpreter.test_items.test_item import (TestItem, test_run, LOG_TEST_STOP, from interpreter.test_items.test_result import (TestResult, TestValue) from interpreter.test_items.test_item import test_data from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet, LIST from runtime.stdout_redirect import stdio_redir class UnittestResult(TextTestResult): @@ -95,6 +96,15 @@ class TestItemUnittestElement(TestItem): class TestItemUnittestFile(TestItem): + + PARAMS = ParamSet( + Param("test_file", required=True, + doc="Path to the Python unittest file (TestCase subclass)."), + Param("test_method", kind=LIST, + doc="Optional list of method names to restrict the run to. " + "When empty, every test_* method in the file is run."), + ) + def __init__(self, dict_item, parent = None, status_queue=None, filename=""): self._name = cst.TYPE_UNITTEST.item_name super().__init__(dict_item, parent, status_queue, filename=filename) diff --git a/src/testium/interpreter/test_items/test_item_value_dialog.py b/src/testium/interpreter/test_items/test_item_value_dialog.py index 69de01c..fbc1cca 100644 --- a/src/testium/interpreter/test_items/test_item_value_dialog.py +++ b/src/testium/interpreter/test_items/test_item_value_dialog.py @@ -2,6 +2,7 @@ from interpreter.test_items.test_item import test_run from interpreter.test_items.test_result import TestValue from interpreter.test_items.test_item_dialog_base import TestItemDialogBase, _is_text_mode, _is_interactive from interpreter.utils.constants import TestItemType as cst +from interpreter.utils.param_decl import Param, ParamSet from runtime.tum_except import item_load_context import api.testium as tm @@ -10,6 +11,19 @@ class TestItemValueDialog(TestItemDialogBase): """dialog_value item usage. dialog_value name: Enter value, question: "Which value did you measure?" """ + + PARAMS = ParamSet( + Param("question", required=True, + doc="Prompt shown above the value input field."), + Param("default", default="", + doc="Pre-filled value of the input field."), + Param("auto_result", default=None, + doc="Batch-mode outcome: None ⇒ FAILURE, 'cancel' ⇒ cancelled, " + "any other truthy ⇒ SUCCESS with auto_value."), + Param("auto_value", default=None, + doc="Value used in batch mode when auto_result is set."), + ) + def __init__(self, dict_item, parent=None, status_queue=None, filename=""): self._name = cst.TYPE_VALUE_DLG.item_name super().__init__(dict_item, parent, status_queue, filename=filename)