From 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:41:41 +0200 Subject: Merging upstream version 1.70.0+dfsg2. Signed-off-by: Daniel Baumann --- src/tools/cargo/tests/testsuite/lto.rs | 850 +++++++++++++++++++++++++++++++++ 1 file changed, 850 insertions(+) create mode 100644 src/tools/cargo/tests/testsuite/lto.rs (limited to 'src/tools/cargo/tests/testsuite/lto.rs') diff --git a/src/tools/cargo/tests/testsuite/lto.rs b/src/tools/cargo/tests/testsuite/lto.rs new file mode 100644 index 000000000..40b4f7ca2 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/lto.rs @@ -0,0 +1,850 @@ +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(index) = line.find("-C lto=") { + let s = &line[index..]; + let end = s.find(' ').unwrap(); + let mode = &line[index..index + end]; + 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(); +} -- cgit v1.2.3