#![cfg(target_os = "macos")]
#![allow(clippy::expect_fun_call)]
#![allow(clippy::field_reassign_with_default)]
use std::time::Instant;
use arcbox_hypervisor::{
darwin::{DarwinMemory, is_supported},
memory::{GuestAddress, PAGE_SIZE},
traits::GuestMemory,
};
mod enable_disable {
use super::*;
#[test]
fn test_enable_dirty_tracking() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(64 * 1024).expect("Failed to create memory");
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
memory
.enable_dirty_tracking()
.expect("Second enable should succeed");
println!("Dirty tracking enabled successfully (idempotent)");
}
#[test]
fn test_disable_dirty_tracking() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(64 * 1024).expect("Failed to create memory");
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
memory
.disable_dirty_tracking()
.expect("Failed to disable dirty tracking");
let result = memory.get_dirty_pages();
assert!(
result.is_err(),
"get_dirty_pages should fail when tracking is disabled"
);
println!("Dirty tracking disabled successfully");
}
}
mod page_detection {
use super::*;
#[test]
fn test_get_dirty_pages_empty() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(64 * 1024).expect("Failed to create memory");
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert!(
dirty.is_empty(),
"Expected no dirty pages immediately after enable, got {}",
dirty.len()
);
let dirty2 = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert!(
dirty2.is_empty(),
"Expected no dirty pages on second call, got {}",
dirty2.len()
);
println!("Empty dirty page detection works correctly");
}
#[test]
fn test_get_dirty_pages_after_write() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(64 * 1024).expect("Failed to create memory");
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
let data = [0xAA_u8; 256];
memory
.write(GuestAddress::new(0x1000), &data)
.expect("Failed to write to memory");
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert!(
!dirty.is_empty(),
"Expected dirty pages after write, got none"
);
let page1_addr = 0x1000_u64; let found = dirty.iter().any(|p| p.guest_addr == page1_addr);
assert!(found, "Page 1 (0x1000) should be dirty, got: {:?}", dirty);
for page in &dirty {
assert_eq!(
page.size, PAGE_SIZE,
"Dirty page should have size {}",
PAGE_SIZE
);
}
let dirty2 = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert!(
dirty2.is_empty(),
"Expected no dirty pages after clearing, got {}",
dirty2.len()
);
println!("Dirty page detection after write works correctly");
}
#[test]
fn test_multiple_dirty_pages() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(64 * 1024).expect("Failed to create memory");
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
let data = [0xBB_u8; 128];
memory
.write(GuestAddress::new(0), &data)
.expect("Failed to write to page 0");
memory
.write(GuestAddress::new(0x2000), &data)
.expect("Failed to write to page 2");
memory
.write(GuestAddress::new(0x5000), &data)
.expect("Failed to write to page 5");
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert_eq!(
dirty.len(),
3,
"Expected 3 dirty pages, got {}",
dirty.len()
);
let dirty_addrs: Vec<u64> = dirty.iter().map(|p| p.guest_addr).collect();
assert!(dirty_addrs.contains(&0), "Page 0 should be dirty");
assert!(dirty_addrs.contains(&0x2000), "Page 2 should be dirty");
assert!(dirty_addrs.contains(&0x5000), "Page 5 should be dirty");
println!(
"Multiple dirty page detection works correctly: {:?}",
dirty_addrs
);
}
#[test]
fn test_cross_page_write() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(64 * 1024).expect("Failed to create memory");
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
let data = [0xCC_u8; 256]; let write_addr = PAGE_SIZE - 128; memory
.write(GuestAddress::new(write_addr), &data)
.expect("Failed to write across page boundary");
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert!(
dirty.len() >= 2,
"Expected at least 2 dirty pages for cross-boundary write, got {}",
dirty.len()
);
let dirty_addrs: Vec<u64> = dirty.iter().map(|p| p.guest_addr).collect();
assert!(
dirty_addrs.contains(&0),
"Page 0 should be dirty for cross-boundary write"
);
assert!(
dirty_addrs.contains(&PAGE_SIZE),
"Page 1 should be dirty for cross-boundary write"
);
println!("Cross-page boundary write detection works correctly");
}
}
mod performance {
use super::*;
#[test]
#[ignore = "long-running performance benchmark, run explicitly"]
fn test_dirty_tracking_performance() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let test_sizes = [
(1024 * 1024, "1MB"), (16 * 1024 * 1024, "16MB"), (64 * 1024 * 1024, "64MB"), ];
println!("\nDirty tracking performance (FNV-1a checksum):");
println!(
"{:>10} {:>12} {:>12} {:>15}",
"Size", "Enable (ms)", "Check (ms)", "Pages/ms"
);
println!("{}", "-".repeat(55));
for (size, label) in test_sizes {
let mut memory = DarwinMemory::new(size).expect("Failed to create memory");
let num_pages = size / PAGE_SIZE;
let start = Instant::now();
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
let enable_time = start.elapsed();
let start = Instant::now();
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
let check_time = start.elapsed();
assert!(dirty.is_empty(), "Expected no dirty pages for clean memory");
let enable_ms = enable_time.as_secs_f64() * 1000.0;
let check_ms = check_time.as_secs_f64() * 1000.0;
let pages_per_ms = num_pages as f64 / check_ms;
println!(
"{:>10} {:>12.2} {:>12.2} {:>15.0}",
label, enable_ms, check_ms, pages_per_ms
);
memory
.disable_dirty_tracking()
.expect("Failed to disable dirty tracking");
}
println!();
println!("Performance test completed");
}
#[test]
#[ignore = "long-running performance benchmark, run explicitly"]
fn test_dirty_tracking_performance_with_writes() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let size = 16 * 1024 * 1024_u64; let mut memory = DarwinMemory::new(size).expect("Failed to create memory");
let num_pages = size / PAGE_SIZE;
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
println!(
"\nDirty tracking with writes (16MB memory, {} pages):",
num_pages
);
println!(
"{:>15} {:>12} {:>15}",
"Dirty pages", "Check (ms)", "Pages/ms"
);
println!("{}", "-".repeat(45));
let dirty_counts = [0, 10, 100, 1000, 4096];
for &dirty_count in &dirty_counts {
let _ = memory.get_dirty_pages();
let data = [0xFF_u8; 64];
for i in 0..dirty_count {
let page_idx = (i * 4) % (num_pages as usize); let addr = (page_idx as u64) * PAGE_SIZE;
memory
.write(GuestAddress::new(addr), &data)
.expect("Failed to write");
}
let start = Instant::now();
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
let check_time = start.elapsed();
let check_ms = check_time.as_secs_f64() * 1000.0;
let pages_per_ms = num_pages as f64 / check_ms;
println!(
"{:>15} {:>12.2} {:>15.0}",
dirty.len(),
check_ms,
pages_per_ms
);
}
println!();
println!("Performance test with writes completed");
}
}
mod edge_cases {
use super::*;
#[test]
fn test_dirty_tracking_single_page() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(PAGE_SIZE).expect("Failed to create single page memory");
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert!(dirty.is_empty(), "Expected no dirty pages initially");
let data = [0xDD_u8; 16];
memory
.write(GuestAddress::new(0), &data)
.expect("Failed to write");
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert_eq!(dirty.len(), 1, "Expected exactly 1 dirty page");
assert_eq!(dirty[0].guest_addr, 0, "Dirty page should be at address 0");
println!("Single page dirty tracking works correctly");
}
#[test]
fn test_same_data_not_dirty() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(64 * 1024).expect("Failed to create memory");
let data = [0xEE_u8; 256];
memory
.write(GuestAddress::new(0x1000), &data)
.expect("Failed to write initial data");
memory
.enable_dirty_tracking()
.expect("Failed to enable dirty tracking");
memory
.write(GuestAddress::new(0x1000), &data)
.expect("Failed to write same data");
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert!(
dirty.is_empty(),
"Page should not be dirty when writing identical data, got {} dirty pages",
dirty.len()
);
let new_data = [0xFF_u8; 256];
memory
.write(GuestAddress::new(0x1000), &new_data)
.expect("Failed to write new data");
let dirty = memory.get_dirty_pages().expect("Failed to get dirty pages");
assert_eq!(
dirty.len(),
1,
"Page should be dirty after writing different data"
);
println!("Same-data-not-dirty optimization works correctly");
}
#[test]
fn test_get_dirty_pages_without_enable() {
if !is_supported() {
println!("Virtualization not supported, skipping");
return;
}
let mut memory = DarwinMemory::new(64 * 1024).expect("Failed to create memory");
let result = memory.get_dirty_pages();
assert!(
result.is_err(),
"get_dirty_pages should fail when tracking not enabled"
);
println!("Error handling for disabled tracking works correctly");
}
}