arcbox-hypervisor 0.4.10

Cross-platform hypervisor abstraction layer for ArcBox
Documentation
//! End-to-end VM boot integration test.
//!
//! This test requires a Linux kernel to be present at:
//! - tests/resources/vmlinuz-arm64
//! - tests/resources/initramfs-arm64 (optional)
//!
//! Run `tests/resources/download-kernel.sh` to download test resources.

use std::path::PathBuf;
use std::time::Duration;

#[cfg(target_os = "macos")]
use arcbox_hypervisor::{
    config::VmConfig,
    darwin::{DarwinHypervisor, is_supported},
    traits::{Hypervisor, VirtualMachine},
    types::CpuArch,
};

/// Gets the path to test resources.
fn test_resources_dir() -> PathBuf {
    let manifest_dir = env!("CARGO_MANIFEST_DIR");
    PathBuf::from(manifest_dir)
        .parent()
        .unwrap()
        .parent()
        .unwrap()
        .join("tests")
        .join("resources")
}

/// Checks if test kernel is available.
fn kernel_available() -> bool {
    let kernel_path = test_resources_dir().join("vmlinuz-arm64");
    kernel_path.exists()
}

#[cfg(target_os = "macos")]
#[test]
fn test_vm_boot_with_kernel() {
    // Skip if virtualization is not supported
    if !is_supported() {
        println!("Virtualization not supported, skipping");
        return;
    }

    // Skip if kernel is not available
    if !kernel_available() {
        println!("Test kernel not found. Run: tests/resources/download-kernel.sh");
        println!("Skipping VM boot test.");
        return;
    }

    // Initialize tracing for debug output
    let _ = tracing_subscriber::fmt()
        .with_max_level(tracing::Level::DEBUG)
        .with_test_writer()
        .try_init();

    let kernel_path = test_resources_dir().join("vmlinuz-arm64");
    let initrd_path = test_resources_dir().join("initramfs-arm64");

    println!("Using kernel: {:?}", kernel_path);
    println!("Using initrd: {:?}", initrd_path);

    // Create hypervisor
    let hypervisor = DarwinHypervisor::new().expect("Failed to create hypervisor");
    let caps = hypervisor.capabilities();

    println!("Hypervisor capabilities:");
    println!("  Max vCPUs: {}", caps.max_vcpus);
    println!(
        "  Max memory: {} GB",
        caps.max_memory / (1024 * 1024 * 1024)
    );
    println!("  Rosetta: {}", caps.rosetta);

    // Create VM config
    let mut config = VmConfig {
        vcpu_count: 2,
        memory_size: 512 * 1024 * 1024, // 512MB
        arch: CpuArch::native(),
        kernel_path: Some(kernel_path.to_string_lossy().to_string()),
        kernel_cmdline: Some(
            "console=hvc0 earlycon=pl011,0x09000000 root=/dev/ram0 rdinit=/bin/sh".to_string(),
        ),
        ..Default::default()
    };

    // Add initrd if available
    if initrd_path.exists() {
        config.initrd_path = Some(initrd_path.to_string_lossy().to_string());
    }

    // Create VM
    let mut vm = hypervisor.create_vm(config).expect("Failed to create VM");

    println!("VM created successfully");
    println!("VM ID: {}", vm.id());

    // Note: Serial console setup causes issues with PTY handling
    // Skip for now - focus on VM lifecycle
    println!("Skipping serial console setup (PTY handling issues)");

    // Start VM
    println!("Starting VM...");

    // Wrap in catch_unwind to handle any panics
    let start_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| vm.start()));

    match start_result {
        Ok(Ok(())) => {
            println!("VM started successfully!");

            // Let it run for a few seconds to observe boot
            println!("Letting VM run for 3 seconds...");
            std::thread::sleep(Duration::from_secs(3));

            // Check state
            println!("VM state: {:?}", vm.state());
            println!("VM running: {}", vm.is_running());

            // Stop VM
            println!("Stopping VM...");
            match vm.stop() {
                Ok(()) => println!("VM stopped successfully"),
                Err(e) => println!("Warning: Error stopping VM: {}", e),
            }
        }
        Ok(Err(e)) => {
            println!("Failed to start VM: {}", e);
            println!(
                "Note: This may fail if the kernel format is not compatible with Virtualization.framework"
            );
            println!("Virtualization.framework requires uncompressed ARM64 Image format");
        }
        Err(panic) => {
            println!("VM start panicked: {:?}", panic);
        }
    }

    println!("VM boot test completed");
}

#[cfg(target_os = "macos")]
#[test]
fn test_vm_lifecycle_without_kernel() {
    // Skip if virtualization is not supported
    if !is_supported() {
        println!("Virtualization not supported, skipping");
        return;
    }

    // Test VM lifecycle without actually booting (no kernel)
    let hypervisor = DarwinHypervisor::new().expect("Failed to create hypervisor");

    let config = VmConfig {
        vcpu_count: 1,
        memory_size: 256 * 1024 * 1024, // 256MB
        arch: CpuArch::native(),
        ..Default::default()
    };

    let vm = hypervisor.create_vm(config).expect("Failed to create VM");

    println!("VM created: ID={}", vm.id());
    println!("Initial state: {:?}", vm.state());

    // VM without kernel can't start, but we can test creation and cleanup
    assert!(!vm.is_running());

    // Drop will clean up
    drop(vm);

    println!("VM lifecycle test (no kernel) passed");
}

#[cfg(target_os = "macos")]
#[test]
fn test_virtio_device_configuration() {
    use arcbox_hypervisor::types::VirtioDeviceConfig;

    // Skip if virtualization is not supported
    if !is_supported() {
        println!("Virtualization not supported, skipping");
        return;
    }

    let hypervisor = DarwinHypervisor::new().expect("Failed to create hypervisor");

    let config = VmConfig {
        vcpu_count: 1,
        memory_size: 256 * 1024 * 1024,
        arch: CpuArch::native(),
        ..Default::default()
    };

    let mut vm = hypervisor.create_vm(config).expect("Failed to create VM");

    // Add network device
    let net_config = VirtioDeviceConfig::network();
    let result = vm.add_virtio_device(net_config);
    println!("Add network device: {:?}", result);

    // Add vsock device
    let vsock_config = VirtioDeviceConfig::vsock();
    let result = vm.add_virtio_device(vsock_config);
    println!("Add vsock device: {:?}", result);

    // Add entropy device
    let rng_config = VirtioDeviceConfig::entropy();
    let result = vm.add_virtio_device(rng_config);
    println!("Add entropy device: {:?}", result);

    println!("VirtIO device configuration test passed");
}