Skip to content

Commit 0fd96c2

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 4a9cf68 commit 0fd96c2

File tree

5 files changed

+567
-0
lines changed

5 files changed

+567
-0
lines changed

crates/video/src/main.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,76 @@ fn main() -> Result<()> {
121121

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

crates/video/src/stream.rs

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

crates/video/src/vhu_video.rs

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

0 commit comments

Comments
 (0)