| //! 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(); |
| } |