Adapters are essential parts for building an Executable Test Suite. Their role is to make the link between the TTCN-3 Abstract Test Suite and the platform executing the test.
The TTCN-3 environment provides one standardised interface: the TRI (TTCN-3 Runtime Interface) for implementing the adapters. It is divided into two components:
T3DevLib provides an implementation of the TRI and a set of C++ classes for easing the development of the adapters.
This section starts with a description of classes used for representing and manipulating data from the TRI, then presents how to implement communication ports and SUT actions (for the System Adapter) and external functions and timers (for the Platform Adapter).
The TTCN-3 Runtime Interface defines several structures for exchanging data and referencing components and ports: QualifiedName, BinaryString, TriComponentId, TriPortId, TriTimerId and TriParameterList.
Any TRI implementation is expected to handle them, which implies tedious operations (manipulating pointers, allocating and deallocating memory, parsing and comparing the contents).
To ease this work, T3DevLib provides a set of C++ classes for embedding these structures, accessing their content and performing most of the work safely and transparently. The mapping is shown in Table 4.1.
|TRI structure||T3DevLib equivalent|
Generally the user does not have to instantiate any of these classes, except Bitstring.
PortId, ComponentId and TimerId are used for referencing TTCN-3 ports, components and timers. T3DevLib manages its own pool for these objects, they are created on the fly and destroyed at the end of the testcase. It will never create two instances of the same class representing the same port or component. Thus the address of a PortId, ComponentId or TimerId object is sufficient for identifying uniquely a port, component or timer during a testcase.
T3DevLib provides a smart way of implementing communication ports. Each port type declared in TTCN-3 is mapped to one C++ class derived from the root class t3devlib::Port. Essential functions are provided by the library : multiplexing API calls, handling and storing informations about connected ports.
An example of port implementation is described in section 6.1.2.
For each port definition a C++ class has to be written.
For example with the following TTCN-3 code a type of port named EchoPort. The purpose of this port will be to send back (echo) all the messages that are sent to it.
The minimal c++ class that must be defined to implement the port is the following one.
The constructor is public and takes one parameter, which identifies the new port in the current testcase. This id must be forwarded to the parent constructor.
Other methods are used for implementing port actions. They all return a boolean value, true means that the action was performed successfully.
Map() and Unmap() are called by T3DevLib when a system port is mapped or unmapped to a port of the testcase (using TTCN-3 instructions map and unmap).
They take one parameter which is the identifier of the testcase port that is being mapped to this port.
These functions can be used for initialising and releasing the underlying hardware, open and close a socket, start a listening thread...
For the echo port, there is no special action to do, just return true:
Once a port is successfully mapped, it appears in the list of connected ports accessible with GetMappedPortIdList().
When a message is sent to a system port by the testcase, T3DevLib calls the Send() member function of the corresponding port in C++.
It takes two parameters: the identifier of the component, that sent the message and the message itself.
Received message can be transferred to the testcase by calling Port::EnqueueMsg(). It also takes two parameters: the identifier of the component, the message is sent to and the message itself.
For the case of the Echo port, the message is sent back in Send().
Before executing a testcase it is necessary to register each port type. This is needed for instantiating correctly the port at the beginning of the testcase.
This is done at System Adapter initialisation (in SAInit()) with the static function Port::Register(). The function takes three parameters: the class implementing the port (as a template parameter), the name of the defining module and the name of the port.
An overview of the typical operation of a System Adapter implementation is shown in Figure 4.1. It features the implementation of two different ports MyPortType1 and MyPortType2.
This scenario contains several stages:
T3DevKit provides a simple mechanism for handling informal actions to be performed on the System Under Test with the TTCN-3 action instruction.
Actions shall be implemented as a function and registered using SUTAction::Register() during the initialisation of the System Adapter. Then, when the action instruction is invoked in the TTCN-3 code the corresponding function will be executed.
These functions take one parameter (the textual description of the action) and return a boolean: true if successful, false otherwise.
Several actions may share the same handler function for several actions, in that case they can be distinguished with the parameter.
At runtime if the requested action is not registered, then T3DevKit will print the raw text to the standard output and will ask for user confirmation to proceed.
The following example show how to implement and register three actions named “Action1”, “Action2” and “Action3”. “Action1” has its own handler and the two others share the same handler.
These three actions can be invoked in TTCN-3 as follows:
For handling external functions, T3DevLib provides:
For each external function definition a C++ function has to be written.
For example with the following TTCN-3 code an external function named EchoFunction. The purpose of this function is to return (echo) the parameter that is given.
It is implemented in C++ as follows.
The function takes two parameters: a reference to the list of parameters used to call the function and a reference to the variable that stores the result of the function.
It returns true when the function was successfully executed. Returning false will trigger an error verdict for the current testcase.
Like ports, external functions must be registered before executing a testcase. This is needed for calling the correct function, when triExternalFunction() is called.
This is done at Platform Adapter initialisation (in PAInit()) with the static function ExternalFunction::Register(). The function takes three parameters: the name of the defining module, the name of the function and a pointer to the function implemented in C++.
Parameters are handled with the ParameterList class. This class is a wrapper for the standard TRI structure TriParameterList. It allows easy and safe access to the values without manipulating pointers.
The number of parameters is known with GetSize() and each parameter can be accessed with GetParam or with the operator , which both return a reference.
The type of every parameter and return value is Bitstring. Parameters are encoded before calling the function and return values are decoded after returning.
It is a good practise to check the consistency of the parameters with assertions before executing a function, especially because errors can occur in the codec before the function call.
This is demonstrated in the next example, which implements a function for performing a bitwise AND on two 8-bit integers.
Definition in TTCN-3:
Implementation in C++:
There are two possible ways of returning values to the test from an external function:
The first method is described in the previous sections and is pretty simple. A reference to an empty Bitstring variable is provided to the function. The function just has to store the result in the variable.
The second method is less straightforward because of an issue with memory allocation. out and inout variables need to be instantiated during the function call and freed afterwards. However there is no way of knowing when a variable is no longer used by the Test Executable.
The best practise we found is to use static variables. This is safe since two function calls cannot occur simultaneously.
The parameters can be returned with ParameterList::SetParam() which takes and keeps a reference to a memory buffer or a Bitstring.
The example given in the previous section can be rewritten as follows.
Definition in TTCN-3:
Implementation in C++:
An overview of the typical operation of a System Adapter implementation is shown in Figure 4.2. It features the implementation of two different functions MyFunc1 and MyFunc2.
This scenario contains several stages:
T3DevLib provides and implementation of TTCN-3 timers based on the POSIX Threads API. It is activated by default and should fit the needs of most users. Except in special cases (like need of a high precision timer or simulated time) this section can be skipped.
A custom timer is implemented with a class derived from the abstract base class Timer. Several timer implementations may coexist, but only one can be active at the same time. The active timer implementation is selected during Platform Adapter Reset before the beginning of the testcase. If no implementation is chosen, then T3DevLib uses its own internal timers.
The selection is performed with Timer::SetImplementation(). It takes one parameter: a pointer to a function that instantiate a new timer (using instruction new) and return a pointer to the newly created timer. This function should be generated with template createTimer<>.
A timer implementation provides a constructor and four primitives. The constructor takes exactly one parameter: the runtime identifier (TimerId) of the timer. The four primitives are Start(), Stop(), Read() and IsRunning(), they map directly to the corresponding TTCN-3 instructions: start, stop, read and running. When a timer expires, the Timeout() method must be called.
Each of these functions must return immediately. Therefore monitoring active timers and reporting timeouts should be performed in a separate thread.
A typical implementation of a timer should look as follows:
Examples of timer operation are shown in Figure 4.3 (basic operation with one timer) and Figure 4.4 (concurrency with two timers).
Further information can be found in the API documentation for class Timer and ultimately the DefaultTimer implementation in T3DevLib sources shows an example of implementation.
T3DevKit provides an alternative implementation of timers implementing simulated time. This may be useful when running tests on pre-recorded traces (eg. a tcpdump capture file). In such cases there are no needs to run the tests in real time, so it is preferable to run them in simulated time in order to speed up the execution.
Simulated time is provided by the VirtualTimer class. In order to use it, you need to select at the beginning of a testcase when the Platform Adapter is reset.
This virtual timer class implements an event queue and a virtual clock. The clock it not initialised when the testcase starts and the event queue is empty (in other words there is no notion of time yet).
Once the testcase is started, the implementation will wait for the first events to be scheduled (eg. timers, messages) and then it will initialise the clock and start dispatching the events.
Note: the clock is initialised to the time of the first event with an absolute time (usually the first received message).
When running in simulated time, it is necessary to synchronise the external events (eg. received messages) with the simulated clock. For example if a system port reads it messages from a capture file, the System Adapter should ensure that the messages are dispatched according to their timestamp (so that they are seen at the right time in the testcase).
The VirtualTimer class provides two functions for scheduling and cancelling events: NewEvent() and ClearEvent().
The NewEvent() function takes three parameters:
ClearEvent() is used to cancel all upcoming events with a given tag.
This feature was removed in T3DevKit v0.10