ax-percpu 0.4.18

Define and access per-CPU data structures
Documentation
use std::{fs, path::Path};

#[test]
fn manifests_expose_only_the_supported_dynamic_features() {
    let percpu_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let workspace_dir = workspace_dir(percpu_dir);

    assert_eq!(
        feature_names(&read(&percpu_dir.join("Cargo.toml"))),
        ["host-test"],
        "ax-percpu must expose only its host-side dynamic-area fixture"
    );
    assert_eq!(
        feature_names(&read(
            &workspace_dir.join("components/cpu-local/Cargo.toml")
        )),
        ["host-test", "tls"],
        "cpu-local must expose only register-mode and host-test capabilities"
    );
    assert!(
        !read(&workspace_dir.join("components/percpu/percpu_macros/Cargo.toml"))
            .contains("[features]"),
        "ax-percpu-macros must not carry storage-mode features"
    );
}

#[test]
fn runtime_cpu_areas_have_one_template_and_no_legacy_linker_abi() {
    let percpu_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let workspace_dir = workspace_dir(percpu_dir);

    for removed in [
        "components/percpu/percpu/src/linked_layout.rs",
        "components/percpu/percpu/src/custom/mod.rs",
        "components/percpu/percpu/src/naive.rs",
        "components/percpu/percpu/test_percpu.x",
        "components/percpu/percpu/test_percpu_custom.x",
        "components/percpu/percpu_macros/src/naive.rs",
        "platforms/someboot/src/smp/legacy.rs",
        "platforms/someboot/src/smp/prealloc.rs",
        "os/arceos/modules/axhal/axplat.lds.S",
        "virtualization/axvm/percpu-test.x",
    ] {
        assert!(
            !workspace_dir.join(removed).exists(),
            "unsupported per-CPU compatibility file remains: {removed}"
        );
    }

    for required in [
        "components/percpu/percpu/host-test.ld",
        "components/scope-local/host-test.ld",
        "os/arceos/modules/axtask/host-test.ld",
        "platforms/someboot/src/smp/layout.rs",
    ] {
        assert!(
            workspace_dir.join(required).is_file(),
            "missing dynamic-only implementation file: {required}"
        );
    }

    for linker in [
        "components/percpu/percpu/host-test.ld",
        "components/scope-local/host-test.ld",
        "os/arceos/modules/axtask/host-test.ld",
        "platforms/someboot/src/ld/data.ld",
        "platforms/axplat-dyn/link.ld",
        "os/arceos/modules/axruntime/runtime.ld",
    ] {
        let path = workspace_dir.join(linker);
        let source = read(&path);
        for forbidden in [
            concat!(".ax_", "percpu"),
            concat!("__A", "X_"),
            concat!("__ax_", "percpu"),
            concat!("_percpu_", "start"),
            concat!("_percpu_", "end"),
            concat!("_percpu_", "load"),
            concat!(".percpu_", "data"),
        ] {
            assert!(
                !source.contains(forbidden),
                "{} retains legacy linker token {forbidden}",
                path.display()
            );
        }
    }

    let template = read(&workspace_dir.join("platforms/someboot/src/ld/data.ld"));
    for section in [".percpu.template", ".percpu.init", ".percpu.align"] {
        assert!(
            template.contains(section),
            "someboot must retain the canonical {section} section"
        );
    }
    assert!(
        !template.contains("CPU_NUM"),
        "the linker must not replicate runtime CPU areas"
    );
}

#[test]
fn layout_initialization_uses_only_the_neutral_c_abi() {
    let percpu_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let workspace_dir = workspace_dir(percpu_dir);
    let provider = read(&percpu_dir.join("src/ffi.rs"));
    let consumer = read(&workspace_dir.join("platforms/someboot/src/smp/mod.rs"));

    let symbol = "__percpu_initialize_layout";
    assert!(provider.contains(symbol), "missing C ABI provider {symbol}");
    assert!(consumer.contains(symbol), "missing C ABI consumer {symbol}");

    for legacy_symbol in [
        "__percpu_initialize_layout_v2",
        "__percpu_image_register_mode_v1",
        concat!("__ax_", "percpu_initialize_layout_v2"),
        concat!("__ax_", "percpu_image_register_mode_v1"),
    ] {
        assert!(
            !provider.contains(legacy_symbol) && !consumer.contains(legacy_symbol),
            "legacy C ABI symbol remains: {legacy_symbol}"
        );
    }
}

fn workspace_dir(crate_dir: &Path) -> &Path {
    crate_dir
        .ancestors()
        .nth(3)
        .expect("ax-percpu must remain under components/percpu/percpu")
}

fn read(path: &Path) -> String {
    fs::read_to_string(path)
        .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()))
}

fn feature_names(manifest: &str) -> Vec<&str> {
    let features = manifest
        .split_once("[features]")
        .expect("manifest must contain a features table")
        .1
        .split_once("\n[")
        .map_or_else(
            || manifest.split_once("[features]").unwrap().1,
            |(table, _)| table,
        );

    let mut names = features
        .lines()
        .map(str::trim)
        .filter(|line| !line.is_empty() && !line.starts_with('#'))
        .filter_map(|line| line.split_once('=').map(|(name, _)| name.trim()))
        .collect::<Vec<_>>();
    names.sort_unstable();
    names
}