axvmconfig 0.4.2

A simple VM configuration tool for ArceOS-Hypervisor.
Documentation
// Copyright 2025 The Axvisor Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use enumerable::Enumerable;

use crate::{
    AxVMCrateConfig, EmulatedDeviceType, VMDevicesConfig, VMInterruptMode, VmMemMappingType,
};

#[test]
fn test_config_deser() {
    const EXAMPLE_CONFIG: &str = r#"
[base]
id = 12
name = "test_vm"
vm_type = 1
cpu_num = 2
phys_cpu_sets = [3, 4]
phys_cpu_ids = [0x500, 0x501]

[kernel]
entry_point = 0xdeadbeef
image_location = "memory"
kernel_path = "amazing-os.bin"
kernel_load_addr = 0xdeadbeef
dtb_path = "impressive-board.dtb"
dtb_load_addr = 0xa0000000

memory_regions = [
    [0x8000_0000, 0x8000_0000, 0x7, 1],
]

[devices]
passthrough_devices = [
    ["dev0", 0x0, 0x0, 0x0800_0000, 0x1],
    ["dev1", 0x0900_0000, 0x0900_0000, 0x0a00_0000, 0x2],
]

emu_devices = [
    ["dev2", 0x0800_0000, 0x1_0000, 0, 0x21, []],
    ["dev3", 0x0808_0000, 0x1_0000, 0, 0x22, []],
]

interrupt_mode = "passthrough"
    "#;

    let config: AxVMCrateConfig = toml::from_str(EXAMPLE_CONFIG).unwrap();

    assert_eq!(config.base.id, 12);
    assert_eq!(config.base.name, "test_vm");
    assert_eq!(config.base.vm_type, 1);
    assert_eq!(config.base.cpu_num, 2);
    assert_eq!(config.base.phys_cpu_ids, Some(vec![0x500, 0x501]));
    assert_eq!(config.base.phys_cpu_sets, Some(vec![3, 4]));

    assert_eq!(config.kernel.entry_point, 0xdeadbeef);
    assert_eq!(config.kernel.image_location, Some("memory".to_string()));
    assert_eq!(config.kernel.kernel_path, "amazing-os.bin");
    assert_eq!(config.kernel.kernel_load_addr, 0xdeadbeef);
    assert_eq!(
        config.kernel.dtb_path,
        Some("impressive-board.dtb".to_string())
    );
    assert_eq!(config.kernel.dtb_load_addr, Some(0xa000_0000));
    assert_eq!(config.kernel.memory_regions.len(), 1);
    assert_eq!(config.kernel.memory_regions[0].gpa, 0x8000_0000);
    assert_eq!(config.kernel.memory_regions[0].size, 0x8000_0000);
    assert_eq!(config.kernel.memory_regions[0].flags, 0x7);
    assert_eq!(
        config.kernel.memory_regions[0].map_type,
        VmMemMappingType::MapIdentical
    );

    assert_eq!(config.devices.passthrough_devices.len(), 2);
    assert_eq!(config.devices.passthrough_devices[0].name, "dev0");
    assert_eq!(config.devices.passthrough_devices[0].base_gpa, 0x0);
    assert_eq!(config.devices.passthrough_devices[0].base_hpa, 0x0);
    assert_eq!(config.devices.passthrough_devices[0].length, 0x0800_0000);
    assert_eq!(config.devices.passthrough_devices[0].irq_id, 1);
    assert_eq!(config.devices.passthrough_devices[1].name, "dev1");
    assert_eq!(config.devices.passthrough_devices[1].base_gpa, 0x0900_0000);
    assert_eq!(config.devices.passthrough_devices[1].base_hpa, 0x0900_0000);
    assert_eq!(config.devices.passthrough_devices[1].length, 0x0a00_0000);
    assert_eq!(config.devices.passthrough_devices[1].irq_id, 2);
    assert_eq!(config.devices.emu_devices.len(), 2);
    assert_eq!(config.devices.emu_devices[0].name, "dev2");
    assert_eq!(config.devices.emu_devices[0].base_gpa, 0x0800_0000);
    assert_eq!(config.devices.emu_devices[0].length, 0x1_0000);
    assert_eq!(config.devices.emu_devices[0].irq_id, 0);
    assert_eq!(
        config.devices.emu_devices[0].emu_type,
        EmulatedDeviceType::GPPTDistributor
    );
    assert_eq!(config.devices.emu_devices[1].name, "dev3");
    assert_eq!(config.devices.emu_devices[1].base_gpa, 0x0808_0000);
    assert_eq!(config.devices.emu_devices[1].length, 0x1_0000);
    assert_eq!(config.devices.emu_devices[1].irq_id, 0);
    assert_eq!(
        config.devices.emu_devices[1].emu_type,
        EmulatedDeviceType::GPPTITS
    );
    assert_eq!(config.devices.interrupt_mode, VMInterruptMode::Passthrough);
}

#[test]
fn test_emu_dev_type_from_usize() {
    for emu_dev_type in EmulatedDeviceType::enumerator() {
        let converted = EmulatedDeviceType::from_usize(emu_dev_type as usize);
        assert_eq!(
            converted, emu_dev_type,
            "Value mismatch after bidirectional conversion: {:?} -> {:?}",
            emu_dev_type, converted
        );
    }
}

#[test]
fn test_interrupt_mode_deser() {
    const EXAMPLE_DEVICE_CONFIG: &str = r#"
passthrough_devices = []
emu_devices = []
    "#;

    let device_config: VMDevicesConfig = toml::from_str(EXAMPLE_DEVICE_CONFIG).unwrap();
    assert_eq!(device_config.interrupt_mode, VMInterruptMode::default());

    fn test_deser(s: &str, expected: VMInterruptMode) {
        let config_str = format!(
            "{}{}",
            EXAMPLE_DEVICE_CONFIG,
            format!("interrupt_mode = \"{}\"", s)
        );
        let device_config: VMDevicesConfig = toml::from_str(&config_str).unwrap();
        assert_eq!(device_config.interrupt_mode, expected);
    }

    test_deser("emulated", VMInterruptMode::Emulated);
    test_deser("emu", VMInterruptMode::Emulated);
    test_deser("passthrough", VMInterruptMode::Passthrough);
    test_deser("pt", VMInterruptMode::Passthrough);
    test_deser("no_irq", VMInterruptMode::NoIrq);
    test_deser("no", VMInterruptMode::NoIrq);
    test_deser("none", VMInterruptMode::NoIrq);
}

#[test]
fn test_vmtype_enum() {
    use crate::VMType;

    assert_eq!(VMType::default(), VMType::VMTRTOS);

    assert_eq!(VMType::from(0), VMType::VMTHostVM);
    assert_eq!(VMType::from(1), VMType::VMTRTOS);
    assert_eq!(VMType::from(2), VMType::VMTLinux);
    assert_eq!(VMType::from(999), VMType::VMTRTOS);

    assert_eq!(usize::from(VMType::VMTHostVM), 0);
    assert_eq!(usize::from(VMType::VMTRTOS), 1);
    assert_eq!(usize::from(VMType::VMTLinux), 2);
}

#[test]
fn test_vm_mem_mapping_type() {
    use crate::VmMemMappingType;

    assert_eq!(VmMemMappingType::default(), VmMemMappingType::MapAlloc);

    let alloc_type = VmMemMappingType::MapAlloc;
    let identical_type = VmMemMappingType::MapIdentical;

    assert_eq!(alloc_type as u8, 0);
    assert_eq!(identical_type as u8, 1);
}

#[test]
fn test_emulated_device_type_removable() {
    use crate::EmulatedDeviceType;

    assert!(EmulatedDeviceType::InterruptController.removable());
    assert!(EmulatedDeviceType::GPPTRedistributor.removable());
    assert!(EmulatedDeviceType::VirtioBlk.removable());
    assert!(EmulatedDeviceType::VirtioNet.removable());
    assert!(EmulatedDeviceType::VirtioConsole.removable());

    assert!(!EmulatedDeviceType::Dummy.removable());
    assert!(!EmulatedDeviceType::Console.removable());
    assert!(!EmulatedDeviceType::IVCChannel.removable());
    assert!(!EmulatedDeviceType::GPPTDistributor.removable());
    assert!(!EmulatedDeviceType::GPPTITS.removable());
}

#[test]
fn test_emulated_device_type_display() {
    use alloc::format;

    use crate::EmulatedDeviceType;

    assert_eq!(format!("{}", EmulatedDeviceType::Dummy), "meta device");
    assert_eq!(
        format!("{}", EmulatedDeviceType::InterruptController),
        "interrupt controller"
    );
    assert_eq!(format!("{}", EmulatedDeviceType::Console), "console");
    assert_eq!(format!("{}", EmulatedDeviceType::IVCChannel), "ivc channel");
    assert_eq!(
        format!("{}", EmulatedDeviceType::GPPTRedistributor),
        "gic partial passthrough redistributor"
    );
    assert_eq!(
        format!("{}", EmulatedDeviceType::GPPTDistributor),
        "gic partial passthrough distributor"
    );
    assert_eq!(
        format!("{}", EmulatedDeviceType::GPPTITS),
        "gic partial passthrough its"
    );
    assert_eq!(format!("{}", EmulatedDeviceType::VirtioBlk), "virtio block");
    assert_eq!(format!("{}", EmulatedDeviceType::VirtioNet), "virtio net");
    assert_eq!(
        format!("{}", EmulatedDeviceType::VirtioConsole),
        "virtio console"
    );
}

#[test]
fn test_config_from_toml_error_handling() {
    use crate::AxVMCrateConfig;

    let invalid_toml = r#"
[base
id = "invalid"
    "#;

    let result = AxVMCrateConfig::from_toml(invalid_toml);
    assert!(result.is_err());

    let invalid_data_type = r#"
[base]
id = "not_a_number"
name = "test"
vm_type = 1
cpu_num = 1
    "#;

    let result = AxVMCrateConfig::from_toml(invalid_data_type);
    assert!(result.is_err());
}

#[test]
fn test_default_implementations() {
    use crate::*;

    assert_eq!(VMType::default(), VMType::VMTRTOS);
    assert_eq!(VmMemMappingType::default(), VmMemMappingType::MapAlloc);
    assert_eq!(EmulatedDeviceType::default(), EmulatedDeviceType::Dummy);
    assert_eq!(VMInterruptMode::default(), VMInterruptMode::NoIrq);

    let vm_mem_config = VmMemConfig::default();
    assert_eq!(vm_mem_config.gpa, 0);
    assert_eq!(vm_mem_config.size, 0);
    assert_eq!(vm_mem_config.flags, 0);
    assert_eq!(vm_mem_config.map_type, VmMemMappingType::MapAlloc);

    let emu_device_config = EmulatedDeviceConfig::default();
    assert_eq!(emu_device_config.name, "");
    assert_eq!(emu_device_config.base_gpa, 0);
    assert_eq!(emu_device_config.length, 0);
    assert_eq!(emu_device_config.irq_id, 0);
    assert_eq!(emu_device_config.emu_type, EmulatedDeviceType::Dummy);
    assert!(emu_device_config.cfg_list.is_empty());

    let passthrough_device_config = PassThroughDeviceConfig::default();
    assert_eq!(passthrough_device_config.name, "");
    assert_eq!(passthrough_device_config.base_gpa, 0);
    assert_eq!(passthrough_device_config.base_hpa, 0);
    assert_eq!(passthrough_device_config.length, 0);
    assert_eq!(passthrough_device_config.irq_id, 0);

    let vm_base_config = VMBaseConfig::default();
    assert_eq!(vm_base_config.id, 0);
    assert_eq!(vm_base_config.name, "");
    assert_eq!(vm_base_config.vm_type, 0);
    assert_eq!(vm_base_config.cpu_num, 0);
    assert!(vm_base_config.phys_cpu_ids.is_none());
    assert!(vm_base_config.phys_cpu_sets.is_none());

    let vm_kernel_config = VMKernelConfig::default();
    assert_eq!(vm_kernel_config.entry_point, 0);
    assert_eq!(vm_kernel_config.kernel_path, "");
    assert_eq!(vm_kernel_config.kernel_load_addr, 0);
    assert!(vm_kernel_config.bios_path.is_none());
    assert!(vm_kernel_config.bios_load_addr.is_none());
    assert!(vm_kernel_config.dtb_path.is_none());
    assert!(vm_kernel_config.dtb_load_addr.is_none());
    assert!(vm_kernel_config.ramdisk_path.is_none());
    assert!(vm_kernel_config.ramdisk_load_addr.is_none());
    assert!(vm_kernel_config.image_location.is_none());
    assert!(vm_kernel_config.cmdline.is_none());
    assert!(vm_kernel_config.disk_path.is_none());
    assert!(vm_kernel_config.memory_regions.is_empty());

    let vm_devices_config = VMDevicesConfig::default();
    assert!(vm_devices_config.emu_devices.is_empty());
    assert!(vm_devices_config.passthrough_devices.is_empty());
    assert_eq!(vm_devices_config.interrupt_mode, VMInterruptMode::NoIrq);

    let axvm_crate_config = AxVMCrateConfig::default();
    assert_eq!(axvm_crate_config.base.id, 0);
    assert_eq!(axvm_crate_config.kernel.entry_point, 0);
    assert!(axvm_crate_config.devices.emu_devices.is_empty());
}