use std::sync::Arc;
use cljrs_reader::Parser;
use cljrs_runtime::env::env::{Env, GlobalEnv};
use cljrs_runtime::env::error::{EvalError, EvalResult};
use cljrs_value::Value;
fn make_env() -> (Arc<GlobalEnv>, Env) {
let globals = cljrs_runtime::Runtime::builder()
.execution_mode(cljrs_runtime::ExecutionMode::TreeWalk)
.eager_clojure_test(true)
.build()
.expect("runtime")
.into_globals();
let env = Env::new(globals.clone(), "user");
(globals, env)
}
#[allow(clippy::result_large_err)]
fn try_eval_all(env: &mut Env, src: &str) -> EvalResult {
let mut parser = Parser::new(src.to_string(), "<test>".to_string());
let forms = parser.parse_all().expect("parse error");
let mut result = Value::Nil;
for form in forms {
result = cljrs_runtime::interp::eval::eval(&form, env)?;
}
Ok(result)
}
fn eval_all(env: &mut Env, src: &str) -> Value {
try_eval_all(env, src).expect("eval error")
}
fn assert_unbound(env: &mut Env, src: &str, sym: &str) {
match try_eval_all(env, src) {
Err(EvalError::UnboundSymbol(s)) => assert_eq!(s, sym),
other => panic!("expected {sym} to be unresolved, got {other:?}"),
}
}
#[test]
fn exclude_removes_the_core_refer() {
let (_, mut env) = make_env();
eval_all(&mut env, "(ns rc.exclude (:refer-clojure :exclude [inc]))");
assert_unbound(&mut env, "(inc 1)", "inc");
assert_eq!(eval_all(&mut env, "(dec 1)"), Value::Long(0));
assert_eq!(eval_all(&mut env, "(clojure.core/inc 1)"), Value::Long(2));
}
#[test]
fn excluded_name_can_be_redefined_locally() {
let (_, mut env) = make_env();
eval_all(
&mut env,
"(ns rc.redef (:refer-clojure :exclude [inc]))
(defn inc [x] (+ x 100))",
);
assert_eq!(eval_all(&mut env, "(inc 1)"), Value::Long(101));
assert_eq!(eval_all(&mut env, "(clojure.core/inc 1)"), Value::Long(2));
}
#[test]
fn only_refers_just_the_listed_names() {
let (_, mut env) = make_env();
eval_all(&mut env, "(ns rc.only (:refer-clojure :only [+ str]))");
assert_eq!(eval_all(&mut env, "(+ 1 2)"), Value::Long(3));
assert_eq!(
eval_all(&mut env, r#"(str "a" "b")"#),
Value::string("ab".to_string())
);
assert_unbound(&mut env, "(inc 1)", "inc");
}
#[test]
fn rename_refers_under_the_new_name_only() {
let (_, mut env) = make_env();
eval_all(
&mut env,
"(ns rc.rename (:refer-clojure :rename {inc plus1}))",
);
assert_eq!(eval_all(&mut env, "(plus1 41)"), Value::Long(42));
assert_unbound(&mut env, "(inc 1)", "inc");
assert_eq!(eval_all(&mut env, "(dec 1)"), Value::Long(0));
}
#[test]
fn exclude_and_only_combine() {
let (_, mut env) = make_env();
eval_all(
&mut env,
"(ns rc.both (:refer-clojure :only [+ inc] :exclude [inc]))",
);
assert_eq!(eval_all(&mut env, "(+ 1 2)"), Value::Long(3));
assert_unbound(&mut env, "(inc 1)", "inc");
assert_unbound(&mut env, "(dec 1)", "dec");
}
#[test]
fn require_refer_overrides_the_exclusion() {
let (_, mut env) = make_env();
eval_all(
&mut env,
"(ns rc.override
(:refer-clojure :exclude [inc])
(:require [clojure.core :refer [inc]]))",
);
assert_eq!(eval_all(&mut env, "(inc 1)"), Value::Long(2));
}
#[test]
fn reevaluating_ns_without_the_clause_restores_core() {
let (_, mut env) = make_env();
eval_all(&mut env, "(ns rc.reload (:refer-clojure :exclude [inc]))");
assert_unbound(&mut env, "(inc 1)", "inc");
eval_all(&mut env, "(ns rc.reload)");
assert_eq!(eval_all(&mut env, "(inc 1)"), Value::Long(2));
}
#[test]
fn other_namespaces_are_unaffected() {
let (_, mut env) = make_env();
eval_all(&mut env, "(ns rc.private (:refer-clojure :exclude [inc]))");
eval_all(&mut env, "(ns rc.other)");
assert_eq!(eval_all(&mut env, "(inc 1)"), Value::Long(2));
}
#[test]
fn unknown_option_is_an_error() {
let (_, mut env) = make_env();
let err = try_eval_all(&mut env, "(ns rc.bad (:refer-clojure :bogus [inc]))")
.expect_err("expected an error");
assert!(
format!("{err}").contains(":bogus"),
"unexpected error: {err}"
);
}
#[test]
fn exclude_expects_a_vector_of_symbols() {
let (_, mut env) = make_env();
let err = try_eval_all(&mut env, "(ns rc.bad2 (:refer-clojure :exclude inc))")
.expect_err("expected an error");
assert!(
format!("{err}").contains(":exclude"),
"unexpected error: {err}"
);
}
#[test]
fn explicit_refer_all_of_core_overrides_the_exclusion() {
let (_, mut env) = make_env();
eval_all(
&mut env,
"(ns rc.all
(:refer-clojure :exclude [inc])
(:require [clojure.core :refer :all]))",
);
assert_eq!(eval_all(&mut env, "(inc 1)"), Value::Long(2));
}
#[test]
fn rename_onto_an_existing_core_name_is_an_error() {
let (_, mut env) = make_env();
let err = try_eval_all(&mut env, "(ns rc.clash (:refer-clojure :rename {inc str}))")
.expect_err("expected an error");
let msg = format!("{err}");
assert!(msg.contains("inc") && msg.contains("str"), "{msg}");
}
#[test]
fn two_renames_onto_one_name_is_an_error() {
let (_, mut env) = make_env();
let err = try_eval_all(
&mut env,
"(ns rc.clash2 (:refer-clojure :rename {inc bump dec bump}))",
)
.expect_err("expected an error");
assert!(format!("{err}").contains("bump"), "{err}");
}
#[test]
fn renaming_onto_an_excluded_name_is_fine() {
let (_, mut env) = make_env();
eval_all(
&mut env,
"(ns rc.clash3 (:refer-clojure :rename {inc str} :exclude [str]))",
);
assert_eq!(eval_all(&mut env, "(str 1)"), Value::Long(2));
}
#[test]
fn only_must_name_vars_core_actually_defines() {
let (_, mut env) = make_env();
let err = try_eval_all(
&mut env,
"(ns rc.typo (:refer-clojure :only [inc no-such-fn]))",
)
.expect_err("expected an error");
let msg = format!("{err}");
assert!(msg.contains("no-such-fn"), "{msg}");
assert!(
!msg.contains("inc"),
"only the unknown name is reported: {msg}"
);
}
#[test]
fn rename_must_name_vars_core_actually_defines() {
let (_, mut env) = make_env();
let err = try_eval_all(
&mut env,
"(ns rc.typo2 (:refer-clojure :rename {no-such-fn f}))",
)
.expect_err("expected an error");
assert!(format!("{err}").contains("no-such-fn"), "{err}");
}
#[test]
fn exclude_of_an_unknown_name_is_allowed() {
let (_, mut env) = make_env();
eval_all(
&mut env,
"(ns rc.tolerant (:refer-clojure :exclude [inc no-such-fn]))",
);
assert_unbound(&mut env, "(inc 1)", "inc");
assert_eq!(eval_all(&mut env, "(dec 1)"), Value::Long(0));
}
#[test]
fn the_last_refer_clojure_clause_wins() {
let (_, mut env) = make_env();
eval_all(
&mut env,
"(ns rc.twice
(:refer-clojure :exclude [inc])
(:refer-clojure :exclude [dec]))",
);
assert_eq!(eval_all(&mut env, "(inc 1)"), Value::Long(2));
assert_unbound(&mut env, "(dec 1)", "dec");
}