-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Filippo Costa <filippo@neysofu.me>
- Loading branch information
Showing
14 changed files
with
938 additions
and
333 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,19 @@ | ||
[package] | ||
name = "reltester" | ||
version = "1.0.1" | ||
edition = "2018" | ||
edition = "2021" | ||
repository = "https://github.com/neysofu/reltester" | ||
license = "MIT" | ||
rust-version = "1.56" | ||
description = "Automatically verify the correctness of [Partial]Eq/Ord implementations" | ||
authors = ["Filippo Neysofu Costa <filippo@neysofu.me>"] | ||
|
||
[dependencies] | ||
thiserror = "1.0.26" | ||
rand = "0.8" | ||
thiserror = "1" | ||
|
||
[dev-dependencies] | ||
quickcheck = "1.0" | ||
quickcheck_macros = "1.0" | ||
quickcheck = "1" | ||
quickcheck_macros = "1" | ||
proptest = "1" | ||
proptest-derive = "0.3" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
//! Why can't `f32` be `Eq`? Here's a counterexample to show why: | ||
|
||
fn main() {} | ||
|
||
#[test] | ||
fn f64_partial_eq_is_not_reflexive() { | ||
assert!(reltester::invariants::eq_reflexivity(&f64::NAN).is_err()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
fn main() {} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use proptest::prelude::*; | ||
use std::net::IpAddr; | ||
|
||
proptest! { | ||
#[test] | ||
fn correctness_u32(a: u32, b: u32, c: u32) { | ||
reltester::eq(&a, &b, &c).unwrap(); | ||
reltester::ord(&a, &b, &c).unwrap(); | ||
} | ||
|
||
#[test] | ||
fn correctness_f32(a: f32, b: f32, c: f32) { | ||
reltester::partial_eq(&a, &b, &c).unwrap(); | ||
reltester::partial_ord(&a, &b, &c).unwrap(); | ||
} | ||
|
||
#[test] | ||
fn correctness_ip_address(a: IpAddr, b: IpAddr, c: IpAddr) { | ||
reltester::eq(&a, &b, &c).unwrap(); | ||
reltester::ord(&a, &b, &c).unwrap(); | ||
} | ||
|
||
#[test] | ||
fn vec_u32_is_truly_double_ended(x: Vec<u32>) { | ||
reltester::double_ended_iterator(x.iter()).unwrap(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
fn main() {} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use quickcheck_macros::quickcheck; | ||
use std::net::IpAddr; | ||
|
||
#[quickcheck] | ||
fn correctness_u32(a: u32, b: u32, c: u32) -> bool { | ||
reltester::eq(&a, &b, &c).is_ok() && reltester::ord(&a, &b, &c).is_ok() | ||
} | ||
|
||
#[quickcheck] | ||
fn correctness_f32(a: f32, b: f32, c: f32) -> bool { | ||
reltester::partial_eq(&a, &b, &c).is_ok() && reltester::partial_ord(&a, &b, &c).is_ok() | ||
} | ||
|
||
#[quickcheck] | ||
fn correctness_ip_address(a: IpAddr, b: IpAddr, c: IpAddr) -> bool { | ||
reltester::eq(&a, &b, &c).is_ok() && reltester::ord(&a, &b, &c).is_ok() | ||
} | ||
|
||
#[quickcheck] | ||
fn vec_u32_is_truly_double_ended(x: Vec<u32>) -> bool { | ||
reltester::double_ended_iterator(x.iter()).is_ok() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
[toolchain] | ||
channel = "1.53" | ||
channel = "1.70" # MSRV is not 1.70 but our dev-dependencies require a more recent rustc. | ||
profile = "default" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
//! Crate error types. | ||
|
||
use thiserror::Error; | ||
|
||
/// Represents a broken invariant of [`PartialEq`]. | ||
#[derive(Error, Debug, Clone)] | ||
#[non_exhaustive] | ||
pub enum PartialEqError { | ||
/// [`PartialEq::ne`] *MUST* always return the negation of [`PartialEq::eq`]. | ||
#[error("PartialEq::ne MUST always return the negation of PartialEq::eq")] | ||
BadNe, | ||
/// If `A: PartialEq<B>` and `B: PartialEq<A>`, then `a == b` *MUST* imply `b == a`. | ||
#[error("a == b MUST imply b == a")] | ||
BrokeSymmetry, | ||
/// If `A: PartialEq<B>` and `B: PartialEq<C>` and `A: PartialEq<C>`, then | ||
/// `a == b && b == c` *MUST* imply `a == c`. | ||
#[error("a == b && b == c MUST imply a == c")] | ||
BrokeTransitivity, | ||
} | ||
|
||
/// Represents a broken invariant of [`Eq`]. | ||
/// | ||
/// Note that [`Eq`] also mandates all invariants of [`PartialEq`]. | ||
#[derive(Error, Debug, Clone)] | ||
#[non_exhaustive] | ||
pub enum EqError { | ||
/// All values must be equal to themselves. | ||
#[error("a == a MUST be true")] | ||
BrokeReflexivity, | ||
} | ||
|
||
/// Represents a broken invariant of [`PartialOrd`]. | ||
/// | ||
/// Note that [`PartialOrd`] also mandates all invariants of [`PartialEq`]. | ||
#[derive(Error, Debug, Clone)] | ||
#[non_exhaustive] | ||
pub enum PartialOrdError { | ||
/// [`PartialOrd::partial_cmp`] *MUST* return `Some(Ordering::Equal)` if | ||
/// and only if [`PartialEq::eq`] returns [`true`]. | ||
#[error("PartialOrd::partial_cmp MUST return Some(Ordering::Equal) if and only if PartialEq::eq returns true")] | ||
BadPartialCmp, | ||
/// [`PartialOrd::lt`] *MUST* return [`true`] | ||
/// if and only if [`PartialOrd::partial_cmp`] returns `Some(Ordering::Less)`. | ||
#[error("PartialOrd::lt MUST return true if and only if PartialOrd::partial_cmp returns Some(Ordering::Less)")] | ||
BadLt, | ||
/// [`PartialOrd::le`] *MUST* return [`true`] if and only if | ||
/// [`PartialOrd::partial_cmp`] returns `Some(Ordering::Less)` or | ||
/// [`Some(Ordering::Equal)`]. | ||
#[error("PartialOrd::le MUST return true if and only if PartialOrd::partial_cmp returns Some(Ordering::Less) or Some(Ordering::Equal)")] | ||
BadLe, | ||
/// [`PartialOrd::gt`] *MUST* return [`true`] if and only if | ||
/// [`PartialOrd::partial_cmp`] returns `Some(Ordering::Greater)`. | ||
#[error("PartialOrd::gt MUST return true if and only if PartialOrd::partial_cmp returns Some(Ordering::Greater)")] | ||
BadGt, | ||
/// [`PartialOrd::ge`] *MUST* return [`true`] if and only if | ||
/// [`PartialOrd::partial_cmp`] returns `Some(Ordering::Greater)` or | ||
/// `Some(Ordering::Equal)`. | ||
#[error("PartialOrd::ge MUST return true if and only if PartialOrd::partial_cmp returns Some(Ordering::Greater) or Some(Ordering::Equal)")] | ||
BadGe, | ||
/// If `a > b`, then `b < a` *MUST* be true. | ||
#[error("If a > b, then b < a MUST be true")] | ||
BrokeDuality, | ||
/// If `a > b` and `b > c`, then `a > c` *MUST* be true. The same must hold true for `<`. | ||
#[error("If a > b and b > c, then a > c MUST be true. The same must hold true for <")] | ||
BrokeTransitivity, | ||
} | ||
|
||
/// Represents a broken invariant of [`Ord`]. | ||
/// | ||
/// Note that [`Ord`] also mandates all invariants of [`PartialOrd`] and [`Eq`]. | ||
#[derive(Error, Debug, Clone)] | ||
#[non_exhaustive] | ||
pub enum OrdError { | ||
/// [`Ord::cmp`] *MUST* always return `Some(PartialOrd::partial_cmp())`. | ||
#[error("`cmp` and `partial_cmp` are not consistent")] | ||
BadCmp, | ||
/// [`Ord::cmp`] and [`Ord::max`] are not consistent. | ||
#[error("`cmp` and `max` are not consistent")] | ||
BadMax, | ||
/// [`Ord::cmp`] and [`Ord::min`] are not consistent. | ||
#[error("`cmp` and `min` are not consistent")] | ||
BadMin, | ||
/// [`Ord::cmp`] and [`Ord::clamp`] are not consistent. | ||
#[error("`cmp` and `clamp` are not consistent")] | ||
BadClamp, | ||
} | ||
|
||
/// Represents a broken invariant of [`Hash`]. | ||
#[derive(Error, Debug, Clone)] | ||
#[non_exhaustive] | ||
pub enum HashError { | ||
/// Equal values *MUST* have equal hash values. | ||
#[error("Equal values MUST have equal hash values")] | ||
EqualButDifferentHashes, | ||
/// When two values are different (as defined by [`PartialEq::ne`]), neither | ||
/// of the two hash outputs can be a prefix of the other. See | ||
/// <https://doc.rust-lang.org/std/hash/trait.Hash.html#prefix-collisions> | ||
/// for more information. | ||
#[error("When two values are different, one of the two hash outputs CAN NOT be a prefix of the other")] | ||
PrefixCollision, | ||
} | ||
|
||
/// Represents a broken invariant of [`Iterator`]. | ||
#[derive(Error, Debug, Clone)] | ||
#[non_exhaustive] | ||
pub enum IteratorError { | ||
/// [`Iterator::size_hint`] *MUST* always provide correct lower and upper | ||
/// bounds. | ||
#[error("Iterator::size_hint MUST always provide correct lower and upper bounds")] | ||
BadSizeHint, | ||
/// [`Iterator::count`] *MUST* be consistent with the actual number of | ||
/// elements returned by [`Iterator::next`]. | ||
#[error( | ||
"Iterator::count MUST be consistent with the actual number of elements returned by .next()" | ||
)] | ||
BadCount, | ||
/// [`Iterator::last`] *MUST* be equal to the last element of the | ||
/// [`Vec`] resulting from [`Iterator::collect`]. | ||
#[error(".last() MUST be equal to the last element of the Vec<_> resulting from .collect()")] | ||
BadLast, | ||
/// [`DoubleEndedIterator::next_back`] *MUST* return the same values as | ||
/// [`Iterator::next`], just in reverse order, and it MUST NOT return | ||
/// different values. | ||
#[error("DoubleEndedIterator::next_back() MUST return the same values as .next(), but in reverse order")] | ||
BadNextBack, | ||
/// [`FusedIterator`](core::iter::FusedIterator) *MUST* return [`None`] | ||
/// indefinitely after exhaustion. | ||
#[error("FusedIterator MUST return None indefinitely after exhaustion")] | ||
FusedIteratorReturnedSomeAfterExhaustion, | ||
} | ||
|
||
/// The crate error type. | ||
#[derive(Error, Debug, Clone)] | ||
#[non_exhaustive] | ||
pub enum Error { | ||
#[error(transparent)] | ||
PartialEq(#[from] PartialEqError), | ||
#[error(transparent)] | ||
Eq(#[from] EqError), | ||
#[error(transparent)] | ||
PartiaOrd(#[from] PartialOrdError), | ||
#[error(transparent)] | ||
Ord(#[from] OrdError), | ||
#[error(transparent)] | ||
Hash(#[from] HashError), | ||
#[error(transparent)] | ||
Iterator(#[from] IteratorError), | ||
} |
Oops, something went wrong.