Skip to content

Commit 3161de9

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 5e548fd commit 3161de9

File tree

5 files changed

+556
-0
lines changed

5 files changed

+556
-0
lines changed

crates/video/src/main.rs

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

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

crates/video/src/stream.rs

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

crates/video/src/vhu_video.rs

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

0 commit comments

Comments
 (0)