#![cfg(test)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::print_stderr,
reason = "unit tests"
)]
use std::collections::VecDeque;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use crate::CaptureError;
use mediaway_common::{AudioFrame, Bytes, CodecKind, Rational, SampleFormat, StreamInfo};
use super::{
SharedQueue, WasapiSession, WindowsWasapiCapture, combine_endpoint_and_interface_names,
copy_pcm_buffer,
};
fn fake_session() -> WindowsWasapiCapture {
let queue = Arc::new(SharedQueue {
frames: Mutex::new(VecDeque::new()),
stop: AtomicBool::new(false),
device_lost: AtomicBool::new(false),
});
let stream_info = StreamInfo::Audio {
id: 0,
codec: CodecKind::RawAudio,
time_base: Rational::new(1, 48_000),
extra_data: Bytes::new(),
sample_rate: 48_000,
channels: 2,
};
WindowsWasapiCapture {
inner: Some(WasapiSession {
stream_info,
queue,
worker: None,
}),
}
}
fn pcm_frame() -> AudioFrame {
AudioFrame {
pts: 0,
duration: 1,
sample_rate: 48_000,
channels: 2,
format: SampleFormat::F32,
data: Bytes::from(vec![0u8; 8]),
}
}
#[test]
fn poll_frame_on_empty_queue_without_device_lost_is_ok_none() {
let mut capture = fake_session();
assert_eq!(capture.poll_frame(), Ok(None));
}
#[test]
fn poll_frame_reports_device_lost_after_queue_drains() {
let mut capture = fake_session();
if let Some(session) = capture.inner.as_ref() {
session
.queue
.frames
.lock()
.expect("lock")
.push_back(pcm_frame());
session
.queue
.device_lost
.store(true, std::sync::atomic::Ordering::SeqCst);
}
assert!(matches!(capture.poll_frame(), Ok(Some(_))));
assert_eq!(capture.poll_frame(), Err(CaptureError::DeviceLost));
assert_eq!(capture.poll_frame(), Err(CaptureError::DeviceLost));
}
#[test]
fn copy_pcm_buffer_reproduces_source_bytes_exactly() {
let src: Vec<u8> = (0u32..2048)
.map(|i| u8::try_from(i % 251).expect("i % 251 fits in u8"))
.collect();
let copied = unsafe { copy_pcm_buffer(src.as_ptr(), src.len()) };
assert_eq!(copied.len(), src.len());
assert_eq!(copied, src);
}
#[test]
fn copy_pcm_buffer_handles_empty_packet() {
let src: Vec<u8> = Vec::new();
let copied = unsafe { copy_pcm_buffer(src.as_ptr(), 0) };
assert!(copied.is_empty());
}
#[test]
fn copy_pcm_buffer_matches_typical_period_size() {
let num_frames = 480usize;
let channels = 2usize;
let bytes = num_frames * channels * 4;
let src: Vec<u8> = (0..bytes)
.map(|i| u8::try_from(i % 256).unwrap_or(0))
.collect();
let copied = unsafe { copy_pcm_buffer(src.as_ptr(), src.len()) };
assert_eq!(copied.len(), bytes);
assert_eq!(copied, src);
}
#[test]
fn combine_names_skips_append_when_interface_name_already_embedded() {
let combined = combine_endpoint_and_interface_names(
Some("스테레오 믹스 (Realtek(R) Audio)".to_owned()),
Some("Realtek(R) Audio".to_owned()),
);
assert_eq!(
combined.as_deref(),
Some("스테레오 믹스 (Realtek(R) Audio)")
);
}
#[test]
fn combine_names_appends_interface_name_when_not_already_present() {
let combined = combine_endpoint_and_interface_names(
Some("마이크".to_owned()),
Some("USB Audio Device".to_owned()),
);
assert_eq!(combined.as_deref(), Some("마이크 (USB Audio Device)"));
}
#[test]
fn combine_names_case_insensitive_match_still_skips_append() {
let combined = combine_endpoint_and_interface_names(
Some("Microphone (REALTEK AUDIO)".to_owned()),
Some("realtek audio".to_owned()),
);
assert_eq!(combined.as_deref(), Some("Microphone (REALTEK AUDIO)"));
}
#[test]
fn combine_names_falls_back_to_whichever_property_is_present() {
assert_eq!(
combine_endpoint_and_interface_names(Some("Endpoint Only".to_owned()), None).as_deref(),
Some("Endpoint Only")
);
assert_eq!(
combine_endpoint_and_interface_names(None, Some("Interface Only".to_owned())).as_deref(),
Some("Interface Only")
);
assert_eq!(combine_endpoint_and_interface_names(None, None), None);
}