tested-trait
tested-trait
provides two macros -- [tested_trait
] and [test_impl
] -- that make it
possible to include associated tests in trait definitions and instantiate associated tests
to test implementations of the trait.
Consider a memory allocator trait like GlobalAlloc
.
The alloc
method takes a Layout
describing size and alignment requirements, and returns a pointer -- the returned pointer
should adhere to layout description, but nothing enforces this contract.
By annotating the trait definition with the [tested_trait
] macro, a test can be associated
with the trait to verify that allocations result in validly aligned pointers -- at least for a
simple sequence of allocations:
```rust use std::alloc::Layout;
trait Allocator { unsafe fn alloc(&mut self, layout: Layout) -> *mut u8;
#[test]
fn alloc_respects_alignment() where Self: Default {
let mut alloc = Self::default();
let layout = Layout::from_size_align(10, 4).unwrap();
for _ in 0..10 {
let ptr = unsafe { alloc.alloc(layout) };
assert_eq!(ptr.align_offset(layout.align()), 0);
}
}
} ```
Note the test's where Self: Default
bound, which it uses to construct an allocator.
Unlike freestanding #[test]
s, associated tests may have where
clauses to require additional
functionality for testing purposes.
Implementers can then use [test_impl
] to verify that their allocators pass this tests and any
others associated with the trait.
For instance, we can test the default system allocator:
```rust use std::alloc;
impl Allocator for alloc::System { unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 { alloc::GlobalAlloc::alloc(self, layout) } } ```
... and a flawed allocator that ignores alignment:
```rust
struct BadAllocator
// Note the BadAllocator<1024>: Allocator
argument here -- the implementation is generic,
// so we use it to specify which concrete implementation should be tested.
impl
// Implement Default since the associated tests require it -- if this implementation
// is omitted, the #[test_impl] attribute will emit a compilation error.
impl
#[test]
syntax (see below)impl<T> Foo<T> for
Bar<T>
with [test_impl
] generates tests named tested_trait_test_impl_Foo_{N}
--
ideally they'd be named tested_trait_test_impl_Foo<{T}>_for_Bar<{T}>
, but converting
types into valid identifiers is difficultquickcheck
and
proptest
#![no_std]
support: this crate itself is #![no-std]
, but the tests it defines require
[std::println!
] and [std::panic::catch_unwind()
]Generic implementations of traits generate concrete implementations for each instantiation of
their generic parameters. It's impossible to test all of these implementations, so annotating a
generic implementation with just #[test_impl]
fails to compile:
```compile_fail
trait Wrapper
#[test]
fn wrap_then_unwrap() where T: Default + PartialEq + Clone {
let value = T::default();
assert!(Self::wrap(value.clone()).unwrap() == value);
}
}
impl
To test such an implementation, pass a non-empty list of Type: Trait
arguments to
[test_impl
] to specify which concrete implementations to test:
```rust
impl
#[test]
syntaxMost of the standard #[test]
syntax is supported:
```rust
trait Foo { #[test] fn standard_test() {}
#[test]
fn result_returning_test() -> Result<(), String> {
Ok(())
}
#[test]
#[should_panic]
fn should_panic_test1() {
panic!()
}
#[test]
#[should_panic = "ahhh"]
fn should_panic_test2() {
panic!("ahhhhh")
}
#[test]
#[should_panic(expected = "ahhh")]
fn should_panic_test3() {
panic!("ahhhhh")
}
}
impl Foo for () {} ```
trait_tests
This crate provides similar functionality to the [trait_tests
] crate, with the following
notable differences:
trait_tests
defines tests in separate FooTests
traits,
while this crate defines them inline in trait definitionstrait_tests
allows placing bounds on FooTests
traits,
while this crate allows placing them on test functions themselvestrait_tests
defines tests as unmarked associated functions,
while this crate supports the standard #[test]
syntax and the niceties that come with ittrait_tests
License: MIT OR Apache-2.0