mod common;
use std::io::Cursor;
use std::panic::{catch_unwind, AssertUnwindSafe};
use common::{
expected_samples, expected_spec, fixture_bytes, open, sha256_hex, vector, vectors, Expected,
};
use wav_codec::{Error, Sample, SampleFormat, WavReader, WavSpecEx, WavWriter};
fn decode_all<S: Sample + Default + PartialEq + std::fmt::Debug>(bytes: &[u8]) -> Vec<S> {
let mut reader = open(bytes).unwrap();
let via_iter: Vec<S> = reader
.samples::<S>()
.collect::<wav_codec::Result<_>>()
.unwrap();
assert_eq!(via_iter.len(), reader.len() as usize);
let mut reader = open(bytes).unwrap();
let mut via_bulk = Vec::new();
let mut buf = vec![S::default(); 3];
loop {
let n = reader.read_samples(&mut buf).unwrap();
if n == 0 {
break;
}
via_bulk.extend_from_slice(&buf[..n]);
}
assert_eq!(via_bulk, via_iter, "read_samples disagrees with samples()");
let via_owned: Vec<S> = open(bytes)
.unwrap()
.into_samples::<S>()
.collect::<wav_codec::Result<_>>()
.unwrap();
assert_eq!(via_owned, via_iter, "into_samples disagrees with samples()");
via_iter
}
fn peek<S: Sample + std::fmt::Debug>(item: Option<wav_codec::Result<S>>) -> Option<S> {
item.map(|r| r.unwrap())
}
fn assert_decodes_to(bytes: &[u8], expected: &Expected, id: &str) {
match expected {
Expected::I8(want) => assert_eq!(&decode_all::<i8>(bytes), want, "{}", id),
Expected::I16(want) => assert_eq!(&decode_all::<i16>(bytes), want, "{}", id),
Expected::I32(want) => assert_eq!(&decode_all::<i32>(bytes), want, "{}", id),
Expected::F32(want) => {
let got: Vec<u32> = decode_all::<f32>(bytes)
.into_iter()
.map(f32::to_bits)
.collect();
assert_eq!(&got, want, "{}", id);
}
Expected::F64(want) => {
let got: Vec<u64> = decode_all::<f64>(bytes)
.into_iter()
.map(f64::to_bits)
.collect();
assert_eq!(&got, want, "{}", id);
}
}
}
fn encode(spec_ex: WavSpecEx, expected: &Expected) -> Vec<u8> {
let mut out = Cursor::new(Vec::new());
{
let mut writer = WavWriter::new_with_spec_ex(&mut out, spec_ex).unwrap();
match expected {
Expected::I8(v) => writer.write_samples(v).unwrap(),
Expected::I16(v) => writer.write_samples(v).unwrap(),
Expected::I32(v) => writer.write_samples(v).unwrap(),
Expected::F32(v) => {
let f: Vec<f32> = v.iter().map(|b| f32::from_bits(*b)).collect();
writer.write_samples(&f).unwrap();
}
Expected::F64(v) => {
let f: Vec<f64> = v.iter().map(|b| f64::from_bits(*b)).collect();
writer.write_samples(&f).unwrap();
}
}
writer.finalize().unwrap();
}
out.into_inner()
}
#[test]
fn fixtures_match_manifest_sha256() {
let all = vectors();
assert_eq!(all.len(), 27, "manifest lists 27 vectors");
for v in &all {
if v.field("kind").as_str() == "perf" {
continue;
}
fixture_bytes(v);
}
}
#[test]
fn byte_exact_round_trip() {
let mut canonical = 0;
let mut decoded = 0;
for v in vectors() {
let kind = v.field("kind").as_str();
if kind != "valid" && kind != "tolerated" {
continue;
}
let id = v.field("id").as_str();
let bytes = fixture_bytes(&v);
let reader = open(&bytes).unwrap();
assert_eq!(reader.spec(), expected_spec(&v), "{} spec", id);
if let Some(c) = v.field("spec").get("container_bytes_per_sample") {
assert_eq!(
u64::from(reader.spec_ex().bytes_per_sample),
c.as_u64(),
"{} container",
id
);
}
let expected = expected_samples(&v).unwrap_or_else(|| panic!("{} lists no samples", id));
assert_eq!(reader.len() as usize, expected.len(), "{} len", id);
assert_eq!(
reader.duration() as usize,
expected.len() / usize::from(reader.spec().channels),
"{} duration",
id
);
assert_decodes_to(&bytes, &expected, id);
decoded += 1;
let is_canonical = v
.field("expect")
.get("canonical")
.map(|c| c.as_bool())
.unwrap_or(false);
if is_canonical {
let written = encode(reader.spec_ex(), &expected);
assert_eq!(
sha256_hex(&written),
v.field("sha256").as_str(),
"{} re-encode is not byte-exact",
id
);
assert_eq!(written, bytes, "{} re-encode bytes", id);
canonical += 1;
}
}
assert_eq!(decoded, 14, "13 valid vectors plus tolerated X03");
assert_eq!(canonical, 6, "V01 to V06 are canonical");
}
#[test]
fn trailing_partial_byte_errors() {
for id in ["X01", "X13"] {
let bytes = fixture_bytes(&vector(id));
match open(&bytes) {
Err(Error::Format(msg)) => assert!(msg.contains("multiple"), "{}: {}", id, msg),
other => panic!(
"{}: expected a Format error at open, got {:?}",
id,
other.map(|_| ())
),
}
}
for id in ["X02", "X08"] {
let v = vector(id);
let bytes = fixture_bytes(&v);
let max_prefix = v.field("expect").field("samples_before_error_max").as_u64() as usize;
let mut reader = open(&bytes).unwrap();
let mut items = Vec::new();
{
let mut iter = reader.samples::<i16>();
for _ in 0..1_000 {
match iter.next() {
Some(item) => items.push(item),
None => break,
}
}
assert!(iter.next().is_none(), "{}: iterator stays fused", id);
assert_eq!(iter.size_hint(), (0, Some(0)), "{}: fused size_hint", id);
}
let prefix: Vec<i16> = items
.iter()
.take_while(|r| r.is_ok())
.map(|r| *r.as_ref().unwrap())
.collect();
assert_eq!(prefix, [1, 2], "{}: whole-sample prefix", id);
assert!(prefix.len() <= max_prefix);
assert_eq!(items.len(), prefix.len() + 1, "{}: exactly one Err", id);
assert!(
matches!(items.last(), Some(Err(Error::Format(_)))),
"{}: the error is a Format error",
id
);
let mut reader = open(&bytes).unwrap();
let mut buf = [0i16; 8];
assert!(
matches!(reader.read_samples(&mut buf), Err(Error::Format(_))),
"{}: read_samples must fail rather than return a short count",
id
);
let owned: Vec<wav_codec::Result<i16>> = open(&bytes).unwrap().into_samples().collect();
assert_eq!(
owned.len(),
3,
"{}: into_samples yields prefix plus one Err",
id
);
}
}
#[test]
fn fused_iterator_size_hint() {
let bytes = fixture_bytes(&vector("X08"));
let mut reader = open(&bytes).unwrap();
let declared = 0xFFFF_FFF0u32 / 2;
assert_eq!(reader.len(), declared);
assert!(!reader.is_empty());
let mut iter = reader.samples::<i16>();
let (lo, hi) = iter.size_hint();
assert_eq!(lo, 0, "a stream cannot promise a lower bound");
assert_eq!(hi, Some(declared as usize));
assert_eq!(peek(iter.next()), Some(1));
assert_eq!(iter.size_hint(), (0, Some(declared as usize - 1)));
assert_eq!(peek(iter.next()), Some(2));
assert!(matches!(iter.next(), Some(Err(Error::Format(_)))));
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.by_ref().count(), 0);
assert!(iter.next().is_none());
}
#[test]
fn odd_chunk_round_trip() {
for id in ["V10", "V11", "V12"] {
let v = vector(id);
let bytes = fixture_bytes(&v);
assert_decodes_to(&bytes, &expected_samples(&v).unwrap(), id);
}
let x04 = fixture_bytes(&vector("X04"));
assert!(
matches!(open(&x04), Err(Error::Format(_))),
"X04 must fail at open"
);
for id in ["V01", "V03"] {
let v = vector(id);
let bytes = fixture_bytes(&v);
let spec_ex = open(&bytes).unwrap().spec_ex();
let written = encode(spec_ex, &expected_samples(&v).unwrap());
assert_eq!(written, bytes, "{} writer output", id);
let riff_size = u32::from_le_bytes([written[4], written[5], written[6], written[7]]);
assert_eq!(riff_size % 2, 0, "{} riff_size is even", id);
assert_eq!(riff_size as usize, written.len() - 8, "{} riff_size", id);
assert_eq!(*written.last().unwrap(), 0, "{} ends in the pad byte", id);
let data_size = u32::from_le_bytes([written[40], written[41], written[42], written[43]]);
assert_eq!(data_size % 2, 1, "{} data size stays odd", id);
}
}
#[test]
fn f64_round_trip() {
let v = vector("V06");
let bytes = fixture_bytes(&v);
let expected = expected_samples(&v).unwrap();
assert_decodes_to(&bytes, &expected, "V06");
let spec_ex = open(&bytes).unwrap().spec_ex();
assert_eq!(spec_ex.spec.sample_format, SampleFormat::Float);
assert_eq!(spec_ex.spec.bits_per_sample, 64);
assert_eq!(spec_ex.bytes_per_sample, 8);
assert_eq!(encode(spec_ex, &expected), bytes);
let f64_ref = std::fs::read(common::fixture_path("fixtures/ref-ffmpeg-f64.wav")).unwrap();
assert_eq!(
sha256_hex(&f64_ref),
"79bf603154840914dbc2500dc7d7bfabd2c13aec73ee7900dea836a6c9a70169"
);
let s24_ref = std::fs::read(common::fixture_path("fixtures/ref-ffmpeg-s24.wav")).unwrap();
assert_eq!(
sha256_hex(&s24_ref),
"d05d40c4fdfedff531a93b2e29bcd550773e3fd8dad1429bbb57ba1417c68ec7"
);
let reader = open(&f64_ref).unwrap();
assert_eq!(reader.spec().sample_format, SampleFormat::Float);
assert_eq!(reader.spec().bits_per_sample, 64);
assert_eq!(reader.spec().sample_rate, 8000);
assert_eq!(reader.duration(), 32);
let f64s = decode_all::<f64>(&f64_ref);
let reader = open(&s24_ref).unwrap();
assert_eq!(reader.spec().bits_per_sample, 24);
assert_eq!(reader.spec_ex().bytes_per_sample, 3);
let s24s = decode_all::<i32>(&s24_ref);
assert_eq!(f64s.len(), 32);
assert_eq!(s24s.len(), 32);
for (f, s) in f64s.iter().zip(&s24s) {
assert_eq!((f * 8_388_608.0).round() as i32, *s);
}
let f32s_of_f64 = open(&f64_ref).unwrap().samples::<f32>().next();
assert!(matches!(f32s_of_f64, Some(Err(Error::TooWide))));
let x13 = fixture_bytes(&vector("X13"));
assert!(matches!(open(&x13), Err(Error::Format(_))));
}
fn drain(bytes: &[u8]) -> Result<usize, Error> {
let mut reader = WavReader::new(Cursor::new(bytes))?;
let mut n = 0;
match reader.spec().sample_format {
SampleFormat::Int => {
for s in reader.samples::<i32>() {
s?;
n += 1;
}
}
SampleFormat::Float => {
for s in reader.samples::<f64>() {
s?;
n += 1;
}
}
}
Ok(n)
}
#[test]
fn no_panic_on_malformed() {
let mut checked = 0;
for v in vectors() {
if v.field("kind").as_str() != "error" {
continue;
}
let id = v.field("id").as_str();
let bytes = fixture_bytes(&v);
let stage = v.field("expect").field("error_stage").as_str();
let result = catch_unwind(AssertUnwindSafe(|| drain(&bytes)));
let result = result.unwrap_or_else(|_| panic!("{} panicked", id));
assert!(
result.is_err(),
"{} must fail, got {:?} samples",
id,
result
);
let opened = catch_unwind(AssertUnwindSafe(|| open(&bytes).map(|_| ())))
.unwrap_or_else(|_| panic!("{} panicked at open", id));
match stage {
"open" => assert!(opened.is_err(), "{} must fail at open", id),
"stream" => assert!(opened.is_ok(), "{} must open and fail in the stream", id),
other => panic!("{}: unknown stage {}", id, other),
}
checked += 1;
}
assert_eq!(checked, 12, "12 error vectors");
}
#[test]
fn seek_advances_by_container_width() {
let bytes = fixture_bytes(&vector("V09"));
let mut reader = open(&bytes).unwrap();
assert_eq!(reader.spec_ex().bytes_per_sample, 4);
assert_eq!(reader.duration(), 3);
reader.seek(1).unwrap();
let frame: Vec<i32> = reader
.samples::<i32>()
.take(2)
.map(Result::unwrap)
.collect();
assert_eq!(frame, [3, 4]);
reader.seek(2).unwrap();
let rest: Vec<i32> = reader.samples::<i32>().map(Result::unwrap).collect();
assert_eq!(rest, [-1, 8_388_607]);
reader.seek(0).unwrap();
let all: Vec<i32> = reader.samples::<i32>().map(Result::unwrap).collect();
assert_eq!(all, [1, 2, 3, 4, -1, 8_388_607]);
reader.seek(3).unwrap();
assert!(reader.samples::<i32>().next().is_none());
let past = reader.seek(4).unwrap_err();
assert_eq!(past.kind(), std::io::ErrorKind::InvalidInput);
reader.seek(2).unwrap();
assert_eq!(peek(reader.samples::<i32>().next()), Some(-1));
reader.seek(1).unwrap();
let frame: Vec<i32> = reader
.samples::<i32>()
.take(2)
.map(Result::unwrap)
.collect();
assert_eq!(frame, [3, 4]);
}
#[test]
fn unknown_guid_rejected() {
let bytes = fixture_bytes(&vector("X11"));
match open(&bytes) {
Err(Error::Unsupported(msg)) => assert!(msg.contains("GUID"), "{}", msg),
other => panic!("expected Unsupported, got {:?}", other.map(|_| ())),
}
}
#[test]
fn extensible_specs_resolve_from_the_guid() {
let v07 = fixture_bytes(&vector("V07"));
let reader = open(&v07).unwrap();
assert_eq!(reader.spec(), expected_spec(&vector("V07")));
assert_eq!(reader.spec_ex().bytes_per_sample, 2);
let v08 = fixture_bytes(&vector("V08"));
let reader = open(&v08).unwrap();
assert_eq!(reader.spec().channels, 6);
assert_eq!(reader.spec().sample_format, SampleFormat::Float);
assert_eq!(reader.duration(), 1);
assert_eq!(reader.len(), 6);
}
#[test]
fn stray_byte_after_data_is_ignored() {
let v = vector("X03");
let bytes = fixture_bytes(&v);
let mut reader = open(&bytes).unwrap();
let got: Vec<wav_codec::Result<i16>> = reader.samples().collect();
assert_eq!(got.len(), 2);
assert_eq!(got[0].as_ref().unwrap(), &1);
assert_eq!(got[1].as_ref().unwrap(), &2);
assert!(reader.samples::<i16>().next().is_none());
}