1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Working with processes.
13 #![unstable(feature = "process", reason = "recently added via RFC 579")]
14 #![allow(non_upper_case_globals)]
21 use io
::{self, Error, ErrorKind}
;
24 use sync
::mpsc
::{channel, Receiver}
;
25 use sys
::pipe2
::{self, AnonPipe}
;
26 use sys
::process2
::Process
as ProcessImp
;
27 use sys
::process2
::Command
as CommandImp
;
28 use sys
::process2
::ExitStatus
as ExitStatusImp
;
29 use sys_common
::{AsInner, AsInnerMut}
;
32 /// Representation of a running or exited child process.
34 /// This structure is used to represent and manage child processes. A child
35 /// process is created via the `Command` struct, which configures the spawning
36 /// process and can itself be constructed using a builder-style interface.
41 /// # #![feature(process)]
43 /// use std::process::Command;
45 /// let output = Command::new("/bin/cat").arg("file.txt").output().unwrap_or_else(|e| {
46 /// panic!("failed to execute child: {}", e)
48 /// let contents = output.stdout;
49 /// assert!(output.status.success());
54 /// None until wait() or wait_with_output() is called.
55 status
: Option
<ExitStatusImp
>,
57 /// The handle for writing to the child's stdin, if it has been captured
58 pub stdin
: Option
<ChildStdin
>,
60 /// The handle for reading from the child's stdout, if it has been captured
61 pub stdout
: Option
<ChildStdout
>,
63 /// The handle for reading from the child's stderr, if it has been captured
64 pub stderr
: Option
<ChildStderr
>,
67 /// A handle to a child procesess's stdin
68 pub struct ChildStdin
{
72 impl Write
for ChildStdin
{
73 fn write(&mut self, buf
: &[u8]) -> io
::Result
<usize> {
77 fn flush(&mut self) -> io
::Result
<()> {
82 /// A handle to a child procesess's stdout
83 pub struct ChildStdout
{
87 impl Read
for ChildStdout
{
88 fn read(&mut self, buf
: &mut [u8]) -> io
::Result
<usize> {
93 /// A handle to a child procesess's stderr
94 pub struct ChildStderr
{
98 impl Read
for ChildStderr
{
99 fn read(&mut self, buf
: &mut [u8]) -> io
::Result
<usize> {
104 /// The `Command` type acts as a process builder, providing fine-grained control
105 /// over how a new process should be spawned. A default configuration can be
106 /// generated using `Command::new(program)`, where `program` gives a path to the
107 /// program to be executed. Additional builder methods allow the configuration
108 /// to be changed (for example, by adding arguments) prior to spawning:
111 /// # #![feature(process)]
113 /// use std::process::Command;
115 /// let output = Command::new("sh").arg("-c").arg("echo hello").output().unwrap_or_else(|e| {
116 /// panic!("failed to execute process: {}", e)
118 /// let hello = output.stdout;
123 // Details explained in the builder methods
124 stdin
: Option
<StdioImp
>,
125 stdout
: Option
<StdioImp
>,
126 stderr
: Option
<StdioImp
>,
130 /// Constructs a new `Command` for launching the program at
131 /// path `program`, with the following default configuration:
133 /// * No arguments to the program
134 /// * Inherit the current process's environment
135 /// * Inherit the current process's working directory
136 /// * Inherit stdin/stdout/stderr for `run` or `status`, but create pipes for `output`
138 /// Builder methods are provided to change these defaults and
139 /// otherwise configure the process.
140 pub fn new
<S
: AsOsStr
+ ?Sized
>(program
: &S
) -> Command
{
142 inner
: CommandImp
::new(program
.as_os_str()),
149 /// Add an argument to pass to the program.
150 pub fn arg
<S
: AsOsStr
+ ?Sized
>(&mut self, arg
: &S
) -> &mut Command
{
151 self.inner
.arg(arg
.as_os_str());
155 /// Add multiple arguments to pass to the program.
156 pub fn args
<S
: AsOsStr
>(&mut self, args
: &[S
]) -> &mut Command
{
157 self.inner
.args(args
.iter().map(AsOsStr
::as_os_str
));
161 /// Inserts or updates an environment variable mapping.
163 /// Note that environment variable names are case-insensitive (but case-preserving) on Windows,
164 /// and case-sensitive on all other platforms.
165 pub fn env
<S
: ?Sized
, T
: ?Sized
>(&mut self, key
: &S
, val
: &T
) -> &mut Command
where
166 S
: AsOsStr
, T
: AsOsStr
168 self.inner
.env(key
.as_os_str(), val
.as_os_str());
172 /// Removes an environment variable mapping.
173 pub fn env_remove
<S
: ?Sized
+ AsOsStr
>(&mut self, key
: &S
) -> &mut Command
{
174 self.inner
.env_remove(key
.as_os_str());
178 /// Clears the entire environment map for the child process.
179 pub fn env_clear(&mut self) -> &mut Command
{
180 self.inner
.env_clear();
184 /// Set the working directory for the child process.
185 pub fn current_dir
<P
: AsPath
+ ?Sized
>(&mut self, dir
: &P
) -> &mut Command
{
186 self.inner
.cwd(dir
.as_path().as_os_str());
190 /// Configuration for the child process's stdin handle (file descriptor 0).
191 /// Defaults to `CreatePipe(true, false)` so the input can be written to.
192 pub fn stdin(&mut self, cfg
: Stdio
) -> &mut Command
{
193 self.stdin
= Some(cfg
.0);
197 /// Configuration for the child process's stdout handle (file descriptor 1).
198 /// Defaults to `CreatePipe(false, true)` so the output can be collected.
199 pub fn stdout(&mut self, cfg
: Stdio
) -> &mut Command
{
200 self.stdout
= Some(cfg
.0);
204 /// Configuration for the child process's stderr handle (file descriptor 2).
205 /// Defaults to `CreatePipe(false, true)` so the output can be collected.
206 pub fn stderr(&mut self, cfg
: Stdio
) -> &mut Command
{
207 self.stderr
= Some(cfg
.0);
211 fn spawn_inner(&self, default_io
: StdioImp
) -> io
::Result
<Child
> {
212 let (their_stdin
, our_stdin
) = try
!(
213 setup_io(self.stdin
.as_ref().unwrap_or(&default_io
), 0, true)
215 let (their_stdout
, our_stdout
) = try
!(
216 setup_io(self.stdout
.as_ref().unwrap_or(&default_io
), 1, false)
218 let (their_stderr
, our_stderr
) = try
!(
219 setup_io(self.stderr
.as_ref().unwrap_or(&default_io
), 2, false)
222 match ProcessImp
::spawn(&self.inner
, their_stdin
, their_stdout
, their_stderr
) {
224 Ok(handle
) => Ok(Child
{
227 stdin
: our_stdin
.map(|fd
| ChildStdin { inner: fd }
),
228 stdout
: our_stdout
.map(|fd
| ChildStdout { inner: fd }
),
229 stderr
: our_stderr
.map(|fd
| ChildStderr { inner: fd }
),
234 /// Executes the command as a child process, returning a handle to it.
236 /// By default, stdin, stdout and stderr are inherited by the parent.
237 pub fn spawn(&mut self) -> io
::Result
<Child
> {
238 self.spawn_inner(StdioImp
::Inherit
)
241 /// Executes the command as a child process, waiting for it to finish and
242 /// collecting all of its output.
244 /// By default, stdin, stdout and stderr are captured (and used to
245 /// provide the resulting output).
250 /// # #![feature(process)]
251 /// use std::process::Command;
253 /// let output = Command::new("cat").arg("foot.txt").output().unwrap_or_else(|e| {
254 /// panic!("failed to execute process: {}", e)
257 /// println!("status: {}", output.status);
258 /// println!("stdout: {}", String::from_utf8_lossy(output.stdout.as_slice()));
259 /// println!("stderr: {}", String::from_utf8_lossy(output.stderr.as_slice()));
261 pub fn output(&mut self) -> io
::Result
<Output
> {
262 self.spawn_inner(StdioImp
::Capture
).and_then(|p
| p
.wait_with_output())
265 /// Executes a command as a child process, waiting for it to finish and
266 /// collecting its exit status.
268 /// By default, stdin, stdout and stderr are inherited by the parent.
273 /// # #![feature(process)]
274 /// use std::process::Command;
276 /// let status = Command::new("ls").status().unwrap_or_else(|e| {
277 /// panic!("failed to execute process: {}", e)
280 /// println!("process exited with: {}", status);
282 pub fn status(&mut self) -> io
::Result
<ExitStatus
> {
283 self.spawn().and_then(|mut p
| p
.wait())
287 #[stable(feature = "rust1", since = "1.0.0")]
288 impl fmt
::Debug
for Command
{
289 /// Format the program and arguments of a Command for display. Any
290 /// non-utf8 data is lossily converted using the utf8 replacement
292 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
293 try
!(write
!(f
, "{:?}", self.inner
.program
));
294 for arg
in &self.inner
.args
{
295 try
!(write
!(f
, " {:?}", arg
));
301 impl AsInner
<CommandImp
> for Command
{
302 fn as_inner(&self) -> &CommandImp { &self.inner }
305 impl AsInnerMut
<CommandImp
> for Command
{
306 fn as_inner_mut(&mut self) -> &mut CommandImp { &mut self.inner }
309 fn setup_io(io
: &StdioImp
, fd
: libc
::c_int
, readable
: bool
)
310 -> io
::Result
<(Option
<AnonPipe
>, Option
<AnonPipe
>)>
312 use self::StdioImp
::*;
318 (Some(AnonPipe
::from_fd(fd
)), None
)
321 let (reader
, writer
) = try
!(unsafe { pipe2::anon_pipe() }
);
323 (Some(reader
), Some(writer
))
325 (Some(writer
), Some(reader
))
331 /// The output of a finished process.
332 #[derive(PartialEq, Eq, Clone)]
334 /// The status (exit code) of the process.
335 pub status
: ExitStatus
,
336 /// The data that the process wrote to stdout.
338 /// The data that the process wrote to stderr.
342 /// Describes what to do with a standard io stream for a child process.
343 pub struct Stdio(StdioImp
);
345 // The internal enum for stdio setup; see below for descriptions.
354 /// A new pipe should be arranged to connect the parent and child processes.
355 pub fn capture() -> Stdio { Stdio(StdioImp::Capture) }
357 /// The child inherits from the corresponding parent descriptor.
358 pub fn inherit() -> Stdio { Stdio(StdioImp::Capture) }
360 /// This stream will be ignored. This is the equivalent of attaching the
361 /// stream to `/dev/null`
362 pub fn null() -> Stdio { Stdio(StdioImp::Capture) }
365 /// Describes the result of a process after it has terminated.
366 #[derive(PartialEq, Eq, Clone, Copy, Debug)]
367 pub struct ExitStatus(ExitStatusImp
);
370 /// Was termination successful? Signal termination not considered a success,
371 /// and success is defined as a zero exit status.
372 pub fn success(&self) -> bool
{
376 /// Return the exit code of the process, if any.
378 /// On Unix, this will return `None` if the process was terminated
379 /// by a signal; `std::os::unix` provides an extension trait for
380 /// extracting the signal and other details from the `ExitStatus`.
381 pub fn code(&self) -> Option
<i32> {
386 impl AsInner
<ExitStatusImp
> for ExitStatus
{
387 fn as_inner(&self) -> &ExitStatusImp { &self.0 }
390 impl fmt
::Display
for ExitStatus
{
391 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
397 /// Forces the child to exit. This is equivalent to sending a
398 /// SIGKILL on unix platforms.
399 pub fn kill(&mut self) -> io
::Result
<()> {
400 #[cfg(unix)] fn collect_status(p: &mut Child) {
401 // On Linux (and possibly other unices), a process that has exited will
402 // continue to accept signals because it is "defunct". The delivery of
403 // signals will only fail once the child has been reaped. For this
404 // reason, if the process hasn't exited yet, then we attempt to collect
405 // their status with WNOHANG.
406 if p
.status
.is_none() {
407 match p
.handle
.try_wait() {
408 Some(status
) => { p.status = Some(status); }
413 #[cfg(windows)] fn collect_status(_p: &mut Child) {}
415 collect_status(self);
417 // if the process has finished, and therefore had waitpid called,
418 // and we kill it, then on unix we might ending up killing a
419 // newer process that happens to have the same (re-used) id
420 if self.status
.is_some() {
421 return Err(Error
::new(
422 ErrorKind
::InvalidInput
,
423 "invalid argument: can't kill an exited process",
428 unsafe { self.handle.kill() }
431 /// Wait for the child to exit completely, returning the status that it
432 /// exited with. This function will continue to have the same return value
433 /// after it has been called at least once.
435 /// The stdin handle to the child process, if any, will be closed
436 /// before waiting. This helps avoid deadlock: it ensures that the
437 /// child does not block waiting for input from the parent, while
438 /// the parent waits for the child to exit.
439 pub fn wait(&mut self) -> io
::Result
<ExitStatus
> {
440 drop(self.stdin
.take());
442 Some(code
) => Ok(ExitStatus(code
)),
444 let status
= try
!(self.handle
.wait());
445 self.status
= Some(status
);
446 Ok(ExitStatus(status
))
451 /// Simultaneously wait for the child to exit and collect all remaining
452 /// output on the stdout/stderr handles, returning a `Output`
455 /// The stdin handle to the child process, if any, will be closed
456 /// before waiting. This helps avoid deadlock: it ensures that the
457 /// child does not block waiting for input from the parent, while
458 /// the parent waits for the child to exit.
459 pub fn wait_with_output(mut self) -> io
::Result
<Output
> {
460 drop(self.stdin
.take());
461 fn read
<T
: Read
+ Send
+ '
static>(stream
: Option
<T
>) -> Receiver
<io
::Result
<Vec
<u8>>> {
462 let (tx
, rx
) = channel();
465 thread
::spawn(move || {
466 let mut stream
= stream
;
467 let mut ret
= Vec
::new();
468 let res
= stream
.read_to_end(&mut ret
);
469 tx
.send(res
.map(|_
| ret
)).unwrap();
472 None
=> tx
.send(Ok(Vec
::new())).unwrap()
476 let stdout
= read(self.stdout
.take());
477 let stderr
= read(self.stderr
.take());
478 let status
= try
!(self.wait());
482 stdout
: stdout
.recv().unwrap().unwrap_or(Vec
::new()),
483 stderr
: stderr
.recv().unwrap().unwrap_or(Vec
::new()),
492 use prelude
::v1
::{Ok, Err, range, drop, Some, None, Vec}
;
493 use prelude
::v1
::{String, Clone}
;
494 use prelude
::v1
::{SliceExt, Str, StrExt, AsSlice, ToString, GenericPath}
;
497 use old_io
::fs
::PathExtensions
;
498 use rt
::running_on_valgrind
;
500 use super::{Child, Command, Output, ExitStatus, Stdio}
;
501 use sync
::mpsc
::channel
;
505 // FIXME(#10380) these tests should not all be ignored on android.
507 #[cfg(not(target_os="android"))]
510 let p
= Command
::new("true").spawn();
512 let mut p
= p
.unwrap();
513 assert
!(p
.wait().unwrap().success());
516 #[cfg(not(target_os="android"))]
519 match Command
::new("if-this-is-a-binary-then-the-world-has-ended").spawn() {
525 #[cfg(not(target_os="android"))]
527 fn exit_reported_right() {
528 let p
= Command
::new("false").spawn();
530 let mut p
= p
.unwrap();
531 assert
!(p
.wait().unwrap().code() == Some(1));
532 drop(p
.wait().clone());
535 #[cfg(all(unix, not(target_os="android")))]
537 fn signal_reported_right() {
538 use os
::unix
::ExitStatusExt
;
540 let p
= Command
::new("/bin/sh").arg("-c").arg("kill -9 $$").spawn();
542 let mut p
= p
.unwrap();
543 match p
.wait().unwrap().signal() {
545 result
=> panic
!("not terminated by signal 9 (instead, {:?})", result
),
549 pub fn run_output(mut cmd
: Command
) -> String
{
552 let mut p
= p
.unwrap();
553 assert
!(p
.stdout
.is_some());
554 let mut ret
= String
::new();
555 p
.stdout
.as_mut().unwrap().read_to_string(&mut ret
).unwrap();
556 assert
!(p
.wait().unwrap().success());
560 #[cfg(not(target_os="android"))]
563 let mut cmd
= Command
::new("echo");
564 cmd
.arg("foobar").stdout(Stdio
::capture());
565 assert_eq
!(run_output(cmd
), "foobar\n");
568 #[cfg(all(unix, not(target_os="android")))]
570 fn set_current_dir_works() {
571 let mut cmd
= Command
::new("/bin/sh");
572 cmd
.arg("-c").arg("pwd")
574 .stdout(Stdio
::capture());
575 assert_eq
!(run_output(cmd
), "/\n");
578 #[cfg(all(unix, not(target_os="android")))]
581 let mut p
= Command
::new("/bin/sh")
582 .arg("-c").arg("read line; echo $line")
583 .stdin(Stdio
::capture())
584 .stdout(Stdio
::capture())
586 p
.stdin
.as_mut().unwrap().write("foobar".as_bytes()).unwrap();
587 drop(p
.stdin
.take());
588 let mut out
= String
::new();
589 p
.stdout
.as_mut().unwrap().read_to_string(&mut out
).unwrap();
590 assert
!(p
.wait().unwrap().success());
591 assert_eq
!(out
, "foobar\n");
595 #[cfg(all(unix, not(target_os="android")))]
600 let mut p
= Command
::new("/bin/sh")
601 .arg("-c").arg("true")
602 .uid(unsafe { libc::getuid() }
)
603 .gid(unsafe { libc::getgid() }
)
605 assert
!(p
.wait().unwrap().success());
608 #[cfg(all(unix, not(target_os="android")))]
610 fn uid_to_root_fails() {
614 // if we're already root, this isn't a valid test. Most of the bots run
615 // as non-root though (android is an exception).
616 if unsafe { libc::getuid() == 0 } { return }
617 assert
!(Command
::new("/bin/ls").uid(0).gid(0).spawn().is_err());
620 #[cfg(not(target_os="android"))]
622 fn test_process_status() {
623 let mut status
= Command
::new("false").status().unwrap();
624 assert
!(status
.code() == Some(1));
626 status
= Command
::new("true").status().unwrap();
627 assert
!(status
.success());
631 fn test_process_output_fail_to_start() {
632 match Command
::new("/no-binary-by-this-name-should-exist").output() {
633 Err(e
) => assert_eq
!(e
.kind(), ErrorKind
::FileNotFound
),
638 #[cfg(not(target_os="android"))]
640 fn test_process_output_output() {
641 let Output {status, stdout, stderr}
642 = Command
::new("echo").arg("hello").output().unwrap();
643 let output_str
= str::from_utf8(stdout
.as_slice()).unwrap();
645 assert
!(status
.success());
646 assert_eq
!(output_str
.trim().to_string(), "hello");
648 if !running_on_valgrind() {
649 assert_eq
!(stderr
, Vec
::new());
653 #[cfg(not(target_os="android"))]
655 fn test_process_output_error() {
656 let Output {status, stdout, stderr}
657 = Command
::new("mkdir").arg(".").output().unwrap();
659 assert
!(status
.code() == Some(1));
660 assert_eq
!(stdout
, Vec
::new());
661 assert
!(!stderr
.is_empty());
664 #[cfg(not(target_os="android"))]
666 fn test_finish_once() {
667 let mut prog
= Command
::new("false").spawn().unwrap();
668 assert
!(prog
.wait().unwrap().code() == Some(1));
671 #[cfg(not(target_os="android"))]
673 fn test_finish_twice() {
674 let mut prog
= Command
::new("false").spawn().unwrap();
675 assert
!(prog
.wait().unwrap().code() == Some(1));
676 assert
!(prog
.wait().unwrap().code() == Some(1));
679 #[cfg(not(target_os="android"))]
681 fn test_wait_with_output_once() {
682 let prog
= Command
::new("echo").arg("hello").stdout(Stdio
::capture())
684 let Output {status, stdout, stderr}
= prog
.wait_with_output().unwrap();
685 let output_str
= str::from_utf8(stdout
.as_slice()).unwrap();
687 assert
!(status
.success());
688 assert_eq
!(output_str
.trim().to_string(), "hello");
690 if !running_on_valgrind() {
691 assert_eq
!(stderr
, Vec
::new());
695 #[cfg(all(unix, not(target_os="android")))]
696 pub fn pwd_cmd() -> Command
{
699 #[cfg(target_os="android")]
700 pub fn pwd_cmd() -> Command
{
701 let mut cmd
= Command
::new("/system/bin/sh");
702 cmd
.arg("-c").arg("pwd");
707 pub fn pwd_cmd() -> Command
{
708 let mut cmd
= Command
::new("cmd");
709 cmd
.arg("/c").arg("cd");
714 fn test_keep_current_working_dir() {
716 let prog
= pwd_cmd().spawn().unwrap();
718 let output
= String
::from_utf8(prog
.wait_with_output().unwrap().stdout
).unwrap();
719 let parent_dir
= os
::getcwd().unwrap();
720 let child_dir
= old_path
::Path
::new(output
.trim());
722 let parent_stat
= parent_dir
.stat().unwrap();
723 let child_stat
= child_dir
.stat().unwrap();
725 assert_eq
!(parent_stat
.unstable
.device
, child_stat
.unstable
.device
);
726 assert_eq
!(parent_stat
.unstable
.inode
, child_stat
.unstable
.inode
);
730 fn test_change_working_directory() {
732 // test changing to the parent of os::getcwd() because we know
733 // the path exists (and os::getcwd() is not expected to be root)
734 let parent_dir
= os
::getcwd().unwrap().dir_path();
735 let result
= pwd_cmd().current_dir(&parent_dir
).output().unwrap();
737 let output
= String
::from_utf8(result
.stdout
).unwrap();
738 let child_dir
= old_path
::Path
::new(output
.trim());
740 let parent_stat
= parent_dir
.stat().unwrap();
741 let child_stat
= child_dir
.stat().unwrap();
743 assert_eq
!(parent_stat
.unstable
.device
, child_stat
.unstable
.device
);
744 assert_eq
!(parent_stat
.unstable
.inode
, child_stat
.unstable
.inode
);
747 #[cfg(all(unix, not(target_os="android")))]
748 pub fn env_cmd() -> Command
{
751 #[cfg(target_os="android")]
752 pub fn env_cmd() -> Command
{
753 let mut cmd
= Command
::new("/system/bin/sh");
754 cmd
.arg("-c").arg("set");
759 pub fn env_cmd() -> Command
{
760 let mut cmd
= Command
::new("cmd");
761 cmd
.arg("/c").arg("set");
765 #[cfg(not(target_os="android"))]
767 fn test_inherit_env() {
769 if running_on_valgrind() { return; }
771 let result
= env_cmd().output().unwrap();
772 let output
= String
::from_utf8(result
.stdout
).unwrap();
775 for &(ref k
, ref v
) in &r
{
776 // don't check windows magical empty-named variables
777 assert
!(k
.is_empty() ||
778 output
.contains(format
!("{}={}", *k
, *v
).as_slice()),
779 "output doesn't contain `{}={}`\n{}",
783 #[cfg(target_os="android")]
785 fn test_inherit_env() {
787 if running_on_valgrind() { return; }
789 let mut result
= env_cmd().output().unwrap();
790 let output
= String
::from_utf8(result
.stdout
).unwrap();
793 for &(ref k
, ref v
) in &r
{
794 // don't check android RANDOM variables
795 if *k
!= "RANDOM".to_string() {
796 assert
!(output
.contains(format
!("{}={}",
799 output
.contains(format
!("{}=\'{}\'",
807 fn test_override_env() {
810 // In some build environments (such as chrooted Nix builds), `env` can
811 // only be found in the explicitly-provided PATH env variable, not in
812 // default places such as /bin or /usr/bin. So we need to pass through
813 // PATH to our sub-process.
814 let mut cmd
= env_cmd();
815 cmd
.env_clear().env("RUN_TEST_NEW_ENV", "123");
816 if let Some(p
) = env
::var_os("PATH") {
819 let result
= cmd
.output().unwrap();
820 let output
= String
::from_utf8_lossy(result
.stdout
.as_slice()).to_string();
822 assert
!(output
.contains("RUN_TEST_NEW_ENV=123"),
823 "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output
);
827 fn test_add_to_env() {
828 let result
= env_cmd().env("RUN_TEST_NEW_ENV", "123").output().unwrap();
829 let output
= String
::from_utf8_lossy(result
.stdout
.as_slice()).to_string();
831 assert
!(output
.contains("RUN_TEST_NEW_ENV=123"),
832 "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output
);