#![cfg(not(target_arch = "wasm32"))]
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Arc;
use cljrs_ir::lower::lower_fn_body;
use cljrs_reader::{Form, Parser};
use cljrs_runtime::env::env::GlobalEnv;
use cljrs_runtime::tiered::{Env, ir_interp::interpret_ir};
use cljrs_value::Value;
fn git_cmd(dir: &Path, args: &[&str]) -> std::process::Output {
Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.env("GIT_AUTHOR_NAME", "Test")
.env("GIT_AUTHOR_EMAIL", "test@example.com")
.env("GIT_COMMITTER_NAME", "Test")
.env("GIT_COMMITTER_EMAIL", "test@example.com")
.env("GIT_TERMINAL_PROMPT", "0")
.output()
.expect("git command failed to start")
}
fn git_ok(dir: &Path, args: &[&str]) {
let out = git_cmd(dir, args);
assert!(
out.status.success(),
"git {args:?} failed:\n{}",
String::from_utf8_lossy(&out.stderr)
);
}
fn git_sha(dir: &Path, rev: &str) -> String {
let out = git_cmd(dir, &["rev-parse", rev]);
assert!(out.status.success(), "git rev-parse {rev} failed");
String::from_utf8(out.stdout).unwrap().trim().to_string()
}
struct LibRepo {
_dir: tempfile::TempDir,
src_dir: PathBuf,
commit_v1: String,
}
fn make_lib_repo() -> LibRepo {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path().to_path_buf();
let src_dir = root.join("src");
std::fs::create_dir_all(&src_dir).unwrap();
git_ok(&root, &["init", "-q", "-b", "main"]);
git_ok(&root, &["config", "commit.gpgsign", "false"]);
let lib_file = src_dir.join("mylib.cljrs");
std::fs::write(&lib_file, "(def the-answer 1)\n").unwrap();
git_ok(&root, &["add", "."]);
git_ok(&root, &["commit", "-q", "-m", "v1"]);
let commit_v1 = git_sha(&root, "HEAD");
std::fs::write(&lib_file, "(def the-answer 2)\n").unwrap();
git_ok(&root, &["add", "."]);
git_ok(&root, &["commit", "-q", "-m", "v2"]);
LibRepo {
_dir: dir,
src_dir,
commit_v1,
}
}
fn parse_body(src: &str) -> Vec<Form> {
let mut p = Parser::new(src.to_string(), "<test>".to_string());
p.parse_all().expect("parse")
}
fn make_globals(src_dir: &Path) -> Arc<GlobalEnv> {
cljrs_runtime::Runtime::builder()
.execution_mode(cljrs_runtime::ExecutionMode::TreeWalk)
.eager_clojure_test(true)
.source_paths(vec![src_dir.to_path_buf()])
.build()
.expect("runtime")
.into_globals()
}
fn eval_tree_walk(globals: &Arc<GlobalEnv>, src: &str) -> Value {
let mut env = Env::new(globals.clone(), "user");
let forms = parse_body(src);
let mut last = Value::Nil;
for form in &forms {
let _alloc_frame = cljrs_gc::push_alloc_frame();
last = cljrs_runtime::interp::eval::eval(form, &mut env)
.unwrap_or_else(|e| panic!("eval of {src:?} failed: {e:?}"));
}
last
}
fn run_ir(globals: &Arc<GlobalEnv>, ns: &str, body_src: &str) -> Value {
let _mutator = cljrs_gc::register_mutator();
let body = parse_body(body_src);
let ir = lower_fn_body(Some("test"), ns, &[], &body, false).expect("lower");
let mut env = Env::new(globals.clone(), ns);
let ns_arc: Arc<str> = Arc::from(ns);
cljrs_runtime::env::callback::push_eval_context(&env);
let result = interpret_ir(&ir, vec![], globals, &ns_arc, &mut env);
cljrs_runtime::env::callback::pop_eval_context();
result.unwrap_or_else(|e| panic!("IR interpret of {body_src:?} failed: {e:?}"))
}
#[test]
fn ir_resolves_pinned_symbol() {
let repo = make_lib_repo();
let globals = make_globals(&repo.src_dir);
eval_tree_walk(&globals, "(require 'mylib)");
assert_eq!(eval_tree_walk(&globals, "mylib/the-answer"), Value::Long(2));
let pinned = run_ir(
&globals,
"user",
&format!("mylib/the-answer@{}", repo.commit_v1),
);
assert_eq!(pinned, Value::Long(1), "IR must see the pinned value");
assert_eq!(eval_tree_walk(&globals, "mylib/the-answer"), Value::Long(2));
assert_eq!(run_ir(&globals, "user", "mylib/the-answer"), Value::Long(2));
}
#[test]
fn ir_in_versioned_ns_resolves_unqualified_at_commit() {
let repo = make_lib_repo();
let globals = make_globals(&repo.src_dir);
eval_tree_walk(&globals, "(require 'mylib)");
let versioned_ns = format!("mylib@{}", repo.commit_v1);
assert!(!globals.is_loaded(&versioned_ns));
let val = run_ir(&globals, &versioned_ns, "the-answer");
assert_eq!(val, Value::Long(1));
assert!(globals.is_loaded(&versioned_ns));
}
#[test]
fn ir_in_versioned_ns_resolves_qualified_self_ref_at_commit() {
let repo = make_lib_repo();
let globals = make_globals(&repo.src_dir);
eval_tree_walk(&globals, "(require 'mylib)");
let versioned_ns = format!("mylib@{}", repo.commit_v1);
let val = run_ir(&globals, &versioned_ns, "mylib/the-answer");
assert_eq!(
val,
Value::Long(1),
"qualified self-ref must see the pinned value"
);
let head = run_ir(&globals, "user", "mylib/the-answer");
assert_eq!(head, Value::Long(2));
}