Event Category

/* @provengo summon eventcategory */

The Event Category library is a convenience library that helps you easily group related events together under a single "category". Each category gets its own color, to make it easier to follow. The methods of the event category objects allow waiting-for and blocking category events, and even refining models (e.g. adding logic between two subsequent events of the category). Additional methods allow easy addition of manual test book steps.

The main object in this library are event category objects. These objects, which are created by calling EventCategory.create(), generate events and event-sets which are members of the event category they represent. They also perform event-related actions, such as requesting or implementing the refinement pattern (see below).

This library is still in its experimental phase. Some changes may happen as we gather feedback from our users (please share your experience with us!).

Sample Usage

Consider a process for deploying a new web system, consisting of two parts: front end and a back end. Setting up these systems has 3 steps: 1) installation, 2) starting, 3) waiting for the system to be ready. We can create an event category for each system, like so:

const frontEnd = EventCategory.create("Front End", { (1)
    names: ["install", "start", "ready"]             (2)
});
const backEnd = EventCategory.create("Back End", {
    names: ["install", "start", "ready"],
    color: "#aaaaee"                               (3)
});
  1. Defining a new event category, called "front end"

  2. The category will have methods related to these events (see below)

  3. Defining a specific color for this category. If no color is specified, a default one is used.

Once the event category is created, it can be used as follows:

bthread("fe", function(){
    frontEnd.doInstall();  (1)
    frontEnd.doStart();
    frontEnd.doReady();
});

bthread("be", function(){
    backEnd.doInstall();  (2)
    backEnd.doStart();
    backEnd.doReady();
});
1 Requesting the install event of the front end category.
2 Requesting the install event of the back end category.

Since it makes no sense for the front end to be started until the back-end is ready, we can add the following constraint, that uses the events defined by the category (note the usage of «name»Event vs. do«Name»):

// don't start the front end until the back end is ready
Constraints.block( frontEnd.startEvent() )
           .until( backEnd.readyEvent() );

The resultant model looks like this:

event category

Static Methods/Properties

These methods are invoked directly on the class, not on objects returned from EventCategory.create().

EventCategory.create(name, properties)

Creates a new event category object. This object can be used to generate and handle events of the category it represents. The name of the category should be unique - two event category objects created with the same name will represent the same category.

name

String. The name of the event category.

properties

Optional object. Customization for the category. Current fields are listed below, all are optional.

color

String. HTML-like color notation (#RRGGBB). This color is used for visualizations and manual test books.

names

String array. A list of event names that are part of the category. This list is used to generate convenience event methods in the created object. For each passed name - e.g. "beep" - three methods will be generated. beepEvent() is the event representing a beep, doBeep() is a convenience method for requesting the beepEvent(), and anyBeep is an event set containing all beepEvent()s. Additionally, a namedEvents property, containing an array of event sets - one for each name - will be added to the returned event category object. See Auto-Generated Methods for more details.

Returns: An object for working with events from the created category.

Example
const carActions = EventCategory.create("Car Actions", { (1)
    color: "#88FF88",
    names: ["start","drive","turn","stop"]
});

bthread("car-main", function(){
    carActions.doStart();                  (2)
    carActions.doDrive();                  (3)
    carActions.doStop();
});

bthread("no double-start", function(){     (4)
    while ( true ) {
        waitFor(carActions.startEvent());  (5)
        sync({
            waitFor: carActions.stopEvent(),
            block: carActions.startEvent()
        });
    }
});
  1. Creating a new event category object, dealing with actions a car may take. The created category will have a light green color, and contain the pre-defined actions listed in the names array.

  2. Using the auto-generated do«Name»() methods to represent a simple scenario for driving a car.

  3. A bthread preventing the car from starting while already running.

  4. Using the auto-generated «name»Event() methods

EventCategory.any

An event set containing all the events from all event categories.

Example
// A bthread requesting a BEEP event after the first two event category events

bthread("2-beep", function(){
    waitFor( EventCategory.any );
    waitFor( EventCategory.any );
    sync({
        block: EventCategory.any,
        request: Event("BEEP")
    });
});

EventCategory.addTestBookStep(event, fmt)

This static method may be used during manual test book generation, to add event category events as test steps in the generated book. If the passed event is an event category event, this method adds a new step to the manual test book being generated, based on the passed event. It then returns true. Otherwise, the passed event is a non-event category event. This method does not add any step to the book, and returns false.

event

Event. The event that might be added to the book.

fmt

Optional String. The format to use for the generated book step. defaults to html. Another option is qc-xls, which should be used when generating Excel-based test books.

Returns: true if a step was added, false otherwise.

event category step
Figure 1. A manual test book step generated from an event category events. The step colors are specified by the event category.
Example

Below is an excerpt from a documentEvent function using EventCategory.addTestBookStep()

function documentEvent( event ) {
    GenBook.autoTag(event);
    if ( EventCategory.addTestBookStep(event) ) return; (1)
    // The event is not a member of an event category,
    // continue processing by other means
    ...
}
1 If the event was handled by the EventCategory.addTestBookStep, stops its processing (as the book has been updated with a matching step). Otherwise, continues as usual.

EventCategory Object Methods

These methods are called on event category objects - that is, objects returned from EventCategory.create().

ec.event(name, value)

Creates an event that is a member of the object’s category.

name

String. The name of the event.

value

Optional. A value that will be available in event.data.value.

Returns: A category event whose name is the passed name, and its data.value field holds the passed value.

Example

A famous nursery rhyme expressed in a bthread, using an event category.

const ec = EventCategory.create("Bobbin");
bthread("wind the bobbin up", function(){
    request( ec.event("wind", {direction:"up"}));
    request( ec.event("wind", {direction:"up"}));
    request( ec.event("pull") );
    request( ec.event("pull") );
    request( ec.event("clap") );
    request( ec.event("clap") );
    request( ec.event("clap") );
});

ec.do(name, value)

Requests a category event with the passed name and value. Semantically equivalent to invoking request with event(name, value).

name

String. The name of the event.

value

Optional. A value that will be available in event.data.value.

Example

A famous nursery rhyme expressed in a bthread, using event category’s do method. Compare to the example for the event method.

const ec = EventCategory.create("Bobbin");
bthread("wind the bobbin up", function(){
    ec.do("wind", {direction:"up"});
    ec.do("wind", {direction:"up"});
    ec.do("pull");                  (1)
    request( ec.event("pull") );    (2)
    ec.do("clap");
    ec.do("clap");
    ec.do("clap");
});
  1. This line is equivalent to the line below it.

  2. This line is equivalent to the line above it.

ec.any

An event set containing all events in this category.

Example

Using the any event set to block category events. Here in a sample test for a web site, where the user b-thread navigates the site and the validate greeting b-thread validates the greeting is correct at the proper time.

// Define event
const userActions = EventCategory.create("User");
const validations = EventCategory.create("Validations");
bthread("user", function(){
    userActions.do("login");
    requestOne([
        userActions.event("dashboard"), userActions.event("personal page"),
        userActions.event("feed"), userActions.event("orders"),
    ]);
    userActions.do("logout");
});
bthread("validate greeting", function(){
    waitFor(userActions.event("login"));
    block( userActions.any, function(){    (1)
        validations.do("Greeting Shown");
    });
});
  1. Using userActions.any to block any user actions while validating the previous user action result.

The resultant model is shown below. Note that after login, all four possible User category events are blocked until the greeting is validated.

event category any

ec.anyNamed(name)

An event set containing all events in the category whose name is name.

name

The name of the events in the category.

Returns: An event set containing events from this category whose name in name.

Example

The code below checks that a robot moved after any move event. See also the example for refine().with().

const robot = EventCategory.create("robot");
bthread("robot", function(){
    requestOne([
        robot.event("move", {direction:"up"}),
        robot.event("move", {direction:"down"}),
        robot.event("move", {direction:"forward"}),
        robot.event("stay")
    ]);
});
bthread("location change check", function(){
    let moveEvent = waitFor( robot.anyNamed("move") );
    request( Event("validate robot moved " + moveEvent.data.value));
});

ec.refine(name).with(handler)

Calls the passed handler whenever a category event with the passed name is selected. While handler executes, all events from this category are blocked.

This methods provides an easy way of introducing a "lower level"/"finer grain" scenarios into a model. A common use case is having an event category represent a business-level flow, and using refine() to create an automation layer by adding a series of automation-related events after category event. See example.

handler should not request events from this category, as they are blocked while handler executes.
name

String. Name of events in the category for which handler should be invoked.

handler

Function. Accepts the selected event as a parameter.

Example

The code below checks that a robot moved after any move event.

const robot = EventCategory.create("robot");
bthread("robot", function(){
    requestOne([
        robot.event("move", {direction:"up"}),
        robot.event("move", {direction:"down"}),
        robot.event("move", {direction:"forward"}),
        robot.event("stay")
    ]);
});

robot.refine("move").with(function(e){
    request( Event("validate robot moved " + moveEvent.data.value));
});

ec.refineAny().with(handler)

Same as refine(name).with(handler), but for any event in the event category.

handler

Function. Accepts the selected event as a parameter.

Example

In the example below, refineAny().with() is used to follow any action the robot takes with an appropriate validation.

const robot = EventCategory.create("robot");
bthread("robot", function(){
    requestOne([
        robot.event("move", {direction:"up"}),
        robot.event("move", {direction:"down"}),
        robot.event("move", {direction:"forward"}),
        robot.event("stay")
    ]);
});

robot.refineAny().with(function(e){
    if ( e.name === "move") {
        request( Event("validate robot moved " + moveEvent.data.value));
    } else {
        request( Event(`validate robot did not move`));
    }
});

Auto-Generated Methods

In many cases, the names of the events in a category are known in advance. The Event Category library makes usage easier in these cases, by auto-generating methods and event sets. To use this mechanism, pass the names of the known event to EventCategory.create.

In the example below, a robot starts up, performs a single action, and turns off. As the actions are known in advance, we can create an event category that contains them, and enjoy the auto generated methods.

const robot = EventCategory.create("robot", {
    names: ["start", "move", "stay", "off"]    (1)
});
bthread("robot", function(){
    robot.doStart();  (2)
    requestOne([
        robot.moveEvent({direction:"up"}),  (3)
        robot.moveEvent({direction:"down"}),
        robot.moveEvent({direction:"forward"}),
        robot.stayEvent()   (4)
    ]);
    robot.doOff();
});

bthread("move tracker", function(){
    let move = waitFor( robot.anyMove );  (5)
    request( Event("validate robot moved " + moveEvent.data.value));
});
  1. Creating an event category for robot actions, with a known action list.

  2. Using the auto-generated doStart() method, which requests a robot start event (similar to robot.do("start"));

  3. Using the moveEvent(). The passed parameter will be available in the generated event’s data.value field.

  4. Using the stayEvent() auto-generated method. Used here with no parameters.

  5. Using the auto-generated event set anyMove, which contains all robot’s move events.

Human Readable Event Names

The Event Category library supports human readable names, such as "add tip" or "test URL validity". The generated methods for these names will follow the Camel Case convention, like so:

const ec = EventCategory.create("Human Readable", {
    names: ["add tip", "test URL Validity"]
});

// generated methods:
ec.doAddTip();
ec.addTipEvent();
ec.anyAddTip;
ec.doTestUrlValidity(); // Note: Url, not URL!
ec.testUrlValidityEvent();
ec.anyTestUrlValidity;

ec.«name»Event(aValue)

An event from the ec category, containing the value aValue.

Example

Using the generated nameEvent method for blocking actions when its battery is low. Equivalent of ec.event(«name», aValue). Here, we assume that the move event does not use parameters. See the example for any«Name» below for dealing with cases with parameters.

const robot = EventCategory.create("robot", {
    names: ["start", "move", "stay", "off", "batteryLow", "batteryOk"]    (1)
});
bthread("Do not move when is battery low", function(){
    while ( true ) {
        waitFor(robot.batteryLowEvent());
        sync({
            block: robot.moveEvent(),
            waitFor: robot.batteryOkEvent()
        });
    }
});

ec.do«Name»(aValue)

Request an event with «name» and aValue. Equivalent of request(ec.event(«name», aValue))

Example

A robot performing a short travel using the ec.do«Name»() calls.

const robot = EventCategory.create("robot", {
    names: ["start", "move", "stay", "off"]    (1)
});
bthread("Do not move when is battery low", function(){
    robot.doStart();
    robot.doMove({direction:"left"});
    robot.doMove({direction:"right", distance:300});
    robot.doMove({direction:"forward", speed:33});
    robot.doStay({duration:1000});
    robot.doOff();
});

ec.any«Name»

An event set containing all events from the category whose name is «name». Equivalent to ec.anyNamed(«name»).

Example

Using the generated any«Name» for blocking robot movements when its battery is low.

const robot = EventCategory.create("robot", {
    names: ["start", "move", "stay", "off", "batteryLow", "batteryOk"]    (1)
});
bthread("Do not move when is battery low", function(){
    while ( true ) {
        waitFor(robot.batteryLowEvent());
        sync({
            block: robot.anyMove,
            waitFor: robot.batteryOkEvent()
        });
    }
});

ec.namedEvents

Contains an array of event sets, one for each passed name. This creates an easy way for defining coverage goals for a given category.

Example

Given a model containing event categories for a rover and a quadcopter, we can generate full action coverage using the following code and the ensemble subcommand.

const rover = EventCategory.create("Rover", {
    names: ["on", "forward", "backward", "left", "right", "stop", "off"]
});
const quadcopter = EventCategory.create("Quadcopter", {
    names: ["up","down","left","right","land"]
});

// Typically, the line below will be in meta-spec/ensemble.js:
const GOALS = rover.namedEvents.concat(quadcopter.namedEvents);