I’m working on a Rust library that provides access to some hardware devices. There are two device types, 1 and 2, and the functionality for type 2 is a superset of the functionality for type 1.
I want to provide different test suites for different circumstances:
I’m using features to represent this behavior: a default feature test-no-device
and optional features test-type-one
and test-type-two
. Then I use the cfg_attr
attribute to ignore the tests based on the selected features:
#[test]
#[cfg_attr(not(feature = "test-type-two"), ignore)]
fn test_exclusive() {
// ...
}
#[test]
#[cfg_attr(not(any(feature = "test-type-two", feature = "test-type-one")), ignore)]
fn test_shared() {
// ...
}
This is rather cumbersome as I have to duplicate this condition for every test and the conditions are hard to read and maintain.
Is there any simpler way to manage the test suites?
I tried to set the ignore
attribute when declaring the module, but apparently it can only be set for each test
function. I think I could disable compilation of the excluded tests by using cfg
on the module, but as the tests should always compile, I would like to avoid that.
Is there a simple way to conditionally enable or ignore entire test suites in Rust?
The easiest is to not even compile the tests:
#[cfg(test)]
mod test {
#[test]
fn no_device_needed() {}
#[cfg(feature = "test1")]
mod test1 {
fn device_one_needed() {}
}
#[cfg(feature = "test2")]
mod test2 {
fn device_two_needed() {}
}
}
I have to duplicate this condition for every test and the conditions are hard to read and maintain.
This is a candidate for a macro.
macro_rules! device_test {
(no-device, $name:ident, {$($body:tt)+}) => (
#[test]
fn $name() {
$($body)+
}
);
(device1, $name:ident, {$($body:tt)+}) => (
#[test]
#[cfg_attr(not(feature = "test-type-one"), ignore)]
fn $name() {
$($body)+
}
);
(device2, $name:ident, {$($body:tt)+}) => (
#[test]
#[cfg_attr(not(feature = "test-type-two"), ignore)]
fn $name() {
$($body)+
}
);
}
device_test!(no-device, one, {
assert_eq!(2, 1+1)
});
device_test!(device1, two, {
assert_eq!(3, 1+1)
});
the functionality for type 2 is a superset of the functionality for type 1
Reflect that in your feature definitions to simplify the code:
[features]
test1 = []
test2 = ["test1"]
If you do this, you shouldn't need to have any
or all
in your config attributes.
a default feature
test-no-device
This doesn't seem useful; instead use normal tests guarded by the normal test config:
#[cfg(test)]
mod test {
#[test]
fn no_device_needed() {}
}
If you follow this, you can remove this case from the macro.
I think if you follow both suggestions, you don't even need the macro.