Difference between revisions of "Studio:How to synchronize scripts"

From STRIDE Wiki
Jump to: navigation, search
m (Text replace - 'Category:Script Writing' to 'Category:Studio:Script Writing')
 
(11 intermediate revisions by 5 users not shown)
Line 1: Line 1:
<nowiki>If a test scenario requires multiple scripts synchronization between the scripts is required. For example if testing a component requires to simulate a depend component it is sometimes easier to split it in multiple scripts. One script could be driving the test which launches a separate script non-blocking which simulates the depended component.  
+
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. The client queries the server for date and time. The server returns a date and time string.
+
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 a SCL file. For the example the SCL files has two interfaces, one to request information (in the example the date and time string) and one to shutdown (end) the server script.  
+
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.
  
SCL File:
+
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.
#define MAX_STRING_SIZE 50
 
int get_time(char* szTime);
 
int shutdown(void);
 
#ifdef _SCL
 
#pragma scl_function(get_time)
 
#pragma scl_ptr(get_time.szTime,  OUT,  PRIVATE)
 
#pragma scl_string(get_time.szTime, MAX_STRING_SIZE)
 
#pragma scl_function(shutdown)
 
#endif /* _SCL */ 
 
  
The server scripts waits for requests, gets the time and returns requests:
+
; Note: An alternative to this synchronization technique using files is shown in the article [[Using Scripts to Simulate Missing Software Units]].
## Used interfaces
 
my $req      = $main::ascript->Functions->Item("get_time")->Owner;
 
my $shutdown = $main::ascript->Functions->Item("shutdown")->Owner;
 
  
## teake ownership of required interfaces
+
==Header Files==
$req->Register();
+
===TargetFunctions.h===
$shutdown->Register();
+
<source lang="c">
 +
/* 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 */
 +
</source>
 +
===ScriptSync.h===
 +
<source lang="c">
 +
/* 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
 +
 +
</source>
 +
 +
==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===
 +
<source lang="perl">
 +
=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
 
## initialize exit variable
 
my $end = 0;
 
my $end = 0;
 
+
 
## Wait for requests from clients until exit
 
## Wait for requests from clients until exit
 
while ($end == 0)
 
while ($end == 0)
 
{
 
{
    ## Wait for an event
+
  ## Wait for an event
    my $event = $main::ascript->WaitForEvent();
+
  my $event = $main::ascript->WaitForEvent();
  
    ## if the event is a get time request, get the time
+
  ## if the event is a get time request, get the time
    ## and return
+
  ## and return
    if ($event->Name eq $req->Name)
+
  if ($event->Name eq $get_time->Name)
    {
+
  {
        $req->OutPointers->{szTime} = gmctime();
+
      $get_time->OutPointers->{szTime} = gmctime();
        $req->{ReturnValue} = 1;
+
      $get_time->{ReturnValue} = 1;
        $req->Return();
+
      $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;
 +
  }
 +
}
 +
</source>
  
    ## Shutdown event. Set the variable to exit the loop
+
==Client Script==
    if ($event->Name eq $shutdown->Name)
+
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===
        $shutdown->{ReturnValue} = 1;
+
<source lang="perl">
        $shutdown->Return();
+
=head
        $end = 1;
+
     Host.pl
     }
+
    This script uses Target.pl to mock the function get_time() which is declared in
}
+
     TargetFunctions.h
  
## Unregister ownership
+
    If Target.pl is not running, it will be started asynchronously.
$req->Unregister();
 
$shutdown->Unregister();
 
  
 +
    get_time() is called and its returned information is displayed repeatedly in a message box
 +
    until message box's Cancel button is pressed.
 +
=cut
  
The client script queries the server. However it also assures that the server is running. To show how scripts can be launched and terminated the client scripts first checks if the server is running. If not it starts the server and waits until the server is ready to accept requests. This is done by waiting until the server has registered the interfaces for script communication. At the end the client script sends a shutdown request to the server to end the server script.
+
use strict;
## Check if server is running
+
use Carp;
if (!($main::ascript->Functions->Item("shutdown")->Owner->IsRegistered))
+
use Win32::OLE;
{
 
    ## Server not available
 
    ## As an option we can start the server here
 
    ## This might not be required in all cases but is a good sample
 
    ## on how to start another script non-blocking
 
    $main::studio->Workspace->Files->Item("server.pl")->RunNonBlocking();
 
  
    ## wait for the server to be up
+
Win32::OLE->Option(Warn => 3);
    ## An easy way is to wait until the server has taken ownership
 
    ## of the supported functions
 
    while (!($main::ascript->Functions->Item("shutdown")->Owner->IsRegistered))
 
    {
 
        $main::ascript->Sleep(100);
 
    }
 
  
 +
## 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();
 
}
 
}
  
## Get a shortcut to the function
+
## Wait for the server to be ready to accept our calls.
my $req = $main::ascript->Functions->Item("get_time")->User;
+
## 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;
  
## Call te function (blocking request to the server)
+
my $continue  = 1;
$req->Call();
 
  
## Check for the return value and display date and time
+
while ($continue)
if ($req->ReturnValue)
 
 
{
 
{
     $main::ascript->MessageBox($req->OutPointers->szTime, "Current Time");
+
     ## Call the function (blocking request to the server)
}
+
    $get_time->Call();
else
+
 
{
+
    my $ret = $main::ascript->MessageBox($get_time->OutPointers->szTime.
  $main::ascript->MessageBox("Server returned Error", "Error");
+
                                        "\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();
 +
</source>
 +
  
  
## Shutdown the server (if required)
+
[[Category:Studio:Script Writing]]
$main::ascript->Functions->Item("shutdown")->User->Call();</nowiki>
 

Latest revision as of 16:31, 20 August 2009

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();