use musefs_core::{
ScanOptions, revalidate, revalidate_with, scan_directory, scan_directory_full_oracle,
scan_directory_with,
};
use musefs_db::Db;
fn flac_minimal(audio: &[u8]) -> Vec<u8> {
let mut b = b"fLaC".to_vec();
b.push(0x80); b.extend_from_slice(&[0, 0, 34]);
b.extend(std::iter::repeat_n(0u8, 34));
b.extend_from_slice(audio);
b
}
fn flac_with_big_art(data_len: usize, audio: &[u8]) -> Vec<u8> {
let mut v = b"fLaC".to_vec();
v.push(0x00); v.extend_from_slice(&[0, 0, 34]);
v.extend(std::iter::repeat_n(0u8, 34));
let mut body = Vec::new();
body.extend_from_slice(&3u32.to_be_bytes()); let mime = b"image/png";
body.extend_from_slice(&u32::try_from(mime.len()).unwrap().to_be_bytes());
body.extend_from_slice(mime);
body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&u32::try_from(data_len).unwrap().to_be_bytes());
body.extend((0u8..=200).cycle().take(data_len));
v.push(0x86); let blen = body.len();
v.extend_from_slice(&[
u8::try_from((blen >> 16) & 0xFF).unwrap(),
u8::try_from((blen >> 8) & 0xFF).unwrap(),
u8::try_from(blen & 0xFF).unwrap(),
]);
v.extend_from_slice(&body);
v.extend_from_slice(audio);
v
}
fn rows(db: &Db) -> Vec<(String, u64, u64)> {
let mut r: Vec<_> = db
.list_tracks()
.unwrap()
.into_iter()
.map(|t| {
(
t.backing_path,
t.bounds.audio_offset(),
t.bounds.audio_length(),
)
})
.collect();
r.sort();
r
}
#[test]
fn widen_then_fallback_matches_oracle_under_tiny_window() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join("big.flac"),
flac_with_big_art(5000, b"AUDIOPAYLOAD-BIG"),
)
.unwrap();
std::fs::write(
dir.path().join("small.flac"),
flac_minimal(b"AUDIOPAYLOAD-SMALL"),
)
.unwrap();
let oracle_db = Db::open_in_memory().unwrap();
scan_directory_full_oracle(&oracle_db, dir.path()).unwrap();
let oracle = rows(&oracle_db);
let bounded_db = Db::open_in_memory().unwrap();
let stats = scan_directory_with(
&bounded_db,
dir.path(),
&ScanOptions {
window: 64,
..Default::default()
},
)
.unwrap();
assert_eq!(stats.scanned, 2);
assert_eq!(rows(&bounded_db), oracle, "bounded widen/fallback diverged");
assert!(!oracle.is_empty());
}
#[test]
fn widen_preserves_art_bytes_vs_oracle() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join("art.flac"),
flac_with_big_art(4096, b"TAILAUDIO"),
)
.unwrap();
let oracle_db = Db::open_in_memory().unwrap();
scan_directory_full_oracle(&oracle_db, dir.path()).unwrap();
let o_track = oracle_db.list_tracks().unwrap().into_iter().next().unwrap();
let o_art = oracle_db.get_track_art(o_track.id).unwrap();
let o_sha = oracle_db.get_art(o_art[0].art_id).unwrap().unwrap().sha256;
let db = Db::open_in_memory().unwrap();
scan_directory_with(
&db,
dir.path(),
&ScanOptions {
window: 16,
..Default::default()
},
)
.unwrap();
let track = db.list_tracks().unwrap().into_iter().next().unwrap();
assert_eq!(track.bounds.audio_offset(), o_track.bounds.audio_offset());
assert_eq!(track.bounds.audio_length(), o_track.bounds.audio_length());
let art = db.get_track_art(track.id).unwrap();
assert_eq!(art.len(), 1, "the embedded picture must survive the widen");
let sha = db.get_art(art[0].art_id).unwrap().unwrap().sha256;
assert_eq!(
sha, o_sha,
"widened art bytes must match the oracle exactly"
);
}
#[test]
fn scans_more_than_batch_files_persists_all_once() {
let n = 300usize; let dir = tempfile::tempdir().unwrap();
for i in 0..n {
std::fs::write(
dir.path().join(format!("t{i:04}.flac")),
flac_minimal(format!("AUDIO-{i}").as_bytes()),
)
.unwrap();
}
let db = Db::open_in_memory().unwrap();
let stats = scan_directory_with(
&db,
dir.path(),
&ScanOptions {
jobs: 4,
..Default::default()
},
)
.unwrap();
assert_eq!(stats.scanned, n as u64, "every file must be scanned once");
assert_eq!(
db.list_tracks().unwrap().len(),
n,
"every file persisted once"
);
let stats2 = scan_directory_with(
&db,
dir.path(),
&ScanOptions {
jobs: 4,
..Default::default()
},
)
.unwrap();
assert_eq!(stats2.scanned, 0);
assert_eq!(stats2.already_present, n as u64);
assert_eq!(db.list_tracks().unwrap().len(), n);
}
#[test]
fn byte_threshold_flush_persists_all_art() {
let n = 20usize;
let dir = tempfile::tempdir().unwrap();
for i in 0..n {
std::fs::write(
dir.path().join(format!("a{i:03}.flac")),
flac_with_big_art(64, format!("AUD-{i}").as_bytes()),
)
.unwrap();
}
let db = Db::open_in_memory().unwrap();
let stats = scan_directory_with(
&db,
dir.path(),
&ScanOptions {
jobs: 4,
batch_bytes: 100,
..Default::default()
},
)
.unwrap();
assert_eq!(stats.scanned, n as u64);
let tracks = db.list_tracks().unwrap();
assert_eq!(tracks.len(), n);
for t in tracks {
assert_eq!(
db.get_track_art(t.id).unwrap().len(),
1,
"each track's art must persist through byte-threshold flushing"
);
}
}
#[test]
fn revalidate_unchanged_count_matches_file_count() {
let n = 5usize;
let dir = tempfile::tempdir().unwrap();
for i in 0..n {
std::fs::write(
dir.path().join(format!("u{i}.flac")),
flac_minimal(format!("AUDIO-{i}").as_bytes()),
)
.unwrap();
}
let db = Db::open_in_memory().unwrap();
scan_directory(&db, dir.path()).unwrap();
let stats = revalidate_with(&db, dir.path(), &ScanOptions::default()).unwrap();
assert_eq!(
stats.unchanged, n as u64,
"all files unchanged → unchanged == N"
);
assert_eq!(stats.updated, 0);
assert_eq!(stats.pruned, 0);
assert_eq!(stats.failed, 0);
}
#[test]
fn revalidate_failed_carries_scan_failure() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("ok.flac"), flac_minimal(b"AUDIO-OK")).unwrap();
let denied = dir.path().join("denied.flac");
std::fs::write(&denied, flac_minimal(b"AUDIO-DENIED")).unwrap();
let db = Db::open_in_memory().unwrap();
let s0 = scan_directory(&db, dir.path()).unwrap();
assert_eq!(s0.scanned, 2);
assert_eq!(s0.failed, 0);
std::fs::write(&denied, flac_minimal(b"AUDIO-DENIED-CHANGED")).unwrap();
std::fs::set_permissions(&denied, std::fs::Permissions::from_mode(0o000)).unwrap();
if std::fs::File::open(&denied).is_ok() {
eprintln!(
"skipping revalidate_failed_carries_scan_failure: file permissions not enforced (running as root?)"
);
std::fs::set_permissions(&denied, std::fs::Permissions::from_mode(0o644)).unwrap();
return;
}
let stats = revalidate(&db, dir.path()).unwrap();
std::fs::set_permissions(&denied, std::fs::Permissions::from_mode(0o644)).unwrap();
assert_eq!(stats.unchanged, 1, "ok.flac is unchanged");
assert_eq!(
stats.failed, 1,
"failed must carry the re-probe scan failure (skip_failed == 0)"
);
}
#[test]
fn oracle_counts_scanned_failed_and_skipped_exactly() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("good.flac"), flac_minimal(b"AUDIO-OK")).unwrap();
std::fs::write(dir.path().join("bad.flac"), b"not a flac at all").unwrap();
std::fs::write(dir.path().join("notes.txt"), b"not audio").unwrap();
let db = Db::open_in_memory().unwrap();
let stats = scan_directory_full_oracle(&db, dir.path()).unwrap();
assert_eq!(stats.scanned, 1, "exactly the one valid FLAC is scanned");
assert_eq!(stats.failed, 1, "the garbage .flac is a failure");
assert_eq!(stats.skipped, 1, "the .txt is skipped at collection");
}
#[test]
fn follow_symlinks_dedups_file_and_sibling_symlink() {
use std::os::unix::fs::symlink;
let dir = tempfile::tempdir().unwrap();
let song = dir.path().join("song.flac");
std::fs::write(&song, flac_minimal(b"AUDIO-SONG")).unwrap();
symlink(&song, dir.path().join("link.flac")).unwrap();
let db = Db::open_in_memory().unwrap();
let opts = ScanOptions {
follow_symlinks: true,
..Default::default()
};
let stats = scan_directory_with(&db, dir.path(), &opts).unwrap();
assert_eq!(stats.scanned, 1, "real file and its symlink ingest once");
assert_eq!(stats.skipped, 0);
assert_eq!(stats.failed, 0);
assert_eq!(db.list_tracks().unwrap().len(), 1);
}
#[test]
fn follow_symlinks_dedups_file_across_directories() {
use std::os::unix::fs::symlink;
let dir = tempfile::tempdir().unwrap();
let a = dir.path().join("a");
let b = dir.path().join("b");
std::fs::create_dir(&a).unwrap();
std::fs::create_dir(&b).unwrap();
let song = a.join("song.flac");
std::fs::write(&song, flac_minimal(b"AUDIO-SONG")).unwrap();
symlink(&song, b.join("alias.flac")).unwrap();
let db = Db::open_in_memory().unwrap();
let opts = ScanOptions {
follow_symlinks: true,
..Default::default()
};
let stats = scan_directory_with(&db, dir.path(), &opts).unwrap();
assert_eq!(stats.scanned, 1);
assert_eq!(db.list_tracks().unwrap().len(), 1);
}
#[test]
fn follow_symlinks_dedups_via_symlinked_directory() {
use std::os::unix::fs::symlink;
let dir = tempfile::tempdir().unwrap();
let real = dir.path().join("real");
std::fs::create_dir(&real).unwrap();
std::fs::write(real.join("song.flac"), flac_minimal(b"AUDIO-SONG")).unwrap();
symlink(&real, dir.path().join("mirror")).unwrap();
let db = Db::open_in_memory().unwrap();
let opts = ScanOptions {
follow_symlinks: true,
..Default::default()
};
let stats = scan_directory_with(&db, dir.path(), &opts).unwrap();
assert_eq!(stats.scanned, 1);
assert_eq!(db.list_tracks().unwrap().len(), 1);
}
#[test]
fn follow_symlinks_counts_unsupported_symlink_target_as_skipped() {
use std::os::unix::fs::symlink;
let dir = tempfile::tempdir().unwrap();
let txt = dir.path().join("notes.txt");
std::fs::write(&txt, b"hi").unwrap();
symlink(&txt, dir.path().join("link.txt")).unwrap();
std::fs::write(dir.path().join("song.flac"), flac_minimal(b"AUDIO")).unwrap();
let db = Db::open_in_memory().unwrap();
let opts = ScanOptions {
follow_symlinks: true,
..Default::default()
};
let stats = scan_directory_with(&db, dir.path(), &opts).unwrap();
assert_eq!(stats.scanned, 1);
assert_eq!(stats.skipped, 2);
}
#[test]
fn scan_single_unsupported_file_root_is_skipped() {
let dir = tempfile::tempdir().unwrap();
let txt = dir.path().join("notes.txt");
std::fs::write(&txt, b"hi").unwrap();
let db = Db::open_in_memory().unwrap();
let stats = scan_directory(&db, &txt).unwrap();
assert_eq!(stats.scanned, 0);
assert_eq!(stats.skipped, 1);
}
#[test]
fn oracle_single_unsupported_file_root_is_skipped() {
let dir = tempfile::tempdir().unwrap();
let txt = dir.path().join("notes.txt");
std::fs::write(&txt, b"hi").unwrap();
let db = Db::open_in_memory().unwrap();
let stats = scan_directory_full_oracle(&db, &txt).unwrap();
assert_eq!(stats.scanned, 0);
assert_eq!(stats.skipped, 1);
}
#[test]
fn follow_symlinks_mirrored_dir_counts_unsupported_file_once() {
use std::os::unix::fs::symlink;
let dir = tempfile::tempdir().unwrap();
let real = dir.path().join("real");
std::fs::create_dir(&real).unwrap();
std::fs::write(real.join("notes.txt"), b"not audio").unwrap();
symlink(&real, dir.path().join("mirror")).unwrap();
let db = Db::open_in_memory().unwrap();
let opts = ScanOptions {
follow_symlinks: true,
..Default::default()
};
let stats = scan_directory_with(&db, dir.path(), &opts).unwrap();
assert_eq!(stats.scanned, 0);
assert_eq!(stats.skipped, 1, "notes.txt is skipped once, not twice");
}
#[test]
fn follow_symlinks_dedups_hardlinks_to_same_inode() {
let dir = tempfile::tempdir().unwrap();
let song = dir.path().join("song.flac");
std::fs::write(&song, flac_minimal(b"AUDIO-SONG")).unwrap();
std::fs::hard_link(&song, dir.path().join("link.flac")).unwrap();
let db = Db::open_in_memory().unwrap();
let opts = ScanOptions {
follow_symlinks: true,
..Default::default()
};
let stats = scan_directory_with(&db, dir.path(), &opts).unwrap();
assert_eq!(
stats.scanned, 1,
"hardlinks to one inode ingest once under follow"
);
assert_eq!(db.list_tracks().unwrap().len(), 1);
}