Advanced Simulation

Watch a video explaining the simulation algorithm, using of simulation variables and debugging of simulation in IDE:

SV Lab Advanced simulation

The simulation in SV Lab is controlled by scenarios containing mostly static data of request and response messages. Values that continuously change every test run can be replaced with simulation variables. This way, you create scenarios that transfer values from requests to responses within one or even multiple services. When accompanied with value generator scripts, the simulation variables enable computing the required values during the simulation, based on live inputs.

Simulation variables

Some part of a request sent by the client may be different each time the simulation is run. A good example of such behavior is values related to current date/time or session identifiers. Simulation variables enable you to handle such situations.

Lets examine a call from the scenario in the hello-world-advanced demo available on GitHub:

let yourName = sv.svVar("world");
this.service.GET("/hello/{name}")
    .withRequest()
        .withPathParameters({name:yourName}) // URI parameter *name* bound to the variable *yourName*
    .withResponse({
              "greetings": [
                  "Hello, ",
                  yourName]                  // variable *yourName* bound to the response
          }, sv.JSON)                        
        .withStatusCode(...);

The yourName simulation variable is declared together with its default value. The variable is assigned in two places: in the request URI parameter and as a property in the response body.

An incoming request loads the value for the variable for subsequent use in responses. The response returned by the simulator will contain the remembered value.

The same variable can be used in more than one call, enabling sharing the remembered data. We could add another call to our scenario that would respond with the last remembered value from the above /hello/{name} request:

this.service.GET("/hello")
    .withRequest()
    .withResponse({
              "greetings": [
                  "Hello, my old friend ",
                  yourName]                  // variable *yourName* bound to another response
          }, sv.JSON)                        
        .withStatusCode(...);

Tip

Using a variable in a request causes the simulator to ignore the actual incoming value when matching calls from the scenario, thereby responding to a request with any name value. This feature can be leveraged to ignore any part of a request simply by enclosing it in a simulation variable. For details, see Using SV variables to capture internal state.

Moving data across multiple services

The primary location for simulation variables is service scenario where they copy dynamic data between requests and responses of one service, although their scope can be wider. When the variables are declared in application scenario, they enable data transfer between multiple services.

This feature is demonstrated in the mqtt-sensors-and-actuators demo, where the first service listens for inputs that are reflected in the output of the second service.

The simulation variable lobbyHeatingStatus is declared in application scenario and then passed as a parameter to two different service scenarios, each describing behavior of a different virtual service:

@sv.applicationScenario
applicationScenario() {
    var lobbyHeatingStatus = sv.svVar("-");

    sv.forkScenario(() => this.control.lobby(lobbyHeatingStatus));
    ...
    sv.forkScenario(() => this.status.lobbyScenario(lobbyHeatingStatus));
}

The service scenarios declaration contains a simulation variable as a parameter. The variable is then used in the usual way in requests and/or responses in the scenario:

@scenario
lobby(lobbyHeatingStatus) {
    ...
    this.service.controlLobby()
        .withRequest({"switch-heating": lobbyHeatingStatus})
        ...
}

During simulation, the shared simulation variable transfers dynamic data between requests and responses of multiple services. It also has a synchronization function: the run of a service scenario with the variable that was not yet initialized by input, is suspended and waits for an incoming message from another service scenario. For details, see Using scenario parameters.

Value generators

Plain simulation variables enable the simulator to ignore and/or copy the input values that differ with each simulation run. You can attach a JavaScript function to a simulation variable as value generator. That function will be executed whenever a value needs to be produced for an output message during simulation.

Note

Value generator functions are evaluated during simulation and can manipulate actual data sent by the client, or work with current time, in contrast to templating functions, which are executed when the simulation model is compiled to create the static content of messages in the scenario.

The demo hello-generators shows an example of a simple generator function embedded in the service model:

@sv.include
makeGreeting(name) {
    return "Hello, " + name + "!";
}

The @sv.include annotation tells SV that the function has to be included in the compiled model as it will be evaluated when the lab is running the simulation.

To pass some input values to the generator function, you must declare SV variables for all its parameters and bind them to messages in the model (so that the simulator knows what type of data to expect in their place). Our function has the name parameter and we will bind it to the URI parameter of the request with the help of the yourName simulation variable:

let yourName = sv.svVar("world");

this.hello.GET("/hello/{name}")
    .withRequest()
        .withPathParameters({name:yourName})     // URI parameter bound to simulation variable

By having the parameter bound to a simulation variable, we can attach the generator to the response using yet another simulation variable, greeting:

let greeting = sv.svVar("Hello, world!").setJsGenerator(this.makeGreeting, yourName);

this.hello.GET("/hello/{name}")
    .withRequest()
        ...
    .withResponse({"greeting": greeting}, sv.JSON)

External value generators

The hello-generators-advanced distributed with SV Lab illustrates more advanced use of value generators. Generators keeping internal state, or generators with complex implementation, can be imported into the service model from an external JavaScript module:

import {count, sha1} from "Generators.svinclude"

The imported count() function is a simple counter and sha1() computes the
hash of given string:

// A simple generator counting from 0 during simulation run:
var counter = 0;
function count() {
    return counter++;
}

// A textbook SHA-1 implementation:
function sha1(msg) {
    ...
}       

The imported pack() function creates a complex JSON structure based on input data. It "packs" letters of your name into "boxes" loaded on "pallets", attaching proper labels and assigning each box a unique id from a sequence within a lab run.

// A generator of complex structure 'packing' the letters of name to boxes loaded on pallets:
var boxSerial = 100000000;
function pack(name) {

    function formatBoxSerial() {
        return "BOX" + boxSerial.toString().substring(1);
    }

    cargo = {
        boxCount: 0,
        firstSerial: formatBoxSerial(),
        lastSerial: "",
        pallets: []
    };

    for (i = 0; i < name.length; i++) {
        letter = name.charAt(i);
        pallet = {
            label: "Letters " + letter,
            boxCount: i + 1,
            boxes: []
        }
        ...
    }

    return cargo;
}

When binding the imported functions to simulation variables with .setJsGenerator(function, arguments...), an example value is provided so that the simulator knows how to map generated values to the output message. In case of a complex structure with optional elements, each optional element must appear in the data sample.

// response values generated during simulation
let seq = sv.svVar(-1).setJsGenerator(count);           
let hash = sv.svVar("").setJsGenerator(sha1, yourName);
let cargo = sv.svVar({                                 
    boxCount: 1,                                                
    firstSerial: "00000000",                                    
    lastSerial: "00000000",                                     
    pallets: [{                                                 
        label: "Letters A",
        boxCount: 1,
        boxes: [{
            serial: "00000000",
            content: "A"
        }]
    }]
}).setJsGenerator(pack, yourName);

Binding the generated variables to the output message is the same as in the case of the inlined greeting generator from a previous example:

this.hello.GET("/hello/{name}")
    .withRequest()
        ...
    .withResponse({
            "greeting": greeting, 
            "seq": seq,
            "hash": hash,
            "cargo": cargo
        }, sv.JSON)

When you run the demo and open the virtual endpoint in the browser, you get responses containing the greeting composed by the makeGreeting() function, incremental count, hash of the name from the URL and the cargo made of the name letters.

For example, opening the http://localhost:9000/hello/Eva URL gives you this response data generated by makeGreeting, count, sha1 and pack functions:

{
    "greeting": "Hello, Eva!",
    "seq": 0,
    "hash": "7c2fa47687b3791bc61bbe6933fabab1d786a9b4",
    "cargo": {
        "boxCount": 6,
        "firstSerial": "BOX00000000",
        "lastSerial": "BOX00000005",
        "pallets": [
            {
                "label": "Letters E",
                "boxCount": 1,
                "boxes": [
                    {
                        "serial": "BOX00000000",
                        "content": "E"
                    }]
            }, {
                "label": "Letters v",
                "boxCount": 2, "boxes": [
                    {
                        "serial": "BOX00000001",
                        "content": "v"
                    }, {
                        "serial": "BOX00000002",
                        "content": "v"
                    }]
            }, {
                "label": "Letters a",
                "boxCount": 3,
                "boxes": [
                    {
                        "serial": "BOX00000003",
                        "content": "a"
                    }, {
                        "serial": "BOX00000004",
                        "content": "a"
                    }, {
                        "serial": "BOX00000005",
                        "content": "a"
                    }]
            }]
    }
}

For details, see Value generators.