This commit is contained in:
Michiel Borkent 2020-11-26 12:06:49 +01:00 committed by GitHub
parent 6ed33d259e
commit 67c33b2270
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 356 additions and 87 deletions

View file

@ -1,7 +1,9 @@
(ns babashka.impl.clojure.core (ns babashka.impl.clojure.core
{:no-doc true} {:no-doc true}
(:refer-clojure :exclude [future read read-string]) (:refer-clojure :exclude [future read+string])
(:require [borkdude.graal.locking :as locking] (:require [babashka.impl.common :as common]
[borkdude.graal.locking :as locking]
[clojure.string :as str]
[sci.core :as sci] [sci.core :as sci]
[sci.impl.namespaces :refer [copy-core-var]])) [sci.impl.namespaces :refer [copy-core-var]]))
@ -19,6 +21,24 @@
(def data-readers (sci/new-dynamic-var '*data-readers* nil)) (def data-readers (sci/new-dynamic-var '*data-readers* nil))
(defn read+string
"Added for compatibility. Must be used with
clojure.lang.LineNumberingPushbackReader. Does not support all of
the options from the original yet."
([sci-ctx]
(read+string sci-ctx @sci/in))
([sci-ctx stream]
(read+string sci-ctx stream true nil))
([sci-ctx stream eof-error? eof-value]
(read+string sci-ctx stream eof-error? eof-value false))
([sci-ctx ^clojure.lang.LineNumberingPushbackReader stream _eof-error? eof-value _recursive?]
(let [_ (.captureString stream)
v (sci/parse-next sci-ctx stream {:eof eof-value})
s (str/trim (.getString stream))]
[(if (identical? :sci.core/eof v)
eof-value
v) s])))
(def core-extras (def core-extras
{'file-seq (copy-core-var file-seq) {'file-seq (copy-core-var file-seq)
'agent (copy-core-var agent) 'agent (copy-core-var agent)
@ -37,4 +57,6 @@
'remove-tap (copy-core-var remove-tap) 'remove-tap (copy-core-var remove-tap)
'*data-readers* data-readers '*data-readers* data-readers
'default-data-readers default-data-readers 'default-data-readers default-data-readers
'xml-seq (copy-core-var xml-seq)}) 'xml-seq (copy-core-var xml-seq)
'read+string (fn [& args]
(apply read+string @common/ctx args))})

View file

@ -13,17 +13,34 @@
:author "Alex Miller" :author "Alex Miller"
:no-doc true} :no-doc true}
babashka.impl.clojure.core.server babashka.impl.clojure.core.server
(:refer-clojure :exclude [locking]) (:require [babashka.impl.clojure.core :as core]
(:require [sci.core :as sci] [babashka.impl.clojure.main :as m]
[babashka.impl.common :refer [verbose?]]
[sci.core :as sci]
[sci.impl.parser :as p]
[sci.impl.vars :as vars]) [sci.impl.vars :as vars])
(:import (:import
[clojure.lang LineNumberingPushbackReader] [clojure.lang LineNumberingPushbackReader]
[java.io BufferedWriter InputStreamReader OutputStreamWriter]
[java.net InetAddress Socket ServerSocket SocketException] [java.net InetAddress Socket ServerSocket SocketException]
[java.io BufferedWriter InputStreamReader OutputStreamWriter])) [java.util.concurrent.locks ReentrantLock]))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
(def server (atom nil)) (def ^:dynamic *session* nil)
;; lock protects servers
(defonce ^:private lock (ReentrantLock.))
(defonce ^:private servers {})
(defmacro ^:private with-lock
[lock-expr & body]
`(let [lockee# ~(with-meta lock-expr {:tag 'java.util.concurrent.locks.ReentrantLock})]
(.lock lockee#)
(try
~@body
(finally
(.unlock lockee#)))))
(defmacro ^:private thread (defmacro ^:private thread
[^String name daemon & body] [^String name daemon & body]
@ -31,6 +48,15 @@
(.setDaemon ~daemon) (.setDaemon ~daemon)
(.start))) (.start)))
(defn- resolve-fn [ctx valf]
(if (symbol? valf)
(let [fully-qualified (p/fully-qualify ctx valf)]
(or (some-> ctx :env deref :namespaces
(get (symbol (namespace fully-qualified)))
(get (symbol (name fully-qualified))))
(throw (Exception. (str "can't resolve: " valf)))))
valf))
(defn- accept-connection (defn- accept-connection
"Start accept function, to be invoked on a client thread, given: "Start accept function, to be invoked on a client thread, given:
conn - client socket conn - client socket
@ -41,18 +67,22 @@
err - err stream err - err stream
accept - accept fn symbol to invoke accept - accept fn symbol to invoke
args - to pass to accept-fn" args - to pass to accept-fn"
[^Socket conn client-id in out err accept args] [ctx ^Socket conn client-id in out err accept args]
(try (let [accept (resolve-fn ctx accept)]
(sci/with-bindings {sci/in in (try
sci/out out (binding [*session* {:server name :client client-id}]
sci/err err (sci/with-bindings {sci/in in
vars/current-ns (vars/->SciNamespace 'user nil)} sci/out out
(swap! server assoc-in [:sessions client-id] {}) sci/err err
(apply accept args)) sci/ns (sci/create-ns 'user nil)}
(catch SocketException _disconnect) (with-lock lock
(finally (alter-var-root #'servers assoc-in [name :sessions client-id] {}))
(swap! server update-in [:sessions] dissoc client-id) (apply accept args)))
(.close conn)))) (catch SocketException _disconnect)
(finally
(with-lock lock
(alter-var-root #'servers update-in [name :sessions] dissoc client-id))
(.close conn)))))
(defn start-server (defn start-server
"Start a socket server given the specified opts: "Start a socket server given the specified opts:
@ -65,14 +95,15 @@
:server-daemon Is server thread a daemon?, defaults to true :server-daemon Is server thread a daemon?, defaults to true
:client-daemon Are client threads daemons?, defaults to true :client-daemon Are client threads daemons?, defaults to true
Returns server socket." Returns server socket."
[opts] ^ServerSocket [ctx opts]
(let [{:keys [address port name accept args bind-err server-daemon client-daemon] (let [{:keys [address port name accept args bind-err server-daemon client-daemon]
:or {bind-err true :or {bind-err true
server-daemon true server-daemon true
client-daemon true}} opts client-daemon true}} opts
address (InetAddress/getByName address) ;; nil returns loopback address (InetAddress/getByName address) ;; nil returns loopback
socket (ServerSocket. port 0 address)] socket (ServerSocket. port 0 address)]
(reset! server {:name name, :socket socket, :sessions {}}) (with-lock lock
(alter-var-root #'servers assoc name {:name name, :socket socket, :sessions {}}))
(thread (thread
(str "Clojure Server " name) server-daemon (str "Clojure Server " name) server-daemon
(try (try
@ -85,19 +116,144 @@
client-id (str client-counter)] client-id (str client-counter)]
(thread (thread
(str "Clojure Connection " name " " client-id) client-daemon (str "Clojure Connection " name " " client-id) client-daemon
(accept-connection conn client-id in out (if bind-err out *err*) accept args))) (accept-connection ctx conn client-id in out (if bind-err out *err*) accept args)))
(catch SocketException _disconnect)) (catch SocketException _disconnect))
(recur (inc client-counter)))) (recur (inc client-counter))))
(finally (finally
(reset! server nil)))) (with-lock lock
(alter-var-root #'servers dissoc name)))))
socket)) socket))
(defn stop-server (defn stop-server
"Stop server with name or use the server-name from *session* if none supplied. "Stop server with name or use the server-name from *session* if none supplied.
Returns true if server stopped successfully, nil if not found, or throws if Returns true if server stopped successfully, nil if not found, or throws if
there is an error closing the socket." there is an error closing the socket."
([]
(stop-server (:server *session*)))
([name]
(with-lock lock
(let [server-socket ^ServerSocket (get-in servers [name :socket])]
(when server-socket
(alter-var-root #'servers dissoc name)
(.close server-socket)
true)))))
(defn stop-servers
"Stop all servers ignores all errors, and returns nil."
[] []
(when-let [s @server] (with-lock lock
(when-let [server-socket ^ServerSocket (get s :socket)] (doseq [name (keys servers)]
(.close server-socket))) (future (stop-server name)))))
(reset! server nil))
(defn- ex->data
[ex phase]
(let [ex (assoc (Throwable->map ex) :phase phase)
ex (if (not @verbose?)
(update ex :trace #(vec (take 100 %)))
ex)]
ex))
(defn prepl
"a REPL with structured output (for programs)
reads forms to eval from in-reader (a LineNumberingPushbackReader)
Closing the input or passing the form :repl/quit will cause it to return
Calls out-fn with data, one of:
{:tag :ret
:val val ;;eval result
:ns ns-name-string
:ms long ;;eval time in milliseconds
:form string ;;iff successfully read
:clojure.error/phase (:execution et al per clojure.main/ex-triage) ;;iff error occurred
}
{:tag :out
:val string} ;chars from during-eval *out*
{:tag :err
:val string} ;chars from during-eval *err*
{:tag :tap
:val val} ;values from tap>
You might get more than one :out or :err per eval, but exactly one :ret
tap output can happen at any time (i.e. between evals)
If during eval an attempt is made to read *in* it will read from in-reader unless :stdin is supplied
Alpha, subject to change."
{:added "1.10"}
[ctx in-reader out-fn & {:keys [stdin]}]
(let [EOF (Object.)
tapfn #(out-fn {:tag :tap :val %1})]
(m/with-bindings
(sci/with-bindings {sci/in (or stdin in-reader)
sci/out (PrintWriter-on #(out-fn {:tag :out :val %1}) nil)
sci/err (PrintWriter-on #(out-fn {:tag :err :val %1}) nil)
sci/ns (sci/create-ns 'user nil)
sci/print-length @sci/print-length
sci/print-meta @sci/print-meta
sci/*1 nil
sci/*2 nil
sci/*3 nil
sci/*e nil}
(try
;; babashka uses Clojure's global tap system so this should be ok
(add-tap tapfn)
(loop []
(when (try
(let [[form s] (core/read+string ctx in-reader false EOF)]
(try
(when-not (identical? form EOF)
(let [start (System/nanoTime)
ret (sci/with-bindings {sci/*1 *1
sci/*2 *2
sci/*3 *3
sci/*e *e}
(sci/eval-form ctx form))
ms (quot (- (System/nanoTime) start) 1000000)]
(when-not (= :repl/quit ret)
(set! *3 *2)
(set! *2 *1)
(set! *1 ret)
(out-fn {:tag :ret
:val (if (instance? Throwable ret)
(Throwable->map ret)
ret)
:ns (str (vars/current-ns-name))
:ms ms
:form s})
true)))
(catch Throwable ex
(prn (ex-message ex))
(set! *e ex)
(out-fn {:tag :ret :val (ex->data ex (or (-> ex ex-data :clojure.error/phase) :execution))
:ns (str (.name *ns*)) :form s
:exception true})
true)))
(catch Throwable ex
(set! *e ex)
(out-fn {:tag :ret :val (ex->data ex :read-source)
:ns (str (.name *ns*))
:exception true})
true))
(recur)))
(finally
(remove-tap tapfn)))))))
(defn io-prepl
"prepl bound to *in* and *out*, suitable for use with e.g. server/repl (socket-repl).
:ret and :tap vals will be processed by valf, a fn of one argument
or a symbol naming same (default pr-str)
Alpha, subject to change."
{:added "1.10"}
[ctx & {:keys [valf] :or {valf pr-str}}]
(let [valf (resolve-fn ctx valf)
out @sci/out
lock (Object.)]
(prepl ctx @sci/in
(fn [m]
(binding [*out* out *flush-on-newline* true, *print-readably* true]
;; we're binding *out* to the out, which was the original
;; sci/out, because we're using Clojure's regular prn below
(locking lock
(prn (if (#{:ret :tap} (:tag m))
(try
(assoc m :val (valf (:val m)))
(catch Throwable ex
(assoc m :val (ex->data ex :print-eval-result)
:exception true)))
m))))))))

View file

@ -2,3 +2,4 @@
;; placeholder for ctx ;; placeholder for ctx
(def ctx (volatile! nil)) (def ctx (volatile! nil))
(def verbose? (volatile! false))

View file

@ -45,13 +45,15 @@
(let [in (r/indexing-push-back-reader (r/push-back-reader @sci/in))] (let [in (r/indexing-push-back-reader (r/push-back-reader @sci/in))]
(m/repl (m/repl
:init (or init :init (or init
#(do (sio/println "Babashka" (fn []
(str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION")))) (sci/with-bindings {sci/out @sci/err}
"REPL.") (sio/println "Babashka"
(sio/println "Use :repl/quit or :repl/exit to quit the REPL.") (str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION"))))
(sio/println "Clojure rocks, Bash reaches.") "REPL.")
(sio/println) (sio/println "Use :repl/quit or :repl/exit to quit the REPL.")
(eval-form sci-ctx '(use 'clojure.repl)))) (sio/println "Clojure rocks, Bash reaches.")
(sio/println))
(eval-form sci-ctx '(use 'clojure.repl))))
:read (or read :read (or read
(fn [_request-prompt request-exit] (fn [_request-prompt request-exit]
(let [v (parser/parse-next sci-ctx in)] (let [v (parser/parse-next sci-ctx in)]

View file

@ -0,0 +1,2 @@
(ns babashka.impl.server)

View file

@ -2,29 +2,45 @@
{:no-doc true} {:no-doc true}
(:require (:require
[babashka.impl.clojure.core.server :as server] [babashka.impl.clojure.core.server :as server]
[babashka.impl.common :as common]
[babashka.impl.repl :as repl] [babashka.impl.repl :as repl]
[clojure.edn :as edn]
[clojure.string :as str])) [clojure.string :as str]))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
(defn start-repl! [host+port sci-ctx] ;; this is mapped to clojure.core.server/repl in babashka.main
(let [parts (str/split host+port #":") (def repl (fn []
[host port] (if (= 1 (count parts)) (repl/repl @common/ctx)))
[nil (Integer. ^String (first parts))]
[(first parts) (Integer. ^String (second parts))]) (defn parse-opts [opts]
host+port (if-not host (str "localhost:" port) (let [opts (str/trim opts)
host+port) opts (if (str/starts-with? opts "{")
socket (server/start-server (edn/read-string opts)
{:address host (let [parts (str/split opts #":")
:port port [host port] (if (= 1 (count parts))
:name "bb" [nil (Integer. ^String (first parts))]
:accept babashka.impl.repl/repl [(first parts) (Integer. ^String (second parts))])]
:args [sci-ctx]})] {:address host
(println "Babashka socket REPL started at" host+port) :port port
:name "bb"
:accept 'clojure.core.server/repl
:args []}))]
opts))
(defn start-repl! [opts sci-ctx]
(let [opts (parse-opts opts)
socket (server/start-server sci-ctx opts)
inet-address (java.net.InetAddress/getByName (:address opts))]
(binding [*out* *err*]
(println (format "Babashka socket REPL started at %s:%d"
(.getHostAddress inet-address)
(.getLocalPort socket))))
socket)) socket))
(defn stop-repl! [] (defn stop-repl! []
(server/stop-server)) ;; This is only used by tests where we run one server at a time.
(server/stop-servers))
(comment (comment
@#'server/servers @#'server/servers

View file

@ -7,6 +7,7 @@
[babashka.impl.classes :as classes] [babashka.impl.classes :as classes]
[babashka.impl.classpath :as cp] [babashka.impl.classpath :as cp]
[babashka.impl.clojure.core :as core :refer [core-extras]] [babashka.impl.clojure.core :as core :refer [core-extras]]
[babashka.impl.clojure.core.server :as server]
[babashka.impl.clojure.java.browse :refer [browse-namespace]] [babashka.impl.clojure.java.browse :refer [browse-namespace]]
[babashka.impl.clojure.java.io :refer [io-namespace]] [babashka.impl.clojure.java.io :refer [io-namespace]]
[babashka.impl.clojure.java.shell :refer [shell-namespace]] [babashka.impl.clojure.java.shell :refer [shell-namespace]]
@ -316,9 +317,7 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
(sci/eval-string* @common/ctx s)))) (sci/eval-string* @common/ctx s))))
(defn start-socket-repl! [address ctx] (defn start-socket-repl! [address ctx]
(socket-repl/start-repl! address ctx) (socket-repl/start-repl! address ctx))
;; hang until SIGINT
@(promise))
(defn start-nrepl! [address ctx] (defn start-nrepl! [address ctx]
(let [dev? (= "true" (System/getenv "BABASHKA_DEV")) (let [dev? (= "true" (System/getenv "BABASHKA_DEV"))
@ -366,6 +365,18 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
:cp new-cp}))) :cp new-cp})))
nil) nil)
;;(def ^:private server-ns-obj (sci/create-ns 'clojure.core.server nil))
(def clojure-core-server
{'repl socket-repl/repl
'prepl (fn [& args]
(apply server/prepl @common/ctx args))
'io-prepl (fn [& args]
(apply server/io-prepl @common/ctx args))
'start-server (fn [& args]
(apply server/start-server @common/ctx args))})
(def namespaces (def namespaces
(cond-> (cond->
{'clojure.tools.cli tools-cli-namespace {'clojure.tools.cli tools-cli-namespace
@ -379,7 +390,10 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
'clojure.stacktrace stacktrace-namespace 'clojure.stacktrace stacktrace-namespace
'clojure.zip zip-namespace 'clojure.zip zip-namespace
'clojure.main {'demunge demunge 'clojure.main {'demunge demunge
'repl-requires clojure-main/repl-requires} 'repl-requires clojure-main/repl-requires
'repl (fn [& opts]
(let [opts (apply hash-map opts)]
(repl/start-repl! @common/ctx opts)))}
'clojure.test t/clojure-test-namespace 'clojure.test t/clojure-test-namespace
'babashka.classpath {'add-classpath add-classpath*} 'babashka.classpath {'add-classpath add-classpath*}
'clojure.pprint pprint-namespace 'clojure.pprint pprint-namespace
@ -389,6 +403,7 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
'clojure.java.browse browse-namespace 'clojure.java.browse browse-namespace
'clojure.datafy datafy-namespace 'clojure.datafy datafy-namespace
'clojure.core.protocols protocols-namespace 'clojure.core.protocols protocols-namespace
'clojure.core.server clojure-core-server
'babashka.process process-namespace} 'babashka.process process-namespace}
features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace)) features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace))
features/yaml? (assoc 'clj-yaml.core @(resolve 'babashka.impl.yaml/yaml-namespace) features/yaml? (assoc 'clj-yaml.core @(resolve 'babashka.impl.yaml/yaml-namespace)
@ -476,6 +491,7 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
:main :uberscript :describe? :main :uberscript :describe?
:jar :uberjar] :as _opts} :jar :uberjar] :as _opts}
(parse-opts args) (parse-opts args)
_ (when verbose? (vreset! common/verbose? true))
_ (do ;; set properties _ (do ;; set properties
(when main (System/setProperty "babashka.main" main)) (when main (System/setProperty "babashka.main" main))
(System/setProperty "babashka.version" version)) (System/setProperty "babashka.version" version))
@ -540,11 +556,7 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
(cp/getResource loader [path] {:url? true}))))) (cp/getResource loader [path] {:url? true})))))
(assoc-in ['user (with-meta '*input* (assoc-in ['user (with-meta '*input*
(when-not stream? (when-not stream?
{:sci.impl/deref! true}))] input-var) {:sci.impl/deref! true}))] input-var))
(assoc-in ['clojure.main 'repl]
(fn [& opts]
(let [opts (apply hash-map opts)]
(repl/start-repl! @common/ctx opts)))))
:env env :env env
:features #{:bb :clj} :features #{:bb :clj}
:classes classes/class-map :classes classes/class-map
@ -581,6 +593,10 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
:preloads preloads :preloads preloads
:loader (:loader @cp-state)}))))) :loader (:loader @cp-state)})))))
nil)) nil))
;; socket REPL is start asynchronously. when no other args are
;; provided, a normal REPL will be started as well, which causes the
;; process to wait until SIGINT
_ (when socket-repl (start-socket-repl! socket-repl sci-ctx))
exit-code exit-code
(or exit-code (or exit-code
(second (second
@ -591,7 +607,6 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
describe? describe?
[(print-describe) 0] [(print-describe) 0]
repl [(repl/start-repl! sci-ctx) 0] repl [(repl/start-repl! sci-ctx) 0]
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
nrepl [(start-nrepl! nrepl sci-ctx) 0] nrepl [(start-nrepl! nrepl sci-ctx) 0]
uberjar [nil 0] uberjar [nil 0]
expressions expressions

View file

@ -1,6 +1,6 @@
(ns babashka.wait (ns babashka.wait
(:require [clojure.java.io :as io]) (:require [clojure.java.io :as io])
(:import [java.net Socket ConnectException])) (:import [java.net Socket SocketException]))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
@ -19,7 +19,7 @@
(loop [] (loop []
(let [v (try (.close (Socket. host port)) (let [v (try (.close (Socket. host port))
(- (System/currentTimeMillis) t0) (- (System/currentTimeMillis) t0)
(catch ConnectException _e (catch SocketException _e
(let [took (- (System/currentTimeMillis) t0)] (let [took (- (System/currentTimeMillis) t0)]
(if (and timeout (>= took timeout)) (if (and timeout (>= took timeout))
:wait-for-port.impl/timed-out :wait-for-port.impl/timed-out

View file

@ -1,10 +1,15 @@
(ns babashka.impl.socket-repl-test (ns babashka.impl.socket-repl-test
(:require (:require
[babashka.impl.common :as common]
[babashka.impl.socket-repl :refer [start-repl! stop-repl!]] [babashka.impl.socket-repl :refer [start-repl! stop-repl!]]
[babashka.main :refer [clojure-core-server]]
[babashka.process :as p]
[babashka.wait :as w]
[babashka.test-utils :as tu] [babashka.test-utils :as tu]
[clojure.java.io :as io] [clojure.java.io :as io]
[clojure.java.shell :refer [sh]] [clojure.java.shell :refer [sh]]
[clojure.string :as str] [clojure.string :as str]
[clojure.edn :as edn]
[clojure.test :as t :refer [deftest is testing]] [clojure.test :as t :refer [deftest is testing]]
[sci.impl.opts :refer [init]])) [sci.impl.opts :refer [init]]))
@ -16,37 +21,38 @@
sw (java.io.StringWriter.) sw (java.io.StringWriter.)
writer (io/writer socket)] writer (io/writer socket)]
(binding [*out* writer] (binding [*out* writer]
(println (str expr)) (println (str expr "\n")))
(println ":repl/exit\n"))
(loop [] (loop []
(when-let [l (.readLine ^java.io.BufferedReader reader)] (when-let [l (try (.readLine ^java.io.BufferedReader reader)
(catch java.net.SocketException _ nil))]
;; (prn :l l)
(binding [*out* sw] (binding [*out* sw]
(println l)) (println l))
(recur))) (let [s (str sw)]
(let [s (str sw)] ;; (prn :s s :expected expected (str/includes? s expected))
(is (str/includes? s expected) (if (if (fn? expected)
(format "\"%s\" does not contain \"%s\"" (expected s)
s expected)) (str/includes? s expected))
s))) (is true)
(recur)))))
(binding [*out* writer]
(println ":repl/quit\n"))
:success))
(def server-process (volatile! nil))
(deftest socket-repl-test (deftest socket-repl-test
(try (try
(if tu/jvm? (if tu/jvm?
(start-repl! "0.0.0.0:1666" (init {:bindings {'*command-line-args* (let [ctx (init {:namespaces {'clojure.core.server clojure-core-server}
["a" "b" "c"]} :features #{:bb}})]
:env (atom {}) (vreset! common/ctx ctx)
:features #{:bb}})) (start-repl! "0.0.0.0:1666" ctx))
(future (do (vreset! server-process
(sh "bash" "-c" (p/process ["./bb" "--socket-repl" "localhost:1666"]))
"echo '[1 2 3]' | ./bb --socket-repl 0.0.0.0:1666 a b c"))) (w/wait-for-port "localhost" 1666)))
;; wait for server to be available (Thread/sleep 50)
(when tu/native?
(while (not (zero? (:exit
(sh "bash" "-c"
"lsof -t -i:1666"))))))
(is (socket-command "(+ 1 2 3)" "user=> 6")) (is (socket-command "(+ 1 2 3)" "user=> 6"))
(testing "*command-line-args*"
(is (socket-command '*command-line-args* "\"a\" \"b\" \"c\"")))
(testing "&env" (testing "&env"
(socket-command "(defmacro bindings [] (mapv #(list 'quote %) (keys &env)))" "bindings") (socket-command "(defmacro bindings [] (mapv #(list 'quote %) (keys &env)))" "bindings")
(socket-command "(defn bar [x y z] (bindings))" "bar") (socket-command "(defn bar [x y z] (bindings))" "bar")
@ -60,8 +66,53 @@
(finally (finally
(if tu/jvm? (if tu/jvm?
(stop-repl!) (stop-repl!)
(sh "bash" "-c" (p/destroy-tree @server-process)))))
"kill -9 $(lsof -t -i:1666)")))))
(deftest socket-repl-opts-test
(try
(if tu/jvm?
(let [ctx (init {:bindings {'*command-line-args*
["a" "b" "c"]}
:env (atom {})
:namespaces {'clojure.core.server clojure-core-server}
:features #{:bb}})]
(vreset! common/ctx ctx)
(start-repl! "{:address \"localhost\" :accept clojure.core.server/repl :port 1666}"
ctx))
(do (vreset! server-process
(p/process ["./bb" "--socket-repl" "{:address \"localhost\" :accept clojure.core.server/repl :port 1666}"]))
(w/wait-for-port "localhost" 1666)))
(Thread/sleep 50)
(is (socket-command "(+ 1 2 3)" "user=> 6"))
(finally
(if tu/jvm?
(stop-repl!)
(p/destroy-tree @server-process)))))
(deftest socket-prepl-test
(try
(if tu/jvm?
(let [ctx (init {:bindings {'*command-line-args*
["a" "b" "c"]}
:env (atom {})
:namespaces {'clojure.core.server clojure-core-server}
:features #{:bb}})]
(vreset! common/ctx ctx)
(start-repl! "{:address \"localhost\" :accept clojure.core.server/io-prepl :port 1666}"
ctx))
(do (vreset! server-process
(p/process ["./bb" "--socket-repl" "{:address \"localhost\" :accept clojure.core.server/io-prepl :port 1666}"]))
(w/wait-for-port "localhost" 1666)))
(Thread/sleep 50)
(is (socket-command "(+ 1 2 3)" (fn [s]
(let [m (edn/read-string s)]
(and (= "6" (:val m))
(= "user" (:ns m))
(= "(+ 1 2 3)" (:form m)))))))
(finally
(if tu/jvm?
(stop-repl!)
(p/destroy-tree @server-process)))))
;;;; Scratch ;;;; Scratch

View file

@ -558,6 +558,10 @@
(deftest java-stream-test (deftest java-stream-test
(is (every? number? (bb nil "(take 2 (iterator-seq (.iterator (.doubles (java.util.Random.)))))")))) (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 (deftest iterable-test
(is (true? (bb nil " (is (true? (bb nil "
(defn iter [coll] (defn iter [coll]