]> git.lizzy.rs Git - rust.git/blob - library/std/src/process/tests.rs
Auto merge of #105426 - flba-eb:fix_tls_destructor_unwinding, r=m-ou-se
[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{output}",
256     );
257 }
258
259 #[test]
260 #[cfg_attr(target_os = "vxworks", ignore)]
261 fn test_add_to_env() {
262     let result = env_cmd().env("RUN_TEST_NEW_ENV", "123").output().unwrap();
263     let output = String::from_utf8_lossy(&result.stdout).to_string();
264
265     assert!(
266         output.contains("RUN_TEST_NEW_ENV=123"),
267         "didn't find RUN_TEST_NEW_ENV inside of:\n\n{output}"
268     );
269 }
270
271 #[test]
272 #[cfg_attr(target_os = "vxworks", ignore)]
273 fn test_capture_env_at_spawn() {
274     use crate::env;
275
276     let mut cmd = env_cmd();
277     cmd.env("RUN_TEST_NEW_ENV1", "123");
278
279     // This variable will not be present if the environment has already
280     // been captured above.
281     env::set_var("RUN_TEST_NEW_ENV2", "456");
282     let result = cmd.output().unwrap();
283     env::remove_var("RUN_TEST_NEW_ENV2");
284
285     let output = String::from_utf8_lossy(&result.stdout).to_string();
286
287     assert!(
288         output.contains("RUN_TEST_NEW_ENV1=123"),
289         "didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{output}"
290     );
291     assert!(
292         output.contains("RUN_TEST_NEW_ENV2=456"),
293         "didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{output}"
294     );
295 }
296
297 // Regression tests for #30858.
298 #[test]
299 fn test_interior_nul_in_progname_is_error() {
300     match Command::new("has-some-\0\0s-inside").spawn() {
301         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
302         Ok(_) => panic!(),
303     }
304 }
305
306 #[test]
307 fn test_interior_nul_in_arg_is_error() {
308     match known_command().arg("has-some-\0\0s-inside").spawn() {
309         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
310         Ok(_) => panic!(),
311     }
312 }
313
314 #[test]
315 fn test_interior_nul_in_args_is_error() {
316     match known_command().args(&["has-some-\0\0s-inside"]).spawn() {
317         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
318         Ok(_) => panic!(),
319     }
320 }
321
322 #[test]
323 fn test_interior_nul_in_current_dir_is_error() {
324     match known_command().current_dir("has-some-\0\0s-inside").spawn() {
325         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
326         Ok(_) => panic!(),
327     }
328 }
329
330 // Regression tests for #30862.
331 #[test]
332 #[cfg_attr(target_os = "vxworks", ignore)]
333 fn test_interior_nul_in_env_key_is_error() {
334     match env_cmd().env("has-some-\0\0s-inside", "value").spawn() {
335         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
336         Ok(_) => panic!(),
337     }
338 }
339
340 #[test]
341 #[cfg_attr(target_os = "vxworks", ignore)]
342 fn test_interior_nul_in_env_value_is_error() {
343     match env_cmd().env("key", "has-some-\0\0s-inside").spawn() {
344         Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
345         Ok(_) => panic!(),
346     }
347 }
348
349 /// Tests that process creation flags work by debugging a process.
350 /// Other creation flags make it hard or impossible to detect
351 /// behavioral changes in the process.
352 #[test]
353 #[cfg(windows)]
354 fn test_creation_flags() {
355     use crate::os::windows::process::CommandExt;
356     use crate::sys::c::{BOOL, DWORD, INFINITE};
357     #[repr(C, packed)]
358     struct DEBUG_EVENT {
359         pub event_code: DWORD,
360         pub process_id: DWORD,
361         pub thread_id: DWORD,
362         // This is a union in the real struct, but we don't
363         // need this data for the purposes of this test.
364         pub _junk: [u8; 164],
365     }
366
367     extern "system" {
368         fn WaitForDebugEvent(lpDebugEvent: *mut DEBUG_EVENT, dwMilliseconds: DWORD) -> BOOL;
369         fn ContinueDebugEvent(
370             dwProcessId: DWORD,
371             dwThreadId: DWORD,
372             dwContinueStatus: DWORD,
373         ) -> BOOL;
374     }
375
376     const DEBUG_PROCESS: DWORD = 1;
377     const EXIT_PROCESS_DEBUG_EVENT: DWORD = 5;
378     const DBG_EXCEPTION_NOT_HANDLED: DWORD = 0x80010001;
379
380     let mut child =
381         Command::new("cmd").creation_flags(DEBUG_PROCESS).stdin(Stdio::piped()).spawn().unwrap();
382     child.stdin.take().unwrap().write_all(b"exit\r\n").unwrap();
383     let mut events = 0;
384     let mut event = DEBUG_EVENT { event_code: 0, process_id: 0, thread_id: 0, _junk: [0; 164] };
385     loop {
386         if unsafe { WaitForDebugEvent(&mut event as *mut DEBUG_EVENT, INFINITE) } == 0 {
387             panic!("WaitForDebugEvent failed!");
388         }
389         events += 1;
390
391         if event.event_code == EXIT_PROCESS_DEBUG_EVENT {
392             break;
393         }
394
395         if unsafe {
396             ContinueDebugEvent(event.process_id, event.thread_id, DBG_EXCEPTION_NOT_HANDLED)
397         } == 0
398         {
399             panic!("ContinueDebugEvent failed!");
400         }
401     }
402     assert!(events > 0);
403 }
404
405 #[test]
406 fn test_command_implements_send_sync() {
407     fn take_send_sync_type<T: Send + Sync>(_: T) {}
408     take_send_sync_type(Command::new(""))
409 }
410
411 // Ensure that starting a process with no environment variables works on Windows.
412 // This will fail if the environment block is ill-formed.
413 #[test]
414 #[cfg(windows)]
415 fn env_empty() {
416     let p = Command::new("cmd").args(&["/C", "exit 0"]).env_clear().spawn();
417     assert!(p.is_ok());
418 }
419
420 #[test]
421 #[cfg(not(windows))]
422 #[cfg_attr(any(target_os = "emscripten", target_env = "sgx"), ignore)]
423 fn main() {
424     const PIDFD: &'static str =
425         if cfg!(target_os = "linux") { "    create_pidfd: false,\n" } else { "" };
426
427     let mut command = Command::new("some-boring-name");
428
429     assert_eq!(format!("{command:?}"), format!(r#""some-boring-name""#));
430
431     assert_eq!(
432         format!("{command:#?}"),
433         format!(
434             r#"Command {{
435     program: "some-boring-name",
436     args: [
437         "some-boring-name",
438     ],
439 {PIDFD}}}"#
440         )
441     );
442
443     command.args(&["1", "2", "3"]);
444
445     assert_eq!(format!("{command:?}"), format!(r#""some-boring-name" "1" "2" "3""#));
446
447     assert_eq!(
448         format!("{command:#?}"),
449         format!(
450             r#"Command {{
451     program: "some-boring-name",
452     args: [
453         "some-boring-name",
454         "1",
455         "2",
456         "3",
457     ],
458 {PIDFD}}}"#
459         )
460     );
461
462     crate::os::unix::process::CommandExt::arg0(&mut command, "exciting-name");
463
464     assert_eq!(
465         format!("{command:?}"),
466         format!(r#"["some-boring-name"] "exciting-name" "1" "2" "3""#)
467     );
468
469     assert_eq!(
470         format!("{command:#?}"),
471         format!(
472             r#"Command {{
473     program: "some-boring-name",
474     args: [
475         "exciting-name",
476         "1",
477         "2",
478         "3",
479     ],
480 {PIDFD}}}"#
481         )
482     );
483
484     let mut command_with_env_and_cwd = Command::new("boring-name");
485     command_with_env_and_cwd.current_dir("/some/path").env("FOO", "bar");
486     assert_eq!(
487         format!("{command_with_env_and_cwd:?}"),
488         r#"cd "/some/path" && FOO="bar" "boring-name""#
489     );
490     assert_eq!(
491         format!("{command_with_env_and_cwd:#?}"),
492         format!(
493             r#"Command {{
494     program: "boring-name",
495     args: [
496         "boring-name",
497     ],
498     env: CommandEnv {{
499         clear: false,
500         vars: {{
501             "FOO": Some(
502                 "bar",
503             ),
504         }},
505     }},
506     cwd: Some(
507         "/some/path",
508     ),
509 {PIDFD}}}"#
510         )
511     );
512 }
513
514 // See issue #91991
515 #[test]
516 #[cfg(windows)]
517 fn run_bat_script() {
518     let tempdir = crate::sys_common::io::test::tmpdir();
519     let script_path = tempdir.join("hello.cmd");
520
521     crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap();
522     let output = Command::new(&script_path)
523         .arg("fellow Rustaceans")
524         .stdout(crate::process::Stdio::piped())
525         .spawn()
526         .unwrap()
527         .wait_with_output()
528         .unwrap();
529     assert!(output.status.success());
530     assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!");
531 }
532
533 // See issue #95178
534 #[test]
535 #[cfg(windows)]
536 fn run_canonical_bat_script() {
537     let tempdir = crate::sys_common::io::test::tmpdir();
538     let script_path = tempdir.join("hello.cmd");
539
540     crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap();
541
542     // Try using a canonical path
543     let output = Command::new(&script_path.canonicalize().unwrap())
544         .arg("fellow Rustaceans")
545         .stdout(crate::process::Stdio::piped())
546         .spawn()
547         .unwrap()
548         .wait_with_output()
549         .unwrap();
550     assert!(output.status.success());
551     assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!");
552 }