use std::fmt::Debug;
use std::iter::FromIterator;
use std::path::PathBuf;
pub trait FileResults: AsRef<[u8]> {
fn is_human_readable(&self) -> bool {
false
}
}
impl FileResults for Vec<u8> {}
impl FileResults for String {
fn is_human_readable(&self) -> bool {
true
}
}
#[track_caller]
pub fn assert_file_eq_impl<T: FileResults>(actual_bytes: &T, file: &str) {
if actual_bytes.is_human_readable() {
let actual_str = String::from_utf8(actual_bytes.as_ref().to_vec()).unwrap();
let expected_path = PathBuf::from_iter(["resources/testdata", file]);
let expected_str = std::fs::read_to_string(&expected_path)
.inspect_err(|err| eprintln!("Failed to read {expected_path:?}: {err}"))
.unwrap_or_default();
if actual_str == expected_str {
return;
}
let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();
if update_expected {
if let Err(err) = std::fs::write(&expected_path, &actual_str) {
eprintln!("Failed to update expected at {expected_path:?}\n{err}");
}
}
assert_eq!(
actual_str, expected_str,
"Actual string did not match contents of {expected_path:?}.\n\
Use `UPDATE_EXPECTED=1 cargo test` to regenerate expected output.\n\
UPDATE_EXPECTED is set: {update_expected:?}"
);
return;
}
let actual_bytes = actual_bytes.as_ref();
let expected_path = PathBuf::from_iter(["resources/testdata", file]);
let expected_bytes = std::fs::read(&expected_path)
.inspect_err(|err| eprintln!("Failed to read {expected_path:?}: {err}"))
.unwrap_or_default();
if actual_bytes == expected_bytes {
return;
}
let actual_dir = "target/testdata";
if let Err(err) = std::fs::create_dir_all(actual_dir) {
eprintln!("Failed to create target/testdata directory: {err}");
}
let actual_path = PathBuf::from_iter([actual_dir, file]);
if let Err(err) = std::fs::write(&actual_path, actual_bytes) {
eprintln!("Failed to write actual bytes to {actual_path:?}: {err}");
}
let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();
if update_expected {
if let Err(err) = std::fs::write(&expected_path, actual_bytes) {
eprintln!("Failed to update expected at {expected_path:?}\n{err}");
};
}
panic!(
"Bytes (stored in {actual_path:?}) did not match expected bytes from {expected_path:?}\n\
Use `UPDATE_EXPECTED=1 cargo test` to regenerate expected output.\n\
UPDATE_EXPECTED is set: {update_expected:?}"
);
}
#[macro_export]
macro_rules! assert_file_eq {
($actual:expr, $expected_file:expr) => {
$crate::test_utils::assert_file_eq_impl(&$actual, $expected_file);
};
}
#[macro_export]
macro_rules! assert_matches {
($expression:expr, $pattern:pat) => {
#[allow(clippy::redundant_pattern_matching)]
if !(matches!($expression, $pattern)) {
let res = $expression;
panic!(
"assertion failed: {expr} result {res:?} does not match {pattern}",
expr = stringify!($expression),
pattern = stringify!($pattern)
);
}
};
}
#[track_caller]
pub fn assert_slice_unordered_eq_impl<T: PartialEq + Debug>(actual: &[T], expected: &[T]) {
let is_eq =
actual.len() == expected.len() && actual.iter().all(|actual| expected.contains(actual));
assert!(
is_eq,
"Vectors do not contain the same elements. \nActual: {:?}\nExpected: {:?}",
actual, expected
);
assert_eq!(actual.len(),
expected.len(),
"Vectors have different lengths, but contain the same unique elements. This suggests duplicates. \nActual: {:?}\nExpected: {:?}",
actual,
expected);
}
#[macro_export]
macro_rules! assert_slice_unordered_eq {
($actual:expr, $expected:expr) => {
$crate::test_utils::assert_slice_unordered_eq_impl(&$actual, &$expected);
};
}