zoro 1.2.0

blazingly fast and sharp shell built in rust
use assert_cmd::Command;
use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;
use predicates::prelude::*;
use std::os::unix::process::ExitStatusExt;
use std::process::Command as StdCommand;

#[test]
fn test_echo_functionality() -> Result<(), Box<dyn std::error::Error>> {
    // Get the command for the zoro binary
    let mut cmd = Command::cargo_bin("zoro")?;

    // Pipe "hello world" to the standard input of the zoro process.
    // The `\n` is crucial as `read_line` waits for a newline.
    cmd.write_stdin("hello world\n");

    // Run the command and assert on the output
    cmd.assert()
        .success() // Check that the process exits with a 0 status code
        .stdout(predicate::str::contains("You typed hello world")); // Check that stdout contains our expected echo

    Ok(())
}

#[test]
fn test_exit_on_eof() -> Result<(), Box<dyn std::error::Error>> {
    // Get the command for the zoro binary
    let mut cmd = Command::cargo_bin("zoro")?;

    // Run the command without piping any stdin. `assert_cmd` will
    // immediately close the stdin pipe, which simulates an EOF (Ctrl+D)
    // for the `read_line` function in our main loop.
    let output = cmd.assert().success();

    // Verify that the output doesn't contain the prompt a second time,
    // which proves the loop was exited.
    // We also check that the output contains the first prompt.
    let stdout_str = String::from_utf8(output.get_output().stdout.clone())?;
    assert!(stdout_str.contains("[zoro]> "));
    assert_eq!(stdout_str.matches("[zoro]> ").count(), 1);

    Ok(())
}

#[test]
fn test_graceful_shutdown_on_ctrl_c() -> Result<(), Box<dyn std::error::Error>> {
    // Use std::process::Command to get the process ID (PID)
    let mut cmd = StdCommand::new(env!("CARGO_BIN_EXE_zoro"));
    let mut child = cmd.spawn()?;

    // Get the PID of the child process
    let pid = Pid::from_raw(child.id() as i32);

    // Give the process a moment to start up and set its signal handler
    std::thread::sleep(std::time::Duration::from_millis(100));

    // Send the SIGINT signal (the equivalent of Ctrl+C)
    signal::kill(pid, Signal::SIGINT)?;

    // Wait for the child process to exit and get its status
    let status = child.wait()?;

    // When a process is terminated by a signal on Unix, its exit code is not 0.
    // Instead, we check that it was terminated by the correct signal (SIGINT).
    // The `ExitStatusExt` trait provides the `signal()` method.
    assert_eq!(status.signal(), Some(Signal::SIGINT as i32));

    Ok(())
}