summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/tests/testsuite/death.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/tests/testsuite/death.rs')
-rw-r--r--src/tools/cargo/tests/testsuite/death.rs101
1 files changed, 101 insertions, 0 deletions
diff --git a/src/tools/cargo/tests/testsuite/death.rs b/src/tools/cargo/tests/testsuite/death.rs
new file mode 100644
index 000000000..f0e182d01
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/death.rs
@@ -0,0 +1,101 @@
+//! Tests for ctrl-C handling.
+
+use std::fs;
+use std::io::{self, Read};
+use std::net::TcpListener;
+use std::process::{Child, Stdio};
+use std::thread;
+
+use cargo_test_support::{project, slow_cpu_multiplier};
+
+#[cargo_test]
+fn ctrl_c_kills_everyone() {
+ let listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = listener.local_addr().unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ use std::net::TcpStream;
+ use std::io::Read;
+
+ fn main() {{
+ let mut socket = TcpStream::connect("{}").unwrap();
+ let _ = socket.read(&mut [0; 10]);
+ panic!("that read should never return");
+ }}
+ "#,
+ addr
+ ),
+ )
+ .build();
+
+ let mut cargo = p.cargo("check").build_command();
+ cargo
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .env("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE", "1");
+ let mut child = cargo.spawn().unwrap();
+
+ let mut sock = listener.accept().unwrap().0;
+ ctrl_c(&mut child);
+
+ assert!(!child.wait().unwrap().success());
+ match sock.read(&mut [0; 10]) {
+ Ok(n) => assert_eq!(n, 0),
+ Err(e) => assert_eq!(e.kind(), io::ErrorKind::ConnectionReset),
+ }
+
+ // Ok so what we just did was spawn cargo that spawned a build script, then
+ // we killed cargo in hopes of it killing the build script as well. If all
+ // went well the build script is now dead. On Windows, however, this is
+ // enforced with job objects which means that it may actually be in the
+ // *process* of being torn down at this point.
+ //
+ // Now on Windows we can't completely remove a file until all handles to it
+ // have been closed. Including those that represent running processes. So if
+ // we were to return here then there may still be an open reference to some
+ // file in the build directory. What we want to actually do is wait for the
+ // build script to *complete* exit. Take care of that by blowing away the
+ // build directory here, and panicking if we eventually spin too long
+ // without being able to.
+ for i in 0..10 {
+ match fs::remove_dir_all(&p.root().join("target")) {
+ Ok(()) => return,
+ Err(e) => println!("attempt {}: {}", i, e),
+ }
+ thread::sleep(slow_cpu_multiplier(100));
+ }
+
+ panic!(
+ "couldn't remove build directory after a few tries, seems like \
+ we won't be able to!"
+ );
+}
+
+#[cfg(unix)]
+pub fn ctrl_c(child: &mut Child) {
+ let r = unsafe { libc::kill(-(child.id() as i32), libc::SIGINT) };
+ if r < 0 {
+ panic!("failed to kill: {}", io::Error::last_os_error());
+ }
+}
+
+#[cfg(windows)]
+pub fn ctrl_c(child: &mut Child) {
+ child.kill().unwrap();
+}