use flac_io::{decode, encode, FlacAudio};
fn sample_audio() -> FlacAudio {
let ch: Vec<i32> = (0..6000).map(|i| (i % 800) - 400).collect();
FlacAudio {
sample_rate: 44100,
channels: 1,
bits_per_sample: 16,
samples: vec![ch],
}
}
const BODY: usize = 8;
#[test]
fn zero_md5_skips_the_check_and_still_decodes() {
let a = sample_audio();
let mut bytes = encode(&a).unwrap();
for b in bytes.iter_mut().skip(BODY + 18).take(16) {
*b = 0;
}
let d = decode(&bytes).expect("decode with zero MD5");
assert_eq!(d.samples, a.samples);
}
#[test]
fn corrupt_md5_is_detected() {
let a = sample_audio();
let mut bytes = encode(&a).unwrap();
bytes[BODY + 18] ^= 0xFF;
assert!(decode(&bytes).is_err(), "corrupt MD5 must be rejected");
}
#[test]
fn unknown_total_samples_decodes_to_end() {
let a = sample_audio();
let mut bytes = encode(&a).unwrap();
bytes[BODY + 13] &= 0xF0;
for b in bytes.iter_mut().skip(BODY + 14).take(4) {
*b = 0;
}
let d = decode(&bytes).expect("decode with unknown total");
assert_eq!(d.samples, a.samples);
}
fn header_only(bps_field: u32) -> Vec<u8> {
let mut bits: Vec<u8> = Vec::new();
let push = |value: u64, n: u32, bits: &mut Vec<u8>| {
for i in (0..n).rev() {
bits.push(((value >> i) & 1) as u8);
}
};
push(16, 16, &mut bits); push(16, 16, &mut bits); push(0, 24, &mut bits); push(0, 24, &mut bits); push(44100, 20, &mut bits); push(0, 3, &mut bits); push(bps_field as u64, 5, &mut bits); push(0, 36, &mut bits); bits.resize(bits.len() + 128, 0);
let mut body = vec![0u8; bits.len() / 8];
for (i, &bit) in bits.iter().enumerate() {
if bit != 0 {
body[i / 8] |= 1 << (7 - (i % 8));
}
}
let mut out = b"fLaC".to_vec();
out.push(0x80); out.extend_from_slice(&[0x00, 0x00, 0x22]); out.extend_from_slice(&body);
out
}
#[test]
fn unsupported_low_bit_depth_is_rejected() {
let stream = header_only(2);
let err = decode(&stream).unwrap_err();
assert!(
matches!(err, flac_io::FlacError::Unsupported(_)),
"expected Unsupported, got {err:?}"
);
}
#[test]
fn info_rejects_unsupported_bit_depth_like_decode() {
let stream = header_only(2);
let info_err = flac_io::info(&stream).unwrap_err();
let decode_err = decode(&stream).unwrap_err();
assert!(matches!(info_err, flac_io::FlacError::Unsupported(_)));
assert_eq!(info_err, decode_err);
}
#[test]
fn info_accepts_supported_depth() {
let info = flac_io::info(&header_only(15)).expect("info on supported depth");
assert_eq!(info.bits_per_sample, 16);
assert_eq!(info.channels, 1);
}
#[test]
fn supported_depth_header_only_decodes_to_silence() {
let stream = header_only(15);
let d = decode(&stream).expect("header-only decode");
assert_eq!(d.bits_per_sample, 16);
assert_eq!(d.samples_per_channel(), 0);
}
#[test]
#[ignore = "set FLAC_IO_CORPUS to a directory of real .flac files"]
fn decode_local_corpus() {
let dir =
std::env::var("FLAC_IO_CORPUS").expect("set FLAC_IO_CORPUS to a directory of .flac files");
let mut count = 0;
let mut failed = Vec::new();
for entry in std::fs::read_dir(&dir).expect("read corpus dir") {
let path = entry.unwrap().path();
if path.extension().map(|e| e == "flac").unwrap_or(false) {
count += 1;
let bytes = std::fs::read(&path).unwrap();
if let Err(e) = decode(&bytes) {
failed.push(format!("{}: {e}", path.display()));
}
}
}
assert!(count > 0, "no .flac files found in {dir}");
assert!(failed.is_empty(), "decode failures: {failed:#?}");
eprintln!("decoded {count} real-world files, all bit-exact");
}