The PAL Language

PAL is not a single language but rather a combination of two things:

  1. A standard set of control commands.

  2. The classes and methods defined for the specific protocol in question.

The standard commands provide loops, if blocks, variables etc. The protocol method commands talk to the server.

Some Sample PAL Scripts

As a basis for this tutorial we take the 'demo' protocol provided with ASL. Here is a sample PAL/demo script:

<?xml?>
<pal
    name = "hello"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <echo>
    Hello world!
    </echo>
</pal>

Here is a script that demonstrates some of the standard PAL control commands:

<?xml?>
<pal
    name = "loop1"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <set name = "index" value = "0" />
    <repeat>
        <inc name = "index" />
        <if name = "index" value = "10">
            <break/>
        </if>
        <else>
            <echo>I can count up to $index</echo>
        </else>
    </repeat>
</pal>

And an equivalent, shorter version:

<?xml?>
<pal
    name = "loop2"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <repeat times = "10" counter = "index">
        <echo>I can count up to $index</echo>
    </repeat>
</pal>

To connect to a server and open a session we use the control command. Here is a script that connects to a server and then displays the connection properties:

<?xml?>
<pal
    name = "session"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <session>
        <echo>version_major=$version_major</echo>
        <echo>version_minor=$version_minor</echo>
        <echo>channel_max=$channel_max</echo>
        <echo>frame_max=$frame_max</echo>
        <echo>heartbeat=$heartbeat</echo>
        <echo>context_key=$context_key</echo>
        <echo>server_product=$server_product</echo>
        <echo>server_version=$server_version</echo>
        <echo>server_platform=$server_platform</echo>
        <echo>server_copyright=$server_copyright</echo>
        <echo>server_information=$server_information</echo>
    </session>
</pal>

These are the connection properties for the demo protocol. Other protocols such as AMQ may have other or different properties. You will want to read the API documentation for the protocol to know what these are.

Note that the script does not specify what server to talk to, nor the IP port. These and other options are passed on the command-line. For the standard C PAL implementation run the script executable with "-h" to get a list of all options.

Having established a session we can send methods to the server:

<?xml?>
<pal
    name = "single"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <session>
        <exchange_declare exchange = "myexchange" class = "fanout" />
        <queue_declare queue = "myqueue" scope = "default" />
        <queue_bind queue = "myqueue" scope = "default" exchange = "myexchange" />
        <basic_content size = "64000" message_id = "id-0001" />
        <basic_publish exchange = "myexchange" routing_key = "myqueue" />
        <basic_browse queue = "myqueue" scope = "default" />
        <basic_arrived>
            <echo>Message '$message_id' came back to us</echo>
        </basic_arrived>
        <empty>
            <echo>Message did not come back, this is bad!</echo>
        </empty>
    </session>
</pal>

PAL lets us define often-used method arguments at the 'session' level. These are then inherited to methods that don't explicity specify them. So we can rewrite the above script to make it shorter:

<?xml?>
<pal
    name = "single2"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <session
        exchange = "myexchange"
        queue = "myqueue"
        scope = "default"
        >
        <exchange_declare class = "fanout" />
        <queue_declare />
        <queue_bind />
        <basic_content size = "64000" message_id = "id-0001" />
        <basic_publish routing_key = "myqueue" />
        <basic_browse />
        <basic_arrived>
            <echo>Message '$message_id' came back to us</echo>
        </basic_arrived>
        <empty>
            <echo>Message did not come back, this is bad!</echo>
        </empty>
    </session>
</pal>

We can also create content bodies by reading data from test data files, or by running helper commands. See the 'read' and 'exec' options for the content commands. It's as simple as (for instance):

<basic_content exec = "perl -S myprog.pl" />

Scripts can be made flexible by passing arguments on the command line. Here is a simple example:

<?xml?>
<pal
    name = "cmdline"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <set name = "number" value = "1234" cmdline = "N" />
    <set name = "string" value = "abcd" cmdline = "S" />
    <echo>Number=$number, string=$string</echo>
</pal>

Which we can run with the options -N and -S:

cmdline -N 9999 -S XXXX

Lastly let's look at macros, which are ways of collecting repetitive commands into groups to save time:

<?xml?>
<pal
    name = "macros"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <macro name = "queue new">
        <exchange_declare exchange = "stdqueue" class = "fanout" />
        <queue_declare queue = "$queue" scope = "default" />
        <queue_bind queue = "$queue" exchange = "stdqueue" />
    </macro>
    <macro name = "send message">
        <basic_content size = "$size" message_id = "id-$random" />
        <basic_publish exchange = "stdqueue" routing_key = "$queue" />
    </macro>
    <session>
        <set name = "queue" value = "myqueue" />
        <invoke macro = "queue new" />
        <invoke macro = "send message">
            <set name = "size" value = "64000" />
        </invoke>
        <basic_browse queue = "myqueue" scope = "default" />
        <basic_arrived>
            <echo>Message '$message_id' came back to us</echo>
        </basic_arrived>
        <empty>
            <echo>Message did not come back, this is bad!</echo>
        </empty>
    </session>
</pal>

If you use macros to any extent you'll want to look at the command, described in the next section.

The Standard PAL Commands

These are the basic scripting commands, which can be nested to form scripts of any complexity:

invoke  - invoke a macro
server  - start a protocol server
set     - define or modify a variable
inc     - increment a counter variable
dec     - decrement a counter variable
echo    - echo text to the console
abort   - echo text to the console and abort the script
assert  - assert some condition is true
repeat  - repeat a loop some number of times
while   - repeat a loop while some condition is true
break   - exit a loop
if      - execute commands if a condition is true
else    - execute commands if the previous if condition was false
elsif   - combined if and else
wait    - wait for the server to return data
sleep   - pause the script

Basic Script Structure

The basic structure of the script is:

<pal
    name = "scriptname"
    script = "palscript"
    target = "stdc"
    [ <include filename = "filename" /> ]...
    [ <macro name = "macroname">
        [ script command ]...
      </macro> ]...
    [ <session>
        [ script command ]...
      </session> ]...
</pal>
  • To write PAL scripts for the ASL demo protocol, use 'script = "demo_pal_gen"'.

  • To write PAL scripts for the AMQ protocol, use 'script = "amq_pal_gen"'.

The Include Command

The command copies the contents of another PAL file into the current script. It has this syntax:

<include
    filename = "scriptfile"
    />
  • The filename must include the file extension. The included file should not have a level but may contain macros or script commands.

The Macro Command

The command defines a block of commands that can be reused in as a single command in the script. It has this syntax:


<macro
    name = "macroname">
    [ script command ]...
</macro>
  • Macros have no effect until they are used through the 'invoke' command.

The Session Command

The command defines a session:

<session
  [ server = "servername" ]
  [ failover = "msecs" ]
    >
    [ script command ]...
</session>
  • PAL may in future allow multiple sessions to be started in parallel, but for now sessions are executed serially. Each session will restart in a new connection, whatever the state of previous sessions.

  • The servername can be used to test multiple servers in a single script. This option is not used for general-purpose scripts.

  • If the failover is set to an integer greater than zero, on a broken connection the script will pause for the specified number of milliseconds, and then try to reconnect to the same or alternate server. To use alternate servers, specify multiple server names in the 'server' attribute, seperated by spaces.

The Invoke Command

The command expands a macro:

<invoke
    macro = "macroname"
    />
  • If the macro uses variables in commands, you can set these variables either before the command, or inside it, using commands.

The Server Command

The commands starts or restarts a protocol server:

<server
    name = "servername"
  [ stdout = "filename" ]
  [ stderr = "filename" ]
  [ where = "directory" ]
    />
  • Do not specify a file extension (.exe) or your scripts will not be portable.

  • If a protocol server was already started, this command stops the server and then restarts it.

  • Only one protocol server can be started at a time.

  • The name value can include arbitrary server arguments but not shell redirection commands.

  • To redirect the server's output, use the stdout and stderr options.

The Timer Command

The commands shows or resets the script timer.

<timer
  [ action = "show | reset" ]
    />
  • The action is optional and defaults to "reset".

The Set Command

The command defines a variable. Variables can be strings or integers. You can use variables in repeat, while, and if blocks, and as symbols for templating arguments and strings. Untyped variables are typed according to their value.

<set
    name = "variablename"
  [ value = "newvalue" ]
  [ type = "string | integer" ]
    cmdline = "char"
    />
  • The value is optional, and defaults to "".

  • If the value is purely numeric, the type will default to "integer", and if not the type will default to "string".

  • The cmdline option specifies a single character. Do not use one of the command-line options already used by the PAL implementation (see section at the end of this document).

The Inc Command

The command increments an integer variable:

<inc
    name = "variablename"
    />

The Dec Command

The command decrements an integer variable:

<dec
    name = "variablename"
    />
  • Decrementing a variable below zero is illegal and raises a fatal error. This is done to catch script errors - negative values are normally not meaningful in test scripts.

The Random Command

The command sets a variable to a random value within a specified range:

<random
    name = "variablename"
  [ min = "minvalue" ]
    max = "maxvalue"
    />
  • The minimum is optional, and defaults to zero.

The Read Command

The command accepts a line of input from the console and assigns this to a variable:

<read
    name = "variablename"
  [ prompt = "promptstring" ]
    />
  • The prompt is optional; if defined, this will be shown to the user (with no newline) before the console waits for input.

The Echo Command

The command echoes a line of text:

<echo [trace = "1|2|3"]>line of text</echo>
  • The text can use variables with the syntax: $variablename.

  • The trace level set using a command-line switch. Use the help option (-h) on the test program for details.

The Assert Command

The command tests a condition and aborts the script if the condition is false.

<assert
    name = "variablename"
  [ test = "eq | ne | gt | lt | ge | le" ]
  [ value = "testvalue" ]
    >[line of text]</assert>
  • The variablename is a script variable, or a connection or session property, or a standard PAL variable.

  • If the test and value are not specified, they default to "ne" and "0" or "" depending on the type of variable.

  • If just the test is not specified, it defaults to "eq".

  • If the assert statement includes a message, this is printed before an assertion failure.

The Repeat Command

The command defines an iterative loop, which can run forever or for a specified number of times. The counter is global (do not use the same counter for two nested loops). To access the counter within the repeat loop, use $variablename.

<repeat
  [ counter = "variablename" ]
  [ times = "integer" ]
  [ progress = "integer" ]
    >
    [ script command ]...
</repeat>
  • If the times attribute is not specified, the loop will run forever or until the script does a .

  • The counter does not need to be previously defined. If no counter is specified, the repeat loop will create its own internal counter which cannot then be used as a symbolic value.

  • If the progress option is set to an integer N, then after every N passes through the loop, the test script will print a dot to the standard error output.

The While Command

The command defines a conditional loop, which runs so long as a specified condition is true:

<while
    name = "variablename"
  [ test = "eq | ne | gt | lt | ge | le" ]
  [ value = "testvalue" ]
  [ counter = "variablename" ]
  [ progress = "integer" ]
    >
    [ script command ]...
</while>
  • See the command for an explanation of the test and value properties.

  • If a counter is specified, this variable is automatically set to zero when the while loop starts and incremented each time the loop runs. You can access the counter variable after the while loop.

  • If the progress option is set to an integer N, then after every N passes through the loop, the test script will print a dot to the standard error output.

The Break Command

The command exits the enveloping repeat or while loop and has this syntax:

<break/>

The command defines a block that is executed if a specific condition is true:

<if
    name = "variablename"
  [ test = "eq | ne | gt | lt | ge | le" ]
  [ value = "testvalue" ]
    >
    [ script command ]...
</if>
  • See the command for an explanation of the test and value properties.

The Else Command

The command defines a block that is executed if the previous condition was false:

<else>
    [ script command ]...
</else>

The Elsif Command

The command defines a block that is executed if the previous condition was false and some further condition is true:

<elsif
    name = "variablename"
  [ test = "eq | ne | gt | lt | ge | le" ]
  [ value = "testvalue" ]
    >
    [ script command ]...
</elsif>
  • See the command for an explanation of the test and value properties.

The Wait Command

The command pauses the script for a number of milliseconds, or until content is received from the server, whichever is sooner:

<wait
  [ timeout = "milliseconds" ]
    />
  • Inside a session the default timeout is 'forever'. Outside a session, the default timeout is 'zero'.

Here is an example of using the command:

<?xml?>
<pal
    name = "waiting"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <set name = "index" value = "0" />
    <echo>Waiting without an active connection...</echo>
    <wait timeout = "1000" />
    <session>
        <echo>Waiting inside an active connection...</echo>
        <wait timeout = "1000" />
    </session>
    <echo>OK</echo>
</pal>

The Sleep Command

The command pauses the script for an exact number of milliseconds. Unlike it's cousin , the sleep time is not affected by any traffic on the session.

<sleep
    timeout = "milliseconds"
    />
  • Timeout is required for .

The Abort Command

The command echoes a line of text and halts the script.

<abort>line of text</abort>
  • The text can use variables with the syntax: $variablename.

The Exit Command

The command halts the script.

<exit [status = "value"] >
  • The default status value is 0.

Protocol-Specific PAL Commands

An Example

This script sends 10 messages to the server and then reads them back. It uses the simple browse commands - not asynchronous consumers - and is specific to the demo protocol (using AMQ one would probably use consumers and commands to get messages):

<?xml?>
<pal
    name = "content"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <session
        queue    = "test-queue"
        exchange = "test-exchange"
        scope    = "test">
        <exchange_declare class = "fanout" />
        <queue_declare />
        <queue_bind>
            <arguments>
                <field name = "currency" value = "USD" />
                <field name = "symbol"   value = "MSFT" />
            </arguments>
        </queue_bind>
        <repeat times = "10" counter = "id">
          <basic_content size = "64000" content_type = "text/html" message_id = "id-$random">
            <headers>
              <field name = "Numbering" value = "$id" />
              <field name = "Max-items" value = "100" />
              <field name = "Server-name" value = "http://www.openamq.org" />
            </headers>
          </basic_content>
          <basic_publish routing_key = "test-queue" />
        </repeat>
        <repeat>
            <basic_browse />
            <basic_returned>
                <echo>Returned: $message_id</echo>
            </basic_returned>
            <basic_arrived>
                <inc name = "count" />
                <echo>Arrived: $message_id, numbering=$headers-Numbering</echo>
            </basic_arrived>
            <empty>
                <break/>
            </empty>
        </repeat>
        <echo>Total number of messages exchanged: $count</echo>
    </session>
</pal>

General Principles

ASL protocols have the useful property of being very high-level. That is, the protocol methods generally need little or no abstraction to be immediately obvious and useful to application developers. This makes it reasonable in PAL to simply expose the protocol methods directly to the scripting language. This strategy is helped by:

  • The use of clear and consistent names for methods and method properties.

  • The use of intelligent defaults for optional properties.

ASL protocols share the same connection and channel initiation and tear-down architecture. The methods used to do this - such as Connection.Tune - are hidden from the PAL developer and are not exposed in the PAL script language. Specifically, we hide:

  • All Connection class methods.

  • The Channel.Open and Close methods.

  • All methods sent by the server and received by the client. Since PAL is for client-side automation, these cannot be scripted.

Content Commands

For the purposes of explanation we will use the 'demo' protocol that is part of the ASL package. The demo protocol defines one content class, "basic".

For each content class, PAL provides a command to create the content and set its properties. E.g.

<basic_content
  [ size = "bodysize"  ("1024") ]
  [ fill = "random | null | repeat" ("random") ]
  [ read = "..." ]
  [ exec = "..." ]
  [ headers = "0|(1)" ]
  [ content_type = "propertyvalue" ]
  [ content_encoding = "propertyvalue" ]
  [ message_id = "propertyvalue" ]
    >
    [ <headers>
      [ <field
            name = "fieldname"
          [ value = "fieldvalue" ]
          [ type = "string | integer"  ("string") ]
            /> ]...
    </headers> ]
  [ content text ]
</basic_content>
  • The size attribute specifies the size in octets of the content buffer. Its default value is "1024".

  • The fill attribute specifies the fill mode. It can be "random", which sets the body to random data, or "null", which sets it to binary zeroes, or "repeat", which repeats the content text up to the specified size.

  • The body of the content item optionally provides a content text. If this is set, it's reformatted as a single line of text, and used as message body. This overrides the default fill ('random').

  • The read attribute specifies a file from which to read the content body. This is useful when you want to send test messages with a specific format.

  • The exec attribute specifies a command to run, so that the stdout of the command can be used as the content body. The command must be the name of an executable program, on the path, with arguments as desired. The program receives the current content body as stdin, much like a web server CGI program.

  • If the headers field is set to zero, the output of the executed program is not reparsed. If one, the output is reparsed to collect message properties and headers as follows: each line specifies a header field name, followed by ":", followed by a space and a value. Field names starting with "x-table-" are stored as-is (minus the x-table- prefix) in a field table with that name. Other fields must match known content properties. Hyphens are allowed in field names, and field names are case-insensitive. The headers are ended with a blank line. Parsed headers create a CGI-like interface for calling programs.

  • For each content property defined in the protocol (except field tables) PAL defines an attribute for the content command.

  • For field tables, PAL defines a child entity with the same name, e.g. 'headers'. Field tables are then constructed from one or more definitions.

  • After a content command, the script can access the content body size as a variables ($body_size in expressions, or body_size in assertions and conditions).

Protocol Method Commands

A protocol method command sends a protocol method to the server. If the method is a synchronous method, the script waits for a response from the server. If the method is asynchronous, the script continues without waiting. The basic syntax for protocol method commands is:

<class_method [properties...]>
    <field_table_name>
      [ <field
            name = "fieldname"
          [ value = "fieldvalue" ]
          [ type = "string | integer"  ("string") ]
            /> ]...
    </field_table_name>
</class_method>

Properties that are not specified take a default value, which is zero for numeric properties, FALSE for Boolean properties, and NULL for strings and field tables.

Processing Arrived Content

For each content class, PAL provides a command that lets you process arrived messages. Contents do not necessarily arrive in a strict synchronous order - it depends on the protocol - so this command acts as a loop, and repeats for each arrived content at the moment it is invoked.

<basic_arrived
  [ counter = "variablename" ]
    >
    [ script command ]...
</basic_arrived>
<empty>
    [ script command ]...
</empty>
  • If a counter is specified, this variable is automatically set to zero when the loop starts and incremented each time the loop runs. You can access the counter variable after the loop.

  • If there was no arrived content, the script executes the following command, if any.

You can use these variables within an arrived loop:

  • $body_size - size of content body.

  • $body_text - content body as printable text.

  • $exchange - original exchange to which content was sent.

  • $routing_key - routing key specified in content.

  • $producer_id - original producer id.

Processing Returned Content

We process returned content in a similar way to arrived content:

<basic_returned
  [ counter = "variablename" ]
    >
    [ script command ]...
</basic_returned>
<empty>
    [ script command ]...
</empty>
  • If a counter is specified, this variable is automatically set to zero when the loop starts and incremented each time the loop runs. You can access the counter variable after the loop.

  • If there was no arrived content, the script executes the following command, if any.

Synchronous Content Processing

PAL does not provide any asynchronous content processing. The script runs as a single-threaded procedure from start to end. Content will arrive when the script is busy, i.e. during any command that talks to the server. To process content after such commands, use the 'arrived' commands. To process content while not doing such commands, use and then use the arrived command.

PAL Variables

PAL uses the convention '$name' to allow variable substitution. This is allowed in:

  • The body of and commands.

  • All attributes except variablenames.

PAL defines all connection and session properties as variables. The API documentation for the protocol you are using will list these. For the demo protocol they are:

  • Connection properties:

$channel_max
$class_id
$context_key
$frame_max
$heartbeat
$method_id
$reply_code
$reply_text
$server_copyright
$server_information
$server_platform
$server_product
$server_version
$version_major
$version_minor
  • Session properties:

$active
$class_id
$consumer_count
$routing_key
$exchange
$message_count
$method_id
$queue
$reply_code
$reply_text
$ticket

Note that the standard ASL technique for returning values from protocol methods is via the session properties. Thus the variable 'message_count' holds the number of messages after a queue.browse request and a queue.browse-ok response.

You should avoid using your own variables that conflict with the standard connection and session variables.

PAL defines these built-in variables:

  • $script - name of current PAL script.

  • $connection - 1 if the connection is alive, else 0.

  • $session - 1 if the session is alive, else 0.

  • $random - a random integer in the range 0..32767, when used as an insertion value, produces a 4-digit hex string.

  • $body_size - the body size of the last content to be created, arrived, or returned.

PAL resolves a variable reference in this order:

  1. First, in-built variables.

  2. Content properties, inside an arrived/returned loop.

  3. Session properties.

  4. Connection properties.

  5. Script variables and counters.

Here is a sample script that demonstrates various ways of using variables:

<?xml?>
<pal
    name = "symbols"
    script = "demo_pal_gen"
    target = "stdc"
    >
    <set name = "expect_major"   value = "9" />
    <set name = "exchange_class" value = "fanout" />
    <set name = "scope"          value = "test" />
    <set name = "queue"          value = "$scope\\-queue" />
    <set name = "exchange"       value = "$scope\\-exchange" />
    <set name = "times"          value = "100" />
    <set name = "size"           value = "64000" />
    <session
        queue    = "$queue"
        exchange = "$exchange"
        scope    = "$scope">
        <echo>Connected to $server_product/$server_version - $server_platform</echo>
        <assert name = "version_major" value = "$expect_major" />
        <exchange_declare class = "$exchange_class" />
        <queue_declare />
        <queue_bind />
        <repeat times = "$times" counter = "id">
          <basic_content size = "$size" content_type = "text/html" message_id = "id-$id" />
          <basic_publish routing_key = "$queue" />
        </repeat>
        <repeat>
            <basic_browse />
            <basic_returned>
                <echo>Returned: $message_id</echo>
            </basic_returned>
            <basic_arrived>
                <inc name = "count" />
                <echo>Arrived: $message_id</echo>
            </basic_arrived>
            <empty>
                <break/>
            </empty>
        </repeat>
        <echo>Total number of messages exchanged: $count</echo>
    </session>
</pal>