1use std::borrow::Cow;
2use std::ffi::OsStr;
3
4#[derive(Debug, thiserror::Error)]
5#[error("Command execution failed: io_error={io_error:?}, exit_status={exit_status:?}")]
6pub struct CommandError {
7 pub io_error: Option<std::io::Error>,
8 pub exit_status: Option<std::process::ExitStatus>,
9}
10
11fn write_stdin(
12 child: &mut std::process::Child,
13 stdin_data: Option<Vec<u8>>,
14) -> Result<(), CommandError> {
15 use std::io::Write;
16
17 if let Some(data) = stdin_data {
18 child
19 .stdin
20 .take()
21 .unwrap()
22 .write_all(&data)
23 .map_err(|io_error| CommandError {
24 io_error: Some(io_error),
25 exit_status: None,
26 })?;
27 }
28
29 Ok(())
30}
31
32fn run_and_wait(
33 mut child: std::process::Child,
34 stdin_data: Option<Vec<u8>>,
35 start: std::time::Instant,
36) -> Result<std::process::Output, CommandError> {
37 write_stdin(&mut child, stdin_data)?;
38
39 let output = child.wait_with_output().map_err(|io_error| CommandError {
40 io_error: Some(io_error),
41 exit_status: None,
42 })?;
43
44 log::debug!(
45 "exit_status={:?} runtime={:?}",
46 output.status,
47 start.elapsed()
48 );
49
50 Ok(output)
51}
52
53fn run_and_wait_status(
54 mut child: std::process::Child,
55 stdin_data: Option<Vec<u8>>,
56 start: std::time::Instant,
57) -> Result<std::process::ExitStatus, CommandError> {
58 write_stdin(&mut child, stdin_data)?;
59
60 let status = child.wait().map_err(|io_error| CommandError {
61 io_error: Some(io_error),
62 exit_status: None,
63 })?;
64
65 log::debug!("exit_status={:?} runtime={:?}", status, start.elapsed());
66
67 Ok(status)
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
74pub struct EnvVariableName<'a>(Cow<'a, str>);
75
76impl EnvVariableName<'_> {
77 #[must_use]
78 pub fn as_str(&self) -> &str {
79 &self.0
80 }
81}
82
83impl AsRef<OsStr> for EnvVariableName<'_> {
84 fn as_ref(&self) -> &OsStr {
85 self.0.as_ref().as_ref()
86 }
87}
88
89impl std::fmt::Display for EnvVariableName<'_> {
90 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 formatter.write_str(self.as_str())
92 }
93}
94
95impl EnvVariableName<'static> {
96 #[must_use]
103 pub const fn from_static_or_panic(s: &'static str) -> Self {
104 match validate_env_variable_name(s) {
105 Ok(()) => {}
106 Err(EnvVariableNameError::Empty) => {
107 panic!("Environment variable name cannot be empty");
108 }
109 Err(EnvVariableNameError::ContainsEquals) => {
110 panic!("Environment variable name cannot contain '='");
111 }
112 }
113 Self(Cow::Borrowed(s))
114 }
115}
116
117#[derive(Debug, thiserror::Error)]
118pub enum EnvVariableNameError {
119 #[error("Environment variable name cannot be empty")]
120 Empty,
121 #[error("Environment variable name cannot contain '='")]
122 ContainsEquals,
123}
124
125impl std::str::FromStr for EnvVariableName<'static> {
126 type Err = EnvVariableNameError;
127
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 validate_env_variable_name(s).map(|()| Self(Cow::Owned(s.to_string())))
130 }
131}
132
133const fn validate_env_variable_name(s: &str) -> Result<(), EnvVariableNameError> {
134 if s.is_empty() {
135 return Err(EnvVariableNameError::Empty);
136 }
137 let bytes = s.as_bytes();
138 let mut i = 0;
139 while i < bytes.len() {
141 if bytes[i] == b'=' {
142 return Err(EnvVariableNameError::ContainsEquals);
143 }
144 i += 1;
145 }
146 Ok(())
147}
148
149#[derive(Debug)]
154pub struct Output {
155 pub stdout: Vec<u8>,
156 pub stderr: Vec<u8>,
157 pub status: std::process::ExitStatus,
158}
159
160impl Output {
161 #[must_use]
163 pub fn success(&self) -> bool {
164 self.status.success()
165 }
166
167 pub fn into_stdout_string(self) -> Result<String, std::string::FromUtf8Error> {
173 String::from_utf8(self.stdout)
174 }
175
176 pub fn into_stderr_string(self) -> Result<String, std::string::FromUtf8Error> {
182 String::from_utf8(self.stderr)
183 }
184}
185
186#[derive(Clone, Copy)]
188enum CaptureSingleStream {
189 Stdout,
190 Stderr,
191}
192
193#[derive(Clone, Copy, Default)]
195pub enum Stdio {
196 Piped,
198 #[default]
200 Inherit,
201 Null,
203}
204
205impl From<Stdio> for std::process::Stdio {
206 fn from(stdio: Stdio) -> Self {
207 match stdio {
208 Stdio::Piped => std::process::Stdio::piped(),
209 Stdio::Inherit => std::process::Stdio::inherit(),
210 Stdio::Null => std::process::Stdio::null(),
211 }
212 }
213}
214
215pub struct Spawn {
219 command: Command,
220 stdin: Stdio,
221 stdout: Stdio,
222 stderr: Stdio,
223}
224
225impl Spawn {
226 fn new(command: Command) -> Self {
227 Self {
228 command,
229 stdin: Stdio::Inherit,
230 stdout: Stdio::Inherit,
231 stderr: Stdio::Inherit,
232 }
233 }
234
235 #[must_use]
237 pub fn stdin(mut self, stdio: Stdio) -> Self {
238 self.stdin = stdio;
239 self
240 }
241
242 #[must_use]
244 pub fn stdout(mut self, stdio: Stdio) -> Self {
245 self.stdout = stdio;
246 self
247 }
248
249 #[must_use]
251 pub fn stderr(mut self, stdio: Stdio) -> Self {
252 self.stderr = stdio;
253 self
254 }
255
256 pub fn run(mut self) -> Result<Child, CommandError> {
258 log::debug!("{:#?}", self.command.inner);
259
260 self.command.inner.stdin(self.stdin);
261 self.command.inner.stdout(self.stdout);
262 self.command.inner.stderr(self.stderr);
263
264 let inner = self
265 .command
266 .inner
267 .spawn()
268 .map_err(|io_error| CommandError {
269 io_error: Some(io_error),
270 exit_status: None,
271 })?;
272
273 Ok(Child { inner })
274 }
275}
276
277#[derive(Debug)]
281pub struct Child {
282 inner: std::process::Child,
283}
284
285impl Child {
286 pub fn stdin(&mut self) -> Option<&mut std::process::ChildStdin> {
288 self.inner.stdin.as_mut()
289 }
290
291 pub fn stdout(&mut self) -> Option<&mut std::process::ChildStdout> {
293 self.inner.stdout.as_mut()
294 }
295
296 pub fn stderr(&mut self) -> Option<&mut std::process::ChildStderr> {
298 self.inner.stderr.as_mut()
299 }
300
301 pub fn take_stdin(&mut self) -> Option<std::process::ChildStdin> {
303 self.inner.stdin.take()
304 }
305
306 pub fn take_stdout(&mut self) -> Option<std::process::ChildStdout> {
308 self.inner.stdout.take()
309 }
310
311 pub fn take_stderr(&mut self) -> Option<std::process::ChildStderr> {
313 self.inner.stderr.take()
314 }
315
316 pub fn wait(mut self) -> Result<std::process::ExitStatus, CommandError> {
318 self.inner.wait().map_err(|io_error| CommandError {
319 io_error: Some(io_error),
320 exit_status: None,
321 })
322 }
323
324 pub fn wait_with_output(self) -> Result<Output, CommandError> {
326 let output = self
327 .inner
328 .wait_with_output()
329 .map_err(|io_error| CommandError {
330 io_error: Some(io_error),
331 exit_status: None,
332 })?;
333
334 Ok(Output {
335 stdout: output.stdout,
336 stderr: output.stderr,
337 status: output.status,
338 })
339 }
340}
341
342pub struct Capture {
344 command: Command,
345 stream: CaptureSingleStream,
346 accept_nonzero_exit: bool,
347}
348
349fn run_capture(
350 mut command: Command,
351 accept_nonzero_exit: bool,
352) -> Result<std::process::Output, CommandError> {
353 log::debug!("{:#?}", command.inner);
354
355 if command.stdin_data.is_some() {
356 command.inner.stdin(std::process::Stdio::piped());
357 }
358
359 let start = std::time::Instant::now();
360
361 let child = command.inner.spawn().map_err(|io_error| CommandError {
362 io_error: Some(io_error),
363 exit_status: None,
364 })?;
365
366 let output = run_and_wait(child, command.stdin_data, start)?;
367
368 if accept_nonzero_exit || output.status.success() {
369 Ok(output)
370 } else {
371 Err(CommandError {
372 io_error: None,
373 exit_status: Some(output.status),
374 })
375 }
376}
377
378impl Capture {
379 fn new(command: Command, stream: CaptureSingleStream) -> Self {
380 Self {
381 command,
382 stream,
383 accept_nonzero_exit: false,
384 }
385 }
386
387 #[must_use]
389 pub fn accept_nonzero_exit(mut self) -> Self {
390 self.accept_nonzero_exit = true;
391 self
392 }
393
394 pub fn bytes(mut self) -> Result<Vec<u8>, CommandError> {
396 use std::process::Stdio;
397
398 let (stdout, stderr) = match self.stream {
399 CaptureSingleStream::Stdout => (Stdio::piped(), Stdio::inherit()),
400 CaptureSingleStream::Stderr => (Stdio::inherit(), Stdio::piped()),
401 };
402
403 self.command.inner.stdout(stdout);
404 self.command.inner.stderr(stderr);
405
406 let output = run_capture(self.command, self.accept_nonzero_exit)?;
407
408 Ok(match self.stream {
409 CaptureSingleStream::Stdout => output.stdout,
410 CaptureSingleStream::Stderr => output.stderr,
411 })
412 }
413
414 pub fn string(self) -> Result<String, CommandError> {
416 let bytes = self.bytes()?;
417 String::from_utf8(bytes).map_err(|utf8_error| CommandError {
418 io_error: Some(std::io::Error::new(
419 std::io::ErrorKind::InvalidData,
420 utf8_error,
421 )),
422 exit_status: None,
423 })
424 }
425}
426
427pub struct CaptureAllStreams {
429 command: Command,
430 accept_nonzero_exit: bool,
431}
432
433impl CaptureAllStreams {
434 fn new(command: Command) -> Self {
435 Self {
436 command,
437 accept_nonzero_exit: false,
438 }
439 }
440
441 #[must_use]
443 pub fn accept_nonzero_exit(mut self) -> Self {
444 self.accept_nonzero_exit = true;
445 self
446 }
447
448 pub fn output(mut self) -> Result<Output, CommandError> {
450 use std::process::Stdio;
451
452 self.command.inner.stdout(Stdio::piped());
453 self.command.inner.stderr(Stdio::piped());
454
455 let output = run_capture(self.command, self.accept_nonzero_exit)?;
456
457 Ok(Output {
458 stdout: output.stdout,
459 stderr: output.stderr,
460 status: output.status,
461 })
462 }
463}
464
465pub struct Command {
466 inner: std::process::Command,
467 stdin_data: Option<Vec<u8>>,
468}
469
470impl Command {
471 pub fn new(value: impl AsRef<OsStr>) -> Self {
472 Command {
473 inner: std::process::Command::new(value),
474 stdin_data: None,
475 }
476 }
477
478 #[cfg(feature = "test-utils")]
487 pub fn test_eq(&self, other: &Self) {
488 assert_eq!(format!("{:?}", self.inner), format!("{:?}", other.inner));
489 }
490
491 pub fn argument(mut self, value: impl AsRef<OsStr>) -> Self {
492 self.inner.arg(value);
493 self
494 }
495
496 pub fn optional_argument(mut self, optional: Option<impl AsRef<OsStr>>) -> Self {
497 if let Some(value) = optional {
498 self.inner.arg(value);
499 }
500 self
501 }
502
503 pub fn optional_flag(mut self, condition: bool, flag: impl AsRef<OsStr>) -> Self {
510 if condition {
511 self.inner.arg(flag);
512 }
513 self
514 }
515
516 pub fn option(mut self, name: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self {
523 self.inner.arg(name);
524 self.inner.arg(value);
525 self
526 }
527
528 pub fn optional_option(
535 mut self,
536 name: impl AsRef<OsStr>,
537 value: Option<impl AsRef<OsStr>>,
538 ) -> Self {
539 if let Some(value) = value {
540 self.inner.arg(name);
541 self.inner.arg(value);
542 }
543 self
544 }
545
546 pub fn arguments<T: AsRef<OsStr>>(mut self, value: impl IntoIterator<Item = T>) -> Self {
547 self.inner.args(value);
548 self
549 }
550
551 pub fn working_directory(mut self, dir: impl AsRef<std::path::Path>) -> Self {
552 self.inner.current_dir(dir);
553 self
554 }
555
556 pub fn env(mut self, key: &EnvVariableName<'_>, val: impl AsRef<OsStr>) -> Self {
557 self.inner.env(key, val);
558 self
559 }
560
561 pub fn envs<'a, I, V>(mut self, vars: I) -> Self
562 where
563 I: IntoIterator<Item = (EnvVariableName<'a>, V)>,
564 V: AsRef<OsStr>,
565 {
566 for (key, val) in vars {
567 self.inner.env(key, val);
568 }
569 self
570 }
571
572 #[must_use]
574 pub fn env_remove(mut self, key: &EnvVariableName<'_>) -> Self {
575 self.inner.env_remove(key);
576 self
577 }
578
579 #[must_use]
580 pub fn stdin_bytes(mut self, data: impl Into<Vec<u8>>) -> Self {
581 self.stdin_data = Some(data.into());
582 self
583 }
584
585 #[must_use]
587 pub fn capture_stdout(self) -> Capture {
588 Capture::new(self, CaptureSingleStream::Stdout)
589 }
590
591 #[must_use]
593 pub fn capture_stderr(self) -> Capture {
594 Capture::new(self, CaptureSingleStream::Stderr)
595 }
596
597 #[must_use]
599 pub fn capture_stderr_stdout(self) -> CaptureAllStreams {
600 CaptureAllStreams::new(self)
601 }
602
603 #[must_use]
634 pub fn spawn(self) -> Spawn {
635 Spawn::new(self)
636 }
637
638 pub fn output(mut self) -> Result<Output, CommandError> {
643 use std::process::Stdio;
644
645 log::debug!("{:#?}", self.inner);
646
647 self.inner.stdout(Stdio::piped());
648 self.inner.stderr(Stdio::piped());
649
650 if self.stdin_data.is_some() {
651 self.inner.stdin(Stdio::piped());
652 }
653
654 let start = std::time::Instant::now();
655
656 let child = self.inner.spawn().map_err(|io_error| CommandError {
657 io_error: Some(io_error),
658 exit_status: None,
659 })?;
660
661 let output = run_and_wait(child, self.stdin_data, start)?;
662
663 Ok(Output {
664 stdout: output.stdout,
665 stderr: output.stderr,
666 status: output.status,
667 })
668 }
669
670 pub fn status(mut self) -> Result<(), CommandError> {
672 use std::process::Stdio;
673
674 log::debug!("{:#?}", self.inner);
675
676 if self.stdin_data.is_some() {
677 self.inner.stdin(Stdio::piped());
678 }
679
680 let start = std::time::Instant::now();
681
682 let child = self.inner.spawn().map_err(|io_error| CommandError {
683 io_error: Some(io_error),
684 exit_status: None,
685 })?;
686
687 let exit_status = run_and_wait_status(child, self.stdin_data, start)?;
688
689 if exit_status.success() {
690 Ok(())
691 } else {
692 Err(CommandError {
693 io_error: None,
694 exit_status: Some(exit_status),
695 })
696 }
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703
704 #[test]
705 fn test_stdout_bytes_success() {
706 assert_eq!(
707 Command::new("echo")
708 .argument("hello")
709 .capture_stdout()
710 .bytes()
711 .unwrap(),
712 b"hello\n"
713 );
714 }
715
716 #[test]
717 fn test_stdout_bytes_nonzero_exit() {
718 let error = Command::new("sh")
719 .arguments(["-c", "exit 42"])
720 .capture_stdout()
721 .bytes()
722 .unwrap_err();
723 assert_eq!(
724 error.exit_status.map(|status| status.code()),
725 Some(Some(42))
726 );
727 assert!(error.io_error.is_none());
728 }
729
730 #[test]
731 fn test_stdout_bytes_io_error() {
732 let error = Command::new("./nonexistent")
733 .capture_stdout()
734 .bytes()
735 .unwrap_err();
736 assert!(error.io_error.is_some());
737 assert_eq!(error.io_error.unwrap().kind(), std::io::ErrorKind::NotFound);
738 assert!(error.exit_status.is_none());
739 }
740
741 #[test]
742 fn test_stdout_string_success() {
743 assert_eq!(
744 Command::new("echo")
745 .argument("hello")
746 .capture_stdout()
747 .string()
748 .unwrap(),
749 "hello\n"
750 );
751 }
752
753 #[test]
754 fn test_stderr_bytes_success() {
755 assert_eq!(
756 Command::new("sh")
757 .arguments(["-c", "echo error >&2"])
758 .capture_stderr()
759 .bytes()
760 .unwrap(),
761 b"error\n"
762 );
763 }
764
765 #[test]
766 fn test_stderr_string_success() {
767 assert_eq!(
768 Command::new("sh")
769 .arguments(["-c", "echo error >&2"])
770 .capture_stderr()
771 .string()
772 .unwrap(),
773 "error\n"
774 );
775 }
776
777 #[test]
778 fn test_status_success() {
779 assert!(Command::new("true").status().is_ok());
780 }
781
782 #[test]
783 fn test_status_nonzero_exit() {
784 let error = Command::new("sh")
785 .arguments(["-c", "exit 42"])
786 .status()
787 .unwrap_err();
788 assert_eq!(
789 error.exit_status.map(|status| status.code()),
790 Some(Some(42))
791 );
792 assert!(error.io_error.is_none());
793 }
794
795 #[test]
796 fn test_status_io_error() {
797 let error = Command::new("./nonexistent").status().unwrap_err();
798 assert!(error.io_error.is_some());
799 assert_eq!(error.io_error.unwrap().kind(), std::io::ErrorKind::NotFound);
800 assert!(error.exit_status.is_none());
801 }
802
803 #[test]
804 fn test_env_variable_name_from_static_or_panic() {
805 const NAME: EnvVariableName<'static> = EnvVariableName::from_static_or_panic("PATH");
806 assert_eq!(NAME.as_str(), "PATH");
807 }
808
809 #[test]
810 fn test_env_variable_name_parse() {
811 let name: EnvVariableName = "HOME".parse().unwrap();
812 assert_eq!(name.as_str(), "HOME");
813 }
814
815 #[test]
816 fn test_env_variable_name_empty() {
817 let result: Result<EnvVariableName, _> = "".parse();
818 assert!(matches!(result, Err(EnvVariableNameError::Empty)));
819 }
820
821 #[test]
822 fn test_env_variable_name_contains_equals() {
823 let result: Result<EnvVariableName, _> = "FOO=BAR".parse();
824 assert!(matches!(result, Err(EnvVariableNameError::ContainsEquals)));
825 }
826
827 #[test]
828 fn test_env_with_variable() {
829 let name: EnvVariableName = "MY_VAR".parse().unwrap();
830 let output = Command::new("sh")
831 .arguments(["-c", "echo $MY_VAR"])
832 .env(&name, "hello")
833 .capture_stdout()
834 .string()
835 .unwrap();
836 assert_eq!(output, "hello\n");
837 }
838
839 #[test]
840 fn test_stdin_bytes() {
841 let output = Command::new("cat")
842 .stdin_bytes(b"hello world".as_slice())
843 .capture_stdout()
844 .string()
845 .unwrap();
846 assert_eq!(output, "hello world");
847 }
848
849 #[test]
850 fn test_stdin_bytes_vec() {
851 let output = Command::new("cat")
852 .stdin_bytes(vec![104, 105])
853 .capture_stdout()
854 .string()
855 .unwrap();
856 assert_eq!(output, "hi");
857 }
858
859 #[test]
860 fn test_output_success() {
861 let output = Command::new("echo").argument("hello").output().unwrap();
862 assert!(output.success());
863 assert_eq!(output.stdout, b"hello\n");
864 assert!(output.stderr.is_empty());
865 }
866
867 #[test]
868 fn test_output_failure_with_stderr() {
869 let output = Command::new("sh")
870 .arguments(["-c", "echo error >&2; exit 1"])
871 .output()
872 .unwrap();
873 assert!(!output.success());
874 assert_eq!(output.into_stderr_string().unwrap(), "error\n");
875 }
876
877 #[test]
878 fn test_output_io_error() {
879 let error = Command::new("./nonexistent").output().unwrap_err();
880 assert!(error.io_error.is_some());
881 assert_eq!(error.io_error.unwrap().kind(), std::io::ErrorKind::NotFound);
882 }
883
884 #[test]
885 fn test_spawn_with_piped_stdout() {
886 use std::io::Read;
887
888 let mut child = Command::new("echo")
889 .argument("hello")
890 .spawn()
891 .stdout(Stdio::Piped)
892 .run()
893 .unwrap();
894
895 let mut output = String::new();
896 child.stdout().unwrap().read_to_string(&mut output).unwrap();
897 assert_eq!(output, "hello\n");
898
899 let status = child.wait().unwrap();
900 assert!(status.success());
901 }
902
903 #[test]
904 fn test_spawn_with_piped_stdin() {
905 use std::io::Write;
906
907 let mut child = Command::new("cat")
908 .spawn()
909 .stdin(Stdio::Piped)
910 .stdout(Stdio::Piped)
911 .run()
912 .unwrap();
913
914 child.stdin().unwrap().write_all(b"hello").unwrap();
915 drop(child.take_stdin());
916
917 let output = child.wait_with_output().unwrap();
918 assert!(output.success());
919 assert_eq!(output.stdout, b"hello");
920 }
921
922 #[test]
923 fn test_spawn_wait() {
924 let child = Command::new("true").spawn().run().unwrap();
925
926 let status = child.wait().unwrap();
927 assert!(status.success());
928 }
929
930 #[test]
931 fn test_spawn_wait_with_output() {
932 let child = Command::new("sh")
933 .arguments(["-c", "echo out; echo err >&2"])
934 .spawn()
935 .stdout(Stdio::Piped)
936 .stderr(Stdio::Piped)
937 .run()
938 .unwrap();
939
940 let output = child.wait_with_output().unwrap();
941 assert!(output.success());
942 assert_eq!(output.stdout, b"out\n");
943 assert_eq!(output.stderr, b"err\n");
944 }
945
946 #[test]
947 fn test_spawn_io_error() {
948 let error = Command::new("./nonexistent").spawn().run().unwrap_err();
949 assert!(error.io_error.is_some());
950 assert_eq!(error.io_error.unwrap().kind(), std::io::ErrorKind::NotFound);
951 }
952
953 #[test]
954 fn test_spawn_take_handles() {
955 use std::io::{Read, Write};
956
957 let mut child = Command::new("cat")
958 .spawn()
959 .stdin(Stdio::Piped)
960 .stdout(Stdio::Piped)
961 .run()
962 .unwrap();
963
964 let mut stdin = child.take_stdin().unwrap();
965 stdin.write_all(b"test").unwrap();
966 drop(stdin);
967
968 let mut stdout = child.take_stdout().unwrap();
969 let mut output = String::new();
970 stdout.read_to_string(&mut output).unwrap();
971 assert_eq!(output, "test");
972
973 child.wait().unwrap();
974 }
975
976 #[test]
977 fn test_option() {
978 let output = Command::new("echo")
979 .option("-n", "hello")
980 .capture_stdout()
981 .string()
982 .unwrap();
983 assert_eq!(output, "hello");
984 }
985
986 #[test]
987 fn test_optional_option_some() {
988 let output = Command::new("echo")
989 .optional_option("-n", Some("hello"))
990 .capture_stdout()
991 .string()
992 .unwrap();
993 assert_eq!(output, "hello");
994 }
995
996 #[test]
997 fn test_optional_option_none() {
998 let output = Command::new("echo")
999 .optional_option("-n", None::<&str>)
1000 .argument("hello")
1001 .capture_stdout()
1002 .string()
1003 .unwrap();
1004 assert_eq!(output, "hello\n");
1005 }
1006
1007 #[test]
1008 fn test_optional_flag_true() {
1009 let output = Command::new("echo")
1010 .optional_flag(true, "-n")
1011 .argument("hello")
1012 .capture_stdout()
1013 .string()
1014 .unwrap();
1015 assert_eq!(output, "hello");
1016 }
1017
1018 #[test]
1019 fn test_optional_flag_false() {
1020 let output = Command::new("echo")
1021 .optional_flag(false, "-n")
1022 .argument("hello")
1023 .capture_stdout()
1024 .string()
1025 .unwrap();
1026 assert_eq!(output, "hello\n");
1027 }
1028}