diff options
Diffstat (limited to 'tvix')
-rw-r--r-- | tvix/eval/src/lib.rs | 2 | ||||
-rw-r--r-- | tvix/eval/src/properties.rs | 140 | ||||
-rw-r--r-- | tvix/eval/src/value/string.rs | 11 |
3 files changed, 153 insertions, 0 deletions
diff --git a/tvix/eval/src/lib.rs b/tvix/eval/src/lib.rs index b0baf3c249e9..34bb3b57e97c 100644 --- a/tvix/eval/src/lib.rs +++ b/tvix/eval/src/lib.rs @@ -11,6 +11,8 @@ mod vm; mod warnings; #[cfg(test)] +mod properties; +#[cfg(test)] mod tests; // Re-export the public interface used by other crates. diff --git a/tvix/eval/src/properties.rs b/tvix/eval/src/properties.rs new file mode 100644 index 000000000000..72a2fe29ef55 --- /dev/null +++ b/tvix/eval/src/properties.rs @@ -0,0 +1,140 @@ +//! Macros that generate proptest test suites checking laws of stdlib traits + +/// Generate a suite of tests to check the laws of the [`Eq`] impl for the given type +macro_rules! eq_laws { + ($ty: ty) => { + eq_laws!( + #[strategy(::proptest::arbitrary::any::<$ty>())] + $ty + ); + }; + (#[$meta: meta] $ty: ty) => { + #[allow(clippy::eq_op)] + mod eq { + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn reflexive(#[$meta] x: $ty) { + assert!(x == x); + } + + #[proptest] + fn symmetric(#[$meta] x: $ty, #[$meta] y: $ty) { + assert_eq!(x == y, y == x); + } + + #[proptest] + fn transitive(#[$meta] x: $ty, #[$meta] y: $ty, #[$meta] z: $ty) { + if x == y && y == z { + assert!(x == z); + } + } + } + }; +} + +/// Generate a suite of tests to check the laws of the [`Ord`] impl for the given type +macro_rules! ord_laws { + ($ty: ty) => { + ord_laws!( + #[strategy(::proptest::arbitrary::any::<$ty>())] + $ty + ); + }; + (#[$meta: meta] $ty: ty) => { + mod ord { + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn partial_cmp_matches_cmp(#[$meta] x: $ty, #[$meta] y: $ty) { + assert_eq!(x.partial_cmp(&y), Some(x.cmp(&y))); + } + + #[proptest] + fn dual(#[$meta] x: $ty, #[$meta] y: $ty) { + if x < y { + assert!(y > x); + } + if y < x { + assert!(x > y); + } + } + + #[proptest] + fn le_transitive(#[$meta] x: $ty, #[$meta] y: $ty, #[$meta] z: $ty) { + if x < y && y < z { + assert!(x < z) + } + } + + #[proptest] + fn gt_transitive(#[$meta] x: $ty, #[$meta] y: $ty, #[$meta] z: $ty) { + if x > y && y > z { + assert!(x > z) + } + } + + #[proptest] + fn trichotomy(#[$meta] x: $ty, #[$meta] y: $ty) { + let less = x < y; + let greater = x > y; + let eq = x == y; + + if less { + assert!(!greater); + assert!(!eq); + } + + if greater { + assert!(!less); + assert!(!eq); + } + + if eq { + assert!(!less); + assert!(!greater); + } + } + } + }; +} + +/// Generate a test to check the laws of the [`Hash`] impl for the given type +macro_rules! hash_laws { + ($ty: ty) => { + hash_laws!( + #[strategy(::proptest::arbitrary::any::<$ty>())] + $ty + ); + }; + (#[$meta: meta] $ty: ty) => { + mod hash { + use test_strategy::proptest; + + use super::*; + + #[proptest] + fn matches_eq(#[$meta] x: $ty, #[$meta] y: $ty) { + let hash = |x: &$ty| { + use std::hash::Hasher; + + let mut hasher = ::std::collections::hash_map::DefaultHasher::new(); + x.hash(&mut hasher); + hasher.finish() + }; + + if x == y { + assert_eq!(hash(&x), hash(&y)); + } + } + } + }; +} + +pub(crate) use eq_laws; +pub(crate) use hash_laws; +pub(crate) use ord_laws; diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs index 20564653433d..bfbaa815db99 100644 --- a/tvix/eval/src/value/string.rs +++ b/tvix/eval/src/value/string.rs @@ -177,3 +177,14 @@ impl Display for NixString { f.write_str("\"") } } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::properties::{eq_laws, hash_laws, ord_laws}; + + eq_laws!(NixString); + hash_laws!(NixString); + ord_laws!(NixString); +} |