axbuild 0.4.1

An OS build lib toolkit used by arceos
Documentation
use std::{
    collections::BTreeSet,
    fs,
    path::{Path, PathBuf},
};

use anyhow::{Context, bail};

use super::board;
use crate::{
    context::{arch_for_target_checked, starry_target_for_arch_checked},
    test_qemu::validate_supported_target,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum StarryTestGroup {
    Normal,
    Stress,
}

impl StarryTestGroup {
    pub(crate) fn as_str(self) -> &'static str {
        match self {
            Self::Normal => "normal",
            Self::Stress => "stress",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct StarryQemuCase {
    pub(crate) name: String,
    pub(crate) qemu_config_path: PathBuf,
}

pub(crate) fn parse_test_target(
    workspace_root: &Path,
    target: &str,
) -> anyhow::Result<(String, String)> {
    let supported_targets = board::board_default_list(workspace_root)?
        .into_iter()
        .filter(|board| board.name.starts_with("qemu-"))
        .map(|board| board.target)
        .collect::<Vec<_>>();

    let supported_target_refs = supported_targets
        .iter()
        .map(String::as_str)
        .collect::<Vec<_>>();
    let supported_arches = supported_targets
        .iter()
        .map(|target| arch_for_target_checked(target))
        .collect::<anyhow::Result<BTreeSet<_>>>()?
        .into_iter()
        .collect::<Vec<_>>();

    if target.contains('-') {
        validate_supported_target(
            target,
            "starry qemu tests",
            "targets",
            &supported_target_refs,
        )?;
        Ok((
            arch_for_target_checked(target)?.to_string(),
            target.to_string(),
        ))
    } else {
        validate_supported_target(
            target,
            "starry qemu tests",
            "arch values",
            &supported_arches,
        )?;
        Ok((
            target.to_string(),
            starry_target_for_arch_checked(target)?.to_string(),
        ))
    }
}

pub(crate) fn discover_qemu_cases(
    workspace_root: &Path,
    arch: &str,
    selected_case: Option<&str>,
    group: StarryTestGroup,
) -> anyhow::Result<Vec<StarryQemuCase>> {
    let test_suite_dir = test_suite_dir(workspace_root, group);
    let config_name = qemu_config_name(arch);

    if let Some(case_name) = selected_case {
        let case_dir = test_suite_dir.join(case_name);
        if !case_dir.is_dir() {
            bail!(
                "unknown Starry {} test case `{case_name}` in {}; available cases are discovered \
                 from direct subdirectories",
                group.as_str(),
                test_suite_dir.display()
            );
        }

        let qemu_config_path = case_dir.join(&config_name);
        if !qemu_config_path.is_file() {
            bail!(
                "Starry test case `{case_name}` does not provide `{}`",
                qemu_config_path.display()
            );
        }

        return Ok(vec![StarryQemuCase {
            name: case_name.to_string(),
            qemu_config_path,
        }]);
    }

    let mut cases = fs::read_dir(&test_suite_dir)
        .with_context(|| format!("failed to read {}", test_suite_dir.display()))?
        .filter_map(|entry| entry.ok())
        .filter_map(|entry| {
            let path = entry.path();
            if !path.is_dir() {
                return None;
            }

            let name = entry.file_name().into_string().ok()?;
            let qemu_config_path = path.join(&config_name);
            if qemu_config_path.is_file() {
                Some(StarryQemuCase {
                    name,
                    qemu_config_path,
                })
            } else {
                None
            }
        })
        .collect::<Vec<_>>();
    cases.sort_by(|left, right| left.name.cmp(&right.name));

    if cases.is_empty() {
        bail!(
            "no Starry {} qemu test cases for arch `{arch}` found under {}",
            group.as_str(),
            test_suite_dir.display()
        );
    }

    Ok(cases)
}

pub(crate) fn finalize_qemu_case_run(
    failed: &[String],
    group: StarryTestGroup,
) -> anyhow::Result<()> {
    if failed.is_empty() {
        println!("all starry {} qemu test cases passed", group.as_str());
        Ok(())
    } else {
        bail!(
            "starry {} qemu tests failed for {} case(s): {}",
            group.as_str(),
            failed.len(),
            failed.join(", ")
        )
    }
}

fn test_suite_dir(workspace_root: &Path, group: StarryTestGroup) -> PathBuf {
    workspace_root
        .join("test-suit")
        .join("starryos")
        .join(group.as_str())
}

fn qemu_config_name(arch: &str) -> String {
    format!("qemu-{arch}.toml")
}