axbuild 0.4.14

An OS build lib toolkit used by arceos
Documentation
use super::*;

#[test]
fn apk_curl_qemu_case_tries_cernet_before_upstream() {
    let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
    let case_dir = workspace_root.join("apps/starry/qemu/apk-curl");
    let script_path = case_dir.join("sh/apk-curl-tests.sh");
    let script = fs::read_to_string(&script_path).unwrap();

    for arch in ["aarch64", "loongarch64", "riscv64", "x86_64"] {
        let config_path = case_dir.join(format!("qemu-{arch}.toml"));
        let content = fs::read_to_string(&config_path).unwrap();
        let config: toml::Value = toml::from_str(&content).unwrap();
        let shell_init_cmd = config
            .get("shell_init_cmd")
            .and_then(toml::Value::as_str)
            .unwrap();

        assert!(
            shell_init_cmd == "/usr/bin/apk-curl-tests.sh",
            "{} must run the injected apk-curl script instead of pasting a long shell body",
            config_path.display()
        );
    }

    assert!(
        script.contains("apk --timeout \"$fetch_timeout\" add curl"),
        "{} must install curl dynamically to exercise the apk add path",
        script_path.display()
    );
    assert!(
        script.contains("mirrors.cernet.edu.cn") && script.contains("dl-cdn.alpinelinux.org"),
        "{} must provide Cernet first and upstream as a fallback",
        script_path.display()
    );
    let cernet_index = script.find("mirrors.cernet.edu.cn").unwrap();
    let upstream_index = script.find("dl-cdn.alpinelinux.org").unwrap();
    assert!(
        cernet_index < upstream_index,
        "{} must try Cernet before upstream",
        script_path.display()
    );
    assert!(
        !script.contains("mirrors.aliyun.com")
            && !script.contains("mirrors.tuna.tsinghua.edu.cn")
            && !script.contains("mirrors.ustc.edu.cn"),
        "{} must avoid mirrors that repeatedly timeout in QEMU",
        script_path.display()
    );
    assert!(
        !script.contains("__original__"),
        "{} must use explicit mirror attempts so the selected repository is diagnosable",
        script_path.display()
    );
    assert!(
        script.contains("APK_CURL_REPO_$label")
            && script.contains("APK_CURL_TEST_PASSED")
            && script.contains("APK_CURL_TEST_FAILED"),
        "{} must keep clear pass/fail diagnostics",
        script_path.display()
    );
}

#[test]
fn dhcp_qemu_case_checks_local_dhcp_state_without_external_apk_fetch() {
    let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
    let build_group_dir = workspace_root.join("apps/starry/qemu");
    let case_dir = workspace_root.join("apps/starry/qemu/dhcp");

    for (arch, target) in [
        ("aarch64", "aarch64-unknown-none-softfloat"),
        ("loongarch64", "loongarch64-unknown-none-softfloat"),
        ("riscv64", "riscv64gc-unknown-none-elf"),
        ("x86_64", "x86_64-unknown-none"),
    ] {
        let build_config_path = build_group_dir.join(format!("build-{target}.toml"));
        let build_content = fs::read_to_string(&build_config_path).unwrap();
        let build_config: toml::Value = toml::from_str(&build_content).unwrap();
        assert_eq!(
            build_config.get("target").and_then(toml::Value::as_str),
            Some(target),
            "{} must target {target}",
            build_config_path.display()
        );

        let config_path = case_dir.join(format!("qemu-{arch}.toml"));
        let content = fs::read_to_string(&config_path).unwrap();
        let config: toml::Value = toml::from_str(&content).unwrap();
        let script = config
            .get("shell_init_cmd")
            .and_then(toml::Value::as_str)
            .unwrap();

        assert!(
            !script.contains("apk update")
                && !script.contains("apk --timeout")
                && !script.contains("http://")
                && !script.contains("https://"),
            "{} must not depend on external APK repositories; DHCP is already local to the QEMU \
             user network",
            config_path.display()
        );
        for marker in [
            "DHCP_PROBE_BEGIN",
            "DHCP_ADDR_OK",
            "DHCP_RESOLVER_OK",
            "DHCP_TEST_DONE",
            "DHCP_TEST_FAILED",
        ] {
            assert!(
                script.contains(marker),
                "{} must print the diagnostic marker `{marker}`",
                config_path.display()
            );
        }
        for expected in [
            "ifconfig eth0",
            "ip addr show",
            "/etc/resolv.conf",
            "10.0.2.15",
            "10.0.2.3",
        ] {
            assert!(
                script.contains(expected),
                "{} must check `{expected}` in the local QEMU DHCP state",
                config_path.display()
            );
        }

        let fail_regex = config
            .get("fail_regex")
            .and_then(toml::Value::as_array)
            .unwrap();
        assert!(
            fail_regex
                .iter()
                .filter_map(toml::Value::as_str)
                .any(|regex| regex.contains("DHCP_TEST_FAILED")),
            "{} must fail explicitly on the DHCP probe failure marker",
            config_path.display()
        );
        let timeout = config
            .get("timeout")
            .and_then(toml::Value::as_integer)
            .unwrap_or_default();
        assert!(
            timeout <= 120,
            "{} must stay a focused local DHCP diagnostic case",
            config_path.display()
        );
    }
}

#[test]
fn dual_net_qemu_case_exercises_two_interfaces_and_parallel_fetches() {
    let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
    let case_dir = workspace_root.join("apps/starry/qemu/dual-net");
    let script_path = case_dir.join("c/dual-net-tests.sh");
    let prebuild_path = case_dir.join("c/prebuild.sh");
    let cmake_path = case_dir.join("c/CMakeLists.txt");

    assert!(
        script_path.is_file(),
        "{} must contain the guest dual-net probe",
        script_path.display()
    );
    assert!(
        prebuild_path.is_file() && cmake_path.is_file(),
        "{} must use the C pipeline so curl is installed before boot",
        case_dir.display()
    );

    for arch in ["x86_64", "aarch64", "riscv64", "loongarch64"] {
        let config_path = case_dir.join(format!("qemu-{arch}.toml"));
        assert!(
            config_path.is_file(),
            "{} must provide a QEMU runtime config",
            config_path.display()
        );

        let config: toml::Value =
            toml::from_str(&fs::read_to_string(&config_path).unwrap()).unwrap();
        let args = config
            .get("args")
            .and_then(toml::Value::as_array)
            .unwrap()
            .iter()
            .filter_map(toml::Value::as_str)
            .collect::<Vec<_>>();
        for expected in [
            "virtio-net-pci,netdev=net0",
            "virtio-net-pci,netdev=net1",
            "user,id=net0,net=10.0.2.0/24,dhcpstart=10.0.2.15",
            "user,id=net1,net=10.0.3.0/24,dhcpstart=10.0.3.15",
        ] {
            assert!(
                args.iter().any(|arg| arg.contains(expected)),
                "{} must include `{expected}`",
                config_path.display()
            );
        }
        assert_eq!(
            config.get("shell_init_cmd").and_then(toml::Value::as_str),
            Some("/usr/bin/dual-net-tests.sh")
        );
        let http = config
            .get("host_http_server")
            .and_then(toml::Value::as_table)
            .expect("dual-net case must start a host HTTP fixture");
        assert_eq!(
            http.get("port").and_then(toml::Value::as_integer),
            Some(18382)
        );
        assert!(
            http.get("body_size")
                .and_then(toml::Value::as_integer)
                .is_some_and(|size| size >= 1024 * 1024),
            "dual-net case must fetch a payload large enough to expose obvious regressions"
        );
        assert!(
            config
                .get("timeout")
                .and_then(toml::Value::as_integer)
                .is_some_and(|timeout| timeout >= 360),
            "{} must leave enough time for the apk package download stability probe",
            config_path.display()
        );
    }

    let script = fs::read_to_string(&script_path).unwrap();
    for expected in [
        "now_ms()",
        "iface_addr_contains eth0 10.0.2.15",
        "iface_addr_contains eth1 10.0.3.15",
        "curl --interface \"$iface\"",
        "fetch_with_iface eth0 10.0.2.2",
        "fetch_with_iface eth1 10.0.3.2",
        "DUAL_NET_FETCH_PARALLEL_MS",
        "apk fetch -R",
        "APK_STRESS_MIN_BYTES",
        "APK_STRESS_RETRIES",
        "DUAL_NET_RETRY",
        "apk verify",
        "sha256sum -c",
        "DUAL_NET_APK_FETCH_MS",
        "DUAL_NET_TEST_PASSED",
        "DUAL_NET_TEST_FAILED",
    ] {
        assert!(
            script.contains(expected),
            "{} must contain `{expected}`",
            script_path.display()
        );
    }

    let prebuild = fs::read_to_string(&prebuild_path).unwrap();
    assert!(
        prebuild.contains("apk add curl"),
        "{} must install curl during asset preparation, not at guest runtime",
        prebuild_path.display()
    );
}