use super::*;
use bytes::Bytes;
use std::io::{Read, Seek, SeekFrom};
#[test]
fn test_decode_audio_bytes_empty() {
let result = decode_audio_bytes(&[]);
assert!(result.is_err(), "Expected error for empty input, got Ok");
}
#[test]
fn test_decode_audio_bytes_invalid_data() {
let garbage: Vec<u8> = (0u8..128).collect();
let result = decode_audio_bytes(&garbage);
assert!(
result.is_err(),
"Expected error for invalid audio data, got Ok"
);
}
#[test]
fn test_decode_audio_bytes_ape_overflow_crash_is_graceful() {
let fixtures: &[&[u8]] = &[
include_bytes!("../../../../tests/fixtures/ape_overflow_crash.bin"),
&[
0xff, 0xf0, 0xff, 0x41, 0x50, 0x45, 0x54, 0x41, 0x47, 0x45, 0x58, 0xd0, 0x07, 0x00,
0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff,
0xff, 0xff, 0xf8, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8,
],
];
assert_eq!(fixtures[0].len(), 36);
assert_eq!(fixtures[1].len(), 38);
for (i, crash) in fixtures.iter().enumerate() {
let result = decode_audio_bytes(crash);
assert!(
result.is_err(),
"fixture {i}: crafted APEv2 header must yield a decode error, not panic or Ok"
);
}
}
#[test]
fn test_decode_audio_bytes_wav() {
let silence: Vec<i16> = vec![0; 16000]; let wav = make_wav_bytes(&silence, 16000);
let samples = decode_audio_bytes(&wav).unwrap();
assert!(!samples.is_empty());
assert!((samples.len() as i64 - 16000).unsigned_abs() <= 100);
}
#[test]
fn test_probe_duration_wav_reports_declared_seconds() {
let wav = make_wav_bytes(&vec![0i16; 16000], 16000); let probed = probe_duration_bytes(Bytes::from(wav)).unwrap();
assert!(
matches!(probed, Some(s) if (s - 1.0).abs() < 1e-6),
"expected ~1.0 s, got {probed:?}"
);
}
#[test]
fn test_probe_duration_agrees_with_decoded_length() {
let wav = make_wav_bytes(&vec![0i16; 24000], 16000); let probed = probe_duration_bytes(Bytes::from(wav.clone()))
.unwrap()
.expect("WAV declares its duration");
let decoded_s = decode_audio_bytes_shared(Bytes::from(wav)).unwrap().len() as f64 / 16_000.0;
assert!(
(probed - decoded_s).abs() < 1e-3,
"probe {probed} vs decode {decoded_s}"
);
}
#[test]
fn test_probe_duration_non_container_does_not_claim_duration() {
let r = probe_duration_bytes(Bytes::from_static(b"definitely not audio"));
assert!(
r.is_err() || matches!(r, Ok(None)),
"garbage bytes must be Err or Ok(None), got {r:?}"
);
}
#[test]
fn bytes_media_source_read_full() {
let data = Bytes::from_static(b"hello world");
let mut src = BytesMediaSource::new(data.clone());
let mut buf = vec![0u8; data.len()];
let n = src.read(&mut buf).unwrap();
assert_eq!(n, data.len());
assert_eq!(buf, data.as_ref());
let mut more = [0u8; 4];
assert_eq!(src.read(&mut more).unwrap(), 0);
}
#[test]
fn bytes_media_source_seek_end() {
let data = Bytes::from_static(b"abcdefgh");
let mut src = BytesMediaSource::new(data);
let pos = src.seek(SeekFrom::End(0)).unwrap();
assert_eq!(pos, 8);
let mut buf = [0u8; 4];
assert_eq!(src.read(&mut buf).unwrap(), 0);
}
#[test]
fn bytes_media_source_seek_past_end_ok() {
let data = Bytes::from_static(b"abc");
let mut src = BytesMediaSource::new(data);
let pos = src.seek(SeekFrom::Start(42)).unwrap();
assert_eq!(pos, 42);
let mut buf = [0u8; 4];
assert_eq!(src.read(&mut buf).unwrap(), 0);
}
#[test]
fn bytes_media_source_seek_before_start_err() {
let data = Bytes::from_static(b"abc");
let mut src = BytesMediaSource::new(data);
let err = src.seek(SeekFrom::Start(2)).unwrap();
assert_eq!(err, 2);
let result = src.seek(SeekFrom::Current(-100));
assert!(result.is_err(), "seek before start should error");
}
#[test]
fn bytes_media_source_partial_read_progress() {
let data = Bytes::from_static(b"abcdefghij");
let mut src = BytesMediaSource::new(data.clone());
let mut out = Vec::new();
let mut chunk = [0u8; 3];
loop {
let n = src.read(&mut chunk).unwrap();
if n == 0 {
break;
}
out.extend_from_slice(&chunk[..n]);
}
assert_eq!(out, data.as_ref());
}
#[test]
fn bytes_media_source_byte_len_matches() {
use symphonia::core::io::MediaSource as _;
let data = Bytes::from_static(b"0123456789");
let src = BytesMediaSource::new(data.clone());
assert_eq!(src.byte_len(), Some(data.len() as u64));
assert!(src.is_seekable());
}
#[test]
fn decode_audio_shim_matches_shared() {
let silence: Vec<i16> = vec![0; 16000];
let wav = make_wav_bytes(&silence, 16000);
let via_shim = decode_audio_bytes(&wav).unwrap();
let via_shared = decode_audio_bytes_shared(Bytes::copy_from_slice(&wav)).unwrap();
assert_eq!(via_shim.len(), via_shared.len());
for (a, b) in via_shim.iter().zip(via_shared.iter()) {
assert!((a - b).abs() < f32::EPSILON);
}
}
#[test]
fn test_decode_stereo_mixes_to_mono() {
let frames: Vec<(i16, i16)> = vec![(16384, -16384); 16000];
let wav = make_stereo_wav_from_frames(&frames, 16000);
let samples = decode_audio_bytes(&wav).unwrap();
assert!(!samples.is_empty());
assert!((samples.len() as i64 - 16000).unsigned_abs() <= 100);
for &s in &samples {
assert!(s.abs() < 0.01, "stereo mix should cancel to ~0, got {s}");
}
}
#[test]
fn test_decode_stereo_constant_preserves_value() {
let frames: Vec<(i16, i16)> = vec![(8192, 8192); 8000];
let wav = make_stereo_wav_from_frames(&frames, 16000);
let samples = decode_audio_bytes(&wav).unwrap();
assert!(!samples.is_empty());
for &s in &samples {
assert!((s - 0.25).abs() < 0.01, "expected ~0.25, got {s}");
}
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_decode_wav_resamples_to_16k() {
let silence: Vec<i16> = vec![0; 48000]; let wav = make_wav_bytes(&silence, 48000);
let samples = decode_audio_bytes(&wav).unwrap();
assert!(!samples.is_empty());
assert!(
samples.len() > 14000 && samples.len() < 17000,
"expected ~16000 after resample, got {}",
samples.len()
);
}
#[test]
fn test_decode_audio_bytes_shared_channels_8khz() {
let sample_rate = 8000u32;
let num_samples = sample_rate as usize;
let left: Vec<i16> = (0..num_samples)
.map(|i| ((i as f32 / num_samples as f32) * 6000.0) as i16)
.collect();
let right: Vec<i16> = (0..num_samples)
.map(|i| ((1.0 - i as f32 / num_samples as f32) * 6000.0) as i16)
.collect();
let wav = make_stereo_wav_bytes(&left, &right, sample_rate);
let channels = decode_audio_bytes_shared_channels(Bytes::from(wav)).unwrap();
assert_eq!(channels.len(), 2);
assert!(channels[0].len() > num_samples * 15 / 10 && channels[0].len() < num_samples * 25 / 10);
assert!(channels[1].len() > num_samples * 15 / 10 && channels[1].len() < num_samples * 25 / 10);
assert!((channels[0][1000] - channels[1][1000]).abs() > 0.01);
}
#[test]
fn test_is_dual_mono_identical_channels() {
let samples: Vec<f32> = (0..1000).map(|i| (i as f32 * 0.01).sin()).collect();
assert!(is_dual_mono(&[samples.clone(), samples]));
}
#[test]
fn test_is_dual_mono_independent_channels() {
let left: Vec<f32> = (0..1000).map(|i| (i as f32 * 0.01).sin()).collect();
let right: Vec<f32> = (0..1000).map(|i| (i as f32 * 0.03).cos()).collect();
assert!(!is_dual_mono(&[left, right]));
}
#[test]
fn test_mix_channels_to_mono() {
let left = vec![1.0_f32];
let right = vec![-1.0_f32];
let mono = mix_channels_to_mono(&[left, right]);
assert_eq!(mono.len(), 1);
assert!(mono[0].abs() < 0.001);
}
#[test]
fn test_is_dual_mono_empty_channels_returns_false() {
assert!(!is_dual_mono(&[]));
}
#[test]
fn test_is_dual_mono_single_channel_returns_false() {
let samples: Vec<f32> = (0..100).map(|i| (i as f32 * 0.01).sin()).collect();
assert!(!is_dual_mono(&[samples]));
}
#[test]
fn test_mix_channels_to_mono_empty_input() {
let mono = mix_channels_to_mono(&[]);
assert!(mono.is_empty());
}
#[test]
fn test_decode_audio_bytes_shared_channels_mono_input() {
let samples: Vec<i16> = (0..8000).map(|i| (i as f32 * 0.1).sin() as i16).collect();
let wav = make_wav_bytes(&samples, 16000);
let mono = decode_audio_bytes(&wav).unwrap();
let channels = decode_audio_bytes_shared_channels(Bytes::copy_from_slice(&wav)).unwrap();
assert_eq!(channels.len(), 1);
assert_eq!(channels[0].len(), mono.len());
for (a, b) in channels[0].iter().zip(mono.iter()) {
assert!(
(a - b).abs() < 1e-5,
"split mono decode diverged: {a} vs {b}"
);
}
}
#[test]
fn test_decode_rejects_adversarial_sample_rate() {
let silence: Vec<i16> = vec![0; 16]; let result = decode_audio_bytes(&make_wav_bytes(&silence, MAX_SAMPLE_RATE + 1));
assert!(
result.is_err(),
"sample_rate above MAX_SAMPLE_RATE must be rejected"
);
let result = decode_audio_bytes(&make_wav_bytes(&silence, 1_000_000_000));
assert!(result.is_err(), "absurd sample_rate must be rejected");
}