Add context_id to py_func and lua_func for shared persistent subprocess

- py_func and lua_func items accept a context_id parameter; items sharing
  the same id reuse the same subprocess for the duration of the test run
- Subprocess-side tm.setgd/tm.gd use a local fallback dict for non-JSON-
  serializable values (py_func only); serializable values reach the main
  process global dict and are accessible from any test item or subprocess
- Shared subprocess engines are cleaned up in process.py finally block
- LuaProcessBase gains is_alive() (was missing, broke all lua_func items)
- Validation tests cover serializable sharing across different context ids,
  non-serializable sharing within the same context_id, and cross-item access
- RST documentation updated for both py_func and lua_func items

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 16:02:36 +02:00
parent 49354b8664
commit d92f518e1e
12 changed files with 390 additions and 52 deletions

View File

@@ -39,11 +39,14 @@ The ``lua_func`` test item is of the form:
Beside common test items attributes, lua_func item has specific attribute, some of which being mandatory.
* ``file``: the script file name that contains the function to be executed.
Only python script format is supported.
Only Lua script format is supported.
* ``func_name``: The function name to be executed.
* ``param``: This is a list of parameters that are passed to the function
in the order they are presented in the script. These parameters are not
mandatory and are highly dependent of the function prototype.
* ``context_id``: Optional. When set, all ``lua_func`` items sharing the same
``context_id`` value run inside the same persistent Lua subprocess for the
duration of the test. See :ref:`lua_func context<sec_lua_func_context>` for details.
.. code-block:: yaml
:caption: ``lua_func`` test item example of usage
@@ -56,16 +59,71 @@ Beside common test items attributes, lua_func item has specific attribute, some
- $(my_param)
The result of the function (after eventual post treatment) is stored in the global
variable named ``pfn_<func_name>``
variable named ``lfn_<item_name>``
(See :ref:`global variables<sec_global_variables>` for more detail
on how to access to global variables from test items and scripts).
In the example above, the global variable ``$(lfn_activity)``
would be created at the end of the item execution. It would contain the resulting
value of the funcToBeExecuted python function.
value of the methodName Lua function.
The ``lua_func`` will always result ``PASS``, except if the called function raises
and exception or if the ``expected_result`` attribute is used.
an exception or if the ``expected_result`` attribute is used.
.. _sec_lua_func_context:
Sharing state between ``lua_func`` calls
------------------------------------------
Each ``lua_func`` item without a ``context_id`` runs in a dedicated subprocess that
is started and stopped around the call. Module-level variables are not preserved
between two such items.
Inside a ``lua_func`` script, the ``tm`` module exposes ``tm.setgd`` and ``tm.gd``
to read and write the testium global dictionary of the test process. Values stored
this way are accessible from any subsequent test item without requiring a shared
subprocess.
.. code-block:: lua
:caption: sharing a value via the global dictionary
local tm = require("tm")
local module = {}
function module.produce(val)
tm.setgd("my_shared_value", val)
return val
end
function module.consume()
return tm.gd("my_shared_value")
end
return module
When ``context_id`` is set, all ``lua_func`` items that share the same identifier
reuse the same persistent subprocess. This allows Lua-side state (upvalues, module
cache) to be retained across calls beyond what ``tm.setgd`` persists.
.. code-block:: yaml
:caption: ``lua_func`` items sharing a persistent subprocess
- lua_func:
name: produce value
file: my_script.lua
func_name: produce
context_id: my_context
param:
- hello
- lua_func:
name: consume value
file: my_script.lua
func_name: consume
context_id: my_context
expected_result: hello
The shared subprocess is automatically stopped at the end of the test run.
**Lua Interpreter environment setup**

View File

@@ -89,6 +89,9 @@ some of which being mandatory.
* ``param``: This is a list of parameters that are passed to the function
in the order they are presented in the script. These parameters are not
mandatory and are highly dependent of the function prototype.
* ``context_id``: Optional. When set, all ``py_func`` items sharing the same
``context_id`` value run inside the same persistent Python subprocess for the
duration of the test. See :ref:`py_func context<sec_py_func_context>` for details.
.. code-block:: yaml
:caption: ``py_func`` test item example of usage
@@ -111,6 +114,86 @@ value of the funcToBeExecuted python function.
The ``py_func`` will always result ``PASS``, except if the called function raises
and exception or if the ``expected_result`` attribute is used.
.. _sec_py_func_context:
Sharing state between ``py_func`` calls
------------------------------------------
Each ``py_func`` item without a ``context_id`` runs in a dedicated subprocess that
is started and stopped around the call. State cannot be shared between two such
items using module-level variables.
Two mechanisms are available to share data across calls:
**Using the testium global dictionary**
Inside a ``py_func`` script, the ``tm`` module exposes ``tm.setgd`` and ``tm.gd``
to read and write the testium global dictionary of the test process. Values stored
this way are accessible from any subsequent test item (including other ``py_func``
items) without requiring a shared subprocess.
.. code-block:: python
:caption: sharing a serializable value via the global dictionary
import py_func.tm as tm
def produce(val):
tm.setgd("my_shared_value", val)
return val
def consume():
return tm.gd("my_shared_value", None)
Values stored with ``tm.setgd`` must be JSON-serializable (str, int, float, list,
dict, bool, None). Non-serializable values (objects, connections, file handles…)
are handled transparently by the local fallback described below.
**Using a shared persistent subprocess (``context_id``)**
When ``context_id`` is set, all ``py_func`` items that share the same identifier
reuse the same subprocess. The subprocess is kept alive until the end of the test.
This is required for non-JSON-serializable objects (e.g. a socket connection, a
device handle). Calling ``tm.setgd`` with such a value stores it inside the
subprocess local dictionary instead of sending it to the main process. It can then
be retrieved with ``tm.gd`` from any subsequent call that runs in the same subprocess.
.. code-block:: python
:caption: sharing a non-serializable object via ``context_id``
import py_func.tm as tm
class _Connection: # not JSON-serializable
def __init__(self):
self.value = "open"
def open_connection():
tm.setgd("conn", _Connection()) # stored locally in the subprocess
return "ok"
def use_connection():
conn = tm.gd("conn") # retrieved from the subprocess local dict
return conn.value
.. code-block:: yaml
:caption: ``py_func`` items sharing a persistent subprocess
- py_func:
name: open connection
file: my_script.py
func_name: open_connection
context_id: my_context
expected_result: ok
- py_func:
name: use connection
file: my_script.py
func_name: use_connection
context_id: my_context
expected_result: open
The shared subprocess is automatically stopped at the end of the test run.
**Python Interpreter environment setup**
Some global variables have an impact on the ``py_func`` test item behavior:

Binary file not shown.