| extern crate cargotest; | 
 | extern crate libc; | 
 |  | 
 | use std::fs; | 
 | use std::io::{self, Read}; | 
 | use std::net::TcpListener; | 
 | use std::process::{Stdio, Child}; | 
 | use std::thread; | 
 | use std::time::Duration; | 
 |  | 
 | use cargotest::support::project; | 
 |  | 
 | #[cfg(unix)] | 
 | fn enabled() -> bool { | 
 |     true | 
 | } | 
 |  | 
 | // On Windows suport for these tests is only enabled through the usage of job | 
 | // objects. Support for nested job objects, however, was added in recent-ish | 
 | // versions of Windows, so this test may not always be able to succeed. | 
 | // | 
 | // As a result, we try to add ourselves to a job object here | 
 | // can succeed or not. | 
 | #[cfg(windows)] | 
 | fn enabled() -> bool { | 
 |     extern crate kernel32; | 
 |     extern crate winapi; | 
 |     unsafe { | 
 |         // If we're not currently in a job, then we can definitely run these | 
 |         // tests. | 
 |         let me = kernel32::GetCurrentProcess(); | 
 |         let mut ret = 0; | 
 |         let r = kernel32::IsProcessInJob(me, 0 as *mut _, &mut ret); | 
 |         assert!(r != 0); | 
 |         if ret == winapi::FALSE { | 
 |             return true | 
 |         } | 
 |  | 
 |         // If we are in a job, then we can run these tests if we can be added to | 
 |         // a nested job (as we're going to create a nested job no matter what as | 
 |         // part of these tests. | 
 |         // | 
 |         // If we can't be added to a nested job, then these tests will | 
 |         // definitely fail, and there's not much we can do about that. | 
 |         let job = kernel32::CreateJobObjectW(0 as *mut _, 0 as *const _); | 
 |         assert!(!job.is_null()); | 
 |         let r = kernel32::AssignProcessToJobObject(job, me); | 
 |         kernel32::CloseHandle(job); | 
 |         r != 0 | 
 |     } | 
 | } | 
 |  | 
 | #[test] | 
 | fn ctrl_c_kills_everyone() { | 
 |     if !enabled() { | 
 |         return | 
 |     } | 
 |  | 
 |     let listener = TcpListener::bind("127.0.0.1:0").unwrap(); | 
 |     let addr = listener.local_addr().unwrap(); | 
 |  | 
 |     let p = project("foo") | 
 |         .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)); | 
 |     p.build(); | 
 |  | 
 |     let mut cargo = p.cargo("build").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(Duration::from_millis(100)); | 
 |     } | 
 |  | 
 |     panic!("couldn't remove build directory after a few tries, seems like \ | 
 |             we won't be able to!"); | 
 | } | 
 |  | 
 | #[cfg(unix)] | 
 | fn ctrl_c(child: &mut Child) { | 
 |     use libc; | 
 |  | 
 |     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)] | 
 | fn ctrl_c(child: &mut Child) { | 
 |     child.kill().unwrap(); | 
 | } |