use super::*;
#[test]
fn reserve_under_cap_rejects_buffer_that_would_exceed_cap() {
let cap = 8usize;
let mut out: Vec<f32> = vec![0.0; cap - 1];
let cap_before = out.capacity();
assert!(reserve_under_cap(&mut out, 1, cap).is_ok());
out.resize(cap, 0.0); let r = reserve_under_cap(&mut out, 1, cap);
assert!(
matches!(r, Err(Error::BoundedDecode(_))),
"over-cap buffer must return a recoverable BoundedDecode error, got {r:?}"
);
assert!(
out.len() <= cap,
"out grew past cap on the rejected path: len={} cap={cap}",
out.len()
);
assert!(
out.capacity() >= cap_before,
"capacity unexpectedly shrank: {} < {cap_before}",
out.capacity()
);
}
#[test]
fn reserve_under_cap_rejects_oversized_buffer_against_empty_out() {
let cap = 16usize;
let mut out: Vec<f32> = Vec::new();
let r = reserve_under_cap(&mut out, cap + 1, cap);
assert!(
matches!(r, Err(Error::BoundedDecode(_))),
"buffer larger than the cap must be rejected, got {r:?}"
);
assert_eq!(
out.len(),
0,
"rejected reservation must not append anything"
);
assert!(
out.capacity() <= cap,
"rejected reservation must not allocate past the cap: capacity={}",
out.capacity()
);
}
#[test]
fn reserve_under_cap_rejects_overflowing_n_without_panic() {
let mut out: Vec<f32> = vec![0.0; 1]; let cap = 1024usize;
let n = usize::MAX;
let r = reserve_under_cap(&mut out, n, cap);
match r {
Err(Error::BoundedDecode(p)) => {
assert_eq!(p.cap(), cap as u64, "cap field must be the configured cap");
assert_eq!(
p.observed(),
u64::MAX,
"observed must be saturated at u64::MAX, not wrapped"
);
}
other => panic!("expected BoundedDecode err, got {other:?}"),
}
assert_eq!(out.len(), 1, "rejected reservation must not append");
}
#[test]
fn reserve_under_cap_accepts_and_reserves_up_to_cap() {
let cap = 32usize;
let mut out: Vec<f32> = Vec::new();
reserve_under_cap(&mut out, cap, cap).expect("filling exactly to cap must succeed");
assert!(
out.capacity() >= cap,
"reservation did not provide cap room: capacity={} cap={cap}",
out.capacity()
);
for i in 0..cap {
out.push(i as f32);
}
assert_eq!(out.len(), cap);
}
#[test]
fn reserve_under_cap_growth_does_not_exceed_cap() {
let cap = 64usize;
let mut out: Vec<f32> = Vec::with_capacity(cap);
out.resize(cap - 1, 0.0);
assert_eq!(out.capacity(), cap, "precondition: capacity == cap");
reserve_under_cap(&mut out, 1, cap).expect("in-cap final packet must be accepted");
assert!(
out.capacity() <= cap,
"reserve grew capacity past the cap: capacity={} cap={cap}",
out.capacity()
);
let packet = 8usize;
let mut out2: Vec<f32> = Vec::with_capacity(cap - packet);
out2.resize(cap - packet, 0.0);
let cap2_before = out2.capacity();
reserve_under_cap(&mut out2, packet, cap).expect("packet filling exactly to cap must succeed");
assert!(
out2.capacity() <= cap,
"reserve grew capacity past the cap: capacity={} cap={cap}",
out2.capacity()
);
assert!(
out2.capacity() >= cap2_before + packet,
"reserve did not provide room for the packet: capacity={} need>={}",
out2.capacity(),
cap2_before + packet
);
for i in 0..packet {
out2.push(i as f32);
}
assert_eq!(out2.len(), cap);
}
fn audio_temp_dir(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("mlxrs_audio_io_{}_{}", std::process::id(), name));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
dir
}
fn strip_rust_comments(src: &str) -> String {
let mut out = String::with_capacity(src.len());
let mut chars = src.chars().peekable();
while let Some(c) = chars.next() {
if c == '/' && chars.peek() == Some(&'/') {
chars.next();
for nc in chars.by_ref() {
if nc == '\n' {
out.push('\n');
break;
}
}
continue;
}
if c == '/' && chars.peek() == Some(&'*') {
chars.next();
let mut prev = '\0';
for nc in chars.by_ref() {
if prev == '*' && nc == '/' {
break;
}
prev = nc;
}
continue;
}
if c == '"' {
out.push('"');
while let Some(nc) = chars.next() {
out.push(nc);
if nc == '\\' {
if let Some(esc) = chars.next() {
out.push(esc);
}
continue;
}
if nc == '"' {
break;
}
}
continue;
}
out.push(c);
}
out
}
#[test]
fn save_wav_fsyncs_parent_dir_after_rename() {
let dir = audio_temp_dir("audio9_fsync_parent");
let path = dir.join("out.wav");
let samples = vec![0.0_f32, 0.5, -0.5, 0.25, -0.25, 1.0, -1.0, 0.0];
save_wav(&path, &samples, 16_000).expect("save_wav must succeed on a fresh path");
assert!(
path.exists(),
"post-save path must be observable (parent-dir fsync did not corrupt the rename)"
);
let meta = fs::metadata(&path).expect("destination metadata must be readable");
assert_eq!(
meta.len(),
44 + 2 * samples.len() as u64,
"post-save WAV size must match the header + i16 samples body"
);
save_wav(&path, &samples, 16_000).expect("save_wav overwrite must succeed");
assert!(
path.exists(),
"post-overwrite path must still be observable"
);
}
#[cfg(unix)]
#[test]
fn save_wav_preserves_xattrs_on_overwrite() {
if !xattr::SUPPORTED_PLATFORM {
return; }
let dir = audio_temp_dir("audio10_xattr_preserve");
let path = dir.join("out.wav");
let samples = vec![0.0_f32, 0.1, -0.1, 0.0];
save_wav(&path, &samples, 16_000).expect("initial save_wav must succeed");
let xattr_name = "user.mlxrs.p6_audio10";
let xattr_value: &[u8] = b"p6-audio10-marker";
if xattr::set(&path, xattr_name, xattr_value).is_err() {
return; }
let new_samples = vec![0.5_f32, -0.5, 0.25, -0.25];
save_wav(&path, &new_samples, 16_000).expect("overwriting save_wav must succeed");
let got = xattr::get(&path, xattr_name).expect("xattr::get on the overwritten file must succeed");
assert_eq!(
got.as_deref(),
Some(xattr_value),
"xattr {xattr_name:?} was lost during the save_wav overwrite — \
capture_xattrs/restore_xattrs is not preserving the user namespace"
);
}
#[cfg(unix)]
#[test]
fn capture_xattrs_returns_some_on_existing_unix_path() {
if !xattr::SUPPORTED_PLATFORM {
return;
}
let dir = audio_temp_dir("audio10_xattr_capture");
let path = dir.join("probe.wav");
let samples = vec![0.0_f32, 0.0];
save_wav(&path, &samples, 16_000).expect("save_wav must succeed");
let captured = capture_xattrs(&path);
assert!(
captured.is_some(),
"capture_xattrs must return Some on an existing path on a supported platform"
);
}
#[test]
fn capture_xattrs_source_includes_acl_security_explicit_probes() {
let src = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/audio/io.rs"))
.expect("io.rs must be readable from CARGO_MANIFEST_DIR");
let decl_start = src
.find("const EXPLICIT_PROBES:")
.expect("capture_xattrs must declare a `const EXPLICIT_PROBES:` constant");
let decl_end_rel = src[decl_start..]
.find(';')
.expect("EXPLICIT_PROBES declaration must terminate with `;`");
let decl_slice = &src[decl_start..decl_start + decl_end_rel];
let stripped = strip_rust_comments(decl_slice);
for needle in [
"system.posix_acl_access",
"system.posix_acl_default",
"security.selinux",
"security.capability",
"security.ima",
"security.evm",
] {
let quoted = format!("\"{needle}\"");
assert!(
stripped.contains("ed),
"capture_xattrs EXPLICIT_PROBES must include {needle:?} as a \
string literal (the ACL/security namespace `listxattr` may \
omit) — removing it silently regresses the #138 ACL/security xattr capture.\n\
Inspected (comments stripped) slice:\n{stripped}"
);
}
}
#[test]
fn save_wav_calls_post_metadata_fsync_helper_before_rename() {
let src = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/audio/io.rs"))
.expect("io.rs must be readable from CARGO_MANIFEST_DIR");
let sig_idx = src
.find("pub fn save_wav")
.expect("save_wav function must be defined");
let body_after = &src[sig_idx..];
let body_end_rel = body_after[1..]
.find("\nfn ")
.or_else(|| body_after[1..].find("\npub fn "))
.expect("save_wav body must be followed by another top-level fn item");
let body = &body_after[..1 + body_end_rel];
let stripped = strip_rust_comments(body);
let restore_idx = stripped
.find("restore_xattrs(")
.expect("save_wav must call restore_xattrs before the rename");
let rename_idx = stripped[restore_idx..]
.find("fs::rename(")
.map(|o| restore_idx + o)
.expect(
"save_wav must call fs::rename AFTER restore_xattrs — \
see #138",
);
let between = &stripped[restore_idx..rename_idx];
let helper_token = "save_wav_post_metadata_fsync(";
let helper_rel = between.find(helper_token).expect(
"save_wav must call `save_wav_post_metadata_fsync(` between \
restore_xattrs and fs::rename — see #138. \
The helper folds the test-only failure-injection branch + the \
real `meta_file.sync_all()` call so the call site is a single \
distinctive function call (no cfg-branch substring matching). \
A regression that dropped this call would leave the \
post-metadata fsync unrun on the metadata-restoration path, \
and the restored permissions/xattrs would not be durable \
across a crash between rename and parent-dir fsync.",
);
let helper_abs = restore_idx + helper_rel;
assert!(
restore_idx < helper_abs && helper_abs < rename_idx,
"save_wav ordering broken: restore_xattrs @ {restore_idx}, \
save_wav_post_metadata_fsync @ {helper_abs}, fs::rename @ \
{rename_idx} — the helper call must sit between restore_xattrs \
and fs::rename"
);
}
#[cfg(unix)]
#[test]
fn save_wav_read_only_overwrite_fsyncs_metadata_before_rename() {
use std::os::unix::fs::PermissionsExt;
let dir = audio_temp_dir("audio138_ro_overwrite");
let path = dir.join("ro.wav");
let initial = vec![0.0_f32, 0.0];
save_wav(&path, &initial, 16_000).expect("initial save_wav must succeed");
fs::set_permissions(&path, std::fs::Permissions::from_mode(0o444))
.expect("set_permissions(0o444) on pre-existing file must succeed");
let new_samples: Vec<f32> = (0..32).map(|i| ((i as f32) - 16.0) / 32.0).collect();
save_wav(&path, &new_samples, 16_000)
.expect("save_wav overwrite of 0o444 destination must succeed (#138)");
assert!(path.exists(), "post-overwrite path must be observable");
let meta = fs::metadata(&path).expect("destination metadata must be readable");
assert_eq!(
meta.len(),
44 + 2 * new_samples.len() as u64,
"post-overwrite WAV size must reflect the NEW sample buffer (not the initial buffer)"
);
let mode = meta.permissions().mode() & 0o777;
assert_eq!(
mode, 0o444,
"captured 0o444 mode must be restored on the published file \
(got {mode:#o}); the post-metadata fsync must run on the \
ORIGINAL writable handle so it doesn't fail with EACCES on \
reopen — see #138"
);
let _ = fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644));
}
#[test]
fn save_wav_post_metadata_fsync_helper_is_called_before_rename_runtime() {
struct ResetOnDrop;
impl Drop for ResetOnDrop {
fn drop(&mut self) {
set_force_meta_fsync_failure(false);
}
}
let dir = audio_temp_dir("audio138_fsync_failure");
let path = dir.join("dest.wav");
let initial_samples = vec![0.5_f32; 256];
save_wav(&path, &initial_samples, 16_000).expect("initial save_wav must succeed");
let original_bytes = fs::read(&path).expect("must read initial bytes");
assert!(
!original_bytes.is_empty(),
"initial save must produce non-empty bytes"
);
set_force_meta_fsync_failure(true);
let _reset = ResetOnDrop;
let new_samples: Vec<f32> = (0..512).map(|i| ((i as f32) - 256.0) / 512.0).collect();
let result = save_wav(&path, &new_samples, 16_000);
match &result {
Err(Error::FileIo(payload)) => {
assert_eq!(
payload.op(),
FileOp::Fsync,
"post-metadata fsync error must carry FileOp::Fsync"
);
assert!(
payload.context().contains("sync_all"),
"post-metadata fsync error context must mention sync_all; got {}",
payload.context()
);
let path_str = payload.path().display().to_string();
assert!(
path_str.contains(".tmp"),
"post-metadata fsync error path must reference the tempfile; got {path_str}"
);
}
other => {
panic!("save_wav must return Err(Error::FileIo) on injected fsync failure; got {other:?}")
}
}
let post_bytes =
fs::read(&path).expect("destination must still exist (rename was not attempted)");
assert_eq!(
post_bytes, original_bytes,
"destination bytes must be UNCHANGED on injected fsync failure; \
rename must NOT proceed when the post-metadata sync_all fails"
);
let leftovers: Vec<String> = fs::read_dir(&dir)
.expect("staging dir must be listable")
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|n| n.contains(".tmp"))
.collect();
assert!(
leftovers.is_empty(),
"staging dir must contain NO tempfile leftovers on injected fsync \
failure path (operator hygiene); found: {leftovers:?}"
);
}
#[test]
fn save_wav_then_load_audio_round_trip_is_bit_exact() {
let dir = audio_temp_dir("audio4_roundtrip");
let path = dir.join("rt.wav");
let samples: Vec<f32> = [-32_768_i32, -1, 0, 1, 16_384, -16_384, 32_767]
.iter()
.map(|&k| (k as f32) / 32_768.0)
.collect();
save_wav(&path, &samples, 16_000).expect("save_wav round-trip must succeed");
let (decoded, sr) = load_audio(&path).expect("load_audio must round-trip the saved WAV");
assert_eq!(sr, 16_000, "sample rate round-trip mismatch");
assert_eq!(
decoded.len(),
samples.len(),
"sample count round-trip mismatch"
);
for (i, (&orig, &got)) in samples.iter().zip(decoded.iter()).enumerate() {
assert_eq!(
got.to_bits(),
orig.to_bits(),
"round-trip drift at index {i}: original {orig}, decoded {got}"
);
}
}
#[test]
fn resample_linear_zero_from_rate_is_invariant_violation() {
let r = resample_linear(&[0.0, 1.0], 0, 16_000);
assert!(
matches!(r, Err(Error::InvariantViolation(_))),
"from_rate==0 must be InvariantViolation, got {r:?}"
);
}
#[test]
fn resample_linear_zero_to_rate_is_invariant_violation() {
let r = resample_linear(&[0.0, 1.0], 16_000, 0);
assert!(
matches!(r, Err(Error::InvariantViolation(_))),
"to_rate==0 must be InvariantViolation, got {r:?}"
);
}
#[test]
fn resample_linear_empty_input_returns_empty() {
let out = resample_linear(&[], 44_100, 16_000).expect("empty input must be Ok");
assert!(out.is_empty(), "empty input must yield empty output");
let out2 = resample_linear(&[], 16_000, 16_000).expect("empty input (equal rates) must be Ok");
assert!(out2.is_empty());
}
#[test]
fn resample_linear_equal_rates_is_verbatim_copy() {
let samples = vec![-1.0_f32, -0.5, 0.0, 0.25, 0.75, 1.0];
let out = resample_linear(&samples, 22_050, 22_050).expect("equal-rate resample must succeed");
assert_eq!(out.len(), samples.len(), "verbatim copy length mismatch");
for (i, (&a, &b)) in samples.iter().zip(out.iter()).enumerate() {
assert_eq!(
a.to_bits(),
b.to_bits(),
"verbatim copy must be bit-exact at index {i}: {a} != {b}"
);
}
}
#[test]
fn resample_linear_downsample_2_to_1_picks_even_indices() {
let samples = vec![0.0_f32, 0.1, 0.2, 0.3, 0.4, 0.5];
let out = resample_linear(&samples, 2, 1).expect("2:1 downsample must succeed");
assert_eq!(out.len(), 3, "2:1 downsample output length");
let expected = [samples[0], samples[2], samples[4]];
for (i, (&got, &exp)) in out.iter().zip(expected.iter()).enumerate() {
assert_eq!(
got.to_bits(),
exp.to_bits(),
"2:1 downsample mismatch at {i}: got {got}, expected {exp}"
);
}
}
#[test]
fn resample_linear_upsample_1_to_2_interpolates_halves() {
let samples = vec![0.0_f32, 1.0];
let out = resample_linear(&samples, 1, 2).expect("1:2 upsample must succeed");
let expected = [0.0_f32, 0.5, 1.0, 1.0];
assert_eq!(out.len(), expected.len(), "1:2 upsample output length");
for (i, (&got, &exp)) in out.iter().zip(expected.iter()).enumerate() {
assert_eq!(
got.to_bits(),
exp.to_bits(),
"1:2 upsample mismatch at {i}: got {got}, expected {exp}"
);
}
}
#[test]
fn resample_linear_out_len_zero_returns_empty() {
let out = resample_linear(&[0.5], 4, 1).expect("zero-output-length resample must be Ok");
assert!(
out.is_empty(),
"an output length that floors to 0 must yield an empty Vec, got len {}",
out.len()
);
}
#[test]
fn resample_linear_over_cap_output_is_rejected() {
let samples = [0.0_f32, 0.0];
let r = resample_linear(&samples, 1, u32::MAX);
match r {
Err(Error::CapExceeded(p)) => {
assert_eq!(
p.cap(),
MAX_RESAMPLED_SAMPLES as u64,
"cap field must be MAX_RESAMPLED_SAMPLES"
);
let expected_out_len = samples.len() as u64 * u64::from(u32::MAX); assert_eq!(
p.observed(),
expected_out_len,
"observed must be the (uncapped) computed output length"
);
assert!(
p.observed() > p.cap(),
"observed must exceed the cap by construction"
);
}
other => panic!("expected CapExceeded for over-cap output length, got {other:?}"),
}
}
#[test]
fn load_audio_with_max_seconds_rejects_non_positive_or_non_finite() {
let nonexistent = Path::new("/mlxrs_no_such_audio_file_for_max_seconds_test.wav");
for bad in [
f32::NAN,
f32::INFINITY,
f32::NEG_INFINITY,
0.0_f32,
-0.0_f32,
-1.5_f32,
] {
let r = load_audio_with_max_seconds(nonexistent, bad);
assert!(
matches!(r, Err(Error::OutOfRange(_))),
"max_seconds={bad} must be rejected up front with OutOfRange (before file IO), got {r:?}"
);
}
}
#[test]
fn load_audio_missing_file_is_fileio_open_error() {
let path = Path::new("/mlxrs_definitely_missing_audio_file.wav");
let r = load_audio(path);
match r {
Err(Error::FileIo(p)) => {
assert_eq!(
p.op(),
FileOp::Open,
"missing-file error must carry FileOp::Open"
);
}
other => panic!("expected FileIo(Open) for a missing file, got {other:?}"),
}
}
#[test]
fn load_audio_unrecognized_bytes_is_parse_error() {
let dir = audio_temp_dir("audio_probe_garbage");
let path = dir.join("garbage.wav");
fs::write(&path, vec![0x7Au8; 64]).expect("write junk file");
let r = load_audio(&path);
assert!(
matches!(r, Err(Error::Parse(_))),
"unrecognized container bytes must surface Error::Parse, got {r:?}"
);
}
fn build_pcm16_wav(channels: u16, sample_rate: u32, frames: u32) -> Vec<u8> {
const BITS: u16 = 16;
let block_align: u16 = channels * (BITS / 8);
let data_size: u32 = frames * u32::from(block_align);
let byte_rate: u32 = sample_rate * u32::from(block_align);
let file_size_minus_8: u32 = 36 + data_size;
let mut v = Vec::with_capacity(44 + data_size as usize);
v.extend_from_slice(b"RIFF");
v.extend_from_slice(&file_size_minus_8.to_le_bytes());
v.extend_from_slice(b"WAVE");
v.extend_from_slice(b"fmt ");
v.extend_from_slice(&16u32.to_le_bytes()); v.extend_from_slice(&1u16.to_le_bytes()); v.extend_from_slice(&channels.to_le_bytes());
v.extend_from_slice(&sample_rate.to_le_bytes());
v.extend_from_slice(&byte_rate.to_le_bytes());
v.extend_from_slice(&block_align.to_le_bytes());
v.extend_from_slice(&BITS.to_le_bytes());
v.extend_from_slice(b"data");
v.extend_from_slice(&data_size.to_le_bytes());
v.extend(std::iter::repeat_n(0u8, data_size as usize));
v
}
#[test]
fn load_audio_rejects_stereo_with_out_of_range() {
let dir = audio_temp_dir("audio_stereo_reject");
let path = dir.join("stereo.wav");
fs::write(&path, build_pcm16_wav(2, 16_000, 8)).expect("write stereo WAV");
let r = load_audio(&path);
assert!(
matches!(r, Err(Error::OutOfRange(_))),
"stereo (2-channel) input must be rejected with OutOfRange, got {r:?}"
);
}
#[test]
fn load_audio_accepts_mono_built_wav() {
let dir = audio_temp_dir("audio_mono_builder");
let path = dir.join("mono.wav");
let frames = 10u32;
fs::write(&path, build_pcm16_wav(1, 8_000, frames)).expect("write mono WAV");
let (decoded, sr) = load_audio(&path).expect("mono WAV must decode");
assert_eq!(sr, 8_000, "sample-rate mismatch");
assert_eq!(decoded.len(), frames as usize, "mono frame count mismatch");
assert!(
decoded.iter().all(|&s| s == 0.0),
"silence body must decode to all-zero samples"
);
}
#[test]
fn save_wav_zero_sample_rate_is_invariant_violation() {
let dir = audio_temp_dir("audio_zero_sr");
let path = dir.join("zero_sr.wav");
let r = save_wav(&path, &[0.0, 0.1], 0);
assert!(
matches!(r, Err(Error::InvariantViolation(_))),
"sample_rate==0 must be InvariantViolation, got {r:?}"
);
assert!(
!path.exists(),
"no file must be created on the zero-sample-rate validation failure"
);
}
#[test]
fn save_wav_oversized_sample_rate_is_out_of_range() {
let dir = audio_temp_dir("audio_big_sr");
let path = dir.join("big_sr.wav");
let r = save_wav(&path, &[0.0, 0.1], (u32::MAX / 2) + 1);
assert!(
matches!(r, Err(Error::OutOfRange(_))),
"sample_rate past the byte-rate ceiling must be OutOfRange, got {r:?}"
);
assert!(
!path.exists(),
"no file must be created on the oversized-sample-rate validation failure"
);
}
#[test]
fn save_wav_non_finite_sample_is_rejected_upfront() {
let dir = audio_temp_dir("audio_nan_sample");
let path = dir.join("nan.wav");
let samples = [0.0_f32, 0.5, f32::NAN, 0.25];
let r = save_wav(&path, &samples, 16_000);
assert!(
matches!(r, Err(Error::LayerKeyed(_))),
"a NaN sample must be rejected with a LayerKeyed(NonFiniteScalar) error, got {r:?}"
);
assert!(
!path.exists(),
"no file must be created when an input sample is non-finite"
);
}
#[test]
fn save_wav_rename_onto_existing_directory_is_fileio_rename_error() {
let dir = audio_temp_dir("audio_rename_onto_dir");
let dest_dir = dir.join("dest_is_a_dir.wav");
fs::create_dir_all(&dest_dir).expect("create destination directory");
fs::write(dest_dir.join("occupant.txt"), b"x").expect("populate destination dir");
let r = save_wav(&dest_dir, &[0.0, 0.1, -0.1], 16_000);
match r {
Err(Error::FileIo(p)) => {
assert_eq!(
p.op(),
FileOp::Rename,
"rename-onto-directory failure must carry FileOp::Rename, got {:?}",
p.op()
);
}
other => {
panic!("expected FileIo(Rename) when renaming onto a non-empty directory, got {other:?}")
}
}
let leftovers: Vec<String> = fs::read_dir(&dir)
.expect("staging dir listable")
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|n| n.contains(".tmp"))
.collect();
assert!(
leftovers.is_empty(),
"rename-failure path must clean up the tempfile; found leftovers: {leftovers:?}"
);
}
#[test]
fn open_excl_tempfile_no_file_name_is_out_of_range() {
let r = open_excl_tempfile(Path::new("/"), 16);
assert!(
matches!(r, Err(Error::OutOfRange(_))),
"a path with no file_name component must be OutOfRange, got {r:?}"
);
}
#[test]
fn open_excl_tempfile_zero_retries_exhausts_budget() {
let dir = audio_temp_dir("audio_tempfile_zero_retries");
let dest = dir.join("dest.wav");
let r = open_excl_tempfile(&dest, 0);
match r {
Err(Error::FileIo(p)) => {
assert_eq!(
p.op(),
FileOp::Create,
"exhausted retry budget must carry FileOp::Create"
);
}
other => panic!("expected FileIo(Create) on a zero-retry budget, got {other:?}"),
}
}
#[test]
fn open_excl_tempfile_success_creates_sibling_tmp() {
let dir = audio_temp_dir("audio_tempfile_success");
let dest = dir.join("dest.wav");
let (tmp_path, file) = open_excl_tempfile(&dest, 4).expect("tempfile open must succeed");
drop(file);
assert_eq!(
tmp_path.parent(),
Some(dir.as_path()),
"tempfile must be a sibling of the destination (same parent dir)"
);
assert!(
tmp_path.exists(),
"tempfile must exist on disk after a successful open"
);
let name = tmp_path.file_name().unwrap().to_string_lossy().into_owned();
assert!(
name.starts_with("dest.wav.") && name.ends_with(".tmp"),
"tempfile name must be `<file_name>.<pid>.<rand>.tmp`, got {name}"
);
let _ = fs::remove_file(&tmp_path);
}
#[test]
fn fsync_parent_dir_handles_all_parent_branches_without_panic() {
fsync_parent_dir(Path::new("/"));
fsync_parent_dir(Path::new("relative_name_no_slash.wav"));
let dir = audio_temp_dir("audio_fsync_parent_branches");
let path = dir.join("present.wav");
save_wav(&path, &[0.0, 0.0], 16_000).expect("seed file for the real-parent fsync branch");
fsync_parent_dir(&path);
}
fn build_wav(format_tag: u16, bits: u16, channels: u16, sample_rate: u32, body: &[u8]) -> Vec<u8> {
let block_align: u16 = channels * (bits / 8);
let data_size: u32 = u32::try_from(body.len()).expect("body fits u32");
let byte_rate: u32 = sample_rate * u32::from(block_align);
let file_size_minus_8: u32 = 36 + data_size;
let mut v = Vec::with_capacity(44 + body.len());
v.extend_from_slice(b"RIFF");
v.extend_from_slice(&file_size_minus_8.to_le_bytes());
v.extend_from_slice(b"WAVE");
v.extend_from_slice(b"fmt ");
v.extend_from_slice(&16u32.to_le_bytes()); v.extend_from_slice(&format_tag.to_le_bytes()); v.extend_from_slice(&channels.to_le_bytes());
v.extend_from_slice(&sample_rate.to_le_bytes());
v.extend_from_slice(&byte_rate.to_le_bytes());
v.extend_from_slice(&block_align.to_le_bytes());
v.extend_from_slice(&bits.to_le_bytes());
v.extend_from_slice(b"data");
v.extend_from_slice(&data_size.to_le_bytes());
v.extend_from_slice(body);
v
}
#[test]
fn load_audio_u8_pcm_decodes_through_u8_arm() {
let dir = audio_temp_dir("audio_u8_pcm");
let path = dir.join("u8.wav");
let body: [u8; 5] = [0x80, 0xC0, 0x40, 0xFF, 0x00];
fs::write(&path, build_wav(1, 8, 1, 8_000, &body)).expect("write u8 WAV");
let (decoded, sr) = load_audio(&path).expect("8-bit PCM WAV must decode through the U8 arm");
assert_eq!(sr, 8_000, "sample-rate mismatch");
let expected = [0.0_f32, 0.5, -0.5, 127.0 / 128.0, -1.0];
assert_eq!(decoded.len(), expected.len(), "U8 frame-count mismatch");
for (i, (&got, &exp)) in decoded.iter().zip(expected.iter()).enumerate() {
assert_eq!(
got.to_bits(),
exp.to_bits(),
"U8 decode mismatch at {i}: got {got}, expected {exp}"
);
}
}
#[test]
fn load_audio_s24_pcm_decodes_through_s24_arm() {
let dir = audio_temp_dir("audio_s24_pcm");
let path = dir.join("s24.wav");
let body: [u8; 9] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0];
fs::write(&path, build_wav(1, 24, 1, 16_000, &body)).expect("write s24 WAV");
let (decoded, sr) = load_audio(&path).expect("24-bit PCM WAV must decode through the S24 arm");
assert_eq!(sr, 16_000, "sample-rate mismatch");
let expected = [0.0_f32, 0.5, -0.5];
assert_eq!(decoded.len(), expected.len(), "S24 frame-count mismatch");
for (i, (&got, &exp)) in decoded.iter().zip(expected.iter()).enumerate() {
assert_eq!(
got.to_bits(),
exp.to_bits(),
"S24 decode mismatch at {i}: got {got}, expected {exp}"
);
}
}
#[test]
fn load_audio_s32_pcm_decodes_through_s32_arm() {
let dir = audio_temp_dir("audio_s32_pcm");
let path = dir.join("s32.wav");
let mut body: Vec<u8> = Vec::new();
body.extend_from_slice(&0i32.to_le_bytes());
body.extend_from_slice(&(1i32 << 30).to_le_bytes()); body.extend_from_slice(&(-(1i32 << 30)).to_le_bytes()); fs::write(&path, build_wav(1, 32, 1, 44_100, &body)).expect("write s32 WAV");
let (decoded, sr) = load_audio(&path).expect("32-bit PCM WAV must decode through the S32 arm");
assert_eq!(sr, 44_100, "sample-rate mismatch");
let expected = [0.0_f32, 0.5, -0.5];
assert_eq!(decoded.len(), expected.len(), "S32 frame-count mismatch");
for (i, (&got, &exp)) in decoded.iter().zip(expected.iter()).enumerate() {
assert_eq!(
got.to_bits(),
exp.to_bits(),
"S32 decode mismatch at {i}: got {got}, expected {exp}"
);
}
}
#[test]
fn load_audio_f32_ieee_decodes_through_f32_arm() {
let dir = audio_temp_dir("audio_f32_ieee");
let path = dir.join("f32.wav");
let samples = [0.0_f32, 0.5, -0.5, 0.25, -1.0];
let mut body: Vec<u8> = Vec::new();
for &s in &samples {
body.extend_from_slice(&s.to_le_bytes());
}
fs::write(&path, build_wav(3, 32, 1, 48_000, &body)).expect("write f32 IEEE WAV");
let (decoded, sr) = load_audio(&path).expect("32-bit IEEE-float WAV must decode through F32 arm");
assert_eq!(sr, 48_000, "sample-rate mismatch");
assert_eq!(decoded.len(), samples.len(), "F32 frame-count mismatch");
for (i, (&got, &exp)) in decoded.iter().zip(samples.iter()).enumerate() {
assert_eq!(
got.to_bits(),
exp.to_bits(),
"F32 pass-through mismatch at {i}: got {got}, expected {exp}"
);
}
}
#[test]
fn load_audio_f64_ieee_decodes_through_f64_arm() {
let dir = audio_temp_dir("audio_f64_ieee");
let path = dir.join("f64.wav");
let samples_f64 = [0.0_f64, 0.5, -0.5, 0.125, -0.75];
let mut body: Vec<u8> = Vec::new();
for &s in &samples_f64 {
body.extend_from_slice(&s.to_le_bytes());
}
fs::write(&path, build_wav(3, 64, 1, 22_050, &body)).expect("write f64 IEEE WAV");
let (decoded, sr) = load_audio(&path).expect("64-bit IEEE-float WAV must decode through F64 arm");
assert_eq!(sr, 22_050, "sample-rate mismatch");
let expected: Vec<f32> = samples_f64.iter().map(|&s| s as f32).collect();
assert_eq!(decoded.len(), expected.len(), "F64 frame-count mismatch");
for (i, (&got, &exp)) in decoded.iter().zip(expected.iter()).enumerate() {
assert_eq!(
got.to_bits(),
exp.to_bits(),
"F64 narrowing mismatch at {i}: got {got}, expected {exp}"
);
}
}
#[test]
fn load_audio_ima_adpcm_wav_fails_at_make_decoder_with_parse() {
let dir = audio_temp_dir("audio_ima_adpcm");
let path = dir.join("adpcm.wav");
const FMT_LEN: u32 = 20;
let channels: u16 = 1;
let sample_rate: u32 = 8_000;
let block_align: u16 = 256; let bits: u16 = 4;
let data: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
let data_size: u32 = data.len() as u32;
let byte_rate: u32 = sample_rate; let file_size_minus_8: u32 = 4 + (8 + FMT_LEN) + (8 + data_size);
let mut v: Vec<u8> = Vec::new();
v.extend_from_slice(b"RIFF");
v.extend_from_slice(&file_size_minus_8.to_le_bytes());
v.extend_from_slice(b"WAVE");
v.extend_from_slice(b"fmt ");
v.extend_from_slice(&FMT_LEN.to_le_bytes());
v.extend_from_slice(&0x0011u16.to_le_bytes()); v.extend_from_slice(&channels.to_le_bytes());
v.extend_from_slice(&sample_rate.to_le_bytes());
v.extend_from_slice(&byte_rate.to_le_bytes());
v.extend_from_slice(&block_align.to_le_bytes());
v.extend_from_slice(&bits.to_le_bytes()); v.extend_from_slice(&2u16.to_le_bytes()); v.extend_from_slice(&505u16.to_le_bytes()); v.extend_from_slice(b"data");
v.extend_from_slice(&data_size.to_le_bytes());
v.extend_from_slice(&data);
fs::write(&path, &v).expect("write IMA ADPCM WAV");
let r = load_audio(&path);
assert!(
matches!(r, Err(Error::Parse(_))),
"an undecodable IMA ADPCM WAV must surface Error::Parse \
(probe succeeds, make_audio_decoder fails), got {r:?}"
);
}
#[test]
fn load_audio_non_finite_float_sample_is_non_finite_scalar() {
let dir = audio_temp_dir("audio_f32_nan");
let path = dir.join("nan.wav");
let mut body: Vec<u8> = Vec::new();
body.extend_from_slice(&0.25_f32.to_le_bytes());
body.extend_from_slice(&f32::NAN.to_le_bytes());
fs::write(&path, build_wav(3, 32, 1, 16_000, &body)).expect("write NaN-bearing f32 WAV");
let r = load_audio(&path);
assert!(
matches!(r, Err(Error::NonFiniteScalar(_))),
"a NaN PCM sample must be rejected with NonFiniteScalar, got {r:?}"
);
}
#[test]
fn load_audio_infinite_float_sample_is_non_finite_scalar() {
let dir = audio_temp_dir("audio_f32_inf");
let path = dir.join("inf.wav");
let mut body: Vec<u8> = Vec::new();
body.extend_from_slice(&f32::INFINITY.to_le_bytes());
fs::write(&path, build_wav(3, 32, 1, 16_000, &body)).expect("write +inf f32 WAV");
let r = load_audio(&path);
assert!(
matches!(r, Err(Error::NonFiniteScalar(_))),
"a +inf PCM sample must be rejected with NonFiniteScalar, got {r:?}"
);
}
#[test]
fn cap_strategy_resolve_src_rate_max_seconds_arithmetic_and_saturation() {
assert_eq!(
CapStrategy::SrcRateMaxSeconds(2.0).resolve(16_000),
32_000,
"src_sr * max_seconds must be the in-cap product"
);
assert_eq!(
CapStrategy::SrcRateMaxSeconds(1_000_000.0).resolve(1_000_000),
MAX_DECODED_SAMPLES,
"an over-ceiling product must clamp to MAX_DECODED_SAMPLES"
);
assert_eq!(
CapStrategy::SrcRateMaxSeconds(f32::NAN).resolve(16_000),
MAX_DECODED_SAMPLES,
"a NaN product must fall back to MAX_DECODED_SAMPLES, not panic/wrap"
);
assert_eq!(
CapStrategy::SrcRateMaxSeconds(f32::INFINITY).resolve(16_000),
MAX_DECODED_SAMPLES,
"an +inf product must fall back to MAX_DECODED_SAMPLES"
);
assert_eq!(
CapStrategy::MaxSamples(usize::MAX).resolve(16_000),
MAX_DECODED_SAMPLES,
"MaxSamples(usize::MAX) must clamp to the global ceiling"
);
assert_eq!(
CapStrategy::MaxSamples(1234).resolve(16_000),
1234,
"an in-ceiling MaxSamples must pass through unchanged"
);
}
#[test]
fn open_excl_tempfile_nonexistent_parent_is_fileio_create_error() {
let missing_parent = Path::new("/mlxrs_no_such_parent_dir_for_tempfile_test_zzz/dest.wav");
let r = open_excl_tempfile(missing_parent, 4);
match r {
Err(Error::FileIo(p)) => {
assert_eq!(
p.op(),
FileOp::Create,
"a non-AlreadyExists create failure must carry FileOp::Create"
);
}
other => panic!("expected FileIo(Create) for a missing parent dir, got {other:?}"),
}
}