2-test_ports.adoc 61.4 KB
Newer Older
Elemer Lelik's avatar
Elemer Lelik committed
1
2
3
4
= Test Ports
:table-number: 0
:toc:

5
The {cpp} source code generated by the Compiler is protocol independent, that is, it does not contain any device specific operations. To provide the connection between the executable test suite and SUT, that is, the physical interface of the test equipmentfootnote:[The test equipment not necessarily requires a special hardware; it can even be a simple PC with an Ethernet interface.], a so-called Test Port is needed.
Elemer Lelik's avatar
Elemer Lelik committed
6

7
The Test Port is a software library written in {cpp} language, which is linked to the executable test program. It maps the device specific operations to function calls specified in an API. This chapter describes the Test Port API in details.
Elemer Lelik's avatar
Elemer Lelik committed
8
9
10
11
12

== Generating the Skeleton

The functions of Test Ports must be written by the user who knows the interface between the executable test suite and the test equipment. In order to make this development easier, the Compiler can generate Test Port skeletons. A Test Port belongs to one certain TTCN–3 port type, so the skeleton is generated based on port type definitions.

13
A Test Port consists of two parts. One part is generated automatically by the Compiler, and it is put into the generated {cpp} code. The user has nothing to do with this part.
Elemer Lelik's avatar
Elemer Lelik committed
14

15
The other part is a {cpp} class, which is written mainly by the user. This class can be found in a separate {cpp} header and source file (their suffixes are `.hh` and `.cc`, respectively). The names of the source files and the {cpp} class are identical to the name of the port type. Please note that the name mapping rules described in <<6-mapping_ttcn3_data_types_to_c++_constructs.adoc#mapping-of-names-and-identifiers, Mapping of Names and Identifiers>> also apply to these class and file names.
Elemer Lelik's avatar
Elemer Lelik committed
16
17
18

During the translation, when the Compiler encounters a port type definition and the `*–t*` command line switch is used, it checks whether the header and source files of Test Port exist in its working directory. If none of them can be found there, the compiler generates the skeleton header and source files for the corresponding test port automatically. This means, once you have generated (and possibly modified) a skeleton, it will never be overwritten. If you want to re-generate the skeleton, you must rename or remove the existing one.

19
If the list of message types/signatures of a TTCN-3 port type changes, the list of the Test Port class member functions also needs to change. If the Test Port skeleton has been generated, it will not be modified, resulting in build errors ({cpp} compile errors like "cannot declare variable of abstract type"/"virtual functions are pure", or linker errors). In this case, the Test Port skeleton files should be renamed/moved, the skeleton generated, and any user-written code should be copied back into the newly generated source files.
Elemer Lelik's avatar
Elemer Lelik committed
20
21
22
23
24

If you have defined a TTCN–3 port type that you intend to use for internal communication only (that is, for sending and receiving messages between TTCN–3 test components), you do not need to generate and compile an empty Test Port skeleton for that port type. Adding the attribute with `{extension "internal"}` to the port type definition in the TTCN–3 module disables the generation and use of a Test Port for the port type.

WARNING: In this case you must not link the object file obtained from a previous Test Port skeleton to your executable test suite.

25
26
In the following we introduce two port type definitions: one for a message based and another one for a procedure based port. 
In our further examples we will refer to the test port skeletons generated according to these definitions given within the module called `MyExample`.
Elemer Lelik's avatar
Elemer Lelik committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

== Message-based Example

The definition of `MyMessagePort`:
[source]
----
type port MyMessagePort message
{
  in octetstring;
  out integer;
  inout charstring;
};
----
That is, the types integer and charstring can be sent, and octetstring and charstring can be received on port `MyMessagePort`.

The generated skeleton header file (that is, `MyMessagePort.hh`) will look as follows:
[source]
----
// This Test Port skeleton header file was generated by the
46
47
// TTCN-3 Compiler of the TTCN-3 Test Executor version CRL 113 200/7 R1A
// for U-ERICSSON\ethbaat (ethbaat@HU-00000670) on Wed May 11 14:49:55 2020
Elemer Lelik's avatar
Elemer Lelik committed
48

balaskoa's avatar
balaskoa committed
49
// Copyright (c) 2000-2020 Ericsson Telecom AB
Elemer Lelik's avatar
Elemer Lelik committed
50
51
52
53
54
55
56

// You may modify this file. Add your attributes and prototypes of your
// member functions here.

#ifndef MyMessagePort_HH
#define MyMessagePort_HH

57
#include "MyExample.hh"
Elemer Lelik's avatar
Elemer Lelik committed
58

59
namespace MyExample {
Elemer Lelik's avatar
Elemer Lelik committed
60
61
62

class MyMessagePort : public MyMessagePort_BASE {
public:
63
64
        MyMessagePort(const char *par_port_name = NULL);
        ~MyMessagePort();
Elemer Lelik's avatar
Elemer Lelik committed
65

66
67
        void set_parameter(const char *parameter_name,
                const char *parameter_value);
Elemer Lelik's avatar
Elemer Lelik committed
68
69

private:
70
71
72
73
74
75
        /* void Handle_Fd_Event(int fd, boolean is_readable,
                boolean is_writable, boolean is_error); */
        void Handle_Fd_Event_Error(int fd);
        void Handle_Fd_Event_Writable(int fd);
        void Handle_Fd_Event_Readable(int fd);
        /* void Handle_Timeout(double time_since_last_call); */
Elemer Lelik's avatar
Elemer Lelik committed
76
protected:
77
78
        void user_map(const char *system_port, Map_Params& params);
        void user_unmap(const char *system_port, Map_Params& params);
Elemer Lelik's avatar
Elemer Lelik committed
79

80
81
        void user_start();
        void user_stop();
Elemer Lelik's avatar
Elemer Lelik committed
82

83
84
        void outgoing_send(const INTEGER& send_par);
        void outgoing_send(const CHARSTRING& send_par);
Elemer Lelik's avatar
Elemer Lelik committed
85
86
87
88
89
90
91
92
93
94
95
96
};

} /* end of namespace */

#endif
----

And the generated skeleton source file, that is, `MyMessagePort.cc`, will be the following:

[source]
----
// This Test Port skeleton source file was generated by the
balaskoa's avatar
balaskoa committed
97
// TTCN-3 Compiler of the TTCN-3 Test Executor version CRL 113 200/7 R1A
98
// for U-ERICSSON\ethbaat (ethbaat@HU-00000670) on Wed May 11 14:49:55 2020
Elemer Lelik's avatar
Elemer Lelik committed
99

balaskoa's avatar
balaskoa committed
100
// Copyright (c) 2000-2020 Ericsson Telecom AB
Elemer Lelik's avatar
Elemer Lelik committed
101
102
103
104
105
106

// You may modify this file. Complete the body of empty functions and
// add your member functions here.

#include "MyMessagePort.hh"

107
namespace MyExample {
Elemer Lelik's avatar
Elemer Lelik committed
108
109

MyMessagePort::MyMessagePort(const char *par_port_name)
110
        : MyMessagePort_BASE(par_port_name)
Elemer Lelik's avatar
Elemer Lelik committed
111
112
113
114
115
116
117
118
119
{

}

MyMessagePort::~MyMessagePort()
{

}

120
121
void MyMessagePort::set_parameter(const char * /*parameter_name*/,
        const char * /*parameter_value*/)
Elemer Lelik's avatar
Elemer Lelik committed
122
123
124
125
126
{

}

/*void MyMessagePort::Handle_Fd_Event(int fd, boolean is_readable,
127
        boolean is_writable, boolean is_error) {}*/
Elemer Lelik's avatar
Elemer Lelik committed
128

129
void MyMessagePort::Handle_Fd_Event_Error(int /*fd*/)
Elemer Lelik's avatar
Elemer Lelik committed
130
131
132
133
{

}

134
void MyMessagePort::Handle_Fd_Event_Writable(int /*fd*/)
Elemer Lelik's avatar
Elemer Lelik committed
135
136
137
138
{

}

139
void MyMessagePort::Handle_Fd_Event_Readable(int /*fd*/)
Elemer Lelik's avatar
Elemer Lelik committed
140
141
142
143
144
145
{

}

/*void MyMessagePort::Handle_Timeout(double time_since_last_call) {}*/

146
void MyMessagePort::user_map(const char * /*system_port*/, Map_Params& /*params*/)
Elemer Lelik's avatar
Elemer Lelik committed
147
148
149
150
{

}

151
void MyMessagePort::user_unmap(const char * /*system_port*/, Map_Params& /*params*/)
Elemer Lelik's avatar
Elemer Lelik committed
152
153
154
155
156
157
158
159
160
161
162
163
164
165
{

}

void MyMessagePort::user_start()
{

}

void MyMessagePort::user_stop()
{

}

166
void MyMessagePort::outgoing_send(const INTEGER& /*send_par*/)
Elemer Lelik's avatar
Elemer Lelik committed
167
168
169
170
{

}

171
void MyMessagePort::outgoing_send(const CHARSTRING& /*send_par*/)
Elemer Lelik's avatar
Elemer Lelik committed
172
173
174
175
176
{

}

} /* end of namespace */
177
178


Elemer Lelik's avatar
Elemer Lelik committed
179
180
----

181
== Procedure-based Example
Elemer Lelik's avatar
Elemer Lelik committed
182

183
The definition of `MyProcedurePort` in module `MyExample`:
Elemer Lelik's avatar
Elemer Lelik committed
184
185
186
187
188
189
190
191
192
193
[source]
----
type port MyProcedurePort procedure
{
  in inProc;
  out outProc;
  inout inoutProc;
};
----

194
The signature definitions are imported from a module called `MyExample`, `noblock` is not used and exceptions are used so that every member function of the port class is generated for this example. If the keyword `noblock` is used the compiler will optimize code generation by not generating outgoing reply, incoming reply member functions and their argument types. If the signature has no exception outgoing raise, incoming exception member functions and related types will not be generated.
Elemer Lelik's avatar
Elemer Lelik committed
195
196
197
198
199
200
201
202
203
204
205

The port type `MyProcedurePort` can handle `call`, `getreply` and `catch` operations referencing the signatures `outProc` and `inoutProc`, and it can handle `getcall`, `reply` and `raise` operations referencing the signatures `inProc` and `inoutProc`.

The generated skeleton header file (that is, `MyProcedurePort.hh`) will look as follows:

[source]
----
// This Test Port skeleton header file was generated by the
// TTCN-3 Compiler of the TTCN-3 Test Executor version 1.7.pre4 build 4
// for Csaba Feher (ecsafeh@ehubuux110) on Tue Jul 29 18:53:35 2008

balaskoa's avatar
balaskoa committed
206
// Copyright (c) 2000-2020 Ericsson Telecom AB
Elemer Lelik's avatar
Elemer Lelik committed
207
208
209
210
211
212
213

// You may modify this file. Add your attributes and prototypes of your
// member functions here.

#ifndef MyProcedurePort_HH
#define MyProcedurePort_HH

214
#include "MyExample.hh"
Elemer Lelik's avatar
Elemer Lelik committed
215

216
namespace MyExample {
Elemer Lelik's avatar
Elemer Lelik committed
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233

class MyProcedurePort : public MyProcedurePort_BASE {
public:
	MyProcedurePort(const char *par_port_name = NULL);
	~MyProcedurePort();

	void set_parameter(const char *parameter_name,
		const char *parameter_value);

private:
	/* void Handle_Fd_Event(int fd, boolean is_readable,
		boolean is_writable, boolean is_error); */
	void Handle_Fd_Event_Error(int fd);
	void Handle_Fd_Event_Writable(int fd);
	void Handle_Fd_Event_Readable(int fd);
	/* void Handle_Timeout(double time_since_last_call); */
protected:
234
235
	void user_map(const char *system_port, Map_Params& params);
	void user_unmap(const char *system_port, Map_Params& params);
Elemer Lelik's avatar
Elemer Lelik committed
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257

	void user_start();
	void user_stop();

	void outgoing_call(const outProc_call& call_par);
	void outgoing_call(const inoutProc_call& call_par);
	void outgoing_reply(const inProc_reply& reply_par);
	void outgoing_reply(const inoutProc_reply& reply_par);
};

} /* end of namespace */

#endif
----

The generated skeleton source file for `MyProcedurePort` (that is, `MyProcedurePort.cc`) will be the following:
[source]
----
// This Test Port skeleton source file was generated by the
// TTCN-3 Compiler of the TTCN-3 Test Executor version 1.7.pre4 build 4
// for Csaba Feher (ecsafeh@ehubuux110) on Tue Jul 29 18:53:35 2008

balaskoa's avatar
balaskoa committed
258
// Copyright (c) 2000-2020 Ericsson Telecom AB
Elemer Lelik's avatar
Elemer Lelik committed
259
260
261
262
263
264

// You may modify this file. Complete the body of empty functions and
// add your member functions here.

#include "MyProcedurePort.hh"

265
namespace MyExample {
Elemer Lelik's avatar
Elemer Lelik committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303

MyProcedurePort::MyProcedurePort(const char *par_port_name)
	: MyProcedurePort_BASE(par_port_name)
{

}

MyProcedurePort::~MyProcedurePort()
{

}

void MyProcedurePort::set_parameter(const char *parameter_name,
	const char *parameter_value)
{

}

/*void MyProcedurePort::Handle_Fd_Event(int fd, boolean is_readable,
	boolean is_writable, boolean is_error) {}*/

void MyProcedurePort::Handle_Fd_Event_Error(int fd)
{

}

void MyProcedurePort::Handle_Fd_Event_Writable(int fd)
{

}

void MyProcedurePort::Handle_Fd_Event_Readable(int fd)
{

}

/*void MyProcedurePort::Handle_Timeout(double time_since_last_call) {}*/

304
void MyProcedurePort::user_map(const char *system_port, Map_Params& params)
Elemer Lelik's avatar
Elemer Lelik committed
305
306
307
308
{

}

309
void MyProcedurePort::user_unmap(const char *system_port, Map_Params& params)
Elemer Lelik's avatar
Elemer Lelik committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
{

}

void MyProcedurePort::user_start()
{

}

void MyProcedurePort::user_stop()
{

}

void MyProcedurePort::outgoing_call(const outProc_call& call_par)
{

}

void MyProcedurePort::outgoing_call(const inoutProc_call& call_par)
{

}

void MyProcedurePort::outgoing_reply(const inProc_reply& reply_par)
{

}

void MyProcedurePort::outgoing_reply(const inoutProc_reply& reply_par)
{

}

} /* end of namespace */
----

[[test-port-functions]]
== Test Port Functions

This section summarizes all possible member functions of the Test Port class. All of these functions exist in the skeleton, but their bodies are empty.

The identical functions of both port types are:

* the constructor and the destructor

* the parameter setting function

* the map and unmap function

* the start and stop function

* descriptor event and timeout handler(s)

* some additional functions and attributes

The functions above will be described using an example of message based ports (`MyMessagePort`, also introducing the functions specific to message based port types). Using these functions is identical (or very similar) in procedure based Test Ports.

Functions specific to message based ports:

* send functions: outgoing send

* incoming functions: incoming message

* Functions specific to procedure based ports:

* outgoing functions: outgoing call, outgoing reply, outgoing raise

* incoming functions: incoming call, incoming reply, incoming exception

Both test port types can use the same logging and error handling mechanism, and the handling of incoming operations on port `MyProcedurePort` is similar to receiving messages on port `MyMessagePort` (regarding the event handler).

=== Constructor and Destructor

The Test Port class belongs to a TTCN–3 port type, and its instances implement the functions of the port instances. That is, each Test Port instance belongs to the port of a TTCN–3 test component. The number of TTCN–3 component types, port types and port instances is not limited; you may have several Test Port classes and several instances of a given Test Port class in one test suite.

The Test Port instances are global and static objects. This means, their constructor and destructor is called before and after the test execution (that is, before the main function starts and after the main function finishes). The name of a Test Port object is composed of the name of the corresponding component type and the name of the port instance within the component type.

In case of parallel test execution, each TTCN–3 test component process has its own Test Port instances of all ports defined in all component types within the entire test suite. Of course, only the Test Ports of the active component type are used, the member functions of other inactive Test Port instances (except constructor and destructor) will never be called. Since all Test Port instances are static, their constructor and destructor is called only once on each host and in the Host Controller process (outside its main function). The test component processes (that is, the child processes of Host Controller) will get a copy of the initialized Test Port instances and no constructor will be called again.

The Test Port class is derived from an abstract base class which can be found in the generated code. The base class implements, for instance, the queue of incoming messages.

The constructor takes one parameter containing the name of the port instance in a NUL character terminated string. This string shall be passed further to the constructor of the base class as it can be found in the skeleton code. The default argument for the test port name is a NULL pointer, which is used when the test port object is a member of a port array.

WARNING: In case of port arrays the name of the test port is set after the constructor is completed. So the name of the test port should not be used in the constructor. The port name is always set correctly when any other member function is called.

The destructor does nothing by default. If some dynamically allocated attributes are added to the test port class, one should free the memory and release all resources in the destructor.

WARNING: As the constructor and the destructor are called outside of main function, be careful when writing them. For instance, there is no way for error recovery; `exit(3)` call may result in a segmentation fault. If file descriptors are opened (and kept opened) here, the `fork(2)` system call of Host Controller will only multiply the file descriptors and not the kernel file structure. Therefore system and library calls should be avoided here.

=== Parameter Setting Function

Test Port parametersfootnote:[Test Port parameters have been introduced in version 1.1.pl3] shall contain information which is independent from the TTCN3 test suite. These values shall not be used in the test suite at all. You can define them as TTCN–3 constants or module parameters, but these definitions are useless and redundant, and they must always be present when the Test Port is used.

For instance, using Test Port parameters can be used to convey configuration data (that is, some options or extra information that is necessary for correct operation) or lower protocol layer addresses (for example, IP addresses).

Test Port parameters shall be specified by the user of executable tests in section `[TESTPORT_PARAMETERS]` of the run-time configuration file (see section `[TESTPORT_PARAMETERS]` in link:https://github.com/eclipse/titan.core/tree/master/usrguide/referenceguide[Programmer's Technical Reference]). The parameters are maintained for each test port instance separately; wildcards can be used as well. In the latter case the parameter is passed to all Test Port matching the wildcard.

Each Test Port parameter must have a name, which must be unique within the Test Port only. The name must be a valid identifier, that is, it must begin with a letter and must contain alphanumerical characters only.

All Test Port parameter values are interpreted by the test executor as character strings. Quotation marks must be used when specifying the parameter values in the configuration file. The interpretation of parameter values is up to you: you can use some of them as symbolic values, numbers, IP addresses or anything that you want.

Before the test execution begins, all parameters belonging to the Test Port are passed to the Test Port by the runtime environment of the test executor using the function `set_parameter`. It is a virtual function, that is, this function may be removed from the header and source file if there are no parameters. Its default ancestor does nothing and ignores all parameters.

Each parameter is passed to the Test Port one-by-one separatelyfootnote:[If the same parameter of the same port instance is specified several times in the configuration file, the function `set_parameter` will also be called several times.], the two arguments of `set_parameter` contain the name and value of the corresponding parameter, respectively, in NUL character terminated strings. If the parameter values are needed in further operations, backup copies must be made of them because the string will disappear after the calling function returns.

It is warmly recommended that the Test Port parameter handling functions be fool-proof. For instance, the Test Port should produce a proper error message (for example by calling `TTCN_error`) if a mandatory parameter is missing instead of causing segmentation fault. Repeated setting of the same parameter should produce warnings for the user (for example by using the function `TTCN_warning`) and not memory leaks.

NOTE: On the MTC, in both single and parallel modes, the handling of Test Port parameters is a bit different from that on PTCs. The parameters are passed only to active ports, but the component type of MTC (thus the set of active ports) depends on the `runs on` clause of the test case that is currently being executed. It would be difficult for the runtime environment to check at the beginning of each test case whether the corresponding MTC component type has already been active during a previous test case run. Therefore all Test Port parameters belonging to the active ports of the MTC are passed to the `set_parameter` function at the beginning of every test case. The Test Ports of MTC shall be prepared to receive the same parameters several times (with the same values, of course) if more than one test case is being executed.

If system related Test Port parameters are used in the run-time configuration file (that is, the keyword `system` is used as component identifier), the parameters are passed to your Test Port during the execution of TTCN–3 `map` operations, but before calling your `user_map` function. Please note that in this case the port identifier of the configuration file refers to the port of the test system interface that your port is mapped to and not the name of your TTCN–3 port.

The name and exact meaning of all supported parameters must be specified in the user documentation of the Test Port.

=== Map and Unmap Functions

The run-time environment of the TTCN–3 executor knows nothing about the communication towards SUT, thus, it is the user’s responsibility to establish and terminate the connection with SUT. The TTCN–3 language uses two operations to control these connections, `map` and `unmap`.

For this purpose, the Test Port class provides two member functions, `user_map` and `user_unmap`. These functions are called by the test executor environment when performing TTCN–3 `map` and `unmap` operations, respectively.

The `map` and `unmap` operations take two pairs of component references and ports as arguments. These operations are correct only if one of the arguments refer to a port of a TTCN–3 test component while the other port corresponds to SUT. This aspect of correctness is verified by the run-time environment, but the existence of a system port is not checked.

The port names of the system are converted to `NUL` character terminated strings and passed to functions `user_map` and `user_unmap` as parameters. Unlike other identifiers, the underscore characters in these port names are not translated.

If these system port names should be reused later, the entire strings (and not only the pointers) must be saved in the internal memory structures since the string values will disappear after the `user_map` or `user_unmap` finishes.

NOTE: in TTCN–3 it is not allowed to map a test component port to several system ports at the same time. The run-time environment, however, is not so strict and allows this to handle transient states during configuration changes. In this case messages can not be sent to SUT even with explicit addressing, but the reception of messages is permitted. When putting messages into the input queue of the port, it is not important for the test executor (even for the TTCN–3 language) which port of the system the message is received from.

The execution of TTCN–3 test component that requested the mapping or unmapping is suspended until your `user_map` or `user_unmap` functions finish. Therefore it is not allowed to block unnecessarily the test execution within these functions.

When the Test Port detects an error situation during the establishment or termination of the physical connection towards the SUT, the function `TTCN_error` shall be used to indicate the failure. If the error occurs within `user_map` the run-time environment will assume that the connection with SUT is not established thus it will not call `user_unmap` to destroy the mapping during the error recovery procedure. If `user_map` fails, it is the Test Port writer’s responsibility to release all allocated resources and bring the object variables into a stable state before calling `TTCN_error`. Within `user_unmap` the errors should be handled in a more robust way. After a minor failure it is better to issue a warning and continue the connection termination instead of panicking. `TTCN_error` shall be called only to indicate critical errors. If `user_unmap` is interrupted with an error the run-time environment assumes that the mapping has been terminated, that is, `user_unmap` will not be called again.

NOTE: if either `user_map` or `user_unmap` fails, the error is indicated on the initiator test component as well; that is, the respective map or `unmap` operation will also fail and error recovery procedure will start on that component.

444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
==== Parameters of the Map and Unmap Functions

Parameters can be sent to the `user_map` and `user_unmap` functions from TTCN code using the `param` clause of the `map` and `unmap` operations.

The 'user_map` and `user_unmap` functions have a parameter of type `Map_Params`, which contains the string representations of the `in` and `inout` parameters of the `map`/`unmap` operation. The string representations of `out` parameters are empty strings (as these are considered as being `unbound` at the beginning of the `map`/`unmap` operation). After the `user_map` or `user_unmap` function ends and the mapping/unmapping is concluded, the final values (string representations) of `out` and `inout` parameters in the `Map_Params` object are sent back to the mapping/unmapping requestor.

The following member functions can be used to obtain or set data in the `Map_Params` object:

[source]
----
unsigned int get_nof_params() const
----
Returns the number of parameters in the object. This will either be zero (if the `map` or `unmap` operation had no `param` clause) or the number of parameters specified in the system port type definition's `map param` or `unmap param` clause.

[source]
----
const CHARSTRING& get_param(unsigned int p_index) const
----
Returns the string representation of the parameter at index `p_index`. This method shall be used to retrieve the values of `in` and `inout` parameters. The parameter indices start at 0. The order of the parameters is the same as their order of declaration. Default values of parameters are automatically set by the runtime environment before the `user_map`/`user_unmap` call. The string representations retrieved with this function can be converted back to the parameter's TTCN-3 type with the predefined function `string_to_ttcn`.

[source]
----
void set_param(unsigned int p_index, const CHARSTRING& p_param)
----
Sets the string representation of the parameter at index `p_index` to the string `p_param`. This method shall be used to set the final values of `out` and `inout` parameters. The string representation of a TTCN-3 value can be obtained using the predefined function `ttcn_to_string`. If the final value of an `out` or `inout` parameter is an empty string, then the variable used as parameter will remain unchanged. Otherwise its new value will be calculated by applying `string_to_ttcn` on the string value set in the `user_map` or `user_unmap` function (this could cause dynamic test case errors if the string representation is invalid).

Usage example:

Port type:
[source]
----
type port MyPort message {
  ...
  map param(in MyInParType in_par, inout MyInOutParType inout_par, out MyOutParType out_par)
}
----
`user_map` function in port implementation:
[source]
----
void MyPort::user_map(const char * system_port, Map_Params& params)
{
  if (params.get_nof_params() != 0) {
    // there were map parameters
    
    // extract 'in' and 'inout' parameters
    MyInParType in_par;
    string_to_ttcn(params.get_param(0), in_par);
    MyInOutParType inout_par;
    string_to_ttcn(params.get_param(1), inout_par);
    MyOutParType out_par; // remains unbound
    
    // do mapping
    ...
    
    // update 'out' and 'inout' parameters
    params.set_param(1, ttcn_to_string(inout_par));
    params.set_param(2, ttcn_to_string(out_par));
  }
  else {
    // there were no map parameters
    
    // do mapping
    ...
  }
}
----

Elemer Lelik's avatar
Elemer Lelik committed
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
=== Start and Stop Functions

The Test Port class has two member functions: `user_start` and `user_stop`. These functions are called when executing `port start` and `port stop` operations, respectively. The functions have no parameters and return types.

These functions are called through a stub in the base class, which registers the current state of the port (whether it is started or not). So `user_start` will never be called twice without calling `user_stop` or vice versa.

WARNING: From version 1.2.pl0 on (according to the latest TTCN–3 standard) all ports of test components are started implicitly immediately after creation. Such operations must not be put in a `user_start` function blocking the execution for a longer period. This not only hangs the new PTC but the also component that performed the `create` operation (usually the MTC). All ports are stopped at the end of test cases or at PTC termination, even if `stop` statements are missing.

In functions `user_start` and `user_stop` the device should be initialized or shut down towards SUT (that is, the communications socket). Also the event handler should be installed or uninstalled (see later).

=== Outgoing Operations

Outgoing operations are `send` (specific to message based ports); `call`, `reply`, and `raise` (specific to procedure based ports).

==== Send Functions

The Test Port class has an overloaded function called `outgoing_send` for each outgoing message type. This function will be called when a message is sent on the port and it should be routed to the system (that is, SUT) according to the addressing semanticsfootnote:[That is, the port has exactly one mapping and either the port has no connections or the message is explicitly addressed by a `send (…) to system` statement.] of TTCN–3. The messages (implicitly or explicitly) addressed to other test components are handled inside the test executor; the Test Ports have nothing to do with them. The function `outgoing_send` will be also called if the port has neither connections nor mappings, but a message is sent on it.

529
The only parameter of `outgoing_send` contains a read-only reference to the message in the internal data representation format of the test executor. The access methods for internal data types are described in <<5-encoding_and_decoding.adoc#xml-encoding-xer, XML Encoding (XER)>>. The test port writer should encode and send the message towards SUT. For information on how to use the standard encoding functions like BER, please consult <<4-logger_plug-ins.adoc, Logger Plug-ins>>. Sending a message on a not started port causes a dynamic test case error. In this case outgoing_send will not be called.
Elemer Lelik's avatar
Elemer Lelik committed
530
531
532
533
534

==== Call, Reply and Raise Functions

The procedure based Test Port class has overloaded functions called `outgoing_call`, `outgoing_reply` and `outgoing_raise` for each `call`, `reply` and `raise` operations, respectively. One of these functions will be called when a port-operation is addressing the system (that is, SUT using the to `system` statement).

535
The only parameter of these functions is an internal representation of the signature parameters (and possibly its return value) or the exceptions it may raise. The signature classes are described in <<6-mapping_ttcn3_data_types_to_c++_constructs.adoc#using-the-signature-classes,Using the Signature Classes>>.
Elemer Lelik's avatar
Elemer Lelik committed
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628

=== Incoming Operations

Incoming operations are `receive` incoming messages (specific to message based ports); `call`, `reply` and `exception` (specific to procedure based ports).

==== Descriptor Event and Timeout Handlers

The handling of incoming messages (or operations) is more difficult than sending. The executable test program has two states. In the first state, it executes the operations one by one as specified in the test suite (for example, it evaluates expressions, calls functions, sends messages, etc.). In the other state it waits for the response from SUT or for a timer to expire. This happens when the execution reaches a blocking statement, that is, one of a stand-alone `receive`, `done`, `timeout` statements or an `alt` construct.

After reaching a blocking statement, the test executor evaluates the current snapshot of its timer and port queues and tries to match it with the reached statements and templates. If the matching fails, the executor sleeps until something happens to its timers or ports. After waking up, it re-evaluates its snapshot and tries to match it again. The last two steps are repeated until the executor finds the first matching statement. If the test executor realizes that its snapshot can never match the reached TTCN–3 statements, it causes a dynamic test case error. This mechanism prevents it from infinite blocking.

The test executor handles its timers itself, but it does not know anything about the communication with SUT. So each Test Port instance should inform the snapshot handler of the executor what kind of event the Test Port is waiting for. The event can be either the reception of data on one or more file descriptors or a timeout (when polling is used) or both of them.

When the test executor reaches a blocking statement and any condition – for which the Test Port waits – is fulfilled, the event handler will be called. First one has to get the incoming message or operation from the operating system. After that, one has to decode it (and possibly decide its type). Finally, if the internal data structure is built, one has to put it into the queue of the port. This can be done using the member function `incoming_message` if it is a message, and using `incoming_call`, `incoming_reply` or `incoming_exception` if it is an operation.

The execution must not be blocked in event handler functions; these must return immediately when the message or operation processing is ready. In other words, always use non-blocking `recv()` system calls. In the case when the messages are fragmented (for instance, when testing TCP based application layer protocols, such as HTTP), intermediate buffering should be performed in the Test Port class.

===== Event and timeout handling interface introduced in TITAN version 1.7.pl4

This descriptor event and timeout handling interface is the preferred interface for new Test Port development.

There are two possibilities to be notified about available events:

* Either the `Handle_Fd_Event` function has to be implemented, or

* `Handle_Fd_Event_Readable`, `Handle_Fd_Event_Writable`, and `Handle_Fd_Event_Error`.

Using `Handle_Fd_Event` allows receiving all events of a descripor in one function call. Using the other three event handler functions allows creating a more structured code.

All these functions are virtual. The unused event handler functions have to be left un-overridden. (When using the second alternative and the Test Port does not wait for all types of events (readable, writable, error) the handlers of the events – for which the Test Port does not wait – can be left un-overridden.)

The following functions can be used to add events to and remove events from the set of events for which the Test Port waits:
[source]
----
void Handler_Add_Fd(int fd, Fd_Event_Type event_mask = EVENT_ALL);
void Handler_Add_Fd_Read(int fd);
void Handler_Add_Fd_Write(int fd);
void Handler_Remove_Fd(int fd, Fd_Event_Type event_mask = EVENT_ALL);
void Handler_Remove_Fd_Read(int fd);
void Handler_Remove_Fd_Write(int fd);
----

The first parameter in all of these functions is the file descriptor. Possible values of the `event_mask` are `EVENT_RD`, `EVENT_WR`, `EVENT_ERR` and combinations of these using bitwise or: "|".

Timeout notification can be received with the `Handle_Timeout` function. The parameter of the function indicates the time elapsed in seconds since its last call of this function or the latest modification of the timer (whichever occurred later).

The timer can be set with the following function:
[source, subs="+quotes"]
void Handler_Set_Timer(double call_interval, boolean is_timeout = TRUE,
    boolean call_anyway = TRUE, boolean is_periodic = TRUE);

`call_interval` is measured in seconds and specifies the time after which the `Handle_Timeout` function will be called. To stop the timer `call_interval` value: 0.0 has to be given.

`is_timeout` specifies if the timer has to be stopped when event handler is called. `call_anyway` is meaningful when `is_timeout` is set to `TRUE`. In this case `call_anyway` indicates if the `Handle_Timeout` function has to be called when event handler is called before the timer would expire. If `call_anyway` is `TRUE` the timeout handler will be called after the call of the event handlers and the timer will be stopped. `is_periodic` indicates if the timer has to be restarted instead of stopping when timer expires or event handler is called and `is_timeout` and `call_anyway` are both `TRUE`.

===== Event handler for Test Ports developed for 1.7pl3 and earlier versions of TITAN

There is only one event handler function in each Test Port class called `Event_Handler`, which is a virtual member function. The run-time environment calls it when an incoming event arrives. You can install or uninstall the event handler by calling the following inherited member functions:
[source, subs="+quotes"]
void Install_Handler(const fd_set *read_fds, const fd_set *write_fds,
                     const fd_set *error_fds, double call_interval);
void Uninstall_Handler();

`Install_Handler` installs the event handler according to its parameters. It takes four arguments, three pointers pointing to bitmasks of file descriptors and a timeout value. Some of the parameters can be ignored, but ignoring all at the same time is not permitted.

The bitmasks are interpreted in the same way as in the select system call. They can be set using the macros `FD_ZERO`, `FD_SET` and `FD_CLR`. If the pointer is NULL, the bitmask is treated as zero. For further details see the manual page of `select(2)` or `select(3)`.

The call interval value is measured in seconds. It means that the event handler function will be called when the time elapsed since its last call reaches the given value. This parameter is ignored when its value is set to zero or negative.

If you want to change your event mask parameters, you may simply call the function `Install_Handler` again (calling of `Uninstall_Handler` is not necessary).

`Uninstall_Handler` will uninstall your previously installed event handler. The `stop` port operation also uninstalls the event handler automatically. The event handler may be installed or uninstalled in any Test Port member function, even in the event handler itself.

The prototype of the event handler function is the following:
[source, subs="+quotes"]
void Event_Handler(const fd_set *r_fds, const fd_set *w_fds,
                   const fd_set *e_fds, double time_since_last_call);

The function `Event_Handler` has four parameters. The first three of them are pointers to bitmasks of file descriptors as described above. They are the bitwise AND combination of bitmasks you have given to `Install_Handler` and the bitmasks given back by the last call of select. They can be useful when waiting for data from many file descriptors, for example when handling more than one SUT mappings simultaneously, because there is no need to issue a select call again within the event handler. +

NOTE: the pointers can be never NULL, they point to a valid memory area even if there are no file descriptors set in the bitmask. The last parameter contains the time elapsed since the last call of the event handler measured in seconds. This value is always calculated even if the call interval has not been set. If the `Event_Handler` is called the first time since its last installation, the time is measured from the call of `Install_Handler`.footnote:[In versions of Test Executor older than 1.1 the event handler function had no parameters. If you want to upgrade a test port developed for older versions, you should insert this formal parameter list to your event handler both in Test Port header and source file. Otherwise the compilation of Test Port will fail.]

==== Receiving messages

The member function `incoming_message` of message based ports can be used to put an incoming message in the queue of the port. There are different polymorphic functions for each incoming message type. These functions are inherited from the base class. The received messages are logged when they are put into the queue and not when they are processed by the test suitefootnote:[Note that if the port has connections as well, the messages coming from other test components will also be inserted into the same queue independently from the event handler.].

In our example the class `MyMessagePort_BASE` has the following member functions:
[source, subs="+quotes"]
incoming_message(const OCTETSTRING& incoming_par);
incoming_message(const CHARSTRING& incoming_par);

==== Receiving calls, replies and exceptions

629
Receiving operations on procedure based ports is similar to receiving messages on message based ports. The difference is that there are different overloaded incoming functions for call, reply and raise operations called `incoming_call`, `incoming_reply` and `incoming_exception`, respectively. The event handler (when called) must recognize the type of operation on receiving and call one of these functions accordingly with one of the internal representations of the signature (see <<6-mapping_ttcn3_data_types_to_c++_constructs.adoc #additional-non-standard-functions, Additional Non-Standard Functions>>).
Elemer Lelik's avatar
Elemer Lelik committed
630

631
In the examplefootnote:[In the example the signatures were defined in a different TTCN–3 module named MyExample, as a consequence all types defined in that module must be prefixed with the {cpp} namespace name of that module.] the class `MyProcedurePort_BASE` has the following member functions for incoming operations:
Elemer Lelik's avatar
Elemer Lelik committed
632
633
[source]
----
634
635
636
637
638
639
incoming_call(const MyExample::inProc_call& incoming_par);
incoming_call(const MyExample::inoutProc_call& incoming_par);
incoming_reply(const MyExample::outProc_reply& incoming_par);
incoming_reply(const MyExample::inoutProc_reply& incoming_par);
incoming_exception(const MyExample::outProc_exception& incoming_par);
incoming_exception(const MyExample::inoutProc_exception& incoming_par);
Elemer Lelik's avatar
Elemer Lelik committed
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
----
For example, if the event handler receives a call operation that refers to the signature called `inoutProc`, it has to fill the parameters of an instance of the class `inoutProc_call` with the received data. Then it has to call the function `incoming_call` with this object to place the operation into the queue of the port.

The following table shows the relation between the direction of the message type or signature in the port type definition and the incoming/outgoing functions that can be used. `MyPort` in the table header refers to `MyMessagePort` or `MyProcedurePort` in the example depending on the type of the port (message based or procedure based).

.Outgoing and incoming operations

[cols=" ",options="header",]
|===
| | 4+^.^|`MyPort::outgoing_` 4+^.^| `MyPort BASE::incoming_`
| | |send |call |reply |raise |message |call |reply |exception
.3+^.^|message type |in |○ |○ |○ |○ |● |○ |○ |○
|out |● |○ |○ |○ |○ |○ |○ |○
|inout |● |○ |○ |○ |● |○ |○ |○
.3+^.^|signature |in |○ |○ |● |● |○ |● |○ |○
|out |○ |● |○ |○ |○ |○ |● |●
|inout |○ |● |● |● |○ |● |● |●
|===

● supported

○ not supported

=== Additional Functions and Attributes

Any kind of attributes or member functions may be added to the Test Port. A file descriptor, which you communicate on, is almost always necessary. Names not interfering with the identifiers generated by the Compiler can be used in the header file (for example, the names containing one underscore character). Avoid using global variables because you may get confused when more than one instances of the Test Port run simultaneously. Any kind of software libraries may be used in the Test Port as well, but included foreign header files may cause name clashes between the library and the generated code.

In addition, the following `protected` attributes of ancestor classes are available:

.Protected attributes

[width="100%",cols="34%,33%,33%",options="header",]
|======================================================================================================
|Name ^.^|Type |Meaning
|`is_started` ^.^|boolean |Indicates whether the Test Port is started.
|`handler_installed` ^.^|boolean |Indicates whether the event handler is installed.
|`port_name` ^.^|const char* |Contains the name of the Test Port instance. (NUL character terminated string)
|======================================================================================================

Underscore characters are not duplicated in port name. In case of port array member instances the name string looks like this: `"Myport_array[5]"`.

== Support of `address` Type

The special user-defined TTCN–3 type `address` can be used for addressing entities inside the SUT on ports mapped to the `system` component. Since the majority of Test Ports does not need TTCN–3 addressing and in order to keep the Test Port API backward compatible the support of `address` type is disabled by default. To enable addressing on a particular port type the extension attribute `"address"` must be added to the TTCN–3 port type definition. In addition to component references this extension will allow the usage `address` values or variables in the `to` or `from` clauses and `sender` redirects of port operations.

In order to use addressing, a type named `address` shall be defined in the same TTCN–3 module as the corresponding port type. Address types defined in other modules of the test suite do not affect the operation of the port type. It is possible to link several Test Ports that use different types for addressing SUT into the same executable test suite.

Test Ports that support SUT addressing have a slightly different API, which is considered when generating Test Port skeleton. This section summarizes only the differences from the normal API.

In the communication operations the test port author is responsible for handling the address information associated with the message or the operation. In case of an incoming message or operation the value of the received address will be stored in the port queue together with the received message or operation.

The generated code for the port skeleton of message based ports will be the same, except `outgoing_send` member function, which has an extra parameter pointing to an `ADDRESS` value. With the example given in <<test-port-functions, Test Port Functions>>:
[source]
----
void outgoing_send(const INTEGER& send_par,
                   const ADDRESS *destination_address);
void outgoing_send(const CHARSTRING& send_par,
                   const ADDRESS *destination_address);
----

If an `address` value was specified in the `to` clause of the corresponding TTCN–3 `send` operation the second argument of `outgoing_send` points to that value. Otherwise it is set to the `NULL` pointer. The Test Port code shall be prepared to handle both cases.

The outgoing operations of procedure based ports are also generated in the same way if the `address` extension is specified. These functions will also have an extra parameter. Based on our example, these will have the following form:
[source]
----
705
void outgoing_call(const MyExample::outProc_call& call_par,
Elemer Lelik's avatar
Elemer Lelik committed
706
                   const ADDRESS *destination_address);
707
void outgoing_call(const MyExample::inoutProc_call& call_par,
Elemer Lelik's avatar
Elemer Lelik committed
708
                   const ADDRESS *destination_address);
709
void outgoing_reply(const MyExample::inProc_reply& reply_par,
Elemer Lelik's avatar
Elemer Lelik committed
710
                    const ADDRESS *destination_address);
711
void outgoing_reply(const MyExample::inoutProc_reply& reply_par,
Elemer Lelik's avatar
Elemer Lelik committed
712
                    const ADDRESS *destination_address);
713
void outgoing_raise(const MyExample::inProc_exception& raise_exception,
Elemer Lelik's avatar
Elemer Lelik committed
714
                    const ADDRESS *destination_address);
715
void outgoing_raise(const MyExample::inoutProc_exception& raise_exception,
Elemer Lelik's avatar
Elemer Lelik committed
716
717
718
719
720
721
                    const ADDRESS *destination_address);
----

The other difference is in the `incoming_message` member function of class `MyMessagePort_BASE`, and in the incoming member functions of class `MyProcedurePort_BASE`. These have an extra parameter, which is a pointer to an `ADDRESS` value. The default value is set the NULL pointer. In our example of `MyMessagePort_BASE`:
[source]
----
722
void incoming_call(const MyExample::inProc_call& incoming_par,
Elemer Lelik's avatar
Elemer Lelik committed
723
                   const ADDRESS *sender_address = NULL);
724
void incoming_call(const MyExample::inoutProc_call& incoming_par,
Elemer Lelik's avatar
Elemer Lelik committed
725
                   const ADDRESS *sender_address = NULL);
726
void incoming_reply(const MyExample::outProc_reply& incoming_par,
Elemer Lelik's avatar
Elemer Lelik committed
727
                    const ADDRESS *sender_address = NULL);
728
void incoming_reply(const MyExample::inoutProc_reply& incoming_par,
Elemer Lelik's avatar
Elemer Lelik committed
729
                    const ADDRESS *sender_address = NULL);
730
void incoming_exception(const MyExample::outProc_exception& incoming_par,
Elemer Lelik's avatar
Elemer Lelik committed
731
                        const ADDRESS *sender_address = NULL);
732
void incoming_exception(const MyExample::inoutProc_exception& incoming_par,
Elemer Lelik's avatar
Elemer Lelik committed
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
                        const ADDRESS *sender_address = NULL);
----

If the event handler of the Test Port can determine the source address where the message or the operation is coming from, it shall pass a pointer to the incoming function, which points to a variable that stores the `address` value. The given address value is not modified by the run-time environment and a copy of it is created when the message or the operation is appended to the port queue. If the event handler is unable to determine the sender address the default `NULL` pointer shall be passed as second argument.

The address value stored in the port queue is used in `receive`, `trigger`, `getcall`, `getreply`, `catch` and `check` port operations: it is matched with the `from` clause and/or stored into the variable given in the `sender` redirect. If the receiving operation wants to use the address information of the first element in the port queue, but the Test Port has not supplied it a dynamic testcase error will occur.

== Provider Port Types

Test Ports that belong to port types marked with `extension` attribute `"provider"` have a slightly different API. Such port types are used to realize dual-faced ports, the details of which can be found in section "Dual-faced ports" in the link:https://github.com/eclipse/titan.core/tree/master/usrguide/referenceguide[Programmer's Technical Reference].

The purpose of this API is to allow the re-use of the Test Port class with other port types marked with attribute `user` or with ports with translation capability (link:https://www.etsi.org/deliver/etsi_es/202700_202799/202781/01.04.01_60/es_202781v010401p.pdf[Methods for Testing and Specification (MTS); The Testing and Test Control Notation version 3; TTCN-3 Language Extensions: Configuration and Deployment Support]). The user port types may have different lists of incoming and outgoing message types. The transformations between incoming and outgoing messages, which are specified entirely by the attribute of the user port type, are done independently of the Test Port. The Test Port needs to support the sending and reception of message types that are listed in the provider port type.

The provider port can be accessed through the port which maps to the port with provider attribute. The `get_provider_port()` is a member function of the PORT class:
[source, subs="+quotes"]
PORT* get_provider_port();

This function is useful when a reference to the provider type is needed. It returns the provider port type for user ports and ports with translation capability. Otherwise returns NULL. The function causes dynamic testcase error when the port has more than one mapping, or the port has both mappings and connections. The function’s return value must be manually cast to the correct provider port type.

This section summarizes only the differences from the normal Test Port API:

* The name of the Test Port class is suffixed with the string `_PROVIDER` (for example `MyMessagePort_PROVIDER` instead of `MyMessagePort`).

* The base class of the Test Port is class `PORT`, which is part of the Base Library. Please note that normal Test Ports are also derived from class PORT, but indirectly through an intermediate class with suffix `_BASE`.

* The member functions that handle incoming messages and procedure-based operations (that is `incoming_message`, `incoming_call`, `incoming_reply` and `incoming_exception`) must be defined in the header file as pure virtual functions. These functions will be implemented in various descendant classes differently.

* The Test Port header file must not include the generated header file of the corresponding TTCN–3 module. The common header file of the Base Library called TTCN3.hh shall be included instead. The source file of the Test Port may include any header file without restriction.

762
763
764
* The member functions of the Test Port may refer to {cpp} classes that are generated from user-defined message types and signatures.
To avoid compilation failures the declarations of the referenced classes must be added to the beginning of the header file. 
At the moment the Test Port skeleton generator has a limitation that it cannot collect the class declarations from the port type, so they must be added manually. Please note that if a message type or signature is imported from another module the corresponding class declaration must be put into the appropriate namespace.
Elemer Lelik's avatar
Elemer Lelik committed
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784

The following example shows the generated Test Port skeleton of a provider port type.

Port type definition in TTCN–3 :
[source]
----
type port MyProviderPort mixed {
  inout MyMessage, MySignature;
} with { extension "provider" }
----

Header file `MyMessagePort.hh`:
[source]
----
// This Test Port skeleton header file was generated by the
// TTCN-3 Compiler of the TTCN-3 Test Executor version 1.7.pl0
// for Janos Zoltan Szabo (ejnosza@EG70E00202E46JR)
// on Wed Mar 7 18:14:33 2007


balaskoa's avatar
balaskoa committed
785
// Copyright (c) 2000-2020 Ericsson Telecom AB
Elemer Lelik's avatar
Elemer Lelik committed
786
787
788
789
790
791
792
793
794
795
796

// You may modify this file. Add your attributes and prototypes of your
// member functions here.


#ifndef MyProviderPort_HH
#define MyProviderPort_HH


#include <TTCN3.hh>

797
// Note: Header file MyExample.hh must not be included into this file!
Elemer Lelik's avatar
Elemer Lelik committed
798
799
800
801
802
803
804
// Class declarations were added manually

namespace MyOtherModule {
  // type MyMessageType was imported from MyOtherModule
  class MyMessageType;
}

805
namespace MyExample {
Elemer Lelik's avatar
Elemer Lelik committed
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851

// signature MySignature was defined locally
class MySignature_call;
class MySignature_reply;
class MySignature_exception;
class MyProviderPort_PROVIDER : public PORT {
public:
  MyProviderPort_PROVIDER(const char *par_port_name = NULL);
  ~MyProviderPort_PROVIDER();

  void set_parameter(const char *parameter_name,
    const char *parameter_value);

  void Event_Handler(const fd_set *read_fds,
    const fd_set *write_fds, const fd_set *error_fds,
    double time_since_last_call);

protected:
  void user_map(const char *system_port);
  void user_unmap(const char *system_port);

  void user_start();
  void user_stop();

  void outgoing_send(const MyOtherModule::MyMessage& send_par);
  void outgoing_call(const MySignature_call& call_par);
  void outgoing_reply(const MySignature_reply& reply_par);
  void outgoing_raise(const MySignature_exception& raise_exception);
  virtual void incoming_message(
    const MyOtherModule::MyMessage& incoming_par) = 0;
  virtual void incoming_call(const MySignature_call& incoming_par) = 0;
  virtual void incoming_reply(const MySignature_reply& incoming_par) = 0;
  virtual void incoming_exception(
    const MySignature_exception& incoming_par) = 0;
};

} /* end of namespace */
----

Source file `MyMessagePort.cc`:
[source]
----
// This Test Port skeleton source file was generated by the
// TTCN-3 Compiler of the TTCN-3 Test Executor version 1.7.pl0
// for Janos Zoltan Szabo (ejnosza@EG70E00202E46JR)
// on Wed Mar 7 18:14:33 2007
balaskoa's avatar
balaskoa committed
852
// Copyright (c) 2000-2020 Ericsson Telecom AB
Elemer Lelik's avatar
Elemer Lelik committed
853
854
855
856
// You may modify this file. Complete the body of empty functions and
// add your member functions here.

#include "MyProviderPort.hh"
857
#include "MyExample.hh"
Elemer Lelik's avatar
Elemer Lelik committed
858

859
namespace MyExample {
Elemer Lelik's avatar
Elemer Lelik committed
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927

MyProviderPort_PROVIDER::MyProviderPort_PROVIDER(const char *par_port_name)
  : PORT(par_port_name)
{
}

MyProviderPort_PROVIDER::~MyProviderPort_PROVIDER()
{
}

void MyProviderPort_PROVIDER::set_parameter(const char *parameter_name,
  const char *parameter_value)
{
}

void MyProviderPort_PROVIDER::Event_Handler(const fd_set *read_fds,
  const fd_set *write_fds, const fd_set *error_fds,
  double time_since_last_call)
{
}

void MyProviderPort_PROVIDER::user_map(const char *system_port)
{
}

void MyProviderPort_PROVIDER::user_unmap(const char *system_port)
{
}

void MyProviderPort_PROVIDER::user_start()
{
}

void MyProviderPort_PROVIDER::user_stop()
{
}

void MyProviderPort_PROVIDER::outgoing_send(
  const MyOtherModule::MyMessage& send_par)
{
}

void MyProviderPort_PROVIDER::outgoing_call(
  const MySignature_call& call_par)
{
}

void MyProviderPort_PROVIDER::outgoing_reply(
  const MySignature_reply& reply_par)
{
}

void MyProviderPort_PROVIDER::outgoing_raise(
  const MySignature_exception& raise_exception)
{
}

} /* end of namespace */
----

== Tips and Tricks

The following sections deal with logging and error handling in Test Ports.

=== Logging

Test Ports may record important events in the Test Executor log during sending/receiving or encoding/decoding messages. Such log messages are also good for debugging fresh code.

928
The Test Port member functions may call the functions of class `TTCN_Logger`. These functions are detailed in <<7-tips_&_troubleshooting.adoc#logging-in-test-ports-or-external-functions, Logging in Test Ports or External Functions>>.
Elemer Lelik's avatar
Elemer Lelik committed
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954

If there are many points in the Test Port code that want to log something, it can be a good practice to write a common log function in the Test Port class. We show here an example function, which takes its arguments as the standard C function `printf` and forwards the message to the Test Executor’s logger:

[source]
----
#include <stdarg.h>
// using in other member functions:
// log("The value of i: %d", i);
void MyPortType::log(const char *fmt, ...)
{
  // this flag can be a class member, which is configured through a
  // test port parameter
  if (logging_is_enabled) {
    va_list ap;
    va_start(ap, fmt);
    TTCN_Logger::begin_event(TTCN_DEBUG);
    TTCN_Logger::log_event("Example Test Port (%s): ", get_name());
    TTCN_Logger::log_event_va_list(fmt, ap);
    TTCN_Logger::end_event();
    va_end(ap);
  }
}
----

=== Error Handling

955
None of the Test Port member functions have return value like a status code. If a function returns normally, the run-time environment assumes that it has performed its task successfully. The handling of run-time errors is done in a special way, using {cpp} exceptions. This simplifies the program code because the return values do not have to be checked everywhere and dynamically created complex error messages can be used if necessary.
Elemer Lelik's avatar
Elemer Lelik committed
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986

If any kind of fatal error is encountered anywhere in the Test Port, the following function should be called:
[source, subs="+quotes"]
void TTCN_error(const char *err_msg, …);

Its parameter should contain the description of the error in a `NUL` terminated string in the format of `printf(3)`. You may pass further parameters to `TTCN_error`, if necessary. The function throws an exception, so it never returns. The exception is usually caught at the end of the test case or PTC function that is being executed. In case of error, the verdict of the component is set to `error` and the execution of the test case or PTC function terminates immediately.

The exception class is called `TC_Error`. For performance reasons this is a trivial (empty) class, that is, it does not contain the error message in a string. The error string is written into the log file by `TTCN_error` immediately. Such type of exception should never be caught or thrown directly. If you want to implement your own error handling and error recovery routines you had better use your own classes as exceptions.

If you write your own error reporting function you can add automatically the name of the port instance to all of your error messages. This makes the fault analysis for the end-users easier. In the following example the error message will occupy two consecutive lines in the log since we can pass only one format string to `TTCN_error`.
[source]
----
void MyPortType::error(const char *msg, ...)
{
  va_list ap;
  va_start(ap, msg);
  TTCN_Logger::begin_event(TTCN_ERROR);
  TTCN_Logger::log_event("Example Test Port (%s): ", get_name());
  TTCN_Logger::log_event_va_list(msg, ap);
  TTCN_Logger::end_event();
  va_end(ap);
  TTCN_error("Fatal error in Example Test Port %s (see above).",
    get_name());
}
----

There is another function for denoting warnings (that is, events that are not so critical) with the same parameter list as TTCN_error:
[source, subs="+quotes"]
void TTCN_warning(const char *warning_msg, …);

This function puts an entry in the executor’s log with severity `TTCN_WARNING`. In contrast to `TTCN_error`, after logging the given message `TTCN_warning` returns and your test port can continue running.
987
988
989

== Setting timestamps

990
In order to use the timestamp redirects (`-> timestamp`) described in chapter 5 of the TTCN-3 standard extension `TTCN-3 Performance and Real Time Testing` (ETSI ES 202 782 V1.3.1, <<8-references.adoc#_16, [16]>>) the test port writer needs to add extra code to set the timestamps for the incoming and outgoing port operations of each port with the `realtime` clause.
991
992
993
994
995
996
997
998
999

=== Incoming operations

The timestamps of incoming port operations (`receive`, `trigger`, `getcall`, `getreply`, `catch` and `check`) need to be set when the incoming message or procedure is added to the queue.

The member functions `incoming_message`, `incoming_call`, `incoming_reply` and `incoming_exception` (which add the message/procedure to the queue) have an optional `float` parameter called `timestamp`, if the test port was declared with the `realtime` clause.

The value given to this parameter will be the one stored in the variable referenced in the timestamp redirect, if the operation has a timestamp redirect (otherwise the value is ignored).

1000
It is recommended that this parameter be set to the current test system time, which can be queried with `TTCN_Runtime::now()`, or to a float variable that was set to the current test system time earier in the function.
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026

Examples:
[source]
----
incoming_message(my_message, TTCN_Runtime::now());
----

[source]
----
FLOAT reply_time = TTCN_Runtime::now();

...

incoming_reply(my_reply, reply_time);
----

=== Outgoing operations

The timestamps of outgoing port operations (`send`, `call`, `reply`, `raise`) need to be set in the member functions `outgoing_send`, `outgoing_call`, `outgoing_reply` and `outgoing_raise`.

These functions have a `float` pointer parameter called `timestamp_redirect`, if the test port was declared with the `realtime` clause.

The value pointed to by this parameter will be the one stored in the variable referenced in the timestamp redirect, if the operation has a timestamp redirect.

If it does not have a timestamp redirect, then this pointer parameter will be `NULL`. Because of this, the parameter must always have a null pointer check before it is assigned a value.

1027
It is recommended that the value pointed to by the parameter be set to the current test system time, which can be queried with `TTCN_Runtime::now()`.
1028
1029
1030
1031
1032
1033
1034
1035
1036

Example:
[source]
----
if (timestamp_redirect != NULL) {
  *timestamp_redirect = TTCN_Runtime::now();
}
----

balaskoa's avatar
Typo    
balaskoa committed
1037
NOTE: Because of this extra parameter, adding or removing the `realtime` clause from a port will cause already-written C++ code for the port to no longer compile. In these cases the new parameters must be manually added or removed from the mentioned functions, or the user-written code copied to newly-generated test port skeletons.
1038