]> git.proxmox.com Git - rustc.git/blob - src/tools/cargo/tests/testsuite/death.rs
New upstream version 1.70.0+dfsg2
[rustc.git] / src / tools / cargo / tests / testsuite / death.rs
1 //! Tests for ctrl-C handling.
2
3 use std::fs;
4 use std::io::{self, Read};
5 use std::net::TcpListener;
6 use std::process::{Child, Stdio};
7 use std::thread;
8
9 use cargo_test_support::{project, slow_cpu_multiplier};
10
11 #[cargo_test]
12 fn ctrl_c_kills_everyone() {
13 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
14 let addr = listener.local_addr().unwrap();
15
16 let p = project()
17 .file(
18 "Cargo.toml",
19 r#"
20 [package]
21 name = "foo"
22 version = "0.0.1"
23 authors = []
24 build = "build.rs"
25 "#,
26 )
27 .file("src/lib.rs", "")
28 .file(
29 "build.rs",
30 &format!(
31 r#"
32 use std::net::TcpStream;
33 use std::io::Read;
34
35 fn main() {{
36 let mut socket = TcpStream::connect("{}").unwrap();
37 let _ = socket.read(&mut [0; 10]);
38 panic!("that read should never return");
39 }}
40 "#,
41 addr
42 ),
43 )
44 .build();
45
46 let mut cargo = p.cargo("check").build_command();
47 cargo
48 .stdin(Stdio::piped())
49 .stdout(Stdio::piped())
50 .stderr(Stdio::piped())
51 .env("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE", "1");
52 let mut child = cargo.spawn().unwrap();
53
54 let mut sock = listener.accept().unwrap().0;
55 ctrl_c(&mut child);
56
57 assert!(!child.wait().unwrap().success());
58 match sock.read(&mut [0; 10]) {
59 Ok(n) => assert_eq!(n, 0),
60 Err(e) => assert_eq!(e.kind(), io::ErrorKind::ConnectionReset),
61 }
62
63 // Ok so what we just did was spawn cargo that spawned a build script, then
64 // we killed cargo in hopes of it killing the build script as well. If all
65 // went well the build script is now dead. On Windows, however, this is
66 // enforced with job objects which means that it may actually be in the
67 // *process* of being torn down at this point.
68 //
69 // Now on Windows we can't completely remove a file until all handles to it
70 // have been closed. Including those that represent running processes. So if
71 // we were to return here then there may still be an open reference to some
72 // file in the build directory. What we want to actually do is wait for the
73 // build script to *complete* exit. Take care of that by blowing away the
74 // build directory here, and panicking if we eventually spin too long
75 // without being able to.
76 for i in 0..10 {
77 match fs::remove_dir_all(&p.root().join("target")) {
78 Ok(()) => return,
79 Err(e) => println!("attempt {}: {}", i, e),
80 }
81 thread::sleep(slow_cpu_multiplier(100));
82 }
83
84 panic!(
85 "couldn't remove build directory after a few tries, seems like \
86 we won't be able to!"
87 );
88 }
89
90 #[cfg(unix)]
91 pub fn ctrl_c(child: &mut Child) {
92 let r = unsafe { libc::kill(-(child.id() as i32), libc::SIGINT) };
93 if r < 0 {
94 panic!("failed to kill: {}", io::Error::last_os_error());
95 }
96 }
97
98 #[cfg(windows)]
99 pub fn ctrl_c(child: &mut Child) {
100 child.kill().unwrap();
101 }