(ns babashka.main-test (:require [babashka.main :as main] [babashka.test-utils :as test-utils] [clojure.edn :as edn] [clojure.java.io :as io] [clojure.java.shell :refer [sh]] [clojure.string :as str] [clojure.test :as test :refer [deftest is testing]] [sci.core :as sci])) (defn bb [input & args] (edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args)))) (deftest parse-opts-test (is (= 123 (bb nil "(println 123)"))) (is (= 123 (bb nil "-e" "(println 123)"))) (is (= 123 (bb nil "--eval" "(println 123)"))) (testing "distinguish automatically between expression or file name" (is (= {:result 8080} (bb nil "test/babashka/scripts/tools.cli.bb"))) (is (thrown-with-msg? Exception #"does not exist" (bb nil "foo.clj"))) (is (thrown-with-msg? Exception #"does not exist" (bb nil "-help"))))) (deftest main-test (testing "-io behaves as identity" (= "foo\nbar\n" (test-utils/bb "foo\nbar\n" "-io" "*input*"))) (testing "if and when" (is (= 1 (bb 0 '(if (zero? *input*) 1 2)))) (is (= 2 (bb 1 '(if (zero? *input*) 1 2)))) (is (= 1 (bb 0 '(when (zero? *input*) 1)))) (is (nil? (bb 1 '(when (zero? *input*) 1))))) (testing "and and or" (is (= false (bb 0 '(and false true *input*)))) (is (= 0 (bb 0 '(and true true *input*)))) (is (= 1 (bb 1 '(or false false *input*)))) (is (= false (bb false '(or false false *input*)))) (is (= 3 (bb false '(or false false *input* 3))))) (testing "fn" (is (= 2 (bb 1 "(#(+ 1 %) *input*)"))) (is (= [1 2 3] (bb 1 "(map #(+ 1 %) [0 1 2])"))) (is (= 1 (bb 1 "(#(when (odd? *input*) *input*))")))) (testing "map" (is (= [1 2 3] (bb 1 '(map inc [0 1 2]))))) (testing "keep" (is (= [false true false] (bb 1 '(keep odd? [0 1 2]))))) (testing "->" (is (= 4 (bb 1 '(-> *input* inc inc (inc)))))) (testing "->>" (is (= 10 (edn/read-string (test-utils/bb "foo\n\baar\baaaaz" "-i" "(->> *input* (map count) (apply max))"))))) (testing "literals" (is (= {:a 4 :b {:a 2} :c [1 1] :d #{1 2}} (bb 1 '{:a (+ 1 2 *input*) :b {:a (inc *input*)} :c [*input* *input*] :d #{*input* (inc *input*)}})))) (testing "shuffle the contents of a file" (let [in "foo\n Clojure is nice. \nbar\n If you're nice to clojure. " in-lines (set (str/split in #"\n")) out (test-utils/bb in "-io" (str '(shuffle *input*))) out-lines (set (str/split out #"\n"))] (is (= in-lines out-lines)))) (testing "find occurrences in file by line number" (is (= '(1 3) (-> (bb "foo\n Clojure is nice. \nbar\n If you're nice to clojure. " "-i" "(map-indexed #(-> [%1 %2]) *input*)") (bb "(keep #(when (re-find #\"(?i)clojure\" (second %)) (first %)) *input*)")))))) (deftest println-test (is (= "hello\n" (test-utils/bb nil "(println \"hello\")")))) (deftest input-test (testing "bb doesn't wait for input if *input* isn't used" (is (= "2\n" (with-out-str (main/main "(inc 1)")))))) (deftest System-test (let [res (bb nil "-f" "test/babashka/scripts/System.bb")] (is (= "bar" (second res))) (doseq [s res] (is (not-empty s)))) (let [out (java.io.StringWriter.) err (java.io.StringWriter.) exit-code (sci/with-bindings {sci/out out sci/err err} (binding [*out* out *err* err] (main/main "--time" "(println \"Hello world!\") (System/exit 42)")))] (is (= (str out) "Hello world!\n")) (is (re-find #"took" (str err))) (is (= 42 exit-code)))) (deftest malformed-command-line-args-test (is (thrown-with-msg? Exception #"File does not exist: non-existing\n" (bb nil "-f" "non-existing")))) (deftest ssl-test (let [resp (bb nil "(slurp \"https://www.google.com\")")] (is (re-find #"doctype html" resp)))) (deftest stream-test (is (= "2\n3\n4\n" (test-utils/bb "1 2 3" "--stream" "(inc *input*)"))) (is (= "2\n3\n4\n" (test-utils/bb "{:x 2} {:x 3} {:x 4}" "--stream" "(:x *input*)"))) (let [x "foo\n\bar\n"] (is (= x (test-utils/bb x "--stream" "-io" "*input*")))) (let [x "f\n\b\n"] (is (= x (test-utils/bb x "--stream" "-io" "(subs *input* 0 1)"))))) (deftest load-file-test (let [tmp (java.io.File/createTempFile "script" ".clj")] (spit tmp "(defn foo [x y] (+ x y)) (defn bar [x y] (* x y))") (is (= "120\n" (test-utils/bb nil (format "(load-file \"%s\") (bar (foo 10 30) 3)" (.getPath tmp))))))) (deftest eval-test (is (= "120\n" (test-utils/bb nil "(eval '(do (defn foo [x y] (+ x y)) (defn bar [x y] (* x y)) (bar (foo 10 30) 3)))")))) (deftest preloads-test ;; THIS TEST REQUIRES: ;; export BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")' (when (System/getenv "BABASHKA_PRELOADS_TEST") (is (= "foobar" (bb nil "(str (__bb__foo) (__bb__bar))"))))) (deftest io-test (is (true? (bb nil "(.exists (io/file \"README.md\"))"))) (is (true? (bb nil "(.canWrite (io/file \"README.md\"))")))) (deftest pipe-test (when test-utils/native? (let [out (:out (sh "bash" "-c" "./bb -o '(range)' | ./bb --stream '(* *input* *input*)' | head -n10")) out (str/split-lines out) out (map edn/read-string out)] (is (= (take 10 (map #(* % %) (range))) out)))) (when test-utils/native? (let [out (:out (sh "bash" "-c" "./bb -O '(repeat \"dude\")' | ./bb --stream '(str *input* \"rino\")' | ./bb -I '(take 3 *input*)'")) out (edn/read-string out)] (is (= '("duderino" "duderino" "duderino") out))))) (deftest lazy-text-in-test (when test-utils/native? (let [out (:out (sh "bash" "-c" "yes | ./bb -i '(take 2 *input*)'")) out (edn/read-string out)] (is (= '("y" "y") out))))) (deftest future-test (is (= 6 (bb nil "@(future (+ 1 2 3))")))) (deftest process-builder-test (is (str/includes? (bb nil " (def ls (-> (ProcessBuilder. [\"ls\"]) (.start))) (def input (.getOutputStream ls)) (.write (io/writer input) \"hello\") ;; dummy test just to see if this works (def output (.getInputStream ls)) (assert (int? (.waitFor ls))) (slurp output)") "LICENSE"))) (deftest create-temp-file-test (let [temp-dir-path (System/getProperty "java.io.tmpdir")] (is (= true (bb nil (format "(let [tdir (io/file \"%s\") tfile (File/createTempFile \"ctf\" \"tmp\" tdir)] (.deleteOnExit tfile) ; for cleanup (.exists tfile))" temp-dir-path)))))) (deftest wait-for-port-test (let [server (test-utils/start-server! 1777)] (is (= 1777 (:port (bb nil "(wait/wait-for-port \"127.0.0.1\" 1777)")))) (test-utils/stop-server! server) (is (= :timed-out (bb nil "(wait/wait-for-port \"127.0.0.1\" 1777 {:default :timed-out :timeout 50})")))) (let [edn (bb nil (io/file "test" "babashka" "scripts" "socket_server.bb"))] (is (= "127.0.0.1" (:host edn))) (is (= 1777 (:port edn))) (is (number? (:took edn))))) (deftest wait-for-path-test (let [temp-dir-path (System/getProperty "java.io.tmpdir")] (is (not= :timed-out (bb nil (format "(let [tdir (io/file \"%s\") tfile (File/createTempFile \"wfp\" \"tmp\" tdir) tpath (.getPath tfile)] (.delete tfile) ; delete now, but re-create it in a future (future (Thread/sleep 50) (shell/sh \"touch\" tpath)) (wait/wait-for-path tpath {:default :timed-out :timeout 100}) (.delete tfile))" temp-dir-path)))) (is (= :timed-out (bb nil (format "(let [tdir (io/file \"%s\") tfile (File/createTempFile \"wfp-to\" \"tmp\" tdir) tpath (.getPath tfile)] (.delete tfile) ; for timing out test and cleanup (wait/wait-for-path tpath {:default :timed-out :timeout 100}))" temp-dir-path)))))) (deftest async-test (is (= "process 2\n" (test-utils/bb nil " (defn async-command [& args] (async/thread (apply shell/sh \"bash\" \"-c\" args))) (-> (async/alts!! [(async-command \"sleep 2 && echo process 1\") (async-command \"sleep 1 && echo process 2\")]) first :out str/trim println)")))) (deftest tools-cli-test (is (= {:result 8080} (bb nil "test/babashka/scripts/tools.cli.bb")))) (deftest try-catch-test (is (zero? (bb nil "(try (/ 1 0) (catch ArithmeticException _ 0))")))) (deftest reader-conditionals-test (is (= :hello (bb nil "#?(:clj (in-ns 'foo)) (println :hello)"))) (is (= :hello (bb nil "#?(:bb :hello :default :bye)")))) (deftest csv-test (is (= '(["Adult" "87727"] ["Elderly" "43914"] ["Child" "33411"] ["Adolescent" "29849"] ["Infant" "15238"] ["Newborn" "10050"] ["In Utero" "1198"]) (bb nil (.getPath (io/file "test" "babashka" "scripts" "csv.bb")))))) (deftest assert-test ;; assert was first implemented in bb but moved to sci later (is (thrown-with-msg? Exception #"should-be-true" (bb nil "(def should-be-true false) (assert should-be-true)")))) (deftest Pattern-test (is (= ["1" "2" "3"] (bb nil "(vec (.split (java.util.regex.Pattern/compile \"f\") \"1f2f3\"))"))) (is (true? (bb nil "(some? java.util.regex.Pattern/CANON_EQ)")))) (deftest writer-test (let [tmp-file (java.io.File/createTempFile "bbb" "bbb") path (.getPath tmp-file)] (bb nil (format "(with-open [w (io/writer \"%s\")] (.write w \"foobar\n\") (.append w \"barfoo\n\") nil)" path)) (is (= "foobar\nbarfoo\n" (slurp path))))) (deftest binding-test (is (= 6 (bb nil "(def w (java.io.StringWriter.)) (binding [clojure.core/*out* w] (println \"hello\")) (count (str w))")))) (deftest with-out-str-test (is (= 6 (bb nil "(count (with-out-str (println \"hello\")))")))) (deftest with-in-str-test (is (= 5 (bb nil "(count (with-in-str \"hello\" (read-line)))")))) (deftest java-nio-test (let [f (java.io.File/createTempFile "foo" "bar") temp-path (.getPath f) p (.toPath (io/file f)) p' (.resolveSibling p "f2") f2 (.toFile p')] (bb nil (format "(let [f (io/file \"%s\") p (.toPath (io/file f)) p' (.resolveSibling p \"f2\")] (.delete (.toFile p')) (dotimes [_ 2] (try (java.nio.file.Files/copy p p' (into-array java.nio.file.CopyOption [])) (catch java.nio.file.FileAlreadyExistsException _ (java.nio.file.Files/copy p p' (into-array [java.nio.file.StandardCopyOption/REPLACE_EXISTING]))))))" temp-path)) (is (.exists f2)))) (deftest future-print-test (testing "the root binding of sci/*out*" (is (= "hello" (bb nil "@(future (prn \"hello\"))"))))) (deftest Math-test (is (== 8.0 (bb nil "(Math/pow 2 3)")))) (deftest Base64-test (is (= "babashka" (bb nil "(String. (.decode (java.util.Base64/getDecoder) (.encode (java.util.Base64/getEncoder) (.getBytes \"babashka\"))))")))) (deftest Thread-test (is (= "hello" (bb nil "(doto (java.lang.Thread. (fn [] (prn \"hello\"))) (.start) (.join)) nil")))) (deftest dynvar-test (is (= 1 (bb nil "(binding [*command-line-args* 1] *command-line-args*)"))) (is (= 1 (bb nil "(binding [*input* 1] *input*)")))) (deftest file-in-error-msg-test (is (thrown-with-msg? Exception #"error.bb" (bb nil (.getPath (io/file "test" "babashka" "scripts" "error.bb")))))) (deftest compatibility-test (is (true? (bb nil "(set! *warn-on-reflection* true)")))) (deftest clojure-main-repl-test (is (= "\"> foo!\\nnil\\n> \"\n" (test-utils/bb nil " (defn foo [] (println \"foo!\")) (with-out-str (with-in-str \"(foo)\" (clojure.main/repl :init (fn []) :prompt (fn [] (print \"> \")))))")))) (deftest command-line-args-test (is (true? (bb nil "(nil? *command-line-args*)"))) (is (= ["1" "2" "3"] (bb nil "*command-line-args*" "1" "2" "3")))) (deftest need-constructors-test (testing "the clojure.lang.Delay constructor works" (is (= 1 (bb nil "@(delay 1)")))) (testing "the clojure.lang.MapEntry constructor works" (is (true? (bb nil "(= (first {1 2}) (clojure.lang.MapEntry. 1 2))"))))) ;;;; Scratch (comment (dotimes [_ 10] (wait-for-port-test)) )