lesavka/testing/tests/client_output_audio_include_contract.rs

132 lines
4.4 KiB
Rust
Raw Normal View History

//! Include-based coverage for client audio output sink selection helpers.
//!
//! Scope: include `client/src/output/audio.rs` and exercise sink discovery
//! branches with controlled `pactl` fixtures.
//! Targets: `client/src/output/audio.rs`.
//! Why: keep sink-resolution behavior deterministic without requiring live
//! desktop audio devices in CI.
#[allow(warnings)]
mod audio_include_contract {
include!(env!("LESAVKA_CLIENT_OUTPUT_AUDIO_SRC"));
use serial_test::serial;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use temp_env::with_var;
use tempfile::tempdir;
fn write_executable(dir: &Path, name: &str, body: &str) {
let path = dir.join(name);
fs::write(&path, body).expect("write script");
let mut perms = fs::metadata(&path).expect("metadata").permissions();
perms.set_mode(0o755);
fs::set_permissions(path, perms).expect("chmod");
}
fn with_fake_pactl(script_body: &str, f: impl FnOnce()) {
let dir = tempdir().expect("tempdir");
write_executable(dir.path(), "pactl", script_body);
let prior = std::env::var("PATH").unwrap_or_default();
let merged = if prior.is_empty() {
dir.path().display().to_string()
} else {
format!("{}:{prior}", dir.path().display())
};
with_var("PATH", Some(merged), f);
}
#[test]
#[serial]
fn pick_sink_element_prefers_operator_override() {
with_var("LESAVKA_AUDIO_SINK", Some("fakesink sync=false"), || {
let sink = pick_sink_element().expect("override sink");
assert_eq!(sink, "fakesink sync=false");
});
}
#[test]
#[serial]
fn pick_sink_element_uses_default_sink_from_pactl_info() {
let script = r#"#!/usr/bin/env sh
if [ "$1" = "info" ]; then
echo "Server String: /run/user/1000/pulse/native"
echo "Default Sink: alsa_output.usb-DAC_1234-00.analog-stereo"
exit 0
fi
exit 0
"#;
with_fake_pactl(script, || {
with_var("LESAVKA_AUDIO_SINK", None::<&str>, || {
let sinks = list_pw_sinks();
assert_eq!(
sinks,
vec![(
"alsa_output.usb-DAC_1234-00.analog-stereo".to_string(),
"UNKNOWN".to_string()
)]
);
let sink = pick_sink_element().expect("pick sink");
assert_eq!(
sink,
"pulsesink device=alsa_output.usb-DAC_1234-00.analog-stereo"
);
});
});
}
#[test]
#[serial]
fn pick_sink_element_falls_back_to_autoaudiosink_without_pactl_default() {
let script = r#"#!/usr/bin/env sh
if [ "$1" = "info" ]; then
echo "Server String: /run/user/1000/pulse/native"
echo "Default Source: alsa_input.usb-Mic_1234-00.analog-stereo"
exit 0
fi
exit 0
"#;
with_fake_pactl(script, || {
with_var("LESAVKA_AUDIO_SINK", None::<&str>, || {
assert!(list_pw_sinks().is_empty(), "no default sink should be parsed");
let sink = pick_sink_element().expect("fallback sink");
assert_eq!(sink, "autoaudiosink");
});
});
}
#[test]
#[serial]
fn audio_out_new_and_push_are_stable_with_sink_override() {
with_var("LESAVKA_AUDIO_SINK", Some("fakesink sync=false"), || {
with_var("LESAVKA_TAP_AUDIO", Some("1"), || {
match AudioOut::new() {
Ok(out) => {
out.push(AudioPacket {
id: 0,
pts: 1_234,
data: vec![0xFF, 0xF1, 0x50, 0x80, 0x00, 0x1F, 0xFC],
});
drop(out);
}
Err(err) => {
assert!(!err.to_string().trim().is_empty());
}
}
});
});
}
#[test]
#[serial]
fn audio_out_new_returns_error_for_invalid_sink_override() {
with_var("LESAVKA_AUDIO_SINK", Some("definitely-not-a-real-gst-sink"), || {
with_var("LESAVKA_TAP_AUDIO", None::<&str>, || {
let result = AudioOut::new();
assert!(result.is_err(), "invalid sink override must fail pipeline parsing");
});
});
}
}