sound/test: add testing module for sound

add testing modules

Signed-off-by: Dorinda Bassey <dbassey@redhat.com>
This commit is contained in:
Dorinda Bassey 2023-10-20 14:13:59 +02:00 committed by Viresh Kumar
parent d04d6b42fc
commit cae9fd0f79
9 changed files with 708 additions and 110 deletions

129
staging/Cargo.lock generated
View File

@ -116,7 +116,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.38",
"syn",
]
[[package]]
@ -207,7 +207,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn",
]
[[package]]
@ -237,19 +237,6 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "env_logger"
version = "0.10.0"
@ -279,6 +266,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "futures"
version = "0.3.28"
@ -335,7 +328,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn",
]
[[package]]
@ -485,16 +478,6 @@ version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
@ -562,29 +545,6 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@ -664,9 +624,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.1"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
@ -676,9 +636,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
@ -722,7 +682,7 @@ dependencies = [
"regex",
"relative-path",
"rustc_version",
"syn 2.0.38",
"syn",
"unicode-ident",
]
@ -754,12 +714,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.20"
@ -783,7 +737,7 @@ checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn",
]
[[package]]
@ -795,31 +749,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serial_test"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "538c30747ae860d6fb88330addbbd3e0ddbe46d662d032855596d8a8ca260611"
dependencies = [
"dashmap",
"futures",
"lazy_static",
"log",
"parking_lot",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "shlex"
version = "1.2.0"
@ -847,17 +776,6 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.38"
@ -888,6 +806,19 @@ version = "0.12.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a"
[[package]]
name = "tempfile"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys",
]
[[package]]
name = "termcolor"
version = "1.3.0"
@ -914,7 +845,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn",
]
[[package]]
@ -997,7 +928,7 @@ dependencies = [
"log",
"pipewire",
"rstest",
"serial_test",
"tempfile",
"thiserror",
"vhost",
"vhost-user-backend",

View File

@ -1,5 +1,5 @@
{
"coverage_score": 7.94,
"coverage_score": 50.43,
"exclude_path": "",
"crate_features": ""
}

View File

@ -30,6 +30,7 @@ vm-memory = "0.12"
vmm-sys-util = "0.11"
[dev-dependencies]
serial_test = "1.0"
rstest = "0.18.2"
tempfile = "3.5"
virtio-queue = { version = "0.9", features = ["test-utils"] }
vm-memory = { version = "0.12", features = ["backend-mmap", "backend-atomic"] }

View File

@ -27,3 +27,31 @@ impl AudioBackend for NullBackend {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_null_backend_write() {
let streams = Arc::new(RwLock::new(vec![Stream::default()]));
let null_backend = NullBackend::new(streams.clone());
assert!(null_backend.write(0).is_ok());
let streams = streams.read().unwrap();
assert_eq!(streams[0].buffers.len(), 0);
}
#[test]
fn test_null_backend_read() {
let streams = Arc::new(RwLock::new(vec![Stream::default()]));
let null_backend = NullBackend::new(streams.clone());
assert!(null_backend.read(0).is_ok());
// buffer lengths should remain unchanged
let streams = streams.read().unwrap();
assert_eq!(streams[0].buffers.len(), 0);
}
}

View File

@ -463,3 +463,149 @@ impl AudioBackend for PwBackend {
Ok(())
}
}
#[cfg(test)]
mod tests {
use vhost_user_backend::{VringRwLock, VringT};
use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE};
use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue, QueueOwnedT};
use vm_memory::{
Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
};
use super::*;
use crate::{ControlMessageKind, SoundDescriptorChain};
// Prepares a single chain of descriptors for request queue
fn prepare_desc_chain<R: ByteValued>(
start_addr: GuestAddress,
hdr: R,
response_len: u32,
) -> SoundDescriptorChain {
let mem = &GuestMemoryMmap::<()>::from_ranges(&[(start_addr, 0x1000)]).unwrap();
let vq = MockSplitQueue::new(mem, 16);
let mut next_addr = vq.desc_table().total_size() + 0x100;
let mut index = 0;
let desc_out = Descriptor::new(
next_addr,
size_of::<R>() as u32,
VRING_DESC_F_NEXT as u16,
index + 1,
);
mem.write_obj::<R>(hdr, desc_out.addr()).unwrap();
vq.desc_table().store(index, desc_out).unwrap();
next_addr += desc_out.len() as u64;
index += 1;
// In response descriptor
let desc_in = Descriptor::new(next_addr, response_len, VRING_DESC_F_WRITE as u16, 0);
vq.desc_table().store(index, desc_in).unwrap();
// Put the descriptor index 0 in the first available ring position.
mem.write_obj(0u16, vq.avail_addr().unchecked_add(4))
.unwrap();
// Set `avail_idx` to 1.
mem.write_obj(1u16, vq.avail_addr().unchecked_add(2))
.unwrap();
// Create descriptor chain from pre-filled memory
vq.create_queue::<Queue>()
.unwrap()
.iter(GuestMemoryAtomic::new(mem.clone()).memory())
.unwrap()
.next()
.unwrap()
}
fn ctrlmsg() -> ControlMessage {
let hdr = VirtioSndPcmSetParams::default();
let resp_len: u32 = 1;
let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
let memr = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(),
);
let vq = MockSplitQueue::new(mem, 16);
let next_addr = vq.desc_table().total_size() + 0x100;
let index = 0;
let vring = VringRwLock::new(memr, 0x1000).unwrap();
ControlMessage {
kind: ControlMessageKind::PcmInfo,
code: 0,
desc_chain: prepare_desc_chain::<VirtioSndPcmSetParams>(GuestAddress(0), hdr, resp_len),
descriptor: Descriptor::new(next_addr, 0x200, VRING_DESC_F_NEXT as u16, index + 1),
vring,
}
}
#[test]
fn test_pipewire_backend_success() {
let streams = Arc::new(RwLock::new(vec![Stream::default()]));
let stream_params = streams.clone();
let pw_backend = PwBackend::new(stream_params);
assert_eq!(pw_backend.stream_hash.read().unwrap().len(), 0);
assert_eq!(pw_backend.stream_listener.read().unwrap().len(), 0);
assert!(pw_backend.prepare(0).is_ok());
assert!(pw_backend.start(0).is_ok());
assert!(pw_backend.stop(0).is_ok());
let msg = ctrlmsg();
assert!(pw_backend.set_parameters(0, msg).is_ok());
let release_msg = ctrlmsg();
assert!(pw_backend.release(0, release_msg).is_ok());
assert!(pw_backend.write(0).is_ok());
let streams = streams.read().unwrap();
assert_eq!(streams[0].buffers.len(), 0);
assert!(pw_backend.read(0).is_ok());
}
#[test]
#[should_panic(expected = "Stream does not exist")]
fn test_pipewire_backend_panics() {
let stream_params = Arc::new(RwLock::new(vec![]));
let pw_backend = PwBackend::new(stream_params);
let msg = ctrlmsg();
let _ = pw_backend.set_parameters(0, msg);
let _ = pw_backend.prepare(0);
}
#[test]
#[should_panic]
fn test_pipewire_prepare_panics() {
let stream_params = Arc::new(RwLock::new(vec![]));
let pw_backend = PwBackend::new(stream_params);
let _ = pw_backend.prepare(0);
}
#[test]
#[should_panic]
fn test_pipewire_start_panics() {
let stream_params = Arc::new(RwLock::new(vec![]));
let pw_backend = PwBackend::new(stream_params);
let _ = pw_backend.start(0);
}
#[test]
#[should_panic]
fn test_pipewire_stop_panics() {
let stream_params = Arc::new(RwLock::new(vec![]));
let pw_backend = PwBackend::new(stream_params);
let _ = pw_backend.stop(0);
}
#[test]
#[should_panic]
fn test_pipewire_release_panics() {
let stream_params = Arc::new(RwLock::new(vec![]));
let pw_backend = PwBackend::new(stream_params);
let msg = ctrlmsg();
let _ = pw_backend.release(0, msg);
}
}

View File

@ -32,7 +32,7 @@ use crate::{
ControlMessageKind, Error, IOMessage, Result, SoundConfig,
};
struct VhostUserSoundThread {
pub struct VhostUserSoundThread {
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
event_idx: bool,
chmaps: Arc<RwLock<Vec<VirtioSoundChmapInfo>>>,
@ -584,9 +584,9 @@ impl VhostUserSoundThread {
}
pub struct VhostUserSoundBackend {
threads: Vec<RwLock<VhostUserSoundThread>>,
pub threads: Vec<RwLock<VhostUserSoundThread>>,
virtio_cfg: VirtioSoundConfig,
exit_event: EventFd,
pub exit_event: EventFd,
audio_backend: RwLock<Box<dyn AudioBackend + Send + Sync>>,
}
@ -820,3 +820,214 @@ impl Drop for ControlMessage {
}
}
}
#[cfg(test)]
mod tests {
use tempfile::tempdir;
use vm_memory::GuestAddress;
use super::*;
use crate::BackendType;
const SOCKET_PATH: &str = "vsound.socket";
#[test]
fn test_sound_thread_success() {
let config = SoundConfig::new(SOCKET_PATH.to_string(), false, BackendType::Null);
let chmaps = Arc::new(RwLock::new(vec![]));
let jacks = Arc::new(RwLock::new(vec![]));
let queue_indexes = vec![1, 2, 3];
let streams = vec![Stream::default()];
let streams_no = streams.len();
let streams = Arc::new(RwLock::new(streams));
let thread =
VhostUserSoundThread::new(chmaps, jacks, queue_indexes, streams.clone(), streams_no);
assert!(thread.is_ok());
let mut t = thread.unwrap();
// Mock memory
let mem = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(),
);
t.mem = Some(mem.clone());
// Mock Vring for queues
let vrings = [
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
];
let audio_backend =
RwLock::new(alloc_audio_backend(config.audio_backend, streams.clone()).unwrap());
assert!(t
.handle_event(CONTROL_QUEUE_IDX, &vrings, &audio_backend)
.is_ok());
let vring = VringRwLock::new(mem, 0x1000).unwrap();
vring.set_queue_info(0x100, 0x200, 0x300).unwrap();
vring.set_queue_ready(true);
assert!(t.process_control(&vring, &audio_backend).is_ok());
assert!(t.process_tx(&vring, &audio_backend).is_ok());
assert!(t.process_rx(&vring, &audio_backend).is_ok());
}
#[test]
fn test_sound_thread_failure() {
let config = SoundConfig::new(SOCKET_PATH.to_string(), false, BackendType::Null);
let chmaps = Arc::new(RwLock::new(vec![]));
let jacks = Arc::new(RwLock::new(vec![]));
let queue_indexes = vec![1, 2, 3];
let streams = Arc::new(RwLock::new(vec![]));
let streams_no = 0;
let thread =
VhostUserSoundThread::new(chmaps, jacks, queue_indexes, streams.clone(), streams_no);
let t = thread.unwrap();
let mem = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(),
);
let audio_backend =
RwLock::new(alloc_audio_backend(config.audio_backend, streams.clone()).unwrap());
let vring = VringRwLock::new(mem, 0x1000).unwrap();
vring.set_queue_info(0x100, 0x200, 0x300).unwrap();
vring.set_queue_ready(true);
assert!(t.process_control(&vring, &audio_backend).is_err());
assert!(t.process_tx(&vring, &audio_backend).is_err());
}
#[test]
fn test_sound_backend() {
let test_dir = tempdir().expect("Could not create a temp test directory.");
let socket_path = test_dir.path().join(SOCKET_PATH).display().to_string();
let config = SoundConfig::new(socket_path, false, BackendType::Null);
let backend = VhostUserSoundBackend::new(config).expect("Could not create backend.");
assert_eq!(backend.num_queues(), NUM_QUEUES as usize);
assert_eq!(backend.max_queue_size(), 64);
assert_ne!(backend.features(), 0);
assert!(!backend.protocol_features().is_empty());
backend.set_event_idx(false);
// Mock memory
let mem = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(),
);
// Mock Vring for queues
let vrings = [
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
];
vrings[CONTROL_QUEUE_IDX as usize]
.set_queue_info(0x100, 0x200, 0x300)
.unwrap();
vrings[CONTROL_QUEUE_IDX as usize].set_queue_ready(true);
vrings[EVENT_QUEUE_IDX as usize]
.set_queue_info(0x100, 0x200, 0x300)
.unwrap();
vrings[EVENT_QUEUE_IDX as usize].set_queue_ready(true);
vrings[TX_QUEUE_IDX as usize]
.set_queue_info(0x1100, 0x1200, 0x1300)
.unwrap();
vrings[TX_QUEUE_IDX as usize].set_queue_ready(true);
vrings[RX_QUEUE_IDX as usize]
.set_queue_info(0x100, 0x200, 0x300)
.unwrap();
vrings[RX_QUEUE_IDX as usize].set_queue_ready(true);
assert!(backend.update_memory(mem).is_ok());
let queues_per_thread = backend.queues_per_thread();
assert_eq!(queues_per_thread.len(), 1);
assert_eq!(queues_per_thread[0], 0xf);
let config = backend.get_config(0, 8);
assert_eq!(config.len(), 8);
let exit = backend.exit_event(0);
assert!(exit.is_some());
exit.unwrap().write(1).unwrap();
let ret = backend.handle_event(CONTROL_QUEUE_IDX, EventSet::IN, &vrings, 0);
assert!(ret.is_ok());
assert!(!ret.unwrap());
let ret = backend.handle_event(EVENT_QUEUE_IDX, EventSet::IN, &vrings, 0);
assert!(ret.is_ok());
assert!(!ret.unwrap());
let ret = backend.handle_event(TX_QUEUE_IDX, EventSet::IN, &vrings, 0);
assert!(ret.is_ok());
assert!(!ret.unwrap());
let ret = backend.handle_event(RX_QUEUE_IDX, EventSet::IN, &vrings, 0);
assert!(ret.is_ok());
assert!(!ret.unwrap());
test_dir.close().unwrap();
}
#[test]
fn test_sound_backend_failures() {
let test_dir = tempdir().expect("Could not create a temp test directory.");
let socket_path = test_dir
.path()
.join("sound_failures.socket")
.display()
.to_string();
let config = SoundConfig::new(socket_path, false, BackendType::Null);
let backend = VhostUserSoundBackend::new(config);
let backend = backend.unwrap();
// Mock memory
let mem = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(),
);
// Mock Vring for queues
let vrings = [
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
];
// Update memory
backend.update_memory(mem).unwrap();
let config = backend.get_config(2, 8);
assert_eq!(config.len(), 8);
let ret = backend.handle_event(CONTROL_QUEUE_IDX, EventSet::IN, &vrings, 0);
assert_eq!(
ret.unwrap_err().to_string(),
Error::DescriptorNotFound.to_string()
);
// Currently handles a single device event, anything higher than 0 will generate
// an error.
let ret = backend.handle_event(TX_QUEUE_IDX, EventSet::IN, &vrings, 0);
assert_eq!(
ret.unwrap_err().to_string(),
Error::DescriptorNotFound.to_string()
);
// Currently handles EventSet::IN only, otherwise an error is generated.
let ret = backend.handle_event(RX_QUEUE_IDX, EventSet::OUT, &vrings, 0);
assert_eq!(
ret.unwrap_err().to_string(),
Error::HandleEventNotEpollIn.to_string()
);
test_dir.close().unwrap();
}
}

View File

@ -107,7 +107,7 @@ pub enum BackendType {
Alsa,
}
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub struct InvalidControlMessage(u32);
impl std::fmt::Display for InvalidControlMessage {
@ -317,3 +317,66 @@ pub fn start_backend_server(config: SoundConfig) {
// No matter the result, we need to shut down the worker thread.
backend.send_exit_event();
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
use super::*;
use crate::ControlMessageKind;
#[test]
fn test_sound_server() {
const SOCKET_PATH: &str = "vsound.socket";
let config = SoundConfig::new(SOCKET_PATH.to_string(), false, BackendType::Null);
let backend = Arc::new(VhostUserSoundBackend::new(config.clone()).unwrap());
let daemon = VhostUserDaemon::new(
String::from("vhost-device-sound"),
backend.clone(),
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
)
.unwrap();
let vring_workers = daemon.get_epoll_handlers();
// VhostUserSoundBackend support a single thread that handles the TX and RX
// queues
assert_eq!(backend.threads.len(), 1);
assert_eq!(vring_workers.len(), backend.threads.len());
}
#[test]
fn test_control_message_kind_try_from() {
assert_eq!(
ControlMessageKind::try_from(<u32 as Into<Le32>>::into(VIRTIO_SND_R_JACK_INFO)),
Ok(ControlMessageKind::JackInfo)
);
assert_eq!(
ControlMessageKind::try_from(<u32 as Into<Le32>>::into(VIRTIO_SND_R_PCM_INFO)),
Ok(ControlMessageKind::PcmInfo)
);
assert_eq!(
ControlMessageKind::try_from(<u32 as Into<Le32>>::into(VIRTIO_SND_R_CHMAP_INFO)),
Ok(ControlMessageKind::ChmapInfo)
);
assert_eq!(
ControlMessageKind::try_from(<u32 as Into<Le32>>::into(VIRTIO_SND_R_PCM_SET_PARAMS)),
Ok(ControlMessageKind::PcmSetParams)
);
}
#[test]
fn test_control_message_kind_try_from_invalid() {
// Test an invalid value that should result in an InvalidControlMessage error
let invalid_value: u32 = 0x1101;
assert_eq!(
ControlMessageKind::try_from(<u32 as Into<Le32>>::into(invalid_value)),
Err(InvalidControlMessage(invalid_value))
);
}
}

View File

@ -41,7 +41,6 @@ fn main() {
#[cfg(test)]
mod tests {
use rstest::*;
use serial_test::serial;
use super::*;
@ -55,7 +54,6 @@ mod tests {
}
#[test]
#[serial]
fn test_sound_config_setup() {
let args = SoundArgs::from_args("/tmp/vhost-sound.socket");
@ -67,7 +65,6 @@ mod tests {
}
#[rstest]
#[serial]
#[case::null_backend("null", BackendType::Null)]
#[cfg_attr(
feature = "pw-backend",

View File

@ -9,7 +9,7 @@ use vm_memory::{Address, Bytes, Le32, Le64};
use crate::{virtio_sound::*, IOMessage, SUPPORTED_FORMATS, SUPPORTED_RATES};
/// Stream errors.
#[derive(Debug, ThisError)]
#[derive(Debug, ThisError, PartialEq)]
pub enum Error {
#[error("Guest driver request an invalid stream state transition from {0} to {1}.")]
InvalidStateTransition(PCMState, PCMState),
@ -91,7 +91,7 @@ type Result<T> = std::result::Result<T, Error>;
/// | | | | |
/// | |<----------| | |
/// ```
#[derive(Debug, Default, Copy, Clone)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub enum PCMState {
#[default]
#[doc(alias = "VIRTIO_SND_R_PCM_SET_PARAMS")]
@ -282,3 +282,224 @@ impl Drop for Buffer {
log::trace!("dropping buffer {:?}", self);
}
}
#[cfg(test)]
mod tests {
use vhost_user_backend::{VringRwLock, VringT};
use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE};
use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue, QueueOwnedT};
use vm_memory::{
Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
};
use super::*;
use crate::SoundDescriptorChain;
// Prepares a single chain of descriptors for request queue
fn prepare_desc_chain<R: ByteValued>(
start_addr: GuestAddress,
hdr: R,
response_len: u32,
) -> SoundDescriptorChain {
let mem = &GuestMemoryMmap::<()>::from_ranges(&[(start_addr, 0x1000)]).unwrap();
let vq = MockSplitQueue::new(mem, 16);
let mut next_addr = vq.desc_table().total_size() + 0x100;
let mut index = 0;
let desc_out = Descriptor::new(
next_addr,
std::mem::size_of::<R>() as u32,
VRING_DESC_F_NEXT as u16,
index + 1,
);
mem.write_obj::<R>(hdr, desc_out.addr()).unwrap();
vq.desc_table().store(index, desc_out).unwrap();
next_addr += desc_out.len() as u64;
index += 1;
// In response descriptor
let desc_in = Descriptor::new(next_addr, response_len, VRING_DESC_F_WRITE as u16, 0);
vq.desc_table().store(index, desc_in).unwrap();
// Put the descriptor index 0 in the first available ring position.
mem.write_obj(0u16, vq.avail_addr().unchecked_add(4))
.unwrap();
// Set `avail_idx` to 1.
mem.write_obj(1u16, vq.avail_addr().unchecked_add(2))
.unwrap();
// Create descriptor chain from pre-filled memory
vq.create_queue::<Queue>()
.unwrap()
.iter(GuestMemoryAtomic::new(mem.clone()).memory())
.unwrap()
.next()
.unwrap()
}
fn iomsg() -> IOMessage {
let hdr = VirtioSndPcmSetParams::default();
let memr = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(),
);
let vring = VringRwLock::new(memr, 0x1000).unwrap();
let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
let vq = MockSplitQueue::new(mem, 16);
let next_addr = vq.desc_table().total_size() + 0x100;
IOMessage {
vring,
status: VIRTIO_SND_S_OK.into(),
desc_chain: prepare_desc_chain::<VirtioSndPcmSetParams>(GuestAddress(0), hdr, 1),
descriptor: Descriptor::new(next_addr, 0x200, VRING_DESC_F_NEXT as u16, 1),
}
}
#[test]
fn test_pcm_state_transitions() {
let mut state = PCMState::new();
assert_eq!(state, PCMState::SetParameters);
assert!(state.set_parameters().is_ok());
state.set_parameters().unwrap();
assert_eq!(state, PCMState::SetParameters);
assert!(state.prepare().is_ok());
state.prepare().unwrap();
assert_eq!(state, PCMState::Prepare);
state.release().unwrap();
assert_eq!(state, PCMState::Release);
}
#[test]
fn test_invalid_state_transition() {
let mut state = PCMState::new();
assert_eq!(state, PCMState::SetParameters);
// Attempt to transition from set_params state to Release state
let result = state.release();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::SetParameters,
PCMState::Release
))
);
let result = state.start();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::SetParameters,
PCMState::Start
))
);
let result = state.stop();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::SetParameters,
PCMState::Stop
))
);
state.prepare().unwrap();
let result = state.stop();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::Prepare,
PCMState::Stop
))
);
state.start().unwrap();
let result = state.set_parameters();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::Start,
PCMState::SetParameters
))
);
let result = state.release();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::Start,
PCMState::Release
))
);
let result = state.prepare();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::Start,
PCMState::Prepare
))
);
state.stop().unwrap();
let result = state.set_parameters();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::Stop,
PCMState::SetParameters
))
);
let result = state.prepare();
assert!(result.is_err());
assert_eq!(
result,
Err(Error::InvalidStateTransition(
PCMState::Stop,
PCMState::Prepare
))
);
}
#[test]
fn test_stream_supports_format() {
let stream = Stream::default();
assert!(stream.supports_format(VIRTIO_SND_PCM_FMT_S16));
assert!(stream.supports_rate(VIRTIO_SND_PCM_RATE_44100));
}
#[test]
fn test_pcm_params_default() {
let params = PcmParams::default();
assert_eq!(params.buffer_bytes, 8192);
assert_eq!(params.period_bytes, 4096);
assert_eq!(params.features, 0);
assert_eq!(params.channels, 1);
assert_eq!(params.format, VIRTIO_SND_PCM_FMT_S16);
assert_eq!(params.rate, VIRTIO_SND_PCM_RATE_44100);
}
#[test]
fn test_buffer_consume() {
let msg = iomsg();
let message = Arc::new(msg);
let desc_msg = iomsg();
let buffer = Buffer::new(desc_msg.descriptor, message);
let mut buf = vec![0; 5];
let result = buffer.consume(&mut buf);
assert!(result.is_ok());
}
}