tokio_process_tools/
lib.rs

1#![warn(missing_docs)]
2
3//!
4#![doc = include_str!("../README.md")]
5//!
6
7mod async_drop;
8mod collector;
9mod error;
10mod inspector;
11mod output;
12mod output_stream;
13mod panic_on_drop;
14mod process;
15mod process_handle;
16mod signal;
17mod terminate_on_drop;
18
19/* public exports */
20pub use collector::{Collector, CollectorError, Sink};
21pub use error::{OutputError, SpawnError, TerminationError, WaitError};
22pub use inspector::{Inspector, InspectorError};
23pub use output::Output;
24pub use output_stream::{
25    DEFAULT_CHANNEL_CAPACITY, DEFAULT_CHUNK_SIZE, LineOverflowBehavior, LineParsingOptions, Next,
26    NumBytes, NumBytesExt, OutputStream, broadcast, single_subscriber,
27};
28pub use process::{AutoName, AutoNameSettings, Process, ProcessName};
29pub use process_handle::{ProcessHandle, RunningState};
30pub use terminate_on_drop::TerminateOnDrop;
31
32#[cfg(test)]
33mod test {
34    use crate::output::Output;
35    use crate::{LineParsingOptions, Next, Process, RunningState};
36    use assertr::prelude::*;
37    use std::time::Duration;
38    use tokio::process::Command;
39
40    #[tokio::test]
41    async fn wait_with_output() {
42        let mut process = Process::new(Command::new("ls"))
43            .name("ls")
44            .spawn_broadcast()
45            .expect("Failed to spawn `ls` command");
46        let Output {
47            status,
48            stdout,
49            stderr,
50        } = process
51            .wait_for_completion_with_output(None, LineParsingOptions::default())
52            .await
53            .unwrap();
54        assert_that(status.success()).is_true();
55        assert_that(stdout).is_equal_to([
56            "Cargo.lock",
57            "Cargo.toml",
58            "LICENSE-APACHE",
59            "LICENSE-MIT",
60            "README.md",
61            "src",
62            "target",
63        ]);
64        assert_that(stderr).is_empty();
65    }
66
67    #[tokio::test]
68    async fn single_subscriber_panics_on_multiple_consumers() {
69        let mut process = Process::new(Command::new("ls"))
70            .name("ls")
71            .spawn_single_subscriber()
72            .expect("Failed to spawn `ls` command");
73
74        let _inspector = process
75            .stdout()
76            .inspect_lines(|_line| Next::Continue, LineParsingOptions::default());
77
78        assert_that_panic_by(|| {
79            let _inspector = process
80                .stdout()
81                .inspect_lines(|_line| Next::Continue, LineParsingOptions::default());
82        })
83        .has_type::<String>()
84        .is_equal_to("Cannot create multiple consumers on SingleSubscriberOutputStream (stream: 'stdout'). Only one inspector or collector can be active at a time. Use .spawn_broadcast() instead of .spawn_single_subscriber() to support multiple consumers.");
85
86        process.wait_for_completion(None).await.unwrap();
87    }
88
89    #[tokio::test]
90    async fn is_running() {
91        let mut cmd = Command::new("sleep");
92        cmd.arg("1");
93        let mut process = Process::new(cmd)
94            .name("sleep")
95            .spawn_broadcast()
96            .expect("Failed to spawn `sleep` command");
97
98        match process.is_running() {
99            RunningState::Running => {}
100            RunningState::Terminated(exit_status) => {
101                assert_that(exit_status).fail("Process should be running");
102            }
103            RunningState::Uncertain(_) => {
104                assert_that_ref(&process).fail("Process state should not be uncertain");
105            }
106        };
107
108        let _exit_status = process.wait_for_completion(None).await.unwrap();
109
110        match process.is_running() {
111            RunningState::Running => {
112                assert_that(process).fail("Process should not be running anymore");
113            }
114            RunningState::Terminated(exit_status) => {
115                assert_that(exit_status.code()).is_some().is_equal_to(0);
116                assert_that(exit_status.success()).is_true();
117            }
118            RunningState::Uncertain(_) => {
119                assert_that(process).fail("Process state should not be uncertain");
120            }
121        };
122    }
123
124    #[tokio::test]
125    async fn terminate() {
126        let mut cmd = Command::new("sleep");
127        cmd.arg("1000");
128        let mut process = Process::new(cmd)
129            .name("sleep")
130            .spawn_broadcast()
131            .expect("Failed to spawn `sleep` command");
132        process
133            .terminate(Duration::from_secs(1), Duration::from_secs(1))
134            .await
135            .unwrap();
136        match process.is_running() {
137            RunningState::Running => {
138                assert_that(process).fail("Process should not be running anymore");
139            }
140            RunningState::Terminated(exit_status) => {
141                // Terminating a process with a signal results in no code being emitted (on linux).
142                assert_that(exit_status.code()).is_none();
143                assert_that(exit_status.success()).is_false();
144            }
145            RunningState::Uncertain(_) => {
146                assert_that(process).fail("Process state should not be uncertain");
147            }
148        };
149    }
150}