]> git.lizzy.rs Git - rust.git/blob - library/std/src/process/tests.rs
Rollup merge of #93748 - klensy:vis-r, r=cjgillot
[rust.git] / library / std / src / process / tests.rs
1 use crate::io::prelude::*;
2
3 use super::{Command, Output, Stdio};
4 use crate::io::ErrorKind;
5 use crate::str;
6
7 fn known_command() -> Command {
8     if cfg!(windows) { Command::new("help") } else { Command::new("echo") }
9 }
10
11 #[cfg(target_os = "android")]
12 fn shell_cmd() -> Command {
13     Command::new("/system/bin/sh")
14 }
15
16 #[cfg(not(target_os = "android"))]
17 fn shell_cmd() -> Command {
18     Command::new("/bin/sh")
19 }
20
21 #[test]
22 #[cfg_attr(any(target_os = "vxworks"), ignore)]
23 fn smoke() {
24     let p = if cfg!(target_os = "windows") {
25         Command::new("cmd").args(&["/C", "exit 0"]).spawn()
26     } else {
27         shell_cmd().arg("-c").arg("true").spawn()
28     };
29     assert!(p.is_ok());
30     let mut p = p.unwrap();
31     assert!(p.wait().unwrap().success());
32 }
33
34 #[test]
35 #[cfg_attr(target_os = "android", ignore)]
36 fn smoke_failure() {
37     match Command::new("if-this-is-a-binary-then-the-world-has-ended").spawn() {
38         Ok(..) => panic!(),
39         Err(..) => {}
40     }
41 }
42
43 #[test]
44 #[cfg_attr(any(target_os = "vxworks"), ignore)]
45 fn exit_reported_right() {
46     let p = if cfg!(target_os = "windows") {
47         Command::new("cmd").args(&["/C", "exit 1"]).spawn()
48     } else {
49         shell_cmd().arg("-c").arg("false").spawn()
50     };
51     assert!(p.is_ok());
52     let mut p = p.unwrap();
53     assert!(p.wait().unwrap().code() == Some(1));
54     drop(p.wait());
55 }
56
57 #[test]
58 #[cfg(unix)]
59 #[cfg_attr(any(target_os = "vxworks"), ignore)]
60 fn signal_reported_right() {
61     use crate::os::unix::process::ExitStatusExt;
62
63     let mut p = shell_cmd().arg("-c").arg("read a").stdin(Stdio::piped()).spawn().unwrap();
64     p.kill().unwrap();
65     match p.wait().unwrap().signal() {
66         Some(9) => {}
67         result => panic!("not terminated by signal 9 (instead, {:?})", result),
68     }
69 }
70
71 pub fn run_output(mut cmd: Command) -> String {
72     let p = cmd.spawn();
73     assert!(p.is_ok());
74     let mut p = p.unwrap();
75     assert!(p.stdout.is_some());
76     let mut ret = String::new();
77     p.stdout.as_mut().unwrap().read_to_string(&mut ret).unwrap();
78     assert!(p.wait().unwrap().success());
79     return ret;
80 }
81
82 #[test]
83 #[cfg_attr(any(target_os = "vxworks"), ignore)]
84 fn stdout_works() {
85     if cfg!(target_os = "windows") {
86         let mut cmd = Command::new("cmd");
87         cmd.args(&["/C", "echo foobar"]).stdout(Stdio::piped());
88         assert_eq!(run_output(cmd), "foobar\r\n");
89     } else {
90         let mut cmd = shell_cmd();
91         cmd.arg("-c").arg("echo foobar").stdout(Stdio::piped());
92         assert_eq!(run_output(cmd), "foobar\n");
93     }
94 }
95
96 #[test]
97 #[cfg_attr(any(windows, target_os = "vxworks"), ignore)]
98 fn set_current_dir_works() {
99     let mut cmd = shell_cmd();
100     cmd.arg("-c").arg("pwd").current_dir("/").stdout(Stdio::piped());
101     assert_eq!(run_output(cmd), "/\n");
102 }
103
104 #[test]
105 #[cfg_attr(any(windows, target_os = "vxworks"), ignore)]
106 fn stdin_works() {
107     let mut p = shell_cmd()
108         .arg("-c")
109         .arg("read line; echo $line")
110         .stdin(Stdio::piped())
111         .stdout(Stdio::piped())
112         .spawn()
113         .unwrap();
114     p.stdin.as_mut().unwrap().write("foobar".as_bytes()).unwrap();
115     drop(p.stdin.take());
116     let mut out = String::new();
117     p.stdout.as_mut().unwrap().read_to_string(&mut out).unwrap();
118     assert!(p.wait().unwrap().success());
119     assert_eq!(out, "foobar\n");
120 }
121
122 #[test]
123 #[cfg_attr(any(target_os = "vxworks"), ignore)]
124 fn test_process_status() {
125     let mut status = if cfg!(target_os = "windows") {
126         Command::new("cmd").args(&["/C", "exit 1"]).status().unwrap()
127     } else {
128         shell_cmd().arg("-c").arg("false").status().unwrap()
129     };
130     assert!(status.code() == Some(1));
131
132     status = if cfg!(target_os = "windows") {
133         Command::new("cmd").args(&["/C", "exit 0"]).status().unwrap()
134     } else {
135         shell_cmd().arg("-c").arg("true").status().unwrap()
136     };
137     assert!(status.success());
138 }
139
140 #[test]
141 fn test_process_output_fail_to_start() {
142     match Command::new("/no-binary-by-this-name-should-exist").output() {
143         Err(e) => assert_eq!(e.kind(), ErrorKind::NotFound),
144         Ok(..) => panic!(),
145     }
146 }
147
148 #[test]
149 #[cfg_attr(any(target_os = "vxworks"), ignore)]
150 fn test_process_output_output() {
151     let Output { status, stdout, stderr } = if cfg!(target_os = "windows") {
152         Command::new("cmd").args(&["/C", "echo hello"]).output().unwrap()
153     } else {
154         shell_cmd().arg("-c").arg("echo hello").output().unwrap()
155     };
156     let output_str = str::from_utf8(&stdout).unwrap();
157
158     assert!(status.success());
159     assert_eq!(output_str.trim().to_string(), "hello");
160     assert_eq!(stderr, Vec::new());
161 }
162
163 #[test]
164 #[cfg_attr(any(target_os = "vxworks"), ignore)]
165 fn test_process_output_error() {
166     let Output { status, stdout, stderr } = if cfg!(target_os = "windows") {
167         Command::new("cmd").args(&["/C", "mkdir ."]).output().unwrap()
168     } else {
169         Command::new("mkdir").arg("./").output().unwrap()
170     };
171
172     assert!(status.code().is_some());
173     assert!(status.code() != Some(0));
174     assert_eq!(stdout, Vec::new());
175     assert!(!stderr.is_empty());
176 }
177
178 #[test]
179 #[cfg_attr(any(target_os = "vxworks"), ignore)]
180 fn test_finish_once() {
181     let mut prog = if cfg!(target_os = "windows") {
182         Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap()
183     } else {
184         shell_cmd().arg("-c").arg("false").spawn().unwrap()
185     };
186     assert!(prog.wait().unwrap().code() == Some(1));
187 }
188
189 #[test]
190 #[cfg_attr(any(target_os = "vxworks"), ignore)]
191 fn test_finish_twice() {
192     let mut prog = if cfg!(target_os = "windows") {
193         Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap()
194     } else {
195         shell_cmd().arg("-c").arg("false").spawn().unwrap()
196     };
197     assert!(prog.wait().unwrap().code() == Some(1));
198     assert!(prog.wait().unwrap().code() == Some(1));
199 }
200
201 #[test]
202 #[cfg_attr(any(target_os = "vxworks"), ignore)]
203 fn test_wait_with_output_once() {
204     let prog = if cfg!(target_os = "windows") {
205         Command::new("cmd").args(&["/C", "echo hello"]).stdout(Stdio::piped()).spawn().unwrap()
206     } else {
207         shell_cmd().arg("-c").arg("echo hello").stdout(Stdio::piped()).spawn().unwrap()
208     };
209
210     let Output { status, stdout, stderr } = prog.wait_with_output().unwrap();
211     let output_str = str::from_utf8(&stdout).unwrap();
212
213     assert!(status.success());
214     assert_eq!(output_str.trim().to_string(), "hello");
215     assert_eq!(stderr, Vec::new());
216 }
217
218 #[cfg(all(unix, not(target_os = "android")))]
219 pub fn env_cmd() -> Command {
220     Command::new("env")
221 }
222 #[cfg(target_os = "android")]
223 pub fn env_cmd() -> Command {
224     let mut cmd = Command::new("/system/bin/sh");
225     cmd.arg("-c").arg("set");
226     cmd
227 }
228
229 #[cfg(windows)]
230 pub fn env_cmd() -> Command {
231     let mut cmd = Command::new("cmd");
232     cmd.arg("/c").arg("set");
233     cmd
234 }
235
236 #[test]
237 #[cfg_attr(target_os = "vxworks", ignore)]
238 fn test_override_env() {
239     use crate::env;
240
241     // In some build environments (such as chrooted Nix builds), `env` can
242     // only be found in the explicitly-provided PATH env variable, not in
243     // default places such as /bin or /usr/bin. So we need to pass through
244     // PATH to our sub-process.
245     let mut cmd = env_cmd();
246     cmd.env_clear().env("RUN_TEST_NEW_ENV", "123");
247     if let Some(p) = env::var_os("PATH") {
248         cmd.env("PATH", &p);
249     }
250     let result = cmd.output().unwrap();
251     let output = String::from_utf8_lossy(&result.stdout).to_string();
252
253     assert!(
254         output.contains("RUN_TEST_NEW_ENV=123"),
255         "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}",
256         output
257     );
258 }
259
260 #[test]
261 #[cfg_attr(target_os = "vxworks", ignore)]
262 fn test_add_to_env() {
263     let result = env_cmd().env("RUN_TEST_NEW_ENV", "123").output().unwrap();
264     let output = String::from_utf8_lossy(&result.stdout).to_string();
265
266     assert!(
267         output.contains("RUN_TEST_NEW_ENV=123"),
268         "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}",
269         output
270     );
271 }
272
273 #[test]
274 #[cfg_attr(target_os = "vxworks", ignore)]
275 fn test_capture_env_at_spawn() {
276     use crate::env;
277
278     let mut cmd = env_cmd();
279     cmd.env("RUN_TEST_NEW_ENV1", "123");
280
281     // This variable will not be present if the environment has already
282     // been captured above.
283     env::set_var("RUN_TEST_NEW_ENV2", "456");
284     let result = cmd.output().unwrap();
285     env::remove_var("RUN_TEST_NEW_ENV2");
286
287     let output = String::from_utf8_lossy(&result.stdout).to_string();
288
289     assert!(
290         output.contains("RUN_TEST_NEW_ENV1=123"),
291         "didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{}",
292         output
293     );
294     assert!(
295         output.contains("RUN_TEST_NEW_ENV2=456"),
296         "didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{}",
297         output
298     );
299 }
300
301 // Regression tests for #30858.
302 #[test]
303 fn test_interior_nul_in_progname_is_error() {
304     match Command::new("has-some-\0\0s-inside").spawn() {
305         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
306         Ok(_) => panic!(),
307     }
308 }
309
310 #[test]
311 fn test_interior_nul_in_arg_is_error() {
312     match known_command().arg("has-some-\0\0s-inside").spawn() {
313         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
314         Ok(_) => panic!(),
315     }
316 }
317
318 #[test]
319 fn test_interior_nul_in_args_is_error() {
320     match known_command().args(&["has-some-\0\0s-inside"]).spawn() {
321         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
322         Ok(_) => panic!(),
323     }
324 }
325
326 #[test]
327 fn test_interior_nul_in_current_dir_is_error() {
328     match known_command().current_dir("has-some-\0\0s-inside").spawn() {
329         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
330         Ok(_) => panic!(),
331     }
332 }
333
334 // Regression tests for #30862.
335 #[test]
336 #[cfg_attr(target_os = "vxworks", ignore)]
337 fn test_interior_nul_in_env_key_is_error() {
338     match env_cmd().env("has-some-\0\0s-inside", "value").spawn() {
339         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
340         Ok(_) => panic!(),
341     }
342 }
343
344 #[test]
345 #[cfg_attr(target_os = "vxworks", ignore)]
346 fn test_interior_nul_in_env_value_is_error() {
347     match env_cmd().env("key", "has-some-\0\0s-inside").spawn() {
348         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
349         Ok(_) => panic!(),
350     }
351 }
352
353 /// Tests that process creation flags work by debugging a process.
354 /// Other creation flags make it hard or impossible to detect
355 /// behavioral changes in the process.
356 #[test]
357 #[cfg(windows)]
358 fn test_creation_flags() {
359     use crate::os::windows::process::CommandExt;
360     use crate::sys::c::{BOOL, DWORD, INFINITE};
361     #[repr(C, packed)]
362     struct DEBUG_EVENT {
363         pub event_code: DWORD,
364         pub process_id: DWORD,
365         pub thread_id: DWORD,
366         // This is a union in the real struct, but we don't
367         // need this data for the purposes of this test.
368         pub _junk: [u8; 164],
369     }
370
371     extern "system" {
372         fn WaitForDebugEvent(lpDebugEvent: *mut DEBUG_EVENT, dwMilliseconds: DWORD) -> BOOL;
373         fn ContinueDebugEvent(
374             dwProcessId: DWORD,
375             dwThreadId: DWORD,
376             dwContinueStatus: DWORD,
377         ) -> BOOL;
378     }
379
380     const DEBUG_PROCESS: DWORD = 1;
381     const EXIT_PROCESS_DEBUG_EVENT: DWORD = 5;
382     const DBG_EXCEPTION_NOT_HANDLED: DWORD = 0x80010001;
383
384     let mut child =
385         Command::new("cmd").creation_flags(DEBUG_PROCESS).stdin(Stdio::piped()).spawn().unwrap();
386     child.stdin.take().unwrap().write_all(b"exit\r\n").unwrap();
387     let mut events = 0;
388     let mut event = DEBUG_EVENT { event_code: 0, process_id: 0, thread_id: 0, _junk: [0; 164] };
389     loop {
390         if unsafe { WaitForDebugEvent(&mut event as *mut DEBUG_EVENT, INFINITE) } == 0 {
391             panic!("WaitForDebugEvent failed!");
392         }
393         events += 1;
394
395         if event.event_code == EXIT_PROCESS_DEBUG_EVENT {
396             break;
397         }
398
399         if unsafe {
400             ContinueDebugEvent(event.process_id, event.thread_id, DBG_EXCEPTION_NOT_HANDLED)
401         } == 0
402         {
403             panic!("ContinueDebugEvent failed!");
404         }
405     }
406     assert!(events > 0);
407 }
408
409 #[test]
410 fn test_command_implements_send_sync() {
411     fn take_send_sync_type<T: Send + Sync>(_: T) {}
412     take_send_sync_type(Command::new(""))
413 }
414
415 // Ensure that starting a process with no environment variables works on Windows.
416 // This will fail if the environment block is ill-formed.
417 #[test]
418 #[cfg(windows)]
419 fn env_empty() {
420     let p = Command::new("cmd").args(&["/C", "exit 0"]).env_clear().spawn();
421     assert!(p.is_ok());
422 }
423
424 // See issue #91991
425 #[test]
426 #[cfg(windows)]
427 fn run_bat_script() {
428     let tempdir = crate::sys_common::io::test::tmpdir();
429     let script_path = tempdir.join("hello.cmd");
430
431     crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap();
432     let output = Command::new(&script_path)
433         .arg("fellow Rustaceans")
434         .stdout(crate::process::Stdio::piped())
435         .spawn()
436         .unwrap()
437         .wait_with_output()
438         .unwrap();
439     assert!(output.status.success());
440     assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!");
441 }