├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── examples ├── f32_partial_eq_is_not_reflexive.rs ├── proptest.rs └── quickcheck.rs ├── rust-toolchain.toml ├── src ├── error.rs ├── invariants.rs └── lib.rs └── tests ├── arweave_ord.rs ├── f23_cant_not_eq.rs ├── hash.rs └── iterator.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - run: cargo check --verbose 18 | - run: cargo clippy 19 | - run: cargo test --verbose 20 | - run: cargo test --examples 21 | - run: cargo test --doc 22 | msrv: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: install cargo-binstall 27 | run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash 28 | - run: cargo binstall --version 0.15.1 --no-confirm cargo-msrv 29 | - run: cargo msrv verify 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reltester" 3 | version = "2.0.0" 4 | edition = "2021" 5 | repository = "https://github.com/neysofu/reltester" 6 | license = "MIT" 7 | rust-version = "1.56" 8 | description = "Automatically verify the correctness of [Partial]Eq/Ord implementations" 9 | authors = ["Filippo Neysofu Costa "] 10 | 11 | [dependencies] 12 | rand = "0.8" 13 | thiserror = "1" 14 | 15 | [dev-dependencies] 16 | quickcheck = "1" 17 | quickcheck_macros = "1" 18 | proptest = "1" 19 | proptest-derive = "0.3" 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2023 (c) Filippo Costa 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reltester 2 | 3 | [![Crates.io](https://img.shields.io/crates/l/reltester)](https://github.com/neysofu/reltester/blob/main/LICENSE.txt) [![docs.rs](https://img.shields.io/docsrs/reltester)](https://docs.rs/reltester/latest/reltester/) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/neysofu/reltester/ci.yml)](https://github.com/neysofu/reltester/actions) [![Crates.io](https://img.shields.io/crates/v/reltester)](https://crates.io/crates/reltester) [![min-rustc](https://img.shields.io/badge/min--rustc-1.56-blue)](https://github.com/neysofu/reltester/blob/main/rust-toolchain.toml) 4 | 5 | **Rel**ation **tester** is a small testing utility for automatically checking the correctness of `[Partial]Eq`, `[Partial]Ord`, `Hash`, and `[DoubleEnded|Fused]Iterator` trait implementations. It's most useful when used in conjuction with [`quickcheck`](https://github.com/BurntSushi/quickcheck) or some other property-based testing framework. 6 | 7 | 8 | *Go to the [docs](https://docs.rs/reltester/latest/reltester/)!* 9 | 10 | ## Rationale 11 | 12 | Imagine a scenario where you have a type `Foo` with a custom implementation of either `PartialEq`, `Eq`, `PartialOrd`, or `Ord`. By "custom" we mean hand-written as opposed to derived. The Rust compiler alone cannot verify the correctness of these implementations and thus it is up to you, the programmer, to uphold certain invariants about the specific [binary relation](https://en.wikipedia.org/wiki/Binary_relation) that you're implementing. For example, if you implement `PartialEq` for `Foo`, you must guarantee that `foo1 == foo2` implies `foo2 == foo1` (*symmetry*). 13 | 14 | Other traits such as `Hash` and `Iterator` mandate several invariants as well – some of which are very intuitive, and [others](https://doc.rust-lang.org/std/hash/trait.Hash.html#prefix-collisions) which are not. It's especially common for less-than-perfect implementations of the `std::iter` family of traits to introduce off-by-one bugs[^1][^2][^3][^4] among others. 15 | 16 | The idea is, instead of keeping these invariants in your head whenever you go about manually implementing one of these traits in your codebase, you can add a Reltester check to your test suite and have a higher degree of confidence that your implementation is correct. 17 | 18 | 19 | ## How to use 20 | 21 | 1. Write some tests that generate random values of the type you wish to test. You can do this by hand or using crates such as [`quickcheck`](https://github.com/BurntSushi/quickcheck) and [`proptest`](https://github.com/proptest-rs/proptest). Calling the checkers on static, non-randomized values is possible but is less effective in catching bugs. 22 | 2. Based on the traits that your type implements, call the appropriate checker(s): 23 | 24 | - `reltester::eq` for `Eq`; 25 | - `reltester::ord` for `Ord`; 26 | - `reltester::partial_eq` for `PartialEq`; 27 | - `reltester::partial_ord` for `PartialOrd`; 28 | - `reltester::hash` for `Hash`; 29 | - `reltester::iterator` for `Iterator`; 30 | - `reltester::fused_iterator` for `FusedIterator`; 31 | - `reltester::double_ended_iterator` for `DoubleEndedIterator`; 32 | 33 | Some of these functions take multiple (two or three) values of the same type. This is because it takes up to three values to test some invariants. 34 | 35 | Please refer to the documentation for more information. The `reltester::invariants` module is available for more granular checks if you can't satisfy the type bounds of the main functions. 36 | 37 | ## Examples 38 | 39 | ### `f32` (`PartialEq`, `PartialOrd`) 40 | 41 | ```rust 42 | use reltester; 43 | use quickcheck_macros::quickcheck; 44 | 45 | #[quickcheck] 46 | fn test_f32(a: f32, b: f32, c: f32) -> bool { 47 | // Let's check if `f32` implements `PartialEq` and `PartialOrd` correctly 48 | // (spoiler: it does). 49 | reltester::partial_eq(&a, &b, &c).is_ok() 50 | && reltester::partial_ord(&a, &b, &c).is_ok() 51 | } 52 | ``` 53 | 54 | ### `u32` (`Hash`) 55 | 56 | ```rust 57 | use reltester; 58 | use quickcheck_macros::quickcheck; 59 | 60 | #[quickcheck] 61 | fn test_u32(a: u32, b: u32) -> bool { 62 | // Unlike `f32`, `u32` implements both `Eq` and `Hash`, which allows us to 63 | // test `Hash` invariants. 64 | reltester::hash(&a, &b).is_ok() 65 | } 66 | ``` 67 | 68 | ### `Vec` (`DoubleEndedIterator`, `FusedIterator`, `Iterator`) 69 | 70 | ```rust 71 | use reltester; 72 | use quickcheck_macros::quickcheck; 73 | 74 | #[quickcheck] 75 | fn test_vec_u32(nums: Vec) -> bool { 76 | // `Iterator` is implied and checked by both `DoubleEndedIterator` and 77 | // `FusedIterator`. 78 | reltester::double_ended_iterator(nums.iter()).is_ok() 79 | && reltester::fused_iterator(nums.iter()).is_ok() 80 | } 81 | ``` 82 | 83 | ## Legal 84 | 85 | Reltester is available under the terms of the MIT license. 86 | 87 | ## External references and footnotes 88 | 89 | [^1]: https://github.com/rust-lang/rust/issues/41964 90 | [^2]: https://github.com/bevyengine/bevy/pull/7469 91 | [^3]: https://github.com/bluejekyll/trust-dns/issues/1638 92 | [^4]: https://github.com/sparsemat/sprs/issues/261 93 | -------------------------------------------------------------------------------- /examples/f32_partial_eq_is_not_reflexive.rs: -------------------------------------------------------------------------------- 1 | //! Why can't `f32` be `Eq`? Here's a counterexample to show why: 2 | 3 | fn main() {} 4 | 5 | #[test] 6 | fn f64_partial_eq_is_not_reflexive() { 7 | assert!(reltester::invariants::eq_reflexivity(&f64::NAN).is_err()); 8 | } 9 | -------------------------------------------------------------------------------- /examples/proptest.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use proptest::prelude::*; 6 | use std::net::IpAddr; 7 | 8 | proptest! { 9 | #[test] 10 | fn correctness_u32(a: u32, b: u32, c: u32) { 11 | reltester::eq(&a, &b, &c).unwrap(); 12 | reltester::ord(&a, &b, &c).unwrap(); 13 | } 14 | 15 | #[test] 16 | fn correctness_f32(a: f32, b: f32, c: f32) { 17 | reltester::partial_eq(&a, &b, &c).unwrap(); 18 | reltester::partial_ord(&a, &b, &c).unwrap(); 19 | } 20 | 21 | #[test] 22 | fn correctness_ip_address(a: IpAddr, b: IpAddr, c: IpAddr) { 23 | reltester::eq(&a, &b, &c).unwrap(); 24 | reltester::ord(&a, &b, &c).unwrap(); 25 | } 26 | 27 | #[test] 28 | fn vec_u32_is_truly_double_ended(x: Vec) { 29 | reltester::double_ended_iterator(x.iter()).unwrap(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/quickcheck.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use quickcheck_macros::quickcheck; 6 | use std::net::IpAddr; 7 | 8 | #[quickcheck] 9 | fn correctness_u32(a: u32, b: u32, c: u32) -> bool { 10 | reltester::eq(&a, &b, &c).is_ok() && reltester::ord(&a, &b, &c).is_ok() 11 | } 12 | 13 | #[quickcheck] 14 | fn correctness_f32(a: f32, b: f32, c: f32) -> bool { 15 | reltester::partial_eq(&a, &b, &c).is_ok() && reltester::partial_ord(&a, &b, &c).is_ok() 16 | } 17 | 18 | #[quickcheck] 19 | fn correctness_ip_address(a: IpAddr, b: IpAddr, c: IpAddr) -> bool { 20 | reltester::eq(&a, &b, &c).is_ok() && reltester::ord(&a, &b, &c).is_ok() 21 | } 22 | 23 | #[quickcheck] 24 | fn vec_u32_is_truly_double_ended(x: Vec) -> bool { 25 | reltester::double_ended_iterator(x.iter()).is_ok() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.70" # MSRV is not 1.70 but our dev-dependencies require a more recent rustc. 3 | profile = "default" 4 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Crate error types. 2 | 3 | use thiserror::Error; 4 | 5 | /// Represents a broken invariant of [`PartialEq`]. 6 | #[derive(Error, Debug, Clone)] 7 | #[non_exhaustive] 8 | pub enum PartialEqError { 9 | /// [`PartialEq::ne`] *MUST* always return the negation of [`PartialEq::eq`]. 10 | #[error("PartialEq::ne MUST always return the negation of PartialEq::eq")] 11 | BadNe, 12 | /// If `A: PartialEq` and `B: PartialEq`, then `a == b` *MUST* imply `b == a`. 13 | #[error("a == b MUST imply b == a")] 14 | BrokeSymmetry, 15 | /// If `A: PartialEq` and `B: PartialEq` and `A: PartialEq`, then 16 | /// `a == b && b == c` *MUST* imply `a == c`. 17 | #[error("a == b && b == c MUST imply a == c")] 18 | BrokeTransitivity, 19 | } 20 | 21 | /// Represents a broken invariant of [`Eq`]. 22 | /// 23 | /// Note that [`Eq`] also mandates all invariants of [`PartialEq`]. 24 | #[derive(Error, Debug, Clone)] 25 | #[non_exhaustive] 26 | pub enum EqError { 27 | /// All values must be equal to themselves. 28 | #[error("a == a MUST be true")] 29 | BrokeReflexivity, 30 | } 31 | 32 | /// Represents a broken invariant of [`PartialOrd`]. 33 | /// 34 | /// Note that [`PartialOrd`] also mandates all invariants of [`PartialEq`]. 35 | #[derive(Error, Debug, Clone)] 36 | #[non_exhaustive] 37 | pub enum PartialOrdError { 38 | /// [`PartialOrd::partial_cmp`] *MUST* return `Some(Ordering::Equal)` if 39 | /// and only if [`PartialEq::eq`] returns [`true`]. 40 | #[error("PartialOrd::partial_cmp MUST return Some(Ordering::Equal) if and only if PartialEq::eq returns true")] 41 | BadPartialCmp, 42 | /// [`PartialOrd::lt`] *MUST* return [`true`] 43 | /// if and only if [`PartialOrd::partial_cmp`] returns `Some(Ordering::Less)`. 44 | #[error("PartialOrd::lt MUST return true if and only if PartialOrd::partial_cmp returns Some(Ordering::Less)")] 45 | BadLt, 46 | /// [`PartialOrd::le`] *MUST* return [`true`] if and only if 47 | /// [`PartialOrd::partial_cmp`] returns `Some(Ordering::Less)` or 48 | /// [`Some(Ordering::Equal)`]. 49 | #[error("PartialOrd::le MUST return true if and only if PartialOrd::partial_cmp returns Some(Ordering::Less) or Some(Ordering::Equal)")] 50 | BadLe, 51 | /// [`PartialOrd::gt`] *MUST* return [`true`] if and only if 52 | /// [`PartialOrd::partial_cmp`] returns `Some(Ordering::Greater)`. 53 | #[error("PartialOrd::gt MUST return true if and only if PartialOrd::partial_cmp returns Some(Ordering::Greater)")] 54 | BadGt, 55 | /// [`PartialOrd::ge`] *MUST* return [`true`] if and only if 56 | /// [`PartialOrd::partial_cmp`] returns `Some(Ordering::Greater)` or 57 | /// `Some(Ordering::Equal)`. 58 | #[error("PartialOrd::ge MUST return true if and only if PartialOrd::partial_cmp returns Some(Ordering::Greater) or Some(Ordering::Equal)")] 59 | BadGe, 60 | /// If `a > b`, then `b < a` *MUST* be true. 61 | #[error("If a > b, then b < a MUST be true")] 62 | BrokeDuality, 63 | /// If `a > b` and `b > c`, then `a > c` *MUST* be true. The same must hold true for `<`. 64 | #[error("If a > b and b > c, then a > c MUST be true. The same must hold true for <")] 65 | BrokeTransitivity, 66 | } 67 | 68 | /// Represents a broken invariant of [`Ord`]. 69 | /// 70 | /// Note that [`Ord`] also mandates all invariants of [`PartialOrd`] and [`Eq`]. 71 | #[derive(Error, Debug, Clone)] 72 | #[non_exhaustive] 73 | pub enum OrdError { 74 | /// [`Ord::cmp`] *MUST* always return `Some(PartialOrd::partial_cmp())`. 75 | #[error("`cmp` and `partial_cmp` are not consistent")] 76 | BadCmp, 77 | /// [`Ord::cmp`] and [`Ord::max`] are not consistent. 78 | #[error("`cmp` and `max` are not consistent")] 79 | BadMax, 80 | /// [`Ord::cmp`] and [`Ord::min`] are not consistent. 81 | #[error("`cmp` and `min` are not consistent")] 82 | BadMin, 83 | /// [`Ord::cmp`] and [`Ord::clamp`] are not consistent. 84 | #[error("`cmp` and `clamp` are not consistent")] 85 | BadClamp, 86 | } 87 | 88 | /// Represents a broken invariant of [`Hash`]. 89 | #[derive(Error, Debug, Clone)] 90 | #[non_exhaustive] 91 | pub enum HashError { 92 | /// Equal values *MUST* have equal hash values. 93 | #[error("Equal values MUST have equal hash values")] 94 | EqualButDifferentHashes, 95 | /// When two values are different (as defined by [`PartialEq::ne`]), neither 96 | /// of the two hash outputs can be a prefix of the other. See 97 | /// 98 | /// for more information. 99 | #[error("When two values are different, one of the two hash outputs CAN NOT be a prefix of the other")] 100 | PrefixCollision, 101 | } 102 | 103 | /// Represents a broken invariant of [`Iterator`]. 104 | #[derive(Error, Debug, Clone)] 105 | #[non_exhaustive] 106 | pub enum IteratorError { 107 | /// [`Iterator::size_hint`] *MUST* always provide correct lower and upper 108 | /// bounds. 109 | #[error("Iterator::size_hint MUST always provide correct lower and upper bounds")] 110 | BadSizeHint, 111 | /// [`Iterator::count`] *MUST* be consistent with the actual number of 112 | /// elements returned by [`Iterator::next`]. 113 | #[error( 114 | "Iterator::count MUST be consistent with the actual number of elements returned by .next()" 115 | )] 116 | BadCount, 117 | /// [`Iterator::last`] *MUST* be equal to the last element of the 118 | /// [`Vec`] resulting from [`Iterator::collect`]. 119 | #[error(".last() MUST be equal to the last element of the Vec<_> resulting from .collect()")] 120 | BadLast, 121 | /// [`DoubleEndedIterator::next_back`] *MUST* return the same values as 122 | /// [`Iterator::next`], just in reverse order, and it MUST NOT return 123 | /// different values. 124 | #[error("DoubleEndedIterator::next_back() MUST return the same values as .next(), but in reverse order")] 125 | BadNextBack, 126 | /// [`FusedIterator`](core::iter::FusedIterator) *MUST* return [`None`] 127 | /// indefinitely after exhaustion. 128 | #[error("FusedIterator MUST return None indefinitely after exhaustion")] 129 | FusedIteratorReturnedSomeAfterExhaustion, 130 | } 131 | 132 | /// The crate error type. 133 | #[derive(Error, Debug, Clone)] 134 | #[non_exhaustive] 135 | pub enum Error { 136 | #[error(transparent)] 137 | PartialEq(#[from] PartialEqError), 138 | #[error(transparent)] 139 | Eq(#[from] EqError), 140 | #[error(transparent)] 141 | PartiaOrd(#[from] PartialOrdError), 142 | #[error(transparent)] 143 | Ord(#[from] OrdError), 144 | #[error(transparent)] 145 | Hash(#[from] HashError), 146 | #[error(transparent)] 147 | Iterator(#[from] IteratorError), 148 | } 149 | -------------------------------------------------------------------------------- /src/invariants.rs: -------------------------------------------------------------------------------- 1 | //! Granular checkers for specific trait invariants. Only use these if you 2 | //! implement [`PartialEq`] and [`PartialOrd`] with a non-`Self` type parameter 3 | //! and you can't satisfy the type bounds of the main helper functions. 4 | 5 | use std::{ 6 | cmp::{max_by, min_by, Ordering}, 7 | hash::{Hash, Hasher}, 8 | iter::FusedIterator, 9 | }; 10 | 11 | use crate::error::*; 12 | 13 | /// Checks that [`PartialEq::eq`] and [`PartialEq::ne`] are strict inverses. 14 | /// 15 | /// This is guaranteed by default method implementations but may be broken 16 | /// by non-default method implementations. 17 | pub fn partial_eq_methods_consistency(a: &A, b: &B) -> Result<(), PartialEqError> 18 | where 19 | A: PartialEq, 20 | { 21 | if (a == b) != !(a != b) { 22 | return Err(PartialEqError::BadNe); 23 | } 24 | 25 | Ok(()) 26 | } 27 | 28 | /// Checks that [`PartialEq`] is a 29 | /// [symmetric relation](https://en.wikipedia.org/wiki/Symmetric_relation). 30 | pub fn partial_eq_symmetry(a: &A, b: &B) -> Result<(), PartialEqError> 31 | where 32 | A: PartialEq, 33 | B: PartialEq, 34 | { 35 | if (a == b) != (b == a) { 36 | return Err(PartialEqError::BrokeSymmetry); 37 | } 38 | 39 | Ok(()) 40 | } 41 | 42 | /// Checks that [`PartialEq`] is a [transitive 43 | /// relation](https://en.wikipedia.org/wiki/Transitive_relation). 44 | pub fn partial_eq_transitivity(a: &A, b: &B, c: &C) -> Result<(), PartialEqError> 45 | where 46 | A: PartialEq + PartialEq, 47 | B: PartialEq, 48 | { 49 | if a == b && b == c && a != c { 50 | return Err(PartialEqError::BrokeTransitivity); 51 | } 52 | 53 | Ok(()) 54 | } 55 | 56 | /// Checks that [`PartialEq`] is a [reflexive 57 | /// relation](https://en.wikipedia.org/wiki/Reflexive_relation). 58 | /// 59 | /// Note that [`PartialEq`] alone does **not** require reflexivity, [`Eq`] 60 | /// does. 61 | pub fn eq_reflexivity(a: &A) -> Result<(), PartialEqError> 62 | where 63 | A: PartialEq, 64 | { 65 | if a != a { 66 | return Err(PartialEqError::BrokeTransitivity); 67 | } 68 | 69 | Ok(()) 70 | } 71 | 72 | /// Checks that [`PartialOrd`] methods are implemented consistently with 73 | /// each other. 74 | /// 75 | /// This is guaranteed by default method implementations but may be broken 76 | /// by non-default method implementations. 77 | pub fn partial_ord_methods_consistency(a: &A, b: &B) -> Result<(), PartialOrdError> 78 | where 79 | A: PartialOrd, 80 | { 81 | if (a == b) != (a.partial_cmp(b) == Some(Ordering::Equal)) { 82 | return Err(PartialOrdError::BadPartialCmp); 83 | } 84 | if (a < b) != (a.partial_cmp(b) == Some(Ordering::Less)) { 85 | return Err(PartialOrdError::BadLt); 86 | } 87 | if (a > b) != (a.partial_cmp(b) == Some(Ordering::Greater)) { 88 | return Err(PartialOrdError::BadGt); 89 | } 90 | if (a <= b) != ((a < b) || (a == b)) { 91 | return Err(PartialOrdError::BadLe); 92 | } 93 | if (a >= b) != ((a > b) || (a == b)) { 94 | return Err(PartialOrdError::BadGe); 95 | } 96 | 97 | Ok(()) 98 | } 99 | 100 | /// Checks that [`PartialOrd`] respects 101 | /// [duality](https://en.wikipedia.org/wiki/Duality_(order_theory)) (i.e. `a 102 | /// > b` iff `b < a`). 103 | pub fn partial_ord_duality(a: &A, b: &B) -> Result<(), PartialOrdError> 104 | where 105 | A: PartialOrd, 106 | B: PartialOrd, 107 | { 108 | if ((a < b) != (b > a)) && ((a > b) != (b < a)) { 109 | return Err(PartialOrdError::BrokeDuality); 110 | } 111 | 112 | Ok(()) 113 | } 114 | 115 | /// Checks that [`PartialOrd`] is a [transitive 116 | /// relation](https://en.wikipedia.org/wiki/Transitive_relation). 117 | pub fn partial_ord_transitivity(a: &A, b: &B, c: &C) -> Result<(), PartialOrdError> 118 | where 119 | A: PartialOrd + PartialOrd, 120 | B: PartialOrd, 121 | { 122 | if a < b && b < c && !(a < c) { 123 | return Err(PartialOrdError::BrokeTransitivity); 124 | } 125 | if a > b && b > c && !(a > c) { 126 | return Err(PartialOrdError::BrokeTransitivity); 127 | } 128 | 129 | Ok(()) 130 | } 131 | 132 | /// Checks that [`Ord`] methods are implemented consistently with each other. 133 | /// 134 | /// This is guaranteed by default method implementations but may be broken 135 | /// by non-default method implementations. 136 | pub fn ord_methods_consistency(a: &T, b: &T, c: &T) -> Result<(), OrdError> 137 | where 138 | T: Ord, 139 | { 140 | if a.partial_cmp(b) != Some(a.cmp(b)) { 141 | return Err(OrdError::BadCmp); 142 | } 143 | if a.max(b) != max_by(a, b, |x, y| x.cmp(y)) { 144 | return Err(OrdError::BadMax); 145 | } 146 | if a.min(b) != min_by(a, b, |x, y| x.cmp(y)) { 147 | return Err(OrdError::BadMin); 148 | } 149 | 150 | // clamp 151 | let min = b.min(c); 152 | let max = b.max(c); 153 | let clamped = a.clamp(min, max); 154 | if clamped < min || clamped > max { 155 | return Err(OrdError::BadClamp); 156 | } 157 | 158 | Ok(()) 159 | } 160 | 161 | /// Checks that the output of [`Hash`] is the same for equal values, and 162 | /// different for different values. 163 | /// 164 | /// See what the `std` 165 | /// [docs](https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq) have 166 | /// to say about this invariant. 167 | pub fn hash_consistency_with_eq(a: &K, b: &K) -> Result<(), HashError> 168 | where 169 | K: Hash + Eq + ?Sized, 170 | { 171 | let hasher_output_equality = hasher_output(a) == hasher_output(b); 172 | let equality = a == b; 173 | 174 | if hasher_output_equality != equality { 175 | return Err(HashError::EqualButDifferentHashes); 176 | } 177 | 178 | Ok(()) 179 | } 180 | 181 | /// Checks that neither of the outputs of [`Hash`] of two different values is a 182 | /// prefix of the other. 183 | /// 184 | /// See what the `std` 185 | /// [docs](https://doc.rust-lang.org/std/hash/trait.Hash.html#prefix-collisions) have 186 | /// to say about this invariant. 187 | pub fn hash_prefix_collision(a: &K, b: &K) -> Result<(), HashError> 188 | where 189 | K: Hash + Eq + ?Sized, 190 | { 191 | if a != b { 192 | let hasher_output_a = hasher_output(a); 193 | let hasher_output_b = hasher_output(b); 194 | 195 | if hasher_output_a.starts_with(&hasher_output_b) 196 | || hasher_output_b.starts_with(&hasher_output_a) 197 | { 198 | return Err(HashError::PrefixCollision); 199 | } 200 | } 201 | 202 | Ok(()) 203 | } 204 | 205 | /// Checks that [`Iterator::size_hint`] provides correct lower and upper bounds 206 | /// which are consistent with the true value of [`Iterator::count`]. 207 | pub fn iterator_size_hint(iter: I) -> Result<(), IteratorError> 208 | where 209 | I: Iterator, 210 | { 211 | let size_hint = iter.size_hint(); 212 | let count = iter.count(); 213 | 214 | if size_hint.0 > count { 215 | return Err(IteratorError::BadSizeHint); 216 | } else if let Some(upper_bound) = size_hint.1 { 217 | if upper_bound < count { 218 | return Err(IteratorError::BadSizeHint); 219 | } 220 | } 221 | 222 | Ok(()) 223 | } 224 | 225 | /// Checks that [`Iterator::count`] returns the same value as the length of the 226 | /// [`Vec`] obtained from [`Iterator::collect`]. 227 | pub fn iterator_count(iter: I) -> Result<(), IteratorError> 228 | where 229 | I: Iterator + Clone, 230 | { 231 | let count = iter.clone().count(); 232 | let collected = iter.collect::>(); 233 | 234 | if count != collected.len() { 235 | return Err(IteratorError::BadCount); 236 | } 237 | 238 | Ok(()) 239 | } 240 | 241 | /// Checks that [`Iterator::last`] returns the same value as the last element of 242 | /// the [`Vec`] obtained from [`Iterator::collect`]. 243 | pub fn iterator_last(iter: I) -> Result<(), IteratorError> 244 | where 245 | I: Iterator + Clone, 246 | I::Item: PartialEq, 247 | { 248 | let last = iter.clone().last(); 249 | let collected = iter.collect::>(); 250 | 251 | if last.as_ref() != collected.last() { 252 | return Err(IteratorError::BadLast); 253 | } 254 | 255 | Ok(()) 256 | } 257 | 258 | /// Checks that alternating random calls to [`Iterator::next`] and 259 | /// [`DoubleEndedIterator::next_back`] results in the same sequence as the 260 | /// [`Vec`] obtained from [`Iterator::collect`]. 261 | pub fn double_ended_iterator_next_back(mut iter: I) -> Result<(), IteratorError> 262 | where 263 | I: DoubleEndedIterator + Clone, 264 | I::Item: PartialEq, 265 | { 266 | let collected = iter.clone().collect::>(); 267 | 268 | let mut from_start = vec![]; 269 | let mut from_end = vec![]; 270 | loop { 271 | if rand::random() { 272 | if let Some(item) = iter.next() { 273 | from_start.push(item); 274 | } else { 275 | break; 276 | } 277 | } else { 278 | if let Some(item) = iter.next_back() { 279 | from_end.push(item); 280 | } else { 281 | break; 282 | } 283 | } 284 | } 285 | 286 | let assembled = from_start 287 | .into_iter() 288 | .chain(from_end.into_iter().rev()) 289 | .collect::>(); 290 | 291 | if assembled != collected { 292 | return Err(IteratorError::BadNextBack); 293 | } 294 | 295 | Ok(()) 296 | } 297 | 298 | /// Checks that [`FusedIterator`] returns [`None`] for a large number of times after 299 | /// returning [`None`] for the first time. 300 | pub fn fused_iterator_none_forever(mut iter: I) -> Result<(), IteratorError> 301 | where 302 | I: FusedIterator + Clone, 303 | { 304 | let mut count = 0; 305 | while iter.next().is_some() { 306 | count += 1; 307 | } 308 | 309 | // How many times does it make sense to keep going to have decent confidence 310 | // it will return `None` forever? Hard to say. I'm going with .count() + 1 311 | // in case the iterator "goes back" or something. 312 | for _ in 0..count + 1 { 313 | if iter.next().is_some() { 314 | return Err(IteratorError::FusedIteratorReturnedSomeAfterExhaustion); 315 | } 316 | } 317 | 318 | Ok(()) 319 | } 320 | 321 | fn hasher_output(item: &K) -> Vec 322 | where 323 | K: Hash + ?Sized, 324 | { 325 | struct NoHasher(Vec); 326 | 327 | impl Hasher for NoHasher { 328 | fn finish(&self) -> u64 { 329 | 0 330 | } 331 | 332 | fn write(&mut self, bytes: &[u8]) { 333 | self.0.extend_from_slice(bytes); 334 | } 335 | } 336 | 337 | let mut hasher = NoHasher(vec![]); 338 | item.hash(&mut hasher); 339 | hasher.0 340 | } 341 | -------------------------------------------------------------------------------- /src/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` and `Foo: PartialOrd` 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` (`DoubleEndedIterator`, `FusedIterator`, `Iterator`) 97 | //! 98 | //! ```rust 99 | //! use reltester; 100 | //! use quickcheck_macros::quickcheck; 101 | //! 102 | //! #[quickcheck] 103 | //! fn test_vec_u32(nums: Vec) -> 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]: 144 | //! 145 | //! [^2]: 146 | //! 147 | //! [^3]: 148 | //! 149 | //! [^4]: 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 | 156 | pub mod error; 157 | pub mod invariants; 158 | 159 | use error::*; 160 | use std::{hash::Hash, iter::FusedIterator}; 161 | 162 | /// Checks the correctness of the [`Ord`] trait (and [`Eq`] and [`PartialOrd`] 163 | /// by extension) for some values. 164 | pub fn ord(a: &T, b: &T, c: &T) -> Result<(), Error> 165 | where 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. 178 | pub fn partial_ord(a: &T, b: &T, c: &T) -> Result<(), Error> 179 | where 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. 197 | pub fn eq(a: &T, b: &T, c: &T) -> Result<(), Error> 198 | where 199 | T: PartialEq, 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. 212 | pub fn partial_eq(a: &T, b: &T, c: &T) -> Result<(), PartialEqError> 213 | where 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. 225 | pub fn hash(a: &K, b: &K) -> Result<(), HashError> 226 | where 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. 238 | pub fn iterator(iter: I) -> Result<(), IteratorError> 239 | where 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. 254 | pub fn double_ended_iterator(iter: I) -> Result<(), IteratorError> 255 | where 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. 270 | pub fn fused_iterator(iter: I) -> Result<(), IteratorError> 271 | where 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")] 284 | struct ReadmeDoctest; 285 | -------------------------------------------------------------------------------- /tests/arweave_ord.rs: -------------------------------------------------------------------------------- 1 | //! Dumb counter-example that I took from https://github.com/graphprotocol/graph-node. 2 | 3 | use std::cmp::Ordering; 4 | 5 | use quickcheck::{Arbitrary, Gen}; 6 | use quickcheck_macros::quickcheck; 7 | 8 | #[derive(Clone, Debug)] 9 | pub enum ArweaveTrigger { 10 | Block(u32), 11 | Transaction(u32), 12 | } 13 | 14 | #[quickcheck] 15 | #[should_panic] 16 | fn arweave_is_incorrect(a: ArweaveTrigger, b: ArweaveTrigger, c: ArweaveTrigger) -> bool { 17 | impl Arbitrary for ArweaveTrigger { 18 | fn arbitrary(g: &mut Gen) -> Self { 19 | if bool::arbitrary(g) { 20 | ArweaveTrigger::Block(u32::arbitrary(g)) 21 | } else { 22 | ArweaveTrigger::Transaction(u32::arbitrary(g)) 23 | } 24 | } 25 | } 26 | 27 | impl PartialEq for ArweaveTrigger { 28 | fn eq(&self, other: &Self) -> bool { 29 | match (self, other) { 30 | (Self::Block(a_ptr), Self::Block(b_ptr)) => a_ptr == b_ptr, 31 | (Self::Transaction(a_tx), Self::Transaction(b_tx)) => a_tx == b_tx, 32 | _ => false, 33 | } 34 | } 35 | } 36 | 37 | impl Eq for ArweaveTrigger {} 38 | 39 | impl PartialOrd for ArweaveTrigger { 40 | fn partial_cmp(&self, other: &Self) -> Option { 41 | Some(self.cmp(other)) 42 | } 43 | } 44 | 45 | impl Ord for ArweaveTrigger { 46 | fn cmp(&self, other: &Self) -> Ordering { 47 | match (self, other) { 48 | // Keep the order when comparing two block triggers 49 | (Self::Block(..), Self::Block(..)) => Ordering::Equal, 50 | 51 | // Block triggers always come last 52 | (Self::Block(..), _) => Ordering::Greater, 53 | (_, Self::Block(..)) => Ordering::Less, 54 | 55 | // Execution outcomes have no intrinsic ordering information so we keep the order in 56 | // which they are included in the `txs` field of `Block`. 57 | (Self::Transaction(..), Self::Transaction(..)) => Ordering::Equal, 58 | } 59 | } 60 | } 61 | 62 | reltester::ord(&a, &b, &c).is_ok() 63 | } 64 | -------------------------------------------------------------------------------- /tests/f23_cant_not_eq.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn f64_not_eq() { 3 | assert!(reltester::invariants::eq_reflexivity(&f64::NAN).is_err()); 4 | } 5 | -------------------------------------------------------------------------------- /tests/hash.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeSet, marker::PhantomData, path::PathBuf, rc::Rc}; 2 | 3 | use quickcheck_macros::quickcheck; 4 | 5 | #[quickcheck] 6 | fn hash_string(x1: String, x2: String) -> bool { 7 | reltester::hash(&x1, &x2).is_ok() 8 | } 9 | 10 | #[quickcheck] 11 | fn hash_u32(x1: u32, x2: u32) -> bool { 12 | reltester::hash(&x1, &x2).is_ok() 13 | } 14 | 15 | #[quickcheck] 16 | fn hash_string_tuples(x1: (String, String), x2: (String, String)) -> bool { 17 | reltester::hash(&x1, &x2).is_ok() 18 | } 19 | 20 | #[quickcheck] 21 | fn hash_btreeset_of_units(x1: BTreeSet<()>, x2: BTreeSet<()>) -> bool { 22 | reltester::hash(&x1, &x2).is_ok() 23 | } 24 | 25 | #[quickcheck] 26 | fn hash_path(x1: PathBuf, x2: PathBuf) -> bool { 27 | reltester::hash(x1.as_path(), x2.as_path()).is_ok() 28 | } 29 | 30 | #[test] 31 | fn hash_array_tuples() { 32 | let x1 = ([1, 2, 3, 4], [5, 6, 7, 8]); 33 | let x2 = ([0, 0, 0, 0], [5, 6, 7, 8]); 34 | assert!(reltester::hash(&x1, &x2).is_ok()); 35 | } 36 | 37 | #[test] 38 | fn hash_phantomdata() { 39 | let phantom = PhantomData::::default(); 40 | assert!(reltester::hash(&phantom, &phantom).is_ok()); 41 | } 42 | 43 | #[test] 44 | fn hash_rc() { 45 | let rc1 = Rc::new(1337); 46 | let rc2 = Rc::new(1337); 47 | let _rc2_cloned = rc2.clone(); 48 | assert!(reltester::hash(&rc1, &rc2).is_ok()); 49 | } 50 | -------------------------------------------------------------------------------- /tests/iterator.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | use quickcheck_macros::quickcheck; 4 | 5 | #[quickcheck] 6 | fn iterator_chars(x: String) -> bool { 7 | reltester::iterator(x.char_indices()).is_ok() 8 | } 9 | 10 | #[quickcheck] 11 | fn iterator_vec_of_strings(x: Vec) -> bool { 12 | reltester::double_ended_iterator(x.iter()).is_ok() 13 | && reltester::fused_iterator(x.iter()).is_ok() 14 | } 15 | 16 | #[quickcheck] 17 | fn iterator_btreeset_of_u32(x: BTreeSet) -> bool { 18 | reltester::double_ended_iterator(x.iter()).is_ok() 19 | && reltester::fused_iterator(x.iter()).is_ok() 20 | } 21 | --------------------------------------------------------------------------------