* make tests run on windows as the default - change from selecting tests to run to selecting tests to skip (remove all :windows tags, add :skip-windows tag to tests that don't currently work on windows) - handfuls of calls to `normalize` and `escape-file-paths` to handle platform differences - split `task-test` to make most of the tests run on windows, and exclude just a couple of Unix-y tests * make a binding name clearer * skip nrepl-server-test on Windows - test fails on CI, so disabling it for now * unset bb environment vars after running tests * unset bb environment var after running release-artifact BABASHKA_EDN being set can interfere with some tests, so unset it before running the native tests * skip uberjar test on Windows uberjar-test's 'throw on empty classpath' test failing on Windows native (but passes on JVM) - skip it for now
216 lines
10 KiB
Clojure
216 lines
10 KiB
Clojure
(ns babashka.impl.nrepl-server-test
|
|
(:require
|
|
[babashka.main :as main]
|
|
[babashka.nrepl.server :refer [start-server! stop-server! parse-opt]]
|
|
[babashka.test-utils :as tu]
|
|
[babashka.wait :as wait]
|
|
[bencode.core :as bencode]
|
|
[clojure.test :as t :refer [deftest is testing]]
|
|
[sci.impl.opts :refer [init]])
|
|
(:import [java.lang ProcessBuilder$Redirect]))
|
|
|
|
(def debug? false)
|
|
|
|
(set! *warn-on-reflection* true)
|
|
|
|
(defn bytes->str [x]
|
|
(if (bytes? x) (String. (bytes x))
|
|
(str x)))
|
|
|
|
(defn read-msg [msg]
|
|
(let [res (zipmap (map keyword (keys msg))
|
|
(map #(if (bytes? %)
|
|
(String. (bytes %))
|
|
%)
|
|
(vals msg)))
|
|
res (if-let [status (:status res)]
|
|
(assoc res :status (mapv bytes->str status))
|
|
res)
|
|
res (if-let [status (:sessions res)]
|
|
(assoc res :sessions (mapv bytes->str status))
|
|
res)]
|
|
res))
|
|
|
|
(defn read-reply [in session id]
|
|
(loop []
|
|
(let [msg (read-msg (bencode/read-bencode in))]
|
|
(if (and (= (:session msg) session)
|
|
(= (:id msg) id))
|
|
(do
|
|
(when debug? (prn "received" msg))
|
|
msg)
|
|
(do
|
|
(when debug? (prn "skipping over msg" msg))
|
|
(recur))))))
|
|
|
|
(defn nrepl-test []
|
|
(with-open [socket (java.net.Socket. "127.0.0.1" 1668)
|
|
in (.getInputStream socket)
|
|
in (java.io.PushbackInputStream. in)
|
|
os (.getOutputStream socket)]
|
|
(bencode/write-bencode os {"op" "clone"})
|
|
(let [session (:new-session (read-msg (bencode/read-bencode in)))
|
|
id (atom 0)
|
|
new-id! #(swap! id inc)]
|
|
(testing "session"
|
|
(is session))
|
|
(testing "describe"
|
|
(bencode/write-bencode os {"op" "describe" "session" session "id" (new-id!)})
|
|
(let [msg (read-reply in session @id)
|
|
id (:id msg)
|
|
versions (:versions msg)
|
|
babashka-version (bytes->str (get versions "babashka"))]
|
|
(is (= 1 id))
|
|
(is (= main/version babashka-version))))
|
|
(testing "eval"
|
|
(bencode/write-bencode os {"op" "eval" "code" "(+ 1 2 3)" "session" session "id" (new-id!)})
|
|
(let [msg (read-reply in session @id)
|
|
id (:id msg)
|
|
value (:value msg)]
|
|
(is (= 2 id))
|
|
(is (= value "6")))
|
|
(testing "creating a namespace and evaluating something in it"
|
|
(bencode/write-bencode os {"op" "eval"
|
|
"code" "(ns ns0) (defn foo [] :foo0) (ns ns1) (defn foo [] :foo1)"
|
|
"session" session
|
|
"id" (new-id!)})
|
|
(read-reply in session @id)
|
|
(testing "not providing the ns key evaluates in the last defined namespace"
|
|
(bencode/write-bencode os {"op" "eval" "code" "(foo)" "session" session "id" (new-id!)})
|
|
(is (= ":foo1" (:value (read-reply in session @id)))))
|
|
(testing "explicitly providing the ns key evaluates in that namespace"
|
|
(bencode/write-bencode os {"op" "eval"
|
|
"code" "(foo)"
|
|
"session" session
|
|
"id" (new-id!)
|
|
"ns" "ns0"})
|
|
(is (= ":foo0" (:value (read-reply in session @id)))))
|
|
(testing "providing an ns value of a non-existing namespace creates the namespace"
|
|
(bencode/write-bencode os {"op" "eval"
|
|
"code" "(ns-name *ns*)"
|
|
"session" session
|
|
"id" (new-id!)
|
|
"ns" "unicorn"})
|
|
(let [reply (read-reply in session @id)]
|
|
(is (= "unicorn" (:value reply))))))
|
|
(testing "multiple top level expressions results in two value replies"
|
|
(bencode/write-bencode os {"op" "eval"
|
|
"code" "(+ 1 2 3) (+ 1 2 3)"
|
|
"session" session
|
|
"id" (new-id!)})
|
|
(let [reply-1 (read-reply in session @id)
|
|
reply-2 (read-reply in session @id)]
|
|
(is (= "6" (:value reply-1) (:value reply-2))))))
|
|
(testing "load-file"
|
|
(bencode/write-bencode os {"op" "load-file" "file" "(ns foo) (defn foo [] :foo)" "session" session "id" (new-id!)})
|
|
(read-reply in session @id)
|
|
(bencode/write-bencode os {"op" "eval" "code" "(foo)" "ns" "foo" "session" session "id" (new-id!)})
|
|
(is (= ":foo" (:value (read-reply in session @id)))))
|
|
(testing "complete"
|
|
(testing "completions for fo"
|
|
(bencode/write-bencode os {"op" "complete"
|
|
"symbol" "fo"
|
|
"session" session
|
|
"id" (new-id!)
|
|
"ns" "foo"})
|
|
(let [reply (read-reply in session @id)
|
|
completions (:completions reply)
|
|
completions (mapv read-msg completions)
|
|
completions (into #{} (map (juxt :ns :candidate)) completions)]
|
|
(is (contains? completions ["foo" "foo"]))
|
|
(is (contains? completions ["clojure.core" "format"]))))
|
|
(testing "completions for quux should be empty"
|
|
(bencode/write-bencode os {"op" "complete"
|
|
"symbol" "quux"
|
|
"session" session "id" (new-id!)
|
|
"ns" "foo"})
|
|
(let [reply (read-reply in session @id)
|
|
completions (:completions reply)]
|
|
(is (empty? completions)))
|
|
(testing "unless quux is an alias"
|
|
(bencode/write-bencode os {"op" "eval" "code" "(require '[cheshire.core :as quux])" "session" session "id" (new-id!)})
|
|
(read-reply in session @id)
|
|
(bencode/write-bencode os {"op" "complete" "symbol" "quux" "session" session "id" (new-id!)})
|
|
(let [reply (read-reply in session @id)
|
|
completions (:completions reply)
|
|
completions (mapv read-msg completions)
|
|
completions (into #{} (map (juxt :ns :candidate)) completions)]
|
|
(is (contains? completions ["cheshire.core" "quux/generate-string"])))))
|
|
(testing "completions for clojure.test"
|
|
(bencode/write-bencode os {"op" "eval" "code" "(require '[clojure.test :as test])" "session" session "id" (new-id!)})
|
|
(read-reply in session @id)
|
|
(bencode/write-bencode os {"op" "complete" "symbol" "test" "session" session "id" (new-id!)})
|
|
(let [reply (read-reply in session @id)
|
|
completions (:completions reply)
|
|
completions (mapv read-msg completions)
|
|
completions (into #{} (map (juxt :ns :candidate)) completions)]
|
|
(is (contains? completions ["clojure.test" "test/deftest"])))))
|
|
(testing "close + ls-sessions"
|
|
(bencode/write-bencode os {"op" "ls-sessions" "session" session "id" (new-id!)})
|
|
(let [reply (read-reply in session @id)
|
|
sessions (set (:sessions reply))]
|
|
(is (contains? sessions session))
|
|
(let [new-sessions (loop [i 0
|
|
sessions #{}]
|
|
(bencode/write-bencode os {"op" "clone" "session" session "id" (new-id!)})
|
|
(let [new-session (:new-session (read-reply in session @id))
|
|
sessions (conj sessions new-session)]
|
|
(if (= i 4)
|
|
sessions
|
|
(recur (inc i) sessions))))]
|
|
(bencode/write-bencode os {"op" "ls-sessions" "session" session "id" (new-id!)})
|
|
(let [reply (read-reply in session @id)
|
|
sessions (set (:sessions reply))]
|
|
(is (= 6 (count sessions)))
|
|
(is (contains? sessions session))
|
|
(is (= new-sessions (disj sessions session)))
|
|
(testing "close"
|
|
(doseq [close-session (disj sessions session)]
|
|
(bencode/write-bencode os {"op" "close" "session" close-session "id" (new-id!)})
|
|
(let [reply (read-reply in close-session @id)]
|
|
(is (contains? (set (:status reply)) "session-closed")))))
|
|
(testing "session not listen in ls-sessions after close"
|
|
(bencode/write-bencode os {"op" "ls-sessions" "session" session "id" (new-id!)})
|
|
(let [reply (read-reply in session @id)
|
|
sessions (set (:sessions reply))]
|
|
(is (contains? sessions session))
|
|
(is (not (some #(contains? sessions %) new-sessions)))))))))
|
|
(testing "output"
|
|
(bencode/write-bencode os {"op" "eval" "code" "(dotimes [i 3] (println \"Hello\"))"
|
|
"session" session "id" (new-id!)})
|
|
(dotimes [_ 3]
|
|
(let [reply (read-reply in session @id)]
|
|
(is (= "Hello\n" (tu/normalize (:out reply))))))))))
|
|
|
|
(deftest ^:skip-windows nrepl-server-test
|
|
(let [proc-state (atom nil)
|
|
server-state (atom nil)]
|
|
(try
|
|
(if tu/jvm?
|
|
(let [nrepl-opts (parse-opt "0.0.0.0:1668")
|
|
nrepl-opts (assoc nrepl-opts
|
|
:describe {"versions" {"babashka" main/version}})
|
|
server (start-server!
|
|
(init {:namespaces main/namespaces
|
|
:features #{:bb}})
|
|
nrepl-opts)]
|
|
(reset! server-state server))
|
|
(let [pb (ProcessBuilder. ["./bb" "nrepl-server" "0.0.0.0:1668"])
|
|
_ (.redirectError pb ProcessBuilder$Redirect/INHERIT)
|
|
;; _ (.redirectOutput pb ProcessBuilder$Redirect/INHERIT)
|
|
;; env (.environment pb)
|
|
;; _ (.put env "BABASHKA_DEV" "true")
|
|
proc (.start pb)]
|
|
(reset! proc-state proc)))
|
|
(babashka.wait/wait-for-port "localhost" 1668)
|
|
(nrepl-test)
|
|
(finally
|
|
(if tu/jvm?
|
|
(stop-server! @server-state)
|
|
(when-let [proc @proc-state]
|
|
(.destroy ^Process proc)))))))
|
|
|
|
;;;; Scratch
|
|
|
|
(comment
|
|
)
|