Studio:How to synchronize scripts

From STRIDE Wiki
Jump to: navigation, search

If a test scenario requires multiple scripts, synchronization between the scripts is required. For example, if testing a component requires you to simulate a dependent component it is sometimes easier to split the task into multiple scripts. One script drives the test, which launches a separate script non-blocking which simulates the dependent component.

The following example shows how communication between two scripts can be achieved using a simple client-server example. Here the client (host) calls the server (target) to get the current date and time as a string.

The interface between the client and server is defined in the header file TargetFunctions.h. A second, independent header file--ScriptSync.h--is added to the workspace to create a broadcast message that we use to communicate between the scripts.

While there are other ways of providing inter-script communication, the technique of using a broadcast message is the best choice in almost every situation. The broadcast message is by far the most lightweight and simple-to-implement alternative.

Note
An alternative to this synchronization technique using files is shown in the article Using Scripts to Simulate Missing Software Units.

Header Files

TargetFunctions.h

/* TargetFunctions.h
   This defines the interface(s) of the function(s) we will be implementing
   in our target (server) script.
*/
#define MAX_TIME_STRING 64
int get_time(char* szTime);
 
#ifdef _SCL
#pragma scl_function(get_time)
#pragma scl_ptr(get_time.szTime,"OUT","PRIVATE")
#pragma scl_string(get_time.szTime, MAX_TIME_STRING)
#endif /* _SCL */

ScriptSync.h

/* ScriptSync.h
   By adding this file to a STRIDE workspace, you define a broadcast message
   that can be used to communicate with one or more asynchronously running scripts
*/
#ifdef _SCL
#include <sr.h>

/* this message is used to tell the target message loop to terminate */
#define _BRD_SCRIPTSYNC_EXITLOOP (55555 | srMT_BRD)
#pragma scl_msg(_BRD_SCRIPTSYNC_EXITLOOP)
#endif

Server Script

The server script registers as the owner of get_time() and subscribes to the _BRD_SCRIPTSYNC_EXITLOOP broadcast message.

The script will service calls to get_time() until the subscribed broadcast message is received, which will cause the script to exit.

Target.pl

=head
    Target.pl

    Implements the functionality of get_time() which is declared in TargetFunctions.h

    Script is intended to run asynchronously; will terminate upon receiving a broadcast
    message with the name _BRD_SCRIPTSYNC_EXITLOOP
=cut

use strict;
use Carp;
use Win32::OLE;
use Time::gmtime;

Win32::OLE->Option(Warn => 3);

## subscribe to receive sync broadcast message
my $syncMsg = $main::ascript->Messages->Item("_BRD_SCRIPTSYNC_EXITLOOP")->User;
$syncMsg->Subscribe();

## take ownership of required interfaces implemented in this script
my $get_time = $main::ascript->Functions->Item("get_time")->Owner; 
$get_time->Register();
 
## initialize exit variable
my $end = 0;
 
## Wait for requests from clients until exit
while ($end == 0)
{
   ## Wait for an event
   my $event = $main::ascript->WaitForEvent();

   ## if the event is a get time request, get the time
   ## and return
   if ($event->Name eq $get_time->Name)
   {
      $get_time->OutPointers->{szTime} = gmctime();
      $get_time->{ReturnValue} = 1;
      $get_time->Return();
   }
 
   ## if the event is exit loop message, set the variable to exit the loop
   elsif ($event->Name eq $syncMsg->Name)
   {
       $main::studio->Output->PrintMessage("Target: exiting");
       $end = 1;
   }
}

Client Script

The client script launches the server script if required and waits until the server is ready to accept requests. Then get_time() is called repeatedly until the user ends the test, at which time the _BRD_SCRIPTSYNC_EXITLOOP message is broadcast to terminate the server script.

Host.pl

=head
    Host.pl
    This script uses Target.pl to mock the function get_time() which is declared in
    TargetFunctions.h

    If Target.pl is not running, it will be started asynchronously.

    get_time() is called and its returned information is displayed repeatedly in a message box
    until message box's Cancel button is pressed.
=cut

use strict;
use Carp;
use Win32::OLE;

Win32::OLE->Option(Warn => 3);

## Start the server script running asyncronously if
## it isn't already running
my $serverScript = $main::studio->Workspace->Files->Item("Target.pl");
if (!$serverScript->IsRunning) 
{
    $serverScript->RunNonBlocking();
}

## Wait for the server to be ready to accept our calls.
## We will poll for the server to have taken ownership
## of the supported function that is registered last
while (!($main::ascript->Functions->Item("get_time")->Owner->IsRegistered))
{
    $main::ascript->Sleep(100);
}
 
## Get the function's user object
my $get_time = $main::ascript->Functions->Item("get_time")->User;

my $continue  = 1;

while ($continue)
{
    ## Call the function (blocking request to the server)
    $get_time->Call();

    my $ret = $main::ascript->MessageBox($get_time->OutPointers->szTime.
                                         "\rRetVal: ".$get_time->ReturnValue,
                                         "get_time()",
                                         "OKCANCEL");
    if ($ret ne "OK")
    {
        $continue = 0;
    }
}
 
## Shut down the server by broadcasting message
$main::ascript->Messages->Item("_BRD_SCRIPTSYNC_EXITLOOP")->Owner->Broadcast();