git » blacktide.git » master » tree

[master] / test / com / tailrecursion / blacktide / aot_test.clj

(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))))))