use falcon_mdf::{
BusSignal, BusSignals, CanDatabase, IdMatching, Metadata, Mf4File, Signal, SignalValues,
UnreadableReason, ValueKind,
};
const _: fn() = || {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Mf4File>();
assert_send_sync::<Signal>();
};
#[test]
fn every_type_a_public_method_returns_can_be_named_from_the_crate_root() {
fn _takes_metadata(_: &Metadata) {}
fn _takes_reason(_: UnreadableReason) {}
fn _takes_values(_: &SignalValues) {}
fn _takes_kind(_: ValueKind) {}
#[allow(unused_imports)]
use falcon_mdf::prelude::*;
}
#[test]
#[allow(clippy::len_zero)]
fn len_and_is_empty_agree_on_every_type_that_has_both() {
let values = SignalValues::U8(Vec::new());
assert_eq!(values.len() == 0, values.is_empty());
let values = SignalValues::U8(vec![1, 2, 3]);
assert_eq!(values.len() == 0, values.is_empty());
let empty = SignalValues::Bytes {
data: Vec::new(),
width: 4,
};
assert_eq!(empty.len() == 0, empty.is_empty());
let signals = BusSignals::default();
assert_eq!(signals.len() == 0, signals.is_empty());
}
#[test]
fn the_bus_decoding_types_are_nameable_from_the_crate_root() {
fn _takes_signals(_: &BusSignals<'_>) {}
fn _takes_signal(_: &BusSignal<'_>) {}
fn _takes_matching(_: IdMatching) {}
assert_eq!(IdMatching::default(), IdMatching::Exact);
assert_eq!(CanDatabase::default().matching(), IdMatching::Exact);
}
#[test]
fn metadata_distinguishes_having_no_properties_from_being_empty() {
let comment_only = Metadata::parse("<c><TX>a comment</TX></c>");
assert_eq!(comment_only.property_count(), 0);
assert!(
!comment_only.is_empty(),
"a block carrying a comment is not empty"
);
assert_eq!(comment_only.text(), "a comment");
let nothing = Metadata::parse("<c/>");
assert_eq!(nothing.property_count(), 0);
assert!(nothing.is_empty());
}
#[test]
fn value_kinds_print_as_their_name() {
assert_eq!(ValueKind::U32.to_string(), "u32");
assert_eq!(ValueKind::Bytes.to_string(), "bytes");
assert_eq!(format!("{}", ValueKind::F64), ValueKind::F64.name());
}
#[test]
fn an_unreadable_reason_prints_its_explanation() {
let reason = UnreadableReason::ArrayComposition;
assert_eq!(reason.to_string(), reason.detail());
assert!(
reason.to_string().contains("array"),
"the explanation should say what is wrong: {reason}"
);
}
#[test]
fn decoded_values_are_comparable_and_printable() {
let a = SignalValues::U16(vec![1, 2, 3]);
let b = SignalValues::U16(vec![1, 2, 3]);
let c = SignalValues::U16(vec![1, 2, 4]);
assert_eq!(a, b);
assert_ne!(a, c);
assert!(format!("{a:?}").contains('1'));
assert_ne!(SignalValues::U8(vec![1]), SignalValues::U16(vec![1]));
}
#[test]
fn kinds_are_comparable_and_copyable() {
let kind = ValueKind::I32;
let copied = kind;
assert_eq!(kind, copied, "ValueKind should be Copy, not moved");
assert!(kind.is_numeric());
assert!(!ValueKind::Str.is_numeric());
assert!(!ValueKind::Bytes.is_numeric());
}
#[test]
fn metadata_is_comparable_and_defaultable() {
assert_eq!(Metadata::default(), Metadata::default());
assert!(Metadata::default().is_empty());
assert_eq!(Metadata::parse("<c/>"), Metadata::parse("<c/>"));
}
#[test]
fn a_signal_prints_a_summary_rather_than_its_samples() {
let corpus_files = corpus();
if corpus_files.is_empty() {
eprintln!("SKIP: no corpus under test_data/");
return;
}
let mut checked = false;
for path in corpus_files {
let Ok(file) = falcon_mdf::Mf4File::open(&path) else {
continue;
};
let Some(channel) = file.channels().next() else {
continue;
};
let Ok(signal) = file.signal(channel) else {
continue;
};
let text = format!("{signal:?}");
assert!(text.starts_with("Signal"), "unexpected debug form: {text}");
assert!(text.contains(&channel.name), "should name the channel");
assert!(
text.len() < 500,
"debug output should summarise, not dump samples: {} chars",
text.len()
);
checked = true;
break;
}
assert!(checked, "expected at least one readable corpus file");
}
fn corpus() -> Vec<std::path::PathBuf> {
fn walk(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().and_then(|s| s.to_str()) == Some("MF4") {
out.push(path);
}
}
}
let mut found = Vec::new();
walk(std::path::Path::new("test_data"), &mut found);
if found.is_empty() {
walk(
std::path::Path::new("../../falcon_mdf/test_data"),
&mut found,
);
}
found.sort();
found
}