Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to test an app that uses rustyline? #652

Open
seanballais opened this issue Sep 26, 2022 · 5 comments
Open

How to test an app that uses rustyline? #652

seanballais opened this issue Sep 26, 2022 · 5 comments
Labels

Comments

@seanballais
Copy link

I have been figuring out how to test the following basic CLI app that uses rustyline using std::process::Command but I could not figure out how to pull it off right.

The following is the code for CLI app:

use std::io::Write;

use rustyline::error::ReadlineError;
use rustyline::Editor;

fn main() {
    let mut line_editor = Editor::<()>::new().unwrap();
    let mut counter = 0;

    loop {
        let readline = line_editor.readline("> ");
        match readline {
            Ok(line) => {
                counter += 1;
                println!("Line: {}", line);
                line_editor.add_history_entry(line.clone());

                if line == "clear" {
                    print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
                    std::io::stdout().flush().unwrap();
                }
            }
            Err(ReadlineError::Interrupted) => {
                println!("Ctrl+C");
                break;
            }
            Err(ReadlineError::Eof) => {
                println!("Ctrl+D | Lines counted: {}", counter);
                break;
            }
            Err(err) => {
                println!("Error: {:?}", err);
                break;
            }
        }
    }
}

And code for the test:

#[test]
fn clear_screen() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("control-cli")?;
    let mut cmd_child = cmd.stdin(Stdio::piped()).spawn()?;
    let mut child_stdin = cmd_child.stdin.take().unwrap();

    child_stdin.write_all(b"history\n").unwrap();

    signal::kill(Pid::from_raw(cmd_child.id() as i32), Signal::SIGINT)?;

    let output = cmd.output()?;

    println!("{}", String::from_utf8_lossy(&output.stdout));

    Ok(())
}

When I run the test with cargo test -- --nocapture, I only get the following output:

Ctrl+D | Lines counted: 0

I was expecting something like:

Line: history
Ctrl+D | Lines counted: 1

I believe #504 may be related to this problem. Nevertheless, how can I achieve the second output?

@gwenn
Copy link
Collaborator

gwenn commented Oct 2, 2022

Not tested: https://crates.io/crates/pty-process

@seanballais
Copy link
Author

I'll try that one out tomorrow.

@rikhuijzer
Copy link

You could call functions inside the readline matches cases and test those on their own:

use std::io::Write;

use rustyline::error::ReadlineError;
use rustyline::Editor;

fn handle_line<W>(mut dest: W, line: String) where W: Write {
    write!(&mut dest, "Line: {line}\n").unwrap();

    if line == "clear" {
        write!(&mut dest, "{esc}[2J{esc}[1;1H", esc = 27 as char).unwrap();
        dest.flush().unwrap();
    }
}

#[test]
fn line_is_handled() {
    let mut dest = Vec::new();
    handle_line(&mut dest, "something".to_string());
    let actual = String::from_utf8(dest).unwrap();
    let expected = "Line: something\n".to_string();
    assert_eq!(actual, expected);
}

fn main() {
    let mut line_editor = Editor::<()>::new().unwrap();
    let mut counter = 0;
    let mut dest = std::io::stdout();

    loop {
        let readline = line_editor.readline("> ");
        match readline {
            Ok(line) => {
                line_editor.add_history_entry(line.clone());
                counter += 1;
                handle_line(&mut dest, line);
            }
            Err(ReadlineError::Interrupted) => {
                println!("Ctrl+C");
                break;
            }
            Err(ReadlineError::Eof) => {
                println!("Ctrl+D | Lines counted: {}", counter);
                break;
            }
            Err(err) => {
                println!("Error: {:?}", err);
                break;
            }
        }
    }
}

This means that the logic inside main is not tested, but that is arguably not so bad since it's mostly declarative and already tested by rustyline. The complex logic will most likely be inside your own logic.

@gruuya
Copy link

gruuya commented Dec 1, 2023

FWIW, here's a working example of end-to-end testing (facilitated via the assert_cmd crate) for a CLI app that uses rustyline: https://github.com/splitgraph/seafowl/blob/main/tests/cli/basic.rs

@gwenn
Copy link
Collaborator

gwenn commented Dec 2, 2023

@gruuya I guess that you are testing the non-interactive mode because stdin/stdout are not tty.
And there are some issues if you want to actually test the interactive mode like #703.

@gwenn gwenn added the question label Oct 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants