use falcon_mdf::{Mf4Error, Mf4File, SignalValues};
use serde_json::Value;
use std::path::Path;
fn corpus() -> Vec<String> {
let mut paths = Vec::new();
for raw in [
include_str!("data/golden.json"),
include_str!("data/reference_golden.json"),
] {
let table: Value = serde_json::from_str(raw).expect("ground truth is malformed");
let files = table
.get("files")
.and_then(Value::as_object)
.or_else(|| table.as_object())
.expect("ground truth must be an object");
paths.extend(
files
.keys()
.map(|key| resolve(key))
.filter(|path| Path::new(path).exists()),
);
}
paths
}
fn resolve(key: &str) -> String {
if key.contains('/') {
key.to_string()
} else {
format!("test_data/reference/{key}")
}
}
fn declined(err: &Mf4Error) -> bool {
matches!(err, Mf4Error::Unsupported { .. })
}
#[test]
fn chunks_reproduce_the_whole_signal() {
let paths = corpus();
if paths.is_empty() {
eprintln!("SKIP: no corpus files present under test_data/");
return;
}
let mut compared = 0usize;
let mut multi_block = 0usize;
let mut declined_count = 0usize;
let mut byte_channels = 0usize;
for path in &paths {
let file = Mf4File::open(path).unwrap_or_else(|e| panic!("{path}: {e}"));
for channel in file.channels() {
let Ok(whole) = file.signal(channel) else {
continue;
};
let Ok(expected_values) = whole.values() else {
continue;
};
let byte_valued = matches!(
expected_values,
SignalValues::Bytes { .. } | SignalValues::VarBytes { .. }
);
let expected: Vec<Vec<u8>> = if byte_valued {
(0..expected_values.len())
.map(|i| expected_values.bytes_at(i).unwrap_or_default().to_vec())
.collect()
} else {
Vec::new()
};
let expected_f64 = if byte_valued {
Vec::new()
} else {
match whole.values_f64() {
Ok(values) => values,
Err(_) => continue,
}
};
let chunks = match file.signal_chunks(channel) {
Ok(chunks) => chunks,
Err(e) if declined(&e) => {
declined_count += 1;
continue;
}
Err(e) => panic!("{path} '{}': {e}", channel.name),
};
let mut streamed_bytes: Vec<Vec<u8>> = Vec::new();
let mut streamed_f64: Vec<f64> = Vec::new();
let mut blocks = 0usize;
for chunk in chunks {
let chunk = chunk.unwrap_or_else(|e| panic!("{path} '{}': {e}", channel.name));
blocks += 1;
if byte_valued {
let values = chunk
.values()
.unwrap_or_else(|e| panic!("{path} '{}': {e}", channel.name));
streamed_bytes.extend(
(0..values.len()).map(|i| values.bytes_at(i).unwrap_or_default().to_vec()),
);
} else {
streamed_f64.extend(
chunk
.values_f64()
.unwrap_or_else(|e| panic!("{path} '{}': {e}", channel.name)),
);
}
}
if blocks > 1 {
multi_block += 1;
}
compared += 1;
if byte_valued {
byte_channels += 1;
}
if byte_valued {
assert_eq!(
streamed_bytes.len(),
expected.len(),
"{path} '{}': payload count over {blocks} block(s)",
channel.name
);
for (i, (got, want)) in streamed_bytes.iter().zip(&expected).enumerate() {
assert_eq!(
got, want,
"{path} '{}' payload {i} over {blocks} block(s)",
channel.name
);
}
} else {
assert_eq!(
streamed_f64.len(),
expected_f64.len(),
"{path} '{}': sample count over {blocks} block(s)",
channel.name
);
for (i, (got, want)) in streamed_f64.iter().zip(&expected_f64).enumerate() {
assert!(
got == want || (got.is_nan() && want.is_nan()),
"{path} '{}' sample {i}: streamed {got} is not {want}",
channel.name
);
}
}
}
}
assert!(compared > 0, "no channel was compared");
assert!(
multi_block > 0,
"every channel fitted in one block, so nothing exercised a block boundary"
);
assert!(
byte_channels > 0,
"no byte-valued channel was compared, so no payload was checked"
);
eprintln!(
"compared {compared} channel(s): {byte_channels} byte-valued, \
{multi_block} spanning several blocks, {declined_count} declined by contract"
);
}
#[test]
fn a_bus_log_payload_channel_streams() {
let path = "test_data/mf4-sample-data-v2.1/J1939 (truck)/LOG/958D2219/00002501/00002081.MF4";
if !Path::new(path).exists() {
eprintln!("SKIP: {path} is absent");
return;
}
let file = Mf4File::open(path).unwrap();
let payload = file.find_channel("CAN_DataFrame.DataBytes").unwrap();
let mut streamed: Vec<Vec<u8>> = Vec::new();
for chunk in file.signal_chunks(payload).unwrap() {
let values = chunk.unwrap().values().unwrap();
streamed.extend((0..values.len()).map(|i| values.bytes_at(i).unwrap().to_vec()));
}
let whole = file.signal(payload).unwrap().values().unwrap();
assert_eq!(streamed.len(), 145_534, "frame count");
for (i, got) in streamed.iter().enumerate() {
assert_eq!(got, whole.bytes_at(i).unwrap(), "payload {i}");
}
assert!(
streamed.iter().any(|p| p.len() != 8),
"expected payloads of differing length"
);
}
#[test]
fn a_signal_data_block_channel_streams() {
let path = "test_data/generated/mdflib_mixed.mf4";
if !std::path::Path::new(path).is_file() {
eprintln!("SKIP: {path} missing; run scripts/make_mdflib_bus_fixtures.sh");
return;
}
let file = Mf4File::open(path).unwrap();
for name in ["Text_Utf8", "Text_Ascii"] {
let channel = file.find_channel(name).expect("listed");
let eager = file.signal(channel).unwrap().values().unwrap();
let SignalValues::Str(expected) = eager else {
panic!("{name}: expected text, got {eager:?}");
};
let mut streamed = Vec::new();
let mut blocks = 0usize;
for chunk in file.signal_chunks(channel).expect("an SD channel streams") {
blocks += 1;
match chunk.unwrap().values().unwrap() {
SignalValues::Str(v) => streamed.extend(v),
other => panic!("{name}: a chunk decoded to {other:?}"),
}
}
assert!(blocks >= 1);
assert_eq!(
streamed, expected,
"{name}: streamed over {blocks} window(s)"
);
}
}
#[test]
fn chunks_read_a_multi_block_file_from_cold() {
let path = "test_data/reference/Vector_DataList_Deflate.mf4";
if !Path::new(path).exists() {
eprintln!("SKIP: {path} is absent");
return;
}
let cold = Mf4File::open(path).unwrap();
let mut streamed_any = false;
for channel in cold.channels() {
let Ok(chunks) = cold.signal_chunks(channel) else {
continue;
};
let mut streamed = Vec::new();
let mut blocks = 0usize;
for chunk in chunks {
blocks += 1;
streamed.extend(chunk.unwrap().values_f64().unwrap());
}
if blocks < 2 {
continue;
}
streamed_any = true;
let warm = Mf4File::open(path).unwrap();
let expected = warm
.signal(warm.find_channel(&channel.name).unwrap())
.unwrap()
.values_f64()
.unwrap();
assert_eq!(
streamed, expected,
"'{}' over {blocks} blocks",
channel.name
);
}
assert!(
streamed_any,
"no channel of a data-list file spanned several blocks"
);
}