pile-audio refactor
This commit is contained in:
63
crates/pile-flac/src/reader/block.rs
Normal file
63
crates/pile-flac/src/reader/block.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::{
|
||||
FlacDecodeError, FlacEncodeError,
|
||||
blocks::{
|
||||
FlacApplicationBlock, FlacAudioFrame, FlacCommentBlock, FlacCuesheetBlock,
|
||||
FlacMetablockDecode, FlacMetablockEncode, FlacMetablockType, FlacPaddingBlock,
|
||||
FlacPictureBlock, FlacSeektableBlock, FlacStreaminfoBlock,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[expect(missing_docs)]
|
||||
pub enum FlacBlock {
|
||||
Streaminfo(FlacStreaminfoBlock),
|
||||
Picture(FlacPictureBlock),
|
||||
Padding(FlacPaddingBlock),
|
||||
Application(FlacApplicationBlock),
|
||||
SeekTable(FlacSeektableBlock),
|
||||
VorbisComment(FlacCommentBlock),
|
||||
CueSheet(FlacCuesheetBlock),
|
||||
AudioFrame(FlacAudioFrame),
|
||||
}
|
||||
|
||||
impl FlacBlock {
|
||||
/// Encode this block
|
||||
pub fn encode(
|
||||
&self,
|
||||
is_last: bool,
|
||||
with_header: bool,
|
||||
target: &mut impl Write,
|
||||
) -> Result<(), FlacEncodeError> {
|
||||
match self {
|
||||
Self::Streaminfo(b) => b.encode(is_last, with_header, target),
|
||||
Self::SeekTable(b) => b.encode(is_last, with_header, target),
|
||||
Self::Picture(b) => b.encode(is_last, with_header, target),
|
||||
Self::Padding(b) => b.encode(is_last, with_header, target),
|
||||
Self::Application(b) => b.encode(is_last, with_header, target),
|
||||
Self::VorbisComment(b) => b.encode(is_last, with_header, target),
|
||||
Self::CueSheet(b) => b.encode(is_last, with_header, target),
|
||||
Self::AudioFrame(b) => b.encode(target),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to decode the given data as a block
|
||||
pub fn decode(block_type: FlacMetablockType, data: &[u8]) -> Result<Self, FlacDecodeError> {
|
||||
Ok(match block_type {
|
||||
FlacMetablockType::Streaminfo => {
|
||||
FlacBlock::Streaminfo(FlacStreaminfoBlock::decode(data)?)
|
||||
}
|
||||
FlacMetablockType::Application => {
|
||||
FlacBlock::Application(FlacApplicationBlock::decode(data)?)
|
||||
}
|
||||
FlacMetablockType::Cuesheet => FlacBlock::CueSheet(FlacCuesheetBlock::decode(data)?),
|
||||
FlacMetablockType::Padding => FlacBlock::Padding(FlacPaddingBlock::decode(data)?),
|
||||
FlacMetablockType::Picture => FlacBlock::Picture(FlacPictureBlock::decode(data)?),
|
||||
FlacMetablockType::Seektable => FlacBlock::SeekTable(FlacSeektableBlock::decode(data)?),
|
||||
FlacMetablockType::VorbisComment => {
|
||||
FlacBlock::VorbisComment(FlacCommentBlock::decode(data)?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
9
crates/pile-flac/src/reader/mod.rs
Normal file
9
crates/pile-flac/src/reader/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod block;
|
||||
pub use block::*;
|
||||
|
||||
#[expect(clippy::module_inception)]
|
||||
mod reader;
|
||||
pub use reader::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
184
crates/pile-flac/src/reader/reader.rs
Normal file
184
crates/pile-flac/src/reader/reader.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use crate::{
|
||||
FlacBlock, FlacDecodeError,
|
||||
blocks::{FlacAudioFrame, FlacMetablockHeader, FlacMetablockType},
|
||||
};
|
||||
|
||||
// TODO: quickly skip blocks we do not need
|
||||
|
||||
/// The next block we expect to read
|
||||
enum ReaderState {
|
||||
MagicBits,
|
||||
MetablockHeader { is_first: bool },
|
||||
MetaBlock { header: FlacMetablockHeader },
|
||||
AudioData,
|
||||
Done,
|
||||
}
|
||||
|
||||
pub struct FlacReader<R: Read + Seek> {
|
||||
inner: R,
|
||||
state: ReaderState,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> FlacReader<R> {
|
||||
const MIN_AUDIO_FRAME_LEN: usize = 5000;
|
||||
|
||||
pub fn new(inner: R) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
state: ReaderState::MagicBits,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> Iterator for FlacReader<R> {
|
||||
type Item = Result<FlacBlock, FlacDecodeError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
match &mut self.state {
|
||||
ReaderState::Done => return None,
|
||||
|
||||
ReaderState::MagicBits => {
|
||||
let mut data = [0u8; 4];
|
||||
if let Err(e) = self.inner.read_exact(&mut data[..4]) {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e.into()));
|
||||
}
|
||||
|
||||
if data != [0x66, 0x4C, 0x61, 0x43] {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(FlacDecodeError::BadMagicBytes));
|
||||
}
|
||||
|
||||
self.state = ReaderState::MetablockHeader { is_first: true };
|
||||
}
|
||||
|
||||
ReaderState::MetablockHeader { is_first } => {
|
||||
let mut data = [0u8; 4];
|
||||
if let Err(e) = self.inner.read_exact(&mut data[..]) {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e.into()));
|
||||
}
|
||||
|
||||
let header = match FlacMetablockHeader::decode(&data) {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e));
|
||||
}
|
||||
};
|
||||
|
||||
if *is_first && !matches!(header.block_type, FlacMetablockType::Streaminfo) {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(FlacDecodeError::BadFirstBlock));
|
||||
}
|
||||
|
||||
self.state = ReaderState::MetaBlock { header };
|
||||
}
|
||||
|
||||
ReaderState::MetaBlock { header } => {
|
||||
let mut data = vec![0u8; header.length as usize];
|
||||
if let Err(e) = self.inner.read_exact(&mut data) {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e.into()));
|
||||
}
|
||||
|
||||
let block = match FlacBlock::decode(header.block_type, &data) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e));
|
||||
}
|
||||
};
|
||||
|
||||
if header.is_last {
|
||||
self.state = ReaderState::AudioData;
|
||||
} else {
|
||||
self.state = ReaderState::MetablockHeader { is_first: false };
|
||||
}
|
||||
|
||||
return Some(Ok(block));
|
||||
}
|
||||
|
||||
ReaderState::AudioData => {
|
||||
let mut data = Vec::new();
|
||||
loop {
|
||||
let mut byte = [0u8; 1];
|
||||
match self.inner.read_exact(&mut byte) {
|
||||
Ok(_) => {
|
||||
data.push(byte[0]);
|
||||
|
||||
if data.len() >= Self::MIN_AUDIO_FRAME_LEN + 2 {
|
||||
let len = data.len();
|
||||
if data[len - 2] == 0b1111_1111
|
||||
&& data[len - 1] & 0b1111_1100 == 0b1111_1000
|
||||
{
|
||||
let frame_data = data[..len - 2].to_vec();
|
||||
|
||||
if frame_data.len() < 2
|
||||
|| frame_data[0] != 0b1111_1111 || frame_data[1]
|
||||
& 0b1111_1100
|
||||
!= 0b1111_1000
|
||||
{
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(FlacDecodeError::BadSyncBytes));
|
||||
}
|
||||
|
||||
let audio_frame = match FlacAudioFrame::decode(&frame_data)
|
||||
{
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e));
|
||||
}
|
||||
};
|
||||
|
||||
// Seek back 2 bytes so the next frame starts with the sync bytes
|
||||
if let Err(e) = self.inner.seek(SeekFrom::Current(-2)) {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e.into()));
|
||||
}
|
||||
|
||||
self.state = ReaderState::AudioData;
|
||||
|
||||
return Some(Ok(FlacBlock::AudioFrame(audio_frame)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
if data.len() > 2 {
|
||||
if data[0] != 0b1111_1111
|
||||
|| data[1] & 0b1111_1100 != 0b1111_1000
|
||||
{
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(FlacDecodeError::BadSyncBytes));
|
||||
}
|
||||
|
||||
let audio_frame = match FlacAudioFrame::decode(&data) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e));
|
||||
}
|
||||
};
|
||||
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Ok(FlacBlock::AudioFrame(audio_frame)));
|
||||
} else {
|
||||
self.state = ReaderState::Done;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
self.state = ReaderState::Done;
|
||||
return Some(Err(e.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
397
crates/pile-flac/src/reader/tests.rs
Normal file
397
crates/pile-flac/src/reader/tests.rs
Normal file
@@ -0,0 +1,397 @@
|
||||
#![expect(clippy::unwrap_used)]
|
||||
|
||||
use itertools::Itertools;
|
||||
use paste::paste;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{fs::File, io::Write, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
FlacDecodeError, TagType,
|
||||
tests::{FlacBlockOutput, FlacTestCase, VorbisCommentTestValue},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
fn read_file(test_case: &FlacTestCase) -> Result<Vec<FlacBlock>, FlacDecodeError> {
|
||||
let file_data = std::fs::read(test_case.get_path()).unwrap();
|
||||
|
||||
// Make sure input file is correct
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&file_data);
|
||||
assert_eq!(
|
||||
test_case.get_in_hash(),
|
||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||
);
|
||||
|
||||
let file = File::open(test_case.get_path()).unwrap();
|
||||
|
||||
let reader = FlacReader::new(file);
|
||||
let mut out_blocks = Vec::new();
|
||||
|
||||
for b in reader {
|
||||
out_blocks.push(b?)
|
||||
}
|
||||
|
||||
return Ok(out_blocks);
|
||||
}
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
fn test_identical(test_case: &FlacTestCase) -> Result<(), FlacDecodeError> {
|
||||
let out_blocks = read_file(test_case)?;
|
||||
|
||||
let mut out = Vec::new();
|
||||
out.write_all(&[0x66, 0x4C, 0x61, 0x43]).unwrap();
|
||||
|
||||
for i in 0..out_blocks.len() {
|
||||
let b = &out_blocks[i];
|
||||
let is_last = if i == out_blocks.len() - 1 {
|
||||
false
|
||||
} else {
|
||||
!matches!(b, FlacBlock::AudioFrame(_))
|
||||
&& matches!(&out_blocks[i + 1], FlacBlock::AudioFrame(_))
|
||||
};
|
||||
|
||||
b.encode(is_last, true, &mut out).unwrap();
|
||||
}
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&out);
|
||||
let result = hasher.finalize().map(|x| format!("{x:02x}")).join("");
|
||||
assert_eq!(result, test_case.get_in_hash(), "Output hash doesn't match");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn test_blockread(test_case: &FlacTestCase) -> Result<(), FlacDecodeError> {
|
||||
let out_blocks = read_file(test_case)?;
|
||||
|
||||
assert_eq!(
|
||||
test_case.get_blocks().unwrap().len(),
|
||||
out_blocks
|
||||
.iter()
|
||||
.filter(|x| !matches!(*x, FlacBlock::AudioFrame(_)))
|
||||
.count(),
|
||||
"Number of blocks didn't match"
|
||||
);
|
||||
|
||||
let mut audio_data_hasher = Sha256::new();
|
||||
let mut result_i = 0;
|
||||
|
||||
for b in out_blocks {
|
||||
match b {
|
||||
FlacBlock::Streaminfo(s) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||
FlacBlockOutput::Streaminfo {
|
||||
min_block_size,
|
||||
max_block_size,
|
||||
min_frame_size,
|
||||
max_frame_size,
|
||||
sample_rate,
|
||||
channels,
|
||||
bits_per_sample,
|
||||
total_samples,
|
||||
md5_signature,
|
||||
} => {
|
||||
assert_eq!(*min_block_size, s.min_block_size,);
|
||||
assert_eq!(*max_block_size, s.max_block_size);
|
||||
assert_eq!(*min_frame_size, s.min_frame_size);
|
||||
assert_eq!(*max_frame_size, s.max_frame_size);
|
||||
assert_eq!(*sample_rate, s.sample_rate);
|
||||
assert_eq!(*channels, s.channels);
|
||||
assert_eq!(*bits_per_sample, s.bits_per_sample);
|
||||
assert_eq!(*total_samples, s.total_samples);
|
||||
assert_eq!(
|
||||
*md5_signature,
|
||||
s.md5_signature.iter().map(|x| format!("{x:02x}")).join("")
|
||||
);
|
||||
}
|
||||
_ => panic!("Unexpected block type"),
|
||||
},
|
||||
|
||||
FlacBlock::Application(a) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||
FlacBlockOutput::Application {
|
||||
application_id,
|
||||
hash,
|
||||
} => {
|
||||
assert_eq!(
|
||||
*application_id, a.application_id,
|
||||
"Application id doesn't match"
|
||||
);
|
||||
assert_eq!(
|
||||
*hash,
|
||||
{
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
hasher.update(&a.data);
|
||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||
},
|
||||
"Application content hash doesn't match"
|
||||
);
|
||||
}
|
||||
_ => panic!("Unexpected block type"),
|
||||
},
|
||||
|
||||
FlacBlock::CueSheet(c) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||
FlacBlockOutput::CueSheet { hash } => {
|
||||
assert_eq!(*hash, {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
hasher.update(&c.data);
|
||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||
});
|
||||
}
|
||||
_ => panic!("Unexpected block type"),
|
||||
},
|
||||
|
||||
FlacBlock::Padding(p) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||
FlacBlockOutput::Padding { size } => {
|
||||
assert_eq!(p.size, *size);
|
||||
}
|
||||
_ => panic!("Unexpected block type"),
|
||||
},
|
||||
|
||||
FlacBlock::SeekTable(t) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||
FlacBlockOutput::Seektable { hash } => {
|
||||
assert_eq!(*hash, {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
hasher.update(&t.data);
|
||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||
});
|
||||
}
|
||||
_ => panic!("Unexpected block type"),
|
||||
},
|
||||
|
||||
FlacBlock::Picture(p) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||
FlacBlockOutput::Picture {
|
||||
picture_type,
|
||||
mime,
|
||||
description,
|
||||
width,
|
||||
height,
|
||||
bit_depth,
|
||||
color_count,
|
||||
img_data,
|
||||
} => {
|
||||
assert_eq!(*picture_type, p.picture_type, "{}", test_case.get_name());
|
||||
assert_eq!(*mime, p.mime, "{}", test_case.get_name());
|
||||
assert_eq!(*description, p.description, "{}", test_case.get_name());
|
||||
assert_eq!(*width, p.width, "{}", test_case.get_name());
|
||||
assert_eq!(*height, p.height, "{}", test_case.get_name());
|
||||
assert_eq!(*bit_depth, p.bit_depth, "{}", test_case.get_name());
|
||||
assert_eq!(*color_count, p.color_count, "{}", test_case.get_name());
|
||||
assert_eq!(
|
||||
*img_data,
|
||||
{
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
hasher.update(&p.img_data);
|
||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||
},
|
||||
"{}",
|
||||
test_case.get_name()
|
||||
);
|
||||
}
|
||||
_ => panic!("Unexpected block type"),
|
||||
},
|
||||
|
||||
FlacBlock::VorbisComment(v) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||
FlacBlockOutput::VorbisComment {
|
||||
vendor,
|
||||
comments,
|
||||
pictures,
|
||||
} => {
|
||||
assert_eq!(*vendor, v.comment.vendor, "Comment vendor doesn't match");
|
||||
|
||||
assert_eq!(
|
||||
v.comment.pictures.len(),
|
||||
pictures.len(),
|
||||
"Number of pictures doesn't match"
|
||||
);
|
||||
|
||||
for (p, e) in v.comment.pictures.iter().zip(*pictures) {
|
||||
match e {
|
||||
FlacBlockOutput::Picture {
|
||||
picture_type,
|
||||
mime,
|
||||
description,
|
||||
width,
|
||||
height,
|
||||
bit_depth,
|
||||
color_count,
|
||||
img_data,
|
||||
} => {
|
||||
assert_eq!(*picture_type, p.picture_type);
|
||||
assert_eq!(*mime, p.mime);
|
||||
assert_eq!(*description, p.description);
|
||||
assert_eq!(*width, p.width);
|
||||
assert_eq!(*height, p.height);
|
||||
assert_eq!(*bit_depth, p.bit_depth);
|
||||
assert_eq!(*color_count, p.color_count);
|
||||
assert_eq!(*img_data, {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&p.img_data);
|
||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||
});
|
||||
}
|
||||
_ => panic!("Bad test data: expected only Picture blocks."),
|
||||
}
|
||||
}
|
||||
|
||||
match comments {
|
||||
VorbisCommentTestValue::Raw { tags } => {
|
||||
assert_eq!(
|
||||
v.comment.comments.len(),
|
||||
tags.len(),
|
||||
"Number of comments doesn't match"
|
||||
);
|
||||
|
||||
for ((got_tag, got_val), (exp_tag, exp_val)) in
|
||||
v.comment.comments.iter().zip(*tags)
|
||||
{
|
||||
assert_eq!(
|
||||
*got_tag,
|
||||
TagType::from_str(exp_tag).unwrap(),
|
||||
"Tag key doesn't match"
|
||||
);
|
||||
assert_eq!(
|
||||
got_val, exp_val,
|
||||
"Tag value of {exp_tag} doesn't match"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
VorbisCommentTestValue::Hash { n_comments, hash } => {
|
||||
assert_eq!(
|
||||
v.comment.comments.len(),
|
||||
*n_comments,
|
||||
"Number of comments doesn't match"
|
||||
);
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
for (got_tag, got_val) in &v.comment.comments {
|
||||
hasher.update(
|
||||
format!("{}={got_val};", got_tag.to_vorbis_string()).as_bytes(),
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
&hasher.finalize().map(|x| format!("{x:02x}")).join(""),
|
||||
hash,
|
||||
"Comment hash doesn't match"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected block type"),
|
||||
},
|
||||
|
||||
FlacBlock::AudioFrame(data) => {
|
||||
let mut vec = Vec::new();
|
||||
data.encode(&mut vec).unwrap();
|
||||
audio_data_hasher.update(&vec);
|
||||
|
||||
if result_i != test_case.get_blocks().unwrap().len() {
|
||||
panic!("There are metadata blocks between audio frames!")
|
||||
}
|
||||
|
||||
// Don't increment result_i
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result_i += 1;
|
||||
}
|
||||
|
||||
// Check audio data hash
|
||||
assert_eq!(
|
||||
test_case.get_audio_hash().unwrap(),
|
||||
audio_data_hasher
|
||||
.finalize()
|
||||
.map(|x| format!("{x:02x}"))
|
||||
.join("")
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Helper macros to generate tests
|
||||
macro_rules! gen_tests {
|
||||
( $test_name:ident ) => {
|
||||
paste! {
|
||||
#[test]
|
||||
pub fn [<blockread_small_ $test_name>]() {
|
||||
let manifest = crate::tests::manifest();
|
||||
let test_case = manifest.iter().find(|x| x.get_name() == stringify!($test_name)).unwrap();
|
||||
|
||||
match test_case {
|
||||
FlacTestCase::Success { .. } => {
|
||||
for _ in 0..5 {
|
||||
test_blockread(
|
||||
test_case,
|
||||
).unwrap()
|
||||
}
|
||||
},
|
||||
|
||||
FlacTestCase::Error { check_error, .. } => {
|
||||
let e = test_blockread(test_case);
|
||||
match e {
|
||||
Err(e) => assert!(check_error(&e), "Unexpected error {e:?}"),
|
||||
_ => panic!("Unexpected error {e:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn [<identical_small_ $test_name>]() {
|
||||
let manifest = crate::tests::manifest();
|
||||
let test_case = manifest.iter().find(|x| x.get_name() == stringify!($test_name)).unwrap();
|
||||
|
||||
match test_case {
|
||||
FlacTestCase::Success { .. } => {
|
||||
for _ in 0..5 {
|
||||
test_identical(
|
||||
test_case,
|
||||
).unwrap()
|
||||
}
|
||||
},
|
||||
|
||||
FlacTestCase::Error { check_error, .. } => {
|
||||
let e = test_identical(test_case);
|
||||
match e {
|
||||
Err(e) => assert!(check_error(&e), "Unexpected error {e:?}"),
|
||||
_ => panic!("Unexpected error {e:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
gen_tests!(custom_01);
|
||||
gen_tests!(custom_02);
|
||||
gen_tests!(custom_03);
|
||||
|
||||
gen_tests!(uncommon_10);
|
||||
|
||||
gen_tests!(faulty_06);
|
||||
gen_tests!(faulty_07);
|
||||
gen_tests!(faulty_10);
|
||||
gen_tests!(faulty_11);
|
||||
|
||||
gen_tests!(subset_45);
|
||||
gen_tests!(subset_46);
|
||||
gen_tests!(subset_47);
|
||||
gen_tests!(subset_48);
|
||||
gen_tests!(subset_49);
|
||||
gen_tests!(subset_50);
|
||||
gen_tests!(subset_51);
|
||||
gen_tests!(subset_52);
|
||||
gen_tests!(subset_53);
|
||||
gen_tests!(subset_54);
|
||||
gen_tests!(subset_55);
|
||||
gen_tests!(subset_56);
|
||||
gen_tests!(subset_57);
|
||||
gen_tests!(subset_58);
|
||||
gen_tests!(subset_59);
|
||||
Reference in New Issue
Block a user