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