Skip to content

Commit 7e1011d

Browse files
committed
Auto merge of #8162 - ehuss:linking-interrupted-flaky, r=alexcrichton
Fix flaky linking_interrupted test. The `linking_interrupted` test is a little flaky, and can randomly fail. On windows-gnu, the last call to `cargo test` was failing because it was hitting a file lock. Presumably this is because the `kill` had not finished? Added a wait to ensure that cargo has exited. Also, sometimes on Windows, the rustc process gets killed. Not sure why that doesn't happen all the time, maybe related to the race mentioned above. Finally, on macOS, I was noticing some errors where I think the rustc process had not yet exited, and deleted the `*.o` files while the next cargo build was running.
2 parents 6a2d62f + 01648b9 commit 7e1011d

File tree

2 files changed

+54
-57
lines changed

2 files changed

+54
-57
lines changed

tests/testsuite/death.rs

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,8 @@ use std::thread;
88

99
use cargo_test_support::{project, slow_cpu_multiplier};
1010

11-
#[cfg(unix)]
12-
fn enabled() -> bool {
13-
true
14-
}
15-
16-
// On Windows support for these tests is only enabled through the usage of job
17-
// objects. Support for nested job objects, however, was added in recent-ish
18-
// versions of Windows, so this test may not always be able to succeed.
19-
//
20-
// As a result, we try to add ourselves to a job object here
21-
// can succeed or not.
22-
#[cfg(windows)]
23-
fn enabled() -> bool {
24-
use winapi::um::{handleapi, jobapi, jobapi2, processthreadsapi};
25-
26-
unsafe {
27-
// If we're not currently in a job, then we can definitely run these
28-
// tests.
29-
let me = processthreadsapi::GetCurrentProcess();
30-
let mut ret = 0;
31-
let r = jobapi::IsProcessInJob(me, 0 as *mut _, &mut ret);
32-
assert_ne!(r, 0);
33-
if ret == ::winapi::shared::minwindef::FALSE {
34-
return true;
35-
}
36-
37-
// If we are in a job, then we can run these tests if we can be added to
38-
// a nested job (as we're going to create a nested job no matter what as
39-
// part of these tests.
40-
//
41-
// If we can't be added to a nested job, then these tests will
42-
// definitely fail, and there's not much we can do about that.
43-
let job = jobapi2::CreateJobObjectW(0 as *mut _, 0 as *const _);
44-
assert!(!job.is_null());
45-
let r = jobapi2::AssignProcessToJobObject(job, me);
46-
handleapi::CloseHandle(job);
47-
r != 0
48-
}
49-
}
50-
5111
#[cargo_test]
5212
fn ctrl_c_kills_everyone() {
53-
if !enabled() {
54-
return;
55-
}
56-
5713
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
5814
let addr = listener.local_addr().unwrap();
5915

@@ -132,14 +88,14 @@ fn ctrl_c_kills_everyone() {
13288
}
13389

13490
#[cfg(unix)]
135-
fn ctrl_c(child: &mut Child) {
91+
pub fn ctrl_c(child: &mut Child) {
13692
let r = unsafe { libc::kill(-(child.id() as i32), libc::SIGINT) };
13793
if r < 0 {
13894
panic!("failed to kill: {}", io::Error::last_os_error());
13995
}
14096
}
14197

14298
#[cfg(windows)]
143-
fn ctrl_c(child: &mut Child) {
99+
pub fn ctrl_c(child: &mut Child) {
144100
child.kill().unwrap();
145101
}

tests/testsuite/freshness.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::process::Stdio;
1010
use std::thread;
1111
use std::time::SystemTime;
1212

13+
use super::death;
1314
use cargo_test_support::paths::{self, CargoPathExt};
1415
use cargo_test_support::registry::Package;
1516
use cargo_test_support::{basic_manifest, is_coarse_mtime, project, rustc_host, sleep_ms};
@@ -2316,8 +2317,14 @@ LLVM version: 9.0
23162317
fn linking_interrupted() {
23172318
// Interrupt during the linking phase shouldn't leave test executable as "fresh".
23182319

2319-
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
2320-
let addr = listener.local_addr().unwrap();
2320+
// This is used to detect when linking starts, then to pause the linker so
2321+
// that the test can kill cargo.
2322+
let link_listener = TcpListener::bind("127.0.0.1:0").unwrap();
2323+
let link_addr = link_listener.local_addr().unwrap();
2324+
2325+
// This is used to detect when rustc exits.
2326+
let rustc_listener = TcpListener::bind("127.0.0.1:0").unwrap();
2327+
let rustc_addr = rustc_listener.local_addr().unwrap();
23212328

23222329
// Create a linker that we can interrupt.
23232330
let linker = project()
@@ -2326,8 +2333,6 @@ fn linking_interrupted() {
23262333
.file(
23272334
"src/main.rs",
23282335
&r#"
2329-
use std::io::Read;
2330-
23312336
fn main() {
23322337
// Figure out the output filename.
23332338
let output = match std::env::args().find(|a| a.starts_with("/OUT:")) {
@@ -2346,43 +2351,79 @@ fn linking_interrupted() {
23462351
std::fs::write(&output, "").unwrap();
23472352
// Tell the test that we are ready to be interrupted.
23482353
let mut socket = std::net::TcpStream::connect("__ADDR__").unwrap();
2349-
// Wait for the test to tell us to exit.
2350-
let _ = socket.read(&mut [0; 1]);
2354+
// Wait for the test to kill us.
2355+
std::thread::sleep(std::time::Duration::new(60, 0));
23512356
}
23522357
"#
2353-
.replace("__ADDR__", &addr.to_string()),
2358+
.replace("__ADDR__", &link_addr.to_string()),
23542359
)
23552360
.build();
23562361
linker.cargo("build").run();
23572362

2363+
// Create a wrapper around rustc that will tell us when rustc is finished.
2364+
let rustc = project()
2365+
.at("rustc-waiter")
2366+
.file("Cargo.toml", &basic_manifest("rustc-waiter", "1.0.0"))
2367+
.file(
2368+
"src/main.rs",
2369+
&r#"
2370+
fn main() {
2371+
let mut conn = None;
2372+
// Check for a normal build (not -vV or --print).
2373+
if std::env::args().any(|arg| arg == "t1") {
2374+
// Tell the test that rustc has started.
2375+
conn = Some(std::net::TcpStream::connect("__ADDR__").unwrap());
2376+
}
2377+
let status = std::process::Command::new("rustc")
2378+
.args(std::env::args().skip(1))
2379+
.status()
2380+
.expect("rustc to run");
2381+
std::process::exit(status.code().unwrap_or(1));
2382+
}
2383+
"#
2384+
.replace("__ADDR__", &rustc_addr.to_string()),
2385+
)
2386+
.build();
2387+
rustc.cargo("build").run();
2388+
23582389
// Build it once so that the fingerprint gets saved to disk.
23592390
let p = project()
23602391
.file("src/lib.rs", "")
23612392
.file("tests/t1.rs", "")
23622393
.build();
23632394
p.cargo("test --test t1 --no-run").run();
2395+
23642396
// Make a change, start a build, then interrupt it.
23652397
p.change_file("src/lib.rs", "// modified");
23662398
let linker_env = format!(
23672399
"CARGO_TARGET_{}_LINKER",
23682400
rustc_host().to_uppercase().replace('-', "_")
23692401
);
2402+
// NOTE: This assumes that the paths to the linker or rustc are not in the
2403+
// fingerprint. But maybe they should be?
23702404
let mut cmd = p
23712405
.cargo("test --test t1 --no-run")
23722406
.env(&linker_env, linker.bin("linker"))
2407+
.env("RUSTC", rustc.bin("rustc-waiter"))
23732408
.build_command();
23742409
let mut child = cmd
23752410
.stdout(Stdio::null())
23762411
.stderr(Stdio::null())
2412+
.env("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE", "1")
23772413
.spawn()
23782414
.unwrap();
2415+
// Wait for rustc to start.
2416+
let mut rustc_conn = rustc_listener.accept().unwrap().0;
23792417
// Wait for linking to start.
2380-
let mut conn = listener.accept().unwrap().0;
2418+
drop(link_listener.accept().unwrap());
23812419

23822420
// Interrupt the child.
2383-
child.kill().unwrap();
2384-
// Note: rustc and the linker are still running, let them exit here.
2385-
conn.write(b"X").unwrap();
2421+
death::ctrl_c(&mut child);
2422+
assert!(!child.wait().unwrap().success());
2423+
// Wait for rustc to exit. If we don't wait, then the command below could
2424+
// start while rustc is still being torn down.
2425+
let mut buf = [0];
2426+
drop(rustc_conn.read_exact(&mut buf));
23862427

23872428
// Build again, shouldn't be fresh.
23882429
p.cargo("test --test t1")

0 commit comments

Comments
 (0)