babashka/test/babashka/main_test.clj
Bob 7df81f1273
make core.async and rewrite-clj integral (#1014)
* make core.async integral (not a feature)

tasks (in particular, parallel tasks) require core.async

* make rewrite_clj integral (not a feature)

* cleanup mistakes
2021-09-23 18:15:01 +02:00

758 lines
32 KiB
Clojure

(ns babashka.main-test
{:clj-kondo/config '{:linters {:unresolved-symbol {:exclude [working?]}}}}
(: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]]
[flatland.ordered.map :refer [ordered-map]]
[sci.core :as sci]))
(defn bb [input & args]
(test-utils/normalize
(edn/read-string
{:readers *data-readers*
:eof nil}
(apply test-utils/bb (when (some? input) (str input)) (map str args)))))
(deftest parse-opts-test
(is (= "1667"
(:nrepl (main/parse-opts ["--nrepl-server"]))))
(is (= "1666"
(:socket-repl (main/parse-opts ["--socket-repl"]))))
(is (= {:nrepl "1667", :classpath "src"}
(main/parse-opts ["--nrepl-server" "-cp" "src"])))
(is (= {:nrepl "1667", :classpath "src"}
(main/parse-opts ["-cp" "src" "nrepl-server"])))
(is (= {:socket-repl "1666", :expressions ["123"]}
(main/parse-opts ["--socket-repl" "-e" "123"])))
(is (= {:socket-repl "1666", :expressions ["123"]}
(main/parse-opts ["--socket-repl" "1666" "-e" "123"])))
(is (= {:nrepl "1666", :expressions ["123"]}
(main/parse-opts ["--nrepl-server" "1666" "-e" "123"])))
(is (= {:classpath "src"
:uberjar "foo.jar"}
(main/parse-opts ["--classpath" "src" "uberjar" "foo.jar"])))
(is (= {:classpath "src"
:uberjar "foo.jar"
:debug true}
(main/parse-opts ["--debug" "--classpath" "src" "uberjar" "foo.jar"])))
(is (= "src" (:classpath (main/parse-opts ["--classpath" "src"]))))
(is (:debug (main/parse-opts ["--debug"])))
(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"))))
(is (= "1 2 3" (bb nil "-e" "(require '[clojure.string :as str1])" "-e" "(str1/join \" \" [1 2 3])")))
(is (= '("-e" "1") (bb nil "-e" "*command-line-args*" "--" "-e" "1")))
(let [v (bb nil "--describe")]
(is (:babashka/version v))
(is (:feature/xml v)))
(is (= {:force? true} (main/parse-opts ["--force"]))))
(deftest version-test
(is (= [1 0 0] (main/parse-version "1.0.0-SNAPSHOT")))
(is (main/satisfies-min-version? "0.1.0"))
(is (main/satisfies-min-version? "0.1.0-SNAPSHOT"))
(is (not (main/satisfies-min-version? "300.0.0")))
(is (not (main/satisfies-min-version? "300.0.0-SNAPSHOT"))))
(deftest print-error-test
(is (thrown-with-msg? Exception #"java.lang.NullPointerException"
(bb nil "(subs nil 0 0)"))))
(deftest input-test
(testing "-io behaves as identity"
(is (= "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*)")))))
(testing "ordered/map data reader works"
(is (= "localhost" (bb "#ordered/map ([:test \"localhost\"])"
"(:test *input*)"))))
(testing "bb doesn't wait for input if *input* isn't used"
(is (= "2\n" (test-utils/normalize (with-out-str (main/main "(inc 1)")))))))
(deftest println-test
(is (= "hello\n" (test-utils/bb nil "(println \"hello\")"))))
(deftest System-test
(let [res (bb nil "-f" "test/babashka/scripts/System.bb")]
(is (= "bar" (second res)))
(doseq [s res]
(is (not-empty s)))))
(deftest malformed-command-line-args-test
(is (thrown-with-msg? Exception #"File does not exist: non-existing"
(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")]
(.deleteOnExit tmp)
(spit tmp "(ns foo) (defn foo [x y] (+ x y)) (defn bar [x y] (* x y))")
(is (= "120\n" (test-utils/bb nil (format "(load-file \"%s\") (foo/bar (foo/foo 10 30) 3)"
(test-utils/escape-file-paths (.getPath tmp))))))
(testing "namespace is restored after load file"
(is (= 'start-ns
(bb nil (format "(ns start-ns) (load-file \"%s\") (ns-name *ns*)"
(test-utils/escape-file-paths (.getPath tmp)))))))))
(deftest repl-source-test
(let [tmp (java.io.File/createTempFile "lib" ".clj")
name (str/replace (.getName tmp) ".clj" "")
dir (.getParent tmp)]
(.deleteOnExit tmp)
(testing "print source from loaded file"
(spit tmp (format "
(ns %s)
(defn foo [x y]
(+ x y))" name))
(is (= "(defn foo [x y]\n (+ x y))\n"
(bb nil (format "
(load-file \"%s\")
(require '[clojure.repl :refer [source]])
(with-out-str (source %s/foo))"
(test-utils/escape-file-paths (.getPath tmp))
name)))))
(testing "print source from file on classpath"
(is (= "(defn foo [x y]\n (+ x y))\n"
(bb nil
"-cp" dir
"-e" (format "(require '[clojure.repl :refer [source]] '[%s])" name)
"-e" (format "(with-out-str (source %s/foo))" name)))))))
(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\"))"))))
; skipped because the windows shell doesn't seem to deal well with infinite things
(deftest ^:skip-windows pipe-test
(when (and test-utils/native?
(not main/windows?))
(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)))
(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 ^:windows-only win-pipe-test
(when (and test-utils/native? main/windows?)
(let [out (:out (sh "cmd" "/c" ".\\bb -O \"(repeat 50 \\\"dude\\\")\" |"
".\\bb --stream \"(str *input* \\\"rino\\\")\" |"
".\\bb -I \"(take 3 *input*)\""))
out (edn/read-string out)]
(is (= '("duderino" "duderino" "duderino") out)))))
(deftest ^:skip-windows 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 promise-test
(is (= :timeout (bb nil "(deref (promise) 1 :timeout)")))
(is (= :ok (bb nil "(let [x (promise)]
(deliver x :ok)
@x)"))))
(deftest process-builder-test
(let [cmd-line (if main/windows?
"[\"cmd\" \"/c\" \"dir\"]"
"[\"ls\"]")]
(is (str/includes? (bb nil (str "
(def pb (ProcessBuilder. " cmd-line "))
(def env (.environment pb))
(.put env \"FOO\" \"BAR\") ;; test for issue 460
(def ls (-> pb (.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")))
(testing "bb is able to kill subprocesses created by ProcessBuilder"
(when test-utils/native?
(let [process-count (if main/windows? 6 3)
output (test-utils/bb nil (io/file "test" "babashka" "scripts" "kill_child_processes.bb"))
parsed (edn/read-string (format "[%s]" output))]
(is (every? number? parsed))
(is (= process-count (count parsed)))))))
(deftest create-temp-file-test
(is (= true
(bb nil "(let [tfile (File/createTempFile \"ctf\" \"tmp\")]
(.deleteOnExit tfile) ; for cleanup
(.exists tfile))"))))
(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 ^:skip-windows 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 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))")))
(is (= :got-it (bb nil "
(defn foo []
(throw (java.util.MissingResourceException. \"o noe!\" \"\" \"\")))
(defn bar
[]
(try (foo)
(catch java.util.MissingResourceException _
:got-it)))
(bar)
"))))
(deftest reader-conditionals-test
(is (= :hello (bb nil "#?(:bb :hello :default :bye)")))
(is (= :hello (bb nil "#? (:bb :hello :default :bye)")))
(is (= :hello (bb nil "#?(:clj :hello :bb :bye)")))
(is (= [1 2] (bb nil "[1 2 #?@(:bb [] :clj [1])]"))))
(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 (test-utils/escape-file-paths (.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 (= (if main/windows? 7 6)
(bb nil "(def w (java.io.StringWriter.))
(binding [clojure.core/*out* w]
(println \"hello\"))
(count (str w))"))))
(deftest with-out-str-test
(is (= (if main/windows? 7 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 (test-utils/escape-file-paths (.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))
(let [v (bb nil "-f" (.getPath (io/file "test-resources" "babashka" "glob.clj")))]
(is (vector? v))
(is (.exists (io/file (first v)))))))
(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*)"))))
(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
(let [expected-outcome (if main/windows?
"\"> foo!\\r\\nnil\\r\\n> \"\n"
"\"> foo!\\nnil\\n> \"\n")]
(is (= expected-outcome (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 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))")))))
(deftest clojure-data-xml-test
(is (= "<?xml version=\"1.0\" encoding=\"UTF-8\"?><items><item>1</item><item>2</item></items>"
(bb nil "(let [xml (xml/parse-str \"<items><item>1</item><item>2</item></items>\")] (xml/emit-str xml))")))
(is (= "0.0.87-SNAPSHOT" (bb nil "examples/pom_version_get.clj" (.getPath (io/file "test-resources" "pom.xml")))))
(is (= ":xmlns.DAV%3A/propfind"
(bb nil "(clojure.data.xml/alias-uri :D \"DAV:\") (str ::D/propfind)"))))
(deftest uberscript-test
(let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")]
(.deleteOnExit tmp-file)
(is (empty? (bb nil "--uberscript" (test-utils/escape-file-paths (.getPath tmp-file)) "-e" "(System/exit 1)")))
(is (= "(System/exit 1)" (slurp tmp-file)))))
(deftest unrestricted-access
(testing "babashka is allowed to mess with built-in vars"
(is (= {} (bb nil "
(def assoc2 assoc) (alter-var-root #'clojure.core/assoc (constantly dissoc))
(let [res (assoc {:a 1} :a 2)]
(alter-var-root #'clojure.core/assoc (constantly assoc2))
res)")))))
(deftest pprint-test
(testing "writer"
(is (string? (bb nil "(let [sw (java.io.StringWriter.)] (clojure.pprint/pprint (range 10) sw) (str sw))"))))
(testing "*print-right-margin*"
(is (= "(0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9)\n"
(bb nil "
(let [sw (java.io.StringWriter.)]
(binding [clojure.pprint/*print-right-margin* 5]
(clojure.pprint/pprint (range 10) sw)) (str sw))")))
(is (= "(0 1 2 3 4 5 6 7 8 9)\n"
(bb nil "
(let [sw (java.io.StringWriter.)]
(binding [clojure.pprint/*print-right-margin* 50]
(clojure.pprint/pprint (range 10) sw)) (str sw))"))))
(testing "print-table writes to sci/out"
(is (str/includes? (test-utils/bb "(with-out-str (clojure.pprint/print-table [{:a 1} {:a 2}]))") "----")))
(testing "cl-format outputs"
(testing "cl-format true writes to sci/out"
(is (= "[1, 2, 3]" (bb nil "(with-out-str (clojure.pprint/cl-format true \"~<[~;~@{~w~^, ~:_~}~;]~:>\" [1,2,3]))"))))
(testing "cl-format nil returns a string"
(is (= "forty-two" (bb nil "(clojure.pprint/cl-format nil \"~R\" 42)"))))
(testing "cl-format with a writer uses the writer"
(is (= "1,234,567 " (bb nil "
(let [sw (java.io.StringWriter.)]
(clojure.pprint/cl-format sw \"~15@<~:d~>\" 1234567)
(str sw))")))))
(testing "formatter-out"
(is (= "[1, 2, 3]\n"
(bb nil (pr-str '(do (require '[clojure.pprint :as pprint])
(def print-array (pprint/formatter-out "~<[~;~@{~w~^, ~:_~}~;]~:>"))
(pprint/with-pprint-dispatch
#(if (seqable? %)
(print-array %)
(print %))
(with-out-str (pprint/pprint [1 2 3]))))))))))
(deftest read-string-test
(testing "namespaced keyword via alias"
(is (= :clojure.string/foo
(bb nil "(ns foo (:require [clojure.string :as str])) (read-string \"::str/foo\")")))))
(deftest available-stream-test
(is (= 0 (bb nil "(.available System/in)"))))
(deftest ^:skip-windows file-reader-test
(when (str/includes? (str/lower-case (System/getProperty "os.name")) "linux")
(let [v (bb nil "(slurp (io/reader (java.io.FileReader. \"/proc/loadavg\")))")]
(prn "output:" v)
(is v))))
(deftest win-file-reader-test
(let [v (bb nil "(slurp (io/reader (java.io.FileReader. \"test-resources/babashka/empty.clj\")))")]
(prn "output:" v)
(is (empty? v))))
(deftest ^:skip-windows download-and-extract-test
;; Disabled because Github throttles bandwidth and this makes for a very slow test.
;; TODO: refactor into individual unit tests
;; One for downloading a small file and one for unzipping.
#_(is (try (= 6 (bb nil (io/file "test" "babashka" "scripts" "download_and_extract_zip.bb")))
(catch Exception e
(is (str/includes? (str e) "timed out"))))))
(deftest get-message-on-exception-info-test
(is "foo" (bb nil "(try (throw (ex-info \"foo\" {})) (catch Exception e (.getMessage e)))")))
(deftest pushback-reader-test
(is (= "foo" (bb nil "
(require '[clojure.java.io :as io])
(let [pb (java.io.PushbackInputStream. (java.io.ByteArrayInputStream. (.getBytes \"foo\")))]
(.unread pb (.read pb))
(slurp pb))"))))
(deftest delete-on-exit-test
(when test-utils/native?
(let [f (java.io.File/createTempFile "foo" "bar")
p (test-utils/escape-file-paths (.getPath f))]
(bb nil (format "(.deleteOnExit (io/file \"%s\"))" p))
(is (false? (.exists f))))))
(deftest yaml-test
(is (str/starts-with?
(bb nil "(yaml/generate-string [{:name \"John Smith\", :age 33} {:name \"Mary Smith\", :age 27}])")
"-")))
(deftest arrays-copy-of-test
(is (= "foo" (bb nil "(String. (java.util.Arrays/copyOf (.getBytes \"foo\") 3))"))))
(deftest data-readers-test
(is (= 2 (bb nil "(set! *data-readers* {'t/tag inc}) #t/tag 1"))))
(deftest ordered-test
(is (= (ordered-map :a 1 :b 2) (bb nil "(flatland.ordered.map/ordered-map :a 1 :b 2)"))))
(deftest data-diff-test
(is (= [[nil 1] [nil 2] [1 nil 2]] (bb nil "(require '[clojure.data :as d]) (d/diff [1 1 2] [1 2 2])"))))
(deftest version-property-test
(is (= "true\ntrue\nfalse\n"
(test-utils/bb nil (.getPath (io/file "test-resources" "babashka" "version.clj"))))))
(defmethod clojure.test/assert-expr 'working? [msg form]
(let [body (next form)]
`(do ~@body
(clojure.test/do-report {:type :pass, :message ~msg,
:expected :success, :actual :success}))))
(deftest empty-expressions-test
(testing "bb executes the empty file and doesn't start a REPL"
(is (working? (test-utils/bb nil (.getPath (io/file "test-resources" "babashka" "empty.clj"))))))
(testing "bb executes the empty expression and doesn't start a REPL"
(is (working? (test-utils/bb nil "-e" "")))))
(deftest file-property-test
(is (= "true\nfalse\n"
(test-utils/bb nil (.getPath (io/file "test-resources" "babashka" "file_property1.clj")))))
(is (= "true\n"
(test-utils/bb nil (.getPath (io/file "test-resources" "babashka" "file_property2.clj")))))
(is (apply =
(bb nil (.getPath (io/file "test" "babashka" "scripts" "simple_file_var.bb")))))
(let [res (bb nil (.getPath (io/file "test" ".." "test" "babashka"
"scripts" "simple_file_var.bb")))]
(is (apply = res))
(is (str/includes? (first res) ".."))))
(deftest file-location-test
(is (thrown-with-msg?
Exception #"file_location2.clj"
(test-utils/bb nil (.getPath (io/file "test-resources" "babashka" "file_location1.clj"))))))
(deftest preloads-file-location-test
(when (System/getenv "BABASHKA_PRELOADS_TEST")
(is (thrown-with-msg?
Exception #"preloads"
(test-utils/bb nil (.getPath (io/file "test-resources" "babashka" "file_location_preloads.clj")))))))
(deftest repl-test
(is (str/includes? (test-utils/bb "(ns foo) ::foo" "--repl") ":foo/foo"))
(is (str/includes? (test-utils/bb "[*warn-on-reflection* (set! *warn-on-reflection* true) *warn-on-reflection*]")
"[false true true]"))
(when-not test-utils/native?
(let [sw (java.io.StringWriter.)]
(sci/with-bindings {sci/err sw}
(test-utils/bb {:in "x" :err sw} "--repl"))
(is (str/includes? (str sw) "Could not resolve symbol: x [at <repl>:1:1]")))))
(deftest java-stream-test
(is (every? number? (bb nil "(take 2 (iterator-seq (.iterator (.doubles (java.util.Random.)))))"))))
(deftest read+string-test
(is (= '[:user/foo "::foo"]
(bb nil "(read+string (clojure.lang.LineNumberingPushbackReader. (java.io.StringReader. \"::foo\")))"))))
(deftest iterable-test
(is (true? (bb nil "
(defn iter [coll]
(if (instance? java.lang.Iterable coll)
(.iterator ^java.lang.Iterable coll)
(let [s (or (seq coll) [])]
(.iterator ^java.lang.Iterable s))))
(= [1 2 3] (iterator-seq (iter [1 2 3])))"))))
(deftest var-print-method-test
(when test-utils/native?
(is (bb nil "(defmethod print-method sci.lang.IVar [o w] (.write w (str :foo (symbol o)))) (def x 1) (= \":foouser/x\" (pr-str #'x))"))
(is (= :foouser/x (bb nil "(defmethod print-method sci.lang.IVar [o w] (.write w (str :foo (symbol o)))) (def x 1)")))))
(deftest stdout-interop-test
(when test-utils/native?
(is (= 'Something (bb nil "(.print (System/out) \"Something\")")))))
(deftest byte-buffer-test
(testing "interop with HeapByteBuffer"
(is (= 42 (bb nil "(count (.array (java.nio.ByteBuffer/allocate 42)))"))))
(testing "interop with HeapByteByfferR"
(is (bb nil "(.hasRemaining (.asReadOnlyBuffer (java.nio.ByteBuffer/allocate 42)))")))
(is (bb nil "
(import 'java.io.RandomAccessFile)
(import 'java.nio.channels.FileChannel$MapMode)
(def raf (RandomAccessFile. \"/tmp/binf-example.dat\" \"rw\"))
;; DirectByteBuffer
(def view (-> raf .getChannel (.map FileChannel$MapMode/READ_WRITE 0 4)))
;; interop with DirectByteBuffer
(.load view)
(.force view)
true"))
(is (bb nil "
(import 'java.io.RandomAccessFile)
(import 'java.nio.channels.FileChannel$MapMode)
(def raf (RandomAccessFile. \"/tmp/binf-example.dat\" \"r\"))
;; DirectByteBuffer
(def view (-> raf .getChannel (.map FileChannel$MapMode/READ_ONLY 0 4)))
;; interop with DirectByteBufferR
(.load view)
(.force view)
true")))
(deftest secure-random-test
(let [prog '(do (import 'java.security.SecureRandom 'java.util.Base64)
(let [random (SecureRandom.)
base64 (.withoutPadding (Base64/getUrlEncoder))]
(defn generate-token []
(let [buffer (byte-array 32)]
(.nextBytes random buffer)
(.encodeToString base64 buffer))))
(generate-token))]
(is (string? (bb nil (str prog))))))
(deftest with-precision-test
(is (= 0.33333333333333333333M (bb nil "(with-precision 20 (/ 1M 3))")))
(is (= 0.33333333333333333334M (bb nil "(with-precision 20 :rounding CEILING (/ 1M 3))"))))
(deftest doc-test
(test-utils/with-config {:paths ["test-resources/task_scripts"]}
(is (str/includes? (apply test-utils/bb nil
(map str ["doc" "tasks"]))
"This is task ns docstring."))
(is (str/includes? (apply test-utils/bb nil
(map str ["doc" "tasks/foo"]))
"Foo docstring"))
(is (str/includes? (apply test-utils/bb nil
(map str ["doc" "tasks/-main"]))
"Main docstring"))
(is (str/includes? (apply test-utils/bb nil
(map str ["doc" "with-precision"]))
"precision"))
(is (str/blank? (with-out-str (main/main "doc" "non-existing"))))
(is (= 1 (main/main "doc" "non-existing")))))
(deftest ^:skip-windows process-handler-info-test
(when test-utils/native?
(is (= ["-e" "(vec (.get (.arguments (.info (java.lang.ProcessHandle/current)))))"]
(bb nil "-e" "(vec (.get (.arguments (.info (java.lang.ProcessHandle/current)))))")))
(is (str/ends-with?
(bb nil "-e" "(.get (.command (.info (java.lang.ProcessHandle/current))))")
"bb"))))
(deftest ^:windows-only win-process-handler-info-test
(when (and test-utils/native? main/windows?)
(is (str/ends-with?
(bb nil "-e" "(.get (.command (.info (java.lang.ProcessHandle/current))))")
"bb.exe"))))
(deftest interop-concurrency-test
(is (= ["true" 3] (last (bb nil "-e"
"
(def f (fn [_]
[(String/valueOf true)
(.length \"foo\")]))
(vec (pmap f (map str (range 10000))))")))))
(deftest print-readably-test
(is (= "\"foo\"" (bb nil "-e" "(binding [*print-readably* true] (pr-str \"foo\"))")))
(is (= "foo" (bb nil "-e" "(binding [*print-readably* false] (pr-str \"foo\"))")))
(is (= "foo\n" (bb nil "-e" "(binding [*print-readably* false] (with-out-str (clojure.pprint/pprint \"foo\")))"))))
; repl-requires: '[[clojure.repl :refer (source apropos pst dir doc find-doc)]
; [clojure.pprint :refer (pp pprint)]]
(deftest repl-requires-test
(testing "the elements of repl-requires are available to scripts passed on the command line"
(is (str/includes? (bb nil "
(load-file \"test-resources/babashka/file_location2.clj\")
(require '[babashka.file-location2 :as fl])
(source fl/ok)") "ok"))
; using <= in case new matching functions get added
(is (<= 8 (bb nil '(count (apropos "first")))))
(is (= [1 2 3] (bb "[1 2 3]" "(pprint *input*)")))
(let [first-doc (test-utils/bb nil "(doc first)")]
(is (every? #(str/includes? first-doc %) ["---" "clojure.core/first" "first item"])))))
(deftest edn-input-test
(testing "clojure's default readers"
(is (= '(#inst "2021-08-24T00:56:02.014-00:00")
(bb "#inst \"2021-08-24T00:56:02.014-00:00\"" "-I" "(println *input*)")))
(is (= '(#uuid "00000000-0000-0000-0000-000000000000")
(bb "#uuid \"00000000-0000-0000-0000-000000000000\"" "-I" "(println *input*)"))))
(testing "use tagged-literal as default data reader fn..."
(testing "when using the -I option"
(is (= "(#made-up-tag 42)\n"
(test-utils/normalize (test-utils/bb "#made-up-tag 42" "-I" "(println *input*)"))))
(is (= "(#abc 123 #cde 789)\n"
(test-utils/normalize (test-utils/bb "{:a #abc 123}{:a #cde 789}" "-I" "(map :a *input*)")))))
(testing "when using --stream and -I"
(is (= "#abc 123\n#cde 789\n"
(test-utils/normalize (test-utils/bb "{:a #abc 123}{:a #cde 789}" "--stream" "-I" "-e" "(println (:a *input*))")))))
(testing "when using --stream (-I is sort of implied if no -i)"
(is (= "#abc 123\n#cde 789\n"
(test-utils/normalize (test-utils/bb "{:a #abc 123}{:a #cde 789}" "--stream" "-e" "(println (:a *input*))")))))
(testing "when reading one EDN form from stdin (no --stream or -I or -i)"
(is (= "#abc 123\n"
(test-utils/normalize (test-utils/bb "{:a #abc 123}{:a #cde 789}" "-e" "(println (:a *input*))")))))))
(deftest piped-input-output-stream-test
(is (= 10 (bb nil "
(def po (java.io.PipedOutputStream.))
(def pi (java.io.PipedInputStream.))
(.connect pi po)
(.write po 10)
(.read pi)
"))))
;;;; Scratch
(comment
(dotimes [_ 10] (wait-for-port-test)))