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.
The following example prints 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.
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.
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:
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.
event arrived is @main_car.arrived
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.
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 | |
- 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.
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.
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
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.
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.
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)")