tvl-depot/tvix/eval/src/properties.rs
Griffin Smith 915ff5ac2a refactor(tvix/eval): Don't (ab)use PartialEq for Nix equality
Using rust's PartialEq trait to implement Nix equality semantics is
reasonably fraught with peril, both because the actual laws are
different than what nix expects, and (more importantly) because certain
things actually require extra context to compare for equality (for
example, thunks need to be forced). This converts the manual PartialEq
impl for Value (and all its descendants) to a *derived* PartialEq
impl (which requires a lot of extra PartialEq derives on miscellanious
other types within the codebase), and converts the previous
nix-semantics equality comparison into a new `nix_eq` method. This
returns an EvalResult, even though it can't currently return an error,
to allow it to fail when eg forcing thunks (which it will do soon).

Since the PartialEq impls for Value and NixAttrs are now quite boring,
this converts the generated proptests for those into handwritten ones
that cover `nix_eq` instead

Change-Id: If3da7171f88c22eda5b7a60030d8b00c3b76f672
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6650
Autosubmit: grfn <grfn@gws.fyi>
Tested-by: BuildkiteCI
Reviewed-by: tazjin <tazjin@tvl.su>
2022-09-18 22:03:41 +00:00

164 lines
4.3 KiB
Rust

//! 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,
Default::default()
);
};
($ty: ty, $config: expr) => {
eq_laws!(
#[strategy(::proptest::arbitrary::any::<$ty>())]
$ty,
$config
);
};
(#[$meta: meta] $ty: ty, $config: expr) => {
#[allow(clippy::eq_op)]
mod eq {
use test_strategy::proptest;
use super::*;
#[proptest($config)]
fn reflexive(#[$meta] x: $ty) {
assert!(x == x);
}
#[proptest($config)]
fn symmetric(#[$meta] x: $ty, #[$meta] y: $ty) {
assert_eq!(x == y, y == x);
}
#[proptest($config)]
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,
Default::default()
);
};
($ty: ty, $config: expr) => {
ord_laws!(
#[strategy(::proptest::arbitrary::any::<$ty>())]
$ty,
$config
);
};
(#[$meta: meta] $ty: ty, $config: expr) => {
mod ord {
use test_strategy::proptest;
use super::*;
#[proptest($config)]
fn partial_cmp_matches_cmp(#[$meta] x: $ty, #[$meta] y: $ty) {
assert_eq!(x.partial_cmp(&y), Some(x.cmp(&y)));
}
#[proptest($config)]
fn dual(#[$meta] x: $ty, #[$meta] y: $ty) {
if x < y {
assert!(y > x);
}
if y < x {
assert!(x > y);
}
}
#[proptest($config)]
fn le_transitive(#[$meta] x: $ty, #[$meta] y: $ty, #[$meta] z: $ty) {
if x < y && y < z {
assert!(x < z)
}
}
#[proptest($config)]
fn gt_transitive(#[$meta] x: $ty, #[$meta] y: $ty, #[$meta] z: $ty) {
if x > y && y > z {
assert!(x > z)
}
}
#[proptest($config)]
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,
Default::default()
);
};
($ty: ty, $config: expr) => {
hash_laws!(
#[strategy(::proptest::arbitrary::any::<$ty>())]
$ty,
$config
);
};
(#[$meta: meta] $ty: ty, $config: expr) => {
mod hash {
use test_strategy::proptest;
use super::*;
#[proptest($config)]
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;