block: Add raw_async_io_tests shared test helpers

Add raw_async_io_tests.rs with punch_hole, write_zeroes, and
multiple_operations helpers that take &mut dyn AsyncIo + &mut File.
These are raw-backend-specific. They verify data by reading the
underlying file directly, which only works for plain file backends
without container format metadata.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-03-14 12:05:17 +01:00
committed by Rob Bradford
parent c53bc3d170
commit 5aa68ddf9e
2 changed files with 164 additions and 0 deletions

View File

@@ -24,6 +24,8 @@ pub mod qcow_sync;
/// Enabled with the `"io_uring"` feature
pub mod raw_async;
pub mod raw_async_aio;
#[cfg(test)]
mod raw_async_io_tests;
pub mod raw_sync;
pub mod vhd;
pub mod vhdx;

View File

@@ -0,0 +1,162 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! Shared test helpers for [`AsyncIo`] backends.
//!
//! Each helper takes a `&mut dyn AsyncIo` together with the [`File`] handle
//! that backs the I/O object, so the same logic exercises every backend with
//! only the constructor differing.
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use crate::async_io::{AsyncIo, AsyncIoError};
/// Tests punching a hole in the middle of a 4 MB file and verifying data
/// integrity around the hole.
pub fn test_punch_hole(async_io: &mut dyn AsyncIo, file: &mut File) {
// Write 4MB of data
let data = vec![0xAA; 4 * 1024 * 1024];
file.write_all(&data).unwrap();
file.sync_all().unwrap();
// Punch hole in the middle (1MB at offset 1MB)
let offset = 1024 * 1024;
let length = 1024 * 1024;
async_io.punch_hole(offset, length, 1).unwrap();
// Check completion
let (user_data, result) = async_io.next_completed_request().unwrap();
assert_eq!(user_data, 1);
assert_eq!(result, 0);
// Verify the hole reads as zeros
file.seek(SeekFrom::Start(offset)).unwrap();
let mut read_buf = vec![0; length as usize];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0),
"Punched hole should read as zeros"
);
// Verify data before hole is intact
file.seek(SeekFrom::Start(0)).unwrap();
let mut read_buf = vec![0; 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0xAA),
"Data before hole should be intact"
);
// Verify data after hole is intact
file.seek(SeekFrom::Start(offset + length)).unwrap();
let mut read_buf = vec![0; 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0xAA),
"Data after hole should be intact"
);
}
/// Tests writing zeroes to a 512 KB region inside a 4 MB file and verifying
/// surrounding data is preserved. Gracefully skips when the filesystem does
/// not support `FALLOC_FL_ZERO_RANGE`.
pub fn test_write_zeroes(async_io: &mut dyn AsyncIo, file: &mut File) {
// Write 4MB of data
let data = vec![0xBB; 4 * 1024 * 1024];
file.write_all(&data).unwrap();
file.sync_all().unwrap();
// Write zeros in the middle (512KB at offset 2MB)
let offset = 2 * 1024 * 1024;
let length = 512 * 1024;
let write_zeroes_result = async_io.write_zeroes(offset, length, 2);
// FALLOC_FL_ZERO_RANGE might not be supported on all filesystems (e.g., tmpfs)
// If it fails with ENOTSUP, skip the test
if let Err(AsyncIoError::WriteZeroes(ref e)) = write_zeroes_result
&& (e.raw_os_error() == Some(libc::EOPNOTSUPP) || e.raw_os_error() == Some(libc::ENOTSUP))
{
eprintln!("Skipping test_write_zeroes: filesystem doesn't support FALLOC_FL_ZERO_RANGE");
return;
}
write_zeroes_result.unwrap();
// Check completion
let (user_data, result) = async_io.next_completed_request().unwrap();
assert_eq!(user_data, 2);
assert_eq!(result, 0);
// Verify the zeroed region reads as zeros
file.seek(SeekFrom::Start(offset)).unwrap();
let mut read_buf = vec![0; length as usize];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0),
"Zeroed region should read as zeros"
);
// Verify data before zeroed region is intact
file.seek(SeekFrom::Start(offset - 1024)).unwrap();
let mut read_buf = vec![0; 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0xBB),
"Data before zeroed region should be intact"
);
// Verify data after zeroed region is intact
file.seek(SeekFrom::Start(offset + length)).unwrap();
let mut read_buf = vec![0; 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0xBB),
"Data after zeroed region should be intact"
);
}
/// Tests punching multiple holes in an 8 MB file and verifying each hole
/// independently reads as zeroes.
pub fn test_punch_hole_multiple_operations(async_io: &mut dyn AsyncIo, file: &mut File) {
// Write 8MB of data
let data = vec![0xCC; 8 * 1024 * 1024];
file.write_all(&data).unwrap();
file.sync_all().unwrap();
// Punch multiple holes
async_io.punch_hole(1024 * 1024, 512 * 1024, 10).unwrap();
async_io
.punch_hole(3 * 1024 * 1024, 512 * 1024, 11)
.unwrap();
async_io
.punch_hole(5 * 1024 * 1024, 512 * 1024, 12)
.unwrap();
// Check all completions
let (user_data, result) = async_io.next_completed_request().unwrap();
assert_eq!(user_data, 10);
assert_eq!(result, 0);
let (user_data, result) = async_io.next_completed_request().unwrap();
assert_eq!(user_data, 11);
assert_eq!(result, 0);
let (user_data, result) = async_io.next_completed_request().unwrap();
assert_eq!(user_data, 12);
assert_eq!(result, 0);
// Verify all holes read as zeros
file.seek(SeekFrom::Start(1024 * 1024)).unwrap();
let mut read_buf = vec![0; 512 * 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(read_buf.iter().all(|&b| b == 0));
file.seek(SeekFrom::Start(3 * 1024 * 1024)).unwrap();
file.read_exact(&mut read_buf).unwrap();
assert!(read_buf.iter().all(|&b| b == 0));
file.seek(SeekFrom::Start(5 * 1024 * 1024)).unwrap();
file.read_exact(&mut read_buf).unwrap();
assert!(read_buf.iter().all(|&b| b == 0));
}