Package: GNAT.Expect

Dependencies

with System;
with GNAT.OS_Lib;
with GNAT.Regpat;

Description

It allows you to easily spawn and communicate with an external process. You can send commands or inputs to the process, and compare the output with some expected regular expression.

Usage example:

Non_Blocking_Spawn (Fd, "ftp machine@domaine"); Timeout := 10000; -- 10 seconds Expect (Fd, Result, Regexp_Array'(+"\(user\)", +"\(passwd\)"), Timeout); case Result is when 1 => Send (Fd, "my_name"); -- matched "user" when 2 => Send (Fd, "my_passwd"); -- matched "passwd" when Expect_Timeout => null; -- timeout when others => null; end case; Close (Fd);

You can also combine multiple regular expressions together, and get the specific string matching a parenthesis pair by doing something like. If you expect either "lang=optional ada" or "lang=ada" from the external process, you can group the two together, which is more efficient, and simply get the name of the language by doing:

declare Matched : Regexp_Array (0 .. 2); begin Expect (Fd, Result, "lang=(optional)? ([a-z]+)", Matched); Put_Line ("Seen: " & Expect_Out (Fd) (Matched (2).First .. Matched (2).Last)); end;

Alternatively, you might choose to use a lower-level interface to the processes, where you can give your own input and output filters every time characters are read from or written to the process.

procedure My_Filter (Descriptor : Process_Descriptor; Str : String) is begin Put_Line (Str); end;

Fd := Non_Blocking_Spawn ("tail -f a_file"); Add_Filter (Fd, My_Filter'Access, Output); Expect (Fd, Result, "", 0); -- wait forever

The above example should probably be run in a separate task, since it is blocking on the call to Expect.

Both examples can be combined, for instance to systematically print the output seen by expect, even though you still want to let Expect do the filtering. You can use the Trace_Filter subprogram for such a filter.

If you want to get the output of a simple command, and ignore any previous existing output, it is recommended to do something like:

Expect (Fd, Result, ".*", Timeout => 0); -- empty the buffer, by matching everything (after checking -- if there was any input). Send (Fd, "command"); Expect (Fd, Result, ".."); -- match only on the output of command

Task Safety ===========

This package is not task-safe. However, you can easily make is task safe by encapsulating the type Process_Descriptor in a protected record. There should not be concurrent calls to Expect.


Header

package GNAT.Expect is
 

Exceptions

Invalid_Process
Raised by most subprograms above when the parameter Descriptor is not a valid process or is a closed process.
Process_Died
Raised by all the expect subprograms if Descriptor was originally a valid process that died while Expect was executing.

Type Summary

Compiled_Regexp_Array
Expect_Match derived from Integer
Filter_Function
Filter_Type
Multiprocess_Regexp
Multiprocess_Regexp_Array
Pattern_Matcher_Access
Process_Descriptor
Primitive Operations:  Add_Filter, Close, Expect, Expect, Expect, Expect, Expect, Expect, Expect, Expect, Expect_Out, Expect_Out_Match, Flush, Get_Error_Fd, Get_Input_Fd, Get_Output_Fd, Get_Pid, Interrupt, Lock_Filters, Remove_Filter, Send, Send_Signal, Unlock_Filters
Process_Descriptor_Access
Process_Id derived from Integer
Regexp_Array

Constants and Named Numbers

Expect_Full_Buffer : constant Expect_Match := -1;
If the buffer was full and some characters were discarded.
Expect_Timeout : constant Expect_Match := -2;
If not output matching the regexps was found before the timeout.
Invalid_Pid : constant Process_Id := -1;
Null_Pid : constant Process_Id := 0;

Other Items:

type Process_Id is new Integer;

type Filter_Type is (Output, Input, Died);
The signals that are emitted by the Process_Descriptor upon state changed in the child. One can connect to any of this signal through the Add_Filter subprograms.

Output => Every time new characters are read from the process associated with Descriptor, the filter is called with these new characters in argument. Note that output is only generated when the program is blocked in a call to Expect.

Input => Every time new characters are written to the process associated with Descriptor, the filter is called with these new characters in argument. Note that input is only generated by calls to Send.

Died => The child process has died, or was explicitly killed


type Process_Descriptor is tagged private;
Contains all the components needed to describe a process handled in this package, including a process identifier, file descriptors associated with the standard input, output and error, and the buffer needed to handle the expect calls.

type Process_Descriptor_Access is access Process_Descriptor'Class;
Spawning a process

procedure Non_Blocking_Spawn
  (Descriptor  : out Process_Descriptor'Class;
   Command     : String;
   Args        : GNAT.OS_Lib.Argument_List;
   Buffer_Size : Natural := 4096;
   Err_To_Out  : Boolean := False);
This call spawns a new process and allows sending commands to the process and/or automatic parsing of the output.

The expect buffer associated with that process can contain at most Buffer_Size characters. Older characters are simply discarded when this buffer is full. Beware that if the buffer is too big, this could slow down the Expect calls if not output is matched, since Expect has to match all the regexp against all the characters in the buffer. If Buffer_Size is 0, there is no limit (ie all the characters are kept till Expect matches), but this is slower.

If Err_To_Out is True, then the standard error of the spawned process is connected to the standard output. This is the only way to get the Except subprograms also match on output on standard error.

Invalid_Process is raised if the process could not be spawned.


procedure Close (Descriptor : in out Process_Descriptor);
Terminate the process and close the pipes to it. It implicitly does the 'wait' command required to clean up the process table. This also frees the buffer associated with the process id.

procedure Send_Signal
  (Descriptor : Process_Descriptor;
   Signal     : Integer);
Send a given signal to the process.

procedure Interrupt (Descriptor : in out Process_Descriptor);
Interrupt the process (the equivalent of Ctrl-C on unix and windows) and call close if the process dies.

function Get_Input_Fd
  (Descriptor : Process_Descriptor)
   return       GNAT.OS_Lib.File_Descriptor;
Return the input file descriptor associated with Descriptor.

function Get_Output_Fd
  (Descriptor : Process_Descriptor)
   return       GNAT.OS_Lib.File_Descriptor;
Return the output file descriptor associated with Descriptor.

function Get_Error_Fd
  (Descriptor : Process_Descriptor)
   return       GNAT.OS_Lib.File_Descriptor;
Return the error output file descriptor associated with Descriptor.

function Get_Pid
  (Descriptor : Process_Descriptor)
   return       Process_Id;
Return the process id assocated with a given process descriptor.

type Filter_Function is access
  procedure
    (Descriptor : Process_Descriptor'Class;
     Str        : String;
     User_Data  : System.Address := System.Null_Address);
This is a rather low-level interface to subprocesses, since basically the filtering is left entirely to the user. See the Expect subprograms below for higher level functions. Function called every time new characters are read from or written to the process.

Str is a string of all these characters.

User_Data, if specified, is a user specific data that will be passed to the filter. Note that no checks are done on this parameter that should be used with cautiousness.


procedure Add_Filter
  (Descriptor : in out Process_Descriptor;
   Filter     : Filter_Function;
   Filter_On  : Filter_Type := Output;
   User_Data  : System.Address := System.Null_Address;
   After      : Boolean := False);
Add a new filter for one of the filter type. This filter will be run before all the existing filters, unless After is set True, in which case it will be run after existing filters. User_Data is passed as is to the filter procedure.

procedure Remove_Filter
  (Descriptor : in out Process_Descriptor;
   Filter     : Filter_Function);
Remove a filter from the list of filters (whatever the type of the filter).

procedure Trace_Filter
  (Descriptor : Process_Descriptor'Class;
   Str        : String;
   User_Data  : System.Address := System.Null_Address);
Function that can be used a filter and that simply outputs Str on Standard_Output. This is mainly used for debugging purposes. User_Data is ignored.

procedure Lock_Filters (Descriptor : in out Process_Descriptor);
Temporarily disables all output and input filters. They will be reactivated only when Unlock_Filters has been called as many times as Lock_Filters;

procedure Unlock_Filters (Descriptor : in out Process_Descriptor);
Unlocks the filters. They are reactivated only if Unlock_Filters has been called as many times as Lock_Filters.

procedure Send
  (Descriptor   : in out Process_Descriptor;
   Str          : String;
   Add_LF       : Boolean := True;
   Empty_Buffer : Boolean := False);
Send a string to the file descriptor.

The string is not formatted in any way, except if Add_LF is True, in which case an ASCII.LF is added at the end, so that Str is recognized as a command by the external process.

If Empty_Buffer is True, any input waiting from the process (or in the buffer) is first discarded before the command is sent. The output filters are of course called as usual.


type Expect_Match is new Integer;
Working on the output (single process, simple regexp)

function "+" (S : String) return GNAT.OS_Lib.String_Access;
Allocate some memory for the string. This is merely a convenience convenience function to help create the array of regexps in the call to Expect.

procedure Expect
  (Descriptor  : in out Process_Descriptor;
   Result      : out Expect_Match;
   Regexp      : String;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Wait till a string matching Fd can be read from Fd, and return 1 if a match was found.

It consumes all the characters read from Fd until a match found, and then sets the return values for the subprograms Expect_Out and Expect_Out_Match.

The empty string "" will never match, and can be used if you only want to match after a specific timeout. Beware that if Timeout is -1 at the time, the current task will be blocked forever.

This command times out after Timeout milliseconds (or never if Timeout is -1). In that case, Expect_Timeout is returned. The value returned by Expect_Out and Expect_Out_Match are meaningless in that case. The regular expression must obey the syntax described in GNAT.Regpat.

If Full_Buffer is True, then Expect will match if the buffer was too small and some characters were about to be discarded. In that case, Expect_Full_Buffer is returned.


procedure Expect
  (Descriptor  : in out Process_Descriptor;
   Result      : out Expect_Match;
   Regexp      : GNAT.Regpat.Pattern_Matcher;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Same as the previous one, but with a precompiled regular expression. This is more efficient however, especially if you are using this expression multiple times, since this package won't need to recompile the regexp every time.

procedure Expect
  (Descriptor  : in out Process_Descriptor;
   Result      : out Expect_Match;
   Regexp      : String;
   Matched     : out GNAT.Regpat.Match_Array;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Same as above, but it is now possible to get the indexes of the substrings for the parentheses in the regexp (see the example at the top of this package, as well as the documentation in the package GNAT.Regpat).

Matched'First should be 0, and this index will contain the indexes for the whole string that was matched. The index 1 will contain the indexes for the first parentheses-pair, and so on.


procedure Expect
  (Descriptor  : in out Process_Descriptor;
   Result      : out Expect_Match;
   Regexp      : GNAT.Regpat.Pattern_Matcher;
   Matched     : out GNAT.Regpat.Match_Array;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Same as above, but with a precompiled regular expression.

type Regexp_Array is array (Positive range <>) of GNAT.OS_Lib.String_Access;
Working on the output (single process, multiple regexp)

type Pattern_Matcher_Access is access GNAT.Regpat.Pattern_Matcher;

type Compiled_Regexp_Array is array (Positive range <>)
  of Pattern_Matcher_Access;

function "+"
  (P    : GNAT.Regpat.Pattern_Matcher)
   return Pattern_Matcher_Access;
Allocate some memory for the pattern matcher. This is only a convenience function to help create the array of compiled regular expressoins.

procedure Expect
  (Descriptor  : in out Process_Descriptor;
   Result      : out Expect_Match;
   Regexps     : Regexp_Array;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Wait till a string matching one of the regular expressions in Regexps is found. This function returns the index of the regexp that matched. This command is blocking, but will timeout after Timeout milliseconds. In that case, Timeout is returned.

procedure Expect
  (Descriptor  : in out Process_Descriptor;
   Result      : out Expect_Match;
   Regexps     : Compiled_Regexp_Array;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Same as the previous one, but with precompiled regular expressions. This can be much faster if you are using them multiple times.

procedure Expect
  (Descriptor  : in out Process_Descriptor;
   Result      : out Expect_Match;
   Regexps     : Regexp_Array;
   Matched     : out GNAT.Regpat.Match_Array;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Same as above, except that you can also access the parenthesis groups inside the matching regular expression. The first index in Matched must be 0, or Constraint_Error will be raised. The index 0 contains the indexes for the whole string that was matched, the index 1 contains the indexes for the first parentheses pair, and so on.

procedure Expect
  (Descriptor  : in out Process_Descriptor;
   Result      : out Expect_Match;
   Regexps     : Compiled_Regexp_Array;
   Matched     : out GNAT.Regpat.Match_Array;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Same as above, but with precompiled regular expressions. The first index in Matched must be 0, or Constraint_Error will be raised.

type Multiprocess_Regexp is record
   Descriptor : Process_Descriptor_Access;
   Regexp     : Pattern_Matcher_Access;
end record;
Working on the output (multi-process)

type Multiprocess_Regexp_Array is array (Positive range <>)
  of Multiprocess_Regexp;

procedure Expect
  (Result      : out Expect_Match;
   Regexps     : Multiprocess_Regexp_Array;
   Matched     : out GNAT.Regpat.Match_Array;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Same as above, but for multi processes.

procedure Expect
  (Result      : out Expect_Match;
   Regexps     : Multiprocess_Regexp_Array;
   Timeout     : Integer := 10000;
   Full_Buffer : Boolean := False);
Same as the previous one, but for multiple processes. This procedure finds the first regexp that match the associated process.

procedure Flush
  (Descriptor : in out Process_Descriptor;
   Timeout    : Integer := 0);
Discard all output waiting from the process.

This output is simply discarded, and no filter is called. This output will also not be visible by the next call to Expect, nor will any output currently buffered.

Timeout is the delay for which we wait for output to be available from the process. If 0, we only get what is immediately available.


function Expect_Out (Descriptor : Process_Descriptor) return String;
Return the string matched by the last Expect call.

The returned string is in fact the concatenation of all the strings read from the file descriptor up to, and including, the characters that matched the regular expression.

For instance, with an input "philosophic", and a regular expression "hi" in the call to expect, the strings returned the first and second time would be respectively "phi" and "losophi".


function Expect_Out_Match (Descriptor : Process_Descriptor) return String;
Return the string matched by the last Expect call.

The returned string includes only the character that matched the specific regular expression. All the characters that came before are simply discarded.

For instance, with an input "philosophic", and a regular expression "hi" in the call to expect, the strings returned the first and second time would both be "hi".


procedure Portable_Execvp (Cmd : String; Args : System.Address);
Executes, in a portable way, the command Cmd (full path must be specified), with the given Args. Note that the first element in Args must be the executable name, and the last element must be a null pointer

private

   --  Implementation-defined ...
end GNAT.Expect;