//! Include-based coverage for microphone source-selection helpers. //! //! Scope: include `client/src/input/microphone.rs` and exercise Pulse source //! parsing + fallback behavior without requiring a live audio stack. //! Targets: `client/src/input/microphone.rs`. //! Why: source selection regressions should be caught with deterministic tests. #[allow(warnings)] mod microphone_include_contract { include!(env!("LESAVKA_CLIENT_MICROPHONE_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 pulse_source_by_substr_matches_expected_device_name() { let script = r#"#!/usr/bin/env sh if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then echo "0 alsa_input.pci.monitor module-alsa-card.c s16le 2ch 48000Hz RUNNING" echo "1 alsa_input.usb-Mic_1234-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz IDLE" exit 0 fi exit 0 "#; with_fake_pactl(script, || { let src = MicrophoneCapture::pulse_source_by_substr("Mic_1234") .expect("matching source"); assert_eq!(src, "alsa_input.usb-Mic_1234-00.analog-stereo"); }); } #[test] #[serial] fn default_source_arg_prefers_non_monitor_source() { let script = r#"#!/usr/bin/env sh if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then echo "0 alsa_input.pci.monitor module-alsa-card.c s16le 2ch 48000Hz RUNNING" echo "1 alsa_input.usb-DeskMic_5678-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz IDLE" exit 0 fi exit 0 "#; with_fake_pactl(script, || { let arg = MicrophoneCapture::default_source_arg(); assert!( arg.contains("device=alsa_input.usb-DeskMic_5678-00.analog-stereo"), "expected escaped non-monitor source argument" ); }); } #[test] #[serial] fn default_source_arg_returns_empty_when_pactl_is_unavailable() { with_var("PATH", Some("/definitely/missing/path"), || { let arg = MicrophoneCapture::default_source_arg(); assert!(arg.is_empty()); }); } #[test] fn pull_returns_none_for_empty_appsink() { gst::init().ok(); let sink: gst_app::AppSink = gst::ElementFactory::make("appsink") .build() .expect("appsink") .downcast::() .expect("appsink cast"); let cap = MicrophoneCapture { pipeline: gst::Pipeline::new(), sink, }; assert!(cap.pull().is_none(), "empty appsink should produce no packet"); } #[test] fn pull_returns_packet_when_appsink_has_buffered_sample() { gst::init().ok(); let pipeline = gst::Pipeline::new(); let src = gst::ElementFactory::make("appsrc") .build() .expect("appsrc") .downcast::() .expect("appsrc cast"); let sink = gst::ElementFactory::make("appsink") .property("emit-signals", false) .property("sync", false) .build() .expect("appsink") .downcast::() .expect("appsink cast"); pipeline .add_many([ src.upcast_ref::(), sink.upcast_ref::(), ]) .expect("add appsrc/appsink"); src.link(&sink).expect("link appsrc->appsink"); pipeline.set_state(gst::State::Playing).ok(); let mut buf = gst::Buffer::from_slice(vec![1_u8, 2, 3, 4]); buf.get_mut() .expect("buffer mut") .set_pts(Some(gst::ClockTime::from_useconds(321))); src.push_buffer(buf).expect("push sample"); let cap = MicrophoneCapture { pipeline, sink }; let pkt = cap.pull().expect("audio packet"); assert_eq!(pkt.id, 0); assert_eq!(pkt.pts, 321); assert_eq!(pkt.data, vec![1, 2, 3, 4]); } #[test] #[serial] fn new_uses_requested_source_fragment_when_available() { let script = r#"#!/usr/bin/env sh if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then echo "1 alsa_input.usb-LavMic_abc-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz RUNNING" exit 0 fi exit 0 "#; with_fake_pactl(script, || { with_var("LESAVKA_MIC_SOURCE", Some("LavMic_abc"), || { let result = MicrophoneCapture::new(); if let Err(err) = result { assert!(!err.to_string().trim().is_empty()); } }); }); } #[test] #[serial] fn new_falls_back_to_default_source_when_requested_fragment_is_missing() { let script = r#"#!/usr/bin/env sh if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then echo "0 alsa_input.pci.monitor module-alsa-card.c s16le 2ch 48000Hz RUNNING" echo "1 alsa_input.usb-DeskMic_777-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz IDLE" exit 0 fi exit 0 "#; with_fake_pactl(script, || { with_var("LESAVKA_MIC_SOURCE", Some("missing-fragment"), || { let result = MicrophoneCapture::new(); if let Err(err) = result { assert!(!err.to_string().trim().is_empty()); } }); }); } }