cargo/tests/testsuite/lto.rs
2023-09-07 17:57:28 +00:00

848 lines
24 KiB
Rust

use cargo::core::compiler::Lto;
use cargo_test_support::registry::Package;
use cargo_test_support::{basic_manifest, project, Project};
use std::process::Output;
#[cargo_test]
fn with_deps() {
Package::new("bar", "0.0.1").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "test"
version = "0.0.0"
[dependencies]
bar = "*"
[profile.release]
lto = true
"#,
)
.file("src/main.rs", "extern crate bar; fn main() {}")
.build();
p.cargo("build -v --release")
.with_stderr_contains("[..]`rustc[..]--crate-name bar[..]-C linker-plugin-lto[..]`")
.with_stderr_contains("[..]`rustc[..]--crate-name test[..]-C lto[..]`")
.run();
}
#[cargo_test]
fn shared_deps() {
Package::new("bar", "0.0.1").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "test"
version = "0.0.0"
[dependencies]
bar = "*"
[build-dependencies]
bar = "*"
[profile.release]
lto = true
"#,
)
.file("build.rs", "extern crate bar; fn main() {}")
.file("src/main.rs", "extern crate bar; fn main() {}")
.build();
p.cargo("build -v --release")
.with_stderr_contains("[..]`rustc[..]--crate-name test[..]-C lto[..]`")
.run();
}
#[cargo_test]
fn build_dep_not_ltod() {
Package::new("bar", "0.0.1").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "test"
version = "0.0.0"
[build-dependencies]
bar = "*"
[profile.release]
lto = true
"#,
)
.file("build.rs", "extern crate bar; fn main() {}")
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build -v --release")
.with_stderr_contains("[..]`rustc[..]--crate-name bar[..]-C embed-bitcode=no[..]`")
.with_stderr_contains("[..]`rustc[..]--crate-name test[..]-C lto[..]`")
.run();
}
#[cargo_test]
fn complicated() {
Package::new("dep-shared", "0.0.1")
.file("src/lib.rs", "pub fn foo() {}")
.publish();
Package::new("dep-normal2", "0.0.1")
.file("src/lib.rs", "pub fn foo() {}")
.publish();
Package::new("dep-normal", "0.0.1")
.dep("dep-shared", "*")
.dep("dep-normal2", "*")
.file(
"src/lib.rs",
"
pub fn foo() {
dep_shared::foo();
dep_normal2::foo();
}
",
)
.publish();
Package::new("dep-build2", "0.0.1")
.file("src/lib.rs", "pub fn foo() {}")
.publish();
Package::new("dep-build", "0.0.1")
.dep("dep-shared", "*")
.dep("dep-build2", "*")
.file(
"src/lib.rs",
"
pub fn foo() {
dep_shared::foo();
dep_build2::foo();
}
",
)
.publish();
Package::new("dep-proc-macro2", "0.0.1")
.file("src/lib.rs", "pub fn foo() {}")
.publish();
Package::new("dep-proc-macro", "0.0.1")
.proc_macro(true)
.dep("dep-shared", "*")
.dep("dep-proc-macro2", "*")
.file(
"src/lib.rs",
"
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn foo(_: TokenStream, a: TokenStream) -> TokenStream {
dep_shared::foo();
dep_proc_macro2::foo();
a
}
",
)
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "test"
version = "0.0.0"
[lib]
crate-type = ['cdylib', 'staticlib']
[dependencies]
dep-normal = "*"
dep-proc-macro = "*"
[build-dependencies]
dep-build = "*"
[profile.release]
lto = true
# force build deps to share an opt-level with the rest of the
# graph so they only get built once.
[profile.release.build-override]
opt-level = 3
"#,
)
.file("build.rs", "fn main() { dep_build::foo() }")
.file(
"src/bin/foo-bin.rs",
"#[dep_proc_macro::foo] fn main() { dep_normal::foo() }",
)
.file(
"src/lib.rs",
"#[dep_proc_macro::foo] pub fn foo() { dep_normal::foo() }",
)
.build();
p.cargo("build -v --release")
// normal deps and their transitive dependencies do not need object
// code, so they should have linker-plugin-lto specified
.with_stderr_contains(
"[..]`rustc[..]--crate-name dep_normal2 [..]-C linker-plugin-lto[..]`",
)
.with_stderr_contains("[..]`rustc[..]--crate-name dep_normal [..]-C linker-plugin-lto[..]`")
// build dependencies and their transitive deps don't need any bitcode,
// so embedding should be turned off
.with_stderr_contains("[..]`rustc[..]--crate-name dep_build2 [..]-C embed-bitcode=no[..]`")
.with_stderr_contains("[..]`rustc[..]--crate-name dep_build [..]-C embed-bitcode=no[..]`")
.with_stderr_contains(
"[..]`rustc[..]--crate-name build_script_build [..]-C embed-bitcode=no[..]`",
)
// proc macro deps are the same as build deps here
.with_stderr_contains(
"[..]`rustc[..]--crate-name dep_proc_macro2 [..]-C embed-bitcode=no[..]`",
)
.with_stderr_contains(
"[..]`rustc[..]--crate-name dep_proc_macro [..]-C embed-bitcode=no[..]`",
)
.with_stderr_contains(
"[..]`rustc[..]--crate-name foo_bin [..]--crate-type bin[..]-C lto[..]`",
)
.with_stderr_contains(
"[..]`rustc[..]--crate-name test [..]--crate-type cdylib[..]-C lto[..]`",
)
.with_stderr_contains("[..]`rustc[..]--crate-name dep_shared [..]`")
.with_stderr_does_not_contain("[..]--crate-name dep_shared[..]-C lto[..]")
.with_stderr_does_not_contain("[..]--crate-name dep_shared[..]-C linker-plugin-lto[..]")
.with_stderr_does_not_contain("[..]--crate-name dep_shared[..]-C embed-bitcode[..]")
.run();
}
#[cargo_test]
fn off_in_manifest_works() {
Package::new("bar", "0.0.1")
.file("src/lib.rs", "pub fn foo() {}")
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "test"
version = "0.0.0"
[dependencies]
bar = "*"
[profile.release]
lto = "off"
"#,
)
.file("src/lib.rs", "pub fn foo() {}")
.file(
"src/main.rs",
"fn main() {
test::foo();
bar::foo();
}",
)
.build();
p.cargo("build -v --release")
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] [..]
[DOWNLOADED] [..]
[COMPILING] bar v0.0.1
[RUNNING] `rustc --crate-name bar [..]--crate-type lib [..]-C lto=off -C embed-bitcode=no[..]
[COMPILING] test [..]
[RUNNING] `rustc --crate-name test [..]--crate-type lib [..]-C lto=off -C embed-bitcode=no[..]
[RUNNING] `rustc --crate-name test src/main.rs [..]--crate-type bin [..]-C lto=off[..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn between_builds() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "test"
version = "0.0.0"
[profile.release]
lto = true
"#,
)
.file("src/lib.rs", "pub fn foo() {}")
.file("src/main.rs", "fn main() { test::foo() }")
.build();
p.cargo("build -v --release --lib")
.with_stderr(
"\
[COMPILING] test [..]
[RUNNING] `rustc [..]--crate-type lib[..]-C linker-plugin-lto[..]
[FINISHED] [..]
",
)
.run();
p.cargo("build -v --release")
.with_stderr_contains(
"\
[COMPILING] test [..]
[RUNNING] `rustc [..]--crate-type bin[..]-C lto[..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn test_all() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.0"
[profile.release]
lto = true
"#,
)
.file("src/main.rs", "fn main() {}")
.file("tests/a.rs", "")
.file("tests/b.rs", "")
.build();
p.cargo("test --release -v")
.with_stderr_contains("[RUNNING] `rustc[..]--crate-name foo[..]-C lto[..]")
.run();
}
#[cargo_test]
fn test_all_and_bench() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.0"
[profile.release]
lto = true
[profile.bench]
lto = true
"#,
)
.file("src/main.rs", "fn main() {}")
.file("tests/a.rs", "")
.file("tests/b.rs", "")
.build();
p.cargo("test --release -v")
.with_stderr_contains("[RUNNING] `rustc[..]--crate-name a[..]-C lto[..]")
.with_stderr_contains("[RUNNING] `rustc[..]--crate-name b[..]-C lto[..]")
.with_stderr_contains("[RUNNING] `rustc[..]--crate-name foo[..]-C lto[..]")
.run();
}
/// Basic setup:
///
/// foo v0.0.0
/// ├── bar v0.0.0
/// │ ├── registry v0.0.1
/// │ └── registry-shared v0.0.1
/// └── registry-shared v0.0.1
///
/// Where `bar` will have the given crate types.
fn project_with_dep(crate_types: &str) -> Project {
Package::new("registry", "0.0.1")
.file("src/lib.rs", r#"pub fn foo() { println!("registry"); }"#)
.publish();
Package::new("registry-shared", "0.0.1")
.file("src/lib.rs", r#"pub fn foo() { println!("shared"); }"#)
.publish();
project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.0"
[workspace]
[dependencies]
bar = { path = 'bar' }
registry-shared = "*"
[profile.release]
lto = true
"#,
)
.file(
"src/main.rs",
"
fn main() {
bar::foo();
registry_shared::foo();
}
",
)
.file(
"bar/Cargo.toml",
&format!(
r#"
[package]
name = "bar"
version = "0.0.0"
[dependencies]
registry = "*"
registry-shared = "*"
[lib]
crate-type = [{}]
"#,
crate_types
),
)
.file(
"bar/src/lib.rs",
r#"
pub fn foo() {
println!("bar");
registry::foo();
registry_shared::foo();
}
"#,
)
.file("tests/a.rs", "")
.file("bar/tests/b.rs", "")
.build()
}
/// Helper for checking which LTO behavior is used for a specific crate.
///
/// `krate_info` is extra compiler flags used to distinguish this if the same
/// crate name is being built multiple times.
fn verify_lto(output: &Output, krate: &str, krate_info: &str, expected_lto: Lto) {
let stderr = std::str::from_utf8(&output.stderr).unwrap();
let mut matches = stderr.lines().filter(|line| {
line.contains("Running")
&& line.contains(&format!("--crate-name {} ", krate))
&& line.contains(krate_info)
});
let line = matches.next().unwrap_or_else(|| {
panic!(
"expected to find crate `{}` info: `{}`, not found in output:\n{}",
krate, krate_info, stderr
);
});
if let Some(line2) = matches.next() {
panic!(
"found multiple lines matching crate `{}` info: `{}`:\nline1:{}\nline2:{}\noutput:\n{}",
krate, krate_info, line, line2, stderr
);
}
let actual_lto = if let Some((_, line)) = line.split_once("-C lto=") {
let mode = line.splitn(2, ' ').next().unwrap();
if mode == "off" {
Lto::Off
} else {
Lto::Run(Some(mode.into()))
}
} else if line.contains("-C lto") {
Lto::Run(None)
} else if line.contains("-C linker-plugin-lto") {
Lto::OnlyBitcode
} else if line.contains("-C embed-bitcode=no") {
Lto::OnlyObject
} else {
Lto::ObjectAndBitcode
};
assert_eq!(
actual_lto, expected_lto,
"did not find expected LTO in line: {}",
line
);
}
#[cargo_test]
fn cdylib_and_rlib() {
let p = project_with_dep("'cdylib', 'rlib'");
let output = p.cargo("build --release -v").exec_with_output().unwrap();
// `registry` is ObjectAndBitcode because it needs Object for the
// rlib, and Bitcode for the cdylib (which doesn't support LTO).
verify_lto(
&output,
"registry",
"--crate-type lib",
Lto::ObjectAndBitcode,
);
// Same as `registry`
verify_lto(
&output,
"registry_shared",
"--crate-type lib",
Lto::ObjectAndBitcode,
);
// Same as `registry`
verify_lto(
&output,
"bar",
"--crate-type cdylib --crate-type rlib",
Lto::ObjectAndBitcode,
);
verify_lto(&output, "foo", "--crate-type bin", Lto::Run(None));
p.cargo("test --release -v")
.with_stderr_unordered(
"\
[FRESH] registry v0.0.1
[FRESH] registry-shared v0.0.1
[FRESH] bar v0.0.0 [..]
[COMPILING] foo [..]
[RUNNING] `rustc --crate-name foo [..]-C lto [..]--test[..]
[RUNNING] `rustc --crate-name a [..]-C lto [..]--test[..]
[FINISHED] [..]
[RUNNING] [..]
[RUNNING] [..]
",
)
.run();
p.cargo("build --release -v --manifest-path bar/Cargo.toml")
.with_stderr_unordered(
"\
[FRESH] registry-shared v0.0.1
[FRESH] registry v0.0.1
[FRESH] bar v0.0.0 [..]
[FINISHED] [..]
",
)
.run();
p.cargo("test --release -v --manifest-path bar/Cargo.toml")
.with_stderr_unordered(
"\
[FRESH] registry-shared v0.0.1
[FRESH] registry v0.0.1
[COMPILING] bar [..]
[RUNNING] `rustc --crate-name bar [..]-C lto[..]--test[..]
[RUNNING] `rustc --crate-name b [..]-C lto[..]--test[..]
[FINISHED] [..]
[RUNNING] [..]target/release/deps/bar-[..]
[RUNNING] [..]target/release/deps/b-[..]
[DOCTEST] bar
[RUNNING] `rustdoc --crate-type cdylib --crate-type rlib --crate-name bar --test [..]-C lto[..]
",
)
.run();
}
#[cargo_test]
fn dylib() {
let p = project_with_dep("'dylib'");
let output = p.cargo("build --release -v").exec_with_output().unwrap();
// `registry` is OnlyObject because rustc doesn't support LTO with dylibs.
verify_lto(&output, "registry", "--crate-type lib", Lto::OnlyObject);
// `registry_shared` is both because it is needed by both bar (Object) and
// foo (Bitcode for LTO).
verify_lto(
&output,
"registry_shared",
"--crate-type lib",
Lto::ObjectAndBitcode,
);
// `bar` is OnlyObject because rustc doesn't support LTO with dylibs.
verify_lto(&output, "bar", "--crate-type dylib", Lto::OnlyObject);
// `foo` is LTO because it is a binary, and the profile specifies `lto=true`.
verify_lto(&output, "foo", "--crate-type bin", Lto::Run(None));
// `cargo test` should not rebuild dependencies. It builds the test
// executables with `lto=true` because the tests are built with the
// `--release` flag.
p.cargo("test --release -v")
.with_stderr_unordered(
"\
[FRESH] registry v0.0.1
[FRESH] registry-shared v0.0.1
[FRESH] bar v0.0.0 [..]
[COMPILING] foo [..]
[RUNNING] `rustc --crate-name foo [..]-C lto [..]--test[..]
[RUNNING] `rustc --crate-name a [..]-C lto [..]--test[..]
[FINISHED] [..]
[RUNNING] [..]
[RUNNING] [..]
",
)
.run();
// Building just `bar` causes `registry-shared` to get rebuilt because it
// switches to OnlyObject because it is now only being used with a dylib
// which does not support LTO.
//
// `bar` gets rebuilt because `registry_shared` got rebuilt.
p.cargo("build --release -v --manifest-path bar/Cargo.toml")
.with_stderr_unordered(
"\
[COMPILING] registry-shared v0.0.1
[FRESH] registry v0.0.1
[RUNNING] `rustc --crate-name registry_shared [..]-C embed-bitcode=no[..]
[DIRTY] bar v0.0.0 ([..]): dependency info changed
[COMPILING] bar [..]
[RUNNING] `rustc --crate-name bar [..]--crate-type dylib [..]-C embed-bitcode=no[..]
[FINISHED] [..]
",
)
.run();
// Testing just `bar` causes `registry` to get rebuilt because it switches
// to needing both Object (for the `bar` dylib) and Bitcode (for the test
// built with LTO).
//
// `bar` the dylib gets rebuilt because `registry` got rebuilt.
p.cargo("test --release -v --manifest-path bar/Cargo.toml")
.with_stderr_unordered(
"\
[FRESH] registry-shared v0.0.1
[COMPILING] registry v0.0.1
[RUNNING] `rustc --crate-name registry [..]
[DIRTY] bar v0.0.0 ([..]): dependency info changed
[COMPILING] bar [..]
[RUNNING] `rustc --crate-name bar [..]--crate-type dylib [..]-C embed-bitcode=no[..]
[RUNNING] `rustc --crate-name bar [..]-C lto [..]--test[..]
[RUNNING] `rustc --crate-name b [..]-C lto [..]--test[..]
[FINISHED] [..]
[RUNNING] [..]
[RUNNING] [..]
",
)
.run();
}
#[cargo_test]
// This is currently broken on windows-gnu, see https://github.com/rust-lang/rust/issues/109797
#[cfg_attr(
all(target_os = "windows", target_env = "gnu"),
ignore = "windows-gnu not working"
)]
fn test_profile() {
Package::new("bar", "0.0.1")
.file("src/lib.rs", "pub fn foo() -> i32 { 123 } ")
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2018"
[profile.test]
lto = 'thin'
[dependencies]
bar = "*"
"#,
)
.file(
"src/lib.rs",
r#"
#[test]
fn t1() {
assert_eq!(123, bar::foo());
}
"#,
)
.build();
p.cargo("test -v")
// unordered because the two `foo` builds start in parallel
.with_stderr_unordered("\
[UPDATING] [..]
[DOWNLOADING] [..]
[DOWNLOADED] [..]
[COMPILING] bar v0.0.1
[RUNNING] `rustc --crate-name bar [..]crate-type lib[..]
[COMPILING] foo [..]
[RUNNING] `rustc --crate-name foo [..]--crate-type lib --emit=dep-info,metadata,link -C linker-plugin-lto[..]
[RUNNING] `rustc --crate-name foo [..]--emit=dep-info,link -C lto=thin [..]--test[..]
[FINISHED] [..]
[RUNNING] [..]
[DOCTEST] foo
[RUNNING] `rustdoc [..]
")
.run();
}
#[cargo_test]
fn doctest() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2018"
[profile.release]
lto = true
[dependencies]
bar = { path = "bar" }
"#,
)
.file(
"src/lib.rs",
r#"
/// Foo!
///
/// ```
/// foo::foo();
/// ```
pub fn foo() { bar::bar(); }
"#,
)
.file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file(
"bar/src/lib.rs",
r#"
pub fn bar() { println!("hi!"); }
"#,
)
.build();
p.cargo("test --doc --release -v")
.with_stderr_contains("[..]`rustc --crate-name bar[..]-C linker-plugin-lto[..]")
.with_stderr_contains("[..]`rustc --crate-name foo[..]-C linker-plugin-lto[..]")
// embed-bitcode should be harmless here
.with_stderr_contains("[..]`rustdoc [..]-C lto[..]")
.run();
// Try with bench profile.
p.cargo("test --doc --release -v")
.env("CARGO_PROFILE_BENCH_LTO", "true")
.with_stderr_unordered(
"\
[FRESH] bar v0.1.0 [..]
[FRESH] foo v0.1.0 [..]
[FINISHED] release [..]
[DOCTEST] foo
[RUNNING] `rustdoc [..]-C lto[..]
",
)
.run();
}
#[cargo_test]
fn dylib_rlib_bin() {
// dylib+rlib linked with a binary
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[lib]
crate-type = ["dylib", "rlib"]
[profile.release]
lto = true
"#,
)
.file("src/lib.rs", "pub fn foo() { println!(\"hi!\"); }")
.file("src/bin/ferret.rs", "fn main() { foo::foo(); }")
.build();
let output = p.cargo("build --release -v").exec_with_output().unwrap();
verify_lto(
&output,
"foo",
"--crate-type dylib --crate-type rlib",
Lto::ObjectAndBitcode,
);
verify_lto(&output, "ferret", "--crate-type bin", Lto::Run(None));
}
#[cargo_test]
fn fresh_swapping_commands() {
// In some rare cases, different commands end up building dependencies
// with different LTO settings. This checks that it doesn't cause the
// cache to thrash in that scenario.
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = "1.0"
[profile.release]
lto = true
"#,
)
.file("src/lib.rs", "pub fn foo() { println!(\"hi!\"); }")
.build();
p.cargo("build --release -v")
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 [..]
[COMPILING] bar v1.0.0
[RUNNING] `rustc --crate-name bar [..]-C linker-plugin-lto[..]
[COMPILING] foo v0.1.0 [..]
[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C linker-plugin-lto[..]
[FINISHED] [..]
",
)
.run();
p.cargo("test --release -v")
.with_stderr_unordered(
"\
[FRESH] bar v1.0.0
[COMPILING] foo v0.1.0 [..]
[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C lto[..]--test[..]
[FINISHED] [..]
[RUNNING] `[..]/foo[..]`
[DOCTEST] foo
[RUNNING] `rustdoc [..]-C lto[..]
",
)
.run();
p.cargo("build --release -v")
.with_stderr(
"\
[FRESH] bar v1.0.0
[FRESH] foo [..]
[FINISHED] [..]
",
)
.run();
p.cargo("test --release -v --no-run -v")
.with_stderr(
"\
[FRESH] bar v1.0.0
[FRESH] foo [..]
[FINISHED] [..]
[EXECUTABLE] `[..]/target/release/deps/foo-[..][EXE]`
",
)
.run();
}