Skip to content
This repository was archived by the owner on Dec 29, 2021. It is now read-only.
This repository was archived by the owner on Dec 29, 2021. It is now read-only.

Feature Request: wait and/or then methods after writing to stdin #106

Closed
@volks73

Description

@volks73

I came across this crate recently, but I cannot remember where...maybe from the CLI-WG? Anyways, it provides a lot of the functionality I wrote in the past for testing some internal projects, so I am excited to start using it in the future, and add tests for some of my Rust CLI applications. Great Work!

I am currently working on a project where I am writing messages into stdin and reading from stdout. I have some tests that deliberately result in relatively long execution times (on the order of 10's of seconds). The general test follows: (1) write message to stdin, (2) wait some amount of time depending on expected processing time, (3) read stdout and check for correctness. For these tests, where a long execution time occurs, I have not found a way to use this crate. I believe the problem is that the stdin handle from the internal Command is being dropped after writing to stdin but before the command has had time to write to stdout, which leaves the contents of stdout from the wait_with_output method empty. Any correctness assertions on the output then fail.

Instead of using this crate, here is a non-working example of the test I implemented:

#[test]
fn slow_reply_works() {
    const RESPONSE_TIME: u64 = 10; // seconds
    let mut child = Command::new("some_cmd") // <-- Not actual command
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Child command");
    {
        let stdin = child.stdin.as_mut().expect("Stdin");
        stdin.write_all("slowReplyMessage".as_bytes()).expect("Write to stdin");
        // Without this, the `stdin` handle is dropped. My CLI Under Test (CUT) exits when 
        // stdin reaches EOF or a Broken Pipe occurs. So, there is no time for
        // the cmd to complete the long/slow computation and write anything to stdout 
        // when the output is later retrieved with the `wait_with_output` method. The
        // output is empty and any assertion for correctness fails.
        thread::sleep(Duration::from_secs(RESPONSE_TIME));
    }
    let output = child.wait_with_output().expect("Wait on child");
    // Correctness assertions for the output follow here, but without the
    // `thread::sleep`, the output is empty. Omitted for clarity.
    // ...
}

I would like to see a wait, hold, or delay method added to the Assert struct as I believe the Assert struct is essentially doing the same time as my non-working example test, but does not have the thread::sleep line. If such a method existed, the above test could be rewritten more succinctly using this crate as follows:

#[test]
fn slow_reply_works() {
    const RESPONSE_TIME: u64 = 10; // seconds
    Assert::command(&["some_cmd"])
        .stdin("slowReplyMessage").wait(RESPONSE_TIME)
        .succeeds()
        .and()
        .stdout().contains("expected message after delay")
        .unwrap();
}

I believe this is cleaner and easier to read, but I understand wait might be confusing since there is the wait and wait_with_output methods for the Command struct in std::process. So, maybe hold or delay would be better, with a slight personal preference at the moment towards "hold"?

However, this got me thinking that maybe a wait/hold/delay method is too restrictive? There might be other tests in the future that need to do more than just wait for some time after writing to stdin. So, maybe a then method with a predicate would be better? The above example with the wait/hold/delay method could be re-implemented as follows:

#[test]
fn slow_reply_works() {
    const RESPONSE_TIME: u64 = 10; // seconds
    Assert::command(&["some_cmd"])
         // Not sure if anything needs to be passed to the closure, handle to stdin?
        .stdin("slowReplyMessage").then(|| { 
            // Waits for the slow reply to complete, just like the previous example, 
            // but other actions can be executed.
            thread::sleep(Duration::from_secs(RESPONSE_TIME);
        })
        .succeeds()
        .and()
        .stdout().contains("expected message after delay")
        .unwrap();
}

Of course, both a wait/hold/delay and then methods could be added because the then implementation of wait/hold/delay is a little more verbose/noisy. I am open to alternatives for the then method name as well.

Any thoughts or comments about this proposed feature/enhancement would be greatly appreciated.

Thanks again for the great crate and work!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions