1use std::sync::Arc;
28
29use cljrs_runtime::Runtime;
30use cljrs_runtime::env::env::GlobalEnv;
31
32#[cfg(not(target_arch = "wasm32"))]
34mod edn;
35#[cfg(not(target_arch = "wasm32"))]
36pub mod io;
37#[cfg(not(target_arch = "wasm32"))]
38pub mod process;
39mod set;
40mod string;
41const CLOJURE_TEST_SRC: &str = include_str!("clojure/test.cljrs");
44const CLOJURE_STRING_SRC: &str = include_str!("clojure/string.cljrs");
45const CLOJURE_SET_SRC: &str = include_str!("clojure/set.cljrs");
46const CLOJURE_TEMPLATE_SRC: &str = include_str!("clojure/template.cljrs");
47#[cfg(not(target_arch = "wasm32"))]
48const CLOJURE_RUST_IO_SRC: &str = include_str!("clojure/rust/io.cljrs");
49#[cfg(not(target_arch = "wasm32"))]
50const CLOJURE_EDN_SRC: &str = include_str!("clojure/edn.cljrs");
51#[cfg(not(target_arch = "wasm32"))]
52const CLOJURE_JAVA_SHELL_SRC: &str = include_str!("clojure/java/shell.cljrs");
53const CLOJURE_WALK_SRC: &str = include_str!("clojure/walk.cljrs");
54const CLOJURE_PPRINT_SRC: &str = include_str!("clojure/pprint.cljrs");
55const CLOJURE_DATA_SRC: &str = include_str!("clojure/data.cljrs");
56const COLJURE_ZIP_SRC: &str = include_str!("clojure/zip.cljrs");
57const CLOJURE_SPEC_ALPHA_SRC: &str = include_str!("clojure/spec/alpha.cljrs");
58const CLOJURE_SPEC_GEN_ALPHA_SRC: &str = include_str!("clojure/spec/gen/alpha.cljrs");
59const CLOJURE_SPEC_TEST_ALPHA_SRC: &str = include_str!("clojure/spec/test/alpha.cljrs");
60
61macro_rules! register_fns {
66 ($globals:expr, $ns:expr, [ $( ($name:expr, $arity:expr, $func:expr) ),* $(,)? ]) => {{
67 use cljrs_gc::GcPtr;
68 use cljrs_value::{NativeFn, Value};
69 let ns: &str = $ns;
70 $(
71 {
72 let nf = NativeFn::new($name, $arity, $func);
73 $globals.intern(ns, std::sync::Arc::from($name), Value::NativeFunction(GcPtr::new(nf)));
74 }
75 )*
76 }};
77}
78
79pub(crate) use register_fns;
80
81pub fn register(globals: &Arc<GlobalEnv>) {
90 string::register(globals, "clojure.string");
93 globals.register_builtin_source("clojure.string", CLOJURE_STRING_SRC);
94
95 set::register(globals, "clojure.set");
97 globals.register_builtin_source("clojure.set", CLOJURE_SET_SRC);
98
99 globals.register_builtin_source("clojure.template", CLOJURE_TEMPLATE_SRC);
101
102 globals.register_builtin_source("clojure.test", CLOJURE_TEST_SRC);
104
105 #[cfg(not(target_arch = "wasm32"))]
107 {
108 io::register(globals, "clojure.rust.io");
109 globals.register_builtin_source("clojure.rust.io", CLOJURE_RUST_IO_SRC);
110
111 edn::register(globals, "clojure.edn");
112 globals.register_builtin_source("clojure.edn", CLOJURE_EDN_SRC);
113
114 process::register(globals, "clojure.rust.process");
117 globals.register_builtin_source("clojure.java.shell", CLOJURE_JAVA_SHELL_SRC);
118 }
119
120 globals.register_builtin_source("clojure.walk", CLOJURE_WALK_SRC);
122
123 globals.register_builtin_source("clojure.data", CLOJURE_DATA_SRC);
125
126 globals.register_builtin_source("clojure.pprint", CLOJURE_PPRINT_SRC);
128
129 globals.register_builtin_source("clojure.zip", COLJURE_ZIP_SRC);
131
132 globals.register_builtin_source("clojure.spec.alpha", CLOJURE_SPEC_ALPHA_SRC);
134
135 globals.register_builtin_source("clojure.spec.gen.alpha", CLOJURE_SPEC_GEN_ALPHA_SRC);
137
138 globals.register_builtin_source("clojure.spec.test.alpha", CLOJURE_SPEC_TEST_ALPHA_SRC);
140}
141
142pub fn install(runtime: &Runtime) {
151 #[cfg(feature = "no-gc")]
152 let _reads = cljrs_gc::region::ReadScope::enter();
153 register(runtime.globals());
154}
155
156#[cfg(test)]
159mod tests {
160 use super::*;
161 use cljrs_reader::Parser;
162 use cljrs_runtime::tiered::{Env, EvalResult, eval};
163 use cljrs_runtime::{ExecutionMode, Runtime};
164 use cljrs_value::{Keyword, Value};
165
166 fn make_env() -> (Arc<GlobalEnv>, Env) {
167 let runtime = Runtime::builder()
168 .execution_mode(ExecutionMode::Tiered)
169 .build()
170 .expect("runtime");
171 install(&runtime);
172 let globals = runtime.globals().clone();
173 let env = Env::new(globals.clone(), "user");
174 (globals, env)
175 }
176
177 #[allow(clippy::result_large_err)]
178 fn run(src: &str, env: &mut Env) -> EvalResult {
179 let mut parser = Parser::new(src.to_string(), "<test>".to_string());
180 let forms = parser.parse_all().expect("parse error");
181 let mut result = Value::Nil;
182 for form in forms {
183 result = eval(&form, env)?;
184 }
185 Ok(result)
186 }
187
188 #[test]
191 fn test_string_upper_lower() {
192 let (_, mut env) = make_env();
193 run("(require '[clojure.string :as str])", &mut env).unwrap();
194 assert_eq!(
195 run("(str/upper-case \"hello\")", &mut env).unwrap(),
196 Value::string("HELLO")
197 );
198 assert_eq!(
199 run("(str/lower-case \"WORLD\")", &mut env).unwrap(),
200 Value::string("world")
201 );
202 }
203
204 #[test]
205 fn test_string_trim() {
206 let (_, mut env) = make_env();
207 run("(require '[clojure.string :as str])", &mut env).unwrap();
208 assert_eq!(
209 run("(str/trim \" hello \")", &mut env).unwrap(),
210 Value::string("hello")
211 );
212 assert_eq!(
213 run("(str/triml \" hi\")", &mut env).unwrap(),
214 Value::string("hi")
215 );
216 assert_eq!(
217 run("(str/trimr \"hi \")", &mut env).unwrap(),
218 Value::string("hi")
219 );
220 }
221
222 #[test]
223 fn test_string_predicates() {
224 let (_, mut env) = make_env();
225 run("(require '[clojure.string :as str])", &mut env).unwrap();
226 assert_eq!(
227 run("(str/blank? \" \")", &mut env).unwrap(),
228 Value::Bool(true)
229 );
230 assert_eq!(
231 run("(str/blank? \"x\")", &mut env).unwrap(),
232 Value::Bool(false)
233 );
234 assert_eq!(
235 run("(str/starts-with? \"hello\" \"hel\")", &mut env).unwrap(),
236 Value::Bool(true)
237 );
238 assert_eq!(
239 run("(str/ends-with? \"hello\" \"llo\")", &mut env).unwrap(),
240 Value::Bool(true)
241 );
242 assert_eq!(
243 run("(str/includes? \"hello\" \"ell\")", &mut env).unwrap(),
244 Value::Bool(true)
245 );
246 }
247
248 #[test]
249 fn test_string_replace() {
250 let (_, mut env) = make_env();
251 run("(require '[clojure.string :as str])", &mut env).unwrap();
252 assert_eq!(
253 run("(str/replace \"aabbcc\" \"bb\" \"XX\")", &mut env).unwrap(),
254 Value::string("aaXXcc")
255 );
256 assert_eq!(
257 run("(str/replace-first \"aabbcc\" \"a\" \"X\")", &mut env).unwrap(),
258 Value::string("Xabbcc")
259 );
260 assert_eq!(
262 run("(str/replace \"--host\" #\"^--\" \"\")", &mut env).unwrap(),
263 Value::string("host")
264 );
265 assert_eq!(
266 run("(str/replace \"aaa\" #\"a\" \"b\")", &mut env).unwrap(),
267 Value::string("bbb")
268 );
269 assert_eq!(
270 run("(str/replace-first \"aaa\" #\"a\" \"b\")", &mut env).unwrap(),
271 Value::string("baa")
272 );
273 assert_eq!(
274 run("(str/replace-first \"--host\" #\"^--\" \"\")", &mut env).unwrap(),
275 Value::string("host")
276 );
277 }
278
279 #[test]
280 fn test_string_split_join() {
281 let (_, mut env) = make_env();
282 run("(require '[clojure.string :as str])", &mut env).unwrap();
283 let v = run("(str/split \"a,b,c\" \",\")", &mut env).unwrap();
284 assert!(matches!(v, Value::Vector(_)));
285 assert_eq!(
286 run("(str/join \"-\" [\"a\" \"b\" \"c\"])", &mut env).unwrap(),
287 Value::string("a-b-c")
288 );
289 }
290
291 #[test]
292 fn test_string_join_char_elements() {
293 let (_, mut env) = make_env();
294 run("(require '[clojure.string :as str])", &mut env).unwrap();
295 assert_eq!(
297 run(r"(str/join [\8 \0])", &mut env).unwrap(),
298 Value::string("80")
299 );
300 assert_eq!(
301 run(r"(str/join \- [\8 \0])", &mut env).unwrap(),
302 Value::string("8-0")
303 );
304 assert_eq!(
306 run(r#"(str/join "-" [nil "a" nil])"#, &mut env).unwrap(),
307 Value::string("-a-")
308 );
309 }
310
311 #[test]
312 fn test_string_capitalize() {
313 let (_, mut env) = make_env();
314 run("(require '[clojure.string :as str])", &mut env).unwrap();
315 assert_eq!(
316 run("(str/capitalize \"hello world\")", &mut env).unwrap(),
317 Value::string("Hello world")
318 );
319 }
320
321 #[test]
322 fn test_string_split_lines() {
323 let (_, mut env) = make_env();
324 run("(require '[clojure.string :as str])", &mut env).unwrap();
325 for (form, expected) in [
326 ("(str/split-lines \"\")", "[\"\"]"),
327 ("(str/split-lines \"\\n\\n\")", "[]"),
328 ("(str/split-lines \"a\\nb\\nc\")", "[\"a\" \"b\" \"c\"]"),
329 ("(str/split-lines \"a\\r\\nb\")", "[\"a\" \"b\"]"),
330 ("(str/split-lines \"a\\n\\nb\")", "[\"a\" \"\" \"b\"]"),
331 ("(str/split-lines \"a\\n\\n\")", "[\"a\"]"),
332 ] {
333 let value = run(form, &mut env).unwrap();
334 assert_eq!(value.to_string(), expected, "{form}");
335 }
336 }
337
338 #[test]
339 fn test_string_split_lines_rejects_non_strings() {
340 let (_, mut env) = make_env();
341 run("(require '[clojure.string :as str])", &mut env).unwrap();
342 for form in [
343 "(str/split-lines nil)",
344 "(str/split-lines \\A)",
345 "(str/split-lines 0)",
346 "(str/split-lines 0.0)",
347 "(str/split-lines :foo)",
348 "(str/split-lines 'foo)",
349 "(str/split-lines [])",
350 ] {
351 assert!(run(form, &mut env).is_err(), "{form} should fail");
352 }
353 }
354
355 #[test]
358 fn test_set_union() {
359 let (_, mut env) = make_env();
360 run("(require '[clojure.set :as s])", &mut env).unwrap();
361 let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
362 match v {
363 Value::Set(s) => assert_eq!(s.count(), 3),
364 other => panic!("expected set, got {other:?}"),
365 }
366 }
367
368 #[test]
369 fn test_set_intersection() {
370 let (_, mut env) = make_env();
371 run("(require '[clojure.set :as s])", &mut env).unwrap();
372 let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
373 match v {
374 Value::Set(s) => assert_eq!(s.count(), 2),
375 other => panic!("expected set, got {other:?}"),
376 }
377 }
378
379 #[test]
380 fn test_set_difference() {
381 let (_, mut env) = make_env();
382 run("(require '[clojure.set :as s])", &mut env).unwrap();
383 let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
384 match v {
385 Value::Set(s) => assert_eq!(s.count(), 1),
386 other => panic!("expected set, got {other:?}"),
387 }
388 }
389
390 #[test]
391 fn test_set_subset_superset() {
392 let (_, mut env) = make_env();
393 run("(require '[clojure.set :as s])", &mut env).unwrap();
394 assert_eq!(
395 run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
396 Value::Bool(true)
397 );
398 assert_eq!(
399 run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
400 Value::Bool(true)
401 );
402 }
403
404 #[test]
405 fn test_set_map_invert() {
406 let (_, mut env) = make_env();
407 run("(require '[clojure.set :as s])", &mut env).unwrap();
408 let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
409 assert!(matches!(v, Value::Map(_)));
410 }
411
412 #[test]
415 fn test_clojure_test_lazy_load() {
416 std::thread::Builder::new()
420 .stack_size(16 * 1024 * 1024)
421 .spawn(|| {
422 let (_, mut env) = make_env();
423 run(
426 "(require '[clojure.test :refer [is deftest run-tests]])",
427 &mut env,
428 )
429 .unwrap();
430 let v = run("(is (= 1 1))", &mut env).unwrap();
431 assert_eq!(v, Value::Bool(true));
432 })
433 .unwrap()
434 .join()
435 .unwrap();
436 }
437
438 fn run_with_big_stack<F: FnOnce() + Send + 'static>(body: F) {
444 std::thread::Builder::new()
445 .stack_size(16 * 1024 * 1024)
446 .spawn(body)
447 .unwrap()
448 .join()
449 .unwrap();
450 }
451
452 #[test]
453 fn test_spec_def_and_valid_conform() {
454 run_with_big_stack(|| {
455 let (_, mut env) = make_env();
456 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
457 assert_eq!(
458 run("(s/def ::x even?)", &mut env).unwrap(),
459 Value::keyword(Keyword::qualified("user", "x"))
460 );
461 assert_eq!(
462 run("(s/valid? ::x 4)", &mut env).unwrap(),
463 Value::Bool(true)
464 );
465 assert_eq!(
466 run("(s/valid? ::x 3)", &mut env).unwrap(),
467 Value::Bool(false)
468 );
469 assert_eq!(run("(s/conform ::x 4)", &mut env).unwrap(), Value::Long(4));
470 assert_eq!(
471 run("(s/invalid? (s/conform ::x 3))", &mut env).unwrap(),
472 Value::Bool(true)
473 );
474 });
475 }
476
477 #[test]
478 fn test_spec_set_and_keyword_ref() {
479 run_with_big_stack(|| {
480 let (_, mut env) = make_env();
481 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
482 run("(s/def ::color #{:red :green})", &mut env).unwrap();
483 assert_eq!(
484 run("(s/valid? ::color :red)", &mut env).unwrap(),
485 Value::Bool(true)
486 );
487 assert_eq!(
488 run("(s/valid? ::color :blue)", &mut env).unwrap(),
489 Value::Bool(false)
490 );
491
492 run("(s/def ::x even?)", &mut env).unwrap();
494 run("(s/def ::y ::x)", &mut env).unwrap();
495 assert_eq!(
496 run("(s/valid? ::y 4)", &mut env).unwrap(),
497 Value::Bool(true)
498 );
499 assert_eq!(
500 run("(s/valid? ::y 3)", &mut env).unwrap(),
501 Value::Bool(false)
502 );
503 });
504 }
505
506 #[test]
507 fn test_spec_forward_reference() {
508 run_with_big_stack(|| {
509 let (_, mut env) = make_env();
510 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
511 run("(s/def ::a ::b)", &mut env).unwrap();
513 run("(s/def ::b string?)", &mut env).unwrap();
514 assert_eq!(
515 run(r#"(s/conform ::a "hi")"#, &mut env).unwrap(),
516 Value::string("hi")
517 );
518 assert_eq!(
519 run("(s/invalid? (s/conform ::a 5))", &mut env).unwrap(),
520 Value::Bool(true)
521 );
522 });
523 }
524
525 #[test]
526 fn test_spec_and_or() {
527 run_with_big_stack(|| {
528 let (_, mut env) = make_env();
529 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
530 assert_eq!(
531 run("(s/valid? (s/and int? even?) 4)", &mut env).unwrap(),
532 Value::Bool(true)
533 );
534 assert_eq!(
535 run("(s/valid? (s/and int? even?) 3)", &mut env).unwrap(),
536 Value::Bool(false)
537 );
538 let v = run("(s/conform (s/or :i int? :s string?) 5)", &mut env).unwrap();
539 match v {
540 Value::Vector(vec) => {
541 let items = vec.get().iter().cloned().collect::<Vec<_>>();
542 assert_eq!(items.len(), 2);
543 assert_eq!(items[0], Value::keyword(Keyword::simple("i")));
544 assert_eq!(items[1], Value::Long(5));
545 }
546 other => panic!("expected [:i 5], got {other:?}"),
547 }
548 });
549 }
550
551 #[test]
552 fn test_spec_spec_and_registry_introspection() {
553 run_with_big_stack(|| {
554 let (_, mut env) = make_env();
555 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
556 assert_eq!(run("(s/spec? even?)", &mut env).unwrap(), Value::Nil);
559 let is_spec = run("(s/spec? (s/and int? even?))", &mut env).unwrap();
560 assert!(
561 !matches!(is_spec, Value::Nil),
562 "expected a truthy spec object"
563 );
564
565 run("(s/def ::x even?)", &mut env).unwrap();
566 assert!(!matches!(
567 run("(s/get-spec ::x)", &mut env).unwrap(),
568 Value::Nil
569 ));
570 assert!(matches!(
571 run("(s/get-spec ::does-not-exist)", &mut env).unwrap(),
572 Value::Nil
573 ));
574
575 run("(s/def ::pos-even (s/and int? even? pos?))", &mut env).unwrap();
576 let form_v = run("(s/form ::pos-even)", &mut env).unwrap();
577 assert_eq!(format!("{form_v}"), "(and int? even? pos?)");
578 let describe_v = run("(s/describe ::pos-even)", &mut env).unwrap();
579 assert_eq!(format!("{describe_v}"), "(and int? even? pos?)");
580 });
581 }
582
583 #[test]
584 fn test_spec_conform_unregistered_keyword_throws() {
585 run_with_big_stack(|| {
586 let (_, mut env) = make_env();
587 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
588 let err = run("(s/conform ::does-not-exist 1)", &mut env).unwrap_err();
589 let msg = format!("{err}");
590 assert!(
591 msg.contains("Unable to resolve spec"),
592 "expected 'Unable to resolve spec' in error, got: {msg}"
593 );
594 });
595 }
596
597 #[track_caller]
601 fn assert_bool(src: &str, expected: bool, env: &mut Env) {
602 assert_eq!(
603 run(src, env).unwrap(),
604 Value::Bool(expected),
605 "expression: {src}"
606 );
607 }
608
609 #[test]
610 fn test_spec_keys_req_opt() {
611 run_with_big_stack(|| {
612 let (_, mut env) = make_env();
613 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
614 run("(s/def ::a int?) (s/def ::b string?)", &mut env).unwrap();
615 run("(s/def ::m (s/keys :req [::a] :opt [::b]))", &mut env).unwrap();
616 assert_bool("(s/valid? ::m {::a 1})", true, &mut env);
617 assert_bool("(s/valid? ::m {::a 1 ::b \"x\"})", true, &mut env);
618 assert_bool("(s/valid? ::m {::b \"x\"})", false, &mut env); assert_bool("(s/valid? ::m {::a \"no\"})", false, &mut env); assert_bool("(s/valid? ::m {::a 1 ::b 2})", false, &mut env); assert_bool("(s/valid? ::m 42)", false, &mut env); assert_bool(
624 "(s/valid? (s/keys :req [::a]) {::a 1 ::b :not-a-string})",
625 false,
626 &mut env,
627 );
628 run("(s/def ::c boolean?)", &mut env).unwrap();
630 run(
631 "(s/def ::conn (s/keys :req [(or ::a (and ::b ::c))]))",
632 &mut env,
633 )
634 .unwrap();
635 assert_bool("(s/valid? ::conn {::a 1})", true, &mut env);
636 assert_bool("(s/valid? ::conn {::b \"x\" ::c true})", true, &mut env);
637 assert_bool("(s/valid? ::conn {::b \"x\"})", false, &mut env);
638 assert_bool("(s/valid? ::conn {})", false, &mut env);
639 });
640 }
641
642 #[test]
643 fn test_spec_keys_un_variants() {
644 run_with_big_stack(|| {
645 let (_, mut env) = make_env();
646 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
647 run("(s/def ::a int?) (s/def ::b string?)", &mut env).unwrap();
648 run(
649 "(s/def ::mu (s/keys :req-un [::a] :opt-un [::b]))",
650 &mut env,
651 )
652 .unwrap();
653 assert_bool("(s/valid? ::mu {:a 1})", true, &mut env);
654 assert_bool("(s/valid? ::mu {:a 1 :b \"x\"})", true, &mut env);
655 assert_bool("(s/valid? ::mu {:b \"x\"})", false, &mut env); assert_bool("(s/valid? ::mu {:a \"no\"})", false, &mut env); assert_bool("(s/valid? ::mu {:a 1 :b 2})", false, &mut env); assert_bool("(= {:a 1} (s/conform ::mu {:a 1}))", true, &mut env);
659 });
660 }
661
662 #[test]
663 fn test_spec_keys_record() {
664 run_with_big_stack(|| {
665 let (_, mut env) = make_env();
666 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
667 run("(defrecord Point [x y])", &mut env).unwrap();
668 run("(s/def ::x number?) (s/def ::y number?)", &mut env).unwrap();
669 run("(s/def ::point (s/keys :req-un [::x ::y]))", &mut env).unwrap();
670 assert_bool("(s/valid? ::point (->Point 1 2))", true, &mut env);
671 assert_bool("(s/valid? ::point (->Point \"a\" 2))", false, &mut env);
672 assert_bool(
674 "(record? (s/conform ::point (->Point 1 2)))",
675 true,
676 &mut env,
677 );
678 });
679 }
680
681 #[test]
682 fn test_spec_explain_data_shape() {
683 run_with_big_stack(|| {
684 let (_, mut env) = make_env();
685 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
686 run("(s/def ::a int?) (s/def ::b string?)", &mut env).unwrap();
687 run("(s/def ::m (s/keys :req [::a] :opt [::b]))", &mut env).unwrap();
688
689 run("(def ed1 (s/explain-data ::m {::b \"x\"}))", &mut env).unwrap();
691 assert_bool(
692 "(pos? (count (:clojure.spec.alpha/problems ed1)))",
693 true,
694 &mut env,
695 );
696 let pred = run(
697 "(pr-str (:pred (first (:clojure.spec.alpha/problems ed1))))",
698 &mut env,
699 )
700 .unwrap();
701 assert_eq!(pred, Value::string("(contains? % :user/a)"));
702 assert_bool(
703 "(= [] (:path (first (:clojure.spec.alpha/problems ed1))))",
704 true,
705 &mut env,
706 );
707
708 run("(def ed2 (s/explain-data ::m {::a \"no\"}))", &mut env).unwrap();
711 run(
712 "(def p2 (first (:clojure.spec.alpha/problems ed2)))",
713 &mut env,
714 )
715 .unwrap();
716 assert_bool("(= [::a] (:path p2))", true, &mut env);
717 assert_bool("(= [::a] (:in p2))", true, &mut env);
718 assert_bool("(= [::m ::a] (:via p2))", true, &mut env);
719 assert_bool("(= \"no\" (:val p2))", true, &mut env);
720
721 assert_bool("(nil? (s/explain-data ::m {::a 4}))", true, &mut env);
723
724 assert_bool(
726 "(= {::a \"no\"} (:clojure.spec.alpha/value ed2))",
727 true,
728 &mut env,
729 );
730 });
731 }
732
733 #[test]
734 fn test_spec_merge() {
735 run_with_big_stack(|| {
736 let (_, mut env) = make_env();
737 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
738 run("(s/def ::a int?) (s/def ::b string?)", &mut env).unwrap();
739 run("(s/def ::ma (s/keys :req [::a]))", &mut env).unwrap();
740 run("(s/def ::mb (s/keys :req [::b]))", &mut env).unwrap();
741 run("(s/def ::mab (s/merge ::ma ::mb))", &mut env).unwrap();
742 assert_bool("(s/valid? ::mab {::a 1 ::b \"x\"})", true, &mut env);
743 assert_bool("(s/valid? ::mab {::a 1})", false, &mut env);
744 assert_bool(
745 "(= {::a 1 ::b \"x\"} (s/conform ::mab {::a 1 ::b \"x\"}))",
746 true,
747 &mut env,
748 );
749 assert_bool(
750 "(pos? (count (:clojure.spec.alpha/problems
751 (s/explain-data ::mab {::a 1}))))",
752 true,
753 &mut env,
754 );
755 });
756 }
757
758 #[test]
759 fn test_spec_explain_str_and_printer() {
760 run_with_big_stack(|| {
761 let (_, mut env) = make_env();
762 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
763 run("(s/def ::a int?)", &mut env).unwrap();
764 run("(s/def ::m (s/keys :req [::a]))", &mut env).unwrap();
765 let estr = match run("(s/explain-str ::m {::a \"no\"})", &mut env).unwrap() {
766 Value::Str(s) => s.get().clone(),
767 other => panic!("expected string from explain-str, got {other:?}"),
768 };
769 assert!(
770 estr.contains("failed") && estr.contains("int?"),
771 "unexpected explain-str output: {estr}"
772 );
773 let ok = run("(s/explain-str ::m {::a 1})", &mut env).unwrap();
774 assert_eq!(ok, Value::string("Success!\n"));
775 });
776 }
777
778 #[test]
779 fn test_spec_keys_unform_roundtrip() {
780 run_with_big_stack(|| {
781 let (_, mut env) = make_env();
782 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
783 run("(s/def ::id (s/or :i int? :s string?))", &mut env).unwrap();
784 run("(s/def ::rm (s/keys :req-un [::id]))", &mut env).unwrap();
785 assert_bool("(= {:id [:i 5]} (s/conform ::rm {:id 5}))", true, &mut env);
787 assert_bool(
788 "(= {:id 5} (s/unform ::rm (s/conform ::rm {:id 5})))",
789 true,
790 &mut env,
791 );
792 });
793 }
794
795 #[test]
798 fn test_spec_regex_cat_and_nesting() {
799 run_with_big_stack(|| {
800 let (_, mut env) = make_env();
801 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
802 assert_bool(
803 "(= {:a 1 :b \"x\"} (s/conform (s/cat :a int? :b string?) [1 \"x\"]))",
804 true,
805 &mut env,
806 );
807 assert_bool(
809 "(= {:a 1 :b [\"x\" \"y\"]}
810 (s/conform (s/cat :a int? :b (s/* string?)) [1 \"x\" \"y\"]))",
811 true,
812 &mut env,
813 );
814 assert_bool(
816 "(s/invalid? (s/conform (s/cat :a int? :b string?) [1 2]))",
817 true,
818 &mut env,
819 );
820 assert_bool(
821 "(s/invalid? (s/conform (s/cat :a int? :b string?) [1]))",
822 true,
823 &mut env,
824 );
825 assert_bool(
826 "(s/invalid? (s/conform (s/cat :a int?) [1 2]))",
827 true,
828 &mut env,
829 );
830 assert_bool("(s/invalid? (s/conform (s/cat :a int?) 5))", true, &mut env);
832 });
833 }
834
835 #[test]
836 fn test_spec_regex_alt_star_plus_maybe() {
837 run_with_big_stack(|| {
838 let (_, mut env) = make_env();
839 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
840 assert_bool(
842 "(= [:i 5] (s/conform (s/alt :i int? :s string?) [5]))",
843 true,
844 &mut env,
845 );
846 assert_bool(
847 "(s/invalid? (s/conform (s/alt :i int? :s string?) [:kw]))",
848 true,
849 &mut env,
850 );
851 assert_bool("(= [] (s/conform (s/* int?) []))", true, &mut env);
853 assert_bool("(s/valid? (s/* int?) [])", true, &mut env);
854 assert_bool("(= [1 2 3] (s/conform (s/* int?) [1 2 3]))", true, &mut env);
855 assert_bool(
856 "(s/invalid? (s/conform (s/* int?) [1 :a 3]))",
857 true,
858 &mut env,
859 );
860 assert_bool("(= [1] (s/conform (s/+ int?) [1]))", true, &mut env);
862 assert_bool("(s/invalid? (s/conform (s/+ int?) []))", true, &mut env);
863 assert_bool("(= 5 (s/conform (s/? int?) [5]))", true, &mut env);
865 assert_bool("(nil? (s/conform (s/? int?) []))", true, &mut env);
866 assert_bool("(s/valid? (s/? int?) [])", true, &mut env);
867 assert_bool("(s/invalid? (s/conform (s/? int?) [1 2]))", true, &mut env);
868 });
869 }
870
871 #[test]
872 fn test_spec_regex_amp_and_regex_inside_and() {
873 run_with_big_stack(|| {
874 let (_, mut env) = make_env();
875 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
876 run("(def even-count? (fn [xs] (even? (count xs))))", &mut env).unwrap();
877 assert_bool(
879 "(= [1 2] (s/conform (s/& (s/* int?) even-count?) [1 2]))",
880 true,
881 &mut env,
882 );
883 assert_bool(
884 "(s/invalid? (s/conform (s/& (s/* int?) even-count?) [1 2 3]))",
885 true,
886 &mut env,
887 );
888 run("(def all-even? (fn [xs] (every? even? xs)))", &mut env).unwrap();
891 assert_bool(
892 "(= [2 4] (s/conform (s/and (s/* int?) all-even?) [2 4]))",
893 true,
894 &mut env,
895 );
896 assert_bool(
897 "(s/valid? (s/and (s/* int?) all-even?) [1 2])",
898 false,
899 &mut env,
900 );
901 });
902 }
903
904 #[test]
905 fn test_spec_regex_keyword_ref_children() {
906 run_with_big_stack(|| {
907 let (_, mut env) = make_env();
908 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
909 run("(s/def ::a int?)", &mut env).unwrap();
911 assert_bool(
912 "(= {:x 1 :y \"x\"} (s/conform (s/cat :x ::a :y string?) [1 \"x\"]))",
913 true,
914 &mut env,
915 );
916 run("(s/def ::r (s/* int?))", &mut env).unwrap();
919 assert_bool(
920 "(= {:nums [1 2] :tail \"x\"}
921 (s/conform (s/cat :nums ::r :tail string?) [1 2 \"x\"]))",
922 true,
923 &mut env,
924 );
925 assert_bool(
927 "(= {:nums [1 2] :tail \"x\"}
928 (s/conform (s/cat :nums (s/spec (s/* int?)) :tail string?)
929 [[1 2] \"x\"]))",
930 true,
931 &mut env,
932 );
933 assert_bool("(= [1 2 3] (s/conform ::r [1 2 3]))", true, &mut env);
935 });
936 }
937
938 #[test]
939 fn test_spec_regex_explain_reasons() {
940 run_with_big_stack(|| {
941 let (_, mut env) = make_env();
942 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
943 run(
945 "(def p-insuff (first (:clojure.spec.alpha/problems
946 (s/explain-data (s/cat :a int? :b string?) [1]))))",
947 &mut env,
948 )
949 .unwrap();
950 assert_bool(
951 "(= \"Insufficient input\" (:reason p-insuff))",
952 true,
953 &mut env,
954 );
955 assert_bool("(= [:b] (:path p-insuff))", true, &mut env);
956 run(
958 "(def p-extra (first (:clojure.spec.alpha/problems
959 (s/explain-data (s/cat :a int?) [1 2]))))",
960 &mut env,
961 )
962 .unwrap();
963 assert_bool("(= \"Extra input\" (:reason p-extra))", true, &mut env);
964 assert_bool("(= [1] (:in p-extra))", true, &mut env);
965 run(
967 "(def p-elem (first (:clojure.spec.alpha/problems
968 (s/explain-data (s/cat :a int? :b string?) [1 :bad]))))",
969 &mut env,
970 )
971 .unwrap();
972 assert_bool("(= [:b] (:path p-elem))", true, &mut env);
973 assert_bool("(= [1] (:in p-elem))", true, &mut env);
974 assert_bool("(= :bad (:val p-elem))", true, &mut env);
975 });
976 }
977
978 #[test]
979 fn test_spec_regex_unform_roundtrips() {
980 run_with_big_stack(|| {
981 let (_, mut env) = make_env();
982 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
983 run("(def even-count? (fn [xs] (even? (count xs))))", &mut env).unwrap();
984 for (spec, input) in [
985 ("(s/cat :a int? :b string?)", "[1 \"x\"]"),
986 ("(s/cat :a int? :b (s/* string?))", "[1 \"x\" \"y\"]"),
987 ("(s/alt :i int? :s string?)", "[5]"),
988 ("(s/* int?)", "[1 2 3]"),
989 ("(s/* int?)", "[]"),
990 ("(s/+ int?)", "[1 2]"),
991 ("(s/? int?)", "[5]"),
992 ("(s/? int?)", "[]"),
993 ("(s/& (s/* int?) even-count?)", "[1 2]"),
994 ] {
995 assert_bool(
996 &format!(
997 "(let [re {spec}] (= {input} (vec (s/unform re (s/conform re {input})))))"
998 ),
999 true,
1000 &mut env,
1001 );
1002 }
1003 });
1004 }
1005
1006 #[test]
1007 fn test_spec_plain_map_is_not_a_spec() {
1008 run_with_big_stack(|| {
1009 let (_, mut env) = make_env();
1010 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1011 let err = run("(s/conform {:a 1} 5)", &mut env).unwrap_err();
1012 let msg = format!("{err}");
1013 assert!(
1014 msg.contains("not a valid spec"),
1015 "expected 'not a valid spec' in error, got: {msg}"
1016 );
1017 });
1018 }
1019
1020 #[test]
1023 fn test_spec_coll_of_options() {
1024 run_with_big_stack(|| {
1025 let (_, mut env) = make_env();
1026 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1027 assert_bool(
1029 "(= [1 2 3] (s/conform (s/coll-of int?) [1 2 3]))",
1030 true,
1031 &mut env,
1032 );
1033 assert_bool(
1034 "(s/invalid? (s/conform (s/coll-of int?) [1 \"x\"]))",
1035 true,
1036 &mut env,
1037 );
1038 assert_bool(
1040 "(s/invalid? (s/conform (s/coll-of int? :kind vector?) '(1 2 3)))",
1041 true,
1042 &mut env,
1043 );
1044 assert_bool(
1046 "(s/invalid? (s/conform (s/coll-of int? :min-count 3) [1 2]))",
1047 true,
1048 &mut env,
1049 );
1050 assert_bool(
1051 "(s/invalid? (s/conform (s/coll-of int? :max-count 2) [1 2 3]))",
1052 true,
1053 &mut env,
1054 );
1055 assert_bool(
1057 "(= [1 2 3] (s/conform (s/coll-of int? :distinct true) [1 2 3]))",
1058 true,
1059 &mut env,
1060 );
1061 assert_bool(
1062 "(s/invalid? (s/conform (s/coll-of int? :distinct true) [1 1 2]))",
1063 true,
1064 &mut env,
1065 );
1066 assert_bool(
1068 "(= #{1 2 3} (s/conform (s/coll-of int? :into #{}) [1 2 3]))",
1069 true,
1070 &mut env,
1071 );
1072 assert_bool(
1075 "(= '(1 2 3) (s/conform (s/coll-of int?) '(1 2 3)))",
1076 true,
1077 &mut env,
1078 );
1079 assert_bool(
1080 "(= '(3 2 1) (s/conform (s/coll-of int? :into '()) '(1 2 3)))",
1081 true,
1082 &mut env,
1083 );
1084 });
1085 }
1086
1087 #[test]
1088 fn test_spec_map_of_key_handling() {
1089 run_with_big_stack(|| {
1090 let (_, mut env) = make_env();
1091 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1092 assert_bool(
1094 "(= {:a 1 :b 2} (s/conform (s/map-of keyword? int?) {:a 1 :b 2}))",
1095 true,
1096 &mut env,
1097 );
1098 assert_bool(
1099 "(s/invalid? (s/conform (s/map-of keyword? int?) {:a \"x\"}))",
1100 true,
1101 &mut env,
1102 );
1103 assert_bool(
1104 "(s/invalid? (s/conform (s/map-of keyword? int?) {\"not-kw\" 1}))",
1105 true,
1106 &mut env,
1107 );
1108 assert_bool(
1110 r#"(= {"a" 1} (s/conform (s/map-of (s/conformer name) int? :conform-keys true) {:a 1}))"#,
1111 true,
1112 &mut env,
1113 );
1114 });
1115 }
1116
1117 #[test]
1118 fn test_spec_every_vs_coll_of_conform_difference() {
1119 run_with_big_stack(|| {
1120 let (_, mut env) = make_env();
1121 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1122 run("(def elem (s/or :i int? :s string?))", &mut env).unwrap();
1123 assert_bool(
1125 "(= [[:i 1] [:s \"x\"]] (s/conform (s/coll-of elem) [1 \"x\"]))",
1126 true,
1127 &mut env,
1128 );
1129 assert_bool(
1131 "(= [1 \"x\"] (s/conform (s/every elem) [1 \"x\"]))",
1132 true,
1133 &mut env,
1134 );
1135 assert_bool(
1136 "(s/invalid? (s/conform (s/every elem) [1 :bad]))",
1137 true,
1138 &mut env,
1139 );
1140 assert_bool(
1142 "(= {:a 1} (s/conform (s/every-kv keyword? int?) {:a 1}))",
1143 true,
1144 &mut env,
1145 );
1146 assert_bool(
1147 "(s/invalid? (s/conform (s/every-kv keyword? int?) {:a \"x\"}))",
1148 true,
1149 &mut env,
1150 );
1151 });
1152 }
1153
1154 #[test]
1155 fn test_spec_tuple_conform_and_explain() {
1156 run_with_big_stack(|| {
1157 let (_, mut env) = make_env();
1158 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1159 assert_bool(
1160 "(= [1 \"x\"] (s/conform (s/tuple int? string?) [1 \"x\"]))",
1161 true,
1162 &mut env,
1163 );
1164 assert_bool(
1165 "(s/invalid? (s/conform (s/tuple int? string?) [1]))",
1166 true,
1167 &mut env,
1168 );
1169 assert_bool(
1170 "(s/invalid? (s/conform (s/tuple int? string?) 5))",
1171 true,
1172 &mut env,
1173 );
1174 run(
1175 "(def tp (first (:clojure.spec.alpha/problems (s/explain-data (s/tuple int? string?) [1 :bad]))))",
1176 &mut env,
1177 )
1178 .unwrap();
1179 assert_bool("(= [1] (:path tp))", true, &mut env);
1180 assert_bool("(= [1] (:in tp))", true, &mut env);
1181 assert_bool("(= :bad (:val tp))", true, &mut env);
1182 assert_bool(
1184 "(= [1 \"x\"] (s/unform (s/tuple int? string?) (s/conform (s/tuple int? string?) [1 \"x\"])))",
1185 true,
1186 &mut env,
1187 );
1188 });
1189 }
1190
1191 #[test]
1192 fn test_spec_nilable_both_branches_and_explain_shape() {
1193 run_with_big_stack(|| {
1194 let (_, mut env) = make_env();
1195 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1196 assert_bool("(= nil (s/conform (s/nilable int?) nil))", true, &mut env);
1197 assert_bool("(= 5 (s/conform (s/nilable int?) 5))", true, &mut env);
1198 assert_bool(
1199 "(s/invalid? (s/conform (s/nilable int?) \"x\"))",
1200 true,
1201 &mut env,
1202 );
1203 run(
1204 "(def np (:clojure.spec.alpha/problems (s/explain-data (s/nilable int?) \"x\")))",
1205 &mut env,
1206 )
1207 .unwrap();
1208 assert_bool("(= 2 (count np))", true, &mut env);
1209 assert_bool(
1210 "(some (fn [p] (= [:clojure.spec.alpha/nil] (:path p))) np)",
1211 true,
1212 &mut env,
1213 );
1214 assert_bool(
1215 "(some (fn [p] (= [:clojure.spec.alpha/pred] (:path p))) np)",
1216 true,
1217 &mut env,
1218 );
1219 });
1220 }
1221
1222 #[test]
1223 fn test_spec_multi_spec_conform_and_no_method() {
1224 run_with_big_stack(|| {
1225 let (_, mut env) = make_env();
1226 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1227 run("(defmulti event-type :type)", &mut env).unwrap();
1228 run("(s/def :evt/type keyword?)", &mut env).unwrap();
1229 run("(s/def :evt/a (s/keys :req-un [:evt/type]))", &mut env).unwrap();
1230 run("(defmethod event-type :a [_] :evt/a)", &mut env).unwrap();
1231 run(
1232 "(s/def :evt/event (s/multi-spec event-type :type))",
1233 &mut env,
1234 )
1235 .unwrap();
1236 assert_bool(
1237 "(= {:type :a} (s/conform :evt/event {:type :a}))",
1238 true,
1239 &mut env,
1240 );
1241 assert_bool(
1242 "(s/invalid? (s/conform :evt/event {:type :unknown}))",
1243 true,
1244 &mut env,
1245 );
1246 run(
1247 "(def mp (first (:clojure.spec.alpha/problems (s/explain-data :evt/event {:type :unknown}))))",
1248 &mut env,
1249 )
1250 .unwrap();
1251 assert_bool("(= \"no method\" (:reason mp))", true, &mut env);
1252 });
1253 }
1254
1255 #[test]
1256 fn test_spec_conformer_transforms_and_threads_through_and() {
1257 run_with_big_stack(|| {
1258 let (_, mut env) = make_env();
1259 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1260 assert_bool(
1261 "(= 10 (s/conform (s/conformer #(* 2 %)) 5))",
1262 true,
1263 &mut env,
1264 );
1265 assert_bool(
1267 "(= 10 (s/conform (s/and int? (s/conformer #(* 2 %))) 5))",
1268 true,
1269 &mut env,
1270 );
1271 assert_bool(
1272 "(= 5 (s/unform (s/conformer #(* 2 %) #(/ % 2)) 10))",
1273 true,
1274 &mut env,
1275 );
1276 assert_bool(
1278 "(= 10 (s/unform (s/conformer #(* 2 %)) 10))",
1279 true,
1280 &mut env,
1281 );
1282 });
1283 }
1284
1285 #[test]
1286 fn test_spec_int_in_and_double_in_bounds() {
1287 run_with_big_stack(|| {
1288 let (_, mut env) = make_env();
1289 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1290 assert_bool("(s/valid? (s/int-in 0 10) 5)", true, &mut env);
1291 assert_bool("(s/valid? (s/int-in 0 10) 10)", false, &mut env); assert_bool("(s/valid? (s/int-in 0 10) 5.0)", false, &mut env); assert_bool(
1294 "(s/valid? (s/double-in :min 0.0 :max 10.0) 5.0)",
1295 true,
1296 &mut env,
1297 );
1298 assert_bool(
1299 "(s/valid? (s/double-in :min 0.0 :max 10.0) 20.0)",
1300 false,
1301 &mut env,
1302 );
1303 run("(def nan (/ 0.0 0.0))", &mut env).unwrap();
1307 run("(def pos-inf (/ 1.0 0.0))", &mut env).unwrap();
1308 assert_bool("(s/valid? (s/double-in :NaN? false) nan)", false, &mut env);
1309 assert_bool("(s/valid? (s/double-in) nan)", true, &mut env);
1310 assert_bool(
1311 "(s/valid? (s/double-in :infinite? false) pos-inf)",
1312 false,
1313 &mut env,
1314 );
1315 assert_bool("(s/valid? (s/double-in) pos-inf)", true, &mut env);
1316 assert_bool(
1318 "(= [1 \"x\"] (s/conform (s/nonconforming (s/cat :a int? :b string?)) [1 \"x\"]))",
1319 true,
1320 &mut env,
1321 );
1322 let err = run("(s/inst-in 0 1)", &mut env).unwrap_err();
1324 let msg = format!("{err}");
1325 assert!(
1326 msg.contains("not implemented"),
1327 "expected 'not implemented' in error, got: {msg}"
1328 );
1329 });
1330 }
1331
1332 #[test]
1335 fn test_spec_fdef_registers_qualified_symbol_and_get_spec() {
1336 run_with_big_stack(|| {
1337 let (_, mut env) = make_env();
1338 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1339 run("(defn my-add [a b] (+ a b))", &mut env).unwrap();
1340 assert_bool(
1341 "(= 'user/my-add (s/fdef my-add :args (s/cat :a int? :b int?) :ret int?))",
1342 true,
1343 &mut env,
1344 );
1345 assert_bool("(s/fspec? (s/get-spec 'user/my-add))", true, &mut env);
1346 assert_bool("(some? (:args (s/get-spec 'user/my-add)))", true, &mut env);
1347 assert_bool(
1350 "(= (s/get-spec 'user/my-add) (s/get-spec `my-add))",
1351 true,
1352 &mut env,
1353 );
1354 });
1355 }
1356
1357 #[test]
1358 fn test_spec_instrument_valid_and_invalid_calls() {
1359 run_with_big_stack(|| {
1360 let (_, mut env) = make_env();
1361 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1362 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1363 run("(defn my-add [a b] (+ a b))", &mut env).unwrap();
1364 run(
1365 "(s/fdef my-add :args (s/cat :a int? :b int?) :ret int?)",
1366 &mut env,
1367 )
1368 .unwrap();
1369 run("(stest/instrument 'user/my-add)", &mut env).unwrap();
1370 assert_eq!(run("(my-add 2 3)", &mut env).unwrap(), Value::Long(5));
1372 run(
1374 r#"(def caught
1375 (try (my-add 2 "x") :did-not-throw
1376 (catch Exception e {:msg (ex-message e) :data (ex-data e)})))"#,
1377 &mut env,
1378 )
1379 .unwrap();
1380 assert_bool(
1381 r#"(= "Call to user/my-add did not conform to spec." (:msg caught))"#,
1382 true,
1383 &mut env,
1384 );
1385 assert_bool(
1386 "(= :instrument (:clojure.spec.alpha/failure (:data caught)))",
1387 true,
1388 &mut env,
1389 );
1390 assert_bool(
1391 "(contains? (:data caught) :clojure.spec.test.alpha/caller)",
1392 true,
1393 &mut env,
1394 );
1395 });
1396 }
1397
1398 #[test]
1399 fn test_spec_unstrument_restores_raw_fn() {
1400 run_with_big_stack(|| {
1401 let (_, mut env) = make_env();
1402 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1403 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1404 run("(defn my-pair [a b] [a b])", &mut env).unwrap();
1405 run("(s/fdef my-pair :args (s/cat :a int? :b int?))", &mut env).unwrap();
1406 run("(stest/instrument 'user/my-pair)", &mut env).unwrap();
1407 assert!(run("(my-pair 1 \"x\")", &mut env).is_err());
1408 run("(stest/unstrument 'user/my-pair)", &mut env).unwrap();
1409 assert_bool("(= [1 \"x\"] (my-pair 1 \"x\"))", true, &mut env);
1412 });
1413 }
1414
1415 #[test]
1416 fn test_spec_instrument_affects_call_sites_evaluated_before_instrument() {
1417 run_with_big_stack(|| {
1418 let (_, mut env) = make_env();
1419 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1420 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1421 run("(defn my-mul [a b] (* a b))", &mut env).unwrap();
1422 run("(s/fdef my-mul :args (s/cat :a int? :b int?))", &mut env).unwrap();
1423 assert_eq!(run("(my-mul 2 3)", &mut env).unwrap(), Value::Long(6));
1426 run("(stest/instrument 'user/my-mul)", &mut env).unwrap();
1427 assert!(run("(my-mul 2 \"x\")", &mut env).is_err());
1430 });
1431 }
1432
1433 #[test]
1434 fn test_spec_instrumentable_syms_and_idempotent_double_instrument() {
1435 run_with_big_stack(|| {
1436 let (_, mut env) = make_env();
1437 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1438 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1439 run("(defn my-pair2 [a b] [a b])", &mut env).unwrap();
1440 run("(s/fdef my-pair2 :args (s/cat :a int? :b int?))", &mut env).unwrap();
1441 assert_bool(
1442 "(boolean (some #{'user/my-pair2} (stest/instrumentable-syms)))",
1443 true,
1444 &mut env,
1445 );
1446 run("(stest/instrument 'user/my-pair2)", &mut env).unwrap();
1447 run("(stest/instrument 'user/my-pair2)", &mut env).unwrap(); assert!(run("(my-pair2 1 \"x\")", &mut env).is_err());
1449 run("(stest/unstrument 'user/my-pair2)", &mut env).unwrap();
1453 assert_bool("(= [1 \"x\"] (my-pair2 1 \"x\"))", true, &mut env);
1454 });
1455 }
1456
1457 #[test]
1458 fn test_spec_with_instrument_disabled_bypasses_checks() {
1459 run_with_big_stack(|| {
1460 let (_, mut env) = make_env();
1461 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1462 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1463 run("(defn my-pair3 [a b] [a b])", &mut env).unwrap();
1464 run("(s/fdef my-pair3 :args (s/cat :a int? :b int?))", &mut env).unwrap();
1465 run("(stest/instrument 'user/my-pair3)", &mut env).unwrap();
1466 assert!(run("(my-pair3 1 \"x\")", &mut env).is_err());
1467 assert_bool(
1468 "(stest/with-instrument-disabled (= [1 \"x\"] (my-pair3 1 \"x\")))",
1469 true,
1470 &mut env,
1471 );
1472 assert!(run("(my-pair3 1 \"x\")", &mut env).is_err());
1474 });
1475 }
1476
1477 #[test]
1478 fn test_spec_assert_happy_sad_and_check_asserts_toggle() {
1479 run_with_big_stack(|| {
1480 let (_, mut env) = make_env();
1481 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1482 assert_eq!(run("(s/assert even? 4)", &mut env).unwrap(), Value::Long(4));
1483 assert!(run("(s/assert even? 5)", &mut env).is_err());
1484 run("(s/check-asserts false)", &mut env).unwrap();
1485 assert_eq!(run("(s/assert even? 5)", &mut env).unwrap(), Value::Long(5));
1489 run("(s/check-asserts true)", &mut env).unwrap();
1490 assert!(run("(s/assert even? 5)", &mut env).is_err());
1491 });
1492 }
1493
1494 #[test]
1495 fn test_spec_gen_and_check_throw_not_implemented_and_gen_ns_loads() {
1496 run_with_big_stack(|| {
1497 let (_, mut env) = make_env();
1498 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1499 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1500 run("(require '[clojure.spec.gen.alpha :as gen])", &mut env).unwrap();
1501 for src in [
1502 "(s/gen even?)",
1503 "(s/exercise even?)",
1504 "(s/exercise-fn 'user/foo)",
1505 "(stest/check)",
1506 "(stest/check 'user/foo)",
1507 "(stest/check-fn (fn [x] x) even?)",
1508 "(gen/generate :whatever)",
1509 "(gen/sample :whatever)",
1510 "(gen/elements [1 2 3])",
1511 ] {
1512 assert!(run(src, &mut env).is_err(), "expected {src} to throw");
1513 }
1514 });
1515 }
1516
1517 #[cfg(not(target_arch = "wasm32"))]
1520 fn shell_env() -> Env {
1521 let (_, mut env) = make_env();
1522 run(
1523 "(require '[clojure.java.shell :refer [sh with-sh-dir with-sh-env]])",
1524 &mut env,
1525 )
1526 .unwrap();
1527 run(
1530 "(defn big [s n] (loop [s s] (if (>= (count s) n) s (recur (str s s)))))",
1531 &mut env,
1532 )
1533 .unwrap();
1534 env
1535 }
1536
1537 #[cfg(not(target_arch = "wasm32"))]
1538 fn truthy(src: &str, env: &mut Env) {
1539 assert_eq!(run(src, env).unwrap(), Value::Bool(true), "{src}");
1540 }
1541
1542 #[test]
1543 #[cfg(unix)]
1544 fn test_sh_exit_is_data() {
1545 let mut env = shell_env();
1546 truthy(
1547 "(= {:exit 0 :out \"hi\" :err \"\"} (sh \"printf\" \"hi\"))",
1548 &mut env,
1549 );
1550 truthy(
1551 "(= {:exit 3 :out \"\" :err \"e\"} (sh \"sh\" \"-c\" \"printf e >&2; exit 3\"))",
1552 &mut env,
1553 );
1554 truthy(
1555 "(= 137 (:exit (sh \"sh\" \"-c\" \"kill -9 $$\")))",
1556 &mut env,
1557 );
1558 }
1559
1560 #[test]
1561 #[cfg(unix)]
1562 fn test_sh_in_dir_env() {
1563 let mut env = shell_env();
1564 truthy("(= \"piped\" (:out (sh \"cat\" :in \"piped\")))", &mut env);
1565 truthy(
1566 "(= 262144 (count (:out (sh \"cat\" :in (big \"x\" 262144)))))",
1567 &mut env,
1568 );
1569 truthy("(= \"/\\n\" (:out (sh \"pwd\" :dir \"/\")))", &mut env);
1570 truthy(
1571 "(= \"/\\n\" (:out (with-sh-dir \"/\" (sh \"pwd\"))))",
1572 &mut env,
1573 );
1574 truthy(
1575 "(= \"FOO=bar\\n\" (:out (sh \"/usr/bin/env\" :env {\"FOO\" \"bar\"})))",
1576 &mut env,
1577 );
1578 truthy(
1579 "(= \"A=1=2\\n\" (:out (with-sh-env [\"A=1=2\"] (sh \"/usr/bin/env\"))))",
1580 &mut env,
1581 );
1582 truthy(
1584 "(= \"x\" (:out (sh \"printf\" \"x\" :env {\"X\" \"1\"})))",
1585 &mut env,
1586 );
1587 truthy(
1589 "(= 0 (:exit (sh \"true\" :in (big \"y\" 1048576))))",
1590 &mut env,
1591 );
1592 }
1593
1594 #[test]
1595 #[cfg(unix)]
1596 fn test_sh_out_bytes() {
1597 let mut env = shell_env();
1598 truthy(
1599 "(= [97 98] (vec (:out (sh \"printf\" \"ab\" :out-enc :bytes))))",
1600 &mut env,
1601 );
1602 }
1603
1604 #[test]
1605 #[cfg(unix)]
1606 fn test_sh_refusals() {
1607 let mut env = shell_env();
1608 for src in [
1609 "(sh \"cljrs-no-such-program-x\")",
1610 "(sh \"pwd\" :dir \"/no/such/dir\")",
1611 "(sh \"printf\" \"x\" :env {\"A=B\" \"1\"})",
1612 "(sh \"printf\" \"x\" :env {\"A\" \"\\u0000\"})",
1613 "(sh \"printf\" \"x\" :out-enc \"latin1\")",
1614 "(sh :in \"x\")",
1615 "(clojure.rust.process/run [])",
1616 ] {
1617 assert!(run(src, &mut env).is_err(), "expected {src} to throw");
1618 }
1619 }
1620
1621 #[cfg(unix)]
1624 fn clj_string_lit(s: &str) -> String {
1625 let mut out = String::from("\"");
1626 for c in s.chars() {
1627 match c {
1628 '"' => out.push_str("\\\""),
1629 '\\' => out.push_str("\\\\"),
1630 ' '..='~' => out.push(c),
1631 _ => out.push_str(&format!("\\u{:04x}", c as u32)),
1632 }
1633 }
1634 out.push('"');
1635 out
1636 }
1637
1638 #[cfg(unix)]
1641 fn bmp_string(max: usize) -> impl proptest::strategy::Strategy<Value = String> {
1642 use proptest::prelude::*;
1643 prop::collection::vec(prop::char::range('\u{1}', '\u{ffff}'), 0..max)
1644 .prop_map(|cs| cs.into_iter().collect())
1645 }
1646
1647 #[test]
1652 #[cfg(unix)]
1653 fn test_env_value_nul_laws() {
1654 use proptest::test_runner::{Config, TestRunner};
1655 use std::cell::RefCell;
1656 let env = RefCell::new(shell_env());
1657 let mut runner = TestRunner::new(Config::with_cases(48));
1658
1659 runner
1660 .run(&(bmp_string(8), bmp_string(8)), |(pre, post)| {
1661 let v = format!("{pre}\0{post}");
1662 let src = format!(
1663 "(clojure.rust.process/run [\"printf\" \"x\"] {{:env {{\"A\" {}}}}})",
1664 clj_string_lit(&v)
1665 );
1666 proptest::prop_assert!(run(&src, &mut env.borrow_mut()).is_err(), "{src}");
1667 Ok(())
1668 })
1669 .unwrap();
1670
1671 runner
1672 .run(&bmp_string(12), |v| {
1673 let src = format!(
1674 "(let [v {}] (= (str \"A=\" v \"\\n\") \
1675 (:out (clojure.rust.process/run [\"/usr/bin/env\"] {{:env {{\"A\" v}}}}))))",
1676 clj_string_lit(&v)
1677 );
1678 let got = run(&src, &mut env.borrow_mut());
1679 proptest::prop_assert_eq!(got.ok(), Some(Value::Bool(true)), "{}", src);
1680 Ok(())
1681 })
1682 .unwrap();
1683 }
1684
1685 #[test]
1693 #[cfg(unix)]
1694 fn test_process_env_golden() {
1695 const GOLDEN: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/golden/process_env.txt");
1696 const ROWS: &[&str] = &[
1697 "(:out (clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A\" \"ok\"}}))",
1698 "(:out (clojure.rust.process/run [\"/usr/bin/env\"] {:env {\"A\" \"a b=c\"}}))",
1699 "(:out (clojure.rust.process/run [\"/usr/bin/env\"] {:env {:A \"kw\"}}))",
1700 "(clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A\" \"\\u0000\"}})",
1701 "(clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A\" \"a\\u0000b\"}})",
1702 "(clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A=B\" \"1\"}})",
1703 "(clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A\" 1}})",
1704 "(clojure.rust.process/run [\"printf\" \"x\"] {:env [\"A\" \"1\"]})",
1705 "(clojure.rust.process/run [])",
1706 ];
1707 let mut env = shell_env();
1708 let mut actual = String::new();
1709 for src in ROWS {
1710 let outcome = match run(src, &mut env) {
1711 Ok(v) => format!("{v:?}"),
1712 Err(e) => {
1713 let text = format!("{e:?}").replace('\n', " ");
1714 let msg = text
1715 .split("message: \"")
1716 .nth(1)
1717 .and_then(|s| s.split('"').next())
1718 .or_else(|| text.split('"').nth(1))
1719 .map(str::to_string)
1720 .unwrap_or(text);
1721 format!("ERROR {msg}")
1722 }
1723 };
1724 actual.push_str(&format!("{src}\n => {outcome}\n"));
1725 }
1726 if std::env::var_os("UPDATE_GOLDEN").is_some() {
1727 std::fs::create_dir_all(std::path::Path::new(GOLDEN).parent().unwrap()).unwrap();
1728 std::fs::write(GOLDEN, &actual).unwrap();
1729 return;
1730 }
1731 let expected = std::fs::read_to_string(GOLDEN)
1732 .unwrap_or_else(|e| panic!("{GOLDEN}: {e}; run with UPDATE_GOLDEN=1 to create it"));
1733 assert_eq!(
1734 actual, expected,
1735 "process :env answers changed; see {GOLDEN}"
1736 );
1737 }
1738
1739 #[test]
1740 #[cfg(not(target_arch = "wasm32"))]
1741 fn test_process_run_denied_under_transaction_policy() {
1742 use cljrs_runtime::env::policy::{TransactionPolicyGuard, check_native};
1743 assert!(check_native(process::RUN_NAME).is_ok());
1744 let _guard = TransactionPolicyGuard::install();
1745 assert!(check_native(process::RUN_NAME).is_err());
1746 }
1747}