Skip to content

Commit f37f3ae

Browse files
committed
Support rustc's -Z panic-abort-tests in Cargo
Recently added in rust-lang/rust#64158 the `-Z panic-abort-tests` flag to the compiler itself will activate a mode in the `test` crate which enables running tests even if they're compiled with `panic=abort`. It effectively runs a test-per-process. This commit brings the same support to Cargo, adding a `-Z panic-abort-tests` flag to Cargo which allows building tests in `panic=abort` mode. While I wanted to be sure to add support for this in Cargo before we stabilize the flag in `rustc`, I don't actually know how we're going to stabilize this here. Today Cargo will automatically switch test targets to `panic=unwind`, and so if we actually were to stabilize this flag then this configuration would break: [profile.dev] panic = 'abort' In that case tests would be compiled with `panic=unwind` (due to how profiles work today) which would clash with crates also being compiled with `panic=abort`. I'm hopeful though that we can perhaps either figure out a solution for this and maybe even integrate it with the ongoing profiles work.
1 parent eb12fff commit f37f3ae

File tree

7 files changed

+182
-11
lines changed

7 files changed

+182
-11
lines changed

src/cargo/core/compiler/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,17 @@ fn build_base_args<'a, 'cfg>(
799799

800800
if test && unit.target.harness() {
801801
cmd.arg("--test");
802+
803+
// Cargo has historically never compiled `--test` binaries with
804+
// `panic=abort` because the `test` crate itself didn't support it.
805+
// Support is now upstream, however, but requires an unstable flag to be
806+
// passed when compiling the test. We require, in Cargo, an unstable
807+
// flag to pass to rustc, so register that here. Eventually this flag
808+
// will simply not be needed when the behavior is stabilized in the Rust
809+
// compiler itself.
810+
if *panic == PanicStrategy::Abort {
811+
cmd.arg("-Zpanic-abort-tests");
812+
}
802813
} else if test {
803814
cmd.arg("--cfg").arg("test");
804815
}

src/cargo/core/compiler/unit_dependencies.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ fn deps_of_roots<'a, 'cfg>(roots: &[Unit<'a>], mut state: &mut State<'a, 'cfg>)
161161
// without, once for `--test`). In particular, the lib included for
162162
// Doc tests and examples are `Build` mode here.
163163
let unit_for = if unit.mode.is_any_test() || state.bcx.build_config.test() {
164-
UnitFor::new_test()
164+
UnitFor::new_test(state.bcx.config)
165165
} else if unit.target.is_custom_build() {
166166
// This normally doesn't happen, except `clean` aggressively
167167
// generates all units.

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ pub struct CliUnstable {
340340
pub build_std: Option<Vec<String>>,
341341
pub timings: Option<Vec<String>>,
342342
pub doctest_xcompile: bool,
343+
pub panic_abort_tests: bool,
343344
}
344345

345346
impl CliUnstable {
@@ -399,6 +400,7 @@ impl CliUnstable {
399400
}
400401
"timings" => self.timings = Some(parse_timings(v)),
401402
"doctest-xcompile" => self.doctest_xcompile = true,
403+
"panic-abort-tests" => self.panic_abort_tests = true,
402404
_ => failure::bail!("unknown `-Z` flag specified: {}", k),
403405
}
404406

src/cargo/core/profiles.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -357,9 +357,10 @@ impl Profiles {
357357
Some(r) => r,
358358
};
359359
let mut profile = maker.get_profile(Some(pkg_id), is_member, unit_for);
360-
// `panic` should not be set for tests/benches, or any of their
361-
// dependencies.
362-
if !unit_for.is_panic_abort_ok() || mode.is_any_test() {
360+
// `panic` may not be set for tests/benches, or any of their
361+
// dependencies, so handle that here if that's what `UnitFor` tells us
362+
// to do.
363+
if !unit_for.is_panic_abort_ok() {
363364
profile.panic = PanicStrategy::Unwind;
364365
}
365366

@@ -901,6 +902,8 @@ impl UnitFor {
901902
pub fn new_build() -> UnitFor {
902903
UnitFor {
903904
build: true,
905+
// Force build scripts to always use `panic=unwind` for now to
906+
// maximally share dependencies with procedural macros.
904907
panic_abort_ok: false,
905908
}
906909
}
@@ -909,22 +912,33 @@ impl UnitFor {
909912
pub fn new_compiler() -> UnitFor {
910913
UnitFor {
911914
build: false,
915+
// Force plugins to use `panic=abort` so panics in the compiler do
916+
// not abort the process but instead end with a reasonable error
917+
// message that involves catching the panic in the compiler.
912918
panic_abort_ok: false,
913919
}
914920
}
915921

916922
/// A unit for a test/bench target or their dependencies.
917-
pub fn new_test() -> UnitFor {
923+
///
924+
/// Note that `config` is taken here for unstable CLI features to detect
925+
/// whether `panic=abort` is supported for tests. Historical versions of
926+
/// rustc did not support this, but newer versions do with an unstable
927+
/// compiler flag.
928+
pub fn new_test(config: &Config) -> UnitFor {
918929
UnitFor {
919930
build: false,
920-
panic_abort_ok: false,
931+
panic_abort_ok: config.cli_unstable().panic_abort_tests,
921932
}
922933
}
923934

924935
/// Creates a variant based on `for_host` setting.
925936
///
926-
/// When `for_host` is true, this clears `panic_abort_ok` in a sticky fashion so
927-
/// that all its dependencies also have `panic_abort_ok=false`.
937+
/// When `for_host` is true, this clears `panic_abort_ok` in a sticky
938+
/// fashion so that all its dependencies also have `panic_abort_ok=false`.
939+
/// This'll help ensure that once we start compiling for the host platform
940+
/// (build scripts, plugins, proc macros, etc) we'll share the same build
941+
/// graph where everything is `panic=unwind`.
928942
pub fn with_for_host(self, for_host: bool) -> UnitFor {
929943
UnitFor {
930944
build: self.build || for_host,
@@ -938,7 +952,8 @@ impl UnitFor {
938952
self.build
939953
}
940954

941-
/// Returns `true` if this unit is allowed to set the `panic` compiler flag.
955+
/// Returns `true` if this unit is allowed to set the `panic=abort`
956+
/// compiler flag.
942957
pub fn is_panic_abort_ok(self) -> bool {
943958
self.panic_abort_ok
944959
}

src/cargo/ops/cargo_compile.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ fn generate_targets<'a>(
642642
) -> CargoResult<Vec<Unit<'a>>> {
643643
// Helper for creating a `Unit` struct.
644644
let new_unit = |pkg: &'a Package, target: &'a Target, target_mode: CompileMode| {
645-
let unit_for = if bcx.build_config.mode.is_any_test() {
645+
let unit_for = if target_mode.is_any_test() {
646646
// NOTE: the `UnitFor` here is subtle. If you have a profile
647647
// with `panic` set, the `panic` flag is cleared for
648648
// tests/benchmarks and their dependencies. If this
@@ -661,7 +661,7 @@ fn generate_targets<'a>(
661661
//
662662
// Forcing the lib to be compiled three times during `cargo
663663
// test` is probably also not desirable.
664-
UnitFor::new_test()
664+
UnitFor::new_test(bcx.config)
665665
} else if target.for_host() {
666666
// Proc macro / plugin should not have `panic` set.
667667
UnitFor::new_compiler()

src/doc/src/reference/unstable.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,17 @@ that information for change-detection (if any binary dependency changes, then
475475
the crate will be rebuilt). The primary use case is for building the compiler
476476
itself, which has implicit dependencies on the standard library that would
477477
otherwise be untracked for change-detection.
478+
479+
### panic-abort-tests
480+
481+
The `-Z panic-abort-tests` flag will enable nightly support to compile test
482+
harness crates with `-Cpanic=abort`. Without this flag Cargo will compile tests,
483+
and everything they depend on, with `-Cpanic=unwind` because it's the only way
484+
`test`-the-crate knows how to operate. As of [rust-lang/rust#64158], however,
485+
the `test` crate supports `-C panic=abort` with a test-per-process, and can help
486+
avoid compiling crate graphs multiple times.
487+
488+
It's currently unclear how this feature will be stabilized in Cargo, but we'd
489+
like to stabilize it somehow!
490+
491+
[rust-lang/rust#64158]: https://github.com/rust-lang/rust/pull/64158

tests/testsuite/test.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3872,3 +3872,132 @@ fn cargo_test_doctest_xcompile_no_runner() {
38723872
)
38733873
.run();
38743874
}
3875+
3876+
#[cargo_test]
3877+
fn panic_abort_tests() {
3878+
if !is_nightly() {
3879+
// -Zpanic-abort-tests in rustc is unstable
3880+
return;
3881+
}
3882+
3883+
let p = project()
3884+
.file(
3885+
"Cargo.toml",
3886+
r#"
3887+
[package]
3888+
name = 'foo'
3889+
version = '0.1.0'
3890+
3891+
[dependencies]
3892+
a = { path = 'a' }
3893+
3894+
[profile.dev]
3895+
panic = 'abort'
3896+
[profile.test]
3897+
panic = 'abort'
3898+
"#,
3899+
)
3900+
.file(
3901+
"src/lib.rs",
3902+
r#"
3903+
#[test]
3904+
fn foo() {
3905+
a::foo();
3906+
}
3907+
"#,
3908+
)
3909+
.file("a/Cargo.toml", &basic_lib_manifest("a"))
3910+
.file("a/src/lib.rs", "pub fn foo() {}")
3911+
.build();
3912+
3913+
p.cargo("test -Z panic-abort-tests -v")
3914+
.with_stderr_contains("[..]--crate-name a [..]-C panic=abort[..]")
3915+
.with_stderr_contains("[..]--crate-name foo [..]-C panic=abort[..]")
3916+
.with_stderr_contains("[..]--crate-name foo [..]-C panic=abort[..]--test[..]")
3917+
.masquerade_as_nightly_cargo()
3918+
.run();
3919+
}
3920+
3921+
#[cargo_test]
3922+
fn panic_abort_only_test() {
3923+
if !is_nightly() {
3924+
// -Zpanic-abort-tests in rustc is unstable
3925+
return;
3926+
}
3927+
3928+
let p = project()
3929+
.file(
3930+
"Cargo.toml",
3931+
r#"
3932+
[package]
3933+
name = 'foo'
3934+
version = '0.1.0'
3935+
3936+
[dependencies]
3937+
a = { path = 'a' }
3938+
3939+
[profile.test]
3940+
panic = 'abort'
3941+
"#,
3942+
)
3943+
.file(
3944+
"src/lib.rs",
3945+
r#"
3946+
#[test]
3947+
fn foo() {
3948+
a::foo();
3949+
}
3950+
"#,
3951+
)
3952+
.file("a/Cargo.toml", &basic_lib_manifest("a"))
3953+
.file("a/src/lib.rs", "pub fn foo() {}")
3954+
.build();
3955+
3956+
p.cargo("test -Z panic-abort-tests -v")
3957+
.with_stderr_does_not_contain("[..]--crate-name a [..]-C panic=abort[..]")
3958+
.with_stderr_contains("[..]--crate-name foo [..]-C panic=abort[..]--test[..]")
3959+
.masquerade_as_nightly_cargo()
3960+
.run();
3961+
}
3962+
3963+
#[cargo_test]
3964+
fn panic_abort_invalid() {
3965+
if !is_nightly() {
3966+
// -Zpanic-abort-tests in rustc is unstable
3967+
return;
3968+
}
3969+
3970+
let p = project()
3971+
.file(
3972+
"Cargo.toml",
3973+
r#"
3974+
[package]
3975+
name = 'foo'
3976+
version = '0.1.0'
3977+
3978+
[dependencies]
3979+
a = { path = 'a' }
3980+
3981+
[profile.dev]
3982+
panic = 'abort'
3983+
"#,
3984+
)
3985+
.file(
3986+
"src/lib.rs",
3987+
r#"
3988+
#[test]
3989+
fn foo() {
3990+
a::foo();
3991+
}
3992+
"#,
3993+
)
3994+
.file("a/Cargo.toml", &basic_lib_manifest("a"))
3995+
.file("a/src/lib.rs", "pub fn foo() {}")
3996+
.build();
3997+
3998+
p.cargo("test -Z panic-abort-tests -v")
3999+
.masquerade_as_nightly_cargo()
4000+
.with_status(101)
4001+
.with_stderr_contains("[..]incompatible with this crate's strategy[..]")
4002+
.run();
4003+
}

0 commit comments

Comments
 (0)