BP-Base

BP-Base is the common base language for all Provengo languages. Commands and constructs listed here are available throughout the system. Other Provengo languages build on this one, in the same way high-level programming languages (e.g. Java and Python) are built on top of lower-level ones (e.g. Assembly or C).

Use this language to create the higher-resolutions parts of your model, and to coordinate between model parts, especially when these are written in different languages. For larger model parts, or for parts that fit common conceptual models such as user stories or state machines, consider working with one of Provengo’s higher-level DSLs.

There is no need to import this language in order to use it - it’s always there for you.

Provengo Models and Behavioral Programming

At its basic level, a Provengo model consists of b-threads: Threads of simple behaviors that synchronize with each other using events. The Provengo engine interweaves these simple b-threads into complex scenarios. A scenario is a series of events - it can describe desired system behavior or a test scenario.

B-Threads synchronize by calling the sync method. Using this method, b-threads can request, wait for, and block events. After calling sync, the b-thread is paused until an event is selected. When all b-threads have called sync, the engine selects an event that was requested and not blocked. It then resumes b-threads that have requested or waited for the selected event. The rest of the b-threads remain paused. They may or may not resume execution after the next event is selected - depending on what event this will be.

sync has some convenience variants: request(e) is for only requesting event e, waitFor(es) is for waiting for events in the es event set, and block(es) blocks all events in es. These methods and also be used for composing requests - see Core Commands.

Events and Event Sets

Scenarios, such as those used for tests and during requirement evaluation, are a series of events. The events themselves are small data objects. Each one has a name, and might also have associated data. Event sets are objects that contain multiple events. They can be composed using methods such as set1.or(set2).

An event is also an event set - one that contains only itself.

The Event class

Event(name, data)

Creates an event whose name is name, and whose data field is data.

name

String. Name of the new event.

data

Optional. Object. The data field of the event.

Properties and Methods

event.name

String. The name of the event.

event.data

Object. The data field of the event. May be null.

All methods from EventSet.

Example: Utilizing Events to Arrange Cards in a Random Order

To fulfill the requirement of "Randomly arranging all cards in a deck", we can set an event for each card. Subsequently, we can employ 52 b-threads, each responsible for placing a unique card into the deck.

["Spades", "Hearts", "Clubs", "Diamonds"].forEach(function (type) {
    ["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"].forEach(function (card) {
        bthread(`Put {card} of {type}`, function () {
            sync({ request: Event("put", { card, type }) }); (1)
        });
    })
})
1 In this particular example, the event consistently carries the name 'put'. The 'data' field encapsulates the details of the card, including its rank and suit.

The EventSet class

EventSet(name, predicate)

Creates a new event set, based on predicate.

name

String. Name of the created event set.

predicate

Function. Gets an event, should return true if the event is a member of the event set, and false otherwise.

Returns: An event set named name, that uses predicate to decide event membership.

Properties and Methods

eventSet.name

String. The name of the event set.

eventSet.contains(e)

Returns true if the event e is a member of this set, and false otherwise.

eventSet.or(otherSet)

Returns a new event set that contains events that are members of this set, or members of otherSet.

eventSet.and(otherSet)

Returns a new event set that contains events that are members of this set and members of otherSet.

eventSet.negate()

Returns a new event set that contains all events this set does not contain.

eventSet.xor(otherSet)

Returns a new event set that contains events that are members of this set or members of otherSet, but not members of both.

eventSet.nor(otherSet)

Returns a new event set that contains events that are not members of either this or otherSet.

eventSet.nand(otherSet)

Returns a new event set that contains events that are not members of both this and otherSet.

Example: Using Event Sets to wait for a specific set of events

A b-thread that waits for all 13 clubs to be placed in the deck. An EventSet is created with the name 'Clubs' and a predicate that checks if the event’s data field contains the type 'Clubs'.

bthread(`Wait for all Clubs`, function () {
    for (let i = 0; i < 13; i++) {
        sync({ waitFor: EventSet("Clubs", e => e.data.type == "Clubs") }); (1)
    }
    bp.log.info("All Clubs are here!")
})
1 In this example, we use lambda functions to define the predicate for the EventSet. In this case, the predicate checks if the event’s data field contains the type 'Clubs'.

EventSets

EventSets.all

An event set that contains all events.

EventSets.none

An event set that does not contain any event.

EventSets.not(es)

An event set containing all events that are not members of es.

EventSets.anyOf(es1, es2, es3…​)

An event set that contains es1, es2, and es3. Similar to es1.or(es2).or(es3).

EventSets.allOf(es1, es2, es3…​)

An event set that is the conjunction of es1, es2, and es3 (so, contains only events that are in es1 and es2 AND es3). Similar to es1.and(es2).and(es3).

Example: Using Event Sets to wait for a specific set of events

We create two EventSets: one for the clubs and one for the aces. We then use EventSets.allOf to create a new EventSet that contains only events that are members of both the clubs and aces EventSets.

bthread(`Wait for Ace of Clubs`, function () {
    let clubsEventSet = EventSet("Clubs", e => e.data.type == "Clubs"); (1)
    let aceEventSet = EventSet("Ace", e => e.data.card == "Ace");

    sync({ waitFor: EventSets.allOf(clubsEventSet, aceEventSet) });
    bp.log.info("Ace of Clubs is here!")
})
1 For clarity and readability, it’s recommended to name the variables storing EventSets identically to the EventSet they represent.

Core Commands

Commands marked with sync contain a Synchronization Point.

bthread(name, data, body)

Adds a new b-thread to the specification. The optional data field allows passing information to the newly-created bthread.

name

Optional. String. The name of new b-thread.

data

Optional. Object. Initial data field for the new b-thread. Can be used to send data for the b-thread, so the b-thread can read it when it starts.

body

Function. The body of new b-thread. Does not take any parameters.

Returns: Nothing

Example 1. UsiExample: ng bthreads to implement a linear behavior

In this example, we use a b-thread to model the sequence of moves for the North player in a game of Bridge. The b-thread requests events for each card the player plays. The sequence of moves is linear, and the b-thread will pause after each move until the event is selected. This ensures the moves are played in the correct order. This b-thread is part of a larger program that models the entire game.

// The sequence of moves recoded for the North player in a game of Bridge
bthread('North', function () {
    request(Event("put", { player: "N", suit: "♠", card: "A" }))
    request(Event("put", { player: "N", suit: "♦", card: "3" }))
    request(Event("put", { player: "N", suit: "♣", card: "3" }))
    request(Event("put", { player: "N", suit: "♣", card: "5" }))
    request(Event("put", { player: "N", suit: "♥", card: "2" }))
    request(Event("put", { player: "N", suit: "♥", card: "5" }))
    request(Event("put", { player: "N", suit: "♦", card: "K" }))
    request(Event("put", { player: "N", suit: "♠", card: "Q" }))
    request(Event("put", { player: "N", suit: "♠", card: "2" }))
    request(Event("put", { player: "N", suit: "♦", card: "4" }))
    request(Event("put", { player: "N", suit: "♣", card: "7" }))
    request(Event("put", { player: "N", suit: "♥", card: "6" }))
    request(Event("put", { player: "N", suit: "♦", card: "Q" }))
});
Example: Using data to pass parameters to a b-thread

In this example, we pass a data object like {type: "Clubs", card: "Queen"} to the b-thread. The b-thread then uses this data to create a new event with the type 'Clubs'.

for( let type of ["Spades", "Hearts", "Clubs", "Diamonds"]) {
    for( let card of ["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]) {  (1)
        bthread(`Put {card} of {type}`, {type, card}, function () {
            sync({ request: Event("put", { card: bp.thread.data.card, type: bp.thread.data.type }) });
        })
    }
}
1 Be aware that type and card are global variables. Although they can be used within the b-thread, this approach is risky. All b-threads share the same pair of variables, and by the time a b-thread runs, these variables will contain the last assigned values ({type: "Diamonds", card: "King"}). To avoid this, it’s safer to pass parameters via the data object to the b-thread. This method ensures the values are copied when the b-thread is created, preserving their intended state. One can also use forEach, as shown in a previous example, because it creates a new scope for each iteration.

request(evt, fn)

When fn is present, executes it, and adds a request for evt on each synchronization point. When fn is missing, requests evt.

If evt is an array of events, the system is free to select any one of them that’s not blocked. This allows splitting a scenario to multiple ones. For example, in a use-case scenario where a customer needs to select one of three products, the bthread modeling it can request selection events for these products. At the point of the request, the test will be split into 3 test scenarios, one for each possible selection.

When fn is missing, this a convenient version of sync({request:evt}).
evt

Event/Event Array. The event to request, either immediately or as an addition to synchronization fn makes.

fn

Optional. Function. If present, executed as usual, except that in any synchronization it issues, evt is added to its requested events. fn does not take any parameters.

Returns: If fn is present, returns its result. Otherwise, returns the event selected during synchronization.

Example: Using request to propose three events for arbitrary selection.

In this example, we have three b-threads: Player A, Player B, and Winning Rules. The first two b-threads request events for each move in the Rock-Paper-Scissors game. The Winning Rules b-thread waits for two moves and then determines the winner. The game is played for three rounds. The Winning Rules b-thread blocks the move event after the three rounds are completed.

const NUMBER_OF_ROUNDS = 2;

bthread('Player A', function () {
    while (true) {
        request([
            Event("move", { player: "A", move: "Rock" }),
            Event("move", { player: "A", move: "Paper" }),
            Event("move", { player: "A", move: "Scissors" })]);

        waitFor(any("winning"));
    }
});


bthread('Player B', function () {
    while (true) {
        request([
            Event("move", { player: "B", move: "Rock" }),
            Event("move", { player: "B", move: "Paper" }),
            Event("move", { player: "B", move: "Scissors" })]);

        waitFor(any("winning"));
    }
});


bthread('Winning Rules', function () {
    for (let i = 0; i < NUMBER_OF_ROUNDS; i++) {
        let first = waitFor(any("move"));
        let second = waitFor(any("move"));

        if (first.data.move == second.data.move) {
            bp.log.info("It's a tie!");
            request(Event("winning", { win: "tie" }));
        } else if (
            (first.data.move == "Rock" && second.data.move == "Scissors") ||
            (first.data.move == "Scissors" && second.data.move == "Paper") ||
            (first.data.move == "Paper" && second.data.move == "Rock")) {
            bp.log.info(`Player ${first.data.player} wins!`);
            request(Event("winning", { win: first.data.player }));
        } else {
            bp.log.info(`Player ${second.data.player} wins!`);
            request(Event("winning", { win: first.data.player }));
        }
        first = second = undefined;
    }
    block(any("move"));
});

The state space of this program is:

rps

waitFor(evtSet, fn)

When fn is missing, waits for events in evtSet. When fn is present, executes it, and adds a wait-for evtSet at each synchronization point fn makes.

evtSet

An event set. The event set to wait for, either immediately or as an addition to synchronizations fn makes.

fn

Optional. Function. If present, executed as usual, except that in any synchronization it issues, it also waits for evtSet.

Returns: If fn is present, returns its result. Otherwise, returns the event selected during synchronization. fn does not take any parameters.

Example: Using the fn parameter of waitFor

Using the fn parameter to execute a function that waits until all four kings are successfully placed in the deck. During this function’s execution, it keeps requesting the event "Please give me more Kings".

let kingEventSet = EventSet("King", e => e.data.card == "King");
let moreKingsEvent = Event("Please give me more Kings", {type: "more", card: undefined});

bthread(`Wait for all Kings while requesting more`, function () {
    request(moreKingsEvent, function () {
        for (let i=0; i < 4; i++) {
            do {
                let e = waitFor(kingEventSet)
            } while(e.data.type == "more");
        }
    })
    e = undefined
    bp.log.info("All Kings are here!");
})

The state space of this b-thread is visually represented in the diagram below:

req fn

Note that the event "Please give me more Kings" (highlighted in yellow) is requested at each synchronization point. Triggering this event does not increment the counter, hence the b-thread goes back to its previous state before the event was activated.

When fn is missing, this a convenient version of sync({waitFor:evt}).

block(evtSet, fn)

When fn is missing, blocks events in evtSet. When fn is present, executes it while blocking events in evtSet.

evtSet

An event set. The event set to block.

fn

Optional. Function. If present, executed as usual, while blocking events in evtSet. fn does not take any parameters.

Returns: If fn is present, returns its result. Otherwise, returns the event selected during synchronization. Note that when not nested in any request or waitFor, it does not return, and the block stays for the remainder of the run.

When fn is missing, this a convenient version of sync({block:evt}).

Example:

Details

Using the fn parameter to execute a function that blocks aces until all kings are present. The function waits for "King" events four times and blocks "Ace" events while it waits for kings.

kingEventSet = EventSet("King", e => e.data.card == "King");
aceEventSet = EventSet("Ace", e => e.data.card == "Ace");

bthread(`Block Aces until all Kings are present`, function () {

    block(aceEventSet, function () {
        for (let i = 0; i < 4; i++) {
            waitFor(kingEventSet);  (1)
        }
    });
    bp.log.info("All Kings are here!");
})
1 A {block: aceEventSet} is implicitly added to the synchronization point of the waitFor(kingEventSet) function. This block prevents the selection of "Ace" events until all four "King" events are selected.

sync(statement)

Enters a synchronization point, based on the passed synchronization statement and any parent request, waitFor, block, and interrupt. All parameters are optional - it can also be used as sync(), in which case the statement contains only components from parent elements.

statement

Synchronization statement: A synchronization statement for this point

Returns: The selected event

Example: Using sync with a composite statement

Using sync with a composite statement that includes a waitFor and a block. The b-thread waits for the "King" events while blocking the "Ace" events.

kingEventSet = EventSet("King", e => e.data.card == "King");
aceEventSet = EventSet("Ace", e => e.data.card == "Ace");

bthread(`Block aces until the first king is present`, function () {
    sync({ waitFor: kingEventSet, block: aceEventSet });  (1)
    bp.log.info("The first king is here!");
})
1 When waitFor and block are passed in a synchronization statement object to sync, the calling b-thread waits for a set of events while blocking another. We can also use sync with a composite statement that includes a request and a waitFor or a block. This allows us to request an event while waiting for another event and/or blocking a third event.

interrupt(evtSet, fn)

Executes fn. During execution, terminates the b-thread if any event contained in evtSet is selected.

evtSet

Members of this event set will terminate the b-thread while fn is executing.

fn

The function to execute. Does not take any parameters.

Returns: The result of fn().

Example: Using data to pass parameters to a b-thread

A b-thread that prevents the occurrence of consecutive Spades events until the "Ace of Spades" event is trigered. The interrupt method is used to terminate the b-thread when the "Ace of Spades" event is selected, no matter where the b-thread is at that moment.

bthread('Do not allow consecutive Spades until Ace of Spades is present', function () {
    interrupt(Any({ card: 'Ace', type: "Spades" }), function () {    (1)
        while (true) {
            waitFor(Any({ type: "Spades" }));
            bp.log.info("Spades => next should not be of Spades");

            block(Any({ type: "Spades" }), function () {
                waitFor(bp.all);
            });
        }
    });
})
1 When invoked with an object, the Any(…​) method generates an event set containing all events whose data aligns with the object. In other words, it includes all events that possess the specified fields with corresponding values.

Extended Commands

choose(v1, v2, v3…​ / valueArray)

Choose one of the values passed. The choice is announced by a choiceEvent(value).

v1, v2, v3…​`

Object. Argument list of values to choose from.

valueArray

Array of Objects. Lists values to choose from.

Returns: One of the passed values.

Example: Using choose to select a random card

In this example, we use choose to randomly select a card from a deck. The selected card is then placed in the deck.

bthread(`Choose a card and put it`, function () {
    let type = choose(["Spades", "Hearts", "Clubs", "Diamonds"]); (1)
    let card = choose(["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]);

    request(Event("put", { card, type }));
})
1 This is similar to requesting the array [ChoiceEvent("Spades"), ChoiceEvent("Hearts"), ChoiceEvent("Clubs"), ChoiceEvent("Diamonds")]. The only difference is that, when using choose, the ChoiceEvent has precedence, meaning it will be chosen over other events when multiple options are available.

chooseSome(v1, v2, v3…​ / valueArray)

Chooses a sub-set of the passed values. The intermediate choices are announced by choiceEvent(value)s. In the returned array, element relative order is maintained. So a call to chooseSome(a,b,c) may return [a,c] but not [c,a].

v1, v2, v3…​

Object. Argument list of values to choose from.

valueArray

Array of Objects. Lists values to choose from.

Returns: An array containing a sub-set of the passed values. May be empty (which is a valid subset).

Example: Using chooseSome to select random cards

Using chooseSome to randomly select some cards and put them in the deck.

const types = ["Spades", "Hearts", "Clubs", "Diamonds"]
const cards = ["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]
const all = types.map(type => cards.map(card => ({ type, card }))).reduce((acc, val) => acc.concat(val), []); (1)

bthread(`Choose some cards and put them`, function () {
    let some = chooseSome(all);

    for (let c of some)
        sync({ request: Event("put", c) });
})
1 Here, all is the Cartesian product of types and cards.

halt(message)

Reports a requirement violation or breach (formally: safety requirement violation), and stops the model execution. During analysis, halts are shown as red hexagons.

halt does not report a test failure in the system-under-test. Rather, it reports a failure in the requirements. That is, the specification requires the system to behave in a bad way. For example, to violate company polices or a regulations, or just do something that does not "make sense". This is sometimes known as requirement-level testing. See Example below for more details.
message

A (typically short) human-readable text explaining why a scenario had to be halted.

Returns: Does not return, as it stops execution of the current scenario.

Example: Using halt to detect illegal scenarios for a beverage mixing controller

Suppose we’re testing a beverage mixing machine controller. The test itself is pretty simple - we have possible ingredients, and we mix three doses to create a single serving. Such serving can be pure coke, two doses of coke and one dose of lemon juice, two doeses of lemon juice and one Mentos candy, etc.

Now, it is well known that one should never mix Mentos and Coke. However, the following test model contains scenarios where Mentos and Coke are expected to be mixed. This is an example for a wrong requirement, yielding illegal scenarios - test scenarios in which the controller cannot win: it either fails the test, or ruining its mixing machine.

bthread("mixing machine", function(){
    let ingredients = [Event("Add Coke"), Event("Add Lemon"), Event("Add Mentos")];
    requestOne( ingredients );
    requestOne( ingredients );
    requestOne( ingredients );
});
Initial test space of the beverage mixing controller

To find these illegal scenarios, we add a bthread detecting such scenarios to the model. This bthread listens to bad mixtures combinations, and invokes halt if they happen.

// Detect mixing soda with Mentos
bthread("no soda gazer", function(){
    waitForAll( Event("Add Coke"), Event("Add Mentos"));
    halt("Don't mix soda with Mentos!");
});

After adding the above bthread, Provengo marks the illegal scenarios. Note that not only we know now that those scenarios exist, we also know which scenarios these are. We can also prevent them, be adding the two constraints below.

Test space with illegal scenarios marked. width=80%
Constraints.after(Event("Add Coke"))
            .block(Event("Add Mentos")).forever();
Constraints.after(Event("Add Mentos"))
            .block(Event("Add Coke")).forever();

After adding the above constraints, we get the below test space. We can see that it contains only legal test scenarios. We can now add an actuation layer and start testing the beverage mixer, knowing that any test we create will be based on a legal, valid test scenario.

Test space with illegal scenarios marked. width=80%
We’ve detected errors in the mixing controller’s requirements, without even having a mixing controller implemented. This is one of the coolest parts of Model Based Testing - you can test important traits of systems even before these systems exist!

inParallel(f1, f2, f3…​ / functionArray)

When called from within a b-thread, runs functions f1…​fn in parallel b-threads. Returns when all functions returned. When called from the the top-level b-program scope, creates a b-thread for each function and returns immediately.

f1, f2, f3…​

Functions to run in parallel. These functions do not take any parameters, and their return value is ignored.

functionArray

An array of functions to be run in parallel.

Returns: Nothing. When called from within a b-thread, returns after all functions completed their execution.

Example: Using inParallel to run functions in parallel

A b-thread that requests an event, and the second b-thread that waits for that event. The second b-thread then runs two functions in parallel, each of which requests two events. The second b-thread then requests another event.

bthread(`Run two b-threads in parallel`, function () {
    request(Event("Begin"));
});

bthread(`Run two b-threads in parallel`, function () {

    waitFor(Event("Begin"));

    inParallel(
        function () {
            sync({ request: Event("A") });
            sync({ request: Event("B") });
        },

        function () {
            sync({ request: Event("1") });
            sync({ request: Event("2") });
        }
    );

    request(Event("End"));
});

The state space of this program is:

parallel state space

Note that the sequences "A", "B" and "1", "2" are requested in parallel. This concurrent execution enables all six potential interleavings of these two sequences (12AB, 1A2b, 1AB2, AB12, A1B2, and A12B). The "End" event is only requested once both sequences have been fully processed.

maybe()

Returns true or false. Splits the scenario into two sub-scenarios, depending on its return value. This statement request maybeEvent().yes and maybeEvent().no, only one of which will be selected in a given scenario.

Returns: true or false

maybe(v)

Returns true or false. The v parameter describes the choice being made. This description can be used by other b-thread to detect the choice, and by trace post-processing tools (such as the gen-book sub-command) to tag and filter the generated trace. This statement requests maybeEvent(v).yes and maybeEvent(v).no. Of course, only one of these events will be selected.

v

String. Describes what may or may not be.

Returns: true or false

Example: Using maybe to make a choice

A b-thread that uses maybe to make a choice between two options. The choice is then used to request an event.

bthread(`Make a choice`, function () {
    if (maybe("go")) {
        request(Event("Go"));
    } else {
        request(Event("Stop"));
    }
})

maybeEvent(v)

Returns an object containing events and event sets related to maybe(v) calls. Can be used to waitFor and block calls to maybe.

v

String. The name of that thing that may or may not be.

Returns: an object with the following fields:

yes

The event causing maybe(v) to return true.

no

The event causing maybe(v) to return false.

any

An event set containing both events above.

Example: Using maybeEvent to wait for a choice

The addon b-thread waits for the "go" event to be selected. The main b-thread uses maybe to make a choice between two options. The choice is then used, by another b-thread, to request an event.

bthread("main", function(){
    if ( maybe("go") ) { (1)
        ...
    }
});

bthread("addon", function(){
    waitFor(maybeEvent("go").yes); (2)
    request(Event("Test for Go"))
})
1 This line requests the go choice selection events and returns true iff the positive choice is chosen.
2 This line awaits the event from the set maybeEvent("go").yes, which corresponds to the positive selection of the go choice.

on(eventSet, handlerFn)

Registers a new b-thread that executes handlerFn whenever an event from eventSet is selected. Stops if handlerFn returns false. Returns immediately.

If an event from eventSet is selected while handlerFn is running, a parallel version of handlerFn will not be invoked. To respond to any selection an event in eventSet, have handlerFn create a new bthread and return immediately.
eventSet

EventSet. The event set to trigger the function on whenever

handlerFn

Function. Gets an event, and returns a boolean.

Returns: Nothing.

Using on to implement the game of Example: FizzBuzz

The following code implements the game of FizzBuzz. The game is played by counting up from 0, replacing numbers divisible by 3 with "Fizz", numbers divisible by 5 with "Buzz", and numbers divisible by both 3 and 5 with "FizzBuzz". The game also announces all other numbers.

FizzEvent = EventSet("Fizz", e => e.data.number % 3 == 2);
BuzzEvent = EventSet("Buzz", e => e.data.number % 5 == 4);

on(FizzEvent.and(EventSets.not(BuzzEvent)), function (e) {
    request(Event("Fizz", { number: e.data.number + 1 }));
})
on(BuzzEvent.and(EventSets.not(FizzEvent)), function (e) {
    request(Event("Buzz", { number: e.data.number + 1 }));
})
on(FizzEvent.and(BuzzEvent), function (e) {
    request(Event("FizzBuzz", { number: e.data.number + 1 }));
})
on(EventSets.not(FizzEvent.or(BuzzEvent)), function (e) {
    bthread("AnnounceHandler", function () {  (1)
        request(Event("Announce", { number: e.data.number + 1 }));
    });
})

bthread('Start', function () {
    request(Event("Announce", { number: 0 }));
    waitFor(any({ number: 30 }));
    block(any("Announce"));
});
1 The announce handler is placed in a separate b-thread to enable parallel execution of the handler with another trigger. This is necessary because the game permits two consecutive announcements. Therefore, we want the handler of the first announcement to trigger the next announcement.

requestAtAnyOrder(e1, e2, e3…​ / eventArray)

Requests the passed events in any order (that is, splits the scenario into many scenarios, one for each request order). Returns after the last event has been selected.

e1, e2, e3 …​

Events. These events will be selected in some order for any given scenario - a scenario will be generated for each possible order. Note that other b-threads might be blocking these events, which may affect the actual choice the system has.

eventArray

Array of Events. An alternative way of passing events to this call.

Returns: Nothing, but after the last event was selected.

Example: Using requestAtAnyOrder to request events in any order

A b-thread that requests events in any order. The b-thread requests the "A" event, followed by the "B" event, and then the "C" event. The b-thread then requests the "next" event, followed by the "D" event, the "E" event, and the "F" event. The b-thread requests the events in any order.

bthread('All', function () {
    requestAtAnyOrder(Event("A"), Event("B"), Event("C"));
    request(Event("next"));
    requestAtAnyOrder(Event("D"), Event("E"), Event("F"));
});

The state space of this program is:

anyorder

requestOne(e1, e2, e3…​ / eventArray)

Requests one of the passed events, or one of the events in the eventArray array. From a test-space standpoint, this splits the test scenario info multiple scenarios, one for each possible choice.

e1, e2, e3 …​

Events. One of these will be selected. Note that other b-threads might be blocking these events, which may affect the actual choice the system has.

eventArray

Array of Events. An alternative way of passing events to this call.

This method is a convenience wrapper around request(eventArray).

Returns: The selected event.

Example: Using requestOne to request one of a set of events

A b-thread that requests the "A" event, the "B" event, or the "C" event. The b-thread then requests the "next" event, followed by the "D" event, the "E" event, or the "F" event.

bthread('All', function () {
    requestOne(Event("A"), Event("B"), Event("C"));
    request(Event("next"));
    requestOne(Event("D"), Event("E"), Event("F"));
});

The state space of this program is:

requestone

select(name).from(v1, v2, v3…​ / valueArray)

Select one of the values passed passed to from, marking their role as name. The choice is announced by a selectEvent(name, value).

name

String. Name of the select event and role of the selected value.

v1, v2, v3…​

Objects. Argument list of values to choose from.

valueArray

Array of Objects. Lists values to choose from.

select(name).any()

Creates an event set containing all selectEvents whose name is name. For example:

name

String. Name of the select event and role of the selected value.

Returns: Event set that will match select events with the name: name

Example: Using select to create a select event and wait for it using select(..).any()

The following code creates a select event with the name "fruit" and either "banana" or "apple" as the value. In another b-thread, the code waits for any of the events above and assigns the selected fruit to the chosenFruit variable.

bthread("fruit", function () {
    select("fruit").from("apple", "banana");    (1)
});

bthread("chosen fruit", function () {
    const chosenFruit = waitFor( select("fruit").any() ).data.value;
    bp.log.info("Chosen fruit: " + chosenFruit); (2)
});
1 This code creates a select event with the name "fruit" and either "banana" or "apple" as the value.
2 This code waits for any of the events above and assigns the selected fruit to the chosenFruit variable.

selectSome(name).from(v1, v2, v3…​ / valueArray)

Select a sub-set of the values passed to from, marking their role as name. The intermediate choices are announced by selectEvent(value)s. In the returned array maintains relative values order, so a call to selectSome("letters").from("a","b","c") may return ["a","c"] but not ["c","a"].

name

name of the select event and role of the selected values.

v1, v2, v3…​

Object. Argument list of values to choose from.

valueArray

Array of Objects. Lists values to choose from.

Returns: An array containing a sub-set of the values passed to from. May be empty (which is a valid subset).

Example: Using selectSome to create a sub-seuqence of events

The following code creates a select event with the name "x" and selects a sub-sequence of the values "A", "B", and "C".

bthread("Select some example", function () {
    selectSome("x").from("A", "B", "C");
});

The state space of this program is:

selectsome

Note that a select event with value . is triggered to mark the end of the selection when it does not end with the last event (C in this code).

waitForAll(es1, es2, es3…​ / eventSetArray)

Waits for all the passed event sets, regardless of the order they appear in. Returns after events from all event sets were selected. Note that a single event may be a member of more than a single event set.

es1, es2, es3 …​

Event sets. These events sets will be waited for.

eventSetArray

Array of Event sets. An alternative way of passing event sets to this call.

Returns: The last event that was selected.

Example: Using waitForAll to wait for events from multiple event sets

The following code creates three b-threads that request a capital letter, a small letter, and a number. The code then waits for all the three types of events to be selected.

var capitalLetters = [], lowercaseLetters = [], digits = [];
for (var i = 0; i < 26; i++) {
    capitalLetters.push(String.fromCharCode(i + 65));
    lowercaseLetters.push(String.fromCharCode(i + 97));
    if (i <= 10) digits.push(i.toString());
}

bthread("Request a capital letter", function () {
    select("CapitalLetter").from(capitalLetters);
});

bthread("Request a lowercase letter", function () {
    select("LowercaseLetter").from(lowercaseLetters);
});

bthread("Request a digit", function () {
    select("Digit").from(digits);
});

bthread("Wait for all selections", function () {
    waitForAll(
        select("CapitalLetter").any(),
        select("LowercaseLetter").any(),
        select("Digit").any(),
    );

    bp.log.info("All selections have been made!");
});

Extended Events and Event Sets

any(filterData)

Creates an event set based on filterData.

filterData
  • When filterData is a String, returns an event set that contains all events whose name is filterData.

  • When filterData is a regular expression, returns an event set that contains all events whose name matches filterData.

  • When filterData is an object, returns an event set that contains all events whose data object existing fields match the field values in filterData.

Returns: an event set based on filterData

Example: Using any to create event sets

The following code shows how to use any to create event sets based on different types of filterData.

bthread('Request 10 countries', function () {
    for (let i = 0; i < 10; i++) {
        requestOne(
            Event("United States", { Language: "English", Continent: "North America" }),
            Event("France", { Language: "French", Continent: "Europe" }),
            Event("Japan", { Language: "Japanese", Continent: "Asia" }),
            Event("Brazil", { Language: "Portuguese", Continent: "South America" }),
            Event("Australia", { Language: "English", Continent: "Australia" }),
            Event("South Africa", { Language: "English", Continent: "Africa" }),
            Event("Russia", { Language: "Russian", Continent: "Europe" }),
            Event("China", { Language: "Chinese", Continent: "Asia" }),
            Event("India", { Language: "Hindi", Continent: "Asia" }),
            Event("Germany", { Language: "German", Continent: "Europe" }),
            Event("Italy", { Language: "Italian", Continent: "Europe" }),
            Event("Spain", { Language: "Spanish", Continent: "Europe" }),
            Event("Mexico", { Language: "Spanish", Continent: "North America" }),
            Event("Canada", { Language: "English", Continent: "North America" }),
            Event("Argentina", { Language: "Spanish", Continent: "South America" }),
        );
    }
});


bthread('Wait for English', function () {
    while (true) {
        waitFor(any({ Language: "English" }));
        bp.log.info("Its an English speaking country!");
    }
});

bthread('Wait for two words name', function () {
    while (true) {
        waitFor(any(/.* .*/));
        bp.log.info("Its a two words name country!");
    }
});

bthread('Wait for Europe', function () {
    while (true) {
        waitFor(any({ Continent: "Europe" }));
        bp.log.info("Its a European country!");
    }
});

bthread('Wait for Canda or Japan', function () {
    while (true) {
        waitFor([any("Canada"), any("Japan")]);
        bp.log.info("Its either Canada or Japan!");
    }
});

choiceEvent(value)

Creates an event describing value being selected by choose() (see below).

value

Object. The selected value.

Returns: The event that marks value being selected as a result of calling choose().

selectEvent(name, value)

Creates a selection event, describing value being selected as a name. For example, if we want to choose a car type, an appropriate selection event might be selectEvent("carType", "DeLorian").

name

String. Describes the purpose/role of the selected value.

value

Object. The selected value itself.

Example: Using selectEvent to create a select event

We select an event with the name "Country" and selects one of the countries from the list. The code then waits for the selection of a country and greets the user in the appropriate language.

bthread('Request 10 countries', function () {
    for (let i = 0; i < 10; i++) {
        select("Country").from(["United States", "France", "Japan", "Brazil", "Russia", "China", "India"]);
    }
});

bthread('Greet in English', function () {
    while (true) {
        waitFor(selectEvent("Country", "United States"));
        sync({ request: Event("Greet", { greeting: "Hello" }), block: select("Country").any() });
    }
});

bthread('Greet in French', function () {
    while (true) {
        waitFor(selectEvent("Country", "France"));
        sync({ request: Event("Greet", { greeting: "Bonjour" }), block: select("Country").any() });
    }
});

bthread('Greet in Japanese', function () {
    while (true) {
        waitFor(selectEvent("Country", "Japan"));
        sync({ request: Event("Greet", { greeting: "こんにちは" }), block: select("Country").any() });
    }
});

bthread('Greet in Portuguese', function () {
    while (true) {
        waitFor(selectEvent("Country", "Brazil"));
        sync({ request: Event("Greet", { greeting: "Olá" }), block: select("Country").any() });
    }
});

bthread('Greet in Russian', function () {
    while (true) {
        waitFor(selectEvent("Country", "Russia"));
        sync({ request: Event("Greet", { greeting: "Привет" }), block: select("Country").any() });
    }
});

bthread('Greet in Chinese', function () {
    while (true) {
        waitFor(selectEvent("Country", "China"));
        sync({ request: Event("Greet", { greeting: "你好" }), block: select("Country").any() });
    }
});

bthread('Greet in Hindi', function () {
    while (true) {
        waitFor(selectEvent("Country", "India"));
        sync({ request: Event("Greet", { greeting: "नमस्ते" }), block: select("Country").any() });
    }
});

Utility Calls

getEnv(name)

Returns the environment variable with with the passed name.

name

String. Name of environment variable

Returns: Value of environment variable.

Example: Using getEnv to get the value of an environment variable

The following code gets the value of the environment variable "USER" and logs it.

bthread('Get environment variable', function () {
    const user = getEnv("USER");
    bp.log.info("The user is: " + user);
});

isInBThread()

Checks whether the current code is running in a b-thread or not.

Returns: true if the code runs in a b-thread, false otherwise.

Example: Using isInBThread to check if the code is running in a b-thread

A function that behaves differently depending if it is running in a b-thread or not.

function foo() {
    if (isInBThread()) {
        bp.log.info("I am in a bthread");
    } else {
        bp.log.info("I am not in a bthread");
    }
}

foo();

bthread('Is in bthread demo', function () {
    foo();
});

isEvent(e)

Checks whether e is an event or not.

e

The object we test for being an event.

Returns: true if e is an event, false otherwise.

Using isEvent to check if an object is an event

A function that behaves differently depending if the object is an event or not.

function foo(e) {
    if (!isEvent(e)) e = Event(e);
    request(e);
}

bthread('Using foo in two ways', function () {
    foo(Event("x"));
    foo("x");
});

isEventSet(e)

Checks whether e is an event or not.

An event is an event set (containing itself only). So isEventSet(anEvent) returns true.
e

The object we test for being an event.

Returns: true if e is an event, false otherwise.

Using isEventSet to check if an object is an event set

A function that behaves differently depending if the object is an event set or not.

function foo(e) {
    if (!isEventSet(e) && !isEvent(e)) e = any(e);
    waitFor(e);
}

bthread('Using foo in three ways', function () {
    foo(Event("x"));
    foo("x");
    foo(any("x"));
    bp.log.info("All done!");
});

bthread('Using foo in three ways', function () {
    request(Event("x"));
    request(Event("x"));
    request(Event("x"));
});