From 5aa68ddf9ebc0c36fc85eb64e03007b965675284 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 12:05:17 +0100 Subject: [PATCH] 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 --- block/src/lib.rs | 2 + block/src/raw_async_io_tests.rs | 162 ++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 block/src/raw_async_io_tests.rs diff --git a/block/src/lib.rs b/block/src/lib.rs index f477cd36c..6e3e50178 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -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; diff --git a/block/src/raw_async_io_tests.rs b/block/src/raw_async_io_tests.rs new file mode 100644 index 000000000..560e41e33 --- /dev/null +++ b/block/src/raw_async_io_tests.rs @@ -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)); +}