Studio:Using Scripts to Simulate Missing Software Units

From STRIDE Wiki
Revision as of 23:46, 20 August 2009 by Timd (talk | contribs) (Text replace - 'Category:Scripting' to 'Category:Studio:Scripting')
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

This note describes how to simulate (mock) a function using a script in STRIDE Studio. This technique is useful when a target-based function you are testing has a dependency on another function that has not been written yet, makes use of unavailable hardware resources, or can't be used for some other reason.

Example code is given in both Perl and JScript demonstrating how the mock script is set up by the test script and how the script receives function calls from the STRIDE runtime.

Note
An alternative to the synchronization technique shown here is given in the article How to synchronize scripts.

Notes on the Examples

In the included examples, the mocked function is called by another host-based script directly, however the runtime can also route calls from a C function on the remote target to a host-based script implementation. A more typical testing scenario would have a host-based test script testing a function on a remote target which--in turn--calls a dependent function which is mocked by a second host-based script.

To allow a target-based function to call a host-based function implementation,

  • the script must be registered as the function's Owner
  • the generated target IM code for the function must contain a proxy (which enables calls from the target to the host).

Further, if the function's IM code contains a remote interceptor (formal dynamic delegate), a test script can dynamically switch ownership of the function implementation between the target and the host while running. The script implementing the mock function does not have to change regardless of the location of its caller.

Perl

TestScript.pl

TestScript.pl is an example test script that is responsible for starting the mock script running, calling the test function and performing required handshaking with the mock script. It is copied from the example shown in the topic Using Scripts to Automate Software Testing with the extra handshaking and synchronization code added.

Note that the mechanics of calling the function do not change no matter where its currently registered Owner resides.

In a testing scenario, this script would be a member of a workspace and would have its Included property set to true so that the script will execute when the workspace is run.

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

# Create a new test in the parent suite
my $test = $main::testSuite->Tests->Add("Leap Year Positive Test");

# Start the mock script running asynchronously
StartMockScript("LeapYearMock.pl");
# Wait for the mock script to register as the owner of the function
WaitForOwnerRegistration("IsLeapYear");

# Initialize the test input values
my @input = (1600, 1604, 1996, 2000);

# Get the function user interface
my $f = $main::ascript->Functions->Item("IsLeapYear")->User;

# Iterate through the memebers of @input
for my $i (0..$#input)
{
    # Set the parameter value
    $f->ParameterList->{uYear} = $input[$i];

    # Call the function synchronously; the STRIDE runtime will route the call to the
    # currently registered function owner
    $f->Call();

    # Test the return value;
    # note that the value is returned as the name of the enumerator
    if ($f->ReturnValue eq "YES") {
        $test->{Status} = "PASS";
    }
    else {
        $test->{Status} = "FAIL";
        last;
    } 
}

# Notify the mock script that it should end
NotifyStopMockScript("LeapYearMock.pl");

#---------------------------------
# Starts a script in the current workspace running asynchronously
# 
# Param:
#   scriptName - Name of the script to be run (name with extension only; no path)
# Notes: 
# - The specified script must be a member of the current Studio Workspace
# - The shutdown notification trigger file is deleted if it exists
#---------------------------------
sub StartMockScript()
{
    my $scriptName = shift;

    # make sure trigger file isn't there
    unlink GetTriggerFileName($scriptName);
    
    my $file = $main::studio->Workspace->Files->Item($scriptName);
    if (!$file->IsRunning) {
        $file->RunNonBlocking();
    }
}

#---------------------------------
# Notifies an asynchronously running script that it should end
# 
# Param:
#   scriptName - Name of the script to notify (name with extension only; no path)
# Notes:
# - The specified script must be a member of the current Studio Workspace<br>
# - No notification occurs if the specified script isn't currently running
#---------------------------------
sub NotifyStopMockScript()
{
    my $scriptName = shift;
    
    my $file = $main::studio->Workspace->Files->Item($scriptName);
    if ($file->IsRunning) {
        # create trigger file informing mock script to end
        my $triggerName = GetTriggerFileName($scriptName);
        open FILE, ">", $triggerName;
    }
}

#---------------------------------
# Waits for the Owner registration of the specified function
#
# Param:
#   functionName - Name of the function to be waited upon
# Notes:
# - Once the function Owner is registerd, it can be called through the STRIDE runtime
# - The function gives up waiting after 1000 ms
#---------------------------------
sub WaitForOwnerRegistration()
{
    my $functionName = shift;

    my $f = $main::ascript->Functions->Item($functionName)->Owner;

    # note: time is expressed in seconds in perl
    my $startTime = time;
    do {
        $main::ascript->Sleep(100);
    } while (!$f->IsRegistered && (time - $startTime) < 2);    
}

#---------------------------------
# Returns the full path of a shutdown trigger file used to communicate between
# scripts running on different threads.
#
# Param:
#   scriptName - Name of the script to be notified
# Notes:
# - By convention, we use the existence of a file to notify an asynchronously running
#   script that it should end. The file is written in the current workspace directory.
#---------------------------------
sub GetTriggerFileName()
{
    my $scriptName = shift;
    return $main::studio->Workspace->Path."\\__$scriptName.shutdown";
}

LeapYearMock.pl

LeapYearMock.pl is an implementation of the function we are mocking. The mock function's implementation can be found in IsLeapYear(); the rest of the script handles housekeeping for the STRIDE runtime.

Pay special attention to the do/while loop which services the message queue in the injected ascript instance. Events are placed in the queue by the STRIDE runtime and returned to the script by the WaitForEvent() method. The returned event object is characterized by testing the values of its Type and Name properties. Then acted upon by the script. (This is analogous to the runtime thread and the IM running on a target system.)

In a testing scenario, this script would be a member of a workspace and would have its Included property set to false so that the script can be started and stopped asynchronously by another script.


# standard STRIDE stuff
use strict;
use Win32::OLE;
Win32::OLE->Option(Warn => 3);

# get the owner instance of the function
my $f = $main::ascript->Functions->Item("IsLeapYear")->Owner;

# Register the script as the owner of the IsLeapYear() function.
# Now all calls to IsLeaprYear() will be routed to this implementation by
# the STRIDE runtime.
$f->Register();

# This causes ascript.WaitForEvent() to time out every 500 ms.
# We do this so that in the message loop below, the while() test
# will be evaluated when no messages are arriving.
$main::ascript->{WaitTimeoutPeriod} = 500;

do {
    # Wait for the runtime to notify us that an event has been routed to this script
    my $e = $main::ascript->WaitForEvent();

    if ($e->Type eq "FunctionOwner" && $e->Name eq "IsLeapYear") {
        # Get the passed-in parameter
        my $uYear = $e->ParameterList->uYear;
        # Call the mock function; set the Owner function return value
        $e->{ReturnValue} = IsLeapYear($uYear);
        # Return from the function call.
        $e->Return();
    }
    # uncomment the following lines to see the timeout error generated when no
    # envent is received after 500 ms of waiting
    #else {
    #    $main::studio->Output->PrintMessage($e->Type." - ".$e->Name);
    #}
} while (!IsTimeToQuit());

# Here we retrieve the value of a symbol #defined in a .h file seen by the
# STRIDE compiler
my $FirstGregorianYear = $main::ascript->Constants->Item("FIRST_GREGORIAN_YEAR");

#---------------------------------
# This is our script implementation of the fucntion we want to simulate (mock)
#
# Param: 
#   uYear - The year to be tested
# Return:
#   String "YES" or "NO"
# Notes:
#  - Any year that is less than FIRST_GREGORIAN_YEAR is considered not a leap year
#---------------------------------
sub IsLeapYear()
{
    # get the passed-in argument
    my $uYear = shift;

    if ($uYear < $FirstGregorianYear) {
        return "NO";
    }

    if ($uYear % 4) {
        return "NO";
    }

    if ($uYear % 100) {
        return "YES";
    }

    if ($uYear % 400) {
        return "NO";
    }

    return "YES";    
}

#---------------------------------
# Returns a value indicating whether the script should end
#
# Return:
#   0 -> the script should not end; 1 -> the script should end
# Notes:
# - By convention, we use the existence of a specific file to notify an asynchronously running
#   script that it should end. The file is written in the current workspace directory.
# - If the trigger file exists it is deleted
#---------------------------------
sub IsTimeToQuit()
{
    # check for existence of trigger file
    my $scriptName = $main::ascript->ScriptName;
    my $triggerName = GetTriggerFileName($scriptName);

    # check for file existence
    if (-e $triggerName)
    {
        # delete the trigger file
        unlink $triggerName;
        return 1;
    }
    return 0;
}

#---------------------------------
# Returns the full path of a shutdown trigger file used to communicate between
# scripts running on different threads
#
# Param: 
#   scriptName - Name of the script to be notified
# Return:
#   String
#---------------------------------
sub GetTriggerFileName()
{
    my $scriptName = shift;
    return $main::studio->Workspace->Path."\\__$scriptName.shutdown";
}

JScript

TestScript.js

TestScript.js is an example test script that is responsible for starting the mock script running, calling the test function and performing required handshaking with the mock script. It is copied from the example shown in the topic Using Scripts to Automate Software Testing with the extra handshaking and synchronization code added.

Note that the mechanics of calling the function do not change no matter where its currently registered Owner resides.

In a testing scenario, this script would be a member of a workspace and would have its Included property set to true so that the script will execute when the workspace is run.

// create a new test in the parent suite
var test = testSuite.Tests.Add("Leap Year Positive Test");

// start the mock script running asynchronously
startMockScript("LeapYearMock.js");
// wait for the mock script to register as the owner of the function
waitForOwnerRegistration("IsLeapYear");

// initialize the test input values
var arInput = new Array(1600, 1604, 1996, 2000);

// get the function user instance
var f = ascript.Functions.Item("IsLeapYear").User;

// iterate through the members of arInput
for (var nIndex in arInput) {
    // set the parameter value
    f.ParameterList.uYear = arInput[nIndex];
    
    // call the function synchronously; the STRIDE runtime will route the call to the
    // currently registered function owner
    f.Call();
    
    // test the return value; 
    // note that the value is returned as the name of the enumerator
    if (f.ReturnValue == "YES") {
        test.Status = "PASS";
    }
    else {
        test.Status = "FAIL";
        break;
    }
}

// notify the mock script that it should end
notifyStopMockScript("LeapYearMock.js");


/**
 * Starts a script in the current workspace running asynchronously
 * 
 * @notes 
 * - The specified script must be a member of the current Studio Workspace<br>
 * - The shutdown notification trigger file is deleted if it exists
 * @param scriptName Name of the script to be run (name with extension only; no path)
 */
function startMockScript(scriptName)
{
    var fso = new ActiveXObject("Scripting.FileSystemObject");

    // make sure that the trigger file isn't there
    var triggerName = GetTriggerFileName(scriptName);
    if (fso.FileExists(triggerName)) {
        // delete the shutdown trigger file if it exists
        fso.DeleteFile(triggerName);
    }

    // get the stride file object from studio
    var file = studio.Workspace.Files.Item(scriptName);
    if (!file.IsRunning) {
        // start the script running in a separate thread
        file.RunNonBlocking();
    }
}

/**
 * Notifies an asynchronously running script that it should end
 * 
 * @notes 
 * - The specified script must be a member of the current Studio Workspace<br>
 * - No notification occurs if the specified script isn't currently running
 * @param scriptName Name of the script to notify (name with extension only; no path)
 */
function notifyStopMockScript(scriptName)
{
    var fso = new ActiveXObject("Scripting.FileSystemObject");

    // get the stride file object from studio
    var file = studio.Workspace.Files.Item(scriptName);
    if (file.IsRunning) {
        var triggerName = GetTriggerFileName(scriptName);
        // create the trigger file; used to notify the script running asynchronously
        fso.CreateTextFile(triggerName);
    }
}

/**
 * Waits for the Owner registration of the specified function
 * @notes
 * - Once the function Owner is registerd, it can be called through the STRIDE runtime<br>
 * - The function gives up waiting after 1000 ms
 * @param functionName
 *               Name of the function to be waited upon
 */
function waitForOwnerRegistration(functionName)
{
    var f = ascript.Functions.Item(functionName).Owner;

    var timeStart = new Date();
    do {
        ascript.Sleep(100);
        var timeNow = new Date();
    } while (!f.IsRegistered & timeNow - timeStart < 1000);
}


/**
 * Returns the full path of a shutdown trigger file used to communicate between
 * scripts running on different threads.
 * @notes
 * - By convention, we use the existence of a file to notify an asynchronously running
 * script that it should end. The file is written in the current workspace directory.
 * @param scriptName Name of the script to be notified
 */
function GetTriggerFileName(scriptName)
{
    return studio.Workspace.Path + "\\__" + scriptName + ".shutdown";
}

LeapYearMock.js

LeapYearMock.js is an implementation of the function we are mocking. The mock function's implementation can be found in IsLeapYear(); the rest of the script handles housekeeping for the STRIDE runtime.

Pay special attention to the do/while loop which services the message queue in the injected ascript instance. Events are placed in the queue by the STRIDE runtime and returned to the script by the WaitForEvent() method. The returned event object is characterized by testing the values of its Type and Name properties. Then acted upon by the script. (This is analogous to the runtime thread and the IM running on a target system.)

In a testing scenario, this script would be a member of a workspace and would have its Included property set to false so that the script can be started and stopped asynchronously by another script.

// get the owner instance of the function
var f = ascript.Functions.Item("IsLeapYear").Owner;

// register this script as the owner of the IsLeapYear() function
// now all calls to IsLeapYear() will be routed to this implementation by
// the STRIDE runtime
f.Register();

// This causes ascript.WaitForEvent() to timeout every 500 ms.
// We do this so that in the message loop below, the while() test
// will be evaluated when no messages are arriving
ascript.WaitTimeoutPeriod = 500;

do {
    // wait for runtime to notify us that an event has been routed to this script
    var e = ascript.WaitForEvent();

    switch (e.Type) {
    case "FunctionOwner":
        if (e.Name == "IsLeapYear") {
            // get the passed-in parameter
            var uYear = e.ParameterList.uYear;
            // call the mock function; set the Owner function return value
            e.ReturnValue = IsLeapYear(uYear);
            // return from the function call
            e.Return();
        }
        break;

    /*  uncomment these lines to see the timeout errors generated when no
        event is received after 500 ms of waiting
    default:
        studio.Output.PrintMessage(e.Type+" - "+e.Name);
    */
    }
} while (!IsTimeToQuit());


// Here we retrieve the value of a symbol #defined in a .h file seen by the
// STRIDE compiler
var firstGregorianYear = ascript.Constants.Item("FIRST_GREGORIAN_YEAR").Value;

/**
 * This is our script implementation of the function we want to simulate (mock)
 * 
 * @param uYear  The year to be tested
 * @return
 * String "YES" or "NO"
 * @notes 
 * - Any year less than FIRST_GREGORIAN_YEAR is considered not a leap year
 */
function IsLeapYear(uYear)
{
    if (uYear < firstGregorianYear) {
        return "NO";
    }

    if (uYear % 4) {
        return "NO";
    }

    if (uYear % 100) {
        return "YES";
    }

    if (uYear % 400) {
        return "NO";
    }

    return "YES";
}

/**
 * Returns true or false, indicating whether this script should end
 * 
 * @notes 
 * - By convention, we use the existence of a specific file to notify an asynchronously running
 * script that it should end. The file is written in the current workspace directory.<br>
 * - If the trigger file exists it is deleted
 */
function IsTimeToQuit()
{
    var fso = new ActiveXObject("Scripting.FileSystemObject");

    // check for existence of trigger file
    var scriptName = ascript.ScriptName;
    if (fso.FileExists(GetTriggerFileName(scriptName))) {
        // delete the trigger file
        fso.DeleteFile(GetTriggerFileName(scriptName));
        return true;
    }
    return false;
}

/**
 * Returns the full path of a shutdown trigger file used to communicate between
 * scripts running on different threads.
 * @notes
 * - By convention, we use the existence of a file to notify an asynchronously running
 * script that it should end. The file is written in the current workspace directory.
 * @param scriptName Name of the script to be notified
 */
function GetTriggerFileName(scriptName)
{
    return studio.Workspace.Path + "\\__" + scriptName + ".shutdown";
}