ptth/crates/always_equal/src/lib.rs

200 lines
3.8 KiB
Rust

//! # Always-Equal
//!
//! Always-Equal lets you wrap `PartialEq` implementations around
//! things that cannot be compared, like a `File`.
//!
//! To use Always-Equal, put these two conditional `use`
//! statements in your module:
//!
//! ```
//! #[cfg (test)]
//! use always_equal::test::AlwaysEqual;
//!
//! #[cfg (not (test))]
//! use always_equal::prod::AlwaysEqual;
//! ```
//!
//! Then wrap `AlwaysEqual` around anything that needs
//! PartialEq but can't possibly implement it:
//!
//! ```
//! #[derive (Debug, PartialEq)]
//! pub struct MyStruct {
//! pub b: bool,
//! pub i: i64,
//! pub file: AlwaysEqual <File>,
//! }
//! ```
//!
//! In test code, you can create an empty wrapper using
//! `testing_blank`, which is equal to any other wrapper:
//!
//! ```
//! let expected = MyStruct {
//! b: true,
//! i: 0,
//! file: AlwaysEqual::testing_blank (),
//! };
//!
//! let actual = MyStruct {
//! b: true,
//! i: 0,
//! file: my_file.into (),
//! };
//!
//! assert_eq! (expected, actual);
//! ```
//!
//! This is implemented with `Option` in test mode.
//! In production mode, wrappers are never equal to any other
//! wrapper, and the `Option` is removed so that
//! `sizeof::<AlwaysEqual <T>> () == sizeof::<T>`
//! _should_ be true.
pub mod test {
#[derive (Debug)]
pub struct AlwaysEqual <T> {
inner: Option <T>,
}
impl <T> AlwaysEqual <T> {
pub fn into_inner (self) -> T {
match self.inner {
Some (x) => x,
None => unreachable! (),
}
}
pub fn testing_blank () -> Self {
Self {
inner: None,
}
}
}
impl <T: Default> Default for AlwaysEqual <T> {
fn default () -> Self {
Self::from (T::default ())
}
}
impl <T> From <T> for AlwaysEqual <T> {
fn from (inner: T) -> Self {
Self {
inner: Some (inner),
}
}
}
impl <T> PartialEq for AlwaysEqual <T> {
fn eq (&self, other: &Self) -> bool {
self.inner.is_none () || other.inner.is_none ()
}
}
}
pub mod prod {
use std::fmt;
pub struct AlwaysEqual <T> {
inner: T,
}
impl <T: fmt::Debug> fmt::Debug for AlwaysEqual <T> {
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt (f)
}
}
impl <T: fmt::Display> fmt::Display for AlwaysEqual <T> {
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt (f)
}
}
impl <T> AlwaysEqual <T> {
pub fn into_inner (self) -> T {
self.inner
}
}
impl <T> From <T> for AlwaysEqual <T> {
fn from (inner: T) -> Self {
Self {
inner,
}
}
}
impl <T> PartialEq for AlwaysEqual <T> {
fn eq (&self, _other: &Self) -> bool {
false
}
}
}
#[cfg (test)]
mod tests {
use std::fs::File;
use super::test::*;
// Can't impl Clone or PartialEq because of the File
type CantCompare = Option <File>;
#[derive (Debug, Default, PartialEq)]
struct MyStruct
{
file: AlwaysEqual <CantCompare>,
name: &'static str,
}
#[test]
fn test_1 () {
let concrete_1 = MyStruct {
file: None.into (),
name: "my_struct",
};
let concrete_2 = MyStruct {
file: None.into (),
name: "my_struct",
};
let concrete_bad = MyStruct {
file: None.into (),
name: "not_my_struct",
};
assert_ne! (concrete_1, concrete_2);
assert_ne! (concrete_2, concrete_bad);
assert_ne! (concrete_bad, concrete_1);
let dummy_1 = MyStruct {
file: AlwaysEqual::testing_blank (),
name: "my_struct",
};
let dummy_2 = MyStruct {
file: AlwaysEqual::testing_blank (),
name: "my_struct",
};
let dummy_bad = MyStruct {
file: AlwaysEqual::testing_blank (),
name: "not_my_struct",
};
assert_eq! (dummy_1, dummy_2);
assert_ne! (dummy_2, dummy_bad);
assert_ne! (dummy_bad, dummy_1);
assert_eq! (concrete_1, dummy_1);
assert_eq! (concrete_bad, dummy_bad);
}
#[test]
fn test_2 () {
let v1 = Vec::<AlwaysEqual <File>>::new ();
let v2 = Vec::<AlwaysEqual <File>>::new ();
assert_eq! (v1, v2);
}
}