mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2026-01-05 00:39:40 +00:00
sound/test: add testing module for sound
add testing modules Signed-off-by: Dorinda Bassey <dbassey@redhat.com>
This commit is contained in:
parent
d04d6b42fc
commit
cae9fd0f79
129
staging/Cargo.lock
generated
129
staging/Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"coverage_score": 7.94,
|
||||
"coverage_score": 50.43,
|
||||
"exclude_path": "",
|
||||
"crate_features": ""
|
||||
}
|
||||
|
||||
@ -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"] }
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user