axbuild 0.4.15

An OS build lib toolkit used by arceos
Documentation
use anyhow::Context;
use ostool::{board::RunBoardOptions, run::uboot::UbootConfig};

use super::{
    AXVISOR_NORMAL_GROUP, BoardTestGroup, discover_board_test_groups,
    discovery::{discover_test_group_names, discover_uboot_test_group, test_suite_root},
};
use crate::{
    axvisor::{ArgsTestBoard, ArgsTestUboot, Axvisor, build},
    context::{AxvisorCliArgs, SnapshotPersistence},
    test::{board as board_test, qemu as test_qemu},
};

impl Axvisor {
    pub(super) async fn test_uboot(&mut self, args: ArgsTestUboot) -> anyhow::Result<()> {
        let group = discover_uboot_test_group(self.app.workspace_root(), &args.board, &args.guest)?;
        let explicit_uboot_config = args.uboot_config.clone();
        let uboot_config_summary = explicit_uboot_config
            .as_ref()
            .map(|path| path.display().to_string())
            .unwrap_or_else(|| "using test-suit board config only".to_string());
        let board_test_config = group.board_test_config_path.clone();
        let board_test_config_summary = board_test_config.display().to_string();

        if let Some(path) = explicit_uboot_config.as_ref()
            && !path.exists()
        {
            anyhow::bail!(
                "missing explicit U-Boot config `{}` for axvisor board tests",
                path.display()
            );
        }

        println!(
            "running axvisor uboot test for board: {} guest: {} case: {}",
            args.board, args.guest, group.name
        );

        let request = self.prepare_request(
            axvisor_board_test_build_args(&group),
            None,
            explicit_uboot_config.clone(),
            SnapshotPersistence::Discard,
        )?;

        let cargo = build::load_cargo_config(&request)?;
        let base_uboot = match request.uboot_config.as_deref() {
            Some(_) => self.load_uboot_config(&request, &cargo).await?,
            None => Some(self.app.ensure_uboot_config_for_cargo(&cargo).await?),
        };
        let board_config = self
            .load_board_config(&cargo, Some(board_test_config.as_path()))
            .await?;
        let uboot = Some(merge_board_test_uboot_config(base_uboot, board_config));
        self.app
            .uboot(cargo, request.build_info_path, uboot)
            .await
            .with_context(|| {
                format!(
                    "axvisor uboot test failed for board `{}` guest `{}` case `{}` \
                     (build_config={}, board_test_config={}, uboot_config={})",
                    args.board,
                    args.guest,
                    group.name,
                    group.build_config.display(),
                    board_test_config_summary,
                    uboot_config_summary
                )
            })
    }

    pub(super) async fn test_board(&mut self, args: ArgsTestBoard) -> anyhow::Result<()> {
        if args.list && args.test_group.is_none() {
            let groups = discover_test_group_names(self.app.workspace_root())?
                .into_iter()
                .filter_map(|group| {
                    match discover_board_test_groups(
                        self.app.workspace_root(),
                        &group,
                        args.test_case.as_deref(),
                        args.board.as_deref(),
                    ) {
                        Ok(groups) if groups.is_empty() => None,
                        Ok(groups) => Some(Ok((group, board_test::labeled_board_cases(groups)))),
                        Err(err) => {
                            let message = err.to_string();
                            if message.starts_with("no Axvisor ") {
                                None
                            } else {
                                Some(Err(err))
                            }
                        }
                    }
                })
                .collect::<anyhow::Result<Vec<_>>>()?;
            if groups.is_empty() {
                anyhow::bail!(
                    "no Axvisor board test groups found under {}",
                    test_suite_root(self.app.workspace_root()).display()
                );
            }
            println!(
                "{}",
                test_qemu::render_labeled_case_forest("axvisor", groups)
            );
            return Ok(());
        }

        let test_group = args.test_group.as_deref().unwrap_or(AXVISOR_NORMAL_GROUP);
        let groups = discover_board_test_groups(
            self.app.workspace_root(),
            test_group,
            args.test_case.as_deref(),
            args.board.as_deref(),
        )?;
        if args.list {
            let case_names = board_test::labeled_board_cases(groups);
            println!(
                "{}",
                test_qemu::render_labeled_case_forest("axvisor", [(test_group, case_names)])
            );
            return Ok(());
        }

        let mut run_state = board_test::BoardTestRunState::new("axvisor", groups.len());
        for (index, group) in groups.into_iter().enumerate() {
            let group_label = run_state.start_group(index, &group);
            let board_test_config = group.board_test_config_path.clone();
            let board_test_config_summary = board_test_config.display().to_string();
            if !board_test_config.exists() {
                run_state.fail_group(
                    group_label,
                    anyhow::anyhow!("missing board test config `{board_test_config_summary}`"),
                );
                continue;
            }

            let result = async {
                let request = self.prepare_request(
                    axvisor_board_test_build_args(&group),
                    None,
                    None,
                    SnapshotPersistence::Discard,
                )?;
                let cargo = build::load_cargo_config(&request)?;
                let board_config = self
                    .load_board_config(&cargo, Some(board_test_config.as_path()))
                    .await?;
                self.app
                    .board(
                        cargo,
                        request.build_info_path,
                        board_config,
                        RunBoardOptions {
                            board_type: args.board_type.clone(),
                            server: args.server.clone(),
                            port: args.port,
                        },
                    )
                    .await
                    .with_context(|| {
                        format!(
                            "axvisor board test failed for group `{}` (build_config={}, \
                             board_test_config={})",
                            group_label,
                            group.build_config.display(),
                            board_test_config_summary
                        )
                    })
            }
            .await;

            match result {
                Ok(()) => run_state.pass_group(&group_label),
                Err(err) => run_state.fail_group(group_label, err),
            }
        }
        run_state.finish()
    }
}

fn merge_board_test_uboot_config(
    base: Option<UbootConfig>,
    board_test: ostool::board::config::BoardRunConfig,
) -> UbootConfig {
    let mut uboot = base.unwrap_or_default();
    let test_uboot = UbootConfig::from_board_run_config(&board_test);
    if test_uboot.dtb_file.is_some() {
        uboot.dtb_file = test_uboot.dtb_file;
    }
    if test_uboot.kernel_load_addr.is_some() {
        uboot.kernel_load_addr = test_uboot.kernel_load_addr;
    }
    if test_uboot.fit_load_addr.is_some() {
        uboot.fit_load_addr = test_uboot.fit_load_addr;
    }
    if test_uboot.bootm_addr.is_some() {
        uboot.bootm_addr = test_uboot.bootm_addr;
    }
    uboot.success_regex = test_uboot.success_regex;
    uboot.fail_regex = test_uboot.fail_regex;
    uboot.uboot_cmd = test_uboot.uboot_cmd;
    uboot.shell_prefix = test_uboot.shell_prefix;
    uboot.shell_init_cmd = test_uboot.shell_init_cmd;
    if test_uboot.timeout.is_some() {
        uboot.timeout = test_uboot.timeout;
    }
    uboot
}

fn axvisor_board_test_build_args(group: &BoardTestGroup) -> AxvisorCliArgs {
    AxvisorCliArgs {
        config: Some(group.build_config.clone()),
        arch: None,
        target: None,
        plat_dyn: None,
        smp: None,
        debug: false,
        vmconfigs: Vec::new(),
    }
}

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

    #[test]
    fn uboot_test_config_uses_board_case_matchers_and_keeps_base_local_config() {
        let base = UbootConfig {
            dtb_file: Some("${env:BOARD_DTB}".to_string()),
            success_regex: vec!["old-ok".to_string()],
            fail_regex: vec!["old-fail".to_string()],
            uboot_cmd: Some(vec!["old-boot".to_string()]),
            shell_prefix: Some("old-login:".to_string()),
            timeout: Some(300),
            local: ostool::run::uboot::LocalUbootConfig {
                serial: Some("/dev/ttyUSB1".to_string()),
                baud_rate: Some("1500000".to_string()),
                ..Default::default()
            },
            ..Default::default()
        };
        let board_test = ostool::board::config::BoardRunConfig {
            board_type: "RDK-S100".to_string(),
            success_regex: vec!["ubuntu login:".to_string()],
            fail_regex: vec!["(?i)panic".to_string()],
            uboot_cmd: Some(vec![
                "run ab_select_cmd".to_string(),
                "run avb_boot".to_string(),
            ]),
            kernel_load_addr: Some("0x200000".to_string()),
            fit_load_addr: Some("0x2000000".to_string()),
            bootm_addr: Some("0x2000000".to_string()),
            shell_prefix: Some("ubuntu login:".to_string()),
            ..Default::default()
        };

        let merged = merge_board_test_uboot_config(Some(base), board_test);

        assert_eq!(merged.success_regex, vec!["ubuntu login:"]);
        assert_eq!(merged.fail_regex, vec!["(?i)panic"]);
        assert_eq!(
            merged.uboot_cmd,
            Some(vec![
                "run ab_select_cmd".to_string(),
                "run avb_boot".to_string()
            ])
        );
        assert_eq!(merged.shell_prefix.as_deref(), Some("ubuntu login:"));
        assert_eq!(merged.dtb_file.as_deref(), Some("${env:BOARD_DTB}"));
        assert_eq!(merged.kernel_load_addr.as_deref(), Some("0x200000"));
        assert_eq!(merged.fit_load_addr.as_deref(), Some("0x2000000"));
        assert_eq!(merged.bootm_addr.as_deref(), Some("0x2000000"));
        assert_eq!(merged.timeout, Some(300));
        assert_eq!(merged.local.serial.as_deref(), Some("/dev/ttyUSB1"));
        assert_eq!(merged.local.baud_rate.as_deref(), Some("1500000"));
    }
}