From cae9fd0f7946b53751a81d3c1e709336bc7ae637 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Fri, 20 Oct 2023 14:13:59 +0200 Subject: [PATCH] sound/test: add testing module for sound add testing modules Signed-off-by: Dorinda Bassey --- staging/Cargo.lock | 129 +++------- staging/coverage_config_x86_64.json | 2 +- staging/vhost-device-sound/Cargo.toml | 3 +- .../src/audio_backends/null.rs | 28 +++ .../src/audio_backends/pipewire.rs | 146 ++++++++++++ staging/vhost-device-sound/src/device.rs | 217 ++++++++++++++++- staging/vhost-device-sound/src/lib.rs | 65 ++++- staging/vhost-device-sound/src/main.rs | 3 - staging/vhost-device-sound/src/stream.rs | 225 +++++++++++++++++- 9 files changed, 708 insertions(+), 110 deletions(-) diff --git a/staging/Cargo.lock b/staging/Cargo.lock index 155ea48..d535299 100644 --- a/staging/Cargo.lock +++ b/staging/Cargo.lock @@ -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", diff --git a/staging/coverage_config_x86_64.json b/staging/coverage_config_x86_64.json index a70b354..aecab76 100644 --- a/staging/coverage_config_x86_64.json +++ b/staging/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 7.94, + "coverage_score": 50.43, "exclude_path": "", "crate_features": "" } diff --git a/staging/vhost-device-sound/Cargo.toml b/staging/vhost-device-sound/Cargo.toml index aff5edd..747d3b4 100644 --- a/staging/vhost-device-sound/Cargo.toml +++ b/staging/vhost-device-sound/Cargo.toml @@ -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"] } diff --git a/staging/vhost-device-sound/src/audio_backends/null.rs b/staging/vhost-device-sound/src/audio_backends/null.rs index 66edb5e..df9f01d 100644 --- a/staging/vhost-device-sound/src/audio_backends/null.rs +++ b/staging/vhost-device-sound/src/audio_backends/null.rs @@ -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); + } +} diff --git a/staging/vhost-device-sound/src/audio_backends/pipewire.rs b/staging/vhost-device-sound/src/audio_backends/pipewire.rs index 624d5fa..18e3c02 100644 --- a/staging/vhost-device-sound/src/audio_backends/pipewire.rs +++ b/staging/vhost-device-sound/src/audio_backends/pipewire.rs @@ -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( + 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::() as u32, + VRING_DESC_F_NEXT as u16, + index + 1, + ); + + mem.write_obj::(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::() + .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::(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); + } +} diff --git a/staging/vhost-device-sound/src/device.rs b/staging/vhost-device-sound/src/device.rs index fed1600..d118a33 100644 --- a/staging/vhost-device-sound/src/device.rs +++ b/staging/vhost-device-sound/src/device.rs @@ -32,7 +32,7 @@ use crate::{ ControlMessageKind, Error, IOMessage, Result, SoundConfig, }; -struct VhostUserSoundThread { +pub struct VhostUserSoundThread { mem: Option>, event_idx: bool, chmaps: Arc>>, @@ -584,9 +584,9 @@ impl VhostUserSoundThread { } pub struct VhostUserSoundBackend { - threads: Vec>, + pub threads: Vec>, virtio_cfg: VirtioSoundConfig, - exit_event: EventFd, + pub exit_event: EventFd, audio_backend: RwLock>, } @@ -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(); + } +} diff --git a/staging/vhost-device-sound/src/lib.rs b/staging/vhost-device-sound/src/lib.rs index b83271b..895287c 100644 --- a/staging/vhost-device-sound/src/lib.rs +++ b/staging/vhost-device-sound/src/lib.rs @@ -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(>::into(VIRTIO_SND_R_JACK_INFO)), + Ok(ControlMessageKind::JackInfo) + ); + assert_eq!( + ControlMessageKind::try_from(>::into(VIRTIO_SND_R_PCM_INFO)), + Ok(ControlMessageKind::PcmInfo) + ); + assert_eq!( + ControlMessageKind::try_from(>::into(VIRTIO_SND_R_CHMAP_INFO)), + Ok(ControlMessageKind::ChmapInfo) + ); + assert_eq!( + ControlMessageKind::try_from(>::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(>::into(invalid_value)), + Err(InvalidControlMessage(invalid_value)) + ); + } +} diff --git a/staging/vhost-device-sound/src/main.rs b/staging/vhost-device-sound/src/main.rs index 8689b39..5226626 100644 --- a/staging/vhost-device-sound/src/main.rs +++ b/staging/vhost-device-sound/src/main.rs @@ -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", diff --git a/staging/vhost-device-sound/src/stream.rs b/staging/vhost-device-sound/src/stream.rs index 1e07f89..e6a0611 100644 --- a/staging/vhost-device-sound/src/stream.rs +++ b/staging/vhost-device-sound/src/stream.rs @@ -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 = std::result::Result; /// | | | | | /// | |<----------| | | /// ``` -#[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( + 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::() as u32, + VRING_DESC_F_NEXT as u16, + index + 1, + ); + + mem.write_obj::(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::() + .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::(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()); + } +}