#![cfg(unix)]
#![cfg(test)]
#![allow(clippy::panic_in_result_fn)]
use anyhow::Context;
use expectrl::{
Expect, Session,
process::unix::{PtyStream, UnixProcess},
repl::ReplSession,
stream::log::LogStream,
};
#[test_with::executable(ping)]
#[test]
fn run_suspend_and_fg() -> anyhow::Result<()> {
let mut session = start_shell_session()?;
session.expect_prompt()?;
session.send_line("ping -c 1000000 127.0.0.1")?;
session
.expect("bytes from")
.context("Initial ping invocation output")?;
for _ in 0..5 {
session.suspend()?;
session.expect_prompt()?;
let jobs_output = session.exec_output("jobs")?;
assert!(jobs_output.contains("ping"));
session.send_line("fg")?;
session.expect("ping").context("Foregrounded ping")?;
}
session.interrupt()?;
session.expect("loss")?;
session.expect_prompt()?;
session.exit()?;
Ok(())
}
#[test_with::executable(ping)]
#[test]
fn run_in_bg_then_fg() -> anyhow::Result<()> {
let mut session = start_shell_session()?;
session.expect_prompt()?;
session.send_line("ping -c 1000000 127.0.0.1")?;
session.expect("bytes from")?;
session.suspend()?;
session.expect_prompt()?;
let jobs_output = session.exec_output("jobs")?;
assert!(jobs_output.contains("ping"));
session.send_line("bg")?;
session.expect_prompt()?;
session.expect("bytes from")?;
session.send_line("kill %1")?;
session.expect_prompt()?;
session.send_line("wait")?;
session.expect_prompt()?;
let jobs_output = session.exec_output("jobs")?;
assert_eq!(jobs_output.trim(), "");
session.exit()?;
Ok(())
}
#[test_with::executable(less)]
#[test]
fn run_pipeline_interactively() -> anyhow::Result<()> {
let mut session = start_shell_session()?;
session.expect_prompt()?;
session.send_line("echo hello | TERM=linux less")?;
session
.expect("hello")
.context("Echoed text didn't show up")?;
session.send("h")?;
session
.expect("SUMMARY")
.context("less help didn't show up")?;
session.send("q")?;
session.send("q")?;
session
.expect_prompt()
.context("Final prompt didn't show up")?;
session.exit()?;
Ok(())
}
type ShellSession = ReplSession<Session<UnixProcess, LogStream<PtyStream, std::io::Stdout>>>;
trait SessionExt {
fn suspend(&mut self) -> anyhow::Result<()>;
fn interrupt(&mut self) -> anyhow::Result<()>;
fn exec_output<S: AsRef<str>>(&mut self, cmd: S) -> anyhow::Result<String>;
}
impl SessionExt for ShellSession {
fn suspend(&mut self) -> anyhow::Result<()> {
self.send(expectrl::ControlCode::Substitute)?;
Ok(())
}
fn interrupt(&mut self) -> anyhow::Result<()> {
self.send(expectrl::ControlCode::EndOfText)?;
Ok(())
}
fn exec_output<S: AsRef<str>>(&mut self, cmd: S) -> anyhow::Result<String> {
let output = self.execute(cmd)?;
let output_str = String::from_utf8(output)?;
Ok(output_str)
}
}
fn start_shell_session() -> anyhow::Result<ShellSession> {
const DEFAULT_PROMPT: &str = "brush> ";
let shell_path = assert_cmd::cargo::cargo_bin!("brush");
let mut cmd = std::process::Command::new(shell_path);
cmd.args([
"--norc",
"--noprofile",
"--no-config",
"--disable-bracketed-paste",
"--disable-color",
"--input-backend=basic",
]);
cmd.env("PS1", DEFAULT_PROMPT);
cmd.env("TERM", "linux");
let session = expectrl::session::Session::spawn(cmd)?;
let session = expectrl::session::log(session, std::io::stdout())?;
let mut session = expectrl::repl::ReplSession::new(session, DEFAULT_PROMPT);
session.set_echo(true);
Ok(session)
}