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 for (form, expected) in [
324 ("(str/split-lines \"\")", "[\"\"]"),
325 ("(str/split-lines \"\\n\\n\")", "[]"),
326 ("(str/split-lines \"a\\nb\\nc\")", "[\"a\" \"b\" \"c\"]"),
327 ("(str/split-lines \"a\\r\\nb\")", "[\"a\" \"b\"]"),
328 ("(str/split-lines \"a\\n\\nb\")", "[\"a\" \"\" \"b\"]"),
329 ("(str/split-lines \"a\\n\\n\")", "[\"a\"]"),
330 ] {
331 let value = run(form, &mut env).unwrap();
332 assert_eq!(value.to_string(), expected, "{form}");
333 }
334 }
335
336 #[test]
337 fn test_string_split_lines_rejects_non_strings() {
338 let (_, mut env) = make_env();
339 run("(require '[clojure.string :as str])", &mut env).unwrap();
340 for form in [
341 "(str/split-lines nil)",
342 "(str/split-lines \\A)",
343 "(str/split-lines 0)",
344 "(str/split-lines 0.0)",
345 "(str/split-lines :foo)",
346 "(str/split-lines 'foo)",
347 "(str/split-lines [])",
348 ] {
349 assert!(run(form, &mut env).is_err(), "{form} should fail");
350 }
351 }
352
353 #[test]
356 fn test_set_union() {
357 let (_, mut env) = make_env();
358 run("(require '[clojure.set :as s])", &mut env).unwrap();
359 let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
360 match v {
361 Value::Set(s) => assert_eq!(s.count(), 3),
362 other => panic!("expected set, got {other:?}"),
363 }
364 }
365
366 #[test]
367 fn test_set_intersection() {
368 let (_, mut env) = make_env();
369 run("(require '[clojure.set :as s])", &mut env).unwrap();
370 let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
371 match v {
372 Value::Set(s) => assert_eq!(s.count(), 2),
373 other => panic!("expected set, got {other:?}"),
374 }
375 }
376
377 #[test]
378 fn test_set_difference() {
379 let (_, mut env) = make_env();
380 run("(require '[clojure.set :as s])", &mut env).unwrap();
381 let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
382 match v {
383 Value::Set(s) => assert_eq!(s.count(), 1),
384 other => panic!("expected set, got {other:?}"),
385 }
386 }
387
388 #[test]
389 fn test_set_subset_superset() {
390 let (_, mut env) = make_env();
391 run("(require '[clojure.set :as s])", &mut env).unwrap();
392 assert_eq!(
393 run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
394 Value::Bool(true)
395 );
396 assert_eq!(
397 run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
398 Value::Bool(true)
399 );
400 }
401
402 #[test]
403 fn test_set_map_invert() {
404 let (_, mut env) = make_env();
405 run("(require '[clojure.set :as s])", &mut env).unwrap();
406 let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
407 assert!(matches!(v, Value::Map(_)));
408 }
409
410 #[test]
413 fn test_clojure_test_lazy_load() {
414 std::thread::Builder::new()
418 .stack_size(16 * 1024 * 1024)
419 .spawn(|| {
420 let (_, mut env) = make_env();
421 run(
424 "(require '[clojure.test :refer [is deftest run-tests]])",
425 &mut env,
426 )
427 .unwrap();
428 let v = run("(is (= 1 1))", &mut env).unwrap();
429 assert_eq!(v, Value::Bool(true));
430 })
431 .unwrap()
432 .join()
433 .unwrap();
434 }
435
436 fn run_with_big_stack<F: FnOnce() + Send + 'static>(body: F) {
442 std::thread::Builder::new()
443 .stack_size(16 * 1024 * 1024)
444 .spawn(body)
445 .unwrap()
446 .join()
447 .unwrap();
448 }
449
450 #[test]
451 fn test_spec_def_and_valid_conform() {
452 run_with_big_stack(|| {
453 let (_, mut env) = make_env();
454 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
455 assert_eq!(
456 run("(s/def ::x even?)", &mut env).unwrap(),
457 Value::keyword(Keyword::qualified("user", "x"))
458 );
459 assert_eq!(
460 run("(s/valid? ::x 4)", &mut env).unwrap(),
461 Value::Bool(true)
462 );
463 assert_eq!(
464 run("(s/valid? ::x 3)", &mut env).unwrap(),
465 Value::Bool(false)
466 );
467 assert_eq!(run("(s/conform ::x 4)", &mut env).unwrap(), Value::Long(4));
468 assert_eq!(
469 run("(s/invalid? (s/conform ::x 3))", &mut env).unwrap(),
470 Value::Bool(true)
471 );
472 });
473 }
474
475 #[test]
476 fn test_spec_set_and_keyword_ref() {
477 run_with_big_stack(|| {
478 let (_, mut env) = make_env();
479 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
480 run("(s/def ::color #{:red :green})", &mut env).unwrap();
481 assert_eq!(
482 run("(s/valid? ::color :red)", &mut env).unwrap(),
483 Value::Bool(true)
484 );
485 assert_eq!(
486 run("(s/valid? ::color :blue)", &mut env).unwrap(),
487 Value::Bool(false)
488 );
489
490 run("(s/def ::x even?)", &mut env).unwrap();
492 run("(s/def ::y ::x)", &mut env).unwrap();
493 assert_eq!(
494 run("(s/valid? ::y 4)", &mut env).unwrap(),
495 Value::Bool(true)
496 );
497 assert_eq!(
498 run("(s/valid? ::y 3)", &mut env).unwrap(),
499 Value::Bool(false)
500 );
501 });
502 }
503
504 #[test]
505 fn test_spec_forward_reference() {
506 run_with_big_stack(|| {
507 let (_, mut env) = make_env();
508 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
509 run("(s/def ::a ::b)", &mut env).unwrap();
511 run("(s/def ::b string?)", &mut env).unwrap();
512 assert_eq!(
513 run(r#"(s/conform ::a "hi")"#, &mut env).unwrap(),
514 Value::string("hi")
515 );
516 assert_eq!(
517 run("(s/invalid? (s/conform ::a 5))", &mut env).unwrap(),
518 Value::Bool(true)
519 );
520 });
521 }
522
523 #[test]
524 fn test_spec_and_or() {
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!(
529 run("(s/valid? (s/and int? even?) 4)", &mut env).unwrap(),
530 Value::Bool(true)
531 );
532 assert_eq!(
533 run("(s/valid? (s/and int? even?) 3)", &mut env).unwrap(),
534 Value::Bool(false)
535 );
536 let v = run("(s/conform (s/or :i int? :s string?) 5)", &mut env).unwrap();
537 match v {
538 Value::Vector(vec) => {
539 let items = vec.get().iter().cloned().collect::<Vec<_>>();
540 assert_eq!(items.len(), 2);
541 assert_eq!(items[0], Value::keyword(Keyword::simple("i")));
542 assert_eq!(items[1], Value::Long(5));
543 }
544 other => panic!("expected [:i 5], got {other:?}"),
545 }
546 });
547 }
548
549 #[test]
550 fn test_spec_spec_and_registry_introspection() {
551 run_with_big_stack(|| {
552 let (_, mut env) = make_env();
553 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
554 assert_eq!(run("(s/spec? even?)", &mut env).unwrap(), Value::Nil);
557 let is_spec = run("(s/spec? (s/and int? even?))", &mut env).unwrap();
558 assert!(
559 !matches!(is_spec, Value::Nil),
560 "expected a truthy spec object"
561 );
562
563 run("(s/def ::x even?)", &mut env).unwrap();
564 assert!(!matches!(
565 run("(s/get-spec ::x)", &mut env).unwrap(),
566 Value::Nil
567 ));
568 assert!(matches!(
569 run("(s/get-spec ::does-not-exist)", &mut env).unwrap(),
570 Value::Nil
571 ));
572
573 run("(s/def ::pos-even (s/and int? even? pos?))", &mut env).unwrap();
574 let form_v = run("(s/form ::pos-even)", &mut env).unwrap();
575 assert_eq!(format!("{form_v}"), "(and int? even? pos?)");
576 let describe_v = run("(s/describe ::pos-even)", &mut env).unwrap();
577 assert_eq!(format!("{describe_v}"), "(and int? even? pos?)");
578 });
579 }
580
581 #[test]
582 fn test_spec_conform_unregistered_keyword_throws() {
583 run_with_big_stack(|| {
584 let (_, mut env) = make_env();
585 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
586 let err = run("(s/conform ::does-not-exist 1)", &mut env).unwrap_err();
587 let msg = format!("{err}");
588 assert!(
589 msg.contains("Unable to resolve spec"),
590 "expected 'Unable to resolve spec' in error, got: {msg}"
591 );
592 });
593 }
594
595 #[track_caller]
599 fn assert_bool(src: &str, expected: bool, env: &mut Env) {
600 assert_eq!(
601 run(src, env).unwrap(),
602 Value::Bool(expected),
603 "expression: {src}"
604 );
605 }
606
607 #[test]
608 fn test_spec_keys_req_opt() {
609 run_with_big_stack(|| {
610 let (_, mut env) = make_env();
611 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
612 run("(s/def ::a int?) (s/def ::b string?)", &mut env).unwrap();
613 run("(s/def ::m (s/keys :req [::a] :opt [::b]))", &mut env).unwrap();
614 assert_bool("(s/valid? ::m {::a 1})", true, &mut env);
615 assert_bool("(s/valid? ::m {::a 1 ::b \"x\"})", true, &mut env);
616 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(
622 "(s/valid? (s/keys :req [::a]) {::a 1 ::b :not-a-string})",
623 false,
624 &mut env,
625 );
626 run("(s/def ::c boolean?)", &mut env).unwrap();
628 run(
629 "(s/def ::conn (s/keys :req [(or ::a (and ::b ::c))]))",
630 &mut env,
631 )
632 .unwrap();
633 assert_bool("(s/valid? ::conn {::a 1})", true, &mut env);
634 assert_bool("(s/valid? ::conn {::b \"x\" ::c true})", true, &mut env);
635 assert_bool("(s/valid? ::conn {::b \"x\"})", false, &mut env);
636 assert_bool("(s/valid? ::conn {})", false, &mut env);
637 });
638 }
639
640 #[test]
641 fn test_spec_keys_un_variants() {
642 run_with_big_stack(|| {
643 let (_, mut env) = make_env();
644 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
645 run("(s/def ::a int?) (s/def ::b string?)", &mut env).unwrap();
646 run(
647 "(s/def ::mu (s/keys :req-un [::a] :opt-un [::b]))",
648 &mut env,
649 )
650 .unwrap();
651 assert_bool("(s/valid? ::mu {:a 1})", true, &mut env);
652 assert_bool("(s/valid? ::mu {:a 1 :b \"x\"})", true, &mut env);
653 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);
657 });
658 }
659
660 #[test]
661 fn test_spec_keys_record() {
662 run_with_big_stack(|| {
663 let (_, mut env) = make_env();
664 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
665 run("(defrecord Point [x y])", &mut env).unwrap();
666 run("(s/def ::x number?) (s/def ::y number?)", &mut env).unwrap();
667 run("(s/def ::point (s/keys :req-un [::x ::y]))", &mut env).unwrap();
668 assert_bool("(s/valid? ::point (->Point 1 2))", true, &mut env);
669 assert_bool("(s/valid? ::point (->Point \"a\" 2))", false, &mut env);
670 assert_bool(
672 "(record? (s/conform ::point (->Point 1 2)))",
673 true,
674 &mut env,
675 );
676 });
677 }
678
679 #[test]
680 fn test_spec_explain_data_shape() {
681 run_with_big_stack(|| {
682 let (_, mut env) = make_env();
683 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
684 run("(s/def ::a int?) (s/def ::b string?)", &mut env).unwrap();
685 run("(s/def ::m (s/keys :req [::a] :opt [::b]))", &mut env).unwrap();
686
687 run("(def ed1 (s/explain-data ::m {::b \"x\"}))", &mut env).unwrap();
689 assert_bool(
690 "(pos? (count (:clojure.spec.alpha/problems ed1)))",
691 true,
692 &mut env,
693 );
694 let pred = run(
695 "(pr-str (:pred (first (:clojure.spec.alpha/problems ed1))))",
696 &mut env,
697 )
698 .unwrap();
699 assert_eq!(pred, Value::string("(contains? % :user/a)"));
700 assert_bool(
701 "(= [] (:path (first (:clojure.spec.alpha/problems ed1))))",
702 true,
703 &mut env,
704 );
705
706 run("(def ed2 (s/explain-data ::m {::a \"no\"}))", &mut env).unwrap();
709 run(
710 "(def p2 (first (:clojure.spec.alpha/problems ed2)))",
711 &mut env,
712 )
713 .unwrap();
714 assert_bool("(= [::a] (:path p2))", true, &mut env);
715 assert_bool("(= [::a] (:in p2))", true, &mut env);
716 assert_bool("(= [::m ::a] (:via p2))", true, &mut env);
717 assert_bool("(= \"no\" (:val p2))", true, &mut env);
718
719 assert_bool("(nil? (s/explain-data ::m {::a 4}))", true, &mut env);
721
722 assert_bool(
724 "(= {::a \"no\"} (:clojure.spec.alpha/value ed2))",
725 true,
726 &mut env,
727 );
728 });
729 }
730
731 #[test]
732 fn test_spec_merge() {
733 run_with_big_stack(|| {
734 let (_, mut env) = make_env();
735 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
736 run("(s/def ::a int?) (s/def ::b string?)", &mut env).unwrap();
737 run("(s/def ::ma (s/keys :req [::a]))", &mut env).unwrap();
738 run("(s/def ::mb (s/keys :req [::b]))", &mut env).unwrap();
739 run("(s/def ::mab (s/merge ::ma ::mb))", &mut env).unwrap();
740 assert_bool("(s/valid? ::mab {::a 1 ::b \"x\"})", true, &mut env);
741 assert_bool("(s/valid? ::mab {::a 1})", false, &mut env);
742 assert_bool(
743 "(= {::a 1 ::b \"x\"} (s/conform ::mab {::a 1 ::b \"x\"}))",
744 true,
745 &mut env,
746 );
747 assert_bool(
748 "(pos? (count (:clojure.spec.alpha/problems
749 (s/explain-data ::mab {::a 1}))))",
750 true,
751 &mut env,
752 );
753 });
754 }
755
756 #[test]
757 fn test_spec_explain_str_and_printer() {
758 run_with_big_stack(|| {
759 let (_, mut env) = make_env();
760 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
761 run("(s/def ::a int?)", &mut env).unwrap();
762 run("(s/def ::m (s/keys :req [::a]))", &mut env).unwrap();
763 let estr = match run("(s/explain-str ::m {::a \"no\"})", &mut env).unwrap() {
764 Value::Str(s) => s.get().clone(),
765 other => panic!("expected string from explain-str, got {other:?}"),
766 };
767 assert!(
768 estr.contains("failed") && estr.contains("int?"),
769 "unexpected explain-str output: {estr}"
770 );
771 let ok = run("(s/explain-str ::m {::a 1})", &mut env).unwrap();
772 assert_eq!(ok, Value::string("Success!\n"));
773 });
774 }
775
776 #[test]
777 fn test_spec_keys_unform_roundtrip() {
778 run_with_big_stack(|| {
779 let (_, mut env) = make_env();
780 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
781 run("(s/def ::id (s/or :i int? :s string?))", &mut env).unwrap();
782 run("(s/def ::rm (s/keys :req-un [::id]))", &mut env).unwrap();
783 assert_bool("(= {:id [:i 5]} (s/conform ::rm {:id 5}))", true, &mut env);
785 assert_bool(
786 "(= {:id 5} (s/unform ::rm (s/conform ::rm {:id 5})))",
787 true,
788 &mut env,
789 );
790 });
791 }
792
793 #[test]
796 fn test_spec_regex_cat_and_nesting() {
797 run_with_big_stack(|| {
798 let (_, mut env) = make_env();
799 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
800 assert_bool(
801 "(= {:a 1 :b \"x\"} (s/conform (s/cat :a int? :b string?) [1 \"x\"]))",
802 true,
803 &mut env,
804 );
805 assert_bool(
807 "(= {:a 1 :b [\"x\" \"y\"]}
808 (s/conform (s/cat :a int? :b (s/* string?)) [1 \"x\" \"y\"]))",
809 true,
810 &mut env,
811 );
812 assert_bool(
814 "(s/invalid? (s/conform (s/cat :a int? :b string?) [1 2]))",
815 true,
816 &mut env,
817 );
818 assert_bool(
819 "(s/invalid? (s/conform (s/cat :a int? :b string?) [1]))",
820 true,
821 &mut env,
822 );
823 assert_bool(
824 "(s/invalid? (s/conform (s/cat :a int?) [1 2]))",
825 true,
826 &mut env,
827 );
828 assert_bool("(s/invalid? (s/conform (s/cat :a int?) 5))", true, &mut env);
830 });
831 }
832
833 #[test]
834 fn test_spec_regex_alt_star_plus_maybe() {
835 run_with_big_stack(|| {
836 let (_, mut env) = make_env();
837 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
838 assert_bool(
840 "(= [:i 5] (s/conform (s/alt :i int? :s string?) [5]))",
841 true,
842 &mut env,
843 );
844 assert_bool(
845 "(s/invalid? (s/conform (s/alt :i int? :s string?) [:kw]))",
846 true,
847 &mut env,
848 );
849 assert_bool("(= [] (s/conform (s/* int?) []))", true, &mut env);
851 assert_bool("(s/valid? (s/* int?) [])", true, &mut env);
852 assert_bool("(= [1 2 3] (s/conform (s/* int?) [1 2 3]))", true, &mut env);
853 assert_bool(
854 "(s/invalid? (s/conform (s/* int?) [1 :a 3]))",
855 true,
856 &mut env,
857 );
858 assert_bool("(= [1] (s/conform (s/+ int?) [1]))", true, &mut env);
860 assert_bool("(s/invalid? (s/conform (s/+ int?) []))", true, &mut env);
861 assert_bool("(= 5 (s/conform (s/? int?) [5]))", true, &mut env);
863 assert_bool("(nil? (s/conform (s/? int?) []))", true, &mut env);
864 assert_bool("(s/valid? (s/? int?) [])", true, &mut env);
865 assert_bool("(s/invalid? (s/conform (s/? int?) [1 2]))", true, &mut env);
866 });
867 }
868
869 #[test]
870 fn test_spec_regex_amp_and_regex_inside_and() {
871 run_with_big_stack(|| {
872 let (_, mut env) = make_env();
873 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
874 run("(def even-count? (fn [xs] (even? (count xs))))", &mut env).unwrap();
875 assert_bool(
877 "(= [1 2] (s/conform (s/& (s/* int?) even-count?) [1 2]))",
878 true,
879 &mut env,
880 );
881 assert_bool(
882 "(s/invalid? (s/conform (s/& (s/* int?) even-count?) [1 2 3]))",
883 true,
884 &mut env,
885 );
886 run("(def all-even? (fn [xs] (every? even? xs)))", &mut env).unwrap();
889 assert_bool(
890 "(= [2 4] (s/conform (s/and (s/* int?) all-even?) [2 4]))",
891 true,
892 &mut env,
893 );
894 assert_bool(
895 "(s/valid? (s/and (s/* int?) all-even?) [1 2])",
896 false,
897 &mut env,
898 );
899 });
900 }
901
902 #[test]
903 fn test_spec_regex_keyword_ref_children() {
904 run_with_big_stack(|| {
905 let (_, mut env) = make_env();
906 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
907 run("(s/def ::a int?)", &mut env).unwrap();
909 assert_bool(
910 "(= {:x 1 :y \"x\"} (s/conform (s/cat :x ::a :y string?) [1 \"x\"]))",
911 true,
912 &mut env,
913 );
914 run("(s/def ::r (s/* int?))", &mut env).unwrap();
917 assert_bool(
918 "(= {:nums [1 2] :tail \"x\"}
919 (s/conform (s/cat :nums ::r :tail string?) [1 2 \"x\"]))",
920 true,
921 &mut env,
922 );
923 assert_bool(
925 "(= {:nums [1 2] :tail \"x\"}
926 (s/conform (s/cat :nums (s/spec (s/* int?)) :tail string?)
927 [[1 2] \"x\"]))",
928 true,
929 &mut env,
930 );
931 assert_bool("(= [1 2 3] (s/conform ::r [1 2 3]))", true, &mut env);
933 });
934 }
935
936 #[test]
937 fn test_spec_regex_explain_reasons() {
938 run_with_big_stack(|| {
939 let (_, mut env) = make_env();
940 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
941 run(
943 "(def p-insuff (first (:clojure.spec.alpha/problems
944 (s/explain-data (s/cat :a int? :b string?) [1]))))",
945 &mut env,
946 )
947 .unwrap();
948 assert_bool(
949 "(= \"Insufficient input\" (:reason p-insuff))",
950 true,
951 &mut env,
952 );
953 assert_bool("(= [:b] (:path p-insuff))", true, &mut env);
954 run(
956 "(def p-extra (first (:clojure.spec.alpha/problems
957 (s/explain-data (s/cat :a int?) [1 2]))))",
958 &mut env,
959 )
960 .unwrap();
961 assert_bool("(= \"Extra input\" (:reason p-extra))", true, &mut env);
962 assert_bool("(= [1] (:in p-extra))", true, &mut env);
963 run(
965 "(def p-elem (first (:clojure.spec.alpha/problems
966 (s/explain-data (s/cat :a int? :b string?) [1 :bad]))))",
967 &mut env,
968 )
969 .unwrap();
970 assert_bool("(= [:b] (:path p-elem))", true, &mut env);
971 assert_bool("(= [1] (:in p-elem))", true, &mut env);
972 assert_bool("(= :bad (:val p-elem))", true, &mut env);
973 });
974 }
975
976 #[test]
977 fn test_spec_regex_unform_roundtrips() {
978 run_with_big_stack(|| {
979 let (_, mut env) = make_env();
980 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
981 run("(def even-count? (fn [xs] (even? (count xs))))", &mut env).unwrap();
982 for (spec, input) in [
983 ("(s/cat :a int? :b string?)", "[1 \"x\"]"),
984 ("(s/cat :a int? :b (s/* string?))", "[1 \"x\" \"y\"]"),
985 ("(s/alt :i int? :s string?)", "[5]"),
986 ("(s/* int?)", "[1 2 3]"),
987 ("(s/* int?)", "[]"),
988 ("(s/+ int?)", "[1 2]"),
989 ("(s/? int?)", "[5]"),
990 ("(s/? int?)", "[]"),
991 ("(s/& (s/* int?) even-count?)", "[1 2]"),
992 ] {
993 assert_bool(
994 &format!(
995 "(let [re {spec}] (= {input} (vec (s/unform re (s/conform re {input})))))"
996 ),
997 true,
998 &mut env,
999 );
1000 }
1001 });
1002 }
1003
1004 #[test]
1005 fn test_spec_plain_map_is_not_a_spec() {
1006 run_with_big_stack(|| {
1007 let (_, mut env) = make_env();
1008 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1009 let err = run("(s/conform {:a 1} 5)", &mut env).unwrap_err();
1010 let msg = format!("{err}");
1011 assert!(
1012 msg.contains("not a valid spec"),
1013 "expected 'not a valid spec' in error, got: {msg}"
1014 );
1015 });
1016 }
1017
1018 #[test]
1021 fn test_spec_coll_of_options() {
1022 run_with_big_stack(|| {
1023 let (_, mut env) = make_env();
1024 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1025 assert_bool(
1027 "(= [1 2 3] (s/conform (s/coll-of int?) [1 2 3]))",
1028 true,
1029 &mut env,
1030 );
1031 assert_bool(
1032 "(s/invalid? (s/conform (s/coll-of int?) [1 \"x\"]))",
1033 true,
1034 &mut env,
1035 );
1036 assert_bool(
1038 "(s/invalid? (s/conform (s/coll-of int? :kind vector?) '(1 2 3)))",
1039 true,
1040 &mut env,
1041 );
1042 assert_bool(
1044 "(s/invalid? (s/conform (s/coll-of int? :min-count 3) [1 2]))",
1045 true,
1046 &mut env,
1047 );
1048 assert_bool(
1049 "(s/invalid? (s/conform (s/coll-of int? :max-count 2) [1 2 3]))",
1050 true,
1051 &mut env,
1052 );
1053 assert_bool(
1055 "(= [1 2 3] (s/conform (s/coll-of int? :distinct true) [1 2 3]))",
1056 true,
1057 &mut env,
1058 );
1059 assert_bool(
1060 "(s/invalid? (s/conform (s/coll-of int? :distinct true) [1 1 2]))",
1061 true,
1062 &mut env,
1063 );
1064 assert_bool(
1066 "(= #{1 2 3} (s/conform (s/coll-of int? :into #{}) [1 2 3]))",
1067 true,
1068 &mut env,
1069 );
1070 assert_bool(
1073 "(= '(1 2 3) (s/conform (s/coll-of int?) '(1 2 3)))",
1074 true,
1075 &mut env,
1076 );
1077 assert_bool(
1078 "(= '(3 2 1) (s/conform (s/coll-of int? :into '()) '(1 2 3)))",
1079 true,
1080 &mut env,
1081 );
1082 });
1083 }
1084
1085 #[test]
1086 fn test_spec_map_of_key_handling() {
1087 run_with_big_stack(|| {
1088 let (_, mut env) = make_env();
1089 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1090 assert_bool(
1092 "(= {:a 1 :b 2} (s/conform (s/map-of keyword? int?) {:a 1 :b 2}))",
1093 true,
1094 &mut env,
1095 );
1096 assert_bool(
1097 "(s/invalid? (s/conform (s/map-of keyword? int?) {:a \"x\"}))",
1098 true,
1099 &mut env,
1100 );
1101 assert_bool(
1102 "(s/invalid? (s/conform (s/map-of keyword? int?) {\"not-kw\" 1}))",
1103 true,
1104 &mut env,
1105 );
1106 assert_bool(
1108 r#"(= {"a" 1} (s/conform (s/map-of (s/conformer name) int? :conform-keys true) {:a 1}))"#,
1109 true,
1110 &mut env,
1111 );
1112 });
1113 }
1114
1115 #[test]
1116 fn test_spec_every_vs_coll_of_conform_difference() {
1117 run_with_big_stack(|| {
1118 let (_, mut env) = make_env();
1119 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1120 run("(def elem (s/or :i int? :s string?))", &mut env).unwrap();
1121 assert_bool(
1123 "(= [[:i 1] [:s \"x\"]] (s/conform (s/coll-of elem) [1 \"x\"]))",
1124 true,
1125 &mut env,
1126 );
1127 assert_bool(
1129 "(= [1 \"x\"] (s/conform (s/every elem) [1 \"x\"]))",
1130 true,
1131 &mut env,
1132 );
1133 assert_bool(
1134 "(s/invalid? (s/conform (s/every elem) [1 :bad]))",
1135 true,
1136 &mut env,
1137 );
1138 assert_bool(
1140 "(= {:a 1} (s/conform (s/every-kv keyword? int?) {:a 1}))",
1141 true,
1142 &mut env,
1143 );
1144 assert_bool(
1145 "(s/invalid? (s/conform (s/every-kv keyword? int?) {:a \"x\"}))",
1146 true,
1147 &mut env,
1148 );
1149 });
1150 }
1151
1152 #[test]
1153 fn test_spec_tuple_conform_and_explain() {
1154 run_with_big_stack(|| {
1155 let (_, mut env) = make_env();
1156 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1157 assert_bool(
1158 "(= [1 \"x\"] (s/conform (s/tuple int? string?) [1 \"x\"]))",
1159 true,
1160 &mut env,
1161 );
1162 assert_bool(
1163 "(s/invalid? (s/conform (s/tuple int? string?) [1]))",
1164 true,
1165 &mut env,
1166 );
1167 assert_bool(
1168 "(s/invalid? (s/conform (s/tuple int? string?) 5))",
1169 true,
1170 &mut env,
1171 );
1172 run(
1173 "(def tp (first (:clojure.spec.alpha/problems (s/explain-data (s/tuple int? string?) [1 :bad]))))",
1174 &mut env,
1175 )
1176 .unwrap();
1177 assert_bool("(= [1] (:path tp))", true, &mut env);
1178 assert_bool("(= [1] (:in tp))", true, &mut env);
1179 assert_bool("(= :bad (:val tp))", true, &mut env);
1180 assert_bool(
1182 "(= [1 \"x\"] (s/unform (s/tuple int? string?) (s/conform (s/tuple int? string?) [1 \"x\"])))",
1183 true,
1184 &mut env,
1185 );
1186 });
1187 }
1188
1189 #[test]
1190 fn test_spec_nilable_both_branches_and_explain_shape() {
1191 run_with_big_stack(|| {
1192 let (_, mut env) = make_env();
1193 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1194 assert_bool("(= nil (s/conform (s/nilable int?) nil))", true, &mut env);
1195 assert_bool("(= 5 (s/conform (s/nilable int?) 5))", true, &mut env);
1196 assert_bool(
1197 "(s/invalid? (s/conform (s/nilable int?) \"x\"))",
1198 true,
1199 &mut env,
1200 );
1201 run(
1202 "(def np (:clojure.spec.alpha/problems (s/explain-data (s/nilable int?) \"x\")))",
1203 &mut env,
1204 )
1205 .unwrap();
1206 assert_bool("(= 2 (count np))", true, &mut env);
1207 assert_bool(
1208 "(some (fn [p] (= [:clojure.spec.alpha/nil] (:path p))) np)",
1209 true,
1210 &mut env,
1211 );
1212 assert_bool(
1213 "(some (fn [p] (= [:clojure.spec.alpha/pred] (:path p))) np)",
1214 true,
1215 &mut env,
1216 );
1217 });
1218 }
1219
1220 #[test]
1221 fn test_spec_multi_spec_conform_and_no_method() {
1222 run_with_big_stack(|| {
1223 let (_, mut env) = make_env();
1224 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1225 run("(defmulti event-type :type)", &mut env).unwrap();
1226 run("(s/def :evt/type keyword?)", &mut env).unwrap();
1227 run("(s/def :evt/a (s/keys :req-un [:evt/type]))", &mut env).unwrap();
1228 run("(defmethod event-type :a [_] :evt/a)", &mut env).unwrap();
1229 run(
1230 "(s/def :evt/event (s/multi-spec event-type :type))",
1231 &mut env,
1232 )
1233 .unwrap();
1234 assert_bool(
1235 "(= {:type :a} (s/conform :evt/event {:type :a}))",
1236 true,
1237 &mut env,
1238 );
1239 assert_bool(
1240 "(s/invalid? (s/conform :evt/event {:type :unknown}))",
1241 true,
1242 &mut env,
1243 );
1244 run(
1245 "(def mp (first (:clojure.spec.alpha/problems (s/explain-data :evt/event {:type :unknown}))))",
1246 &mut env,
1247 )
1248 .unwrap();
1249 assert_bool("(= \"no method\" (:reason mp))", true, &mut env);
1250 });
1251 }
1252
1253 #[test]
1254 fn test_spec_conformer_transforms_and_threads_through_and() {
1255 run_with_big_stack(|| {
1256 let (_, mut env) = make_env();
1257 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1258 assert_bool(
1259 "(= 10 (s/conform (s/conformer #(* 2 %)) 5))",
1260 true,
1261 &mut env,
1262 );
1263 assert_bool(
1265 "(= 10 (s/conform (s/and int? (s/conformer #(* 2 %))) 5))",
1266 true,
1267 &mut env,
1268 );
1269 assert_bool(
1270 "(= 5 (s/unform (s/conformer #(* 2 %) #(/ % 2)) 10))",
1271 true,
1272 &mut env,
1273 );
1274 assert_bool(
1276 "(= 10 (s/unform (s/conformer #(* 2 %)) 10))",
1277 true,
1278 &mut env,
1279 );
1280 });
1281 }
1282
1283 #[test]
1284 fn test_spec_int_in_and_double_in_bounds() {
1285 run_with_big_stack(|| {
1286 let (_, mut env) = make_env();
1287 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1288 assert_bool("(s/valid? (s/int-in 0 10) 5)", true, &mut env);
1289 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(
1292 "(s/valid? (s/double-in :min 0.0 :max 10.0) 5.0)",
1293 true,
1294 &mut env,
1295 );
1296 assert_bool(
1297 "(s/valid? (s/double-in :min 0.0 :max 10.0) 20.0)",
1298 false,
1299 &mut env,
1300 );
1301 run("(def nan (/ 0.0 0.0))", &mut env).unwrap();
1305 run("(def pos-inf (/ 1.0 0.0))", &mut env).unwrap();
1306 assert_bool("(s/valid? (s/double-in :NaN? false) nan)", false, &mut env);
1307 assert_bool("(s/valid? (s/double-in) nan)", true, &mut env);
1308 assert_bool(
1309 "(s/valid? (s/double-in :infinite? false) pos-inf)",
1310 false,
1311 &mut env,
1312 );
1313 assert_bool("(s/valid? (s/double-in) pos-inf)", true, &mut env);
1314 assert_bool(
1316 "(= [1 \"x\"] (s/conform (s/nonconforming (s/cat :a int? :b string?)) [1 \"x\"]))",
1317 true,
1318 &mut env,
1319 );
1320 let err = run("(s/inst-in 0 1)", &mut env).unwrap_err();
1322 let msg = format!("{err}");
1323 assert!(
1324 msg.contains("not implemented"),
1325 "expected 'not implemented' in error, got: {msg}"
1326 );
1327 });
1328 }
1329
1330 #[test]
1333 fn test_spec_fdef_registers_qualified_symbol_and_get_spec() {
1334 run_with_big_stack(|| {
1335 let (_, mut env) = make_env();
1336 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1337 run("(defn my-add [a b] (+ a b))", &mut env).unwrap();
1338 assert_bool(
1339 "(= 'user/my-add (s/fdef my-add :args (s/cat :a int? :b int?) :ret int?))",
1340 true,
1341 &mut env,
1342 );
1343 assert_bool("(s/fspec? (s/get-spec 'user/my-add))", true, &mut env);
1344 assert_bool("(some? (:args (s/get-spec 'user/my-add)))", true, &mut env);
1345 assert_bool(
1348 "(= (s/get-spec 'user/my-add) (s/get-spec `my-add))",
1349 true,
1350 &mut env,
1351 );
1352 });
1353 }
1354
1355 #[test]
1356 fn test_spec_instrument_valid_and_invalid_calls() {
1357 run_with_big_stack(|| {
1358 let (_, mut env) = make_env();
1359 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1360 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1361 run("(defn my-add [a b] (+ a b))", &mut env).unwrap();
1362 run(
1363 "(s/fdef my-add :args (s/cat :a int? :b int?) :ret int?)",
1364 &mut env,
1365 )
1366 .unwrap();
1367 run("(stest/instrument 'user/my-add)", &mut env).unwrap();
1368 assert_eq!(run("(my-add 2 3)", &mut env).unwrap(), Value::Long(5));
1370 run(
1372 r#"(def caught
1373 (try (my-add 2 "x") :did-not-throw
1374 (catch Exception e {:msg (ex-message e) :data (ex-data e)})))"#,
1375 &mut env,
1376 )
1377 .unwrap();
1378 assert_bool(
1379 r#"(= "Call to user/my-add did not conform to spec." (:msg caught))"#,
1380 true,
1381 &mut env,
1382 );
1383 assert_bool(
1384 "(= :instrument (:clojure.spec.alpha/failure (:data caught)))",
1385 true,
1386 &mut env,
1387 );
1388 assert_bool(
1389 "(contains? (:data caught) :clojure.spec.test.alpha/caller)",
1390 true,
1391 &mut env,
1392 );
1393 });
1394 }
1395
1396 #[test]
1397 fn test_spec_unstrument_restores_raw_fn() {
1398 run_with_big_stack(|| {
1399 let (_, mut env) = make_env();
1400 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1401 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1402 run("(defn my-pair [a b] [a b])", &mut env).unwrap();
1403 run("(s/fdef my-pair :args (s/cat :a int? :b int?))", &mut env).unwrap();
1404 run("(stest/instrument 'user/my-pair)", &mut env).unwrap();
1405 assert!(run("(my-pair 1 \"x\")", &mut env).is_err());
1406 run("(stest/unstrument 'user/my-pair)", &mut env).unwrap();
1407 assert_bool("(= [1 \"x\"] (my-pair 1 \"x\"))", true, &mut env);
1410 });
1411 }
1412
1413 #[test]
1414 fn test_spec_instrument_affects_call_sites_evaluated_before_instrument() {
1415 run_with_big_stack(|| {
1416 let (_, mut env) = make_env();
1417 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1418 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1419 run("(defn my-mul [a b] (* a b))", &mut env).unwrap();
1420 run("(s/fdef my-mul :args (s/cat :a int? :b int?))", &mut env).unwrap();
1421 assert_eq!(run("(my-mul 2 3)", &mut env).unwrap(), Value::Long(6));
1424 run("(stest/instrument 'user/my-mul)", &mut env).unwrap();
1425 assert!(run("(my-mul 2 \"x\")", &mut env).is_err());
1428 });
1429 }
1430
1431 #[test]
1432 fn test_spec_instrumentable_syms_and_idempotent_double_instrument() {
1433 run_with_big_stack(|| {
1434 let (_, mut env) = make_env();
1435 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1436 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1437 run("(defn my-pair2 [a b] [a b])", &mut env).unwrap();
1438 run("(s/fdef my-pair2 :args (s/cat :a int? :b int?))", &mut env).unwrap();
1439 assert_bool(
1440 "(boolean (some #{'user/my-pair2} (stest/instrumentable-syms)))",
1441 true,
1442 &mut env,
1443 );
1444 run("(stest/instrument 'user/my-pair2)", &mut env).unwrap();
1445 run("(stest/instrument 'user/my-pair2)", &mut env).unwrap(); assert!(run("(my-pair2 1 \"x\")", &mut env).is_err());
1447 run("(stest/unstrument 'user/my-pair2)", &mut env).unwrap();
1451 assert_bool("(= [1 \"x\"] (my-pair2 1 \"x\"))", true, &mut env);
1452 });
1453 }
1454
1455 #[test]
1456 fn test_spec_with_instrument_disabled_bypasses_checks() {
1457 run_with_big_stack(|| {
1458 let (_, mut env) = make_env();
1459 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1460 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1461 run("(defn my-pair3 [a b] [a b])", &mut env).unwrap();
1462 run("(s/fdef my-pair3 :args (s/cat :a int? :b int?))", &mut env).unwrap();
1463 run("(stest/instrument 'user/my-pair3)", &mut env).unwrap();
1464 assert!(run("(my-pair3 1 \"x\")", &mut env).is_err());
1465 assert_bool(
1466 "(stest/with-instrument-disabled (= [1 \"x\"] (my-pair3 1 \"x\")))",
1467 true,
1468 &mut env,
1469 );
1470 assert!(run("(my-pair3 1 \"x\")", &mut env).is_err());
1472 });
1473 }
1474
1475 #[test]
1476 fn test_spec_assert_happy_sad_and_check_asserts_toggle() {
1477 run_with_big_stack(|| {
1478 let (_, mut env) = make_env();
1479 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1480 assert_eq!(run("(s/assert even? 4)", &mut env).unwrap(), Value::Long(4));
1481 assert!(run("(s/assert even? 5)", &mut env).is_err());
1482 run("(s/check-asserts false)", &mut env).unwrap();
1483 assert_eq!(run("(s/assert even? 5)", &mut env).unwrap(), Value::Long(5));
1487 run("(s/check-asserts true)", &mut env).unwrap();
1488 assert!(run("(s/assert even? 5)", &mut env).is_err());
1489 });
1490 }
1491
1492 #[test]
1493 fn test_spec_gen_and_check_throw_not_implemented_and_gen_ns_loads() {
1494 run_with_big_stack(|| {
1495 let (_, mut env) = make_env();
1496 run("(require '[clojure.spec.alpha :as s])", &mut env).unwrap();
1497 run("(require '[clojure.spec.test.alpha :as stest])", &mut env).unwrap();
1498 run("(require '[clojure.spec.gen.alpha :as gen])", &mut env).unwrap();
1499 for src in [
1500 "(s/gen even?)",
1501 "(s/exercise even?)",
1502 "(s/exercise-fn 'user/foo)",
1503 "(stest/check)",
1504 "(stest/check 'user/foo)",
1505 "(stest/check-fn (fn [x] x) even?)",
1506 "(gen/generate :whatever)",
1507 "(gen/sample :whatever)",
1508 "(gen/elements [1 2 3])",
1509 ] {
1510 assert!(run(src, &mut env).is_err(), "expected {src} to throw");
1511 }
1512 });
1513 }
1514
1515 #[cfg(not(target_arch = "wasm32"))]
1518 fn shell_env() -> Env {
1519 let (_, mut env) = make_env();
1520 run(
1521 "(require '[clojure.java.shell :refer [sh with-sh-dir with-sh-env]])",
1522 &mut env,
1523 )
1524 .unwrap();
1525 run(
1528 "(defn big [s n] (loop [s s] (if (>= (count s) n) s (recur (str s s)))))",
1529 &mut env,
1530 )
1531 .unwrap();
1532 env
1533 }
1534
1535 #[cfg(not(target_arch = "wasm32"))]
1536 fn truthy(src: &str, env: &mut Env) {
1537 assert_eq!(run(src, env).unwrap(), Value::Bool(true), "{src}");
1538 }
1539
1540 #[test]
1541 #[cfg(unix)]
1542 fn test_sh_exit_is_data() {
1543 let mut env = shell_env();
1544 truthy(
1545 "(= {:exit 0 :out \"hi\" :err \"\"} (sh \"printf\" \"hi\"))",
1546 &mut env,
1547 );
1548 truthy(
1549 "(= {:exit 3 :out \"\" :err \"e\"} (sh \"sh\" \"-c\" \"printf e >&2; exit 3\"))",
1550 &mut env,
1551 );
1552 truthy(
1553 "(= 137 (:exit (sh \"sh\" \"-c\" \"kill -9 $$\")))",
1554 &mut env,
1555 );
1556 }
1557
1558 #[test]
1559 #[cfg(unix)]
1560 fn test_sh_in_dir_env() {
1561 let mut env = shell_env();
1562 truthy("(= \"piped\" (:out (sh \"cat\" :in \"piped\")))", &mut env);
1563 truthy(
1564 "(= 262144 (count (:out (sh \"cat\" :in (big \"x\" 262144)))))",
1565 &mut env,
1566 );
1567 truthy("(= \"/\\n\" (:out (sh \"pwd\" :dir \"/\")))", &mut env);
1568 truthy(
1569 "(= \"/\\n\" (:out (with-sh-dir \"/\" (sh \"pwd\"))))",
1570 &mut env,
1571 );
1572 truthy(
1573 "(= \"FOO=bar\\n\" (:out (sh \"/usr/bin/env\" :env {\"FOO\" \"bar\"})))",
1574 &mut env,
1575 );
1576 truthy(
1577 "(= \"A=1=2\\n\" (:out (with-sh-env [\"A=1=2\"] (sh \"/usr/bin/env\"))))",
1578 &mut env,
1579 );
1580 truthy(
1582 "(= \"x\" (:out (sh \"printf\" \"x\" :env {\"X\" \"1\"})))",
1583 &mut env,
1584 );
1585 truthy(
1587 "(= 0 (:exit (sh \"true\" :in (big \"y\" 1048576))))",
1588 &mut env,
1589 );
1590 }
1591
1592 #[test]
1593 #[cfg(unix)]
1594 fn test_sh_out_bytes() {
1595 let mut env = shell_env();
1596 truthy(
1597 "(= [97 98] (vec (:out (sh \"printf\" \"ab\" :out-enc :bytes))))",
1598 &mut env,
1599 );
1600 }
1601
1602 #[test]
1603 #[cfg(unix)]
1604 fn test_sh_refusals() {
1605 let mut env = shell_env();
1606 for src in [
1607 "(sh \"cljrs-no-such-program-x\")",
1608 "(sh \"pwd\" :dir \"/no/such/dir\")",
1609 "(sh \"printf\" \"x\" :env {\"A=B\" \"1\"})",
1610 "(sh \"printf\" \"x\" :env {\"A\" \"\\u0000\"})",
1611 "(sh \"printf\" \"x\" :out-enc \"latin1\")",
1612 "(sh :in \"x\")",
1613 "(clojure.rust.process/run [])",
1614 ] {
1615 assert!(run(src, &mut env).is_err(), "expected {src} to throw");
1616 }
1617 }
1618
1619 #[cfg(unix)]
1622 fn clj_string_lit(s: &str) -> String {
1623 let mut out = String::from("\"");
1624 for c in s.chars() {
1625 match c {
1626 '"' => out.push_str("\\\""),
1627 '\\' => out.push_str("\\\\"),
1628 ' '..='~' => out.push(c),
1629 _ => out.push_str(&format!("\\u{:04x}", c as u32)),
1630 }
1631 }
1632 out.push('"');
1633 out
1634 }
1635
1636 #[cfg(unix)]
1639 fn bmp_string(max: usize) -> impl proptest::strategy::Strategy<Value = String> {
1640 use proptest::prelude::*;
1641 prop::collection::vec(prop::char::range('\u{1}', '\u{ffff}'), 0..max)
1642 .prop_map(|cs| cs.into_iter().collect())
1643 }
1644
1645 #[test]
1650 #[cfg(unix)]
1651 fn test_env_value_nul_laws() {
1652 use proptest::test_runner::{Config, TestRunner};
1653 use std::cell::RefCell;
1654 let env = RefCell::new(shell_env());
1655 let mut runner = TestRunner::new(Config::with_cases(48));
1656
1657 runner
1658 .run(&(bmp_string(8), bmp_string(8)), |(pre, post)| {
1659 let v = format!("{pre}\0{post}");
1660 let src = format!(
1661 "(clojure.rust.process/run [\"printf\" \"x\"] {{:env {{\"A\" {}}}}})",
1662 clj_string_lit(&v)
1663 );
1664 proptest::prop_assert!(run(&src, &mut env.borrow_mut()).is_err(), "{src}");
1665 Ok(())
1666 })
1667 .unwrap();
1668
1669 runner
1670 .run(&bmp_string(12), |v| {
1671 let src = format!(
1672 "(let [v {}] (= (str \"A=\" v \"\\n\") \
1673 (:out (clojure.rust.process/run [\"/usr/bin/env\"] {{:env {{\"A\" v}}}}))))",
1674 clj_string_lit(&v)
1675 );
1676 let got = run(&src, &mut env.borrow_mut());
1677 proptest::prop_assert_eq!(got.ok(), Some(Value::Bool(true)), "{}", src);
1678 Ok(())
1679 })
1680 .unwrap();
1681 }
1682
1683 #[test]
1691 #[cfg(unix)]
1692 fn test_process_env_golden() {
1693 const GOLDEN: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/golden/process_env.txt");
1694 const ROWS: &[&str] = &[
1695 "(:out (clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A\" \"ok\"}}))",
1696 "(:out (clojure.rust.process/run [\"/usr/bin/env\"] {:env {\"A\" \"a b=c\"}}))",
1697 "(:out (clojure.rust.process/run [\"/usr/bin/env\"] {:env {:A \"kw\"}}))",
1698 "(clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A\" \"\\u0000\"}})",
1699 "(clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A\" \"a\\u0000b\"}})",
1700 "(clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A=B\" \"1\"}})",
1701 "(clojure.rust.process/run [\"printf\" \"x\"] {:env {\"A\" 1}})",
1702 "(clojure.rust.process/run [\"printf\" \"x\"] {:env [\"A\" \"1\"]})",
1703 "(clojure.rust.process/run [])",
1704 ];
1705 let mut env = shell_env();
1706 let mut actual = String::new();
1707 for src in ROWS {
1708 let outcome = match run(src, &mut env) {
1709 Ok(v) => format!("{v:?}"),
1710 Err(e) => {
1711 let text = format!("{e:?}").replace('\n', " ");
1712 let msg = text
1713 .split("message: \"")
1714 .nth(1)
1715 .and_then(|s| s.split('"').next())
1716 .or_else(|| text.split('"').nth(1))
1717 .map(str::to_string)
1718 .unwrap_or(text);
1719 format!("ERROR {msg}")
1720 }
1721 };
1722 actual.push_str(&format!("{src}\n => {outcome}\n"));
1723 }
1724 if std::env::var_os("UPDATE_GOLDEN").is_some() {
1725 std::fs::create_dir_all(std::path::Path::new(GOLDEN).parent().unwrap()).unwrap();
1726 std::fs::write(GOLDEN, &actual).unwrap();
1727 return;
1728 }
1729 let expected = std::fs::read_to_string(GOLDEN)
1730 .unwrap_or_else(|e| panic!("{GOLDEN}: {e}; run with UPDATE_GOLDEN=1 to create it"));
1731 assert_eq!(
1732 actual, expected,
1733 "process :env answers changed; see {GOLDEN}"
1734 );
1735 }
1736
1737 #[test]
1738 #[cfg(not(target_arch = "wasm32"))]
1739 fn test_process_run_denied_under_transaction_policy() {
1740 use cljrs_runtime::env::policy::{TransactionPolicyGuard, check_native};
1741 assert!(check_native(process::RUN_NAME).is_ok());
1742 let _guard = TransactionPolicyGuard::install();
1743 assert!(check_native(process::RUN_NAME).is_err());
1744 }
1745}