Erlang -- Event Handling

User's Guide
Reference Manual
Release Notes
PDF
Top

Common Test
User's Guide
Version 1.11

Expand All
Contract All

Chapters

- Common Test Basics
  - Top of chapter
  - Introduction
  - Test Suite Organisation
  - Support Libraries
  - Suites and Test Cases
  - External Interfaces
- Getting Started
  - Top of chapter
  - Are you new around here?
  - Test case execution
  - A simple test suite
  - A test suite with configuration functions
  - What happens next?
- Installation
  - Top of chapter
  - General information
- Writing Test Suites
  - Top of chapter
  - Support for test suite authors
  - Test suites
  - Init and end per suite
  - Init and end per test case
  - Test cases
  - Test case info function
  - Test suite info function
  - Test case groups
  - The parallel property and nested groups
  - Parallel test cases and IO
  - Repeated groups
  - Shuffled test case order
  - Group info function
  - Info functions for init- and end-configuration
  - Data and Private Directories
  - Execution environment
  - Timetrap timeouts
  - Logging - categories and verbosity levels
  - Illegal dependencies
- Test Structure
  - Top of chapter
  - Test structure
  - Skipping test cases
  - Definition of terms
- Examples and Templates
  - Top of chapter
  - Test suite example
  - Test suite templates
- Running Tests and Analyzing Results
  - Top of chapter
  - Using the Common Test Framework
  - Automatic compilation of test suites and help modules
  - Running tests from the OS command line
  - Running tests from the Erlang shell or from an Erlang program
  - Test case group execution
  - Running the interactive shell mode
  - Step by step execution of test cases with the Erlang Debugger
  - Test Specifications
  - Running tests from the Web based GUI
  - Log files
  - HTML Style Sheets
  - Repeating tests
  - Silent Connections
- External Configuration Data
  - Top of chapter
  - General
  - Syntax
  - Requiring and reading configuration data
  - Using configuration variables defined in multiple files
  - Encrypted configuration files
  - Opening connections by using configuration data
  - User specific configuration data formats
  - Examples of configuration data handling
  - Example of user specific configuration handler
- Code Coverage Analysis
  - Top of chapter
  - General
  - Usage
  - Stopping the cover tool when tests are completed
  - The cover specification file
  - Cross cover analysis
  - Logging
- Using Common Test for Large Scale Testing
  - Top of chapter
  - General
  - Usage
  - Test Specifications
  - Automatic startup of test target nodes
- Event Handling
  - Top of chapter
  - General
  - Usage
- Dependencies between Test Cases and Suites
  - Top of chapter
  - General
  - Saving configuration data
  - Sequences
- Common Test Hooks
  - Top of chapter
  - General
  - Installing a CTH
  - CTH Scope
  - Manipulating tests
  - Synchronizing external user applications with Common Test
  - Example CTH
  - Built-in CTHs
- Some thoughts about testing
  - Top of chapter
  - Goals
  - What to test?

# 11 Event Handling

### 11.1 General

It is possible for the operator of a Common Test system to receive event notifications continously during a test run. It is reported e.g. when a test case starts and stops, what the current count of successful, failed and skipped cases is, etc. This information can be used for different purposes such as logging progress and results on other format than HTML, saving statistics to a database for report generation and test system supervision.

Common Test has a framework for event handling which is based on the OTP event manager concept and gen_event behaviour. When the Common Test server starts, it spawns an event manager. During test execution the manager gets a notification from the server every time something of potential interest happens. Any event handler plugged into the event manager can match on events of interest, take action, or maybe simply pass the information on. Event handlers are Erlang modules implemented by the Common Test user according to the gen_event behaviour (see the OTP User's Guide and Reference Manual for more information).

As already described, a Common Test server always starts an event manager. The server also plugs in a default event handler which has as its only purpose to relay notifications to a globally registered CT Master event manager (if a CT Master server is running in the system). The CT Master also spawns an event manager at startup. Event handlers plugged into this manager will receive the events from all the test nodes as well as information from the CT Master server itself.

User specific event handlers may be plugged into a Common Test event manager, either by telling Common Test to install them before the test run (see below), or by adding the handlers dynamically during the test run by means of gen_event:add_handler/3 or gen_event:add_sup_handler/3. In the latter scenario, the reference of the Common Test event manager is required. To get it, call ct:get_event_mgr_ref/0 or (on the CT Master node) ct_master:get_event_mgr_ref/0.

### 11.2 Usage

Event handlers may be installed by means of an event_handler start flag (ct_run) or option (ct:run_test/1), where the argument specifies the names of one or more event handler modules. Example:

$ ct_run -suite test/my_SUITE -event_handler handlers/my_evh1 handlers/my_evh2 -pa $PWD/handlers

Use the ct_run -event_handler_init option instead of -event_handler to pass start arguments to the event handler init function.

All event handler modules must have gen_event behaviour. Note also that these modules must be precompiled, and that their locations must be added explicitly to the Erlang code server search path (like in the example).

An event_handler tuple in the argument Opts has the following definition (see also ct:run_test/1 in the reference manual):

        {event_handler,EventHandlers}

        EventHandlers = EH | [EH]
        EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
        InitArgs = [term()]

Example:

        1> ct:run_test([{suite,"test/my_SUITE"},{event_handler,[my_evh1,{my_evh2,[node()]}]}]).

This will install two event handlers for the my_SUITE test. Event handler my_evh1 is started with [] as argument to the init function. Event handler my_evh2 is started with the name of the current node in the init argument list.

Event handlers can also be plugged in by means of test specification terms:

{event_handler, EventHandlers}, or

{event_handler, EventHandlers, InitArgs}, or

{event_handler, NodeRefs, EventHandlers}, or

{event_handler, NodeRefs, EventHandlers, InitArgs}

EventHandlers is a list of module names. Before a test session starts, the init function of each plugged in event handler is called (with the InitArgs list as argument or [] if no start arguments are given).

To plug a handler into the CT Master event manager, specify master as the node in NodeRefs.

For an event handler to be able to match on events, the module must include the header file ct_event.hrl. An event is a record with the following definition:

#event{name, node, data}

name is the label (type) of the event. node is the name of the node the event has originated from (only relevant for CT Master event handlers). data is specific for the particular event.

General events:

- #event{name = start_logging, data = LogDir}

  LogDir = string(), top level log directory for the test run.

  Indicates that the logging process of Common Test has started successfully and is ready to receive IO messages.

- #event{name = stop_logging, data = []}

  Indicates that the logging process of Common Test has been shut down at the end of the test run.

- #event{name = test_start, data = {StartTime,LogDir}}

  StartTime = {date(),time()}, test run start date and time.

  LogDir = string(), top level log directory for the test run.

  This event indicates that Common Test has finished initial preparations and will begin executing test cases.

- #event{name = test_done, data = EndTime}

  EndTime = {date(),time()}, date and time the test run finished.

  This indicates that the last test case has been executed and Common Test is shutting down.

- #event{name = start_info, data = {Tests,Suites,Cases}}

  Tests = integer(), the number of tests.

  Suites = integer(), the total number of suites.

  Cases = integer() | unknown, the total number of test cases.

  Initial test run information that can be interpreted as: "This test run will execute Tests separate tests, in total containing Cases number of test cases, in Suites number of suites". Note that if a test case group with a repeat property exists in any test, the total number of test cases can not be calculated (unknown).

- #event{name = tc_start, data = {Suite,FuncOrGroup}}

  Suite = atom(), name of the test suite.

  FuncOrGroup = Func | {Conf,GroupName,GroupProperties}

  Func = atom(), name of test case or configuration function.

  Conf = init_per_group | end_per_group, group configuration function.

  GroupName = atom(), name of the group.

  GroupProperties = list(), list of execution properties for the group.

  This event informs about the start of a test case, or a group configuration function. The event is sent also for init_per_suite and end_per_suite, but not for init_per_testcase and end_per_testcase. If a group configuration function is starting, the group name and execution properties are also given.

- #event{name = tc_logfile, data = {{Suite,Func},LogFileName}}

  Suite = atom(), name of the test suite.

  Func = atom(), name of test case or configuration function.

  LogFileName = string(), full name of test case log file.

  This event is sent at the start of each test case (and configuration function except init/end_per_testcase) and carries information about the full name (i.e. the file name including the absolute directory path) of the current test case log file.

- #event{name = tc_done, data = {Suite,FuncOrGroup,Result}}

  Suite = atom(), name of the suite.

  FuncOrGroup = Func | {Conf,GroupName,GroupProperties}

  Func = atom(), name of test case or configuration function.

  Conf = init_per_group | end_per_group, group configuration function.

  GroupName = unknown | atom(), name of the group (unknown if init- or end function times out).

  GroupProperties = list(), list of execution properties for the group.

  Result = ok | {auto_skipped,SkipReason} | {skipped,SkipReason} | {failed,FailReason}, the result.

  SkipReason = {require_failed,RequireInfo} | {require_failed_in_suite0,RequireInfo} | {failed,{Suite,init_per_testcase,FailInfo}} | UserTerm, the reason why the case has been skipped.

  FailReason = {error,FailInfo} | {error,{RunTimeError,StackTrace}} | {timetrap_timeout,integer()} | {failed,{Suite,end_per_testcase,FailInfo}}, reason for failure.

  RequireInfo = {not_available,atom() | tuple()}, why require has failed.

  FailInfo = {timetrap_timeout,integer()} | {RunTimeError,StackTrace} | UserTerm, detailed information about an error.

  RunTimeError = term(), a run-time error, e.g. badmatch, undef, etc.

  StackTrace = list(), list of function calls preceeding a run-time error.

  UserTerm = term(), arbitrary data specified by user, or exit/1 info.

  This event informs about the end of a test case or a configuration function (see the tc_start event for details on the FuncOrGroup element). With this event comes the final result of the function in question. It is possible to determine on the top level of Result if the function was successful, skipped (by the user), or if it failed. It is of course possible to dig deeper and also perform pattern matching on the various reasons for skipped or failed. Note that {'EXIT',Reason} tuples have been translated into {error,Reason}. Note also that if a {failed,{Suite,end_per_testcase,FailInfo} result is received, it actually means the test case was successful, but that end_per_testcase for the case failed.

- #event{name = tc_auto_skip, data = {Suite,TestName,Reason}}

  Suite = atom(), the name of the suite.

  TestName = init_per_suite | end_per_suite | {init_per_group,GroupName} | {end_per_group,GroupName} | {FuncName,GroupName} | FuncName

  FuncName = atom(), the name of the test case or configuration function.

  GroupName = atom(), the name of the test case group.

  Reason = {failed,FailReason} | {require_failed_in_suite0,RequireInfo}, reason for auto skipping Func.

  FailReason = {Suite,ConfigFunc,FailInfo}} | {Suite,FailedCaseInSequence}, reason for failure.

  RequireInfo = {not_available,atom() | tuple()}, why require has failed.

  ConfigFunc = init_per_suite | init_per_group

  FailInfo = {timetrap_timeout,integer()} | {RunTimeError,StackTrace} | bad_return | UserTerm, detailed information about an error.

  FailedCaseInSequence = atom(), name of a case that has failed in a sequence.

  RunTimeError = term(), a run-time error, e.g. badmatch, undef, etc.

  StackTrace = list(), list of function calls preceeding a run-time error.

  UserTerm = term(), arbitrary data specified by user, or exit/1 info.

  This event gets sent for every test case or configuration function that Common Test has skipped automatically because of either a failed init_per_suite or init_per_group, a failed require in suite/0, or a failed test case in a sequence. Note that this event is never received as a result of a test case getting skipped because of init_per_testcase failing, since that information is carried with the tc_done event. If a failed test case belongs to a test case group, the second data element is a tuple {FuncName,GroupName}, otherwise simply the function name.

- #event{name = tc_user_skip, data = {Suite,TestName,Comment}}

  Suite = atom(), the name of the suite.

  TestName = init_per_suite | end_per_suite | {init_per_group,GroupName} | {end_per_group,GroupName} | {FuncName,GroupName} | FuncName

  FuncName = atom(), the name of the test case or configuration function.

  GroupName = atom(), the name of the test case group.

  Comment = string(), reason for skipping the test case.

  This event specifies that a test case has been skipped by the user. It is only ever received if the skip was declared in a test specification. Otherwise, user skip information is received as a {skipped,SkipReason} result in the tc_done event for the test case. If a skipped test case belongs to a test case group, the second data element is a tuple {FuncName,GroupName}, otherwise simply the function name.

- #event{name = test_stats, data = {Ok,Failed,Skipped}}

  Ok = integer(), the current number of successful test cases.

  Failed = integer(), the current number of failed test cases.

  Skipped = {UserSkipped,AutoSkipped}

  UserSkipped = integer(), the current number of user skipped test cases.

  AutoSkipped = integer(), the current number of auto skipped test cases.

  This is a statistics event with the current count of successful, skipped and failed test cases so far. This event gets sent after the end of each test case, immediately following the tc_done event.

Internal events:

- #event{name = start_make, data = Dir}

  Dir = string(), running make in this directory.

  An internal event saying that Common Test will start compiling modules in directory Dir.

- #event{name = finished_make, data = Dir}

  Dir = string(), finished running make in this directory.

  An internal event saying that Common Test is finished compiling modules in directory Dir.

- #event{name = start_write_file, data = FullNameFile}

  FullNameFile = string(), full name of the file.

  An internal event used by the Common Test Master process to synchronize particular file operations.

- #event{name = finished_write_file, data = FullNameFile}

  FullNameFile = string(), full name of the file.

  An internal event used by the Common Test Master process to synchronize particular file operations.

The events are also documented in ct_event.erl. This module may serve as an example of what an event handler for the CT event manager can look like.

Note

To ensure that printouts to standard out (or printouts made with ct:log/2/3 or ct:pal/2/3) get written to the test case log file, and not to the Common Test framework log, you can syncronize with the Common Test server by matching on the tc_start and tc_done events. In the period between these events, all IO gets directed to the test case log file. These events are sent synchronously to avoid potential timing problems (e.g. that the test case log file gets closed just before an IO message from an external process gets through). Knowing this, you need to be careful that your handle_event/2 callback function doesn't stall the test execution, possibly causing unexpected behaviour as a result.