Skip to content

Add tests from taglib #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added tests/taglib/data/alaw.aifc
Binary file not shown.
Binary file added tests/taglib/data/empty.aiff
Binary file not shown.
Binary file added tests/taglib/data/excessive_alloc.aif
Binary file not shown.
Binary file added tests/taglib/data/segfault.aif
Binary file not shown.
3 changes: 3 additions & 0 deletions tests/taglib/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub(crate) mod util;

mod test_aiff;
116 changes: 116 additions & 0 deletions tests/taglib/test_aiff.rs
Original file line number Diff line number Diff line change
@@ -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));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

running 1 test
thread 'test_aiff::test_save_id3v2' panicked at 'assertion failed: !tfile.contains_tag_type(lofty::TagType::ID3v2)', tests/taglib/test_aiff.rs:80:9
stack backtrace:
   0: rust_begin_unwind
             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14
   2: core::panicking::panic
             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:48:5
   3: taglib::test_aiff::test_save_id3v2
             at ./tests/taglib/test_aiff.rs:80:3
   4: taglib::test_aiff::test_save_id3v2::{{closure}}
             at ./tests/taglib/test_aiff.rs:49:1
   5: core::ops::function::FnOnce::call_once
             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5
   6: core::ops::function::FnOnce::call_once
             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
test test_aiff::test_save_id3v2 ... FAILED

Copy link
Contributor Author

@sagudev sagudev Aug 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if I comment out this line similar test in line 90 runs successfully.

Lofty apparently keeps empty ID3v2 in it struct but does not write it, while TagLib probably removes it on save to keep internal struct in-sync with file.

Is this made on porpuse or is it a bug @Serial-ATA?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like TagLib removes them in File::save. Its not purposeful, just didn't think of doing it that way. I'll add it soon 🙂.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And something analogus to how TagLib handles files would be very useful too. The File instance would then needed to be stored somewhere inside.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean tying a TaggedFile to the File it was read from? Something like that could be pretty easily accomplished with a wrapper. I could add that as BoundTaggedFile maybe? I don't want that to be the default behavior though, to keeps things reusable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, something like that. I was also thinking about wrapper that would allowed that and provide backwards compatibility.

}

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);
// }
114 changes: 114 additions & 0 deletions tests/taglib/util/mod.rs
Original file line number Diff line number Diff line change
@@ -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<P: AsRef<std::path::Path>>(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());
};
}