#[cfg(not(feature = "serde"))]
#[macro_export]
macro_rules! compare_structs {
($expected:expr, $actual:expr, $($field:ident),+) => {{
let mut diffs = String::new();
$(
if $expected.$field != $actual.$field {
diffs.push_str(&format!(
"{}: {:#?} != {:#?}\n",
stringify!($field),
$expected.$field,
$actual.$field
));
}
)+
assert!(diffs.is_empty(), "{diffs}");
}};
}
#[cfg(feature = "serde")]
#[macro_export]
macro_rules! compare_structs {
($expected:expr, $actual:expr) => {{
let expected_val =
serde_json::to_value(&$expected).expect("Could not serialize expected value");
let actual_val = serde_json::to_value(&$actual).expect("Could not serialize actual value");
if expected_val != actual_val {
let expected_map = expected_val
.as_object()
.expect("Expected value is not an object");
let actual_map = actual_val
.as_object()
.expect("Actual value is not an object");
let mut diffs = String::new();
for (key, expected_field_val) in expected_map {
match actual_map.get(key) {
Some(actual_field_val) => {
if expected_field_val != actual_field_val {
diffs.push_str(&format!(
"{}: {:#?} != {:#?}\n",
key, expected_field_val, actual_field_val
));
}
}
None => {
diffs.push_str(&format!(
"{}: field missing from actual: {:#?}\n",
key, expected_field_val
));
}
}
}
for (key, actual_field_val) in actual_map {
if !expected_map.contains_key(key) {
diffs.push_str(&format!(
"{}: field missing from expected: {:#?}\n",
key, actual_field_val
));
}
}
assert!(diffs.is_empty(), "{diffs}");
}
}};
($expected:expr, $actual:expr, $($field:ident),+) => {{
let mut diffs = String::new();
$(
if $expected.$field != $actual.$field {
diffs.push_str(&format!(
"{}: {:#?} != {:#?}\n",
stringify!($field),
$expected.$field,
$actual.$field
));
}
)+
assert!(diffs.is_empty(), "{diffs}");
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "serde")]
use serde::Serialize;
#[cfg_attr(feature = "serde", derive(Serialize))]
struct A<'a> {
a: i32,
b: &'a str,
c: [(f64, f32); 2],
}
#[cfg_attr(feature = "serde", derive(Serialize))]
struct B<'a> {
a: i32,
b: &'a str,
c: [(f64, f32); 2],
}
static STRUCT_A: A = A {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
static STRUCT_B: B = B {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
#[test]
#[cfg(feature = "serde")]
fn compare_all_fields_no_args() {
let struct_a = A {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
let struct_b = B {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
compare_structs!(struct_a, struct_b);
}
#[test]
#[should_panic]
#[cfg(feature = "serde")]
fn compare_all_fields_no_args_panic() {
let struct_a = A {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
let struct_b = B {
a: 10,
b: "different",
c: [(1.0, 1.0), (2.0, 2.0)],
};
compare_structs!(struct_a, struct_b);
}
#[test]
fn compare_all_fields() {
let struct_a = A {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
let struct_b = B {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
compare_structs!(STRUCT_A, STRUCT_B, a, b, c);
compare_structs!(struct_a, struct_b, a, b, c);
}
#[test]
fn compare_some_fields() {
let struct_a = A {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
let struct_b = B {
a: 10,
b: "diff str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
compare_structs!(struct_a, struct_b, a, c);
}
#[test]
fn compare_same_struct() {
let struct_a = A {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.1, 2.0)],
};
let struct_a_again = A {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.1, 2.0)],
};
compare_structs!(struct_a, struct_a_again, a, b, c);
}
#[test]
#[should_panic]
fn compare_different_structs() {
let struct_a = A {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 3.0)],
};
let struct_b = B {
a: 10,
b: "str",
c: [(1.0, 1.0), (2.0, 2.0)],
};
compare_structs!(struct_a, struct_b, a, b, c);
}
}