diff --git a/tests/taglib/data/alaw.aifc b/tests/taglib/data/alaw.aifc new file mode 100644 index 000000000..33b4ea2a5 Binary files /dev/null and b/tests/taglib/data/alaw.aifc differ diff --git a/tests/taglib/data/empty.aiff b/tests/taglib/data/empty.aiff new file mode 100644 index 000000000..849b762da Binary files /dev/null and b/tests/taglib/data/empty.aiff differ diff --git a/tests/taglib/data/excessive_alloc.aif b/tests/taglib/data/excessive_alloc.aif new file mode 100644 index 000000000..9cb3a6e10 Binary files /dev/null and b/tests/taglib/data/excessive_alloc.aif differ diff --git a/tests/taglib/data/segfault.aif b/tests/taglib/data/segfault.aif new file mode 100644 index 000000000..5dce192b0 Binary files /dev/null and b/tests/taglib/data/segfault.aif differ diff --git a/tests/taglib/main.rs b/tests/taglib/main.rs new file mode 100644 index 000000000..9da6db782 --- /dev/null +++ b/tests/taglib/main.rs @@ -0,0 +1,3 @@ +pub(crate) mod util; + +mod test_aiff; diff --git a/tests/taglib/test_aiff.rs b/tests/taglib/test_aiff.rs new file mode 100644 index 000000000..4361f9bcc --- /dev/null +++ b/tests/taglib/test_aiff.rs @@ -0,0 +1,116 @@ +use std::io::Seek; + +use crate::util::get_filetype; +use crate::{assert_delta, temp_file}; +use lofty::{Accessor, AudioFile, FileType}; + +#[test] +#[ignore] +fn test_aiff_properties() { + let file = lofty::read_from_path("tests/taglib/data/empty.aiff", true).unwrap(); + + assert_eq!(file.file_type(), FileType::AIFF); + + let properties = file.properties(); + assert_eq!(properties.duration().as_secs(), 0); + assert_delta!(properties.duration().as_millis(), 67, 1); + assert_delta!(properties.audio_bitrate().unwrap(), 706, 1); + assert_eq!(properties.sample_rate(), Some(44100)); + assert_eq!(properties.channels(), Some(1)); + assert_eq!(properties.bit_depth(), Some(16)); + // TODO: get those options in lofty + // CPPUNIT_ASSERT_EQUAL(2941U, f.audioProperties()->sampleFrames()); + // CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isAiffC()); +} + +#[test] +#[ignore] +fn test_aifc_properties() { + let file = lofty::read_from_path("tests/taglib/data/alaw.aifc", true).unwrap(); + + assert_eq!(file.file_type(), FileType::AIFF); + + let properties = file.properties(); + assert_eq!(properties.duration().as_secs(), 0); + assert_delta!(properties.duration().as_millis(), 37, 1); + assert_eq!(properties.audio_bitrate(), Some(355)); + assert_eq!(properties.sample_rate(), Some(44100)); + assert_eq!(properties.channels(), Some(1)); + assert_eq!(properties.bit_depth(), Some(16)); + // TODO: get those options in lofty + // CPPUNIT_ASSERT_EQUAL(1622U, f.audioProperties()->sampleFrames()); + // CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isAiffC()); + // CPPUNIT_ASSERT_EQUAL(ByteVector("ALAW"), f.audioProperties()->compressionType()); + // CPPUNIT_ASSERT_EQUAL(String("SGI CCITT G.711 A-law"), f.audioProperties()->compressionName()); +} + +#[test] +#[ignore] +fn test_save_id3v2() { + let mut file = temp_file!("tests/taglib/data/empty.aiff"); + + { + let mut tfile = lofty::read_from(&mut file, true).unwrap(); + + assert_eq!(tfile.file_type(), FileType::AIFF); + + assert!(tfile.tag(lofty::TagType::ID3v2).is_none()); + + let mut tag = lofty::Tag::new(lofty::TagType::ID3v2); + tag.set_title("TitleXXX".to_string()); + tfile.insert_tag(tag); + file.rewind().unwrap(); + tfile.save_to(&mut file).unwrap(); + assert!(tfile.contains_tag_type(lofty::TagType::ID3v2)); + } + + file.rewind().unwrap(); + + { + let mut tfile = lofty::read_from(&mut file, true).unwrap(); + + assert_eq!(tfile.file_type(), FileType::AIFF); + + let mut tag = tfile.tag(lofty::TagType::ID3v2).unwrap().to_owned(); + assert_eq!(tag.title(), Some("TitleXXX")); + tag.set_title("".to_string()); + tfile.insert_tag(tag); + file.rewind().unwrap(); + tfile.save_to(&mut file).unwrap(); + assert!(!tfile.contains_tag_type(lofty::TagType::ID3v2)); + } + + file.rewind().unwrap(); + + { + let tfile = lofty::read_from(&mut file, true).unwrap(); + + assert_eq!(tfile.file_type(), FileType::AIFF); + + assert!(!tfile.contains_tag_type(lofty::TagType::ID3v2)); + } +} + +// TODO: testSaveID3v23 +// TODO: testDuplicateID3v2 + +#[test] +#[ignore] +fn test_fuzzed_file1() { + assert_eq!( + get_filetype("tests/taglib/data/segfault.aif"), + FileType::AIFF + ); +} + +// the file doesn't even have a valid signature +// #[test] +// #[ignore] +// fn test_fuzzed_file2() { +// let mut file = File::open("tests/taglib/data/excessive_alloc.aif").unwrap(); +// +// let mut buf = [0; 12]; +// file.read_exact(&mut buf).unwrap(); +// +// assert_eq!(FileType::from_buffer(&buf).unwrap(), FileType::AIFF); +// } diff --git a/tests/taglib/util/mod.rs b/tests/taglib/util/mod.rs new file mode 100644 index 000000000..f81c89ca1 --- /dev/null +++ b/tests/taglib/util/mod.rs @@ -0,0 +1,114 @@ +/// This function tries to simulate TagLibs isValid function +// https://github.com/Serial-ATA/lofty-rs/pull/51#discussion_r873171570 +pub fn get_filetype>(path: P) -> lofty::FileType { + let mut file = std::fs::File::open(path).unwrap(); + let mut buf = [0; 12]; + std::io::Read::read_exact(&mut file, &mut buf).unwrap(); + lofty::FileType::from_buffer(&buf).unwrap() +} + +#[macro_export] +macro_rules! assert_delta { + ($x:expr, $y:expr, $d:expr) => { + if $x > $y { + assert!($x - $y <= $d) + } else if $y > $x { + assert!($y - $x <= $d) + } + }; +} + +#[macro_export] +macro_rules! temp_file { + ($path:tt) => {{ + use std::io::{Seek, Write}; + let mut file = tempfile::tempfile().unwrap(); + file.write_all(&std::fs::read($path).unwrap()).unwrap(); + + file.seek(std::io::SeekFrom::Start(0)).unwrap(); + + file + }}; +} + +#[macro_export] +macro_rules! verify_artist { + ($file:ident, $method:ident, $expected_value:literal, $item_count:expr) => {{ + println!("VERIFY: Expecting `{}` to have {} items, with an artist of \"{}\"", stringify!($method), $item_count, $expected_value); + + verify_artist!($file, $method(), $expected_value, $item_count) + }}; + ($file:ident, $method:ident, $arg:path, $expected_value:literal, $item_count:expr) => {{ + println!("VERIFY: Expecting `{}` to have {} items, with an artist of \"{}\"", stringify!($arg), $item_count, $expected_value); + + verify_artist!($file, $method($arg), $expected_value, $item_count) + }}; + ($file:ident, $method:ident($($arg:path)?), $expected_value:literal, $item_count:expr) => {{ + assert!($file.$method($(&$arg)?).is_some()); + + let tag = $file.$method($(&$arg)?).unwrap(); + + assert_eq!(tag.item_count(), $item_count); + + assert_eq!( + tag.get_item_ref(&ItemKey::TrackArtist), + Some(&TagItem::new( + ItemKey::TrackArtist, + ItemValue::Text(String::from($expected_value)) + )) + ); + + tag + }}; +} + +#[macro_export] +macro_rules! set_artist { + ($tagged_file:ident, $method:ident, $expected_value:literal, $item_count:expr => $file_write:ident, $new_value:literal) => { + let tag = verify_artist!($tagged_file, $method, $expected_value, $item_count); + println!( + "WRITE: Writing artist \"{}\" to {}\n", + $new_value, + stringify!($method) + ); + set_artist!($file_write, $new_value, tag) + }; + ($tagged_file:ident, $method:ident, $arg:path, $expected_value:literal, $item_count:expr => $file_write:ident, $new_value:literal) => { + let tag = verify_artist!($tagged_file, $method, $arg, $expected_value, $item_count); + println!( + "WRITE: Writing artist \"{}\" to {}\n", + $new_value, + stringify!($arg) + ); + set_artist!($file_write, $new_value, tag) + }; + ($file_write:ident, $new_value:literal, $tag:ident) => { + $tag.insert_item_unchecked(TagItem::new( + ItemKey::TrackArtist, + ItemValue::Text(String::from($new_value)), + )); + + $file_write.seek(std::io::SeekFrom::Start(0)).unwrap(); + + $tag.save_to(&mut $file_write).unwrap(); + }; +} + +#[macro_export] +macro_rules! remove_tag { + ($path:tt, $tag_type:path) => { + let mut file = temp_file!($path); + + let tagged_file = lofty::read_from(&mut file, false).unwrap(); + assert!(tagged_file.tag(&$tag_type).is_some()); + + file.seek(std::io::SeekFrom::Start(0)).unwrap(); + + $tag_type.remove_from(&mut file).unwrap(); + + file.seek(std::io::SeekFrom::Start(0)).unwrap(); + + let tagged_file = lofty::read_from(&mut file, false).unwrap(); + assert!(tagged_file.tag(&$tag_type).is_none()); + }; +}