Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
7-tips_and_troubleshooting.adoc 19.16 KiB

Tips & Troubleshooting

Information not fitting in any of the previous chapters is given in this chapter.

Migrating Existing C++ Code to the Naming Rules of Version 1.7

When using the new naming rules[1] the compiler generates a C++ namespace for each TTCN–3 and ASN.1 module. The name of the namespace corresponds to the module. The generated C++ entities of a module are all placed in its namespace; therefore all the test port or protocol module code must use these namespaces.

Rules to follow when writing C++ code:

  • When referencing an entity located in a different module its C++ name has to be prefixed with the namespace name of that module.

  • A test port class must be placed into the namespace of its module.

  • Encoding and decoding functions must be placed into the namespace of the TTCN–3 module in which the external function was defined.

  • All C++ entities have to be placed into namespace. An exception to this may be C++ entities used only locally; these are defined with the keyword static.

  • For convenience the using namespace directive can be used in C++ source files. It is forbidden to use this directive in header files!

  • C++ enum types are placed in the scope of their value class; enum types have to be prefixed by the C++ name of the value class.[2]

Using External C++ Functions in TTCN–3 Test Suites

Sometimes standard library functions[3] are called in the test suite or there is a need for efficiently implemented "bit-crunching" functions in the TTCN–3 ATS. In these cases functions to be called from the test suite can be developed in C++.

There are the standard library functions as well as other libraries in the C++ functions. The logging and error handling facilities of the run-time environment are also available as in case of Test Ports.

Since version 1.4.pl1 the semantic analyzer of the compiler checks the import statements thoroughly. Therefore one cannot use the virtual C++ modules as before: C++ functions must be defined as external functions to be accessible from TTCN–3 modules.

For example, the following definitions make two C++ functions accessible from TTCN–3 module MyExample and from any other module that imports MyExample.

Example TTCN–3 Module (MyExample.ttcn)

module MyExample {
[...]
  external function MyFunction(integer par1, in octetstring par2)
    return bitstring;
  external function MyAnotherFunction(inout My_Type par1,
    out MyAnotherType par2);
[...]
}

The compiler will translate those external function definitions to C++ function prototypes in the generated header file MyExample.hh:

[...]
  extern BITSTRING MyFunction(const INTEGER& par1, const OCTETSTRING& par2);
  extern void MyAnotherFunction(My__Type& par1, MyAnotherType& par2);
[...]

Both pre-defined and user-defined TTCN–3 data types can be used as parameters and/or return types of the C++ functions. The detailed description of the equivalent C++ classes as well as the name mapping rules are described in chapter XML Encoding (XER).

Using templates as formal parameters in external functions is possible, but not recommended because the API of the classes realizing templates is not documented and subject to change without notice.

The formal parameters of external TTCN–3 functions are mapped to C++ function parameters according to the following table:

Table 35. TTCN–3 formal parameters and their C++ equivalents
TTCN–3 formal parameter Its C++ equivalent

[in] MyType myPar

const MyType& myPar

out MyType myPar

MyType& myPar

inout MyType myPar

MyType& myPar

[in] template MyType myPar

Not recommended.

In versions 1.6.pl3 and earlier the in keyword had an extra meaning in formal parameter lists. According to the TTCN–3 standard the parameter definitions MyType myPar and in MyType myPar are totally equivalent, but the earlier versions of the compiler distinguished them. Unless the keyword in was present the compiler passed the parameter by value (involving a copy constructor call) instead of using a const reference. That is why it was recommended to use an explicit in keyword in parameter lists of external functions.

Due to the strictness of the TTCN–3 semantic analyzer one cannot use C/C++ data types with external functions as formal parameters or return types, only TTCN–3 and ASN.1 data types are allowed. Similarly, one cannot use pointers as parameters or return values because they have no equivalents in TTCN–3 .

The external functions can be implemented in one or more C++ source files. The generated header file that contains the prototypes of the external functions shall be included into each C++ source file. This file makes accessible all built-in data types, the user-defined types of the corresponding TTCN–3 module and all available services of the run-time environment (logging, error handling, etc.).

The name, return type and the parameters of the implemented C++ functions must match exactly the generated function prototypes or the compilation will fail. The generated function prototype is in the namespace of the module, therefore the implementation of the function has to be placed in that namespace, too.

Logging in Test Ports or External Functions

When developing Test Ports or external functions the need may arise for debug messages. Instead of using printf or fprintf, there is a simple way to put these messages into the log file of test executor. This feature can be also useful in case when an error or warning situation is encountered in the Test Port, especially when decoding an incoming message.

There is a class called TTCN_Logger in the Base Library, which takes care of logging. For historical reasons it has a static instance (object), which is called TTCN_logger. Since all member functions of TTCN_Logger are static, they can be and should be called without the logger object. The usage of object TTCN_logger should be avoided in newly written code.

The class TTCN_Logger provides some public member functions. Using them any kind of message can be put into the log file. There are two ways to log a single message, the unbuffered and the buffered mode.

Unbuffered Mode

In unbuffered mode the message will be put into log immediately as a separate line together with a time stamp. Thus, the entire message must be passed to the logger class at one function call. The log member function of the logger class should be used. Its prototype is:

static void TTCN_Logger::log(int severity, const char *fmt, …);

The parameter severity is used for filtering the log messages. The allowed values of the parameter are listed in table "First level (coarse) log filtering" in the Programmer’s Technical Reference. We recommend using in Test Ports only TTCN_WARNING, TTCN_ERROR and TTCN_DEBUG. The parameter fmt is a pointer to a format string, which is interpreted as in printf(3). The dots represent the optional additional parameters that are referred in format string. There is no need to put a newline character at the end of format string; otherwise the log file will contain an empty line after your entry.

Here is an example, which logs an integer value:

int myVar = 5;
TTCN_Logger::log(TTCN_WARNING, ``myVar = %d'', myVar);

Sometimes the string to be logged is static. In such cases there is no need for printf-style argument processing, which may introduce extra risks if the string contains the character %. The logger class offers a function for logging a static (or previously assembled) string:

static void TTCN_Logger::log_str(int severity, const char *str);

The function log_str runs significantly faster than log because it bypasses the interpretation of the argument string.

There is another special function for unbuffered mode:

static void TTCN_Logger::log_va_list(int severity, const char *fmt,
	va_list ap);

The function log_va list resembles to log, but it takes the additional printf arguments in one va_list structure; va_list is defined in the standard C header file stdarg.h and used in functions with variable number of arguments.

This function (and especially its buffered mode version, log_event_va_list) is useful if there is a need for a wrapper function with printf-like syntax, but the message should be passed further to TTCN_Logger. With these functions one can avoid the handling of temporary buffers, which could be a significant performance penalty.

Buffered Mode

As opposite to the unbuffered operation, in buffered mode the logger class stores the message fragments in a temporary buffer. New fragments can be added after the existing ones. When finished, the fragments can be flushed after each other to the log file as a simple message. This mode is useful when assembling the message in many functions since the buffer management of logger class is more efficient than passing the fragments as parameters between the functions.

In buffered mode, the following member functions are available.

begin_event

begin_event creates a new empty event buffer within the logger. You have to pass the severity value, which will be valid for all fragments (the list of possible values can be found in the table "First level (coarse) log filtering" in the Technical Reference. If the logger already has an unfinished event when begin event is called the pending event will be pushed onto an internal stack of the logger. That event can be continued and completed after finishing the newly created event.

static void TTCN_Logger::begin_event(int severity);

log_event

log_event appends a new fragment at the end of current buffer. The parameter fmt contains a printf format string like in unbuffered mode. If you try to add a fragment without initializing the buffer by calling begin event, your fragment will be discarded and a warning message will be logged.

static void TTCN_Logger::log_event(const char *fmt, …);

log_char

log_char appends the character c at the end of current buffer. Its operation is very fast compared to log_event.

static void TTCN_Logger::log_char(char c);

log_event_str and log_event_va_list

The functions log_str and log_va_list also have the buffered versions called log_event_str and log_event_va_list, respectively. Those interpret the parameters as described in case of unbuffered mode.

static void TTCN_Logger::log_event_str(const char *str);
static void TTCN_Logger::log_event_va_list(const char *fmt, va_list ap);

OS_error

The function OS_error appends the textual description of the error code stored in global variable errno at the end of current buffer. Thereafter that variable errno will be set to zero. The function does nothing if the value of errno is already zero. For further information about possible error codes and their textual descriptions please consult the manual page of errno(3) and strerror(3).

static void TTCN_Logger::OS_error();

log

The C++ classes of predefined and compound data types are equipped with a member function called log. This function puts the actual value of the variable at the end of current buffer. Unbound variables and fields are denoted by the symbol <unbound>. The contents of TTCN–3 value objects can be logged only in buffered mode.

void <any TTCN-3 type>::log() const;

end_event

The function end_event flushes the current buffer into the log file as a simple message, then it destroys the current buffer. If the stack of pending events is not empty the topmost event is popped from the stack and becomes active. The time stamp of each log entry is generated at the end and not at the beginning. If there is no active buffer when end_event is called, a warning message will be logged.

static void TTCN_Logger::end_event();

If an unbuffered message is sent to the logger while the buffer contains a pending event the unbuffered message will be printed to the log immediately and the buffer remains unchanged.

Logging Format of TTCN-3 Values and Templates

TTCN-3 values and templates can be logged in the following formats:

TITAN legacy logger format: this is the default format which has always been used in TITAN

TTCN-3 format: this format has ttcn-3 syntax, thus it can be copied into TTCN-3 source files.

Differences between the formats:

Value/template Legacy format output TTCN-3 format output

Unbound value

"<unbound>"

"-"

Uninitialized template

"<uninitialized template>"

"-"

Enumerated value

name (number)

name

The "-" symbol is the NotUsedSymbol which can be used inside compound values, but when logging an unbound value which is not inside a record or record of the TTCN-3 output format of the logger is actually not a legal TTCN-3 value/template because a value or template cannot be set to be unbound. Thus this output format can be copy-pasted from a log file into a ttcn-3 file or to a module parameter value in a configuration file only if it semantically makes sense.

The C++ API extensions to change the logging format:
A new enum type for the format in TTCN_Logger class:+ enum data_log_format_t { LF_LEGACY, LF_TTCN };
Static functions to get/set the format globally:
data_log_format_t TTCN_Logger::get_log_format();void TTCN_Logger::set_log_format(data_log_format_t p_data_log_format);
A helper class to use a format until the end of the scope, when used as local variable. This can be used as follows:

{
    Logger_Format_Scope lfs(TTCN_Logger::LF_TTCN); // sets TTCN-3 log format
    <log some values and templates>
} // end of scope -> the original format is restored

It is recommended to use this helper class because using directly the format setting functions of TTCN_Logger is more error prone, if the globally used logging format is not restored properly then log files might contain values/templates in a mixed/unexpected format.

Examples

The example below demonstrates the combined usage of buffered and unbuffered modes as well as the working mechanism of the event stack:

TTCN_Logger::begin_event(TTCN_DEBUG);
TTCN_Logger::log_event_str("first ");
TTCN_Logger::begin_event(TTCN_DEBUG);
TTCN_Logger::log_event_str("second ");
TTCN_Logger::log_str(TTCN_DEBUG, "third message");
TTCN_Logger::log_event_str("message");
TTCN_Logger::end_event();
TTCN_Logger::log_event_str("message");
TTCN_Logger::end_event();

The above code fragment will produce three lines in the log in the following order:

third message second message first message

If the code calls a C++ function that might throw an exception while the logger has an active event buffer care must be taken that event is properly finished during stack unwinding. Otherwise the stack of the logger and the call stack of the program will get out of sync. The following example illustrates the proper usage of buffered mode with exceptions:

TTCN_Logger::begin_event(TTCN_DEBUG);
try {
  TTCN_Logger::log_event_str("something");
  // a function is called from here
  // that might throw an exception (for example TTCN_error())
  TTCN_Logger::log_event_str("something else");
  TTCN_Logger::end_event();
} catch (...) {
  // don’t forget about the pending event
  TTCN_Logger::end_event();
  throw;
}

Error Recovery during Test Execution

If a fatal error is encountered in the Test Port, you should call the function TTCN_error must be called to do the error handling. It has the following prototype in the Base Library:

void TTCN_error(const char *fmt, …);

The parameter fmt contains the reason of the error in a NUL terminated character string in the format of a printf format string. If necessary, additional values should be passed to TTCN_error as specified in the format string. The error handling in the executable test program is implemented using C++ exceptions so the function TTCN_error never returns; instead, it throws an exception. The exception value contains an instance of the empty class called TC_Error. This exception is normally caught at the end of each test case and module control part. After logging the reason TTCN_Logger::OS error() is called. Finally, the verdict is set to error and the test executor performs an error recovery, so it continues the execution with the next test case.

It is not recommended to use own error recovery combined with the default method (that is, catching this exception).

Using UNIX Signals

The UNIX signals may interrupt the normal execution of programs. This may happen when the program executes system calls. In this case, when the signal handler is finished the system call will fail and return immediately with an error code.

In the executable test program there are system calls not only in the Base Library, but in Test Ports as well. Since the other Test Ports that you are using may have been written by many developers, one cannot be sure that they are prepared to the effects of signals. So it is recommended to avoid using signals in Test Ports.

Mixing C and C++ Modules

Modules written in C language may be used in the Test Ports. In this case the C header files must be included into the Test Port source code and the object files of the C module must be linked to the executable. Using a C compiler to compile the C modules may lead to errors when linking the modules together. This is because the C and C++ compilers use different rules for mapping function names to symbol names of the object file to avoid name clashes caused by the C++ polymorphism. There are two possible solutions to solve this problem:

  1. Use the same C++ compiler to compile all of your source code (including C modules).

  2. If the first one is impossible (when using a third party software that is available in binary format only), the definitions of the C header file must be put into an extern "C" block like this.

#ifdef __cplusplus
extern "C" {
#endif

<... your C definitions ...>

#ifdef __cplusplus
};
#endif

The latter solution does not work with all C++ compilers; it was tested on GNU C++ compiler only.


1. The new naming rules are used by default; the naming rules can be changed using the compiler command line switch -N.
2. The enum hack option has become obsolete with the new naming rules.
3. C language functions cannot be called directly from TTCN–3; you need at least a wrapper function for them.