Implementing quickcheck::Arbitrary only when testing crates


#1

Hi,

In my (really simplified) project I have two crates: “my-stdlib” and “my-crate”. The “my-stdlib” crate should contain only code which doesn’t depend on anything outside Rust’s stdlib (when doing release build). It contains things like type definitions, helper functions, etc.

I would like to implement quickcheck::Arbitrary for these types in “my-stdlib” so that these implementations are only available when someone somewhere runs “cargo test”. This brings quickcheck dependency to “my-stdlib”, but I has hoping it’s only dev-dependency.

The actual quickcheck properties are then defined in place of actual use, for example in this case it’s “my-crate”.

This is what I have in my-stdlib/src/lib.rs:

#[derive(Clone, Debug, PartialEq)]
pub enum MyEnum {
    One,
    Two,
    Three,
}

#[cfg(test)]
extern crate quickcheck;

#[cfg(test)]
impl quickcheck::Arbitrary for MyEnum {
    fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
        let vals = &[
            MyEnum::One,
            MyEnum::Two,
            MyEnum::Three,
        ];
        g.choose(vals).expect("choose value").clone()
    }
}

And in my-crate/src/lib.rs is the code I try to test with quickcheck:

extern crate my_stdlib;

use my_stdlib::MyEnum;

pub fn from_enum(val: &MyEnum) -> u8 {
    match *val {
        MyEnum::One => 1,
        MyEnum::Two => 2,
        MyEnum::Three => 3,
    }
}

pub fn to_enum(val: u8) -> MyEnum {
    match val {
        1 => MyEnum::One,
        2 => MyEnum::Two,
        3 => MyEnum::Three,
        _ => panic!("Invalid value"),
    }
}


#[cfg(test)]
#[macro_use]
extern crate quickcheck;

#[cfg(test)]
mod tests {
    use my_stdlib::MyEnum;
    use super::*;

    quickcheck! {
        fn prop_myenum(xs: MyEnum) -> bool {
            xs == to_enum(from_enum(&xs))
        }
    }
}

The error I get is:

error[E0277]: the trait bound `my_stdlib::MyEnum: quickcheck::Arbitrary` is not satisfied
  --> my-crate/src/lib.rs:32:5
   |
32 | /     quickcheck! {
33 | |         fn prop_myenum(xs: MyEnum) -> bool {
34 | |             xs == to_enum(from_enum(&xs))
35 | |         }
36 | |     }
   | |_____^ the trait `quickcheck::Arbitrary` is not implemented for `my_stdlib::MyEnum`
   |
   = note: required because of the requirements on the impl of `quickcheck::Testable` for `fn(my_stdlib::MyEnum) -> bool`
   = note: required by `quickcheck::quickcheck`
   = note: this error originates in a macro outside of the current crate

Is there some kind of way to achieve this?


Cfg(test) doesn't propagate to external crates
Cargo feature pass through to another crate
#2

We can get close by using a feature: Specifying optional = true in the metadata for a dependency foo produces a feature named foo that the user can enable.

(Actually, I’ve never played around with features before, so I used this post as an excuse to try them out a bit. :smile:)

This will allow user crates to dodge the dependency in the case where the user crate doesn’t need quickcheck at all. However, I don’t see a way to conditionally toggle features, so users that want to use quickcheck during tests will end up also depending on it in regular builds.

dep/Cargo.toml

[package]
name = "dep"
version = "0.1.0"

[dependencies]
quickcheck = { version = "0.4.1", optional = true }

dep/src/lib.rs

#[derive(Clone)]
pub struct Unit;

#[cfg(feature = "quickcheck")]
extern crate quickcheck;

#[cfg(feature = "quickcheck")]
impl quickcheck::Arbitrary for Unit {
    fn arbitrary<G: quickcheck::Gen>(_: &mut G) -> Unit { Unit }
}

user/Cargo.toml

[package]
name = "user"
version = "0.1.0"

[dependencies]
dep = { version = "0.1.0", features = ["quickcheck"] }
quickcheck = "0.4.1"

user/src/main.rs

extern crate quickcheck;
extern crate dep;

use quickcheck::Arbitrary;

fn main() {
    // check that the impl exists
    check_arbitrary(dep::Unit);
}

fn check_arbitrary<A:Arbitrary>(a: A) {}

(I tried putting dep = { version = "0.1.0", features = ["quickcheck"] } as a dev-dependency and dep = "0.1.0" as a regular dependency, but this simply added the quickcheck feature to the regular dependency)


#3

I think the user can have their own feature with a different name, something like:

[dependencies]
quickcheck = { version = "0.4.1", optional = true }

[features]
qc = ["quickcheck", "dep/quickcheck"]

#4

Thanks @ExpHP and @cuviper!

Using features I achieved what I wanted. Truly amazing.