Skip to content

Defining events

Events are transient objects that represent a point in time and can trigger actions defined in scenarios. You can define an event within a struct, but more typically within an actor or scenario.

Scenarios can emit events. Events are used to:

  • Cause scenarios waiting for that event to proceed.
  • Assign a sampled value to a field when that event occurs.
  • Collect coverage data when that event occurs.

Note that coverage or other metrics are impacted if they are emitted on these events. In fact any logic that is defined on these events will not take place if the events are not emitted.

An event declaration can include either event parameters or an event expression (using is). See Events for syntax and parameter definitions.

Predefined events

The following events are defined for every scenario:

  • start - Emitted when the scenario starts.

  • end - Emitted when the scenario ends successfully.

  • fail - Emitted when the scenario fails.

  • finish - Emitted when the scenario ends successfully or fails, after the end or fail events.

In scenarios that invoke other scenarios, each scenario may emit its own start, end, fail and finish events.

For global modifiers, start is emitted at the start of the test and end and finish are emitted at the end of the test, regardless of whether the test succeeded or failed.

Note

Coverage and performance metrics are not collected if the event triggering collection is not emitted. For example, if a scenario fails, coverage and performance metrics defined on the scenario's end event are not collected.

Example 1

The following example prints a message at the start of a scenario.

OSC2 code: print a message at the start of a scenario
extend top.main:
    sut_v: sut_vehicle

    do serial:
        drv1: sut_v.drive() with:
           duration([10..20]sec)
        drv2: sut_v.drive() with:
           duration([10..20]sec)

    on @drv2.start:
        logger.log_info("SUT speed at start of drv2 is $(sut_v.state.speed)")

See complete example.

Example 2

The following example records a value on the finish event so that it is captured even if the scenario fails. In the example, the scenario fails because the pedestrian crossing interferes with the drive, but min_time_to_collision is still recorded.

OSC2 code: record a value on finish event
extend top.main:
    sut_v: sut_vehicle
    npc: vehicle

    do parallel(overlap:equal):
        # Plan for SUT to drive with constant speed, without lane changes
        sut_v.drive() with:
            speed(30kph, run_mode: best_effort)
            keep_lane(run_mode: best_effort)
            duration([3..10]sec)
        # NPC will drive in the same lane as SUT, ahead of it,
        # and decrease its speed during the run. This will create
        # a collision course between the SUT and the NPC
        npc.drive() with:
            position(time: [3..5]second, ahead_of: sut.car, at: start)
            lane(same_as: sut.car, at: start)
            speed(25kph, at: start, run_mode: best_effort)
            change_speed([-10..-5]kph)
            avoid_collisions(false)
            keep_lane()

    # Calculate and record minimal time-to-collision

    var samples_count := 0
    var min_ttc : time
    on @top.clk:
        var curr_ttc := sut_v.get_ttc_to_object(npc)
        samples_count = samples_count + 1
        if curr_ttc < min_ttc or samples_count == 1:
            min_ttc = curr_ttc

    record(name: min_time_to_collision, expression: min_ttc, unit: millisecond, event: finish)

    # Simulate a scenario completion error.
    # min_time_to_collision will be recorded even though the scenario fails

    on elapsed(3sec):
        scenario_completion_error(incomplete_scenario, "Test reached max allowed duration")

See complete example.

Event expressions

Event expressions have two parts:

  • A Boolean expression.
  • An event path (a path expression that evaluates to another event in the program tree).

The event is emitted if the Boolean expression is true when the event specified by the event path occurs. If no event path is specified, the basic clock is used. If no Boolean expression is specified, the default is true.

Example 1: no event expression

You can declare events without an event expression. However, the event does not occur unless it is raised by an emit action. For example, the arrived event declared here must be emitted explicitly:

OSC2 code: event declaration without event expression
extend top.main:
    event arrived

See complete example

Example 2: event path

If you define an event with an event path leading to another event but no Boolean expression, the event occurs when the event specified by the path occurs. This type of event is called a bound event. In this example, arrived occurs when the arrived event in main_car occurs.

OSC2 code: event declaration with event path
    event arrived is @main_car.arrived
See complete example

Example 3: Boolean expression

If you define an event with a Boolean expression but no event path, the basic clock is used. The following event occurs when snow_depth is greater than 15 centimeters.

OSC2 code: event declaration with Boolean expression
    event deep_snow is (snow_depth > 15cm)

See complete example

Example 4: event with a condition

In this example, the intention is to create an event that is triggered 1 second after the start of simulation.

OSC2 code: event declaration with condition
1
2
3
4
extend top.main:
    var sim_start_time := sample(top.time, @start)
    event sim_start is (top.time - sim_start_time == 0s)
    event sim_start_plus_1 is (top.time - sim_start_time == 1s) # 1 second after simulation start
  • Line 2 samples top.time at the start of simulation. (Simulation does not usually start at 0.00.)
  • Line 3 creates an event that is triggered at the start of simulation.
  • Line 4 creates an event that is triggered 1 second after the start of simulation.

See complete example

Events with parameters

An event that is not defined with a qualified event can have parameters. An event's parameters are then accessible from a second event that references the parameters via a pseudo-variable declared by as <param-name>.

For example, the car_passing_by event is defined in vehicle with a parameter. The info struct contains fields specifying which vehicle is passing by and how much time until it passes by.

OSC2 code: event declaration with parameters
extend vehicle:
    event car_passing_by(info : pass_by_info)

You can then define a second event that is emitted when the vehicle passing by is encroaching_car and it will pass by in the current simulation step.

OSC2 code: using event parameters
extend top.main:

    event encroached_passed_by is @encroached_car.car_passing_by as dat  \
        if (dat.info.other_car == encroaching_car and dat.info.time_to_passing_by < config.test.step_time)

See complete example

Event access

There are several methods you can use to determine whether an event has occurred or to retrieve an event's parameters.

Event-related method Description
event_occurrences() -> uint Returns the number of times the event has been emitted.
event_occurred() -> bool Returns true if the event has been emitted at least once (equivalent to: event_occurrences() > 0)
event_data() -> <event_data_struct> Returns a struct containing the parameter values of the last emitted event. Returns null if the event hasn't been emitted yet.

The event_data() method can be used for events without parameters. In this case it returns any_event_data_struct, which is a struct with no fields.

Example 1: how to use all event access methods

OSC2 code: use of all event access methods
extend top.main:

  event e1(a: int, b: string)

  do serial:
    log("e1 has been emitted $(e1.event_occurrences()) times")
    log("e1 $(e1.event_occurred()?"has":"hasn't") been emitted")
    log("The latest e1 was emitted with: a=$(e1.event_data().a), b=$(e1.event_data().b)")

Example 2: check event occurrences

This example uses event_occurrences() and event_occurred() to check:

  • Whether an event occurred but was not reported.
  • Whether an event was reported but did not occur.
  • Whether the event count was correct.
OSC2 code: use of event_occurrences() and event_occurred
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

scenario sut.refill_fuel:
    nearest_station: length
    nearest_restaurant: length

    event passing_by_gas_station is (nearest_station < 2000m)
    event pick_up_food is (nearest_restaurant < 1000m)
    event interceptor_passed_by

    do serial:
       sut_drive: sut.car.drive() with:
           speed(speed: 30kph)
       emit interceptor_passed_by
       emit interceptor_passed_by
       emit interceptor_passed_by

extend test_config:
    set map = "$FTX_PACKAGES/maps/highway.xodr"

extend top.main:
    do serial(duration: [2..5]s):
       pt_to_event:sut.refill_fuel() with :
           keep(it.nearest_station == 1000m)
           keep(it.nearest_restaurant == 5000m)

    # Event occurs check
    on @pt_to_event.end if pt_to_event.passing_by_gas_station.event_occurrences() == 0:
        call other_error(assertion, "Event occurred $(pt_to_event.passing_by_gas_station.event_occurrences()) times, but not reported.")

    # Event does not occur check
    on @pt_to_event.end if pt_to_event.pick_up_food.event_occurred():
        call other_error(assertion, "Event pick_up_food reported but did not actually occur.")

    # Event occurred for the specified number of time
    on @pt_to_event.end if pt_to_event.interceptor_passed_by.event_occurrences() != 3 :
        call other_error(assertion, "Event occurrence count is wrong.")

Example 3: event_data()

This example uses event_data() to determine whether the event data is correct.

OSC2 code: use of event_data()
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

scenario sut.refill_fuel:
    nearest_station: length

    event passing_by_gas_station is (nearest_station < 2000m)
    event check_fuel(fuel_empty: bool, some_info: string)

    do serial():
        sut_drive: sut.car.drive() with:
           speed(speed: 30kph)
        emit check_fuel(true, "Passed_by")

extend test_config:
    set map = "$FTX_PACKAGES/maps/highway.xodr"

extend top.main:
    do serial(duration: [2..5]s):
       pt_to_event: sut.refill_fuel() with :
           keep(it.nearest_station == 1000m)

    on @pt_to_event.end if pt_to_event.check_fuel.event_data().fuel_empty == false:
       call other_error(assertion, "fuel_empty: $(pt_to_event.check_fuel.event_data().fuel_empty)")

    on @pt_to_event.end if pt_to_event.check_fuel.event_data().some_info != "Passed_by":
       call other_error(assertion, "some_info: $(pt_to_event.check_fuel.event_data().some_info)")