Skip to content

Commit a5066ec

Browse files
committed
video: add tests
Add test for all modules, with dev-dependencies (including rstest [1] for parametrized tests), and infrastructure. [1] - https://docs.rs/rstest/latest/rstest/ Signed-off-by: Albert Esteve <[email protected]>
1 parent 4d9825f commit a5066ec

File tree

7 files changed

+1256
-0
lines changed

7 files changed

+1256
-0
lines changed

staging/vhost-device-video/src/main.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,77 @@ fn main() -> Result<()> {
126126

127127
start_backend(VuVideoConfig::try_from(VideoArgs::parse()).unwrap())
128128
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use assert_matches::assert_matches;
133+
use rstest::*;
134+
use tempfile::tempdir;
135+
136+
use super::*;
137+
138+
#[rstest]
139+
// No device specified defaults to /dev/video0
140+
#[case::no_device(vec!["", "-s", "video.sock", "-b", "null"],
141+
VideoArgs {
142+
socket_path: "video.sock".into(),
143+
v4l2_device: "/dev/video0".into(),
144+
backend: BackendType::Null,
145+
})]
146+
// Specifying device overwrite the default value
147+
#[case::set_device(vec!["", "-s" , "video.sock", "-d", "/dev/video1", "-b", "null"],
148+
VideoArgs {
149+
socket_path: "video.sock".into(),
150+
v4l2_device: "/dev/video1".into(),
151+
backend: BackendType::Null,
152+
})]
153+
// Selecting different decoder
154+
#[case::set_v4l2_decoder(vec![" ", "--socket-path", "long-video.sock", "-b", "v4l2-decoder"],
155+
VideoArgs {
156+
socket_path: "long-video.sock".into(),
157+
v4l2_device: "/dev/video0".into(),
158+
backend: BackendType::V4L2Decoder,
159+
})]
160+
fn test_command_line_arguments(#[case] args: Vec<&str>, #[case] command_line: VideoArgs) {
161+
let args: VideoArgs = Parser::parse_from(args.as_slice());
162+
163+
assert_eq!(
164+
VuVideoConfig::try_from(command_line).unwrap(),
165+
VuVideoConfig::try_from(args).unwrap()
166+
);
167+
}
168+
169+
#[test]
170+
fn test_fail_create_backend() {
171+
use vhu_video::VuVideoError;
172+
let config = VideoArgs {
173+
socket_path: "video.sock".into(),
174+
v4l2_device: "/path/invalid/video.dev".into(),
175+
backend: BackendType::V4L2Decoder,
176+
};
177+
assert_matches!(
178+
start_backend(VuVideoConfig::try_from(config.clone()).unwrap()).unwrap_err(),
179+
Error::CouldNotCreateBackend(VuVideoError::AccessVideoDeviceFile)
180+
);
181+
}
182+
183+
#[test]
184+
fn test_fail_listener() {
185+
use std::fs::File;
186+
let test_dir = tempdir().expect("Could not create a temp test directory.");
187+
let v4l2_device = test_dir.path().join("video.dev");
188+
File::create(&v4l2_device).expect("Could not create a test device file.");
189+
let config = VideoArgs {
190+
socket_path: "~/path/invalid/video.sock".into(),
191+
v4l2_device: v4l2_device.to_owned(),
192+
backend: BackendType::Null,
193+
};
194+
assert_matches!(
195+
start_backend(VuVideoConfig::try_from(config).unwrap()).unwrap_err(),
196+
Error::FailedCreatingListener(_)
197+
);
198+
// cleanup
199+
std::fs::remove_file(v4l2_device).expect("Failed to clean up");
200+
test_dir.close().unwrap();
201+
}
202+
}

staging/vhost-device-video/src/stream.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,118 @@ impl Stream {
371371
}
372372
}
373373
}
374+
375+
#[cfg(test)]
376+
mod tests {
377+
use assert_matches::assert_matches;
378+
use rstest::*;
379+
use tempfile::TempDir;
380+
381+
use super::*;
382+
use crate::vhu_video::tests::{test_dir, VideoDeviceMock};
383+
384+
const TEST_PLANES: [ResourcePlane; 1] = [ResourcePlane {
385+
offset: 0,
386+
address: 0x100,
387+
length: 1024,
388+
}];
389+
const INVALID_MEM_TYPE: u32 = (MemoryType::VirtioObject as u32) + 1;
390+
const INVALID_FORMAT: u32 = (Format::Fwht as u32) + 1;
391+
392+
#[rstest]
393+
fn test_video_stream(test_dir: TempDir) {
394+
let stream_id: u32 = 1;
395+
let v4l2_device = VideoDeviceMock::new(&test_dir);
396+
let resource_id: u32 = 1;
397+
let mut stream = Stream::new(
398+
stream_id,
399+
Path::new(&v4l2_device.path),
400+
MemoryType::GuestPages as u32,
401+
MemoryType::VirtioObject as u32,
402+
Format::Fwht as u32,
403+
)
404+
.expect("Failed to create stream");
405+
assert_matches!(stream.memory(QueueType::InputQueue), MemoryType::GuestPages);
406+
assert_matches!(
407+
stream.memory(QueueType::OutputQueue),
408+
MemoryType::VirtioObject
409+
);
410+
411+
// Add resource
412+
let planes_layout = 0;
413+
let res = stream.add_resource(
414+
resource_id,
415+
planes_layout,
416+
Vec::from(TEST_PLANES),
417+
QueueType::InputQueue,
418+
);
419+
assert!(res.is_none());
420+
// Resource is retrievable
421+
{
422+
let res = stream.find_resource_mut(resource_id, QueueType::InputQueue);
423+
assert!(res.is_some());
424+
let res = res.unwrap();
425+
assert_eq!(res.planes_layout, planes_layout);
426+
assert_eq!(res.queue_type, QueueType::InputQueue);
427+
assert_eq!(res.state(), ResourceState::Created);
428+
// Query resource
429+
res.set_queried();
430+
}
431+
assert!(stream.all_resources_state(QueueType::InputQueue, ResourceState::Queried));
432+
{
433+
let res = stream
434+
.find_resource_mut(resource_id, QueueType::InputQueue)
435+
.unwrap();
436+
// Queue resource
437+
res.set_queued();
438+
}
439+
// Start streaming
440+
assert!(!stream.is_queue_streaming(QueueType::InputQueue));
441+
stream.set_queue_streaming(QueueType::InputQueue);
442+
assert!(stream.is_queue_streaming(QueueType::InputQueue));
443+
assert!(stream.all_resources_state(QueueType::InputQueue, ResourceState::Queued));
444+
// Resource can be found by index
445+
assert!(stream
446+
.find_resource_mut_by_index(0, QueueType::InputQueue)
447+
.is_some());
448+
{
449+
let res = stream
450+
.find_resource_mut(resource_id, QueueType::InputQueue)
451+
.unwrap();
452+
// Ready up resource
453+
res.set_ready();
454+
}
455+
assert!(stream.all_resources_state(QueueType::InputQueue, ResourceState::Ready));
456+
// Clean resources
457+
stream.empty_resources(QueueType::InputQueue);
458+
assert!(stream.resources_mut(QueueType::InputQueue).is_empty());
459+
}
460+
461+
#[rstest]
462+
#[case::invalid_in_mem(
463+
INVALID_MEM_TYPE, MemoryType::GuestPages as u32, Format::Fwht as u32)]
464+
#[case::invalid_out_mem(
465+
MemoryType::VirtioObject as u32, INVALID_MEM_TYPE, Format::Nv12 as u32)]
466+
#[case::invalid_format(
467+
MemoryType::VirtioObject as u32, MemoryType::VirtioObject as u32, INVALID_FORMAT)]
468+
fn test_video_stream_failures(
469+
test_dir: TempDir,
470+
#[case] in_mem: u32,
471+
#[case] out_mem: u32,
472+
#[case] format: u32,
473+
) {
474+
let stream_id: u32 = 1;
475+
let v4l2_device = VideoDeviceMock::new(&test_dir);
476+
assert_matches!(
477+
Stream::new(
478+
stream_id,
479+
Path::new(&v4l2_device.path),
480+
in_mem,
481+
out_mem,
482+
format
483+
)
484+
.unwrap_err(),
485+
VuVideoError::VideoStreamCreate
486+
);
487+
}
488+
}

staging/vhost-device-video/src/vhu_video.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,123 @@ impl VhostUserBackendMut<VringRwLock, ()> for VuVideoBackend {
260260
self.exit_event.try_clone().ok()
261261
}
262262
}
263+
264+
#[cfg(test)]
265+
pub mod tests {
266+
use std::{fs::File, path::PathBuf};
267+
268+
use rstest::*;
269+
use tempfile::{tempdir, TempDir};
270+
use vm_memory::GuestAddress;
271+
272+
use super::*;
273+
274+
pub struct VideoDeviceMock {
275+
pub path: PathBuf,
276+
_dev: File,
277+
}
278+
279+
impl VideoDeviceMock {
280+
pub fn new(test_dir: &TempDir) -> Self {
281+
let v4l2_device = test_dir.path().join("video.dev");
282+
Self {
283+
path: v4l2_device.to_owned(),
284+
_dev: File::create(v4l2_device.as_path())
285+
.expect("Could not create a test device file."),
286+
}
287+
}
288+
}
289+
290+
impl Drop for VideoDeviceMock {
291+
fn drop(&mut self) {
292+
std::fs::remove_file(&self.path).expect("Failed to clean up test device file.");
293+
}
294+
}
295+
296+
fn setup_backend_memory(backend: &mut VuVideoBackend) -> [VringRwLock; 2] {
297+
let mem = GuestMemoryAtomic::new(
298+
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(),
299+
);
300+
let vrings = [
301+
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
302+
VringRwLock::new(mem.clone(), 0x2000).unwrap(),
303+
];
304+
vrings[0].set_queue_info(0x100, 0x200, 0x300).unwrap();
305+
vrings[0].set_queue_ready(true);
306+
vrings[1].set_queue_info(0x1100, 0x1200, 0x1300).unwrap();
307+
vrings[1].set_queue_ready(true);
308+
309+
assert!(backend.update_memory(mem).is_ok());
310+
311+
vrings
312+
}
313+
314+
/// Creates a new test dir. There is no need to clean it after, since Drop
315+
/// is implemented for TempDir.
316+
#[fixture]
317+
pub fn test_dir() -> TempDir {
318+
tempdir().expect("Could not create a temp test directory.")
319+
}
320+
321+
#[rstest]
322+
fn test_video_backend(test_dir: TempDir) {
323+
let v4l2_device = VideoDeviceMock::new(&test_dir);
324+
let backend = VuVideoBackend::new(Path::new(&v4l2_device.path), BackendType::Null);
325+
326+
assert!(backend.is_ok());
327+
let mut backend = backend.unwrap();
328+
329+
assert_eq!(backend.num_queues(), NUM_QUEUES);
330+
assert_eq!(backend.max_queue_size(), QUEUE_SIZE);
331+
assert_ne!(backend.features(), 0);
332+
assert!(!backend.protocol_features().is_empty());
333+
backend.set_event_idx(false);
334+
335+
let vrings = setup_backend_memory(&mut backend);
336+
337+
let config = backend.get_config(0, 4);
338+
assert_eq!(config.len(), 4);
339+
let version = u32::from_le_bytes(config.try_into().unwrap());
340+
assert_eq!(version, 0);
341+
342+
let exit = backend.exit_event(0);
343+
assert!(exit.is_some());
344+
exit.unwrap().write(1).unwrap();
345+
for queue in COMMAND_Q..VIDEO_EVENT {
346+
// Skip exit event
347+
if queue == NUM_QUEUES as u16 {
348+
continue;
349+
}
350+
let ret = backend.handle_event(queue, EventSet::IN, &vrings, 0);
351+
assert!(ret.is_ok());
352+
assert!(!ret.unwrap());
353+
}
354+
}
355+
356+
#[rstest]
357+
fn test_video_backend_failures(test_dir: TempDir) {
358+
let v4l2_device = VideoDeviceMock::new(&test_dir);
359+
let mut backend = VuVideoBackend::new(Path::new(&v4l2_device.path), BackendType::Null)
360+
.expect("Could not create backend");
361+
let vrings = setup_backend_memory(&mut backend);
362+
363+
// reading out of the config space, expecting empty config
364+
let config = backend.get_config(44, 1);
365+
assert_eq!(config.len(), 0);
366+
367+
assert_eq!(
368+
backend
369+
.handle_event(COMMAND_Q, EventSet::OUT, &vrings, 0)
370+
.unwrap_err()
371+
.to_string(),
372+
VuVideoError::HandleEventNotEpollIn.to_string()
373+
);
374+
assert_eq!(
375+
backend
376+
.handle_event(VIDEO_EVENT + 1, EventSet::IN, &vrings, 0)
377+
.unwrap_err()
378+
.to_string(),
379+
VuVideoError::HandleUnknownEvent.to_string()
380+
);
381+
}
382+
}

0 commit comments

Comments
 (0)