hara-native 0.1.21

HAL-free native host runtime and package launcher for Hara
Documentation
use super::super::source::SourceModule;
use super::super::{analyze_modules, BuildPlan};

fn plan(entrypoint: &str) -> BuildPlan {
    BuildPlan {
        project_id: "demo-app".into(),
        project_version: "0.1.0".into(),
        profile: "production".into(),
        language: "hara".into(),
        main: "app.main".into(),
        entrypoints: vec![entrypoint.into()],
        default_entrypoint: entrypoint.into(),
        keep_vars: Vec::new(),
        keep_namespaces: Vec::new(),
        output_bundle: "target/demo-app-production.hbx".into(),
        output_report: "target/demo-app-production.shake.edn".into(),
    }
}

#[test]
fn reports_unbounded_dynamic_access_at_its_source_unit() {
    let modules = vec![SourceModule::synthetic(
        "app.main",
        "(ns app.main)\n(defn start [target] (resolve target))\n",
    )];
    let analysis = analyze_modules(&plan("app.main/start"), modules.clone()).unwrap();
    let diagnostic = analysis
        .diagnostics
        .iter()
        .find(|diagnostic| diagnostic.code == "production/unbounded-dynamic-var")
        .expect("unbounded resolve must fail production analysis");
    assert_eq!(diagnostic.location.path, "fixture:app.main");
    assert!(diagnostic.location.line >= 2);

    let mut bounded = plan("app.main/start");
    bounded.keep_vars = vec!["app.main/start".into()];
    let analysis = analyze_modules(&bounded, modules).unwrap();
    assert!(!analysis
        .diagnostics
        .iter()
        .any(|diagnostic| diagnostic.code == "production/unbounded-dynamic-var"));
}

#[test]
fn literal_dynamic_targets_become_ordinary_edges() {
    let modules = vec![
        SourceModule::synthetic("app.handlers", "(ns app.handlers)\n(defn run [] 42)\n"),
        SourceModule::synthetic(
            "app.main",
            "(ns app.main (:require [app.handlers]))\n(defn start [] (resolve 'app.handlers/run))\n",
        ),
    ];
    let analysis = analyze_modules(&plan("app.main/start"), modules).unwrap();
    assert!(analysis.runtime_closure.contains("app.handlers/run"));
    assert!(analysis.retained_vars.contains("app.handlers/run"));
}

#[test]
fn reports_dynamic_access_generated_by_a_referred_macro() {
    let modules = vec![
        SourceModule::synthetic(
            "app.macros",
            "(ns app.macros)\n(defmacro dynamic-form [target] (std.native.Base/list 'std.native.Base/resolve target))\n",
        ),
        SourceModule::synthetic(
            "app.main",
            "(ns app.main (:require [app.macros :refer-macros [dynamic-form]]))\n(defn start [target] (dynamic-form target))\n",
        ),
    ];
    let analysis = analyze_modules(&plan("app.main/start"), modules).unwrap();
    assert!(analysis.diagnostics.iter().any(|diagnostic| {
        diagnostic.code == "production/unbounded-dynamic-var" && diagnostic.module == "app.main"
    }));
}

#[test]
fn ignores_dynamic_operation_names_shadowed_by_lexical_bindings() {
    let modules = vec![SourceModule::synthetic(
        "app.main",
        "(ns app.main)\n(defn start [resolve require eval load-string]\n  (let [resolve (fn [value] value)]\n    [(resolve 1) (require 2) (eval 3) (load-string 4)\n     ((fn [resolve] (resolve 5)) resolve)]))\n",
    )];
    let analysis = analyze_modules(&plan("app.main/start"), modules).unwrap();
    assert!(analysis.succeeded(), "{:?}", analysis.diagnostics);
    assert!(!analysis.diagnostics.iter().any(|diagnostic| {
        diagnostic.code.contains("dynamic")
            || diagnostic.code.contains("eval")
            || diagnostic.code.contains("generated")
    }));
}