User-defined tools¶
To use a user-defined tool, its Python module must be saved in the Utilities directory
(as specified in the menu).
A file __init__.py needs to exist in the same directory as the Python module.
The user-defined tool can then be configured in the test bench configuration and the Tooljobs defined in the tool can be inserted into the test case. The specific structure of a user-defined tool is described in the API documentation api/tools/usertool-api/usertool-api.html.
Migration from UserToolAdapter¶
The deprecated UserToolAdapter is no longer supported. To upgrade to a UserTool, please see the UserTool migration help.
Example implementations¶
Various example implementations of user-defined tools can be found in the subdirectory
Templates/DefaultData/Utilities/ExampleUserTools/ of the ecu.test installation directory:
BasicExampleUserTool.pyBasic example of a UserTools including a UserJobs to implement dynamic domains of job parameters.ExampleUserToolBasePort.pyExample of a UserTool with Base port by implementing the UserPort interface.ExampleUserToolImagePort.pyExample of a UserTool with Image port by implementing the interfaces UserImagePort and UserTouchInput.ExampleUserToolModelPort.pyExample of a UserTool with Model port by implementing the interfaces UserModelPort, UserRecorder and UserSimulationManager. By implementing a model port, the model-based bus port will be available for the tool.
The minimal example from BasicExampleUserTool.py contains the following code:
"""
This module contains an example of a user-defined Tool. The source code of this example
can be found in the ecu.test installation directory under ``Templates/DefaultData/Utilities``.
A user-defined Tool must be placed in the Utilities directory of the current workspace. If a Tool
is changed, it is necessary to stop the current test bench (if it uses the Tooladapter) and
select *Extras -> Update user libraries* in ecu.test.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, TypedDict, NotRequired, Any, Literal
if TYPE_CHECKING:
from collections.abc import Callable
# Required so that ecu.test can discover this module and recognize it as a user tool.
MODULE_TYPE = 'USER_TOOL'
class ExampleUserTool:
def __init__(self, properties: dict[str, str | bool]) -> None:
"""
This method is called on tool start (test bench start, manual start in
configuration window). It can be used to start any external tool needed by this
ToolAdapter. Use `pass` if no external tool is used.
:param properties: Dictionary with the properties from the test bench configuration for
this tool and the configured values. The possible properties are
defined by the return value of the :func:`GetProperties` method.
"""
self._properties = properties
logging.getLogger('ecu.test').info(f'{self.GetToolName()} initialized')
@classmethod
def GetToolName(cls) -> str:
"""
The name of the tool. This name is displayed in the test bench configuration.
"""
return 'ExampleUserTool'
@classmethod
def GetJobs(cls) -> list[Callable]:
"""
Returns a list of methods that should be registered on this tool as jobs.
"""
return [cls.ExampleJob, DynamicJob]
@classmethod
def GetProperties(cls) -> dict[str, dict]:
"""
Properties of the tool. These properties can be configured in the test bench configuration.
The configured properties are passed to the `__init__` method when the configuration is
started. Available property types are STRING, TEXT, BOOL and CHOICE. Properties may be
sorted into groups to improve visual separation in the test bench configuration.
Informational properties can be marked readonly.
For BOOL and CHOICE properties, it is possible to define sub properties that are only
available for a given property value.
"""
return {
'Option 1': {
'Description': 'This is Option 1',
'Type': 'STRING',
'Default': 'default value',
},
'Group': {
'Option 2': {
'Description': 'This is Option 2',
'Type': 'BOOL',
'Default': True,
},
'Option 3': {
'Description': 'This is Option 3',
'Type': 'CHOICE',
'Choices': ['c1', 'c2', 'c3'],
'Default': 'c2',
},
'Nested group': {
'Option 4': {
'Description': 'This is Option 4',
'Type': 'STRING',
'Default': '4',
},
},
},
'Group with sub properties': {
'Option 5': {
'Description': 'This BOOL option has a sub property',
'Type': 'BOOL',
'Default': True,
'Children': {
'Sub option 5.1': {
'Description': 'This option is visible if Option 5 is checked',
'Type': 'TEXT',
'ReadOnly': True,
'Default': 'Information on\nmultiple lines.\n',
},
},
},
'Option 6': {
'Description': 'This CHOICE option has a sub property',
'Type': 'CHOICE',
'Choices': {
'c1': {
'Sub option 6.1': {
'Description': 'This option is only visible for choice c1',
'Type': 'STRING',
'Default': 'default value',
},
},
'c2': {
},
},
'Default': 'c2',
},
},
}
def GetProperty(self, name: str) -> str | bool | None:
return self._properties.get(name)
def GetVersion(self) -> str:
"""
Version of the external tool. This version will be documented in the test report.
"""
return '1.0.0'
def ExampleJob(self, paramOne: float = 1.0, paramTwo: float = 2.0) -> float:
"""
Example job implementation. The function arguments will be automatically converted to job
parameters based on their type annotations. This docstring will be used as the job's
description. See `GetJobs()` for how to register this method as a job.
:param paramOne: Description of paramOne
:param paramTwo: Description of paramTwo
:return: Multiplication of paramOne, paramTwo and value from TBC
"""
try:
valueFromTBC = float(self._properties['Option 4'])
except ValueError:
valueFromTBC = 1.0
return paramOne * paramTwo * valueFromTBC
class DynamicParameterInformation(TypedDict):
Choices: NotRequired[list[Any]]
Default: NotRequired[Any]
class DynamicJob:
"""
Example implementation of a job that allows the choice options and default values of selected
parameters to be determined dynamically.
"""
def __init__(self, parent: ExampleUserTool) -> None:
self._parent = parent
@classmethod
def GetCategory(cls) -> str:
"""
Optional method to specify the category of the job. Categories are used to group jobs in the
job panel configuration.
:return: The category of the job as a string.
"""
return 'Example Category'
def GetDynamicParameterInformation(self) -> dict[str, DynamicParameterInformation]:
"""
This method specifies the choice options for selected parameters.
:return: Dictionary where the key represents the parameter name and the value contains the
dynamically determined choices and default values.
"""
if self._parent.GetProperty('Option 2'):
choices = ['a', 'b', 'c']
else:
choices = ['x', 'y', 'z']
return {
'param2': {
'Choices': choices,
'Default': choices[0]
},
}
def Call(self, param1: Literal['on', 'off'], param2: str) -> str:
"""
This method is executed when the job is executed in ecu.test.
:param param1: Description of param1
:param param2: Description of param2
:return: Output string containing param1 and param2.
"""
return f'The parameters were set to {param1} and {param2}.'