//! Tests for running multiple `cargo` processes at the same time. use std::fs; use std::net::TcpListener; use std::process::Stdio; use std::sync::mpsc::channel; use std::thread; use std::{env, str}; use cargo_test_support::cargo_process; use cargo_test_support::git; use cargo_test_support::install::{assert_has_installed_exe, cargo_home}; use cargo_test_support::registry::Package; use cargo_test_support::{basic_manifest, execs, project, slow_cpu_multiplier}; fn pkg(name: &str, vers: &str) { Package::new(name, vers) .file("src/main.rs", "fn main() {{}}") .publish(); } #[cargo_test] fn multiple_installs() { let p = project() .no_manifest() .file("a/Cargo.toml", &basic_manifest("foo", "0.0.0")) .file("a/src/main.rs", "fn main() {}") .file("b/Cargo.toml", &basic_manifest("bar", "0.0.0")) .file("b/src/main.rs", "fn main() {}"); let p = p.build(); let mut a = p.cargo("install").cwd("a").build_command(); let mut b = p.cargo("install").cwd("b").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); let a = a.spawn().unwrap(); let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); execs().run_output(&a); execs().run_output(&b); assert_has_installed_exe(cargo_home(), "foo"); assert_has_installed_exe(cargo_home(), "bar"); } #[cargo_test] fn concurrent_installs() { const LOCKED_BUILD: &str = "waiting for file lock on build directory"; pkg("foo", "0.0.1"); pkg("bar", "0.0.1"); let mut a = cargo_process("install foo").build_command(); let mut b = cargo_process("install bar").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); let a = a.spawn().unwrap(); let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); assert!(!str::from_utf8(&a.stderr).unwrap().contains(LOCKED_BUILD)); assert!(!str::from_utf8(&b.stderr).unwrap().contains(LOCKED_BUILD)); execs().run_output(&a); execs().run_output(&b); assert_has_installed_exe(cargo_home(), "foo"); assert_has_installed_exe(cargo_home(), "bar"); } #[cargo_test] fn one_install_should_be_bad() { let p = project() .no_manifest() .file("a/Cargo.toml", &basic_manifest("foo", "0.0.0")) .file("a/src/main.rs", "fn main() {}") .file("b/Cargo.toml", &basic_manifest("foo", "0.0.0")) .file("b/src/main.rs", "fn main() {}"); let p = p.build(); let mut a = p.cargo("install").cwd("a").build_command(); let mut b = p.cargo("install").cwd("b").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); let a = a.spawn().unwrap(); let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); execs().run_output(&a); execs().run_output(&b); assert_has_installed_exe(cargo_home(), "foo"); } #[cargo_test] fn multiple_registry_fetches() { let mut pkg = Package::new("bar", "1.0.2"); for i in 0..10 { let name = format!("foo{}", i); Package::new(&name, "1.0.0").publish(); pkg.dep(&name, "*"); } pkg.publish(); let p = project() .no_manifest() .file( "a/Cargo.toml", r#" [package] name = "foo" authors = [] version = "0.0.0" [dependencies] bar = "*" "#, ) .file("a/src/main.rs", "fn main() {}") .file( "b/Cargo.toml", r#" [package] name = "bar" authors = [] version = "0.0.0" [dependencies] bar = "*" "#, ) .file("b/src/main.rs", "fn main() {}"); let p = p.build(); let mut a = p.cargo("build").cwd("a").build_command(); let mut b = p.cargo("build").cwd("b").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); let a = a.spawn().unwrap(); let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); execs().run_output(&a); execs().run_output(&b); let suffix = env::consts::EXE_SUFFIX; assert!(p .root() .join("a/target/debug") .join(format!("foo{}", suffix)) .is_file()); assert!(p .root() .join("b/target/debug") .join(format!("bar{}", suffix)) .is_file()); } #[cargo_test] fn git_same_repo_different_tags() { let a = git::new("dep", |project| { project .file("Cargo.toml", &basic_manifest("dep", "0.5.0")) .file("src/lib.rs", "pub fn tag1() {}") }); let repo = git2::Repository::open(&a.root()).unwrap(); git::tag(&repo, "tag1"); a.change_file("src/lib.rs", "pub fn tag2() {}"); git::add(&repo); git::commit(&repo); git::tag(&repo, "tag2"); let p = project() .no_manifest() .file( "a/Cargo.toml", &format!( r#" [package] name = "foo" authors = [] version = "0.0.0" [dependencies] dep = {{ git = '{}', tag = 'tag1' }} "#, a.url() ), ) .file( "a/src/main.rs", "extern crate dep; fn main() { dep::tag1(); }", ) .file( "b/Cargo.toml", &format!( r#" [package] name = "bar" authors = [] version = "0.0.0" [dependencies] dep = {{ git = '{}', tag = 'tag2' }} "#, a.url() ), ) .file( "b/src/main.rs", "extern crate dep; fn main() { dep::tag2(); }", ); let p = p.build(); let mut a = p.cargo("build -v").cwd("a").build_command(); let mut b = p.cargo("build -v").cwd("b").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); let a = a.spawn().unwrap(); let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); execs().run_output(&a); execs().run_output(&b); } #[cargo_test] fn git_same_branch_different_revs() { let a = git::new("dep", |project| { project .file("Cargo.toml", &basic_manifest("dep", "0.5.0")) .file("src/lib.rs", "pub fn f1() {}") }); let p = project() .no_manifest() .file( "a/Cargo.toml", &format!( r#" [package] name = "foo" authors = [] version = "0.0.0" [dependencies] dep = {{ git = '{}' }} "#, a.url() ), ) .file( "a/src/main.rs", "extern crate dep; fn main() { dep::f1(); }", ) .file( "b/Cargo.toml", &format!( r#" [package] name = "bar" authors = [] version = "0.0.0" [dependencies] dep = {{ git = '{}' }} "#, a.url() ), ) .file( "b/src/main.rs", "extern crate dep; fn main() { dep::f2(); }", ); let p = p.build(); // Generate a Cargo.lock pointing at the current rev, then clear out the // target directory p.cargo("build").cwd("a").run(); fs::remove_dir_all(p.root().join("a/target")).unwrap(); // Make a new commit on the master branch let repo = git2::Repository::open(&a.root()).unwrap(); a.change_file("src/lib.rs", "pub fn f2() {}"); git::add(&repo); git::commit(&repo); // Now run both builds in parallel. The build of `b` should pick up the // newest commit while the build of `a` should use the locked old commit. let mut a = p.cargo("build").cwd("a").build_command(); let mut b = p.cargo("build").cwd("b").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); let a = a.spawn().unwrap(); let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); execs().run_output(&a); execs().run_output(&b); } #[cargo_test] fn same_project() { let p = project() .file("src/main.rs", "fn main() {}") .file("src/lib.rs", ""); let p = p.build(); let mut a = p.cargo("build").build_command(); let mut b = p.cargo("build").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); let a = a.spawn().unwrap(); let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); execs().run_output(&a); execs().run_output(&b); } // Make sure that if Cargo dies while holding a lock that it's released and the // next Cargo to come in will take over cleanly. #[cargo_test] fn killing_cargo_releases_the_lock() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" authors = [] version = "0.0.0" build = "build.rs" "#, ) .file("src/main.rs", "fn main() {}") .file( "build.rs", r#" use std::net::TcpStream; fn main() { if std::env::var("A").is_ok() { TcpStream::connect(&std::env::var("ADDR").unwrap()[..]) .unwrap(); std::thread::sleep(std::time::Duration::new(10, 0)); } } "#, ); let p = p.build(); // Our build script will connect to our local TCP socket to inform us that // it's started and that's how we know that `a` will have the lock // when we kill it. let l = TcpListener::bind("127.0.0.1:0").unwrap(); let mut a = p.cargo("build").build_command(); let mut b = p.cargo("build").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); a.env("ADDR", l.local_addr().unwrap().to_string()) .env("A", "a"); b.env("ADDR", l.local_addr().unwrap().to_string()) .env_remove("A"); // Spawn `a`, wait for it to get to the build script (at which point the // lock is held), then kill it. let mut a = a.spawn().unwrap(); l.accept().unwrap(); a.kill().unwrap(); // Spawn `b`, then just finish the output of a/b the same way the above // tests does. let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); // We killed `a`, so it shouldn't succeed, but `b` should have succeeded. assert!(!a.status.success()); execs().run_output(&b); } #[cargo_test] fn debug_release_ok() { let p = project().file("src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build").run(); fs::remove_dir_all(p.root().join("target")).unwrap(); let mut a = p.cargo("build").build_command(); let mut b = p.cargo("build --release").build_command(); a.stdout(Stdio::piped()).stderr(Stdio::piped()); b.stdout(Stdio::piped()).stderr(Stdio::piped()); let a = a.spawn().unwrap(); let b = b.spawn().unwrap(); let a = thread::spawn(move || a.wait_with_output().unwrap()); let b = b.wait_with_output().unwrap(); let a = a.join().unwrap(); execs() .with_stderr_contains( "\ [COMPILING] foo v0.0.1 [..] [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run_output(&a); execs() .with_stderr_contains( "\ [COMPILING] foo v0.0.1 [..] [FINISHED] release [optimized] target(s) in [..] ", ) .run_output(&b); } #[cargo_test] fn no_deadlock_with_git_dependencies() { let dep1 = git::new("dep1", |project| { project .file("Cargo.toml", &basic_manifest("dep1", "0.5.0")) .file("src/lib.rs", "") }); let dep2 = git::new("dep2", |project| { project .file("Cargo.toml", &basic_manifest("dep2", "0.5.0")) .file("src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" authors = [] version = "0.0.0" [dependencies] dep1 = {{ git = '{}' }} dep2 = {{ git = '{}' }} "#, dep1.url(), dep2.url() ), ) .file("src/main.rs", "fn main() { }"); let p = p.build(); let n_concurrent_builds = 5; let (tx, rx) = channel(); for _ in 0..n_concurrent_builds { let cmd = p .cargo("build") .build_command() .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn(); let tx = tx.clone(); thread::spawn(move || { let result = cmd.unwrap().wait_with_output().unwrap(); tx.send(result).unwrap() }); } for _ in 0..n_concurrent_builds { let result = rx.recv_timeout(slow_cpu_multiplier(30)).expect("Deadlock!"); execs().run_output(&result); } }