Skip to content

Commit

Permalink
video: add tests
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
aesteve-rh committed Sep 18, 2023
1 parent dbdd2de commit 046da40
Show file tree
Hide file tree
Showing 5 changed files with 549 additions and 0 deletions.
73 changes: 73 additions & 0 deletions crates/video/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,76 @@ fn main() -> Result<()> {

start_backend(VuVideoConfig::try_from(VideoArgs::parse()).unwrap())
}

#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use rstest::*;
use tempfile::tempdir;

#[rstest]
// No device specified defaults to /dev/video0
#[case::no_device(vec!["", "-s", "video.sock", "-b", "null"],
VideoArgs {
socket_path: "video.sock".into(),
v4l2_device: "/dev/video0".into(),
backend: BackendType::Null,
})]
// Specifying device overwrite the default value
#[case::set_device(vec!["", "-s" , "video.sock", "-d", "/dev/video1", "-b", "null"],
VideoArgs {
socket_path: "video.sock".into(),
v4l2_device: "/dev/video1".into(),
backend: BackendType::Null,
})]
// Selecting different decoder
#[case::set_v4l2_decoder(vec![" ", "--socket-path", "long-video.sock", "-b", "v4l2-decoder"],
VideoArgs {
socket_path: "long-video.sock".into(),
v4l2_device: "/dev/video0".into(),
backend: BackendType::V4L2Decoder,
})]
fn test_command_line_arguments(#[case] args: Vec<&str>, #[case] command_line: VideoArgs) {
let args: VideoArgs = Parser::parse_from(args.as_slice());

assert_eq!(
VuVideoConfig::try_from(command_line).unwrap(),
VuVideoConfig::try_from(args).unwrap()
);
}

#[test]
fn test_fail_create_backend() {
use vhu_video::VuVideoError;
let config = VideoArgs {
socket_path: "video.sock".into(),
v4l2_device: "/path/invalid/video.dev".into(),
backend: BackendType::V4L2Decoder,
};
assert_matches!(
start_backend(VuVideoConfig::try_from(config.clone()).unwrap()).unwrap_err(),
Error::CouldNotCreateBackend(VuVideoError::AccessVideoDeviceFile)
);
}

#[test]
fn test_fail_listener() {
use std::fs::File;
let test_dir = tempdir().expect("Could not create a temp test directory.");
let v4l2_device = test_dir.path().join("video.dev");
File::create(&v4l2_device).expect("Could not create a test device file.");
let config = VideoArgs {
socket_path: "~/path/invalid/video.sock".into(),
v4l2_device: v4l2_device.to_owned(),
backend: BackendType::Null,
};
assert_matches!(
start_backend(VuVideoConfig::try_from(config).unwrap()).unwrap_err(),
Error::FailedCreatingListener(_)
);
// cleanup
std::fs::remove_file(v4l2_device).expect("Failed to clean up");
test_dir.close().unwrap();
}
}
88 changes: 88 additions & 0 deletions crates/video/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,91 @@ impl Stream {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::vhu_video::tests::{test_dir, VideoDeviceMock};
use assert_matches::assert_matches;
use rstest::*;
use tempfile::TempDir;

const TEST_PLANES: [ResourcePlane; 1] = [ResourcePlane {
offset: 0,
address: 0x100,
length: 1024,
}];
const INVALID_MEM_TYPE: u32 = (MemoryType::VirtioObject as u32) + 1;
const INVALID_FORMAT: u32 = (Format::Fwht as u32) + 1;

#[rstest]
fn test_video_stream(test_dir: TempDir) {
let stream_id: u32 = 1;
let v4l2_device = VideoDeviceMock::new(&test_dir);
let resource_id: u32 = 1;
let mut stream = Stream::new(
stream_id,
Path::new(&v4l2_device.path),
MemoryType::GuestPages as u32,
MemoryType::VirtioObject as u32,
Format::Fwht as u32,
)
.expect("Failed to create stream");
assert_matches!(stream.memory(QueueType::InputQueue), MemoryType::GuestPages);
assert_matches!(
stream.memory(QueueType::OutputQueue),
MemoryType::VirtioObject
);

// Add resource
let planes_layout = 0;
let res = stream.add_resource(
resource_id,
planes_layout,
Vec::from(TEST_PLANES),
QueueType::InputQueue,
);
assert!(res.is_none());
// Resource is retrievable
let res = stream.find_resource_mut(resource_id, QueueType::InputQueue);
assert!(res.is_some());
let res = res.unwrap();
assert_eq!(res.planes_layout, planes_layout);
assert_eq!(res.queue_type, QueueType::InputQueue);
assert_eq!(res.state(), ResourceState::Created);
// Change resource state
res.set_queued();
assert!(stream.all_queued(QueueType::InputQueue));
// Clean resources
stream.empty_resources(QueueType::InputQueue);
assert!(stream.resources_mut(QueueType::InputQueue).is_empty());
}

#[rstest]
#[case::invalid_in_mem(
INVALID_MEM_TYPE, MemoryType::GuestPages as u32, Format::Fwht as u32)]
#[case::invalid_out_mem(
MemoryType::VirtioObject as u32, INVALID_MEM_TYPE, Format::Nv12 as u32)]
#[case::invalid_format(
MemoryType::VirtioObject as u32, MemoryType::VirtioObject as u32, INVALID_FORMAT)]
fn test_video_stream_failures(
test_dir: TempDir,
#[case] in_mem: u32,
#[case] out_mem: u32,
#[case] format: u32,
) {
let stream_id: u32 = 1;
let v4l2_device = VideoDeviceMock::new(&test_dir);
assert_matches!(
Stream::new(
stream_id,
Path::new(&v4l2_device.path),
in_mem,
out_mem,
format
)
.unwrap_err(),
VuVideoError::VideoStreamCreate
);
}
}
118 changes: 118 additions & 0 deletions crates/video/src/vhu_video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,121 @@ impl VhostUserBackendMut<VringRwLock, ()> for VuVideoBackend {
self.exit_event.try_clone().ok()
}
}

#[cfg(test)]
pub mod tests {
use super::*;
use rstest::*;
use std::fs::File;
use std::path::PathBuf;
use tempfile::{tempdir, TempDir};
use vm_memory::GuestAddress;

pub struct VideoDeviceMock {
pub path: PathBuf,
_dev: File,
}

impl VideoDeviceMock {
pub fn new(test_dir: &TempDir) -> Self {
let v4l2_device = test_dir.path().join("video.dev");
Self {
path: v4l2_device.to_owned(),
_dev: File::create(v4l2_device.as_path())
.expect("Could not create a test device file."),
}
}
}

impl Drop for VideoDeviceMock {
fn drop(&mut self) {
std::fs::remove_file(&self.path).expect("Failed to clean up test device file.");
}
}

fn setup_backend_memory(backend: &mut VuVideoBackend) -> [VringRwLock; 2] {
let mem = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(),
);
let vrings = [
VringRwLock::new(mem.clone(), 0x1000).unwrap(),
VringRwLock::new(mem.clone(), 0x2000).unwrap(),
];
vrings[0].set_queue_info(0x100, 0x200, 0x300).unwrap();
vrings[0].set_queue_ready(true);
vrings[1].set_queue_info(0x1100, 0x1200, 0x1300).unwrap();
vrings[1].set_queue_ready(true);

assert!(backend.update_memory(mem).is_ok());

vrings
}

/// Creates a new test dir. There is no need to clean it after, since Drop is implemented for TempDir.
#[fixture]
pub fn test_dir() -> TempDir {
tempdir().expect("Could not create a temp test directory.")
}

#[rstest]
fn test_video_backend(test_dir: TempDir) {
let v4l2_device = VideoDeviceMock::new(&test_dir);
let backend = VuVideoBackend::new(Path::new(&v4l2_device.path), BackendType::Null);

assert!(backend.is_ok());
let mut backend = backend.unwrap();

assert_eq!(backend.num_queues(), NUM_QUEUES);
assert_eq!(backend.max_queue_size(), QUEUE_SIZE);
assert_ne!(backend.features(), 0);
assert!(!backend.protocol_features().is_empty());
backend.set_event_idx(false);

let vrings = setup_backend_memory(&mut backend);

let config = backend.get_config(0, 4);
assert_eq!(config.len(), 4);
let version = u32::from_le_bytes(config.try_into().unwrap());
assert_eq!(version, 0);

let exit = backend.exit_event(0);
assert!(exit.is_some());
exit.unwrap().write(1).unwrap();
for queue in COMMAND_Q..VIDEO_EVENT {
// Skip exit event
if queue == NUM_QUEUES as u16 {
continue;
}
let ret = backend.handle_event(queue, EventSet::IN, &vrings, 0);
assert!(ret.is_ok());
assert!(!ret.unwrap());
}
}

#[rstest]
fn test_video_backend_failures(test_dir: TempDir) {
let v4l2_device = VideoDeviceMock::new(&test_dir);
let mut backend = VuVideoBackend::new(Path::new(&v4l2_device.path), BackendType::Null)
.expect("Could not create backend");
let vrings = setup_backend_memory(&mut backend);

// reading out of the config space, expecting empty config
let config = backend.get_config(44, 1);
assert_eq!(config.len(), 0);

assert_eq!(
backend
.handle_event(COMMAND_Q, EventSet::OUT, &vrings, 0)
.unwrap_err()
.to_string(),
VuVideoError::HandleEventNotEpollIn.to_string()
);
assert_eq!(
backend
.handle_event(VIDEO_EVENT + 1, EventSet::IN, &vrings, 0)
.unwrap_err()
.to_string(),
VuVideoError::HandleUnknownEvent.to_string()
);
}
}
Loading

0 comments on commit 046da40

Please sign in to comment.