Skip to content

Composition operators

Composition operators invoke lower-level scenarios that serve as operands. These scenarios sometimes enforce implicit constraints on their operands. Composition operators have all the attributes of any scenario, such as start, end and fail events.

The serial operator is an example of a composition operator. It takes as operands one or more scenarios and executes them in sequence.

Most operators have an implicit serial operator. In other words, if they have more than one invocation in them, the invocations are put inside an implicit serial. Only parallel and first_of do not have implicit an serial operator.

first_of

Purpose

Run multiple scenarios in parallel until the first one terminates

Category

Composition operator

Syntax

first_of
    <scenario-list>
[<with-block>]

Syntax parameters

<scenario-list>
(Required) Is a list of two or more scenarios. Each scenario is on a separate line, indented consistently from the previous line.
<with-block>
(Optional) Is a list of one or more keep() constraints or scenario modifiers, where the members are listed on separate lines as a block or on the same line as with: and separated by semi-colons.

Description

This operator runs multiple scenarios in parallel until the first one terminates. The other members, if any, are abandoned, meaning they are stopped without emitting the end event or collecting coverage.

Example

This scenario uses first_of to set two trucks moving towards each other in parallel. Two wait actions are kicked off at the same time to end the scenario if truck1 passes through the meeting point without meeting truck2 or if it stops before the meeting point.

OSC2 code: composition operator first_of
    do first_of:
        parallel(overlap: any):
            truck1_drive: truck1.drive(duration: truck1_drive_duration) with:
                along(truck1_path)

            serial:
                truck2_wait: truck2.drive(duration: truck2_wait_duration) with:
                    along(truck2_path)
                    speed(0kph)
                truck2_startup: truck2.drive(duration: truck2_startup_duration)
                truck2_drive: truck2.drive(duration: truck2_drive_duration) with:
                    speed([10kph..15kph], at: start)
                    avoid_collisions(false)
        serial:
            wait @passed_meeting
            log_info("truck1 passed meeting point.")
        serial:
            wait @truck1_stopped
            log_info("truck1 stopped before meeting point.")

See complete example.

parallel

Purpose

Execute one or more secondary scenarios in parallel with a primary scenario

Category

Composition operator

Syntax

parallel[([[overlap: ] <mix-overlap-enum>][,
    [start_to_start: ] <time-exp>][,
    [end_to_end: ] <time-exp>][,
    [duration: ] <time-exp>])]:
    <invocation-list>
[<with-block>]

Syntax parameters

<invocation-list>

(Required) Is a list of the scenarios that you want to invoke in parallel. The first scenario listed is the primary scenario, and it determines the time and context.

These invocations can have the full syntax including argument list and member block.

<with-block>
(Optional) Is a list of one or more keep() constraints or scenario modifiers, where the members are listed on separate lines as a block or on the same line as with: and separated by semi-colons.

Scenario arguments

If no parameters are specified, the parentheses are optional.

overlap

Specifies the overlap between the primary scenario (the first scenario in the invocation list) and the other scenarios in the list. Overlap is one of the following:

  • any specifies that there is no constraint on the amount of overlap between the primary and secondary scenarios.
  • full specifies that the secondary scenarios fully overlap the primary one. In other words, the secondary scenarios do not start later than or end earlier than the primary scenario: start_to_start <= 0, end_to_end >= 0.
  • equal specifies that all scenarios start and end at the same point in time: start_to_start = 0 and end_to_end = 0.
  • inside specifies that the secondary scenarios are fully overlapped by the primary scenario. In other words, the secondary scenarios do not start earlier or end later than the primary scenario: start_to_start >= 0, end_to_end =< 0.
  • initial specifies that the secondary scenarios cover at least the start of the primary scenario. In other words, the secondary scenarios do not start later than the primary scenario: start_to_start <= 0, end_to_end can be anything.
  • final specifies that the secondary scenarios cover at least the end of the primary scenario. In other words, the secondary scenarios do not end earlier than the primary scenario: end_to_end >= 0, start_to_start can be anything.
  • start (default) specifies that all scenarios start together: start_to_start = 0.
  • end specifies that all scenarios end together: end_to_end = 0.

Note

The various overlap parameters apply separately for each secondary scenario. The secondary scenarios do not have to overlap each another in the manner described. For example, parallel(a,b,c,d) is really parallel(parallel(parallel(a,b),c),d). The image below shows the various types of overlap between the primary scenario and a secondary one.

start_to_start, end_to_end
Are the time offsets of the secondary scenarios relative to the primary one.
<time-exp>
Is an expression of type time. It may be a single value or a range of values.

Description

The parallel operator describes the activities of two or more actors that overlap in some way. For example:

OSC2 code: composition operator parallel
extend top.main:
    do p1: parallel(overlap:equal):
        car1.drive()
        environment.weather(kind: clear)

See complete example.

To describe consecutive activities of multiple actors, invoke parallel multiple times from within the serial operator. For example:

OSC2 code: composition operator parallel() used to describe consecutive actions
extend top.main:
    do s1: serial:
        p1: parallel(overlap:equal):
            car1.drive()
            environment.weather(kind: clear)
        p2: parallel(overlap:equal):
            car1.drive()
            environment.weather(kind: rain)

See complete example.

Each invocation of parallel is referred to informally as a phase. Each activity (or nested scenario) commences at the start of a phase. A phase ends when any of the following conditions is met:

  • If all of the scenario’s nested scenarios end, the scenario ends.
  • If a duration parameter is specified for a phase, then it ends when the specified duration has been reached.

Failure of any member causes parallel to fail.

parallel() is non-symmetrical: the first scenario is primary. For example, given the following definition of the sut.cut_in_with_rain() scenario, rainstorm and cut_in begin at the same time, but rainstorm may end first.

OSC2 code: primacy of first scenario in parallel()
scenario sut.cut_in_with_rain:
    do parallel(overlap:equal):
        cut_in()
        rainstorm()

See complete example.

However, given the following definition, cut_in may end first.

OSC2: overlap:any
scenario sut.cut_in_with_rain:
    do parallel(overlap:any):
        rainstorm()
        cut_in()

See complete example.

Note

Scenarios with zero time are not allowed within parallel operators.

serial

Purpose

Execute two or more scenarios in a serial fashion

Category

Composition operator

Syntax

serial[([duration: ]<time-exp>)]:
    <scenario>+
[<with-block>]

Syntax parameters

<scenario>+
(Required) Is a list of the scenarios that you want to invoke.
<with-block>
(Optional) Is a list of one or more keep() constraints or scenario modifiers, where the members are listed on separate lines as a block or on the same line as with: and separated by semi-colons.

Scenario arguments

<time-exp>

(Optional) Is an expression of type time specifying how long the activities occur.

If no parameters are specified, the parentheses are optional.

Description

In serial execution mode, each member starts when its predecessor ends. The scenario ends when the last member ends. Failure of any member causes serial to fail.

The default execution for all scenarios you create is serial, with no gaps. You can add gaps between scenarios using the wait or wait_time scenarios.

Example

In the following example, a gap of 3 to 5 seconds is added between the execution of first_scenario() and second_scenario().

OSC2 code: composition operator serial()
scenario sut.my_scenario:
    do serial():
        fs: top.first_scenario()
        w1: wait elapsed([3..5]second)
        ss: top.second_scenario()

See complete example.