| use super::child_pipe::{Pipes, child_pipe}; | |
| use super::{Arg, make_command_line}; | |
| use crate::ffi::{OsStr, OsString}; | |
| use crate::os::windows::io::AsHandle; | |
| use crate::process::{Command, Stdio}; | |
| use crate::time::Duration; | |
| use crate::{env, thread}; | |
| #[test] | |
| fn test_raw_args() { | |
| let command_line = &make_command_line( | |
| OsStr::new("quoted exe"), | |
| &[ | |
| Arg::Regular(OsString::from("quote me")), | |
| Arg::Raw(OsString::from("quote me *not*")), | |
| Arg::Raw(OsString::from("\t\\")), | |
| Arg::Raw(OsString::from("internal \\\"backslash-\"quote")), | |
| Arg::Regular(OsString::from("optional-quotes")), | |
| ], | |
| false, | |
| ) | |
| .unwrap(); | |
| assert_eq!( | |
| String::from_utf16(command_line).unwrap(), | |
| "\"quoted exe\" \"quote me\" quote me *not* \t\\ internal \\\"backslash-\"quote optional-quotes" | |
| ); | |
| } | |
| #[test] | |
| fn test_thread_handle() { | |
| use crate::os::windows::io::BorrowedHandle; | |
| use crate::os::windows::process::{ChildExt, CommandExt}; | |
| const CREATE_SUSPENDED: u32 = 0x00000004; | |
| let p = Command::new("whoami").stdout(Stdio::null()).creation_flags(CREATE_SUSPENDED).spawn(); | |
| assert!(p.is_ok()); | |
| // Ensure the process is killed in the event something goes wrong. | |
| struct DropGuard(crate::process::Child); | |
| impl Drop for DropGuard { | |
| fn drop(&mut self) { | |
| let _ = self.0.kill(); | |
| } | |
| } | |
| let mut p = DropGuard(p.unwrap()); | |
| let p = &mut p.0; | |
| unsafe extern "system" { | |
| unsafe fn ResumeThread(hHandle: BorrowedHandle<'_>) -> u32; | |
| unsafe fn WaitForSingleObject(hHandle: BorrowedHandle<'_>, dwMilliseconds: u32) -> u32; | |
| } | |
| unsafe { | |
| ResumeThread(p.main_thread_handle()); | |
| // Wait until the process exits or 1 minute passes. | |
| // We don't bother checking the result here as that's done below using `try_wait`. | |
| WaitForSingleObject(p.as_handle(), 1000 * 60); | |
| } | |
| let res = p.try_wait(); | |
| assert!(res.is_ok()); | |
| assert!(res.unwrap().is_some()); | |
| assert!(p.try_wait().unwrap().unwrap().success()); | |
| } | |
| #[test] | |
| fn test_make_command_line() { | |
| fn test_wrapper(prog: &str, args: &[&str], force_quotes: bool) -> String { | |
| let command_line = &make_command_line( | |
| OsStr::new(prog), | |
| &args.iter().map(|a| Arg::Regular(OsString::from(a))).collect::<Vec<_>>(), | |
| force_quotes, | |
| ) | |
| .unwrap(); | |
| String::from_utf16(command_line).unwrap() | |
| } | |
| assert_eq!(test_wrapper("prog", &["aaa", "bbb", "ccc"], false), "\"prog\" aaa bbb ccc"); | |
| assert_eq!(test_wrapper("prog", &[r"C:\"], false), r#""prog" C:\"#); | |
| assert_eq!(test_wrapper("prog", &[r"2slashes\\"], false), r#""prog" 2slashes\\"#); | |
| assert_eq!(test_wrapper("prog", &[r" C:\"], false), r#""prog" " C:\\""#); | |
| assert_eq!(test_wrapper("prog", &[r" 2slashes\\"], false), r#""prog" " 2slashes\\\\""#); | |
| assert_eq!( | |
| test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa"], false), | |
| "\"C:\\Program Files\\blah\\blah.exe\" aaa" | |
| ); | |
| assert_eq!( | |
| test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa", "v*"], false), | |
| "\"C:\\Program Files\\blah\\blah.exe\" aaa v*" | |
| ); | |
| assert_eq!( | |
| test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa", "v*"], true), | |
| "\"C:\\Program Files\\blah\\blah.exe\" \"aaa\" \"v*\"" | |
| ); | |
| assert_eq!( | |
| test_wrapper("C:\\Program Files\\test", &["aa\"bb"], false), | |
| "\"C:\\Program Files\\test\" aa\\\"bb" | |
| ); | |
| assert_eq!(test_wrapper("echo", &["a b c"], false), "\"echo\" \"a b c\""); | |
| assert_eq!( | |
| test_wrapper("echo", &["\" \\\" \\", "\\"], false), | |
| "\"echo\" \"\\\" \\\\\\\" \\\\\" \\" | |
| ); | |
| assert_eq!( | |
| test_wrapper("\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}", &[], false), | |
| "\"\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}\"" | |
| ); | |
| } | |
| // On Windows, environment args are case preserving but comparisons are case-insensitive. | |
| // See: #85242 | |
| #[test] | |
| fn windows_env_unicode_case() { | |
| let test_cases = [ | |
| ("ä", "Ä"), | |
| ("ß", "SS"), | |
| ("Ä", "Ö"), | |
| ("Ä", "Ö"), | |
| ("I", "İ"), | |
| ("I", "i"), | |
| ("I", "ı"), | |
| ("i", "I"), | |
| ("i", "İ"), | |
| ("i", "ı"), | |
| ("İ", "I"), | |
| ("İ", "i"), | |
| ("İ", "ı"), | |
| ("ı", "I"), | |
| ("ı", "i"), | |
| ("ı", "İ"), | |
| ("ä", "Ä"), | |
| ("ß", "SS"), | |
| ("Ä", "Ö"), | |
| ("Ä", "Ö"), | |
| ("I", "İ"), | |
| ("I", "i"), | |
| ("I", "ı"), | |
| ("i", "I"), | |
| ("i", "İ"), | |
| ("i", "ı"), | |
| ("İ", "I"), | |
| ("İ", "i"), | |
| ("İ", "ı"), | |
| ("ı", "I"), | |
| ("ı", "i"), | |
| ("ı", "İ"), | |
| ]; | |
| // Test that `cmd.env` matches `env::set_var` when setting two strings that | |
| // may (or may not) be case-folded when compared. | |
| for (a, b) in test_cases.iter() { | |
| let mut cmd = Command::new("cmd"); | |
| cmd.env(a, "1"); | |
| cmd.env(b, "2"); | |
| unsafe { | |
| env::set_var(a, "1"); | |
| env::set_var(b, "2"); | |
| } | |
| for (key, value) in cmd.get_envs() { | |
| assert_eq!( | |
| env::var(key).ok(), | |
| value.map(|s| s.to_string_lossy().into_owned()), | |
| "command environment mismatch: {a} {b}", | |
| ); | |
| } | |
| } | |
| } | |
| // UWP applications run in a restricted environment which means this test may not work. | |
| #[cfg(not(target_vendor = "uwp"))] | |
| #[test] | |
| fn windows_exe_resolver() { | |
| use super::resolve_exe; | |
| use crate::io; | |
| use crate::sys::fs::symlink; | |
| use crate::test_helpers::tmpdir; | |
| let env_paths = || env::var_os("PATH"); | |
| // Test a full path, with and without the `exe` extension. | |
| let mut current_exe = env::current_exe().unwrap(); | |
| assert!(resolve_exe(current_exe.as_ref(), env_paths, None).is_ok()); | |
| current_exe.set_extension(""); | |
| assert!(resolve_exe(current_exe.as_ref(), env_paths, None).is_ok()); | |
| // Test lone file names. | |
| assert!(resolve_exe(OsStr::new("cmd"), env_paths, None).is_ok()); | |
| assert!(resolve_exe(OsStr::new("cmd.exe"), env_paths, None).is_ok()); | |
| assert!(resolve_exe(OsStr::new("cmd.EXE"), env_paths, None).is_ok()); | |
| assert!(resolve_exe(OsStr::new("fc"), env_paths, None).is_ok()); | |
| // Invalid file names should return InvalidInput. | |
| assert_eq!( | |
| resolve_exe(OsStr::new(""), env_paths, None).unwrap_err().kind(), | |
| io::ErrorKind::InvalidInput | |
| ); | |
| assert_eq!( | |
| resolve_exe(OsStr::new("\0"), env_paths, None).unwrap_err().kind(), | |
| io::ErrorKind::InvalidInput | |
| ); | |
| // Trailing slash, therefore there's no file name component. | |
| assert_eq!( | |
| resolve_exe(OsStr::new(r"C:\Path\to\"), env_paths, None).unwrap_err().kind(), | |
| io::ErrorKind::InvalidInput | |
| ); | |
| /* | |
| Some of the following tests may need to be changed if you are deliberately | |
| changing the behavior of `resolve_exe`. | |
| */ | |
| let empty_paths = || None; | |
| // The resolver looks in system directories even when `PATH` is empty. | |
| assert!(resolve_exe(OsStr::new("cmd.exe"), empty_paths, None).is_ok()); | |
| // The application's directory is also searched. | |
| let current_exe = env::current_exe().unwrap(); | |
| assert!(resolve_exe(current_exe.file_name().unwrap().as_ref(), empty_paths, None).is_ok()); | |
| // Create a temporary path and add a broken symlink. | |
| let temp = tmpdir(); | |
| let mut exe_path = temp.path().to_owned(); | |
| exe_path.push("exists.exe"); | |
| // A broken symlink should still be resolved. | |
| // Skip this check if not in CI and creating symlinks isn't possible. | |
| let is_ci = env::var("CI").is_ok(); | |
| let result = symlink("<DOES NOT EXIST>".as_ref(), &exe_path); | |
| if is_ci || result.is_ok() { | |
| result.unwrap(); | |
| assert!( | |
| resolve_exe(OsStr::new("exists.exe"), empty_paths, Some(temp.path().as_ref())).is_ok() | |
| ); | |
| } | |
| } | |
| /// Test the synchronous fallback for overlapped I/O. | |
| /// | |
| /// While technically testing `Handle` functionality, this is situated in this | |
| /// module to allow easier access to `ChildPipe`. | |
| #[test] | |
| fn overlapped_handle_fallback() { | |
| // Create some pipes. `ours` will be asynchronous. | |
| let Pipes { ours, theirs } = child_pipe(true, false).unwrap(); | |
| let async_readable = ours.into_handle(); | |
| let sync_writeable = theirs.into_handle(); | |
| thread::scope(|_| { | |
| thread::sleep(Duration::from_millis(100)); | |
| sync_writeable.write(b"hello world!").unwrap(); | |
| }); | |
| // The pipe buffer starts empty so reading won't complete synchronously unless | |
| // our fallback path works. | |
| let mut buffer = [0u8; 1024]; | |
| async_readable.read(&mut buffer).unwrap(); | |
| } |