#pty #automation #testing #expect #rexpect

expectrl

A tool for automating terminal applications in Unix like Don libes expect

2 releases

0.1.1 Jul 19, 2021
0.1.0 Jul 12, 2021

#75 in Unix APIs

MIT license

35KB
763 lines

Build coverage status crate docs.rs

expectrl

A tool for automating terminal applications in Unix.

Using the library you can:

  • Spawn process
  • Control process
  • Expect/Verify responces

It was heavily inspired by philippkeller/rexpect and pexpect.

Basic usage

Add the following line to your Cargo.toml file:

[dependencies]
expectrl = "0.1"

An example for interacting via ftp:

use expectrl::{spawn, Regex, Eof, WaitStatus};

fn main() {
    let mut p = spawn("ftp speedtest.tele2.net").unwrap();
    p.expect(Regex("Name \\(.*\\):")).unwrap();
    p.send_line("anonymous").unwrap();
    p.expect("Password").unwrap();
    p.send_line("test").unwrap();
    p.expect("ftp>").unwrap();
    p.send_line("cd upload").unwrap();
    p.expect("successfully changed.\r\nftp>").unwrap();
    p.send_line("pwd").unwrap();
    p.expect(Regex("[0-9]+ \"/upload\"")).unwrap();
    p.send_line("exit").unwrap();
    p.expect(Eof).unwrap();
    assert_eq!(p.wait().unwrap(), WaitStatus::Exited(p.pid(), 0));
}

Example bash with async feature

use expectrl::{repl::spawn_bash, Regex, Error, ControlCode};
use futures_lite::io::AsyncBufReadExt;

#[tokio::main]
fn main() -> Result<(), Error> {
    let mut p = spawn_bash().await?;

    p.send_line("hostname").await?;
    let mut hostname = String::new();
    p.read_line(&mut hostname).await?;
    p.expect_prompt().await?; // go sure `hostname` is really done
    println!("Current hostname: {:?}", hostname);

    Ok(())
}

Example with bash and job control

One frequent bitfall with sending signals is that you need to somehow ensure that the program has fully loaded, otherwise they goes into nowhere. There are 2 handy function execute for this purpouse:

  • execute - does a command and ensures that the prompt is shown again.
  • expect_prompt - ensures that the prompt is shown.
use expectrl::{repl::spawn_bash, Error, ControlCode};

fn main() -> Result<(), Error> {
    let mut p = spawn_bash()?;
    p.send_line("ping 8.8.8.8")?;
    p.expect("bytes of data")?;
    p.send_control(ControlCode::Substitute)?; // CTRL_Z
    p.expect_prompt()?;
    // bash writes 'ping 8.8.8.8' to stdout again to state which job was put into background
    p.send_line("bg")?;
    p.expect("ping 8.8.8.8")?;
    p.expect_prompt()?;
    p.send_line("sleep 0.5")?;
    p.expect_prompt()?;
    // bash writes 'ping 8.8.8.8' to stdout again to state which job was put into foreground
    p.send_line("fg")?;
    p.expect("ping 8.8.8.8")?;
    p.send_control(ControlCode::EndOfText)?;
    p.expect("packet loss")?;

    Ok(())
}

Examples

For more examples, check the examples directory.

Comparison to philippkeller/rexpect

It will be fair to say that without it there would be no expectrl.

  • It has an async support.
  • It does a couple of inner things diferently.
  • It has a different interface.
  • It supports (not full coverage) logging.
  • ...

Licensed under MIT License

Dependencies

~3MB
~69K SLoC