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, Stdin};
30pub use terminate_on_drop::TerminateOnDrop;
31
32#[allow(dead_code)]
33trait SendSync: Send + Sync {}
34impl SendSync for broadcast::BroadcastOutputStream {}
35impl SendSync for single_subscriber::SingleSubscriberOutputStream {}
36impl<O: OutputStream + SendSync> SendSync for ProcessHandle<O> {}
37
38#[cfg(test)]
39mod test {
40    use crate::output::Output;
41    use crate::{LineParsingOptions, Next, Process, RunningState};
42    use assertr::prelude::*;
43    use std::time::Duration;
44    use tokio::process::Command;
45
46    #[tokio::test]
47    async fn wait_with_output() {
48        let mut process = Process::new(Command::new("ls"))
49            .name("ls")
50            .spawn_broadcast()
51            .expect("Failed to spawn `ls` command");
52        let Output {
53            status,
54            stdout,
55            stderr,
56        } = process
57            .wait_for_completion_with_output(None, LineParsingOptions::default())
58            .await
59            .unwrap();
60        assert_that(status.success()).is_true();
61        assert_that(stdout).is_equal_to([
62            "Cargo.lock",
63            "Cargo.toml",
64            "LICENSE-APACHE",
65            "LICENSE-MIT",
66            "README.md",
67            "src",
68            "target",
69        ]);
70        assert_that(stderr).is_empty();
71    }
72
73    #[tokio::test]
74    async fn single_subscriber_panics_on_multiple_consumers() {
75        let mut process = Process::new(Command::new("ls"))
76            .name("ls")
77            .spawn_single_subscriber()
78            .expect("Failed to spawn `ls` command");
79
80        let _inspector = process
81            .stdout()
82            .inspect_lines(|_line| Next::Continue, LineParsingOptions::default());
83
84        assert_that_panic_by(|| {
85            let _inspector = process
86                .stdout()
87                .inspect_lines(|_line| Next::Continue, LineParsingOptions::default());
88        })
89        .has_type::<String>()
90        .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.");
91
92        process.wait_for_completion(None).await.unwrap();
93    }
94
95    #[tokio::test]
96    async fn is_running() {
97        let mut cmd = Command::new("sleep");
98        cmd.arg("1");
99        let mut process = Process::new(cmd)
100            .name("sleep")
101            .spawn_broadcast()
102            .expect("Failed to spawn `sleep` command");
103
104        match process.is_running() {
105            RunningState::Running => {}
106            RunningState::Terminated(exit_status) => {
107                assert_that(exit_status).fail("Process should be running");
108            }
109            RunningState::Uncertain(_) => {
110                assert_that_ref(&process).fail("Process state should not be uncertain");
111            }
112        };
113
114        let _exit_status = process.wait_for_completion(None).await.unwrap();
115
116        match process.is_running() {
117            RunningState::Running => {
118                assert_that(process).fail("Process should not be running anymore");
119            }
120            RunningState::Terminated(exit_status) => {
121                assert_that(exit_status.code()).is_some().is_equal_to(0);
122                assert_that(exit_status.success()).is_true();
123            }
124            RunningState::Uncertain(_) => {
125                assert_that(process).fail("Process state should not be uncertain");
126            }
127        };
128    }
129
130    #[tokio::test]
131    async fn terminate() {
132        let mut cmd = Command::new("sleep");
133        cmd.arg("1000");
134        let mut process = Process::new(cmd)
135            .name("sleep")
136            .spawn_broadcast()
137            .expect("Failed to spawn `sleep` command");
138        process
139            .terminate(Duration::from_secs(1), Duration::from_secs(1))
140            .await
141            .unwrap();
142        match process.is_running() {
143            RunningState::Running => {
144                assert_that(process).fail("Process should not be running anymore");
145            }
146            RunningState::Terminated(exit_status) => {
147                // Terminating a process with a signal results in no code being emitted (on linux).
148                assert_that(exit_status.code()).is_none();
149                assert_that(exit_status.success()).is_false();
150            }
151            RunningState::Uncertain(_) => {
152                assert_that(process).fail("Process state should not be uncertain");
153            }
154        };
155    }
156}