I'm currently working on a Rust project that has a library target as well as a binary target.
The library defines two panic handlers, one for production use and one for testing, which are conditionally compiled using the #[cfg(test)] attribute. This works fine for unit testing src/lib.rs, but as soon as integration tests in tests/ (or src/main.rs for that matter), the "production" panic handler is linked in, i.e. in those circumstances, lib.rs gets compiled with test=false.
Is there some way to prevent/configure that?
This is pretty minimally reproducible with the following directory structure:
Cargo.toml
src/lib.rs
src/main.rs
The manifest looks like this:
# Cargo.toml
...
[lib]
name = "kernel"
[[bin]]
path = "src/main.rs"
name = "titanium"
Code for the binary:
// main.rs
fn main() {
println!("Hello, world!");
}
#[test]
fn it_works() {
assert_eq!(kernel::hello(), "Hello!".to_string());
}
... and for the library:
// lib.rs
#[cfg(test)]
pub fn hello() -> String {
"Hello!".to_string()
}
#[cfg(not(test))]
pub fn hello() -> String {
"Goodbye!".to_string()
}
#[test]
fn it_works() {
assert_eq!(hello(), "Hello!".to_string())
}
Answer Above behaviour is in fact desirable for probably 99% of use cases, however, mine isn't one of them.
For my use case (testsuite for toy OS running in a VM "headless", i.e. with output only to stdout of the host machine) I ended up defining a feature to switch panic handlers. Adapted to above example it now looks like this:
# Cargo.toml
...
[features]
test_qemu_headless = []
The binary then remains unchanged, and in the library I do
// lib.rs
#[cfg(feature = "test_qemu_headless")]
pub fn hello() -> String {
"Hello!".to_string()
}
#[cfg(not(feature = "test_qemu_headless"))]
pub fn hello() -> String {
"Goodbye!".to_string()
}
#[test]
fn it_works() {
assert_eq!(hello(), "Hello!".to_string())
}
The tests are then run with
cargo test --features test_qemu_headless
The lib target gets compiled twice: Once when running the unit tests; here, #[cfg(test)]
is active because the lib is currently being compiled as rustc --test
. The second time when running the integration tests; here, #[cfg(test)]
is not active because the lib is being compiled normally and then linked to the integration test modules, which themselves are compiled as tests. Put differently, when used with integration tests, the lib target has no idea it is being compiled and used in tests (which to some degree is the point of an integration test).
What you are looking for is either #[cfg(debug_assertions)]
to decorate your panic handler. This attribute is active whenever the code gets compiled in "debug" mode, for whatever reason. Using this attribute, your test-handler will be active for both unit- and integration tests.
However, if you compile the bin target in "debug" mode and run it (e.g. via cargo run
without --release
), or if your library is depended on by some other crate that is compiled in "debug" mode, the test-handler in the library will be active as well, which may not be what you want. If so, you'll need to define a feature
for your crate, which the tests that depend on your panic handler can use as a flag for conditional compilation (e.g. foocrate_use_internal_dummy_panic_handler
). You'll then need to activate that feature - which becomes the global knob - when running your tests. There is also a required-feature
-field in the manifest.