expectrl icon indicating copy to clipboard operation
expectrl copied to clipboard

Method to extract remaining buffer if an `expect()` timeout

Open ArchieAtkinson opened this issue 1 year ago • 3 comments

I'm writing a test harness for a CLI application and would like to get the buffer's content if the session times out while expecting something. This would help debug when something isn't working as expected.

I currently have this implementation:

match session.expect(&parsed_expected) {
    Ok(_) => (),
    Err(expectrl::Error::ExpectTimeout) => {
        let debug_output = format!("{:?}", self.session);
        if let Some(start) = debug_output.find("buffer: [") {
            if let Some(end) = debug_output[start..].find("]") {
                let numbers = debug_output[start + 8..start + end]
                    .split(", ")
                    .filter_map(|n| n.parse::<u8>().ok())
                    .collect::<Vec<u8>>();

                let stripped = strip_ansi_escapes::strip(&numbers);
                let output = String::from_utf8(stripped).unwrap();
                panic!("Timeout Reached, Unexpected output:\n{}", output);
            }
            panic!("Timeout Reached, couldn't extract buffer");
        }
    }
    Err(e) => return Err(e).wrap_err("Failed to expect string"),
}

I'm extracting the buffer from the fmt output, so I know it's possible to get it, but all my attempts using the API end up blocking indefinitely. If there is an alternative method that I'm missing, that would be great, but I would also be willing to create a PR if its not.

Thanks

ArchieAtkinson avatar Feb 05 '25 20:02 ArchieAtkinson

Hi @ArchieAtkinson

I think your approach may be right. I am not sure why it is not failing, cause it must if there's no match, by default it's 10 seconds before failing. You could change the timeout using session.set_expect_timeout(Some(Duration::from_secs(1))).

Let me know if that helps. I probably could help you further but I need a reproducible or something?

zhiburt avatar Feb 05 '25 22:02 zhiburt

Ahhhh yeee the only thing is how you using let debug_output = format!("{:?}", self.session); is pretty funny 😅

Let me check

Look at this example what you can do:

use expectrl::{self, Error, Expect};
use std::time::Duration;

fn main() {
    let mut p = expectrl::spawn("cat").unwrap();
    p.set_expect_timeout(Some(Duration::from_secs(1)));

    p.send_line("Hello").unwrap();
    p.send_line("ArchieAtkinson").unwrap();

    let err = p.expect("SOMETHING WE WONT GET").expect_err("");
    match err {
        Error::ExpectTimeout => {
            let mut buf = [0; 1024];
            let mut buf_all = Vec::new();
            while let Ok(n) = p.try_read(&mut buf) {
                buf_all.extend(&buf[..n]);
            }

            let string = String::from_utf8_lossy(&buf_all);

            println!("Get available: {:?}", string);
        },
        _ => unreachable!(),
    }
}

The only downside to be honest is that actually when we do try_read there could be more characters read and we may already match what originally we failed to expect. So....maybe we could add one more method get_available() or something like it. We kind of already have it is just private.

zhiburt avatar Feb 05 '25 22:02 zhiburt

Thank you, that is a lot more convenient than my method. Given it's just to help debugging this should be good enough for me but any improvements to it would be welcome!

ArchieAtkinson avatar Feb 05 '25 23:02 ArchieAtkinson