(ns com.tailrecursion.blacktide.aot-test
"AOT in a fresh JVM must compile dependencies and run without source files or
the type checker. Preloading compiler namespaces would hide initializer limits."
(:require [clojure.test :refer [deftest is]]
[clojure.java.io :as io]
[clojure.string :as str]))
(defn subprocess [classpath form]
(let [java (str (io/file (System/getProperty "java.home") "bin" "java"))
process (-> (ProcessBuilder. ^java.util.List [java "-Xmx768m" "-cp" classpath "clojure.main" "-e" (pr-str form)])
(.redirectErrorStream true) (.start))
output (slurp (.getInputStream process))]
{:exit (.waitFor process) :output output}))
(deftest fresh-aot-and-source-free-use
(let [directory (.toFile (java.nio.file.Files/createTempDirectory "blacktide-aot-" (make-array java.nio.file.attribute.FileAttribute 0)))
classpath (System/getProperty "java.class.path")
dependencies (filter #(str/ends-with? % ".jar") (str/split classpath (re-pattern java.io.File/pathSeparator)))
compiled (subprocess classpath `(binding [*compile-path* ~(.getPath directory)]
(compile 'com.tailrecursion.blacktide.aot-fixture)))]
(try
(is (zero? (:exit compiled)) (:output compiled))
(when (zero? (:exit compiled))
(let [loaded (subprocess (str/join java.io.File/pathSeparator (cons (.getPath directory) dependencies))
'(do (require 'com.tailrecursion.blacktide.aot-fixture)
(assert (= 9 (com.tailrecursion.blacktide.aot-fixture/sample 4)))
(assert (= 7 (com.tailrecursion.blacktide.aot-fixture/keyword-read {:k 7})))
(let [value (Object.)]
(doseq [flag [true false]]
(assert (identical? value (com.tailrecursion.blacktide.aot-fixture/reference-site flag value)))))
(assert (= 9 (eval '(com.tailrecursion.blacktide/loophole [x 1]
(com.tailrecursion.blacktide/setq x 9) x))))
(assert (= 11 (eval '(com.tailrecursion.blacktide/loophole [x nil]
(:k (com.tailrecursion.blacktide/setq x {:k 11}))))))
(println "AOT client and source-free macro expansion passed")))]
(is (zero? (:exit loaded)) (:output loaded))))
;; Execute generated function classes with only Clojure and the client.
;; Loading the client's namespace would also execute its macro require.
(when (zero? (:exit compiled))
(doseq [file (file-seq directory)
:when (and (.isFile file) (not (.startsWith (.getName file) "aot_fixture")))]
(io/delete-file file))
(let [loaded (subprocess (str/join java.io.File/pathSeparator (cons (.getPath directory) dependencies))
'(do
(assert (= 9 (clojure.lang.Reflector/invokeStaticMethod
"com.tailrecursion.blacktide.aot_fixture$sample" "invokeStatic" (to-array [4]))))
(assert (= 7 (clojure.lang.Reflector/invokeStaticMethod
"com.tailrecursion.blacktide.aot_fixture$keyword_read" "invokeStatic" (to-array [{:k 7}]))))
(println "Generated code runs without compiler classes")))]
(is (zero? (:exit loaded)) (:output loaded))))
(finally (doseq [file (reverse (file-seq directory))] (io/delete-file file))))))