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.

Example

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;

[tested_trait]

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;

[test_impl]

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 { buf: Box<[u8; SIZE]>, next: usize, }

// Note the BadAllocator<1024>: Allocator argument here -- the implementation is generic, // so we use it to specify which concrete implementation should be tested.

[test_impl(BadAllocator<1024>: Allocator)]

impl Allocator for BadAllocator { unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 { if self.next + layout.size() <= self.buf.len() { let ptr = &mut self.buf[self.next] as *mut u8; self.next += layout.size(); ptr } else { core::ptr::null_mut() } } }

// Implement Default since the associated tests require it -- if this implementation // is omitted, the #[test_impl] attribute will emit a compilation error. impl Default for BadAllocator { fn default() -> Self { Self { buf: Box::new([0; SIZE]), next: 0 } } } ```

Features

Testing generic implementations

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

use testedtrait::{testedtrait, test_impl};

[tested_trait]

trait Wrapper { fn wrap(value: T) -> Self; fn unwrap(self) -> T;

#[test]
fn wrap_then_unwrap() where T: Default + PartialEq + Clone {
    let value = T::default();
    assert!(Self::wrap(value.clone()).unwrap() == value);
}

}

[test_impl]

impl Wrapper for Option { fn wrap(value: T) -> Self { Some(value) } fn unwrap(self) -> T { self.unwrap() } } ```

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

[test_impl(Option: Wrapper, Option: Wrapper)]

impl Wrapper for Option { fn wrap(value: T) -> Self { Some(value) } fn unwrap(self) -> T { self.unwrap() } } ```

Supported #[test] syntax

Most of the standard #[test] syntax is supported:

```rust

[tested_trait]

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")
}

}

[test_impl]

impl Foo for () {} ```

Comparison to trait_tests

This crate provides similar functionality to the [trait_tests] crate, with the following notable differences:

License: MIT OR Apache-2.0