cargo/tests/testsuite/check.rs
bors 2e09266f66 Auto merge of #6883 - alexcrichton:pipelining-v2, r=ehuss
Implement the Cargo half of pipelined compilation (take 2)

This commit starts to lay the groundwork for #6660 where Cargo will
invoke rustc in a "pipelined" fashion. The goal here is to execute one
command to produce both an `*.rmeta` file as well as an `*.rlib` file
for candidate compilations. In that case if another rlib depends on that
compilation, then it can start as soon as the `*.rmeta` is ready and not
have to wait for the `*.rlib` compilation.

Initially attempted in #6864 with a pretty invasive refactoring this
iteration is much more lightweight and fits much more cleanly into
Cargo's backend. The approach taken here is to update the
`DependencyQueue` structure to carry a piece of data on each dependency
edge. This edge information represents the artifact that one node
requires from another, and then we a node has no outgoing edges it's
ready to build.

A dependency on a metadata file is modeled as just that, a dependency on
just the metadata and not the full build itself. Most of cargo's backend
doesn't really need to know about this edge information so it's
basically just calculated as we insert nodes into the `DependencyQueue`.
Once that's all in place it's just a few pieces here and there to
identify compilations that *can* be pipelined and then they're wired up
to depend on the rmeta file instead of the rlib file.

Closes #6660
2019-05-10 14:36:30 +00:00

762 lines
21 KiB
Rust

use std::fmt::{self, Write};
use crate::support::install::exe;
use crate::support::paths::CargoPathExt;
use crate::support::registry::Package;
use crate::support::{basic_manifest, project};
#[test]
fn check_success() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "../bar"
"#,
)
.file(
"src/main.rs",
"extern crate bar; fn main() { ::bar::baz(); }",
)
.build();
let _bar = project()
.at("bar")
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("src/lib.rs", "pub fn baz() {}")
.build();
foo.cargo("check").run();
}
#[test]
fn check_fail() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "../bar"
"#,
)
.file(
"src/main.rs",
"extern crate bar; fn main() { ::bar::baz(42); }",
)
.build();
let _bar = project()
.at("bar")
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("src/lib.rs", "pub fn baz() {}")
.build();
foo.cargo("check")
.with_status(101)
.with_stderr_contains("[..]this function takes 0 parameters but 1 parameter was supplied")
.run();
}
#[test]
fn custom_derive() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "../bar"
"#,
)
.file(
"src/main.rs",
r#"
#[macro_use]
extern crate bar;
trait B {
fn b(&self);
}
#[derive(B)]
struct A;
fn main() {
let a = A;
a.b();
}
"#,
)
.build();
let _bar = project()
.at("bar")
.file(
"Cargo.toml",
r#"
[package]
name = "bar"
version = "0.1.0"
authors = []
[lib]
proc-macro = true
"#,
)
.file(
"src/lib.rs",
r#"
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(B)]
pub fn derive(_input: TokenStream) -> TokenStream {
format!("impl B for A {{ fn b(&self) {{}} }}").parse().unwrap()
}
"#,
)
.build();
foo.cargo("check").run();
}
#[test]
fn check_build() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "../bar"
"#,
)
.file(
"src/main.rs",
"extern crate bar; fn main() { ::bar::baz(); }",
)
.build();
let _bar = project()
.at("bar")
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("src/lib.rs", "pub fn baz() {}")
.build();
foo.cargo("check").run();
foo.cargo("build").run();
}
#[test]
fn build_check() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "../bar"
"#,
)
.file(
"src/main.rs",
"extern crate bar; fn main() { ::bar::baz(); }",
)
.build();
let _bar = project()
.at("bar")
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("src/lib.rs", "pub fn baz() {}")
.build();
foo.cargo("build -v").run();
foo.cargo("check -v").run();
}
// Checks that where a project has both a lib and a bin, the lib is only checked
// not built.
#[test]
fn issue_3418() {
let foo = project()
.file("src/lib.rs", "")
.file("src/main.rs", "fn main() {}")
.build();
foo.cargo("check -v")
.with_stderr_contains("[..] --emit=[..]metadata [..]")
.run();
}
// Some weirdness that seems to be caused by a crate being built as well as
// checked, but in this case with a proc macro too.
#[test]
fn issue_3419() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies]
rustc-serialize = "*"
"#,
)
.file(
"src/lib.rs",
r#"
extern crate rustc_serialize;
use rustc_serialize::Decodable;
pub fn take<T: Decodable>() {}
"#,
)
.file(
"src/main.rs",
r#"
extern crate rustc_serialize;
extern crate foo;
#[derive(RustcDecodable)]
pub struct Foo;
fn main() {
foo::take::<Foo>();
}
"#,
)
.build();
Package::new("rustc-serialize", "1.0.0")
.file(
"src/lib.rs",
r#"pub trait Decodable: Sized {
fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error>;
}
pub trait Decoder {
type Error;
fn read_struct<T, F>(&mut self, s_name: &str, len: usize, f: F)
-> Result<T, Self::Error>
where F: FnOnce(&mut Self) -> Result<T, Self::Error>;
} "#,
)
.publish();
p.cargo("check").run();
}
// Check on a dylib should have a different metadata hash than build.
#[test]
fn dylib_check_preserves_build_cache() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
authors = []
[lib]
crate-type = ["dylib"]
[dependencies]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("build")
.with_stderr(
"\
[..]Compiling foo v0.1.0 ([..])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
p.cargo("check").run();
p.cargo("build")
.with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
.run();
}
// test `cargo rustc --profile check`
#[test]
fn rustc_check() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "../bar"
"#,
)
.file(
"src/main.rs",
"extern crate bar; fn main() { ::bar::baz(); }",
)
.build();
let _bar = project()
.at("bar")
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("src/lib.rs", "pub fn baz() {}")
.build();
foo.cargo("rustc --profile check -- --emit=metadata").run();
}
#[test]
fn rustc_check_err() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "../bar"
"#,
)
.file(
"src/main.rs",
"extern crate bar; fn main() { ::bar::qux(); }",
)
.build();
let _bar = project()
.at("bar")
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("src/lib.rs", "pub fn baz() {}")
.build();
foo.cargo("rustc --profile check -- --emit=metadata")
.with_status(101)
.with_stderr_contains("[CHECKING] bar [..]")
.with_stderr_contains("[CHECKING] foo [..]")
.with_stderr_contains("[..]cannot find function `qux` in module `bar`")
.run();
}
#[test]
fn check_all() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[workspace]
[dependencies]
b = { path = "b" }
"#,
)
.file("src/main.rs", "fn main() {}")
.file("examples/a.rs", "fn main() {}")
.file("tests/a.rs", "")
.file("src/lib.rs", "")
.file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
.file("b/src/main.rs", "fn main() {}")
.file("b/src/lib.rs", "")
.build();
p.cargo("check --all -v")
.with_stderr_contains("[..] --crate-name foo src/lib.rs [..]")
.with_stderr_contains("[..] --crate-name foo src/main.rs [..]")
.with_stderr_contains("[..] --crate-name b b/src/lib.rs [..]")
.with_stderr_contains("[..] --crate-name b b/src/main.rs [..]")
.run();
}
#[test]
fn check_virtual_all_implied() {
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["bar", "baz"]
"#,
)
.file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("bar/src/lib.rs", "pub fn bar() {}")
.file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
.file("baz/src/lib.rs", "pub fn baz() {}")
.build();
p.cargo("check -v")
.with_stderr_contains("[..] --crate-name bar bar/src/lib.rs [..]")
.with_stderr_contains("[..] --crate-name baz baz/src/lib.rs [..]")
.run();
}
#[test]
fn exclude_warns_on_non_existing_package() {
let p = project().file("src/lib.rs", "").build();
p.cargo("check --all --exclude bar")
.with_stdout("")
.with_stderr(
r#"[WARNING] excluded package(s) bar not found in workspace `[CWD]`
[CHECKING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
"#,
)
.run();
}
#[test]
fn targets_selected_default() {
let foo = project()
.file("src/main.rs", "fn main() {}")
.file("src/lib.rs", "pub fn smth() {}")
.file("examples/example1.rs", "fn main() {}")
.file("tests/test2.rs", "#[test] fn t() {}")
.file("benches/bench3.rs", "")
.build();
foo.cargo("check -v")
.with_stderr_contains("[..] --crate-name foo src/lib.rs [..]")
.with_stderr_contains("[..] --crate-name foo src/main.rs [..]")
.with_stderr_does_not_contain("[..] --crate-name example1 examples/example1.rs [..]")
.with_stderr_does_not_contain("[..] --crate-name test2 tests/test2.rs [..]")
.with_stderr_does_not_contain("[..] --crate-name bench3 benches/bench3.rs [..]")
.run();
}
#[test]
fn targets_selected_all() {
let foo = project()
.file("src/main.rs", "fn main() {}")
.file("src/lib.rs", "pub fn smth() {}")
.file("examples/example1.rs", "fn main() {}")
.file("tests/test2.rs", "#[test] fn t() {}")
.file("benches/bench3.rs", "")
.build();
foo.cargo("check --all-targets -v")
.with_stderr_contains("[..] --crate-name foo src/lib.rs [..]")
.with_stderr_contains("[..] --crate-name foo src/main.rs [..]")
.with_stderr_contains("[..] --crate-name example1 examples/example1.rs [..]")
.with_stderr_contains("[..] --crate-name test2 tests/test2.rs [..]")
.with_stderr_contains("[..] --crate-name bench3 benches/bench3.rs [..]")
.run();
}
#[test]
fn check_unit_test_profile() {
let foo = project()
.file(
"src/lib.rs",
r#"
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
badtext
}
}
"#,
)
.build();
foo.cargo("check").run();
foo.cargo("check --profile test")
.with_status(101)
.with_stderr_contains("[..]badtext[..]")
.run();
}
// Verify what is checked with various command-line filters.
#[test]
fn check_filters() {
let p = project()
.file(
"src/lib.rs",
r#"
fn unused_normal_lib() {}
#[cfg(test)]
mod tests {
fn unused_unit_lib() {}
}
"#,
)
.file(
"src/main.rs",
r#"
fn main() {}
fn unused_normal_bin() {}
#[cfg(test)]
mod tests {
fn unused_unit_bin() {}
}
"#,
)
.file(
"tests/t1.rs",
r#"
fn unused_normal_t1() {}
#[cfg(test)]
mod tests {
fn unused_unit_t1() {}
}
"#,
)
.file(
"examples/ex1.rs",
r#"
fn main() {}
fn unused_normal_ex1() {}
#[cfg(test)]
mod tests {
fn unused_unit_ex1() {}
}
"#,
)
.file(
"benches/b1.rs",
r#"
fn unused_normal_b1() {}
#[cfg(test)]
mod tests {
fn unused_unit_b1() {}
}
"#,
)
.build();
p.cargo("check")
.with_stderr_contains("[..]unused_normal_lib[..]")
.with_stderr_contains("[..]unused_normal_bin[..]")
.with_stderr_does_not_contain("[..]unused_normal_t1[..]")
.with_stderr_does_not_contain("[..]unused_normal_ex1[..]")
.with_stderr_does_not_contain("[..]unused_normal_b1[..]")
.with_stderr_does_not_contain("[..]unused_unit_[..]")
.run();
p.root().join("target").rm_rf();
p.cargo("check --tests -v")
.with_stderr_contains("[..] --crate-name foo src/lib.rs [..] --test [..]")
.with_stderr_contains("[..] --crate-name foo src/lib.rs [..] --crate-type lib [..]")
.with_stderr_contains("[..] --crate-name foo src/main.rs [..] --test [..]")
.with_stderr_contains("[..]unused_unit_lib[..]")
.with_stderr_contains("[..]unused_unit_bin[..]")
.with_stderr_contains("[..]unused_normal_lib[..]")
.with_stderr_contains("[..]unused_normal_bin[..]")
.with_stderr_contains("[..]unused_unit_t1[..]")
.with_stderr_does_not_contain("[..]unused_normal_ex1[..]")
.with_stderr_does_not_contain("[..]unused_unit_ex1[..]")
.with_stderr_does_not_contain("[..]unused_normal_b1[..]")
.with_stderr_does_not_contain("[..]unused_unit_b1[..]")
.with_stderr_does_not_contain("[..]--crate-type bin[..]")
.run();
p.root().join("target").rm_rf();
p.cargo("check --test t1 -v")
.with_stderr_contains("[..]unused_normal_lib[..]")
.with_stderr_contains("[..]unused_unit_t1[..]")
.with_stderr_does_not_contain("[..]unused_unit_lib[..]")
.with_stderr_does_not_contain("[..]unused_normal_bin[..]")
.with_stderr_does_not_contain("[..]unused_unit_bin[..]")
.with_stderr_does_not_contain("[..]unused_normal_ex1[..]")
.with_stderr_does_not_contain("[..]unused_normal_b1[..]")
.with_stderr_does_not_contain("[..]unused_unit_ex1[..]")
.with_stderr_does_not_contain("[..]unused_unit_b1[..]")
.run();
p.root().join("target").rm_rf();
p.cargo("check --all-targets -v")
.with_stderr_contains("[..]unused_normal_lib[..]")
.with_stderr_contains("[..]unused_normal_bin[..]")
.with_stderr_contains("[..]unused_normal_t1[..]")
.with_stderr_contains("[..]unused_normal_ex1[..]")
.with_stderr_contains("[..]unused_normal_b1[..]")
.with_stderr_contains("[..]unused_unit_b1[..]")
.with_stderr_contains("[..]unused_unit_t1[..]")
.with_stderr_contains("[..]unused_unit_lib[..]")
.with_stderr_contains("[..]unused_unit_bin[..]")
.with_stderr_does_not_contain("[..]unused_unit_ex1[..]")
.run();
}
#[test]
fn check_artifacts() {
// Verify which artifacts are created when running check (#4059).
let p = project()
.file("src/lib.rs", "")
.file("src/main.rs", "fn main() {}")
.file("tests/t1.rs", "")
.file("examples/ex1.rs", "fn main() {}")
.file("benches/b1.rs", "")
.build();
p.cargo("check").run();
assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 2);
p.root().join("target").rm_rf();
p.cargo("check --lib").run();
assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 1);
p.root().join("target").rm_rf();
p.cargo("check --bin foo").run();
assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 2);
p.root().join("target").rm_rf();
p.cargo("check --test t1").run();
assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
assert_eq!(p.glob("target/debug/t1-*").count(), 0);
assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 1);
assert_eq!(p.glob("target/debug/deps/libt1-*.rmeta").count(), 1);
p.root().join("target").rm_rf();
p.cargo("check --example ex1").run();
assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
assert!(!p
.root()
.join("target/debug/examples")
.join(exe("ex1"))
.is_file());
assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 1);
assert_eq!(p.glob("target/debug/examples/libex1-*.rmeta").count(), 1);
p.root().join("target").rm_rf();
p.cargo("check --bench b1").run();
assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
assert_eq!(p.glob("target/debug/b1-*").count(), 0);
assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 1);
assert_eq!(p.glob("target/debug/deps/libb1-*.rmeta").count(), 1);
}
#[test]
fn short_message_format() {
let foo = project()
.file("src/lib.rs", "fn foo() { let _x: bool = 'a'; }")
.build();
foo.cargo("check --message-format=short")
.with_status(101)
.with_stderr_contains(
"\
src/lib.rs:1:27: error[E0308]: mismatched types
error: aborting due to previous error
error: Could not compile `foo`.
",
)
.run();
}
#[test]
fn proc_macro() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "demo"
version = "0.0.1"
[lib]
proc-macro = true
"#,
)
.file(
"src/lib.rs",
r#"
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(Foo)]
pub fn demo(_input: TokenStream) -> TokenStream {
"".parse().unwrap()
}
"#,
)
.file(
"src/main.rs",
r#"
#[macro_use]
extern crate demo;
#[derive(Foo)]
struct A;
fn main() {}
"#,
)
.build();
p.cargo("check -v").env("CARGO_LOG", "cargo=trace").run();
}
#[test]
fn does_not_use_empty_rustc_wrapper() {
let p = project().file("src/lib.rs", "").build();
p.cargo("check").env("RUSTC_WRAPPER", "").run();
}
#[test]
fn error_from_deep_recursion() -> Result<(), fmt::Error> {
let mut big_macro = String::new();
writeln!(big_macro, "macro_rules! m {{")?;
for i in 0..130 {
writeln!(big_macro, "({}) => {{ m!({}); }};", i, i + 1)?;
}
writeln!(big_macro, "}}")?;
writeln!(big_macro, "m!(0);")?;
let p = project().file("src/lib.rs", &big_macro).build();
p.cargo("check --message-format=json")
.with_status(101)
.with_stdout_contains(
"[..]\"message\":\"recursion limit reached while expanding the macro `m`\"[..]",
)
.run();
Ok(())
}