blue-lang-cli 0.0.220

The blue command line: run, fmt, ast, erase, check.
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use std::process::Command;

use blue_lang_mondou::def_hash::DefHash;
use blue_lang_mondou::store::{Identity, Store, DEFAULT_MAX_BYTES};

const DEP: &str = "def g(x)\n  x * 2\nend\n\ndef unrelated(x)\n  x\nend\n";
const TEST: &str = "use(\"dep\", [:g])\n\ntest \"g doubles\"\n  assert g(2) == 4\nend\n";
const APP: &str = "@moduledoc \"\"\"\napp quadruples.\n\n    quad(1) #=> 4\n\"\"\"\n\nuse(\"dep\", [:g])\n\n@doc \"\"\"\nquad: the number times four.\n\n    quad(1) #=> 4\n\"\"\"\ndef quad(n)\n  g(g(n))\nend\n";
const MAIN: &str = "use(\"dep\")\n\ndef main()\n  println(dep::g(21))\nend\n";

struct Place {
    root: PathBuf,
}

impl Place {
    fn new(name: &str) -> Self {
        let root =
            std::env::temp_dir().join(format!("blue-shared-cache-{name}-{}", std::process::id()));
        let _ = std::fs::remove_dir_all(&root);
        std::fs::create_dir_all(&root).expect("scratch dir");
        Self { root }
    }

    fn lib(&self, at: &str, dep: &str) -> PathBuf {
        let lib = self.root.join(at);
        std::fs::create_dir_all(lib.join("dep")).expect("dep dir");
        std::fs::write(
            lib.join("dep").join("Bluefile"),
            "package(\"dep\", \"0.1.0\")\n",
        )
        .expect("Bluefile");
        std::fs::write(lib.join("dep").join("dep.b"), dep).expect("dep.b");
        lib
    }

    fn file(&self, at: &str, src: &str) -> PathBuf {
        let dir = self.root.join(at);
        std::fs::create_dir_all(&dir).expect("dir");
        let f = dir.join("t.b");
        std::fs::write(&f, src).expect("t.b");
        f
    }

    fn node(&self, name: &str) -> PathBuf {
        self.root.join(name).join("blue")
    }

    fn engine_at(&self, name: &str) -> PathBuf {
        let dir = self.root.join(name);
        std::fs::create_dir_all(&dir).expect("engine dir");
        let to = dir.join("blue");
        std::fs::copy(env!("CARGO_BIN_EXE_blue"), &to).expect("copies the engine");
        to
    }

    #[allow(clippy::too_many_arguments)]
    fn blue(
        &self,
        engine: &Path,
        args: &[&str],
        file: &Path,
        lib: &Path,
        node: &str,
        shared: Option<&str>,
    ) -> (bool, String) {
        let mut c = Command::new(engine);
        c.args(args)
            .arg(file)
            .env_remove("BLUE_CONFIG")
            .env_remove("BLUE_TIER")
            .env_remove("BLUE_MEMO")
            .env_remove("BLUE_CHECK_CACHE")
            .env_remove("BLUE_CACHE_SHARED")
            .env("BLUE_TEST_CACHE", "on")
            .env("BLUE_PATH", lib)
            .env("XDG_CACHE_HOME", self.root.join(node));
        if let Some(s) = shared {
            c.env("BLUE_CACHE_SHARED", self.node(s));
        }
        let out = c.output().expect("blue runs");
        (
            out.status.success(),
            format!(
                "{}{}",
                String::from_utf8_lossy(&out.stdout),
                String::from_utf8_lossy(&out.stderr)
            ),
        )
    }

    fn check_keys(&self, node: &str) -> BTreeSet<[u8; 32]> {
        let tier = self.node(node).join("check");
        let mut keys = BTreeSet::new();
        for id_dir in std::fs::read_dir(&tier).expect("a check tier").flatten() {
            let name = id_dir.file_name().to_string_lossy().into_owned();
            let id = DefHash::from_hex(name.trim_start_matches("v1-")).expect("identity dir");
            let store = Store::open(&tier, Identity(id.0), DEFAULT_MAX_BYTES).expect("opens");
            for f in std::fs::read_dir(id_dir.path()).expect("buckets").flatten() {
                let bucket = DefHash::from_hex(&f.file_name().to_string_lossy()).expect("bucket");
                keys.extend(store.load(&bucket).entries.into_keys());
            }
        }
        keys
    }
}

impl Drop for Place {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.root);
    }
}

fn says(out: &(bool, String), tally: &str) {
    assert!(out.1.contains(tally), "expected {tally:?} in:\n{}", out.1);
}

#[test]
fn a_test_passed_on_one_node_is_cached_on_another_running_the_same_bytes_elsewhere() {
    let p = Place::new("node");
    let lib = p.lib("lib", DEP);
    let t = p.file("src", TEST);
    let here = p.engine_at("engine-a");
    let there = p.engine_at("engine-b");
    says(
        &p.blue(&here, &["test"], &t, &lib, "a", None),
        "1 test(s): 1 passed, 0 failed\n",
    );
    says(
        &p.blue(&there, &["test"], &t, &lib, "b", Some("a")),
        "1 test(s): 0 passed, 0 failed, 1 cached\n",
    );
    says(
        &p.blue(&there, &["test"], &t, &lib, "b", None),
        "1 test(s): 0 passed, 0 failed, 1 cached\n",
    );
    says(
        &p.blue(&there, &["test"], &t, &lib, "c", None),
        "1 test(s): 1 passed, 0 failed\n",
    );
}

#[test]
fn a_changed_dependency_runs_the_test_again_whatever_a_shared_root_holds() {
    let p = Place::new("dep-test");
    let lib = p.lib("lib", DEP);
    let t = p.file("src", TEST);
    let engine = PathBuf::from(env!("CARGO_BIN_EXE_blue"));
    says(
        &p.blue(&engine, &["test"], &t, &lib, "a", None),
        "1 test(s): 1 passed, 0 failed\n",
    );
    let changed = p.lib("lib2", &DEP.replace("x * 2", "x * 3"));
    let out = p.blue(&engine, &["test"], &t, &changed, "b", Some("a"));
    says(&out, "1 test(s): 0 passed, 1 failed\n");
    assert!(!out.0, "{}", out.1);
    let unrelated = p.lib("lib3", &DEP.replace("  x\nend", "  x + 1\nend"));
    says(
        &p.blue(&engine, &["test"], &t, &unrelated, "c", Some("a")),
        "1 test(s): 0 passed, 0 failed, 1 cached\n",
    );
}

#[test]
fn a_package_checked_from_two_places_keys_every_check_the_same() {
    let p = Place::new("check-place");
    let engine = PathBuf::from(env!("CARGO_BIN_EXE_blue"));
    let lib_a = p.lib("there/lib", DEP);
    let lib_b = p.lib("here/lib", DEP);
    for lib in [&lib_a, &lib_b] {
        std::fs::create_dir_all(lib.join("app")).expect("app dir");
        std::fs::write(
            lib.join("app").join("Bluefile"),
            "package(\"app\", \"0.1.0\")\nneeds(\"dep\", \"^0.1\")\n",
        )
        .expect("Bluefile");
        std::fs::write(lib.join("app").join("app.b"), APP).expect("app.b");
    }
    let file = |lib: &Path| lib.join("app").join("app.b");
    let first = p.blue(&engine, &["check"], &file(&lib_a), &lib_a, "a", None);
    assert!(first.0, "{}", first.1);
    let theirs = p.check_keys("a");
    assert!(!theirs.is_empty());
    assert!(
        p.blue(&engine, &["check"], &file(&lib_b), &lib_b, "b", None)
            .0
    );
    assert_eq!(
        p.check_keys("b"),
        theirs,
        "the same package keyed its checks differently in another place"
    );
}

#[test]
fn a_dependency_that_drops_a_name_is_checked_again_whatever_a_shared_root_holds() {
    let p = Place::new("dep-check");
    let engine = PathBuf::from(env!("CARGO_BIN_EXE_blue"));
    let lib = p.lib("lib", DEP);
    let t = p.file("src", MAIN);
    assert!(p.blue(&engine, &["check"], &t, &lib, "a", None).0);
    let renamed = p.lib("lib2", &DEP.replace("def g(x)", "def h(x)"));
    let out = p.blue(&engine, &["check"], &t, &renamed, "b", Some("a"));
    assert!(
        !out.0,
        "a check served from the shared root hid a dropped name:\n{}",
        out.1
    );
    assert!(
        out.1.contains("error[B0011]: bidama `dep` defines no `g`"),
        "{}",
        out.1
    );
    let off = p.blue(&engine, &["check", "--no-cache"], &t, &renamed, "c", None);
    assert!(!off.0);
}