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, andfalse
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 evente
is a member ofthis
set, andfalse
otherwise. eventSet.or(otherSet)
-
Returns a new event set that contains events that are members of
this
set, or members ofotherSet
. eventSet.and(otherSet)
-
Returns a new event set that contains events that are members of
this
set and members ofotherSet
. 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 ofotherSet
, but not members of both. eventSet.nor(otherSet)
-
Returns a new event set that contains events that are not members of either
this
orotherSet
. eventSet.nand(otherSet)
-
Returns a new event set that contains events that are not members of both
this
andotherSet
.
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
, andes3
. Similar toes1.or(es2).or(es3)
. EventSets.allOf(es1, es2, es3…)
-
An event set that is the conjunction of
es1
,es2
, andes3
(so, contains only events that are ines1
andes2
ANDes3
). Similar toes1.and(es2).and(es3)
.
Example: Using Event Sets to wait for a specific set of events
We create two EventSet
s: 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 EventSet
s.
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 EventSet s 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
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:
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:
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 );
});
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.
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.
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:
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 returntrue
. no
-
The event causing
maybe(v)
to returnfalse
. 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:
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:
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 selectEvent
s 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:
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 isfilterData
. -
When
filterData
is a regular expression, returns an event set that contains all events whose name matchesfilterData
. -
When
filterData
is an object, returns an event set that contains all events whose data object existing fields match the field values infilterData
.
-
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"));
});