From 3ad043769c16162abf33c58ad7068fb8ebc6679f Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Tue, 21 Feb 2023 16:23:03 +0100 Subject: [PATCH] Use GraalVM 22.3.1 on JDK 19.0.2 with virtual thread support (#1500) --- .circleci/script/gen_ci.clj | 10 ++-- .cirrus.yml | 4 +- .portal/vs-code.edn | 1 + Dockerfile | 12 ++-- appveyor.yml | 6 +- doc/build.md | 8 +-- doc/dev.md | 2 +- resources/META-INF/babashka/deps.edn | 2 +- script/bump_graal_version.clj | 10 ++-- script/compile | 3 +- script/compile.bat | 1 + script/install-graalvm | 37 ++++++------ src/babashka/impl/classes.clj | 10 +++- test/babashka/crypto_test.clj | 1 + test/babashka/error_test.clj | 85 ++++++++++++++++------------ 15 files changed, 110 insertions(+), 82 deletions(-) create mode 100644 .portal/vs-code.edn diff --git a/.circleci/script/gen_ci.clj b/.circleci/script/gen_ci.clj index 5ca6ad66..fe58ab63 100644 --- a/.circleci/script/gen_ci.clj +++ b/.circleci/script/gen_ci.clj @@ -80,7 +80,7 @@ :working_directory "~/repo" :environment {:LEIN_ROOT "true" :BABASHKA_PLATFORM "linux" - :GRAALVM_VERSION "22.3.0" + :GRAALVM_VERSION "22.3.1" :GRAALVM_HOME graalvm-home} :resource_class "large" :steps @@ -119,7 +119,7 @@ java -jar \"$jar\" --config .build/bb.edn --deps-root . release-artifact \"$refl (defn unix [shorted? static? musl? arch executor-conf resource-class graalvm-home platform] (let [env {:LEIN_ROOT "true" - :GRAALVM_VERSION "22.3.0" + :GRAALVM_VERSION "22.3.1" :GRAALVM_HOME graalvm-home :BABASHKA_PLATFORM (if (= "mac" platform) "macos" @@ -175,7 +175,7 @@ java -jar \"$jar\" --config .build/bb.edn --deps-root . release-artifact \"$refl {:persist_to_workspace {:root "/tmp" :paths ["release"]}} {:save_cache - {:paths ["~/.m2" "~/graalvm-ce-java11-22.3.0"] + {:paths ["~/.m2" "~/graalvm-ce-java19-22.3.1"] :key cache-key}} {:store_artifacts {:path "/tmp/release" :destination "release"}} @@ -187,8 +187,8 @@ java -jar \"$jar\" --config .build/bb.edn --deps-root . release-artifact \"$refl (let [docker-executor-conf {:docker [{:image "circleci/clojure:openjdk-11-lein-2.9.8-bullseye"}]} machine-executor-conf {:machine {:image "ubuntu-2004:202111-01"}} mac-executor-conf {:macos {:xcode "14.0.0"}} - linux-graalvm-home "/home/circleci/graalvm-ce-java11-22.3.0" - mac-graalvm-home "/Users/distiller/graalvm-ce-java11-22.3.0/Contents/Home"] + linux-graalvm-home "/home/circleci/graalvm-ce-java19-22.3.1" + mac-graalvm-home "/Users/distiller/graalvm-ce-java19-22.3.1/Contents/Home"] (ordered-map :version 2.1 :commands diff --git a/.cirrus.yml b/.cirrus.yml index 34ebcced..d0e0445b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -5,8 +5,8 @@ task: skip: "changesIncludeOnly('logo/*', '**.md')" env: LEIN_ROOT: "true" - GRAALVM_VERSION: "22.3.0" - GRAALVM_HOME: ${HOME}/graalvm-ce-java11-22.3.0/Contents/Home + GRAALVM_VERSION: "22.3.1" + GRAALVM_HOME: ${HOME}/graalvm-ce-java19-22.3.1/Contents/Home BABASHKA_PLATFORM: macos # used in release script BABASHKA_ARCH: aarch64 BABASHKA_TEST_ENV: native diff --git a/.portal/vs-code.edn b/.portal/vs-code.edn new file mode 100644 index 00000000..90c32986 --- /dev/null +++ b/.portal/vs-code.edn @@ -0,0 +1 @@ +{:host "localhost", :port 51379} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f9ad88b3..789bc921 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN apt update RUN apt install --no-install-recommends -yy build-essential zlib1g-dev WORKDIR "/opt" -ENV GRAALVM_VERSION="22.3.0" +ENV GRAALVM_VERSION="22.3.1" ARG TARGETARCH # Do not set those directly, use TARGETARCH instead ENV BABASHKA_ARCH= @@ -16,14 +16,14 @@ RUN if [ "${TARGETARCH}" = "" ] || [ "${TARGETARCH}" = "amd64" ]; then \ export GRAALVM_ARCH=aarch64; \ fi && \ echo "Installing GraalVM for ${GRAALVM_ARCH}" && \ - curl -sLO https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${GRAALVM_VERSION}/graalvm-ce-java11-linux-${GRAALVM_ARCH}-${GRAALVM_VERSION}.tar.gz && \ - tar -xzf graalvm-ce-java11-linux-${GRAALVM_ARCH}-${GRAALVM_VERSION}.tar.gz && \ - rm graalvm-ce-java11-linux-${GRAALVM_ARCH}-${GRAALVM_VERSION}.tar.gz + curl -sLO https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${GRAALVM_VERSION}/graalvm-ce-java19-linux-${GRAALVM_ARCH}-${GRAALVM_VERSION}.tar.gz && \ + tar -xzf graalvm-ce-java19-linux-${GRAALVM_ARCH}-${GRAALVM_VERSION}.tar.gz && \ + rm graalvm-ce-java19-linux-${GRAALVM_ARCH}-${GRAALVM_VERSION}.tar.gz ARG BABASHKA_XMX="-J-Xmx4500m" -ENV GRAALVM_HOME="/opt/graalvm-ce-java11-${GRAALVM_VERSION}" -ENV JAVA_HOME="/opt/graalvm-ce-java11-${GRAALVM_VERSION}/bin" +ENV GRAALVM_HOME="/opt/graalvm-ce-java19-${GRAALVM_VERSION}" +ENV JAVA_HOME="/opt/graalvm-ce-java19-${GRAALVM_VERSION}/bin" ENV PATH="$JAVA_HOME:$PATH" ENV BABASHKA_XMX=$BABASHKA_XMX diff --git a/appveyor.yml b/appveyor.yml index c051030f..ff8a0464 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,8 +7,8 @@ image: Visual Studio 2017 clone_folder: C:\projects\babashka environment: - GRAALVM_HOME: C:\projects\babashka\graalvm\graalvm-ce-java11-22.3.0 - JAVA_HOME: C:\projects\babashka\graalvm\graalvm-ce-java11-22.3.0 + GRAALVM_HOME: C:\projects\babashka\graalvm\graalvm-ce-java19-22.3.1 + JAVA_HOME: C:\projects\babashka\graalvm\graalvm-ce-java19-22.3.1 BABASHKA_XMX: "-J-Xmx5g" skip_commits: @@ -38,7 +38,7 @@ clone_script: build_script: - cmd: >- - powershell -Command "if (Test-Path('graalvm')) { return } else { (New-Object Net.WebClient).DownloadFile('https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.0/graalvm-ce-java11-windows-amd64-22.3.0.zip', 'graalvm.zip') }" + powershell -Command "if (Test-Path('graalvm')) { return } else { (New-Object Net.WebClient).DownloadFile('https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.1/graalvm-ce-java19-windows-amd64-22.3.1.zip', 'graalvm.zip') }" powershell -Command "if (Test-Path('graalvm')) { return } else { Expand-Archive graalvm.zip graalvm }" diff --git a/doc/build.md b/doc/build.md index 4de497e0..00857f8a 100644 --- a/doc/build.md +++ b/doc/build.md @@ -3,24 +3,24 @@ ## Prerequisites - Install [lein](https://leiningen.org/) for producing uberjars -- Download [GraalVM](https://www.graalvm.org/downloads/). Currently we use *java11-22.3.0*. +- Download [GraalVM](https://www.graalvm.org/downloads/). Currently we use *java19-22.3.1*. - For Windows, installing Visual Studio 2019 with the "Desktop development with C++" workload is recommended. - Set `$GRAALVM_HOME` to the GraalVM distribution directory. On macOS this can look like: ``` shell - export GRAALVM_HOME=~/Downloads/graalvm-ce-java11-22.3.0/Contents/Home + export GRAALVM_HOME=~/Downloads/graalvm-ce-java19-22.3.1/Contents/Home ``` On linux: ``` shell - export GRAALVM_HOME=~/Downloads/graalvm-ce-java11-22.3.0 + export GRAALVM_HOME=~/Downloads/graalvm-ce-java19-22.3.1 ``` On Windows, from the [Visual Studio 2019 x64 Native Tools Command Prompt](https://github.com/oracle/graal/issues/2116#issuecomment-590470806) or `cmd.exe` (not Powershell): ``` - set GRAALVM_HOME=%USERPROFILE%\Downloads\graalvm-ce-java11-22.3.0 + set GRAALVM_HOME=%USERPROFILE%\Downloads\graalvm-ce-java19-22.3.1 ``` If you are not running from the x64 Native Tools Command Prompt, you will need to set additional environment variables using: ``` diff --git a/doc/dev.md b/doc/dev.md index 872cfa8e..6415f933 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -37,7 +37,7 @@ reasons: ## Requirements -You need [lein](https://leiningen.org/) for running JVM tests and/or producing uberjars. For building binaries you need GraalVM. Currently we use java11-22.3.0. +You need [lein](https://leiningen.org/) for running JVM tests and/or producing uberjars. For building binaries you need GraalVM. Currently we use java19-22.3.1. ## Clone repository diff --git a/resources/META-INF/babashka/deps.edn b/resources/META-INF/babashka/deps.edn index 8e297b70..6559fddd 100644 --- a/resources/META-INF/babashka/deps.edn +++ b/resources/META-INF/babashka/deps.edn @@ -50,7 +50,7 @@ org.clojure/data.priority-map {:mvn/version "1.1.0"} insn/insn {:mvn/version "0.5.2"} org.clojure/core.rrb-vector {:mvn/version "0.1.2"} - org.babashka/cli {:mvn/version "0.6.45"} + org.babashka/cli {:mvn/version "0.6.46"} org.babashka/http-client {:mvn/version "0.1.4"}} :aliases {:babashka/dev {:main-opts ["-m" "babashka.main"]} diff --git a/script/bump_graal_version.clj b/script/bump_graal_version.clj index 191c8e01..415d5896 100755 --- a/script/bump_graal_version.clj +++ b/script/bump_graal_version.clj @@ -11,7 +11,7 @@ ;; GraalVM Community Edition 19.3.2 based on OpenJDK 8u252 ;; GraalVM Community Edition 19.3.2 based on OpenJDK 11.0.7 ;; -;; Currently we use GraalVM java11-20.1.0 +;; Currently we use GraalVM java19-20.1.0 (ns bump-graal-version (:require [clojure.string :as str] @@ -31,8 +31,8 @@ "" "./bump_graal_version.clj -g 19.3.2 (the new version)" "or" - "./bump_graal_version.clj -g 19.3.2 --java java11" - "(for GraalVM java11-19.3.2)" + "./bump_graal_version.clj -g 19.3.2 --java java19" + "(for GraalVM java19-19.3.2)" ""] (str/join \newline)))) @@ -54,8 +54,8 @@ ;; OR ;; ;; We could have them as environment variables -(def current-graal-version "22.3.0") -(def current-java-version "java11") +(def current-graal-version "22.3.1") +(def current-java-version "java19") (def cl-options [["-g" "--graal VERSION" "graal version"] diff --git a/script/compile b/script/compile index 67c281de..74b37be6 100755 --- a/script/compile +++ b/script/compile @@ -44,7 +44,8 @@ args=("-jar" "$BABASHKA_JAR" "--no-fallback" "--native-image-info" # --trace-class-initialization=jdk.internal.net.http.common.DebugLogger,jdk.internal.net.http.websocket.WebSocketImpl,jdk.internal.net.http.common.Utils - "$BABASHKA_XMX") + "$BABASHKA_XMX" + "--enable-preview") BABASHKA_STATIC=${BABASHKA_STATIC:-} BABASHKA_MUSL=${BABASHKA_MUSL:-} diff --git a/script/compile.bat b/script/compile.bat index 56d3396c..78a1be3e 100644 --- a/script/compile.bat +++ b/script/compile.bat @@ -29,6 +29,7 @@ call %GRAALVM_HOME%\bin\native-image.cmd ^ "-H:+ReportExceptionStackTraces" ^ "--verbose" ^ "--no-fallback" ^ + "--enable-preview" ^ "%BABASHKA_XMX%" if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/script/install-graalvm b/script/install-graalvm index 19d43c5a..4e564e6f 100755 --- a/script/install-graalvm +++ b/script/install-graalvm @@ -7,31 +7,34 @@ INSTALL_DIR="${1:-$HOME}" GRAALVM_VERSION="${GRAALVM_VERSION:-21.2.0}" case "$BABASHKA_PLATFORM" in - macos) - GRAALVM_PLATFORM="darwin" - ;; - linux) - GRAALVM_PLATFORM="linux" - ;; + macos) + GRAALVM_PLATFORM="darwin" + ;; + linux) + GRAALVM_PLATFORM="linux" + ;; esac case "${BABASHKA_ARCH:-}" in - aarch64) - GRAALVM_ARCH="aarch64" - ;; - *) - GRAALVM_ARCH="amd64" - ;; + aarch64) + GRAALVM_ARCH="aarch64" + ;; + *) + GRAALVM_ARCH="amd64" + ;; esac -GRAALVM_FILENAME="graalvm-ce-java11-$GRAALVM_PLATFORM-$GRAALVM_ARCH-$GRAALVM_VERSION.tar.gz" +GRAALVM_FILENAME="graalvm-ce-java19-$GRAALVM_PLATFORM-$GRAALVM_ARCH-$GRAALVM_VERSION.tar.gz" pushd "$INSTALL_DIR" >/dev/null -if ! [ -d "graalvm-ce-java11-$GRAALVM_VERSION" ]; then - echo "Downloading GraalVM $GRAALVM_PLATFORM-$GRAALVM_ARCH-$GRAALVM_VERSION on '$PWD'..." - curl -O -sL "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-$GRAALVM_VERSION/$GRAALVM_FILENAME" - tar xzf "$GRAALVM_FILENAME" +if ! [ -d "graalvm-ce-java19-$GRAALVM_VERSION" ]; then + echo "Downloading GraalVM $GRAALVM_PLATFORM-$GRAALVM_ARCH-$GRAALVM_VERSION on '$PWD'..." + echo "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-$GRAALVM_VERSION/$GRAALVM_FILENAME" + curl -LO "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-$GRAALVM_VERSION/$GRAALVM_FILENAME" + ls -la + tar xzvf "$GRAALVM_FILENAME" + ls -la "graalvm-ce-java19-$GRAALVM_VERSION" fi popd >/dev/null diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index 7749c702..96a114ea 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -8,6 +8,10 @@ [sci.core :as sci] [sci.impl.types :as t])) +(def has-of-virtual? + (some #(= "ofVirtual" (.getName ^java.lang.reflect.Method %)) + (.getMethods Thread))) + (def base-custom-map `{clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true :allPublicMethods true} @@ -47,7 +51,8 @@ {:name "sleep"} {:name "start"} {:name "toString"} - {:name "yield"}]} + {:name "yield"} + ~@(when has-of-virtual? [{:name "ofVirtual"}])]} java.net.URL {:allPublicConstructors true :allPublicFields true @@ -433,6 +438,7 @@ java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy java.util.concurrent.ThreadPoolExecutor$DiscardOldestPolicy java.util.concurrent.ThreadPoolExecutor$DiscardPolicy + java.util.concurrent.ExecutorService java.util.concurrent.ScheduledExecutorService java.util.concurrent.Future java.util.concurrent.FutureTask @@ -663,6 +669,8 @@ java.util.concurrent.Future (instance? java.util.concurrent.ScheduledExecutorService v) java.util.concurrent.ScheduledExecutorService + (instance? java.util.concurrent.ExecutorService v) + java.util.concurrent.ExecutorService (instance? java.util.Iterator v) java.util.Iterator (instance? javax.crypto.SecretKey v) diff --git a/test/babashka/crypto_test.clj b/test/babashka/crypto_test.clj index 4bcc67fc..22141991 100644 --- a/test/babashka/crypto_test.clj +++ b/test/babashka/crypto_test.clj @@ -20,6 +20,7 @@ expected-sha (String. (.encode (java.util.Base64/getEncoder) (hmac-sha-256 (.getBytes key-s) data)) "utf-8")] + (prn expected-sha) (is (= expected-sha (bb '(do (ns net (:import (javax.crypto Mac) (javax.crypto.spec SecretKeySpec))) diff --git a/test/babashka/error_test.clj b/test/babashka/error_test.clj index e82a7175..e79967b3 100644 --- a/test/babashka/error_test.clj +++ b/test/babashka/error_test.clj @@ -7,13 +7,19 @@ [clojure.test :as t :refer [deftest is testing]] [matcher-combinators.test])) +(defn process-difference [line] + (-> line str/trimr + ;; take into account JDK14+ and native image differences + (str/replace "class clojure.lang" "clojure.lang") + (str/replace #" \(.*\)$" ""))) + (defmacro multiline-equals [s1 s2] `(let [lines-s1# (str/split-lines ~s1) lines-s2# (str/split-lines ~s2) max-lines# (max (count lines-s1#) (count lines-s2#)) lines-s1# (take max-lines# lines-s1#) lines-s2# (take max-lines# lines-s2#)] - (is (~'match? (map str/trimr lines-s1#) (map str/trimr lines-s2#))) + (is (~'match? (map process-difference lines-s1#) (map process-difference lines-s2#))) #_(run! (fn [i] (let [l1 (get lines-s1 i) l2 (get lines-s2 i)] @@ -117,58 +123,63 @@ user - :1:1"))))) (deftest error-while-macroexpanding-test - (let [output (try (tu/bb nil "-e" "(defmacro foo [x] (subs nil 1) `(do ~x ~x)) (foo 1)") + (let [output (try (tu/bb nil "-e" "(defmacro foo [x] (assoc :foo 1 2) `(do ~x ~x)) (foo 1)") (catch Exception e (ex-message e)))] (multiline-equals output "----- Error -------------------------------------------------------------------- -Type: java.lang.NullPointerException +Type: java.lang.ClassCastException +Message: clojure.lang.Keyword cannot be cast to clojure.lang.Associative Location: :1:19 Phase: macroexpand ----- Context ------------------------------------------------------------------ -1: (defmacro foo [x] (subs nil 1) `(do ~x ~x)) (foo 1) - ^--- +1: (defmacro foo [x] (assoc :foo 1 2) `(do ~x ~x)) (foo 1) + ^--- clojure.lang.Keyword cannot be cast to clojure.lang.Associative ----- Stack trace -------------------------------------------------------------- -clojure.core/subs - -user/foo - :1:19 -user/foo - :1:1 -user - :1:45"))) +clojure.core/assoc--5481 - +clojure.core/assoc - +user/foo - :1:19 +user/foo - :1:1 +user - :1:49"))) (deftest error-in-macroexpansion-test - (let [output (try (tu/bb nil "-e" "(defmacro foo [x] `(subs nil ~x)) (foo 1)") + (let [output (try (tu/bb nil "-e" "(defmacro foo [x] `(assoc :foo ~x 2)) (foo 1)") (catch Exception e (ex-message e)))] (multiline-equals output "----- Error -------------------------------------------------------------------- -Type: java.lang.NullPointerException -Location: :1:35 +Type: java.lang.ClassCastException +Message: clojure.lang.Keyword cannot be cast to clojure.lang.Associative +Location: :1:39 ----- Context ------------------------------------------------------------------ -1: (defmacro foo [x] `(subs nil ~x)) (foo 1) - ^--- +1: (defmacro foo [x] `(assoc :foo ~x 2)) (foo 1) + ^--- clojure.lang.Keyword cannot be cast to clojure.lang.Associative ----- Stack trace -------------------------------------------------------------- -clojure.core/subs - -user - :1:35 -")) +clojure.core/assoc--5481 - +clojure.core/assoc - +user - :1:39")) (testing "calling a var inside macroexpansion" - (let [output (try (tu/bb nil "-e" "(defn quux [] (subs nil 1)) (defmacro foo [x & xs] `(do (quux) ~x)) (defn bar [] (foo 1)) (bar)") + (let [output (try (tu/bb nil "-e" "(defn quux [] (assoc :foo 1 2)) (defmacro foo [x & xs] `(do (quux) ~x)) (defn bar [] (foo 1)) (bar)") (catch Exception e (ex-message e)))] (multiline-equals output "----- Error -------------------------------------------------------------------- -Type: java.lang.NullPointerException +Type: java.lang.ClassCastException +Message: clojure.lang.Keyword cannot be cast to clojure.lang.Associative Location: :1:15 ----- Context ------------------------------------------------------------------ -1: (defn quux [] (subs nil 1)) (defmacro foo [x & xs] `(do (quux) ~x)) (defn bar [] (foo 1)) (bar) - ^--- +1: (defn quux [] (assoc :foo 1 2)) (defmacro foo [x & xs] `(do (quux) ~x)) (defn bar [] (foo 1)) (bar) + ^--- clojure.lang.Keyword cannot be cast to clojure.lang.Associative ----- Stack trace -------------------------------------------------------------- -clojure.core/subs - -user/quux - :1:15 -user/quux - :1:1 -user/bar - :1:69 -user - :1:91")))) +clojure.core/assoc--5481 - +clojure.core/assoc - +user/quux - :1:15 +user/quux - :1:1 +user/bar - :1:73 +user - :1:95")))) (deftest print-exception-data-test (testing "output of uncaught ExceptionInfo" @@ -232,29 +243,31 @@ clojure.lang.ExceptionInfo: Divide by zero"))) "{:type :sci/error, :line 1, :column 12, :message \"Divide by zero\","))))) (deftest macro-test - (let [output (try (tu/bb nil "--debug" "(defmacro foo [x] (subs nil 1) `(do ~x ~x)) (foo 1)") + (let [output (try (tu/bb nil "--debug" "(defmacro foo [x] (assoc :foo 1 2) `(do ~x ~x)) (foo 1)") (is false) (catch Exception e (ex-message e))) output (tu/normalize output) - actual-lines (str/join "\n" (take 17 (str/split-lines output)))] + actual-lines (str/join "\n" (take 19 (str/split-lines output)))] (multiline-equals actual-lines "----- Error -------------------------------------------------------------------- -Type: java.lang.NullPointerException +Type: java.lang.ClassCastException +Message: clojure.lang.Keyword cannot be cast to clojure.lang.Associative Location: :1:19 Phase: macroexpand ----- Context ------------------------------------------------------------------ -1: (defmacro foo [x] (subs nil 1) `(do ~x ~x)) (foo 1) - ^--- +1: (defmacro foo [x] (assoc :foo 1 2) `(do ~x ~x)) (foo 1) + ^--- clojure.lang.Keyword cannot be cast to clojure.lang.Associative ----- Stack trace -------------------------------------------------------------- -clojure.core/subs - -user/foo - :1:19 -user/foo - :1:1 -user - :1:45 +clojure.core/assoc--5481 - +clojure.core/assoc - +user/foo - :1:19 +user/foo - :1:1 +user - :1:49 ----- Exception ---------------------------------------------------------------- -clojure.lang.ExceptionInfo: null"))) +clojure.lang.ExceptionInfo: clojure.lang.Keyword cannot be cast to clojure.lang.Associative"))) (deftest native-stacktrace-test (let [output (try (tu/bb nil "(merge 1 2 3)")