diff --git a/tests/testsuite/death.rs b/tests/testsuite/death.rs index eb8d57219..381088fbb 100644 --- a/tests/testsuite/death.rs +++ b/tests/testsuite/death.rs @@ -88,7 +88,7 @@ fn ctrl_c_kills_everyone() { } #[cfg(unix)] -fn ctrl_c(child: &mut Child) { +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()); @@ -96,6 +96,6 @@ fn ctrl_c(child: &mut Child) { } #[cfg(windows)] -fn ctrl_c(child: &mut Child) { +pub fn ctrl_c(child: &mut Child) { child.kill().unwrap(); } diff --git a/tests/testsuite/freshness.rs b/tests/testsuite/freshness.rs index 4cc901717..62059734c 100644 --- a/tests/testsuite/freshness.rs +++ b/tests/testsuite/freshness.rs @@ -10,6 +10,7 @@ use std::process::Stdio; use std::thread; use std::time::SystemTime; +use super::death; use cargo_test_support::paths::{self, CargoPathExt}; use cargo_test_support::registry::Package; use cargo_test_support::{basic_manifest, is_coarse_mtime, project, rustc_host, sleep_ms}; @@ -2318,8 +2319,12 @@ fn linking_interrupted() { // This is used to detect when linking starts, then to pause the linker so // that the test can kill cargo. - let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - let addr = listener.local_addr().unwrap(); + let link_listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let link_addr = link_listener.local_addr().unwrap(); + + // This is used to detect when rustc exits. + let rustc_listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let rustc_addr = rustc_listener.local_addr().unwrap(); // Create a linker that we can interrupt. let linker = project() @@ -2328,8 +2333,6 @@ fn linking_interrupted() { .file( "src/main.rs", &r#" - use std::io::Read; - fn main() { // Figure out the output filename. let output = match std::env::args().find(|a| a.starts_with("/OUT:")) { @@ -2348,58 +2351,79 @@ fn linking_interrupted() { std::fs::write(&output, "").unwrap(); // Tell the test that we are ready to be interrupted. let mut socket = std::net::TcpStream::connect("__ADDR__").unwrap(); - // Wait for the test to tell us to exit. - let _ = socket.read(&mut [0; 1]); + // Wait for the test to kill us. + std::thread::sleep(std::time::Duration::new(60, 0)); } "# - .replace("__ADDR__", &addr.to_string()), + .replace("__ADDR__", &link_addr.to_string()), ) .build(); linker.cargo("build").run(); + // Create a wrapper around rustc that will tell us when rustc is finished. + let rustc = project() + .at("rustc-waiter") + .file("Cargo.toml", &basic_manifest("rustc-waiter", "1.0.0")) + .file( + "src/main.rs", + &r#" + fn main() { + let mut conn = None; + // Check for a normal build (not -vV or --print). + if std::env::args().any(|arg| arg == "t1") { + // Tell the test that rustc has started. + conn = Some(std::net::TcpStream::connect("__ADDR__").unwrap()); + } + let status = std::process::Command::new("rustc") + .args(std::env::args().skip(1)) + .status() + .expect("rustc to run"); + std::process::exit(status.code().unwrap_or(1)); + } + "# + .replace("__ADDR__", &rustc_addr.to_string()), + ) + .build(); + rustc.cargo("build").run(); + // Build it once so that the fingerprint gets saved to disk. let p = project() .file("src/lib.rs", "") .file("tests/t1.rs", "") .build(); p.cargo("test --test t1 --no-run").run(); + // Make a change, start a build, then interrupt it. p.change_file("src/lib.rs", "// modified"); let linker_env = format!( "CARGO_TARGET_{}_LINKER", rustc_host().to_uppercase().replace('-', "_") ); - // NOTE: This assumes that the path to the linker is not in the - // fingerprint. But maybe it should be? + // NOTE: This assumes that the paths to the linker or rustc are not in the + // fingerprint. But maybe they should be? let mut cmd = p .cargo("test --test t1 --no-run") .env(&linker_env, linker.bin("linker")) + .env("RUSTC", rustc.bin("rustc-waiter")) .build_command(); let mut child = cmd .stdout(Stdio::null()) .stderr(Stdio::null()) + .env("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE", "1") .spawn() .unwrap(); + // Wait for rustc to start. + let mut rustc_conn = rustc_listener.accept().unwrap().0; // Wait for linking to start. - let mut conn = listener.accept().unwrap().0; + drop(link_listener.accept().unwrap()); // Interrupt the child. - child.kill().unwrap(); - child.wait().unwrap(); - // Note: rustc and the linker may still be running because we didn't kill - // the entire process group. Normally, when a user hits Ctrl-C, everything - // is killed. However, setting up process groups in a cross-platform test - // is a pain, and there's no easy way to know when everything has been - // killed. This write will tell them to exit, pretending that they died - // before finishing. Ignore the error, because (sometimes?) on Windows - // everything is already killed. - let _ = conn.write(b"X"); - // Sleep a bit to allow rustc to clean up and exit. I have seen some race - // conditions on macOS where clang dies with `no such - // file...t1-HASH.rcgu.o`. I think what is happening is that the old rustc - // process is still running, and deletes the `*.o` files while the command - // below is trying to write them. Not sure if that is macOS-specific. - std::thread::sleep(std::time::Duration::new(2, 0)); + death::ctrl_c(&mut child); + assert!(!child.wait().unwrap().success()); + // Wait for rustc to exit. If we don't wait, then the command below could + // start while rustc is still being torn down. + let mut buf = [0]; + drop(rustc_conn.read_exact(&mut buf)); // Build again, shouldn't be fresh. p.cargo("test --test t1")