tokio_process_tools/process.rs
1//! Builder API for spawning processes with explicit stream type selection.
2
3use crate::error::SpawnError;
4use crate::output_stream::broadcast::BroadcastOutputStream;
5use crate::output_stream::single_subscriber::SingleSubscriberOutputStream;
6use crate::output_stream::{DEFAULT_CHANNEL_CAPACITY, DEFAULT_CHUNK_SIZE};
7use crate::{NumBytes, ProcessHandle};
8use std::borrow::Cow;
9
10/// Controls how the process name is automatically generated when not explicitly provided.
11///
12/// This determines what information is included in the auto-generated process name
13/// used for logging and debugging purposes.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum AutoName {
16 /// Capture a name from the command as specified by the provided settings.
17 ///
18 /// Example: `"ls -la"` from `Command::new("ls").arg("-la")`
19 Using(AutoNameSettings),
20
21 /// Capture the full Debug representation of the Command.
22 ///
23 /// Example: `"Command { std: \"ls\" \"-la\", kill_on_drop: false }"`
24 ///
25 /// Note: This includes internal implementation details and may change with tokio updates.
26 Debug,
27}
28
29impl Default for AutoName {
30 fn default() -> Self {
31 Self::Using(AutoNameSettings::program_with_args())
32 }
33}
34
35/// Controls in detail which parts of the command are automatically captured as the process name.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct AutoNameSettings {
38 include_current_dir: bool,
39 include_envs: bool,
40 include_program: bool,
41 include_args: bool,
42}
43
44impl AutoNameSettings {
45 /// Capture the program name.
46 ///
47 /// Example: `Command::new("ls").arg("-la").env("FOO", "foo)` is captured as `"ls"`.
48 pub fn program_only() -> Self {
49 AutoNameSettings {
50 include_current_dir: false,
51 include_envs: false,
52 include_program: true,
53 include_args: false,
54 }
55 }
56
57 /// Capture the program name and all arguments.
58 ///
59 /// Example: `Command::new("ls").arg("-la").env("FOO", "foo)` is captured as `"ls -la"`.
60 pub fn program_with_args() -> Self {
61 AutoNameSettings {
62 include_current_dir: false,
63 include_envs: false,
64 include_program: true,
65 include_args: true,
66 }
67 }
68
69 /// Capture the program name and all environment variables and arguments.
70 ///
71 /// Example: `Command::new("ls").arg("-la").env("FOO", "foo)` is captured as `"FOO=foo ls -la"`.
72 pub fn program_with_env_and_args() -> Self {
73 AutoNameSettings {
74 include_current_dir: false,
75 include_envs: true,
76 include_program: true,
77 include_args: true,
78 }
79 }
80
81 /// Capture the directory and the program name and all environment variables and arguments.
82 ///
83 /// Example: `Command::new("ls").arg("-la").env("FOO", "foo)` is captured as `"/some/dir % FOO=foo ls -la"`.
84 pub fn full() -> Self {
85 AutoNameSettings {
86 include_current_dir: true,
87 include_envs: true,
88 include_program: true,
89 include_args: true,
90 }
91 }
92
93 fn format_cmd(&self, cmd: &std::process::Command) -> String {
94 let mut name = String::new();
95 if self.include_current_dir {
96 if let Some(current_dir) = cmd.get_current_dir() {
97 name.push_str(current_dir.to_string_lossy().as_ref());
98 name.push_str(" % ");
99 }
100 }
101 if self.include_envs {
102 let envs = cmd.get_envs();
103 if envs.len() != 0 {
104 for (key, value) in envs
105 .filter(|(_key, value)| value.is_some())
106 .map(|(key, value)| (key, value.expect("present")))
107 {
108 name.push_str(key.to_string_lossy().as_ref());
109 name.push('=');
110 name.push_str(value.to_string_lossy().as_ref());
111 name.push(' ');
112 }
113 }
114 }
115 if self.include_program {
116 name.push_str(cmd.get_program().to_string_lossy().as_ref());
117 name.push(' ');
118 }
119 if self.include_args {
120 let args = cmd.get_args();
121 if args.len() != 0 {
122 for arg in args {
123 name.push('"');
124 name.push_str(arg.to_string_lossy().as_ref());
125 name.push('"');
126 name.push(' ');
127 }
128 }
129 }
130 if name.ends_with(' ') {
131 name.pop();
132 }
133 name
134 }
135}
136
137/// Specifies how a process should be named.
138///
139/// This enum allows you to either provide an explicit name or configure automatic
140/// name generation. Using this type ensures you cannot accidentally set both an
141/// explicit name and an auto-naming mode at the same time.
142#[derive(Debug, Clone)]
143pub enum ProcessName {
144 /// Use an explicit custom name.
145 ///
146 /// Example: `ProcessName::Explicit("my-server")`
147 Explicit(Cow<'static, str>),
148
149 /// Auto-generate the name based on the command.
150 ///
151 /// Example: `ProcessName::Auto(AutoName::ProgramWithArgs)`
152 Auto(AutoName),
153}
154
155impl Default for ProcessName {
156 fn default() -> Self {
157 Self::Auto(AutoName::default())
158 }
159}
160
161impl From<&'static str> for ProcessName {
162 fn from(s: &'static str) -> Self {
163 Self::Explicit(Cow::Borrowed(s))
164 }
165}
166
167impl From<String> for ProcessName {
168 fn from(s: String) -> Self {
169 Self::Explicit(Cow::Owned(s))
170 }
171}
172
173impl From<Cow<'static, str>> for ProcessName {
174 fn from(s: Cow<'static, str>) -> Self {
175 Self::Explicit(s)
176 }
177}
178
179impl From<AutoName> for ProcessName {
180 fn from(mode: AutoName) -> Self {
181 Self::Auto(mode)
182 }
183}
184
185/// A builder for configuring and spawning a process.
186///
187/// This provides an ergonomic API for spawning processes while keeping the stream type
188/// (broadcast vs single subscriber) explicit at the spawn callsite.
189///
190/// # Examples
191///
192/// ```no_run
193/// use tokio_process_tools::*;
194/// use tokio::process::Command;
195///
196/// # tokio_test::block_on(async {
197/// // Simple case with auto-derived name
198/// let process = Process::new(Command::new("ls"))
199/// .spawn_broadcast()?;
200///
201/// // With explicit name (no allocation when using string literal)
202/// let process = Process::new(Command::new("server"))
203/// .name("my-server")
204/// .spawn_single_subscriber()?;
205///
206/// // With custom capacities
207/// let process = Process::new(Command::new("cargo"))
208/// .name("test-runner")
209/// .stdout_capacity(512)
210/// .stderr_capacity(512)
211/// .spawn_broadcast()?;
212/// # Ok::<_, SpawnError>(())
213/// # });
214/// ```
215pub struct Process {
216 cmd: tokio::process::Command,
217 name: ProcessName,
218 stdout_chunk_size: NumBytes,
219 stderr_chunk_size: NumBytes,
220 stdout_capacity: usize,
221 stderr_capacity: usize,
222}
223
224impl Process {
225 /// Creates a new process builder from a tokio command.
226 ///
227 /// If no name is explicitly set via [`Process::name`], the name will be auto-derived
228 /// from the command's program name.
229 ///
230 /// # Examples
231 ///
232 /// ```no_run
233 /// use tokio_process_tools::*;
234 /// use tokio::process::Command;
235 ///
236 /// # tokio_test::block_on(async {
237 /// let process = Process::new(Command::new("ls"))
238 /// .spawn_broadcast()?;
239 /// # Ok::<_, SpawnError>(())
240 /// # });
241 /// ```
242 pub fn new(cmd: tokio::process::Command) -> Self {
243 Self {
244 cmd,
245 name: ProcessName::default(),
246 stdout_chunk_size: DEFAULT_CHUNK_SIZE,
247 stderr_chunk_size: DEFAULT_CHUNK_SIZE,
248 stdout_capacity: DEFAULT_CHANNEL_CAPACITY,
249 stderr_capacity: DEFAULT_CHANNEL_CAPACITY,
250 }
251 }
252
253 /// Sets how the process should be named.
254 ///
255 /// You can provide either an explicit name or configure automatic name generation.
256 /// The name is used for logging and debugging purposes.
257 ///
258 /// # Examples
259 ///
260 /// ```no_run
261 /// use tokio_process_tools::*;
262 /// use tokio::process::Command;
263 ///
264 /// # tokio_test::block_on(async {
265 /// // Explicit name
266 /// let process = Process::new(Command::new("server"))
267 /// .name(ProcessName::Explicit("my-server".into()))
268 /// .spawn_broadcast()?;
269 ///
270 /// // Auto-generated with arguments
271 /// let mut cmd = Command::new("cargo");
272 /// cmd.arg("test");
273 /// let process = Process::new(cmd)
274 /// .name(ProcessName::Auto(AutoName::Using(AutoNameSettings::program_with_args())))
275 /// .spawn_broadcast()?;
276 /// # Ok::<_, SpawnError>(())
277 /// # });
278 /// ```
279 pub fn name(mut self, name: impl Into<ProcessName>) -> Self {
280 self.name = name.into();
281 self
282 }
283
284 /// Convenience method to set an explicit process name.
285 ///
286 /// This is a shorthand for `.name(ProcessName::Explicit(...))`.
287 ///
288 /// # Examples
289 ///
290 /// ```no_run
291 /// use tokio_process_tools::*;
292 /// use tokio::process::Command;
293 ///
294 /// # tokio_test::block_on(async {
295 /// // Static name (no allocation)
296 /// let process = Process::new(Command::new("server"))
297 /// .with_name("my-server")
298 /// .spawn_broadcast()?;
299 ///
300 /// // Dynamic name (allocates)
301 /// let id = 42;
302 /// let process = Process::new(Command::new("worker"))
303 /// .with_name(format!("worker-{id}"))
304 /// .spawn_single_subscriber()?;
305 /// # Ok::<_, SpawnError>(())
306 /// # });
307 /// ```
308 pub fn with_name(self, name: impl Into<Cow<'static, str>>) -> Self {
309 self.name(ProcessName::Explicit(name.into()))
310 }
311
312 /// Convenience method to configure automatic name generation.
313 ///
314 /// This is a shorthand for `.name(ProcessName::Auto(...))`.
315 ///
316 /// # Examples
317 ///
318 /// ```no_run
319 /// use tokio_process_tools::*;
320 /// use tokio::process::Command;
321 ///
322 /// # tokio_test::block_on(async {
323 /// let mut cmd = Command::new("server");
324 /// cmd.arg("--database").arg("sqlite");
325 /// cmd.env("S3_ENDPOINT", "127.0.0.1:9000");
326 ///
327 /// let process = Process::new(cmd)
328 /// .with_auto_name(AutoName::Using(AutoNameSettings::program_with_env_and_args()))
329 /// .spawn_broadcast()?;
330 /// # Ok::<_, SpawnError>(())
331 /// # });
332 /// ```
333 pub fn with_auto_name(self, mode: AutoName) -> Self {
334 self.name(ProcessName::Auto(mode))
335 }
336
337 /// Sets the stdout chunk size.
338 ///
339 /// This controls the size of the buffer used when reading from the process's stdout stream.
340 /// Default is [DEFAULT_CHUNK_SIZE].
341 ///
342 /// # Examples
343 ///
344 /// ```no_run
345 /// use tokio_process_tools::*;
346 /// use tokio::process::Command;
347 ///
348 /// # tokio_test::block_on(async {
349 /// let process = Process::new(Command::new("server"))
350 /// .stdout_chunk_size(32.kilobytes())
351 /// .spawn_broadcast()?;
352 /// # Ok::<_, SpawnError>(())
353 /// # });
354 /// ```
355 pub fn stdout_chunk_size(mut self, chunk_size: NumBytes) -> Self {
356 self.stdout_chunk_size = chunk_size;
357 self
358 }
359
360 /// Sets the stderr chunk size.
361 ///
362 /// This controls the size of the buffer used when reading from the process's stderr stream.
363 /// Default is [DEFAULT_CHUNK_SIZE].
364 ///
365 /// # Examples
366 ///
367 /// ```no_run
368 /// use tokio_process_tools::*;
369 /// use tokio::process::Command;
370 ///
371 /// # tokio_test::block_on(async {
372 /// let process = Process::new(Command::new("server"))
373 /// .stderr_chunk_size(32.kilobytes())
374 /// .spawn_broadcast()?;
375 /// # Ok::<_, SpawnError>(())
376 /// # });
377 /// ```
378 pub fn stderr_chunk_size(mut self, chunk_size: NumBytes) -> Self {
379 self.stderr_chunk_size = chunk_size;
380 self
381 }
382
383 /// Sets the stdout and stderr chunk sizes.
384 ///
385 /// This controls the size of the buffers used when reading from the process's stdout and
386 /// stderr streams.
387 /// Default is [DEFAULT_CHUNK_SIZE].
388 ///
389 /// # Examples
390 ///
391 /// ```no_run
392 /// use tokio_process_tools::*;
393 /// use tokio::process::Command;
394 ///
395 /// # tokio_test::block_on(async {
396 /// let process = Process::new(Command::new("server"))
397 /// .chunk_sizes(32.kilobytes())
398 /// .spawn_broadcast()?;
399 /// # Ok::<_, SpawnError>(())
400 /// # });
401 /// ```
402 pub fn chunk_sizes(mut self, chunk_size: NumBytes) -> Self {
403 self.stdout_chunk_size = chunk_size;
404 self.stderr_chunk_size = chunk_size;
405 self
406 }
407
408 /// Sets the stdout channel capacity.
409 ///
410 /// This controls how many chunks can be buffered before backpressure is applied.
411 /// Default is [DEFAULT_CHANNEL_CAPACITY].
412 ///
413 /// # Examples
414 ///
415 /// ```no_run
416 /// use tokio_process_tools::*;
417 /// use tokio::process::Command;
418 ///
419 /// # tokio_test::block_on(async {
420 /// let process = Process::new(Command::new("server"))
421 /// .stdout_capacity(512)
422 /// .spawn_broadcast()?;
423 /// # Ok::<_, SpawnError>(())
424 /// # });
425 /// ```
426 pub fn stdout_capacity(mut self, capacity: usize) -> Self {
427 self.stdout_capacity = capacity;
428 self
429 }
430
431 /// Sets the stderr channel capacity.
432 ///
433 /// This controls how many chunks can be buffered before backpressure is applied.
434 /// Default is [DEFAULT_CHANNEL_CAPACITY].
435 ///
436 /// # Examples
437 ///
438 /// ```no_run
439 /// use tokio_process_tools::*;
440 /// use tokio::process::Command;
441 ///
442 /// # tokio_test::block_on(async {
443 /// let process = Process::new(Command::new("server"))
444 /// .stderr_capacity(256)
445 /// .spawn_broadcast()?;
446 /// # Ok::<_, SpawnError>(())
447 /// # });
448 /// ```
449 pub fn stderr_capacity(mut self, capacity: usize) -> Self {
450 self.stderr_capacity = capacity;
451 self
452 }
453
454 /// Sets the stdout and stderr channel capacity.
455 ///
456 /// This controls how many chunks can be buffered before backpressure is applied.
457 /// Default is [DEFAULT_CHANNEL_CAPACITY].
458 ///
459 /// # Examples
460 ///
461 /// ```no_run
462 /// use tokio_process_tools::*;
463 /// use tokio::process::Command;
464 ///
465 /// # tokio_test::block_on(async {
466 /// let process = Process::new(Command::new("server"))
467 /// .capacities(256)
468 /// .spawn_broadcast()?;
469 /// # Ok::<_, SpawnError>(())
470 /// # });
471 /// ```
472 pub fn capacities(mut self, capacity: usize) -> Self {
473 self.stdout_capacity = capacity;
474 self.stderr_capacity = capacity;
475 self
476 }
477
478 /// Generates a process name based on the configured naming strategy.
479 fn generate_name(&self) -> Cow<'static, str> {
480 match &self.name {
481 ProcessName::Explicit(name) => name.clone(),
482 ProcessName::Auto(auto_name) => match auto_name {
483 AutoName::Using(settings) => settings.format_cmd(self.cmd.as_std()).into(),
484 AutoName::Debug => format!("{:?}", self.cmd).into(),
485 },
486 }
487 }
488
489 /// Spawns the process with broadcast output streams.
490 ///
491 /// Broadcast streams support multiple concurrent consumers of stdout/stderr,
492 /// which is useful when you need to inspect, collect, and process output
493 /// simultaneously. This comes with slightly higher memory overhead due to cloning.
494 ///
495 /// # Examples
496 ///
497 /// ```no_run
498 /// use tokio_process_tools::*;
499 /// use tokio::process::Command;
500 ///
501 /// # tokio_test::block_on(async {
502 /// let mut process = Process::new(Command::new("ls"))
503 /// .spawn_broadcast()?;
504 ///
505 /// // Multiple consumers can read the same output
506 /// let _logger = process.stdout().inspect_lines(|line| {
507 /// println!("{}", line);
508 /// tokio_process_tools::Next::Continue
509 /// }, Default::default());
510 ///
511 /// let _collector = process.stdout().collect_lines_into_vec(Default::default());
512 /// # Ok::<_, SpawnError>(())
513 /// # });
514 /// ```
515 pub fn spawn_broadcast(self) -> Result<ProcessHandle<BroadcastOutputStream>, SpawnError> {
516 let name = self.generate_name();
517 ProcessHandle::<BroadcastOutputStream>::spawn_with_capacity(
518 name,
519 self.cmd,
520 self.stdout_chunk_size,
521 self.stderr_chunk_size,
522 self.stdout_capacity,
523 self.stderr_capacity,
524 )
525 }
526
527 /// Spawns the process with single subscriber output streams.
528 ///
529 /// Single subscriber streams are more efficient (lower memory, no cloning) but
530 /// only allow one consumer of stdout/stderr at a time. Use this when you only
531 /// need to either inspect OR collect output, not both simultaneously.
532 ///
533 /// # Examples
534 ///
535 /// ```no_run
536 /// use tokio_process_tools::*;
537 /// use tokio::process::Command;
538 ///
539 /// # tokio_test::block_on(async {
540 /// let process = Process::new(Command::new("ls"))
541 /// .spawn_single_subscriber()?;
542 ///
543 /// // Only one consumer allowed
544 /// let collector = process.stdout().collect_lines_into_vec(Default::default());
545 /// # Ok::<_, SpawnError>(())
546 /// # });
547 /// ```
548 pub fn spawn_single_subscriber(
549 self,
550 ) -> Result<ProcessHandle<SingleSubscriberOutputStream>, SpawnError> {
551 let name = self.generate_name();
552 ProcessHandle::<SingleSubscriberOutputStream>::spawn_with_capacity(
553 name,
554 self.cmd,
555 self.stdout_chunk_size,
556 self.stderr_chunk_size,
557 self.stdout_capacity,
558 self.stderr_capacity,
559 )
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566 use crate::{LineParsingOptions, NumBytesExt, Output, OutputStream};
567 use assertr::prelude::*;
568 use std::path::PathBuf;
569 use tokio::process::Command;
570
571 #[tokio::test]
572 async fn process_builder_broadcast() {
573 let mut process = Process::new(Command::new("ls"))
574 .spawn_broadcast()
575 .expect("Failed to spawn");
576
577 assert_that(process.stdout().chunk_size()).is_equal_to(DEFAULT_CHUNK_SIZE);
578 assert_that(process.stderr().chunk_size()).is_equal_to(DEFAULT_CHUNK_SIZE);
579 assert_that(process.stdout().channel_capacity()).is_equal_to(DEFAULT_CHANNEL_CAPACITY);
580 assert_that(process.stderr().channel_capacity()).is_equal_to(DEFAULT_CHANNEL_CAPACITY);
581
582 let Output {
583 status,
584 stdout,
585 stderr,
586 } = process
587 .wait_for_completion_with_output(None, LineParsingOptions::default())
588 .await
589 .unwrap();
590
591 assert_that(status.success()).is_true();
592 assert_that(stdout).is_not_empty();
593 assert_that(stderr).is_empty();
594 }
595
596 #[tokio::test]
597 async fn process_builder_broadcast_with_custom_capacities() {
598 let mut process = Process::new(Command::new("ls"))
599 .stdout_chunk_size(42.kilobytes())
600 .stderr_chunk_size(43.kilobytes())
601 .stdout_capacity(42)
602 .stderr_capacity(43)
603 .spawn_broadcast()
604 .expect("Failed to spawn");
605
606 assert_that(process.stdout().chunk_size()).is_equal_to(42.kilobytes());
607 assert_that(process.stderr().chunk_size()).is_equal_to(43.kilobytes());
608 assert_that(process.stdout().channel_capacity()).is_equal_to(42);
609 assert_that(process.stderr().channel_capacity()).is_equal_to(43);
610
611 let Output {
612 status,
613 stdout,
614 stderr,
615 } = process
616 .wait_for_completion_with_output(None, LineParsingOptions::default())
617 .await
618 .unwrap();
619
620 assert_that(status.success()).is_true();
621 assert_that(stdout).is_not_empty();
622 assert_that(stderr).is_empty();
623 }
624
625 #[tokio::test]
626 async fn process_builder_single_subscriber() {
627 let mut process = Process::new(Command::new("ls"))
628 .spawn_single_subscriber()
629 .expect("Failed to spawn");
630
631 assert_that(process.stdout().chunk_size()).is_equal_to(DEFAULT_CHUNK_SIZE);
632 assert_that(process.stderr().chunk_size()).is_equal_to(DEFAULT_CHUNK_SIZE);
633 assert_that(process.stdout().channel_capacity()).is_equal_to(DEFAULT_CHANNEL_CAPACITY);
634 assert_that(process.stderr().channel_capacity()).is_equal_to(DEFAULT_CHANNEL_CAPACITY);
635
636 let Output {
637 status,
638 stdout,
639 stderr,
640 } = process
641 .wait_for_completion_with_output(None, LineParsingOptions::default())
642 .await
643 .unwrap();
644
645 assert_that(status.success()).is_true();
646 assert_that(stdout).is_not_empty();
647 assert_that(stderr).is_empty();
648 }
649
650 #[tokio::test]
651 async fn process_builder_single_subscriber_with_custom_capacities() {
652 let mut process = Process::new(Command::new("ls"))
653 .stdout_chunk_size(42.kilobytes())
654 .stderr_chunk_size(43.kilobytes())
655 .stdout_capacity(42)
656 .stderr_capacity(43)
657 .spawn_single_subscriber()
658 .expect("Failed to spawn");
659
660 assert_that(process.stdout().chunk_size()).is_equal_to(42.kilobytes());
661 assert_that(process.stderr().chunk_size()).is_equal_to(43.kilobytes());
662 assert_that(process.stdout().channel_capacity()).is_equal_to(42);
663 assert_that(process.stderr().channel_capacity()).is_equal_to(43);
664
665 let Output {
666 status,
667 stdout,
668 stderr,
669 } = process
670 .wait_for_completion_with_output(None, LineParsingOptions::default())
671 .await
672 .unwrap();
673
674 assert_that(status.success()).is_true();
675 assert_that(stdout).is_not_empty();
676 assert_that(stderr).is_empty();
677 }
678
679 #[tokio::test]
680 async fn process_builder_auto_name_captures_command_with_args_if_not_otherwise_specified() {
681 let mut cmd = Command::new("ls");
682 cmd.arg("-la");
683 cmd.env("FOO", "foo");
684 cmd.current_dir(PathBuf::from("./"));
685
686 let mut process = Process::new(cmd)
687 .spawn_broadcast()
688 .expect("Failed to spawn");
689
690 assert_that(&process.name).is_equal_to("ls \"-la\"");
691
692 let _ = process.wait_for_completion(None).await;
693 }
694
695 #[tokio::test]
696 async fn process_builder_auto_name_only_captures_command_when_requested() {
697 let mut cmd = Command::new("ls");
698 cmd.arg("-la");
699 cmd.env("FOO", "foo");
700 cmd.current_dir(PathBuf::from("./"));
701
702 let mut process = Process::new(cmd)
703 .with_auto_name(AutoName::Using(AutoNameSettings::program_only()))
704 .spawn_broadcast()
705 .expect("Failed to spawn");
706
707 assert_that(&process.name).is_equal_to("ls");
708
709 let _ = process.wait_for_completion(None).await;
710 }
711
712 #[tokio::test]
713 async fn process_builder_auto_name_captures_command_with_envs_and_args_when_requested() {
714 let mut cmd = Command::new("ls");
715 cmd.arg("-la");
716 cmd.env("FOO", "foo");
717 cmd.current_dir(PathBuf::from("./"));
718
719 let mut process = Process::new(cmd)
720 .with_auto_name(AutoName::Using(
721 AutoNameSettings::program_with_env_and_args(),
722 ))
723 .spawn_broadcast()
724 .expect("Failed to spawn");
725
726 assert_that(&process.name).is_equal_to("FOO=foo ls \"-la\"");
727
728 let _ = process.wait_for_completion(None).await;
729 }
730
731 #[tokio::test]
732 async fn process_builder_auto_name_captures_command_with_current_dir_envs_and_args_when_requested()
733 {
734 let mut cmd = Command::new("ls");
735 cmd.arg("-la");
736 cmd.env("FOO", "foo");
737 cmd.current_dir(PathBuf::from("./"));
738
739 let mut process = Process::new(cmd)
740 .with_auto_name(AutoName::Using(AutoNameSettings::full()))
741 .spawn_broadcast()
742 .expect("Failed to spawn");
743
744 assert_that(&process.name).is_equal_to("./ % FOO=foo ls \"-la\"");
745
746 let _ = process.wait_for_completion(None).await;
747 }
748
749 #[tokio::test]
750 async fn process_builder_auto_name_captures_full_command_debug_string_when_requested() {
751 let mut cmd = Command::new("ls");
752 cmd.arg("-la");
753 cmd.env("FOO", "foo");
754 cmd.current_dir(PathBuf::from("./"));
755
756 let mut process = Process::new(cmd)
757 .with_auto_name(AutoName::Debug)
758 .spawn_broadcast()
759 .expect("Failed to spawn");
760
761 assert_that(&process.name).is_equal_to(
762 "Command { std: cd \"./\" && FOO=\"foo\" \"ls\" \"-la\", kill_on_drop: false }",
763 );
764
765 let _ = process.wait_for_completion(None).await;
766 }
767
768 #[tokio::test]
769 async fn process_builder_custom_name() {
770 let id = 42;
771 let mut process = Process::new(Command::new("ls"))
772 .with_name(format!("worker-{id}"))
773 .spawn_broadcast()
774 .expect("Failed to spawn");
775
776 assert_that(&process.name).is_equal_to("worker-42");
777
778 let _ = process.wait_for_completion(None).await;
779 }
780}