New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generated tests for trait impls #4782

Closed
Kimundi opened this Issue Feb 4, 2013 · 8 comments

Comments

Projects
None yet
8 participants
@Kimundi
Member

Kimundi commented Feb 4, 2013

After an discussion about how for example 0 == -0, and how it interacts with traits, I had the thought it might be nice to have unit tests on traits that get generated for for each impl of it.
Example:

trait Bar {
    static fn zero() -> Self;
    fn neg(&self) -> Self;
}

#[test(trait)]
fn test_bar<T: Bar>() {
    let x: T = Bar::zero();
    let y = x.neg();
    assert x == y;
}

in some other crate:

impl Bar for float {
    static fn zero() -> float { 0.0 }
    fn neg(&self) -> float { - *self }
}

Then a rustc --test for that crate would generate this function:

#[test]
fn test_bar_float() { test_bar::<float>() }

This would require trait test to somehow be made publicly callable from other crates for test compilation.

Alternative example, which might be easier to implement:

trait Bar {
    static fn zero() -> Self;
    fn neg(&self) -> Self;

    #[test]
    fn test_bar() {
        let x: Self = zero();
        let y = x.neg();
        assert x == y;
    }
}
@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Feb 5, 2013

Contributor

This is kind of a nifty idea.

Contributor

nikomatsakis commented Feb 5, 2013

This is kind of a nifty idea.

@sanxiyn

This comment has been minimized.

Show comment
Hide comment
@sanxiyn

sanxiyn Feb 6, 2013

Member

See also #4801.

Member

sanxiyn commented Feb 6, 2013

See also #4801.

@catamorphism

This comment has been minimized.

Show comment
Hide comment
@catamorphism

catamorphism May 23, 2013

Contributor

Neato, but far-future.

Contributor

catamorphism commented May 23, 2013

Neato, but far-future.

@cmr

This comment has been minimized.

Show comment
Hide comment
@cmr

cmr Aug 5, 2013

Member

I don't really understand what this is or why. It's just testing that the invariants the trait assumes are valid for every impl?

Member

cmr commented Aug 5, 2013

I don't really understand what this is or why. It's just testing that the invariants the trait assumes are valid for every impl?

@Kimundi

This comment has been minimized.

Show comment
Hide comment
@Kimundi

Kimundi Aug 5, 2013

Member

@cmr: right, basically predefined generic unit tests for anyone implementing your trait.

Member

Kimundi commented Aug 5, 2013

@cmr: right, basically predefined generic unit tests for anyone implementing your trait.

@huonw

This comment has been minimized.

Show comment
Hide comment
@huonw

huonw Nov 3, 2013

Member

Triage: I like, but no progress.

(It seems like this would need some adjustments for more generic traits that don't have a constructor built-in (i.e. Zero has zero() -> Self, but, say, Add only has add(&Self, &Self) -> Self so there needs to be some way to provide constructors.)

Member

huonw commented Nov 3, 2013

Triage: I like, but no progress.

(It seems like this would need some adjustments for more generic traits that don't have a constructor built-in (i.e. Zero has zero() -> Self, but, say, Add only has add(&Self, &Self) -> Self so there needs to be some way to provide constructors.)

@Gankro

This comment has been minimized.

Show comment
Hide comment
@Gankro

Gankro Jul 12, 2014

Contributor

I think this would make more sense to be an opt-in feature for an implementer. Not necessarily "All implementers of this trait should pass these tests" but "if you implement this trait, here are some great pre-made tests to save you some time". That way we don't have everyone who implements a MutableMap copy-pasting the same insert/remove/len boilerplate. If a structure is simple enough, it could literally see it just doing:

#[cfg(test)]
mod test{
    use super::MyImpl;
    use std::Trait1;
    use std::Trait2;
    use std::Trait3;

    #[test_impl(MyImpl, Trait1)]
    #[test_impl(MyImpl, Trait2)]
    #[test_impl(MyImpl, Trait3)]
}

It should also permit some tests to only be run if other traits are present, as I think we expect a lot of orthogonal traits to be together and interact predictably. For instance it's not a stretch to say that most MutableMaps are going to implement Default and Collection, and so if an impl does, we can provide tests that confirm construction and length and mutation all interact the way we'd expect.

So something like:

#[test(MutableMap)]
    fn test_len<MapImpl: MutableMap<int> + Collection<int> + Default<int>>(){
        let map = MapImpl::default();
        assert!(map.is_empty());
        assert!(map.insert(1));
        assert_eq!(map.len(),1);
        ...
    }

And then:

#[test_impl(MyMap, MutableMap)]

produces all applicable tests. Of course we could have no syntax at all, and just have modules of "good common tests" supplied with traits that implementers manually use. At very least I think that's easy and desirable. I vaguely recall seeing some structure pull in some testing utilities from Deque, though I can't for the life of me find it now. We should have more of that. Any way to increase test reuse.

Contributor

Gankro commented Jul 12, 2014

I think this would make more sense to be an opt-in feature for an implementer. Not necessarily "All implementers of this trait should pass these tests" but "if you implement this trait, here are some great pre-made tests to save you some time". That way we don't have everyone who implements a MutableMap copy-pasting the same insert/remove/len boilerplate. If a structure is simple enough, it could literally see it just doing:

#[cfg(test)]
mod test{
    use super::MyImpl;
    use std::Trait1;
    use std::Trait2;
    use std::Trait3;

    #[test_impl(MyImpl, Trait1)]
    #[test_impl(MyImpl, Trait2)]
    #[test_impl(MyImpl, Trait3)]
}

It should also permit some tests to only be run if other traits are present, as I think we expect a lot of orthogonal traits to be together and interact predictably. For instance it's not a stretch to say that most MutableMaps are going to implement Default and Collection, and so if an impl does, we can provide tests that confirm construction and length and mutation all interact the way we'd expect.

So something like:

#[test(MutableMap)]
    fn test_len<MapImpl: MutableMap<int> + Collection<int> + Default<int>>(){
        let map = MapImpl::default();
        assert!(map.is_empty());
        assert!(map.insert(1));
        assert_eq!(map.len(),1);
        ...
    }

And then:

#[test_impl(MyMap, MutableMap)]

produces all applicable tests. Of course we could have no syntax at all, and just have modules of "good common tests" supplied with traits that implementers manually use. At very least I think that's easy and desirable. I vaguely recall seeing some structure pull in some testing utilities from Deque, though I can't for the life of me find it now. We should have more of that. Any way to increase test reuse.

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Jan 21, 2015

Member

I'm pulling a massive triage effort to get us ready for 1.0. As part of this, I'm moving stuff that's wishlist-like to the RFCs repo, as that's where major new things should get discussed/prioritized.

This issue has been moved to the RFCs repo: rust-lang/rfcs#616

Member

steveklabnik commented Jan 21, 2015

I'm pulling a massive triage effort to get us ready for 1.0. As part of this, I'm moving stuff that's wishlist-like to the RFCs repo, as that's where major new things should get discussed/prioritized.

This issue has been moved to the RFCs repo: rust-lang/rfcs#616

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment