cljrs_runtime/interp/
versioned.rs1use crate::env::env::Env;
11use crate::env::error::EvalResult;
12use cljrs_value::Symbol;
13
14pub fn resolve_versioned_symbol(sym: &Symbol, commit: &str, env: &mut Env) -> EvalResult {
17 crate::env::versioned::resolve_versioned_value(
18 &env.globals,
19 &env.current_ns,
20 sym.namespace.as_deref(),
21 &sym.name,
22 commit,
23 )
24}
25
26#[cfg(test)]
29mod tests {
30 use std::sync::Arc;
31
32 use crate::env::env::{Env, GlobalEnv};
33 use cljrs_gc::GcPtr;
34 use cljrs_value::{NativeFn, Value};
35
36 fn make_env(ns: &str) -> (Arc<GlobalEnv>, Env) {
37 let globals = crate::Runtime::builder()
38 .execution_mode(crate::ExecutionMode::TreeWalk)
39 .build()
40 .expect("runtime")
41 .into_globals();
42 globals.get_or_create_ns(ns);
43 let env = Env::new(globals.clone(), ns);
44 (globals, env)
45 }
46
47 fn fake_commit() -> &'static str {
48 "abc1234def56"
49 }
50
51 fn const_native(tag: i64) -> NativeFn {
53 NativeFn {
54 name: Arc::from("test-fn"),
55 arity: cljrs_value::Arity::Fixed(0),
56 func: Arc::new(move |_args| Ok(Value::Long(tag))),
57 }
58 }
59
60 #[test]
65 fn head_fallback_for_native_function() {
66 let (globals, mut env) = make_env("mylib");
67 let commit = "deadbeef01234";
68
69 let nf = const_native(99);
72 globals.intern(
73 "mylib",
74 Arc::from("stable-fn"),
75 Value::NativeFunction(GcPtr::new(nf)),
76 );
77
78 let sym = cljrs_value::Symbol {
79 namespace: Some(Arc::from("mylib")),
80 name: Arc::from("stable-fn"),
81 version: Some(Arc::from(commit)),
82 };
83 let result = super::resolve_versioned_symbol(&sym, commit, &mut env)
84 .expect("HEAD fallback should succeed");
85
86 assert!(matches!(result, Value::NativeFunction(_)));
87 }
88
89 #[test]
93 fn missing_symbol_gives_unbound_error() {
94 let (_globals, mut env) = make_env("mylib");
95 let commit = fake_commit();
96
97 let sym = cljrs_value::Symbol {
98 namespace: Some(Arc::from("mylib")),
99 name: Arc::from("does-not-exist"),
100 version: Some(Arc::from(commit)),
101 };
102 let err = super::resolve_versioned_symbol(&sym, commit, &mut env)
103 .expect_err("should error for unknown symbol");
104
105 assert!(
106 matches!(err, crate::env::error::EvalError::UnboundSymbol(_)),
107 "expected UnboundSymbol, got {err:?}"
108 );
109 }
110
111 fn versioned_sym(ns: &str, name: &str, commit: &str) -> cljrs_value::Symbol {
114 cljrs_value::Symbol {
115 namespace: Some(Arc::from(ns)),
116 name: Arc::from(name),
117 version: Some(Arc::from(commit)),
118 }
119 }
120
121 #[test]
124 fn matching_provenance_is_silent() {
125 let (globals, mut env) = make_env("provlib");
126 let full_commit = "deadbeef0123456789abcdef0123456789abcdef";
127
128 globals.intern(
129 "provlib",
130 Arc::from("f"),
131 Value::NativeFunction(GcPtr::new(const_native(1))),
132 );
133 globals.set_native_provenance("provlib", full_commit);
134
135 let sym = versioned_sym("provlib", "f", "deadbeef012");
137 super::resolve_versioned_symbol(&sym, "deadbeef012", &mut env).expect("should resolve");
138
139 assert!(
140 globals.provenance_warned.lock().unwrap().is_empty(),
141 "matching provenance must not warn"
142 );
143 }
144
145 #[test]
148 fn mismatched_provenance_warns_once() {
149 let (globals, mut env) = make_env("provlib2");
150 globals.intern(
151 "provlib2",
152 Arc::from("f"),
153 Value::NativeFunction(GcPtr::new(const_native(2))),
154 );
155 globals.set_native_provenance("provlib2", "1111111111111111");
156
157 let commit = "2222222222222222";
158 let sym = versioned_sym("provlib2", "f", commit);
159 let val = super::resolve_versioned_symbol(&sym, commit, &mut env)
160 .expect("mismatch still resolves to HEAD by default");
161 assert!(matches!(val, Value::NativeFunction(_)));
162
163 let warned = globals.provenance_warned.lock().unwrap();
164 assert_eq!(warned.len(), 1, "exactly one warning per pin");
165 assert!(warned.contains(&Arc::<str>::from("provlib2@2222222222222222")));
166 }
167
168 #[test]
170 fn enforce_native_versions_makes_mismatch_an_error() {
171 let (globals, mut env) = make_env("provlib3");
172 globals.intern(
173 "provlib3",
174 Arc::from("f"),
175 Value::NativeFunction(GcPtr::new(const_native(3))),
176 );
177 globals.set_native_provenance("provlib3", "1111111111111111");
178 globals.set_enforce_native_versions(true);
179
180 let commit = "2222222222222222";
181 let sym = versioned_sym("provlib3", "f", commit);
182 let err = super::resolve_versioned_symbol(&sym, commit, &mut env)
183 .expect_err("strict mode must reject a provenance mismatch");
184 let msg = format!("{err:?}");
185 assert!(
186 msg.contains("provlib3") && msg.contains("1111111111111111"),
187 "error should describe the mismatch: {msg}"
188 );
189 }
190}