hara-native 0.1.17

HAL-free native host runtime and package launcher for Hara
Documentation
#![cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]

use hara_wasm::direct_native::NativeEngine;
use hara_wasm::native_cli::RuntimeBroker;
use hara_wasm::project;
use hara_wasm::Runtime;
use std::path::PathBuf;

fn enable_native(runtime: &mut Runtime) {
    runtime
        .set_execution_backend("direct-native")
        .expect("direct-native backend must be available in this test build");
}

#[test]
fn native_cli_broker_evaluates_without_tracing() {
    let broker = RuntimeBroker::start_with_backend(None, false, false, false, "direct-native")
        .expect("native CLI broker must accept the native backend");
    assert_eq!(broker.eval("ROOT", "(+ 40 2)").unwrap(), "42");
}

#[test]
fn runtime_backend_executes_ordinary_hara_functions_in_the_vm() {
    let mut runtime = Runtime::core();
    assert_eq!(runtime.execution_backend(), "interpreter");
    enable_native(&mut runtime);
    assert_eq!(runtime.execution_backend(), "direct-native");
    runtime
        .eval_direct_native("(defn increment [value] (+ value 1))")
        .unwrap();
    assert_eq!(
        runtime
            .eval_direct_native("(let [value 20] (+ value 22))")
            .unwrap(),
        "42"
    );
    assert_eq!(runtime.eval_direct_native("(increment 41)").unwrap(), "42");
    let telemetry = runtime.native_execution_telemetry();
    assert!(telemetry.bytecode_functions > 0);
    assert!(telemetry.bytecode_instructions > 0);
    assert!(telemetry.native_target_calls > 0);
}

#[test]
fn direct_native_preserves_the_long_bigint_boundary() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    assert_eq!(
        runtime
            .eval_direct_native("[(std.native.Base/long? 9223372036854775807) (std.native.Base/type 9223372036854775808)]")
            .unwrap(),
        "[true :std.native.BigInteger]"
    );
    assert_eq!(
        runtime
            .eval_direct_native("(+ 9223372036854775807 1)")
            .unwrap(),
        "9223372036854775808"
    );
}

#[test]
fn direct_native_keeps_the_disj_core_intrinsic_available() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    assert_eq!(
        runtime
            .eval_direct_native("(disj #{:removed} :removed)")
            .unwrap(),
        "#{}"
    );
}

#[test]
fn direct_native_guest_protocols_dispatch_without_tree_evaluator_reentry() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    assert_eq!(
        runtime
            .eval_direct_native(
                "(do (defstruct Box [value]) \
                     (defprotocol BoxOps (read [self])) \
                     (extend-type Box BoxOps (read [self] (:value self))) \
                     (read (Box 42)))",
            )
            .unwrap(),
        "42"
    );
}

#[test]
fn bytes_new_matches_the_bytes_constructor_contract() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    assert_eq!(
        runtime
            .eval_direct_native(
                "[(Bytes/count (Bytes/new 1 2 -3 255)) (Bytes/get (Bytes/new 1 2 -3) 2)]"
            )
            .unwrap(),
        "[4 253]"
    );
    let error = runtime
        .eval_direct_native("(Bytes/new 256)")
        .expect_err("Bytes/new must reject values outside the byte range");
    assert!(
        error.contains("bytes expects a value in the range -128..255"),
        "{error}"
    );
}

#[test]
fn runtime_direct_native_loader_handles_require_inside_a_native_frame() {
    let mut runtime = Runtime::core();
    runtime.register_resource(
        "example.direct-late-dependency",
        "(ns example.direct-late-dependency) (defn increment [value] (+ value 1))",
    );
    enable_native(&mut runtime);

    runtime
        .eval_direct_native("(require [example.direct-late-dependency :as dependency :lazy true])")
        .unwrap();
    runtime
        .eval_direct_native(
            "(defn load-late [value] (require [example.direct-late-dependency]) (dependency/increment value))",
        )
        .unwrap();
    assert_eq!(runtime.eval_direct_native("(load-late 41)").unwrap(), "42");
}

#[test]
fn direct_native_preserves_multimethods_across_evaluations_and_loads() {
    let mut runtime = Runtime::core();
    runtime.register_resource(
        "example.direct-multimethod",
        "(ns example.direct-multimethod)
         (Base/multimethod (Base/current-namespace) 'classify (fn [value] value))
         (Base/method (Base/current-namespace) 'classify :ok (fn [value] 42))",
    );
    enable_native(&mut runtime);

    runtime
        .eval_direct_native(
            "(Base/multimethod (Base/current-namespace) 'local-classify (fn [value] value))",
        )
        .unwrap();
    runtime
        .eval_direct_native(
            "(Base/method (Base/current-namespace) 'local-classify :ok (fn [value] 41))",
        )
        .unwrap();
    assert_eq!(
        runtime.eval_direct_native("(local-classify :ok)").unwrap(),
        "41"
    );

    runtime
        .eval_direct_native("(require [example.direct-multimethod :as example])")
        .unwrap();
    assert_eq!(
        runtime
            .eval_direct_native("(example/classify :ok)")
            .unwrap(),
        "42"
    );
}

#[test]
fn direct_native_compiles_async_try_finally_with_recur() {
    let runtime = Runtime::core();
    runtime
        .compile_bytecode(
            "(fn [resolve reject] (try (if true resolve reject) (catch error error)))",
        )
        .expect("catch bindings must remain visible in nested functions");
    let program = runtime
        .compile_bytecode(
            "(fn ^:async [] (try (loop [] (do (std.native.Coroutine/yield 1) (recur))) (finally nil)))",
        )
        .expect("async try/finally must compile to a direct program");
    let function = &program.functions[1];
    assert!(function.async_function);
    assert!(function
        .handlers
        .iter()
        .any(|handler| handler.finally.is_some()));
    assert!(function
        .code
        .iter()
        .any(|instruction| matches!(instruction, hara_wasm::vm::Instruction::Yield)));
}

#[test]
fn direct_native_keeps_unbound_reads_catchable_at_runtime() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    assert_eq!(
        runtime
            .eval_direct_native("(try (missing-value) (catch error true))")
            .unwrap(),
        "true"
    );
}

#[test]
fn direct_native_preserves_exception_source_spans_without_marker_symbols() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    assert_eq!(
        runtime
            .eval_direct_native(
                r#"(try
  (throw
    (ex :host {:value 1} :ex/message "boom"))
  (catch error
    (let [provenance (ex-provenance error)]
      [(:ex/created-at provenance)
       (:ex/throws provenance)])))"#,
            )
            .unwrap(),
        "[{:resource nil :column 5 :namespace \"user\" :line 3} [{:resource nil :column 3 :namespace \"user\" :line 2}]]"
    );
}

#[test]
fn async_direct_native_functions_preserve_the_promise_return_shape() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    assert_eq!(
        runtime
            .eval_direct_native("(do (defn ^:async answer [] 42) (answer))")
            .unwrap(),
        "<promise>"
    );
    assert_eq!(
        runtime
            .eval_direct_native("(std.protocol.ideref.IDeref/deref (answer))")
            .unwrap(),
        "42"
    );
}

#[test]
fn direct_native_evaluates_dynamic_runtime_forms_without_falling_back() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    assert_eq!(
        runtime
            .eval_direct_native("(Runtime/eval '(+ 1 2))")
            .unwrap(),
        "3"
    );
    assert_eq!(
        runtime
            .eval_direct_native("(Runtime/load-string \"(+ 19 23)\")")
            .unwrap(),
        "42"
    );
    assert_eq!(
        runtime
            .eval_direct_native("(do (Runtime/eval '(def dynamic-value 41)) dynamic-value)")
            .unwrap(),
        "41"
    );
    assert_eq!(
        runtime
            .eval_direct_native(
                "(do
                   (Runtime/eval '(Base/multimethod (Base/current-namespace) 'dynamic-classify (fn [value] value)))
                   (Runtime/eval '(Base/method (Base/current-namespace) 'dynamic-classify :ok (fn [value] 42)))
                   (dynamic-classify :ok))",
            )
            .unwrap(),
        "42"
    );
}

#[test]
fn direct_native_eval_native_batches_macroexpansion_validation() {
    let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
    let project = project::read(&project_root).expect("core project must load");
    let catalog = project::source_catalog(&project).expect("core source catalog must load");
    let mut runtime = Runtime::core();
    runtime.register_source_catalog(&catalog);
    runtime
        .bootstrap_source_foundation()
        .expect("source Foundation must bootstrap");
    enable_native(&mut runtime);
    let source =
        "(ns example.direct-macro (:require [std.lib.collection :as collection]))
         [(try (do (macroexpand (quote (collection/select {:a 1} :a))) false) (catch error true))
          (try (do (macroexpand (quote (collection/select {} [:walk]))) false) (catch error true))
          (try (do (macroexpand (quote (collection/transform {} [:walk :a] inc))) false) (catch error true))
          (try (do (macroexpand (quote (collection/select {} [1.5]))) false) (catch error true))]";
    let result = runtime.eval_native(source);
    assert_eq!(result.unwrap(), "[true true true true]");
    assert_eq!(runtime.native_execution_telemetry().invocations, 1);
}

#[test]
fn direct_native_backend_executes_persisted_bytecode_artifacts() {
    let mut runtime = Runtime::core();
    enable_native(&mut runtime);
    let artifact = runtime
        .compile_bytecode_artifact("(+ 20 22)")
        .expect("bytecode artifact must compile");
    assert_eq!(runtime.eval_bytecode_artifact(&artifact).unwrap(), "42");
}

#[test]
fn native_engine_shares_telemetry_but_runtime_namespaces_are_isolated() {
    let engine = NativeEngine::new();
    let mut first = Runtime::with_native_engine(engine.clone());
    enable_native(&mut first);
    first
        .eval_direct_native("(defn shared [value] (+ value 1))")
        .unwrap();
    assert_eq!(
        first
            .eval_direct_native("(def isolated 41) isolated")
            .unwrap(),
        "41"
    );
    let first_telemetry = first.native_execution_telemetry();

    let mut second = Runtime::with_native_engine(engine);
    enable_native(&mut second);
    second
        .eval_direct_native("(defn shared [value] (+ value 1))")
        .unwrap();
    assert_eq!(
        second
            .eval_direct_native("(def isolated 42) isolated")
            .unwrap(),
        "42"
    );
    let after_definitions = second.native_execution_telemetry();
    assert_eq!(first.eval_direct_native("isolated").unwrap(), "41");
    let before_repeat = second.native_execution_telemetry();
    assert_eq!(
        second
            .eval_direct_native("(def isolated 42) isolated")
            .unwrap(),
        "42"
    );
    let after_repeat = second.native_execution_telemetry();
    assert!(
        after_repeat.bytecode_functions > before_repeat.bytecode_functions,
        "each native entry validates its bytecode unit"
    );
    assert!(
        after_definitions.bytecode_functions >= first_telemetry.bytecode_functions,
        "the shared engine should retain cumulative bytecode telemetry"
    );
    assert!(
        second.native_execution_telemetry().invocations > first_telemetry.invocations,
        "shared engine should record both runtime owners"
    );
}