axbuild 0.1.1

An OS build lib toolkit used by arceos
Documentation
use crate::{
    axvisor::qemu_test::ShellAutoInitConfig,
    context::{starry_target_for_arch_checked, target_for_arch_checked},
};

pub(crate) const ARCEOS_TEST_PACKAGES: &[&str] = &[
    "arceos-memtest",
    "arceos-affinity",
    "arceos-irq",
    "arceos-parallel",
    "arceos-priority",
    "arceos-sleep",
    "arceos-wait-queue",
    "arceos-yield",
];

const ARCEOS_TEST_TARGETS: &[&str] = &[
    "x86_64-unknown-none",
    "riscv64gc-unknown-none-elf",
    "aarch64-unknown-none-softfloat",
    "loongarch64-unknown-none-softfloat",
];

pub(crate) const STARRY_TEST_PACKAGE: &str = "starryos-test";
const STARRY_TEST_ARCHES: &[&str] = &["x86_64", "riscv64", "aarch64", "loongarch64"];
const AXVISOR_TEST_ARCHES: &[&str] = &["aarch64", "x86_64"];
const AXVISOR_AARCH64_TEST_SHELL_PREFIX: &str = "~ #";
const AXVISOR_AARCH64_TEST_SHELL_INIT_CMD: &str = "pwd && echo 'guest test pass!'";
const AXVISOR_AARCH64_TEST_SUCCESS_REGEX: &[&str] = &["^guest test pass!$"];
const AXVISOR_X86_64_TEST_SHELL_PREFIX: &str = ">>";
const AXVISOR_X86_64_TEST_SHELL_INIT_CMD: &str = "hello_world";
const AXVISOR_X86_64_TEST_SUCCESS_REGEX: &[&str] = &["Hello world from user mode program!"];
const AXVISOR_TEST_FAIL_REGEX: &[&str] = &[
    "(?i)\\bpanic(?:ked)?\\b",
    "(?i)kernel panic",
    "(?i)login incorrect",
    "(?i)permission denied",
];

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct AxvisorUbootBoardConfig {
    pub(crate) board: &'static str,
    pub(crate) build_config: &'static str,
    pub(crate) vmconfig: &'static str,
}

const AXVISOR_UBOOT_BOARD_CONFIGS: &[AxvisorUbootBoardConfig] = &[
    AxvisorUbootBoardConfig {
        board: "phytiumpi",
        build_config: "os/axvisor/configs/board/phytiumpi.toml",
        vmconfig: "os/axvisor/configs/vms/linux-aarch64-e2000-smp1.toml",
    },
    AxvisorUbootBoardConfig {
        board: "roc-rk3568-pc",
        build_config: "os/axvisor/configs/board/roc-rk3568-pc.toml",
        vmconfig: "os/axvisor/configs/vms/linux-aarch64-rk3568-smp1.toml",
    },
];

pub(crate) fn validate_arceos_target(target: &str) -> anyhow::Result<&str> {
    validate_supported_target(target, "arceos qemu tests", "targets", ARCEOS_TEST_TARGETS)?;
    Ok(target)
}

pub(crate) fn parse_starry_test_target(target: &str) -> anyhow::Result<(&str, &'static str)> {
    parse_arch_alias_target(
        target,
        "starry qemu tests",
        STARRY_TEST_ARCHES,
        starry_target_for_arch_checked,
    )
}

pub(crate) fn parse_axvisor_test_target(target: &str) -> anyhow::Result<(&str, &'static str)> {
    parse_arch_alias_target(
        target,
        "axvisor qemu tests",
        AXVISOR_TEST_ARCHES,
        target_for_arch_checked,
    )
}

pub(crate) fn axvisor_uboot_board_config(board: &str) -> anyhow::Result<AxvisorUbootBoardConfig> {
    AXVISOR_UBOOT_BOARD_CONFIGS
        .iter()
        .copied()
        .find(|config| config.board == board)
        .ok_or_else(|| {
            anyhow!(
                "unsupported board `{}` for axvisor uboot tests. Supported boards are: {}",
                board,
                supported_board_names()
            )
        })
}

fn default_axvisor_test_success_regex() -> Vec<String> {
    owned_patterns(AXVISOR_AARCH64_TEST_SUCCESS_REGEX)
}

fn default_axvisor_test_fail_regex() -> Vec<String> {
    owned_patterns(AXVISOR_TEST_FAIL_REGEX)
}

pub(crate) fn axvisor_test_shell_config(arch: &str) -> anyhow::Result<ShellAutoInitConfig> {
    match arch {
        "aarch64" => Ok(ShellAutoInitConfig {
            shell_prefix: AXVISOR_AARCH64_TEST_SHELL_PREFIX.to_string(),
            shell_init_cmd: AXVISOR_AARCH64_TEST_SHELL_INIT_CMD.to_string(),
            success_regex: default_axvisor_test_success_regex(),
            fail_regex: default_axvisor_test_fail_regex(),
        }),
        "x86_64" => Ok(ShellAutoInitConfig {
            shell_prefix: AXVISOR_X86_64_TEST_SHELL_PREFIX.to_string(),
            shell_init_cmd: AXVISOR_X86_64_TEST_SHELL_INIT_CMD.to_string(),
            success_regex: owned_patterns(AXVISOR_X86_64_TEST_SUCCESS_REGEX),
            fail_regex: default_axvisor_test_fail_regex(),
        }),
        _ => bail!(
            "unsupported target `{arch}` for axvisor qemu tests. Supported arch values are: {}",
            AXVISOR_TEST_ARCHES.join(", ")
        ),
    }
}

fn validate_supported_target(
    target: &str,
    suite_name: &str,
    supported_kind: &str,
    supported: &[&str],
) -> anyhow::Result<()> {
    if supported.contains(&target) {
        Ok(())
    } else {
        bail!(
            "unsupported target `{}` for {}. Supported {} are: {}",
            target,
            suite_name,
            supported_kind,
            supported.join(", ")
        )
    }
}

fn validate_supported_arch_alias(
    target: &str,
    suite_name: &str,
    supported_arches: &[&str],
) -> anyhow::Result<()> {
    if target.contains('-') {
        bail!(
            "unsupported target `{target}` for {suite_name}. Pass an arch value like: {}",
            supported_arches.join(", ")
        );
    }

    validate_supported_target(target, suite_name, "arch values", supported_arches)
}

fn parse_arch_alias_target<'a>(
    target: &'a str,
    suite_name: &str,
    supported_arches: &[&str],
    resolve_target: fn(&str) -> anyhow::Result<&'static str>,
) -> anyhow::Result<(&'a str, &'static str)> {
    validate_supported_arch_alias(target, suite_name, supported_arches)?;
    Ok((target, resolve_target(target)?))
}

fn supported_board_names() -> String {
    AXVISOR_UBOOT_BOARD_CONFIGS
        .iter()
        .map(|config| config.board)
        .collect::<Vec<_>>()
        .join(", ")
}

fn owned_patterns(patterns: &[&str]) -> Vec<String> {
    patterns
        .iter()
        .map(|pattern| (*pattern).to_string())
        .collect()
}

pub(crate) fn finalize_qemu_test_run(suite_name: &str, failed: &[String]) -> anyhow::Result<()> {
    if failed.is_empty() {
        println!("all {} qemu tests passed", suite_name);
        Ok(())
    } else {
        bail!(
            "{} qemu tests failed for {} package(s): {}",
            suite_name,
            failed.len(),
            failed.join(", ")
        )
    }
}

pub(crate) fn unsupported_uboot_test_command(os: &str) -> anyhow::Result<()> {
    bail!(
        "{os} does not support `test uboot` yet; only axvisor currently implements a U-Boot test \
         suite"
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn accepts_supported_arceos_targets() {
        assert_eq!(
            validate_arceos_target("x86_64-unknown-none").unwrap(),
            "x86_64-unknown-none"
        );
        assert_eq!(
            validate_arceos_target("aarch64-unknown-none-softfloat").unwrap(),
            "aarch64-unknown-none-softfloat"
        );
    }

    #[test]
    fn rejects_unsupported_arceos_targets() {
        let err = validate_arceos_target("aarch64").unwrap_err();

        assert!(err.to_string().contains("unsupported target `aarch64`"));
    }

    #[test]
    fn parses_supported_starry_arch_aliases() {
        assert_eq!(
            parse_starry_test_target("x86_64").unwrap(),
            ("x86_64", "x86_64-unknown-none")
        );
        assert_eq!(
            parse_starry_test_target("aarch64").unwrap(),
            ("aarch64", "aarch64-unknown-none-softfloat")
        );
    }

    #[test]
    fn rejects_starry_full_target_triples() {
        let err = parse_starry_test_target("x86_64-unknown-none").unwrap_err();

        assert!(
            err.to_string()
                .contains("unsupported target `x86_64-unknown-none`")
        );
    }

    #[test]
    fn parses_supported_axvisor_arch_aliases() {
        assert_eq!(
            parse_axvisor_test_target("aarch64").unwrap(),
            ("aarch64", "aarch64-unknown-none-softfloat")
        );
        assert_eq!(
            parse_axvisor_test_target("x86_64").unwrap(),
            ("x86_64", "x86_64-unknown-none")
        );
    }

    #[test]
    fn rejects_axvisor_full_target_triples() {
        let err = parse_axvisor_test_target("aarch64-unknown-none-softfloat").unwrap_err();

        assert!(
            err.to_string().contains("Pass an arch value like: aarch64"),
            "{}",
            err
        );
    }

    #[test]
    fn rejects_unsupported_axvisor_arches() {
        let err = parse_axvisor_test_target("riscv64").unwrap_err();

        assert!(
            err.to_string()
                .contains("Supported arch values are: aarch64")
        );
    }

    #[test]
    fn parses_axvisor_uboot_board_config_for_linux_smoke() {
        assert_eq!(
            axvisor_uboot_board_config("phytiumpi").unwrap(),
            AxvisorUbootBoardConfig {
                board: "phytiumpi",
                build_config: "os/axvisor/configs/board/phytiumpi.toml",
                vmconfig: "os/axvisor/configs/vms/linux-aarch64-e2000-smp1.toml",
            }
        );
        assert_eq!(
            axvisor_uboot_board_config("roc-rk3568-pc").unwrap(),
            AxvisorUbootBoardConfig {
                board: "roc-rk3568-pc",
                build_config: "os/axvisor/configs/board/roc-rk3568-pc.toml",
                vmconfig: "os/axvisor/configs/vms/linux-aarch64-rk3568-smp1.toml",
            }
        );
    }

    #[test]
    fn rejects_unsupported_axvisor_uboot_board() {
        let err = axvisor_uboot_board_config("orangepi-5-plus").unwrap_err();

        assert!(
            err.to_string()
                .contains("unsupported board `orangepi-5-plus`")
        );
        assert!(err.to_string().contains("phytiumpi"));
        assert!(err.to_string().contains("roc-rk3568-pc"));
    }

    #[test]
    fn qemu_failure_summary_is_aggregated() {
        let err = finalize_qemu_test_run("arceos", &["pkg-b".to_string(), "pkg-c".to_string()])
            .unwrap_err();

        assert!(
            err.to_string()
                .contains("arceos qemu tests failed for 2 package(s): pkg-b, pkg-c")
        );
    }

    #[test]
    fn unsupported_uboot_error_is_explicit() {
        let err = unsupported_uboot_test_command("arceos").unwrap_err();

        assert!(
            err.to_string()
                .contains("arceos does not support `test uboot` yet")
        );
    }
}