Skip to content

Commit

Permalink
Add a wrapper to wait for rustc to exit.
Browse files Browse the repository at this point in the history
  • Loading branch information
ehuss committed Apr 27, 2020
1 parent ca085e9 commit 01648b9
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 28 deletions.
4 changes: 2 additions & 2 deletions tests/testsuite/death.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ 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());
}
}

#[cfg(windows)]
fn ctrl_c(child: &mut Child) {
pub fn ctrl_c(child: &mut Child) {
child.kill().unwrap();
}
76 changes: 50 additions & 26 deletions tests/testsuite/freshness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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()
Expand All @@ -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:")) {
Expand All @@ -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")
Expand Down

0 comments on commit 01648b9

Please sign in to comment.