hara-native 0.1.13

HAL-free native host runtime and package launcher for Hara
Documentation
use super::GeneratedNamespaceConfig;
use crate::kernel::parse_forms;

#[test]
fn rejects_removed_namespace_clause_without_intrinsics_compatibility() {
    let forms = parse_forms("(:intrinsics :all)").unwrap();
    assert!(GeneratedNamespaceConfig::configure(&forms)
        .unwrap_err()
        .contains("Unsupported ns clause: :intrinsics"));
}

#[test]
fn rejects_removed_builtins_config_option() {
    let forms = parse_forms("(:config {:builtins [+ - = count get]})").unwrap();
    assert!(GeneratedNamespaceConfig::configure(&forms)
        .unwrap_err()
        .contains("Unsupported :config option: :builtins"));
}

#[test]
fn config_role_defaults_validates_and_is_retained() {
    assert_eq!(GeneratedNamespaceConfig::defaults().role(), "standard");
    for role in ["default", "internal", "facade"] {
        let config = GeneratedNamespaceConfig::configure(
            &parse_forms(&format!("(:config {{:role :{role}}})")).unwrap(),
        )
        .unwrap();
        assert_eq!(config.role(), if role == "default" { "standard" } else { role });
    }
    assert!(GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:role \"internal\"})").unwrap()
    )
    .unwrap_err()
    .contains(":config :role expects :default, :internal, or :facade"));
    assert!(GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:role :unsupported})").unwrap()
    )
    .unwrap_err()
    .contains(":config :role expects :default, :internal, or :facade"));
}

#[test]
fn rejects_host_flavors_on_the_rust_runtime() {
    for flavor in ["jvm", "dotnet"] {
        let error = GeneratedNamespaceConfig::configure(
            &parse_forms(&format!(
                "(:flavor :{flavor} [java.lang String]) (:import vendor.numeric.Vector)"
            ))
            .unwrap(),
        )
        .unwrap_err();
        assert_eq!(
            error,
            format!(
                "native/unsupported-flavor: :{flavor} (host flavors are only available on JVM/.NET runtimes)"
            )
        );
    }
}

#[test]
fn wasm_is_not_a_host_flavor() {
    let error =
        GeneratedNamespaceConfig::configure(&parse_forms("(:flavor :wasm)").unwrap()).unwrap_err();
    assert_eq!(
        error,
        "native/unsupported-flavor: :wasm (Wasm modules use :import)"
    );
}

#[test]
fn config_override_omits_selected_foundation_vars() {
    let config = GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:override [compile pointer]})").unwrap(),
    )
    .unwrap();
    assert!(config.excluded_foundation().contains("compile"));
    assert!(config.excluded_foundation().contains("pointer"));
    assert!(GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:blank true :override [compile]})").unwrap()
    )
    .unwrap_err()
    .contains("cannot be combined"));
}

#[test]
fn config_only_selects_an_exact_foundation_surface() {
    let config = GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:only [map reduce]})").unwrap(),
    )
    .unwrap();
    let exposed = config.exposed_foundation().unwrap();
    assert!(exposed.contains("map"));
    assert!(exposed.contains("reduce"));
    assert!(GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:override [map] :only [reduce]})").unwrap()
    )
    .unwrap_err()
    .contains("cannot be combined"));
    assert!(GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:blank true :only []})").unwrap()
    )
    .unwrap_err()
    .contains("cannot be combined"));
}

#[test]
fn refer_clojure_is_not_a_hara_namespace_clause() {
    assert!(GeneratedNamespaceConfig::configure(
        &parse_forms("(:refer-clojure :exclude [compile])").unwrap()
    )
    .unwrap_err()
    .contains("Unsupported ns clause: :refer-clojure"));
}

#[test]
fn records_used_namespaces_for_runtime_referral() {
    let config = GeneratedNamespaceConfig::configure_with(
        &parse_forms("(:use code.test)").unwrap(),
        |target| target == "code.test",
    )
    .unwrap();
    assert_eq!(config.required_namespaces(), &["code.test"]);
    assert_eq!(config.used_namespaces(), &["code.test"]);
    assert!(
        GeneratedNamespaceConfig::configure(&parse_forms("(:use [code.test])").unwrap())
            .unwrap_err()
            .contains(":use expects unqualified namespace symbols")
    );
}

#[test]
fn records_lazy_alias_without_an_eager_dependency() {
    let config = GeneratedNamespaceConfig::configure_with(
        &parse_forms("(:require [code.test :as test :lazy true])").unwrap(),
        |target| target == "code.test",
    )
    .unwrap();
    assert!(config.required_namespaces().is_empty());
    assert_eq!(config.lazy_target("test"), Some("code.test"));
    assert_eq!(
        config
            .rewrite(parse_forms("test/run").unwrap().remove(0))
            .to_string(),
        "test/run"
    );
}

#[test]
fn coroutine_aliases_rewrite_to_fiber_control_forms() {
    let mut config = GeneratedNamespaceConfig::defaults();
    config.set_global_aliases([("co".to_owned(), "std.native.Coroutine".to_owned())]);
    assert_eq!(
        config
            .rewrite(parse_forms("co/yield").unwrap().remove(0))
            .to_string(),
        "std.native.Coroutine/yield"
    );
    assert_eq!(
        config
            .rewrite(parse_forms("co/await").unwrap().remove(0))
            .to_string(),
        "std.native.Coroutine/await"
    );
}

#[test]
fn global_aliases_are_source_declared_not_runtime_defaults() {
    let config = GeneratedNamespaceConfig::defaults();
    assert_eq!(config.global_alias(), None);

    let declared =
        GeneratedNamespaceConfig::configure(&parse_forms("(:config {:set-global-alias str})").unwrap())
            .unwrap();
    assert_eq!(declared.global_alias(), Some("str"));

    let rebound = GeneratedNamespaceConfig::configure_with(
        &parse_forms("(:require [demo.kernel :as kernel])").unwrap(),
        |target| target == "demo.kernel",
    )
    .unwrap();
    assert!(rebound
        .aliases()
        .contains(&("kernel".into(), "demo.kernel".into())));
}

#[test]
fn set_global_records_qualified_vars_and_rejects_unqualified_vars() {
    let config = GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:set-global [demo.global/value IColl/start-string]})").unwrap(),
    )
    .unwrap();
    assert_eq!(
        config.declared_global_imports(),
        &["demo.global/value".to_owned(), "IColl/start-string".to_owned()]
    );
    assert!(GeneratedNamespaceConfig::configure(
        &parse_forms("(:config {:set-global [value]})").unwrap()
    )
    .unwrap_err()
    .contains(":config :set-global expects qualified Vars"));
}

#[test]
fn native_aliases_are_universal_and_cannot_be_rebound() {
    let config =
        GeneratedNamespaceConfig::configure(&parse_forms("(:config {:blank true})").unwrap())
            .unwrap();
    assert_eq!(
        config
            .rewrite(parse_forms("Iter/iter-map").unwrap().remove(0))
            .to_string(),
        "std.native.Iter/iter-map"
    );
    assert_eq!(
        config
            .rewrite(parse_forms("String/encode-utf8").unwrap().remove(0))
            .to_string(),
        "std.native.String/encode-utf8"
    );
    assert_eq!(
        config
            .rewrite(parse_forms("str/encode-utf8").unwrap().remove(0))
            .to_string(),
        "str/encode-utf8"
    );
    assert_eq!(
        config
            .rewrite(parse_forms("Bytes/slice").unwrap().remove(0))
            .to_string(),
        "std.native.Bytes/slice"
    );
    assert_eq!(
        config
            .rewrite(parse_forms("bytes/slice").unwrap().remove(0))
            .to_string(),
        "bytes/slice"
    );
    assert!(GeneratedNamespaceConfig::configure(
        &parse_forms("(:require [std.native.Maths :as Iter])").unwrap()
    )
    .unwrap_err()
    .contains("Namespace alias already refers to std.native.Iter"));
}

#[test]
fn rewrites_explicit_macro_refers_only_for_macro_expansion() {
    let config = GeneratedNamespaceConfig::configure_with(
        &parse_forms("(:require [app.macros :as macros :refer [ordinary] :refer-macros [expand]])")
            .unwrap(),
        |target| target == "app.macros",
    )
    .unwrap();
    let source = parse_forms("(expand (ordinary (macros/expand 42)))")
        .unwrap()
        .remove(0);
    assert_eq!(
        config.rewrite(source.clone()).to_string(),
        "(expand (app.macros/ordinary (app.macros/expand 42)))"
    );
    assert_eq!(
        config.rewrite_for_macroexpand(source).to_string(),
        "(app.macros/expand (app.macros/ordinary (app.macros/expand 42)))"
    );
}