tested_trait/
lib.rs

1#![cfg_attr(not(any(test, doc)), no_std)]
2#![deny(missing_docs, unnameable_test_items)]
3
4//! # `tested-trait`
5//!
6//! `tested-trait` provides two macros -- [`tested_trait`] and [`test_impl`] -- that make it
7//! possible to include associated tests in trait definitions and instantiate associated tests
8//! to test implementations of the trait.
9//!
10//! ## Example
11//!
12//! Consider a memory allocator trait like [`GlobalAlloc`](core::alloc::GlobalAlloc).
13//!
14//! The [`alloc`](core::alloc::GlobalAlloc::alloc) method takes a [`Layout`](core::alloc::Layout)
15//! describing size and alignment requirements, and returns a pointer -- the returned pointer
16//! *should* adhere to layout description, but nothing enforces this contract.
17//!
18//! By annotating the trait definition with the [`tested_trait`] macro, a test can be associated
19//! with the trait to verify that allocations result in validly aligned pointers -- at least for a
20//! simple sequence of allocations:
21//!
22//! ```
23//! # use tested_trait::{tested_trait, test_impl};
24//! use std::alloc::Layout;
25//!
26//! #[tested_trait]
27//! trait Allocator {
28//!     unsafe fn alloc(&mut self, layout: Layout) -> *mut u8;
29//!
30//!     #[test]
31//!     fn alloc_respects_alignment() where Self: Default {
32//!         let mut alloc = Self::default();
33//!         let layout = Layout::from_size_align(10, 4).unwrap();
34//!         for _ in 0..10 {
35//!             let ptr = unsafe { alloc.alloc(layout) };
36//!             assert_eq!(ptr.align_offset(layout.align()), 0);
37//!         }
38//!     }
39//! }
40//! ```
41//!
42//! Note the test's `where Self: Default` bound, which it uses to construct an allocator.
43//! Unlike freestanding `#[test]`s, associated tests may have `where` clauses to require additional
44//! functionality for testing purposes.
45//!
46//! Implementers can then use [`test_impl`] to verify that their allocators pass this tests and any
47//! others associated with the trait.
48//! For instance, we can test the default system allocator:
49//!
50//! ```
51//! # use tested_trait::{tested_trait, test_impl};
52//! # use std::alloc::Layout;
53//! # #[tested_trait]
54//! # trait Allocator {
55//! #     unsafe fn alloc(&mut self, layout: Layout) -> *mut u8;
56//! #     #[test]
57//! #     fn alloc_respects_alignment() where Self: Default {
58//! #         let mut alloc = Self::default();
59//! #         let layout = Layout::from_size_align(10, 4).unwrap();
60//! #         for _ in 0..10 {
61//! #             let ptr = unsafe { alloc.alloc(layout) };
62//! #             assert_eq!(ptr.align_offset(layout.align()), 0);
63//! #         }
64//! #     }
65//! # }
66//! use std::alloc;
67//!
68//! #[test_impl]
69//! # #[in_integration_test]
70//! impl Allocator for alloc::System {
71//!     unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 {
72//!         alloc::GlobalAlloc::alloc(self, layout)
73//!     }
74//! }
75//! ```
76//!
77//! ... and a flawed allocator that ignores alignment:
78//!
79//! ```should_panic
80//! # use tested_trait::{tested_trait, test_impl};
81//! # use std::alloc::Layout;
82//! # #[tested_trait]
83//! # trait Allocator {
84//! #     unsafe fn alloc(&mut self, layout: Layout) -> *mut u8;
85//! #     #[test]
86//! #     fn alloc_respects_alignment() where Self: Default {
87//! #         let mut alloc = Self::default();
88//! #         let layout = Layout::from_size_align(10, 4).unwrap();
89//! #         for _ in 0..10 {
90//! #             let ptr = unsafe { alloc.alloc(layout) };
91//! #             assert_eq!(ptr.align_offset(layout.align()), 0);
92//! #         }
93//! #     }
94//! # }
95//! struct BadAllocator<const SIZE: usize> {
96//!     buf: Box<[u8; SIZE]>,
97//!     next: usize,
98//! }
99//!
100//! // Note the `BadAllocator<1024>: Allocator` argument here -- the implementation is generic,
101//! // so we use it to specify which concrete implementation should be tested.
102//! #[test_impl(BadAllocator<1024>: Allocator)]
103//! # #[in_integration_test]
104//! impl<const SIZE: usize> Allocator for BadAllocator<SIZE> {
105//!     unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 {
106//!         if self.next + layout.size() <= self.buf.len() {
107//!             let ptr = &mut self.buf[self.next] as *mut u8;
108//!             self.next += layout.size();
109//!             ptr
110//!         } else {
111//!             core::ptr::null_mut()
112//!         }
113//!     }
114//! }
115//!
116//! // Implement Default since the associated tests require it -- if this implementation
117//! // is omitted, the #[test_impl] attribute will emit a compilation error.
118//! impl<const SIZE: usize> Default for BadAllocator<SIZE> {
119//!     fn default() -> Self {
120//!         Self { buf: Box::new([0; SIZE]), next: 0 }
121//!     }
122//! }
123//! ```
124//!
125//! ## Features
126//!
127//! - [x] Associating tests with trait definitions
128//! - [x] Running associated tests against non-generic trait implementations and concrete
129//!       instantiations of generic implementations (see [below](#testing-generic-implementations))
130//! - [x] Most of the standard `#[test]` syntax (see [below](#supported-test-syntax))
131//! - [ ] Understandable names for generated tests: currently, annotating `impl<T> Foo<T> for
132//!       Bar<T>` with [`test_impl`] generates tests named `tested_trait_test_impl_Foo_{N}` --
133//!       ideally they'd be named `tested_trait_test_impl_Foo<{T}>_for_Bar<{T}>`, but converting
134//!       types into valid identifiers is difficult
135//! - [ ] Testing trait implementations for unsized types
136//! - [ ] Support for property-based tests with
137//!       [`quickcheck`](https://docs.rs/quickcheck/latest/quickcheck/) and
138//!       [`proptest`](https://docs.rs/proptest/latest/proptest/)
139//! - [ ] `#![no_std]` support: this crate itself is `#![no-std]`, but the tests it defines require
140//!       [`std::println!`] and [`std::panic::catch_unwind()`]
141//!
142//! ### Testing generic implementations
143//!
144//! Generic implementations of traits generate *concrete implementations* for each instantiation of
145//! their generic parameters. It's impossible to test all of these implementations, so annotating a
146//! generic implementation with *just* [`#[test_impl]`](test_impl) fails to compile:
147//!
148//! ```compile_fail
149//! # use tested_trait::{tested_trait, test_impl};
150//! #[tested_trait]
151//! trait Wrapper<T> {
152//!     fn wrap(value: T) -> Self;
153//!     fn unwrap(self) -> T;
154//!
155//!     #[test]
156//!     fn wrap_then_unwrap() where T: Default + PartialEq + Clone {
157//!         let value = T::default();
158//!         assert!(Self::wrap(value.clone()).unwrap() == value);
159//!     }
160//! }
161//!
162//! #[test_impl]
163//! impl<T> Wrapper<T> for Option<T> {
164//!     fn wrap(value: T) -> Self {
165//!         Some(value)
166//!     }
167//!     fn unwrap(self) -> T {
168//!         self.unwrap()
169//!     }
170//! }
171//! ```
172//!
173//! To test such an implementation, pass a non-empty list of `Type: Trait` arguments to
174//! [`test_impl`] to specify which concrete implementations to test:
175//!
176//! ```
177//! # use tested_trait::{tested_trait, test_impl};
178//! # #[tested_trait]
179//! # trait Wrapper<T> {
180//! #     fn wrap(value: T) -> Self;
181//! #     fn unwrap(self) -> T;
182//! #     #[test]
183//! #     fn wrap_then_unwrap() where T: Default + PartialEq + Clone {
184//! #         let value = T::default();
185//! #         assert!(Self::wrap(value.clone()).unwrap() == value);
186//! #     }
187//! # }
188//! #[test_impl(Option<u32>: Wrapper<u32>, Option<String>: Wrapper<String>)]
189//! impl<T> Wrapper<T> for Option<T> {
190//!     fn wrap(value: T) -> Self {
191//!         Some(value)
192//!     }
193//!     fn unwrap(self) -> T {
194//!         self.unwrap()
195//!     }
196//! }
197//! ```
198//!
199//! ### Supported `#[test]` syntax
200//!
201//! Most of the standard `#[test]` syntax is supported:
202//!
203//! ```
204//! # use tested_trait::{tested_trait, test_impl};
205//! #[tested_trait]
206//! trait Foo {
207//!     #[test]
208//!     fn standard_test() {}
209//!
210//!     #[test]
211//!     fn result_returning_test() -> Result<(), String> {
212//!         Ok(())
213//!     }
214//!
215//!     #[test]
216//!     #[should_panic]
217//!     fn should_panic_test1() {
218//!         panic!()
219//!     }
220//!
221//!     #[test]
222//!     #[should_panic = "ahhh"]
223//!     fn should_panic_test2() {
224//!         panic!("ahhhhh")
225//!     }
226//!
227//!     #[test]
228//!     #[should_panic(expected = "ahhh")]
229//!     fn should_panic_test3() {
230//!         panic!("ahhhhh")
231//!     }
232//! }
233//!
234//! #[test_impl]
235//! # #[in_integration_test]
236//! impl Foo for () {}
237//! ```
238//!
239//! ## Comparison to `trait_tests`
240//!
241//! This crate provides similar functionality to the [`trait_tests`] crate, with the following
242//! notable differences:
243//!
244//! - `trait_tests` defines tests in separate `FooTests` traits,
245//!   while this crate defines them inline in trait definitions
246//! - `trait_tests` allows placing bounds on `FooTests` traits,
247//!   while this crate allows placing them on test functions themselves
248//! - `trait_tests` defines tests as unmarked associated functions,
249//!   while this crate supports the standard `#[test]` syntax and the niceties that come with it
250//! - From my testing, this crate's macros are more hygienic and robust to varying inputs than those
251//!   of `trait_tests`
252//!
253//! [`trait_tests`]: https://crates.io/crates/trait_tests
254
255/// Compiles functions marked with `#[test]` in the definition of the annotated trait into a test
256/// suite that can be instantiated with [`test_impl`] to verify an implementation of the trait.
257///
258/// See the [crate-level docs](crate) for examples and more details.
259pub use tested_trait_macros::tested_trait;
260
261/// Tests the annotated trait implementation against associated tests defined with [`tested_trait`].
262///
263/// See the [crate-level docs](crate) for examples and more details.
264pub use tested_trait_macros::test_impl;
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    mod object_safety {
271        #[super::tested_trait]
272        trait Foo {}
273        #[super::test_impl]
274        impl Foo for () {}
275        const _: &dyn Foo = &();
276    }
277
278    mod default_bound {
279        #[super::tested_trait]
280        trait Foo {
281            fn must_be_true(&self) -> bool;
282
283            #[test]
284            fn test_simple()
285            where
286                Self: Default,
287            {
288                let foo = Self::default();
289                assert!(foo.must_be_true());
290            }
291        }
292
293        #[super::test_impl]
294        impl Foo for () {
295            fn must_be_true(&self) -> bool {
296                true
297            }
298        }
299    }
300
301    mod should_panic {
302        #[super::tested_trait]
303        trait OptionLike<T> {
304            const NONE: Self;
305            fn unwrap(self) -> T;
306
307            #[test]
308            #[should_panic]
309            fn unwrap_none() {
310                Self::NONE.unwrap();
311            }
312        }
313
314        #[super::test_impl(Option<()>: OptionLike<()>)]
315        impl<T> OptionLike<T> for Option<T> {
316            const NONE: Self = None;
317            fn unwrap(self) -> T {
318                self.unwrap()
319            }
320        }
321    }
322
323    #[test]
324    #[should_panic]
325    fn should_panic_doesnt_panic() {
326        #[tested_trait]
327        trait Foo {
328            #[test]
329            #[should_panic]
330            fn doesnt_panic() {}
331        }
332
333        #[test_impl]
334        #[in_integration_test]
335        impl Foo for () {}
336    }
337
338    #[test]
339    fn concrete_impls() {
340        #[tested_trait]
341        trait Foo {}
342
343        #[test_impl((): Foo, u32: Foo, String: Foo)]
344        #[in_integration_test]
345        impl<T> Foo for T {}
346    }
347}
348
349#[cfg(doctest)]
350mod doctests {
351    // TODO: this should be a test in `tests/ui`, but stable and nightly
352    // currently produce different compiler errors.
353    /// ```compile_fail
354    /// use tested_trait::tested_trait;
355    ///
356    /// fn main() {
357    ///     #[tested_trait]
358    ///     trait Foo {
359    ///         #[test]
360    ///         #[should_panic]
361    ///         fn test() -> Result<(), ()> {
362    ///             Ok(())
363    ///         }
364    ///     }
365    /// }
366    /// ```
367    struct ShouldPanicReturnsResult;
368}