tokio_process_tools/
lib.rs1#![warn(missing_docs)]
2
3#![doc = include_str!("../README.md")]
5mod 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
19pub 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 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}