Skip to content

Commit 0a33d7c

Browse files
authored
Rollup merge of rust-lang#136682 - ChrisDenton:move-win-proc-tests, r=joboet
Move two windows process tests to tests/ui Spawning processes from std unit tests is not something it's well suited for so moving them into tests/ui is more robust and means we don't need to hack around `cmd.exe`. Follow up to rust-lang#136630
2 parents 3536503 + 6307270 commit 0a33d7c

File tree

3 files changed

+169
-148
lines changed

3 files changed

+169
-148
lines changed

library/std/src/process/tests.rs

-148
Original file line numberDiff line numberDiff line change
@@ -391,154 +391,6 @@ fn test_interior_nul_in_env_value_is_error() {
391391
}
392392
}
393393

394-
/// Tests that process creation flags work by debugging a process.
395-
/// Other creation flags make it hard or impossible to detect
396-
/// behavioral changes in the process.
397-
#[test]
398-
#[cfg(windows)]
399-
fn test_creation_flags() {
400-
use crate::os::windows::process::CommandExt;
401-
use crate::sys::c::{BOOL, INFINITE};
402-
#[repr(C)]
403-
struct DEBUG_EVENT {
404-
pub event_code: u32,
405-
pub process_id: u32,
406-
pub thread_id: u32,
407-
// This is a union in the real struct, but we don't
408-
// need this data for the purposes of this test.
409-
pub _junk: [u8; 164],
410-
}
411-
412-
extern "system" {
413-
fn WaitForDebugEvent(lpDebugEvent: *mut DEBUG_EVENT, dwMilliseconds: u32) -> BOOL;
414-
fn ContinueDebugEvent(dwProcessId: u32, dwThreadId: u32, dwContinueStatus: u32) -> BOOL;
415-
}
416-
417-
const DEBUG_PROCESS: u32 = 1;
418-
const EXIT_PROCESS_DEBUG_EVENT: u32 = 5;
419-
const DBG_EXCEPTION_NOT_HANDLED: u32 = 0x80010001;
420-
421-
let mut child = Command::new("cmd")
422-
.creation_flags(DEBUG_PROCESS)
423-
.stdin(Stdio::piped())
424-
.stdout(Stdio::null())
425-
.stderr(Stdio::null())
426-
.spawn()
427-
.unwrap();
428-
child.stdin.take().unwrap().write_all(b"exit\r\n").unwrap();
429-
let mut events = 0;
430-
let mut event = DEBUG_EVENT { event_code: 0, process_id: 0, thread_id: 0, _junk: [0; 164] };
431-
loop {
432-
if unsafe { WaitForDebugEvent(&mut event as *mut DEBUG_EVENT, INFINITE) } == 0 {
433-
panic!("WaitForDebugEvent failed!");
434-
}
435-
events += 1;
436-
437-
if event.event_code == EXIT_PROCESS_DEBUG_EVENT {
438-
break;
439-
}
440-
441-
if unsafe {
442-
ContinueDebugEvent(event.process_id, event.thread_id, DBG_EXCEPTION_NOT_HANDLED)
443-
} == 0
444-
{
445-
panic!("ContinueDebugEvent failed!");
446-
}
447-
}
448-
assert!(events > 0);
449-
}
450-
451-
/// Tests proc thread attributes by spawning a process with a custom parent process,
452-
/// then comparing the parent process ID with the expected parent process ID.
453-
#[test]
454-
#[cfg(windows)]
455-
fn test_proc_thread_attributes() {
456-
use crate::mem;
457-
use crate::os::windows::io::AsRawHandle;
458-
use crate::os::windows::process::{CommandExt, ProcThreadAttributeList};
459-
use crate::sys::c::{BOOL, CloseHandle, HANDLE};
460-
use crate::sys::cvt;
461-
462-
#[repr(C)]
463-
#[allow(non_snake_case)]
464-
struct PROCESSENTRY32W {
465-
dwSize: u32,
466-
cntUsage: u32,
467-
th32ProcessID: u32,
468-
th32DefaultHeapID: usize,
469-
th32ModuleID: u32,
470-
cntThreads: u32,
471-
th32ParentProcessID: u32,
472-
pcPriClassBase: i32,
473-
dwFlags: u32,
474-
szExeFile: [u16; 260],
475-
}
476-
477-
extern "system" {
478-
fn CreateToolhelp32Snapshot(dwflags: u32, th32processid: u32) -> HANDLE;
479-
fn Process32First(hsnapshot: HANDLE, lppe: *mut PROCESSENTRY32W) -> BOOL;
480-
fn Process32Next(hsnapshot: HANDLE, lppe: *mut PROCESSENTRY32W) -> BOOL;
481-
}
482-
483-
const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: usize = 0x00020000;
484-
const TH32CS_SNAPPROCESS: u32 = 0x00000002;
485-
486-
struct ProcessDropGuard(crate::process::Child);
487-
488-
impl Drop for ProcessDropGuard {
489-
fn drop(&mut self) {
490-
let _ = self.0.kill();
491-
}
492-
}
493-
494-
let mut parent = Command::new("cmd");
495-
parent.stdout(Stdio::null()).stderr(Stdio::null());
496-
497-
let parent = ProcessDropGuard(parent.spawn().unwrap());
498-
499-
let mut child_cmd = Command::new("cmd");
500-
child_cmd.stdout(Stdio::null()).stderr(Stdio::null());
501-
502-
let parent_process_handle = parent.0.as_raw_handle();
503-
504-
let mut attribute_list = ProcThreadAttributeList::build()
505-
.attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_process_handle)
506-
.finish()
507-
.unwrap();
508-
509-
let child = ProcessDropGuard(child_cmd.spawn_with_attributes(&mut attribute_list).unwrap());
510-
511-
let h_snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
512-
513-
let mut process_entry = PROCESSENTRY32W {
514-
dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
515-
cntUsage: 0,
516-
th32ProcessID: 0,
517-
th32DefaultHeapID: 0,
518-
th32ModuleID: 0,
519-
cntThreads: 0,
520-
th32ParentProcessID: 0,
521-
pcPriClassBase: 0,
522-
dwFlags: 0,
523-
szExeFile: [0; 260],
524-
};
525-
526-
unsafe { cvt(Process32First(h_snapshot, &mut process_entry as *mut _)) }.unwrap();
527-
528-
loop {
529-
if child.0.id() == process_entry.th32ProcessID {
530-
break;
531-
}
532-
unsafe { cvt(Process32Next(h_snapshot, &mut process_entry as *mut _)) }.unwrap();
533-
}
534-
535-
unsafe { cvt(CloseHandle(h_snapshot)) }.unwrap();
536-
537-
assert_eq!(parent.0.id(), process_entry.th32ParentProcessID);
538-
539-
drop(child)
540-
}
541-
542394
#[test]
543395
fn test_command_implements_send_sync() {
544396
fn take_send_sync_type<T: Send + Sync>(_: T) {}
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Test that windows `creation_flags` extension to `Command` works.
2+
3+
//@ run-pass
4+
//@ only-windows
5+
//@ needs-subprocess
6+
7+
use std::env;
8+
use std::os::windows::process::CommandExt;
9+
use std::process::{Command, exit};
10+
11+
fn main() {
12+
if env::args().skip(1).any(|s| s == "--child") {
13+
child();
14+
} else {
15+
parent();
16+
}
17+
}
18+
19+
fn parent() {
20+
let exe = env::current_exe().unwrap();
21+
22+
// Use the DETACH_PROCESS to create a subprocess that isn't attached to the console.
23+
// The subprocess's exit status will be 0 if it's detached.
24+
let status = Command::new(&exe)
25+
.arg("--child")
26+
.creation_flags(DETACH_PROCESS)
27+
.spawn()
28+
.unwrap()
29+
.wait()
30+
.unwrap();
31+
assert_eq!(status.code(), Some(0));
32+
33+
// Try without DETACH_PROCESS to ensure this test works.
34+
let status = Command::new(&exe).arg("--child").spawn().unwrap().wait().unwrap();
35+
assert_eq!(status.code(), Some(1));
36+
}
37+
38+
// exits with 1 if the console is attached or 0 otherwise
39+
fn child() {
40+
// Get the attached console's code page.
41+
// This will fail (return 0) if no console is attached.
42+
let has_console = GetConsoleCP() != 0;
43+
exit(has_console as i32);
44+
}
45+
46+
// Windows API definitions.
47+
const DETACH_PROCESS: u32 = 0x00000008;
48+
#[link(name = "kernel32")]
49+
unsafe extern "system" {
50+
safe fn GetConsoleCP() -> u32;
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Tests proc thread attributes by spawning a process with a custom parent process,
2+
// then comparing the parent process ID with the expected parent process ID.
3+
4+
//@ run-pass
5+
//@ only-windows
6+
//@ needs-subprocess
7+
//@ edition: 2021
8+
9+
#![feature(windows_process_extensions_raw_attribute)]
10+
11+
use std::os::windows::io::AsRawHandle;
12+
use std::os::windows::process::{CommandExt, ProcThreadAttributeList};
13+
use std::process::{Child, Command};
14+
use std::{env, mem, ptr, thread, time};
15+
16+
// Make a best effort to ensure child processes always exit.
17+
struct ProcessDropGuard(Child);
18+
impl Drop for ProcessDropGuard {
19+
fn drop(&mut self) {
20+
let _ = self.0.kill();
21+
}
22+
}
23+
24+
fn main() {
25+
if env::args().skip(1).any(|s| s == "--child") {
26+
child();
27+
} else {
28+
parent();
29+
}
30+
}
31+
32+
fn parent() {
33+
let exe = env::current_exe().unwrap();
34+
35+
let (fake_parent_id, child_parent_id) = {
36+
// Create a process to be our fake parent process.
37+
let fake_parent = Command::new(&exe).arg("--child").spawn().unwrap();
38+
let fake_parent = ProcessDropGuard(fake_parent);
39+
let parent_handle = fake_parent.0.as_raw_handle();
40+
41+
// Create another process with the parent process set to the fake.
42+
let mut attribute_list = ProcThreadAttributeList::build()
43+
.attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_handle)
44+
.finish()
45+
.unwrap();
46+
let child =
47+
Command::new(&exe).arg("--child").spawn_with_attributes(&mut attribute_list).unwrap();
48+
let child = ProcessDropGuard(child);
49+
50+
// Return the fake's process id and the child's parent's id.
51+
(process_info(&fake_parent.0).process_id(), process_info(&child.0).parent_id())
52+
};
53+
54+
assert_eq!(fake_parent_id, child_parent_id);
55+
}
56+
57+
// A process that stays running until killed.
58+
fn child() {
59+
// Don't wait forever if something goes wrong.
60+
thread::sleep(time::Duration::from_secs(60));
61+
}
62+
63+
fn process_info(child: &Child) -> PROCESS_BASIC_INFORMATION {
64+
unsafe {
65+
let mut info: PROCESS_BASIC_INFORMATION = mem::zeroed();
66+
let result = NtQueryInformationProcess(
67+
child.as_raw_handle(),
68+
ProcessBasicInformation,
69+
ptr::from_mut(&mut info).cast(),
70+
mem::size_of_val(&info).try_into().unwrap(),
71+
ptr::null_mut(),
72+
);
73+
assert_eq!(result, 0);
74+
info
75+
}
76+
}
77+
78+
// Windows API
79+
mod winapi {
80+
#![allow(nonstandard_style)]
81+
use std::ffi::c_void;
82+
83+
pub type HANDLE = *mut c_void;
84+
type NTSTATUS = i32;
85+
type PROCESSINFOCLASS = i32;
86+
87+
pub const ProcessBasicInformation: i32 = 0;
88+
pub const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: usize = 0x00020000;
89+
#[repr(C)]
90+
pub struct PROCESS_BASIC_INFORMATION {
91+
pub ExitStatus: NTSTATUS,
92+
pub PebBaseAddress: *mut (),
93+
pub AffinityMask: usize,
94+
pub BasePriority: i32,
95+
pub UniqueProcessId: usize,
96+
pub InheritedFromUniqueProcessId: usize,
97+
}
98+
impl PROCESS_BASIC_INFORMATION {
99+
pub fn parent_id(&self) -> usize {
100+
self.InheritedFromUniqueProcessId
101+
}
102+
pub fn process_id(&self) -> usize {
103+
self.UniqueProcessId
104+
}
105+
}
106+
107+
#[link(name = "ntdll")]
108+
extern "system" {
109+
pub fn NtQueryInformationProcess(
110+
ProcessHandle: HANDLE,
111+
ProcessInformationClass: PROCESSINFOCLASS,
112+
ProcessInformation: *mut c_void,
113+
ProcessInformationLength: u32,
114+
ReturnLength: *mut u32,
115+
) -> NTSTATUS;
116+
}
117+
}
118+
use winapi::*;

0 commit comments

Comments
 (0)