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}