reltester/
lib.rs

1//! **Rel**ation **tester** is a small testing utility for automatically
2//! checking the correctness of `[Partial]Eq`, `[Partial]Ord`, `Hash`, and
3//! `[DoubleEnded|Fused]Iterator` trait implementations. It's most useful when
4//! used in conjuction with
5//! [`quickcheck`](https://github.com/BurntSushi/quickcheck) or some other
6//! property-based testing framework.
7//!
8//! # Rationale
9//!
10//! Imagine a scenario where you have a type `Foo` with a custom implementation
11//! of either [`PartialEq`], [`Eq`], [`PartialOrd`], or [`Ord`]. By "custom" we mean
12//! hand-written as opposed to derived. The Rust compiler alone cannot verify
13//! the correctness of these implementations and thus it is up to you, the
14//! programmer, to uphold certain invariants about the specific [binary
15//! relation](https://en.wikipedia.org/wiki/Binary_relation) that you're
16//! implementing. For example, if you implement [`PartialEq`] for `Foo`, you must
17//! guarantee that `foo1 == foo2` implies `foo2 == foo1` (*symmetry*).
18//!
19//! Other traits such as [`Hash`] and [`Iterator`] mandate their own invariants as
20//! well – some of which are very intuitive, and
21//! [others](https://doc.rust-lang.org/std/hash/trait.Hash.html#prefix-collisions)
22//! which are not. It's especially common for less-than-perfect implementations
23//! of the [`std::iter`] family of traits to introduce off-by-one
24//! bugs[^1] [^2] [^3] [^4] among others.
25//!
26//! The idea is, instead of keeping these invariants in your head whenever you
27//! go about manually implementing one of these traits in your codebase, you can
28//! add a Reltester check to your test suite and have a higher degree of
29//! confidence that your implementation is correct.
30//!
31//! # How to use
32//!
33//! 1. Write some tests that generate random values of the type you wish to
34//!    test. You can do this by hand or using crates such as
35//!    [`quickcheck`](https://github.com/BurntSushi/quickcheck) and
36//!    [`proptest`](https://github.com/proptest-rs/proptest). Calling the checkers
37//!    on static, non-randomized values is possible but is less effective in
38//!    catching bugs.
39//! 2. Based on the traits that your type implements, call the appropriate checker(s):
40//!
41//!    - [`reltester::eq`](eq) for [`Eq`];
42//!    - [`reltester::ord`](ord) for [`Ord`];
43//!    - [`reltester::partial_eq`](partial_eq) for [`PartialEq`];
44//!    - [`reltester::partial_ord`](partial_ord) for [`PartialOrd`];
45//!    - [`reltester::hash`](hash) for [`Hash`];
46//!    - [`reltester::iterator`](iterator) for [`Iterator`];
47//!    - [`reltester::fused_iterator`](fused_iterator) for [`FusedIterator`];
48//!    - [`reltester::double_ended_iterator`](double_ended_iterator) for [`DoubleEndedIterator`];
49//!
50//!    Some of these functions take multiple (two or three) values of the same
51//!    type. This is because it takes up to three values to test some
52//!    invariants.
53//!
54//! The [`reltester::invariants`](invariants) module is available for more
55//! granular checks if you can't satisfy the type bounds of the main functions.
56//!
57//! ## Multi-type relations: `Foo: PartialEq<Bar>` and `Foo: PartialOrd<Bar>`
58//!
59//! In some cases your [`PartialEq`] and [`PartialOrd`] implementations
60//! may use a non-`Self` type parameter. (Note: [`Eq`] and [`Ord`] don't accept
61//! type parameters and this use case doesn't apply to them.) Reltester
62//! supports this use case and exposes granular invariant checking functions in
63//! the [`invariants`] module with more lax type constraints.
64//!
65//! ## Examples
66//!
67//! ### `f32` (`PartialEq`, `PartialOrd`)
68//!
69//! ```rust
70//! use reltester;
71//! use quickcheck_macros::quickcheck;
72//!
73//! #[quickcheck]
74//! fn test_f32(a: f32, b: f32, c: f32) -> bool {
75//!     // Let's check if `f32` implements `PartialEq` and `PartialOrd` correctly
76//!     // (spoiler: it does).
77//!     reltester::partial_eq(&a, &b, &c).is_ok()
78//!         && reltester::partial_ord(&a, &b, &c).is_ok()
79//! }
80//! ```
81//!
82//! ### `u32` (`Hash`)
83//!
84//! ```rust
85//! use reltester;
86//! use quickcheck_macros::quickcheck;
87//!
88//! #[quickcheck]
89//! fn test_u32(a: u32, b: u32) -> bool {
90//!     // Unlike `f32`, `u32` implements both `Eq` and `Hash`, which allows us to
91//!     // test `Hash` invariants.
92//!     reltester::hash(&a, &b).is_ok()
93//! }
94//! ```
95//!
96//! ### `Vec<u32>` (`DoubleEndedIterator`, `FusedIterator`, `Iterator`)
97//!
98//! ```rust
99//! use reltester;
100//! use quickcheck_macros::quickcheck;
101//!
102//! #[quickcheck]
103//! fn test_vec_u32(nums: Vec<u32>) -> bool {
104//!     // `Iterator` is implied and checked by both `DoubleEndedIterator` and
105//!     // `FusedIterator`.
106//!     reltester::double_ended_iterator(nums.iter()).is_ok()
107//!         && reltester::fused_iterator(nums.iter()).is_ok()
108//! }
109//! ```
110//!
111//! # TL;DR invariants of the comparison traits
112//!
113//! Chances are you don't need to concern yourself with the mathematical definitions of
114//! comparison traits; as long as your implementations are sensible and your
115//! `reltester` tests pass, you can move on and assume your implementations are
116//! correct. The required invariants are listed here only for the sake of
117//! completeness.
118//!
119//! - [`PartialEq`] requires **symmetry** and **transitivity** of `==` whenever applicable ([partial
120//!   equivalence
121//!   relation](https://en.wikipedia.org/wiki/Partial_equivalence_relation) in the
122//!   case of `Rhs == Self`).
123//! - [`Eq`] requires **symmetry**, **transitivity**, and **reflexivity** of `==` ([equivalence relation](https://en.wikipedia.org/wiki/Equivalence_relation)).
124//! - [`PartialOrd`] requires **symmetry** of `==`, **transitivity** of `>`,
125//!   `==`, and `<`; and **duality** of `>` and `<`. Note that duality is not
126//!   common mathematical
127//!   terminology, it's just what the Rust [`std`] uses to describe `a > b iff b < a`.
128//!   Thus the exact mathematical definition of [`PartialOrd`] seems [open to
129//!   debate](https://users.rust-lang.org/t/traits-in-std-cmp-and-mathematical-terminology/69887),
130//!   though it's generally understood to mean [strict partial
131//!   order](https://en.wikipedia.org/wiki/Partially_ordered_set#Strict_partial_orders).
132//! - [`Ord`] requires **symmetry** and **reflexivity** of `==`; **transitivity** of `>`, `==`, and `<`; and **duality** of `>` and `<`.
133//!   `==`; **transitivity** and **duality** of `>` and `<`; and must be **trichotomous**[^5]. Just like
134//!   [`PartialOrd`], the mathematical definition of [`Ord`] is a bit open to
135//!   interpretation, though it's generally understood to mean [total
136//!   order](https://en.wikipedia.org/wiki/Total_order#Strict_and_non-strict_total_orders).
137//!
138//! In addition to the above, trait method default implementation overrides (for e.g.
139//! [`PartialOrd::lt`] or [`Ord::max`]) must have the same behavior as the
140//! default implementations. `reltester` always checks these for you.
141//!
142//!
143//! [^1]: <https://github.com/rust-lang/rust/issues/41964>
144//!
145//! [^2]: <https://github.com/bevyengine/bevy/pull/7469>
146//!
147//! [^3]: <https://github.com/bluejekyll/trust-dns/issues/1638>
148//!
149//! [^4]: <https://github.com/sparsemat/sprs/issues/261>
150//!
151//! [^5]: Trichotomy is a corollary that follows from the definitions of `>`,
152//! `==`, and `<` based on [`Ordering`](std::cmp::Ordering).
153
154#![allow(clippy::eq_op, clippy::double_comparisons)]
155
156pub mod error;
157pub mod invariants;
158
159use error::*;
160use std::{hash::Hash, iter::FusedIterator};
161
162/// Checks the correctness of the [`Ord`] trait (and [`Eq`] and [`PartialOrd`]
163/// by extension) for some values.
164pub fn ord<T>(a: &T, b: &T, c: &T) -> Result<(), Error>
165where
166    T: Ord,
167{
168    eq(a, b, c)?;
169    partial_ord(a, b, c)?;
170
171    invariants::ord_methods_consistency(a, b, c)?;
172
173    Ok(())
174}
175
176/// Checks the correctness of the [`PartialOrd`] trait (and [`PartialEq`] by
177/// extension) for some values.
178pub fn partial_ord<T>(a: &T, b: &T, c: &T) -> Result<(), Error>
179where
180    T: PartialOrd,
181{
182    partial_eq(a, b, c)?;
183
184    invariants::partial_ord_methods_consistency(a, b)?;
185    invariants::partial_ord_duality(a, b)?;
186    invariants::partial_ord_transitivity(a, b, c)?;
187
188    Ok(())
189}
190
191/// Checks the correctness of the [`Eq`] trait (and [`PartialEq`] by extension)
192/// for some values.
193///
194/// The type bound is intentionally [`PartialEq`] instead of [`Eq`] to allow
195/// for negative testing, i.e. ensuring that your [`PartialEq`] implementor
196/// *doesn't* implement [`Eq`] when it shouldn't.
197pub fn eq<T>(a: &T, b: &T, c: &T) -> Result<(), Error>
198where
199    T: PartialEq<T>,
200{
201    partial_eq(a, b, c)?;
202
203    // Checking `Eq` is the same as checking `PartialEq`, except it also
204    // requires reflexivity.
205    invariants::eq_reflexivity(a)?;
206
207    Ok(())
208}
209
210/// Checks the correctness of the [`PartialEq`] trait
211/// for some values.
212pub fn partial_eq<T>(a: &T, b: &T, c: &T) -> Result<(), PartialEqError>
213where
214    T: PartialEq,
215{
216    invariants::partial_eq_methods_consistency(a, b)?;
217    invariants::partial_eq_symmetry(a, b)?;
218    invariants::partial_eq_transitivity(a, b, c)?;
219
220    Ok(())
221}
222
223/// Checks the correctness of the [`Hash`] trait in relation to [`Eq`] for some
224/// values.
225pub fn hash<K>(a: &K, b: &K) -> Result<(), HashError>
226where
227    K: Hash + Eq + ?Sized,
228{
229    invariants::hash_consistency_with_eq(a, b)?;
230    invariants::hash_prefix_collision(a, b)?;
231
232    Ok(())
233}
234
235/// Checks the correctness of the [`Iterator`] trait for some value `iter`.
236///
237/// Note that `iter` must be a finite iterator.
238pub fn iterator<I>(iter: I) -> Result<(), IteratorError>
239where
240    I: Iterator + Clone,
241    I::Item: PartialEq,
242{
243    invariants::iterator_size_hint(iter.clone())?;
244    invariants::iterator_count(iter.clone())?;
245    invariants::iterator_last(iter)?;
246
247    Ok(())
248}
249
250/// Checks the correctness of the [`DoubleEndedIterator`] trait (and
251/// [`Iterator`] by extension) for some value `iter`.
252///
253/// Note that `iter` must be a finite iterator.
254pub fn double_ended_iterator<I>(iter: I) -> Result<(), IteratorError>
255where
256    I: DoubleEndedIterator + Clone,
257    I::Item: PartialEq,
258{
259    iterator(iter.clone())?;
260
261    invariants::double_ended_iterator_next_back(iter)?;
262
263    Ok(())
264}
265
266/// Checks the correctness of the [`FusedIterator`] trait (and
267/// [`Iterator`] by extension) for some value `iter`.
268///
269/// Note that `iter` must be a finite iterator.
270pub fn fused_iterator<I>(iter: I) -> Result<(), IteratorError>
271where
272    I: FusedIterator + Clone,
273    I::Item: PartialEq,
274{
275    iterator(iter.clone())?;
276
277    invariants::fused_iterator_none_forever(iter)?;
278
279    Ok(())
280}
281
282#[allow(dead_code)]
283#[doc = include_str!("../README.md")]
284struct ReadmeDoctest;