Concurrency Test Explained

Before you record and/or code your concurrency test, you record window declarations that describe the elements of the application’s GUI. These are placed in a file named frame.inc, which is automatically included with your test case when you compile. Use Silk Test Classic to generate this file because Silk Test Classic does most of the work.

The following code sample gives just those window declarations that are used in the Concurrency Test Example:
window MainWin Personnel
  tag "Personnel"
  PopupList EmployeeList
  Menu Employee
    tag "Employee"
  MenuItem Edit
    tag "Edit"
  // ...

window DialogBox Employee
  tag "Employee"
  parent Personnel
  TextField Salary
    tag "Salary"
  PushButton OK
    tag "OK"
  // ...

The following explanation of the Concurrency Test Example gives the testing paradigm for a simple concurrency test and provides explanations of many of the code constructs. This should enable you to read the example without referring to the Help. There you will find more detailed explanations of these language constructs, plus explanations of the constructs not explained here. The explanation of each piece of code follows that code.

const ACCEPT_TIMEOUT = 15

The first line of the testcase file defines the timeout value (in seconds) to be used while waiting for a window to display.

multitestcase MyTest (LIST OF STRING lsMachine)

The test case function declaration starts with the multitestcase keyword. It specifies a LIST OF STRING argument that contains the machine names for the set of client machines to be tested. You can implement and maintain this list in your test plan, by using the test plan editor. The machine names you use in this list are the names of the Agents of your target machines.

for each sMachine in lsMachine
  SetUpMachine (sMachine, Personnel)

To prepare your client machines for testing, you must connect Silk Test Classic to each Agent and, by means of the Agent, bring up the application on each machine. In this example, all Agents are running the same software and so all have the same MainWin declaration and therefore just one test frame file. This means you can initialize all your machines the same way; for each machine being tested, you pass to SetUpMachine the main window name you specified in your test frame file. The SetUpMachine function issues a Connect call for each machine. It associates the main window name you specified (Personnel) with each machine so that the DefaultBaseState function can subsequently retrieve it.

SetMultiAppStates ()

The SetMultiAppStates function reads the information associated with each machine to determine whether the machine needs to be set to an application state. In this case no application state was specified (it would have been a third argument for SetUpMachine). Therefore, SetMultiAppStates calls the DefaultBaseState function for each machine. In this example, DefaultBaseState drives the Agent for each machine to open the main window of the Personnel application. This application is then active on the client machine and 4Test can send test case statements to the Agent to drive application operations.

 for each sMachine in lsMachine
  spawn
    // The code to be executed in parallel by
    // all machines... (described below)
rendezvous

Because this is a concurrency test, you want all client applications to execute the test at exactly the same time. The spawn statement starts an execution thread in which each statement in the indented code block runs in parallel with all currently running threads. In this example, a thread is started for each machine in the list of machines being tested. 4Test sends the statements in the indented code block to the Agents on each machine and then waits at the rendezvous statement until all Agents report that all the code statements have been executed.

The following is the code defined for the spawn statement:
// The code to be executed in parallel by
// all machines:
SetMachine (sMachine)
Personnel.EmployeeList.Select ("John Doe")
Personnel.Employee.Edit.Pick ()
Employee.Salary.SetText 
[STRING] RandInt (50000, 70000))

Each thread executes operations that prepare for an attempt to perform concurrent writes to the same database record. The SetMachine function establishes the Agent that is to execute the code in this thread. The next two statements drive the application’s user interface to select John Doe’s record from the employee list box and then to pick the Edit option from the Employee menu. This opens the Employee dialog box and displays John Doe’s employee record. The last thread operation sets the salary field in this dialog box to a random number. At this point the client is prepared to attempt a write to John Doe’s employee record. When this point has been reached by all clients, the rendezvous statement is satisfied, and 4Test can continue with the next script statement.

for each sMachine in lsMachine
  spawn
    SetMachine (sMachine)
    Employee.OK.Click ()
    if (MessageBox.Exists (ACCEPT_TIMEOUT))
      SetMachineData (NULL, "sMessage", 
      MessageBox.Message.GetText ())
      MessageBox.OK.Click ()
      Employee.Cancel.Click ()
    else if (Employee.Exists ())
      AppError ("Employee dialog not dismissed 
        after {ACCEPT_TIMEOUT} seconds")
rendezvous

Now that all the clients are ready to write to the database, the script creates a thread for each client, in which each attempts to save the same employee record at the same time. There is only one operation for each Agent to execute: Employee.OK.Click, which clicks the OK button to commit the edit performed in the previous thread.

The test expects the application to report the concurrency conflict with message boxes for all but one client and for that client to close its dialog box within 15 seconds. The if-else construct saves the text of the message in the error message box by means of the SetMachineData function. It then closes the message box and the Employee window so that the recovery system will not report that it had to close windows. This is good practice because it means fewer messages to interpret in the results file.

The "else if" section of the if-else checks to see whether the Employee window remains open, presumably because it is held by a deadlock condition; this is a test case failure. In this case, the AppError function places the string "***ERROR:" in front of the descriptive error message and raises an exception; all Agents terminate their threads and the test case exits.

iSucceed = 0
for each sMachine in lsMachine
  sMessage = GetMachineData (sMachine, "sMessage")
  if (sMessage == NULL)
    iSucceed += 1
  else
    Print ("Machine {sMachine} got message '{sMessage}'")
Verify (iSucceed, 1, "number of machines that succeeded")

The last section of code evaluates the results of the concurrency test in the event that all threads completed. If more than one client successfully wrote to the database, the test actually failed.

GetMachineData retrieves the message box message (if any) associated with each machine. If there was no message, iSucceed is incremented; it holds the count of "successes." The Print function writes the text of the message box to the results file for each machine that had a message box. You can read the results file to verify that the correct message was reported. Alternatively, you could modify the test to automatically verify the message text.

The Verify function verifies that one and only one machine succeeded. If the comparison in the Verify function fails, Verify raises an exception. All exceptions are listed in the results file.