axvisor 0.5.24

A lightweight type-1 hypervisor based on ArceOS
use super::*;

fn route_shortcut(mux: &GuestConsoleMux, suffix: u8) -> ConsoleInputEvent {
    assert_eq!(mux.route_host_byte(ESC).event, ConsoleInputEvent::Consumed);
    mux.route_host_byte(suffix).event
}

#[test]
fn lowest_running_vm_is_default_and_input_only_reaches_foreground() {
    let mux = GuestConsoleMux::new();
    let backend_1 = mux.core.create_serial_backend(1);
    let backend_2 = mux.core.create_serial_backend(2);

    assert_eq!(mux.attach_default([2, 1]), Some(1));
    assert_eq!(mux.route_host_byte(b'x').event, ConsoleInputEvent::Consumed);

    let mut input = [0u8; 2];
    assert_eq!(backend_1.read(&mut input), 1);
    assert_eq!(input[0], b'x');
    assert_eq!(backend_2.read(&mut input), 0);
}

#[test]
fn console_shortcuts_detach_and_cycle_running_guests() {
    let mux = GuestConsoleMux::new();
    for vm_id in [2, 7, 10] {
        mux.core.create_serial_backend(vm_id);
    }
    assert_eq!(mux.attach_default([10, 2, 7]), Some(2));

    assert_eq!(
        route_shortcut(&mux, CTRL_RIGHT_BRACKET),
        ConsoleInputEvent::Attached(7)
    );
    assert_eq!(
        route_shortcut(&mux, CTRL_RIGHT_BRACKET),
        ConsoleInputEvent::Attached(10)
    );
    assert_eq!(route_shortcut(&mux, ESC), ConsoleInputEvent::Attached(7));
    assert_eq!(route_shortcut(&mux, CTRL_H), ConsoleInputEvent::Detached(7));
    assert_eq!(mux.attached_vm(), None);
    assert_eq!(
        route_shortcut(&mux, CTRL_RIGHT_BRACKET),
        ConsoleInputEvent::Attached(10)
    );
}

#[test]
fn non_shortcut_escape_sequences_reach_the_current_console() {
    let mux = GuestConsoleMux::new();
    let backend = mux.core.create_serial_backend(7);
    assert_eq!(mux.attach_default([7]), Some(7));

    assert_eq!(route_shortcut(&mux, b'['), ConsoleInputEvent::Consumed);
    let mut input = [0u8; 2];
    assert_eq!(backend.read(&mut input), 2);
    assert_eq!(input, [ESC, b'[']);

    assert_eq!(route_shortcut(&mux, CTRL_H), ConsoleInputEvent::Detached(7));
    assert_eq!(
        route_shortcut(&mux, b'['),
        ConsoleInputEvent::ShellSequence(ESC, b'[')
    );
}

#[test]
fn stopping_foreground_guest_returns_to_shell() {
    let mux = GuestConsoleMux::new();
    mux.core.create_serial_backend(3);
    mux.attach_default([3]);

    assert_eq!(mux.set_running([]), Some(3));
    assert_eq!(mux.attached_vm(), None);
}

#[test]
fn stopped_or_removed_guest_invalidates_its_serial_backend_generation() {
    let mux = GuestConsoleMux::new();
    let backend = mux.core.create_serial_backend(4);
    assert_eq!(mux.attach_default([4]), Some(4));
    assert_eq!(mux.route_host_byte(b'x').event, ConsoleInputEvent::Consumed);

    assert!(mux.mark_stopped(4));

    let mut input = [0u8; 1];
    assert_eq!(backend.read(&mut input), 0);
    assert_eq!(
        mux.core
            .format_guest_output(4, backend.generation, b"late output"),
        None
    );

    let removed_backend = mux.core.create_serial_backend(3);
    mux.remove(3);
    removed_backend.write(b"late output");
    assert!(!mux.core.lock_state().guests.contains_key(&3));
}

#[test]
fn multiple_running_guests_receive_line_prefixes() {
    let mux = GuestConsoleMux::new();
    let backend_1 = mux.core.create_serial_backend(1);
    mux.set_running([1]);
    assert_eq!(
        mux.core
            .format_guest_output(1, backend_1.generation, b"boot\n"),
        Some(b"boot\n".to_vec())
    );

    let backend_2 = mux.core.create_serial_backend(2);
    mux.set_running([1, 2]);
    assert_eq!(
        mux.core
            .format_guest_output(1, backend_1.generation, b"ready\nprompt"),
        Some(b"[VM 1] ready\n[VM 1] prompt".to_vec())
    );
    assert_eq!(
        mux.core
            .format_guest_output(2, backend_2.generation, b"other\n"),
        Some(b"\n[VM 2] other\n".to_vec())
    );
    assert_eq!(
        mux.core
            .format_guest_output(1, backend_1.generation, b"> \n"),
        Some(b"[VM 1] > \n".to_vec())
    );
}

#[test]
fn replacement_backend_invalidates_the_previous_vm_generation() {
    let mux = GuestConsoleMux::new();
    let stale_backend = mux.core.create_serial_backend(8);
    let current_backend = mux.core.create_serial_backend(8);
    mux.attach_default([8]);
    mux.route_host_byte(b'x');

    let mut input = [0u8; 1];
    assert_eq!(stale_backend.read(&mut input), 0);
    assert_eq!(current_backend.read(&mut input), 1);
    assert_eq!(input, [b'x']);
    assert_eq!(
        mux.core
            .format_guest_output(8, stale_backend.generation, b"stale"),
        None
    );
}