Expect.jl: Synchronous communication with interactive programs

Expect.jl allows to spawn interactive applications and control them by communicating through their standard input/output streams.

Expect.jl is similar to D. Libes’s Expect and many other Expect-like modules. It can be used to automate interactive applications such as shells, perform software testing or drive test harnesses easily.

Warning

This is a work-in-progress, the API is subject to change without notice. Suggestions about API design are highly appeciated.

Releases

This module is registered in the main Julia Package Repository. See Julia’s Pkg module for installation instructions.

Releases are ephemeral at the time of writing. Julia 0.3 is supported, but all development is performed with Julia 0.4.

All the relevant source/developer information can be found on Gitlab:

https://gitlab.com/wavexx/Expect.jl

Introduction

The idea behind Expect is simple: commands are spawned with their I/O descriptors attached to the current process. You write to the command’s standard input, then you wait for a set of known replies from it’s output.

If the program you’re communicating with was already meant to be controlled through a serial protocol, then using readandwrite directly is recommended as it avoids some overhead when spawning the process.

Expect differs from readandwrite in two major ways:

  • Commands are spawned under a pseudo-TTY interface, forcing libc-based programs to flush their output buffer at each line.
  • Reading is performed for you until a set of possible conditions is met (a pattern matches, timeout occurs or the command exits).

Basic usage

Using ftp to retrieve a remote file list:

julia> # Start a new process
julia> using Expect;
julia> proc = ExpectProc(`ftp ftp.scene.org`, 16);
julia> # Wait for the prompt
julia> expect!(proc, "> ");
julia> # Send "ls" and wait for prompt
julia> sendline(proc, "ls");
julia> expect!(proc, "> ");
julia> # Collect all the output since the last prompt
julia> list = proc.before;
julia> println(list);
drwxrwsr-x  11 redhound ftpadm       4096 Mar  2 20:57 incoming
drwx------   2 redhound ftpadm      16384 Feb 24 12:58 lost+found
-rw-r--r--   1 redhound ftpadm   16060223 Mar 22 01:15 ls-lR
-rw-r--r--   1 redhound ftpadm    2723919 Mar 22 01:15 ls-lR.gz
drwxrwsr-x  12 redhound ftpadm       4096 Nov 26  2013 mirrors
drwxrwsr-x   8 redhound ftpadm       4096 Mar  4 18:25 pub
-rw-r--r--   1 redhound ftpadm        547 Nov  9  2013 welcome.msg

Some highlights about the example:

  • The first argument to ExpectProc is just a regular Cmd object.
  • You don’t have to worry about buffering.
  • An ExpectProc handle can be read or written to using the standard I/O functions (though regular “read” calls will not handle timeouts).
  • proc.before contains all the command’s output since the last expect! match (excluding the match itself).

The last point can be clarified with the following example:

julia> using Expect;
julia> proc = ExpectProc(`echo "a b c "`, 16);
julia> expect!(proc, " ")
"a"
julia> expect!(proc, " ")
"b"
julia> expect!(proc, " ")
"c"

For convenience, expect! already returns the contents of proc.before when given a single pattern to match.

expect! however is normally used with a list of possible matches to perform. In this scenario, the index that matched first will be returned. The matched string itself is also available in proc.match. This is useful to perform conditional processing depending on the command’s output:

using Expect
proc = ExpectProc(`interpreter`, 16)
sendline(proc, "perform")
idx = expect!(proc, ["> ", "ERROR: "])
if idx == 2
    # error occurred ...
end

The matches themselves can be regular strings or Regex objects. When a Regex is used, the content of proc.match contains a match object for the element that matched.

See tests/runtests.jl for more usage examples.

Reference

Constructor

ExpectProc(cmd, timeout; env, codec):

Constructs a new ExpectProc object.

cmd:the Cmd command to be spawned.
timeout:default communication timeout.
env:environment for the command (defaults as a copy of the current)
codec:output decoding function (defaults to utf8)

Functions

sendline(proc, string):

Write string to the standard input of the program, followed by a newline.

expect!(proc, vector; timeout):

Read the standard output of the program until one of the strings/regular expressions specified in vector matches. The index of the element that matched first is returned. Matches are searched in sequential order.

When timeout is specified, it overrides the default timeout specified in the constructor.

proc.before is reset at each call to contain all the standard output before the match.

proc.match contains either a string or a match object for the element that matched.

expect!(proc, element; timeout):

Read the standard output of the program until the string/regular expressions specified in element matches. The content of proc.before is returned.

Exceptions

ExpectTimeout:
Reading from the command stalled for the specified number of seconds without matching any pattern. Reading can continue.
ExpectEOF:
The output ended without matching any of the specified patterns.