use super::core::create_fixed_test_io_writable;
use super::support::*;
fn create_fixed_test_io() -> TestContext {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test-fixed-ro.vhdx");
create_vhdx(&path)
.size(4 * u64::from(MIB))
.block_size(MIB)
.logical_sector_size(4096)
.fixed(true)
.finish()
.expect("create fixed test vhdx");
let file = open_vhdx(&path);
TestContext {
_dir: dir,
file,
overlay: None,
}
}
#[test]
fn byte_offset_zero_read_matches_full_sector() {
let mut ctx = create_fixed_test_io_writable();
let mut io = ctx.io();
let mut sw = io.sector(0, 1).expect("sector 0");
sw.seek(SeekFrom::Start(0)).expect("seek to 0");
sw.write_all(&[0x42u8; SECTOR_SIZE as usize])
.expect("write sector 0");
let mut full_buf = vec![0u8; SECTOR_SIZE.into()];
let mut sr = io.sector(0, 1).expect("sector 0");
sr.seek(SeekFrom::Start(0)).expect("seek to 0");
sr.read_exact(&mut full_buf).expect("read sector 0");
assert_eq!(full_buf, [0x42u8; SECTOR_SIZE as usize]);
let mut small_buf = [0u8; 10];
let mut sr2 = io.sector(0, 1).expect("sector 0");
sr2.seek(SeekFrom::Start(0)).expect("seek to 0");
sr2.read_exact(&mut small_buf).expect("read 10 bytes");
assert_eq!(small_buf, [0x42u8; 10]);
assert_eq!(&full_buf[..10], &small_buf[..10]);
}
#[test]
fn byte_offset_non_aligned_read_extracts_correct_bytes() {
let mut ctx = create_fixed_test_io_writable();
let mut io = ctx.io();
let mut sw0 = io.sector(0, 1).expect("sector 0");
sw0.seek(SeekFrom::Start(0)).expect("seek to 0");
sw0.write_all(&[0x11u8; SECTOR_SIZE as usize])
.expect("write");
let mut buf = [0u8; 100];
let mut sr = io.sector(0, 1).expect("sector 0");
sr.seek(SeekFrom::Start(50)).expect("seek to 50");
sr.read_exact(&mut buf).expect("read at offset 50");
assert_eq!(buf, [0x11u8; 100]);
let mut sw1 = io.sector(1, 1).expect("sector 1");
sw1.seek(SeekFrom::Start(0)).expect("seek to 0");
sw1.write_all(&[0x11u8; SECTOR_SIZE as usize])
.expect("write sector 1");
let mut sector = io.sector(0, 2).expect("sector(0,2)");
let mut cross_buf = [0u8; 50];
sector.seek(SeekFrom::Start(4090)).expect("seek to 4090");
sector
.read_exact(&mut cross_buf)
.expect("read at offset 4090");
assert_eq!(cross_buf, [0x11u8; 50]);
}
#[test]
fn byte_offset_rmw_write_preserves_surrounding_bytes() {
let mut ctx = create_fixed_test_io_writable();
let mut io = ctx.io();
let mut sector = io.sector(0, 1).expect("sector 0");
sector.seek(SeekFrom::Start(0)).expect("seek to 0");
sector
.write_all(&[0xAAu8; SECTOR_SIZE as usize])
.expect("write full sector");
sector.seek(SeekFrom::Start(100)).expect("seek to 100");
sector
.write_all(&[0xBBu8; 10])
.expect("write 10 bytes at offset 100");
let mut full_buf = vec![0u8; SECTOR_SIZE.into()];
sector.seek(SeekFrom::Start(0)).expect("seek to 0");
sector.read_exact(&mut full_buf).expect("read full sector");
assert_eq!(&full_buf[0..100], &[0xAAu8; 100], "before patch preserved");
assert_eq!(&full_buf[100..110], &[0xBBu8; 10], "patch applied");
assert_eq!(
&full_buf[110..SECTOR_SIZE.into()],
&[0xAAu8; 3986],
"after patch preserved"
);
}
#[test]
fn byte_offset_cross_block_boundary_read() {
let mut ctx = create_fixed_test_io_writable();
let mut io = ctx.io();
let mut sector = io.sector(254, 4).expect("sector(254,4)");
let mut pattern = Vec::with_capacity(4 * SECTOR_SIZE as usize);
for i in 0..(4 * SECTOR_SIZE) {
pattern.push(u8::try_from(i % 256).expect("modulo 256 fits u8"));
}
sector.seek(SeekFrom::Start(0)).expect("seek to 0");
sector.write_all(&pattern).expect("write pattern");
let mut buf = [0u8; 200];
sector.seek(SeekFrom::Start(8092)).expect("seek to 8092");
sector
.read_exact(&mut buf)
.expect("read crossing block boundary");
assert_eq!(buf, &pattern[8092..8292], "cross-boundary read mismatch");
}
#[test]
fn byte_offset_cross_block_boundary_write_rmw() {
let mut ctx = create_fixed_test_io_writable();
let mut io = ctx.io();
let mut sector = io.sector(254, 4).expect("sector(254,4)");
let init = [0xDDu8; 4 * SECTOR_SIZE as usize];
sector.seek(SeekFrom::Start(0)).expect("seek to 0");
sector.write_all(&init).expect("write initial");
let patch = [0xEEu8; 30];
sector.seek(SeekFrom::Start(8180)).expect("seek to 8180");
sector
.write_all(&patch)
.expect("write cross-boundary patch");
let mut buf = vec![0u8; 4 * SECTOR_SIZE as usize];
sector.seek(SeekFrom::Start(0)).expect("seek to 0");
sector.read_exact(&mut buf).expect("read back");
assert_eq!(&buf[0..8180], &[0xDDu8; 8180], "before patch preserved");
assert_eq!(&buf[8180..8210], &[0xEEu8; 30], "patch applied");
assert_eq!(
&buf[8210..],
&[0xDDu8; 4 * SECTOR_SIZE as usize - 8210],
"after patch preserved"
);
}
#[test]
fn byte_offset_validation_exceeds_range() {
let mut ctx = create_fixed_test_io_writable();
let mut io = ctx.io();
let mut sector = io.sector(0, 1).expect("sector 0");
sector
.seek(SeekFrom::Start(u64::from(SECTOR_SIZE)))
.expect("seek to EOF");
let mut tiny = [0u8; 1];
let n = sector.read(&mut tiny).expect("read at EOF");
assert_eq!(n, 0, "read at EOF should return 0");
sector.seek(SeekFrom::Start(4090)).expect("seek to 4090");
let mut buf = [0u8; 10];
let n = sector.read(&mut buf).expect("read at 4090");
assert_eq!(n, 6, "should read partial at end");
sector
.seek(SeekFrom::Start(u64::from(SECTOR_SIZE)))
.expect("seek to EOF");
let n = sector.write(b"x").expect("write at EOF");
assert_eq!(n, 0, "write at EOF should return 0");
sector.seek(SeekFrom::Start(4090)).expect("seek to 4090");
let n = sector.write(&[0xFFu8; 10]).expect("write partial at end");
assert_eq!(n, 6, "should write partial at end");
}
#[test]
fn byte_offset_empty_buf_is_noop() {
let mut ctx = create_fixed_test_io_writable();
let mut io = ctx.io();
let mut sector = io.sector(0, 1).expect("sector 0");
assert_eq!(
sector.read(&mut []).expect("empty read"),
0,
"empty read returns 0"
);
assert_eq!(
sector.write(&[]).expect("empty write"),
0,
"empty write returns 0"
);
}
#[test]
fn byte_offset_write_to_read_only_returns_error() {
let mut ctx = create_fixed_test_io();
let mut io = ctx.io();
let mut sector = io.sector(0, 1).expect("sector 0");
let err = sector.write(&[0x42u8; 10]).unwrap_err();
assert_eq!(
err.kind(),
ErrorKind::PermissionDenied,
"should be PermissionDenied error"
);
}
#[test]
fn byte_offset_write_to_not_present_block_allocates() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test-dynamic-rw.vhdx");
create_vhdx(&path)
.size(256 * u64::from(MIB))
.block_size(32 * MIB)
.logical_sector_size(4096)
.finish()
.expect("create dynamic test vhdx");
let file = open_vhdx_writable(&path);
let mut ctx = TestContext {
_dir: dir,
file,
overlay: None,
};
let mut io = ctx.io();
let mut sector = io.sector(0, 1).expect("sector 0");
let written = sector.write(&[0x42u8; 10]).expect("write allocates block");
assert_eq!(written, 10, "partial write should report bytes written");
sector.seek(SeekFrom::Start(0)).expect("seek to start");
let mut buf = vec![0xFFu8; SECTOR_SIZE.into()];
sector.read_exact(&mut buf).expect("read allocated sector");
assert_eq!(&buf[..10], &[0x42u8; 10]);
assert!(buf[10..].iter().all(|&byte| byte == 0));
}