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}