mirror of
https://github.com/rust-lang/cargo
synced 2024-10-31 07:46:57 +00:00
250 lines
7.4 KiB
Rust
250 lines
7.4 KiB
Rust
//! Tests for ctrl-C handling.
|
|
|
|
use cargo_test_support::{project, slow_cpu_multiplier};
|
|
use std::fs;
|
|
use std::io::{self, Read};
|
|
use std::net::TcpListener;
|
|
use std::process::{Child, Stdio};
|
|
use std::thread;
|
|
use std::time;
|
|
|
|
#[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!"
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn kill_cargo_add_never_corrupts_cargo_toml() {
|
|
cargo_test_support::registry::init();
|
|
cargo_test_support::registry::Package::new("my-package", "0.1.1+my-package").publish();
|
|
|
|
let with_dependency = r#"
|
|
[package]
|
|
name = "foo"
|
|
version = "0.0.1"
|
|
authors = []
|
|
|
|
[dependencies]
|
|
my-package = "0.1.1"
|
|
"#;
|
|
let without_dependency = r#"
|
|
[package]
|
|
name = "foo"
|
|
version = "0.0.1"
|
|
authors = []
|
|
"#;
|
|
|
|
for sleep_time_ms in [30, 60, 90] {
|
|
let p = project()
|
|
.file("Cargo.toml", without_dependency)
|
|
.file("src/lib.rs", "")
|
|
.build();
|
|
|
|
let mut cargo = p.cargo("add").arg("my-package").build_command();
|
|
cargo
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
|
|
let mut child = cargo.spawn().unwrap();
|
|
|
|
thread::sleep(time::Duration::from_millis(sleep_time_ms));
|
|
|
|
assert!(child.kill().is_ok());
|
|
assert!(child.wait().is_ok());
|
|
|
|
// check the Cargo.toml
|
|
let contents = fs::read(p.root().join("Cargo.toml")).unwrap();
|
|
|
|
// not empty
|
|
assert_ne!(
|
|
contents, b"",
|
|
"Cargo.toml is empty, and should not be at {} milliseconds",
|
|
sleep_time_ms
|
|
);
|
|
|
|
// We should have the original Cargo.toml or the new one, nothing else.
|
|
if std::str::from_utf8(&contents)
|
|
.unwrap()
|
|
.contains("[dependencies]")
|
|
{
|
|
assert_eq!(
|
|
std::str::from_utf8(&contents).unwrap(),
|
|
with_dependency,
|
|
"Cargo.toml is with_dependency after add at {} milliseconds",
|
|
sleep_time_ms
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
std::str::from_utf8(&contents).unwrap(),
|
|
without_dependency,
|
|
"Cargo.toml is without_dependency after add at {} milliseconds",
|
|
sleep_time_ms
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn kill_cargo_remove_never_corrupts_cargo_toml() {
|
|
let with_dependency = r#"
|
|
[package]
|
|
name = "foo"
|
|
version = "0.0.1"
|
|
authors = []
|
|
build = "build.rs"
|
|
|
|
[dependencies]
|
|
bar = "0.0.1"
|
|
"#;
|
|
let without_dependency = r#"
|
|
[package]
|
|
name = "foo"
|
|
version = "0.0.1"
|
|
authors = []
|
|
build = "build.rs"
|
|
"#;
|
|
|
|
// This test depends on killing the cargo process at the right time to cause a failed write.
|
|
// Note that we're iterating and using the index as time in ms to sleep before killing the cargo process.
|
|
// If it is working correctly, we never fail, but can't hang out here all day...
|
|
// So we'll just run it a few times and hope for the best.
|
|
for sleep_time_ms in [30, 60, 90] {
|
|
// new basic project with a single dependency
|
|
let p = project()
|
|
.file("Cargo.toml", with_dependency)
|
|
.file("src/lib.rs", "")
|
|
.build();
|
|
|
|
// run cargo remove the dependency
|
|
let mut cargo = p.cargo("remove").arg("bar").build_command();
|
|
cargo
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
|
|
let mut child = cargo.spawn().unwrap();
|
|
|
|
thread::sleep(time::Duration::from_millis(sleep_time_ms));
|
|
|
|
assert!(child.kill().is_ok());
|
|
assert!(child.wait().is_ok());
|
|
|
|
// check the Cargo.toml
|
|
let contents = fs::read(p.root().join("Cargo.toml")).unwrap();
|
|
|
|
// not empty
|
|
assert_ne!(
|
|
contents, b"",
|
|
"Cargo.toml is empty, and should not be at {} milliseconds",
|
|
sleep_time_ms
|
|
);
|
|
|
|
// We should have the original Cargo.toml or the new one, nothing else.
|
|
if std::str::from_utf8(&contents)
|
|
.unwrap()
|
|
.contains("[dependencies]")
|
|
{
|
|
assert_eq!(
|
|
std::str::from_utf8(&contents).unwrap(),
|
|
with_dependency,
|
|
"Cargo.toml is not the same as the original at {} milliseconds",
|
|
sleep_time_ms
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
std::str::from_utf8(&contents).unwrap(),
|
|
without_dependency,
|
|
"Cargo.toml is not the same as expected at {} milliseconds",
|
|
sleep_time_ms
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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();
|
|
}
|