git » blacktide.git » commit ddf183a

Make semantic test suites independently runnable

author Alan Dipert
2026-11-11 03:24:01 UTC
committer Alan Dipert
2026-11-11 03:24:01 UTC
parent 2014360db343c62d6d7d0930bee900f0b5b97b92

Make semantic test suites independently runnable

test/com/tailrecursion/blacktide/assignment_test.clj +3 -4
test/com/tailrecursion/blacktide/backend_fixture.clj +14 -2
test/com/tailrecursion/blacktide/case_test.clj +17 -17
test/com/tailrecursion/blacktide/collections_test.clj +1 -1
test/com/tailrecursion/blacktide/control_test.clj +4 -4
test/com/tailrecursion/blacktide/invocation_test.clj +3 -6
test/com/tailrecursion/blacktide/jumps_test.clj +2 -2
test/com/tailrecursion/blacktide/jvm_fixture.clj +50 -11
test/com/tailrecursion/blacktide/jvm_test.clj +4 -20
test/com/tailrecursion/blacktide/lookup_test.clj +3 -15
test/com/tailrecursion/blacktide/loops_test.clj +19 -19
test/com/tailrecursion/blacktide/native_sites_test.clj +3 -4
test/com/tailrecursion/blacktide/source_loops_test.clj +11 -12
test/com/tailrecursion/blacktide/source_test.clj +4 -5
test/com/tailrecursion/blacktide/subset_test.clj +3 -3

diff --git a/test/com/tailrecursion/blacktide/assignment_test.clj b/test/com/tailrecursion/blacktide/assignment_test.clj
index 0e26566..a235757 100644
--- a/test/com/tailrecursion/blacktide/assignment_test.clj
+++ b/test/com/tailrecursion/blacktide/assignment_test.clj
@@ -7,7 +7,6 @@
             [clojure.test.check.properties :as prop]
             [com.tailrecursion.blacktide :as lh :refer [loophole setq psetq]]
             [com.tailrecursion.blacktide.backend-fixture :as backend]
-            [com.tailrecursion.blacktide.source-test :as source]
             [com.tailrecursion.assignment-model :as oracle]))
 
 (defn add [a b] (+ a b))
@@ -60,10 +59,10 @@
                        ['(setq [x] 1) :invalid-assignment]
                        ['(psetq missing 1) :unknown-assignment]
                        ['(let [x 3] (setq x 4)) :immutable-assignment]]]
-    (is (= kind (:kind (source/rejection (list 'loophole '[x 1] body))))))
+    (is (= kind (:kind (backend/rejection (list 'loophole '[x 1] body))))))
   (is (= [1 2] (loophole [setq vector] (setq 1 2))))
   (is (= [3] (loophole [psetq vector] (psetq 3))))
-  (is (= :immutable-assignment (:kind (source/rejection '(let [outside 1] (loophole [] (setq outside 2)))))))
+  (is (= :immutable-assignment (:kind (backend/rejection '(let [outside 1] (loophole [] (setq outside 2)))))))
   (is (= 2 (loophole [x 1] (lh/setq x 2)))))
 
 (defn render [node]
@@ -103,7 +102,7 @@
     (let [args (concat (map #(list 'setq 'x %) (range 1 (inc n))) ['(do (setq f list) x) 'x])
           form (list 'loophole '[f vector x 0] [(cons 'f args) '(f :after) 'x])
           expected [(vec (concat (range 1 (inc n)) [n n])) '(:after) n]]
-      (is (= expected (source/run-form form)) (pr-str {:arity (+ n 2) :form form})))))
+      (is (= expected (backend/run-form form)) (pr-str {:arity (+ n 2) :form form})))))
 
 (defspec generated-effectful-conditions {:num-tests 100 :seed 20261104}
   (prop/for-all [test (gen/elements [(oracle/batch :psetq 'x (oracle/read-var 'y) 'y (oracle/read-var 'x))
diff --git a/test/com/tailrecursion/blacktide/backend_fixture.clj b/test/com/tailrecursion/blacktide/backend_fixture.clj
index 1076334..576b63b 100644
--- a/test/com/tailrecursion/blacktide/backend_fixture.clj
+++ b/test/com/tailrecursion/blacktide/backend_fixture.clj
@@ -13,9 +13,21 @@
                                             compiler/compile-program)]
     (f)))
 
+(defn run-form
+  ([form] (run-form 'com.tailrecursion.blacktide.backend-fixture form))
+  ([namespace form] (binding [*ns* (the-ns namespace)] (eval form))))
+
+(defn root-error [error]
+  (if-let [cause (ex-cause error)] (recur cause) error))
+
+(defn rejection
+  ([form] (rejection 'com.tailrecursion.blacktide.backend-fixture form))
+  ([namespace form]
+   (try (run-form namespace form) nil
+        (catch Throwable error (ex-data (root-error error))))))
+
 (defn compile-program [form mode]
-  (binding [*ns* (the-ns 'com.tailrecursion.blacktide.backend-fixture)]
-    (with-backend mode #(eval (list 'fn '[events] form)))))
+  (with-backend mode #(run-form (list 'fn '[events] form))))
 
 (defn capture [pass form]
   (let [result (atom nil) original @pass]
diff --git a/test/com/tailrecursion/blacktide/case_test.clj b/test/com/tailrecursion/blacktide/case_test.clj
index c8ee1da..90767f7 100644
--- a/test/com/tailrecursion/blacktide/case_test.clj
+++ b/test/com/tailrecursion/blacktide/case_test.clj
@@ -7,8 +7,8 @@
             [clojure.test.check.properties :as prop]
             [clojure.walk :as walk]
             [com.tailrecursion.blacktide :refer [loophole setq psetq label goto]]
-            [com.tailrecursion.blacktide.jvm-test :as jvm]
-            [com.tailrecursion.blacktide.source-test :as source]))
+            [com.tailrecursion.blacktide.jvm-fixture :as jvm]
+            [com.tailrecursion.blacktide.backend-fixture :as backend]))
 
 (defn compile-function [form]
   (binding [*ns* (the-ns 'com.tailrecursion.blacktide.case-test)]
@@ -57,10 +57,10 @@
   (is (= 0 (loophole [x 0] (case (setq x 1) :default) (setq x 0))))
   (is (= [:string :long :object :object]
          (loophole [x 1]
-           [(.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser (case x 1 "x" "y"))
-            (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser (case x 1 2 3))
-            (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser (case x 1 true false))
-            (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser (case x 1 2 nil))]))))
+           [(.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser (case x 1 "x" "y"))
+            (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser (case x 1 2 3))
+            (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser (case x 1 true false))
+            (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser (case x 1 2 nil))]))))
 
 (deftest case-jumps-do-not-fall-through
   (is (= :done (loophole []
@@ -81,8 +81,8 @@
                      (0 1 2) (do (setq sum (+ sum i)) (recur (inc i)))
                      3 (do (setq sum (+ sum i)) [i sum]))))))
   (doseq [form '[(loop [i 3] (case i 0 :done (recur (dec i))))
-                (loop [i 1] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice chooser (case i 1 i 0)))
-                (loop [i 1] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice chooser (case i 1 "a" nil)))
+                (loop [i 1] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice chooser (case i 1 i 0)))
+                (loop [i 1] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice chooser (case i 1 "a" nil)))
                 (loop [i 0] (case i 0 (let [i 1] (case i 1 :yes :no)) :wrong))]]
     (is (= (jvm/outcome form) (jvm/outcome (list 'loophole [] form))) (pr-str form)))
   (is (= :done (loophole [] (loop [i 1] (case i 0 :done (do (setq) (psetq) (recur 0))))))))
@@ -95,7 +95,7 @@
 (deftest case-macro-environments-and-initialization
   (reset! expansions [])
   (is (= :group
-         (source/run-form
+         (backend/run-form
            '(com.tailrecursion.blacktide/loophole []
               (case :a
                 (:a :b) (com.tailrecursion.blacktide.case-test/expanded :group :group)
@@ -111,7 +111,7 @@
       (is (= :marker-position
              (try (eval form) nil
                   (catch Throwable error
-                    (:kind (ex-data (loop [error error] (if-let [cause (ex-cause error)] (recur cause) error)))))))))))
+                    (:kind (ex-data (backend/root-error error))))))))))
 
 (deftest native-case-errors-and-source-validation
   (doseq [form '[(case 1 1 :a 1 :b)
@@ -122,12 +122,12 @@
     (is (= (:error (jvm/outcome form)) (:error (jvm/outcome (list 'loophole [] form)))) (pr-str form)))
   ;; The public diagnostic contract distinguishes a missing dispatch expression
   ;; from core case's constant/default errors.
-  (is (= :invalid-case (:kind (source/rejection '(loophole [] (case))))))
-  (is (= :marker-position (:kind (source/rejection '(loophole [] (case 1 1 (label same) 2 (label same)))))))
-  (is (= :undefined-label (:kind (source/rejection '(loophole [] (case 1 1 :ok 2 (goto missing)))))))
+  (is (= :invalid-case (:kind (backend/rejection '(loophole [] (case))))))
+  (is (= :marker-position (:kind (backend/rejection '(loophole [] (case 1 1 (label same) 2 (label same)))))))
+  (is (= :undefined-label (:kind (backend/rejection '(loophole [] (case 1 1 :ok 2 (goto missing)))))))
   (is (= :marker-position
-         (:kind (source/rejection '(loophole [] (goto inside) (+ 1 (case 0 0 (do (label inside) 2) 3)))))))
-  (is (= :invalid-recur (:kind (source/rejection '(loophole [] (loop [] (inc (case 0 0 (recur) 1)))))))))
+         (:kind (backend/rejection '(loophole [] (goto inside) (+ 1 (case 0 0 (do (label inside) 2) 3)))))))
+  (is (= :invalid-recur (:kind (backend/rejection '(loophole [] (loop [] (inc (case 0 0 (recur) 1)))))))))
 
 (deftest prepared-dispatch-and-expansion-equivalence
   (doseq [body '[(case input 0 :zero 2 :two :other)
@@ -153,7 +153,7 @@
                 (case input 1 "x" nil)
                 (case input 1 nil "x")]]
     (let [form (list 'let '[input 1]
-                    (list '.choose (with-meta 'chooser {:tag 'com.tailrecursion.blacktide.jvm_test.Choice}) body))]
+                    (list '.choose (with-meta 'chooser {:tag 'com.tailrecursion.blacktide.jvm_fixture.Choice}) body))]
       (is (= (jvm/outcome form) (jvm/outcome (list 'loophole [] form)))))))
 
 (defn command-model [{:keys [tokens a b weight width]}]
@@ -199,7 +199,7 @@
      (label ~'finished) [~'m ~'n ~'i ~'out]))
 
 (defn command-outcome [config wrap]
-  (try {:value (source/run-form (wrap (command-program config)))}
+  (try {:value (backend/run-form (wrap (command-program config)))}
        (catch Throwable error {:error (class error) :message (ex-message error)})))
 
 (defspec generated-case-loop-jump-programs {:num-tests 120 :seed 173205}
diff --git a/test/com/tailrecursion/blacktide/collections_test.clj b/test/com/tailrecursion/blacktide/collections_test.clj
index 46bca6c..1fed09e 100644
--- a/test/com/tailrecursion/blacktide/collections_test.clj
+++ b/test/com/tailrecursion/blacktide/collections_test.clj
@@ -57,7 +57,7 @@
 (deftest constant-duplicates-are-compile-errors
   (doseq [body [(array-map '(key-a) 1 '(key-b) 2)]
           form [body (list 'loophole [] body)]]
-    (let [error (try (observe form) nil (catch Throwable e (loop [e e] (if-let [cause (ex-cause e)] (recur cause) e))))]
+    (let [error (try (observe form) nil (catch Throwable e (backend/root-error e)))]
       (is (instance? IllegalArgumentException error))))
   ;; Native constant sets coalesce macro-expanded duplicates. Metadata prevents
   ;; that constant folding, so the same duplicates then fail during construction.
diff --git a/test/com/tailrecursion/blacktide/control_test.clj b/test/com/tailrecursion/blacktide/control_test.clj
index 5c08b27..4a20282 100644
--- a/test/com/tailrecursion/blacktide/control_test.clj
+++ b/test/com/tailrecursion/blacktide/control_test.clj
@@ -2,7 +2,7 @@
   "Source control flow and live values across state-machine transitions."
   (:require [clojure.test :refer [deftest is]]
             [com.tailrecursion.blacktide :refer [loophole setq psetq]]
-            [com.tailrecursion.blacktide.source-test :as source]))
+            [com.tailrecursion.blacktide.backend-fixture :as backend]))
 
 (defn record! [events tag value] (swap! events conj tag) value)
 
@@ -54,8 +54,8 @@
                  (record! events :after :bad))
                (catch Throwable e e))))
     (is (= [:first 1] @events)))
-  (is (= :unresolved-symbol (:kind (source/rejection '(loophole [] (if true 1 missing))))))
-  (is (= :unresolved-symbol (:kind (source/rejection '(loophole [] (throw nil) missing)))))
+  (is (= :unresolved-symbol (:kind (backend/rejection '(loophole [] (if true 1 missing))))))
+  (is (= :unresolved-symbol (:kind (backend/rejection '(loophole [] (throw nil) missing)))))
   (doseq [form ['(if true) '(if true 1 2 3) '(throw) '(throw nil nil)]]
     (is (= (if (= 'if (first form)) :invalid-if :invalid-throw)
-           (:kind (source/rejection (list 'loophole [] form)))))))
+           (:kind (backend/rejection (list 'loophole [] form)))))))
diff --git a/test/com/tailrecursion/blacktide/invocation_test.clj b/test/com/tailrecursion/blacktide/invocation_test.clj
index 3e72e58..78f72cb 100644
--- a/test/com/tailrecursion/blacktide/invocation_test.clj
+++ b/test/com/tailrecursion/blacktide/invocation_test.clj
@@ -3,7 +3,8 @@
   method Var. Compare values, failures and evaluation traces with Clojure."
   (:require [clojure.test :refer [deftest is]]
             [clojure.core.protocols :as p]
-            [com.tailrecursion.blacktide.backend-fixture :as backend]))
+            [com.tailrecursion.blacktide.backend-fixture :as backend]
+            [com.tailrecursion.blacktide.jvm-fixture]))
 
 (deftype DirectReduction [events]
   p/CollReduce
@@ -112,10 +113,6 @@
 (def java-list {:on 'java.util.List :method-map {:list-set :set}})
 (defn list-set [& _] :fallback)
 (alter-meta! #'list-set assoc :protocol #'java-list)
-(def java-map {:on 'java.util.Map :method-map {:map-compute :compute}})
-(defn map-compute [_ k f] (f k :fallback))
-(alter-meta! #'map-compute assoc :protocol #'java-map)
-
 (deftest interface-conversions-are-conditional-and-ordered
   (let [observe (fn [body mode location direct?]
                   (let [events (atom []) f ((backend/compile-program (wrapped body location) mode) nil)
@@ -131,7 +128,7 @@
           (pr-str [index direct? mode])))))
 
 (deftest interface-functional-arguments
-  (let [body '(com.tailrecursion.blacktide.invocation-test/map-compute target :key
+  (let [body '(com.tailrecursion.blacktide.jvm-fixture/map-compute target :key
                 (do (swap! events conj :argument) vector))
         observe (fn [mode location direct?]
                   (let [events (atom []) f ((backend/compile-program (wrapped body location) mode) nil)
diff --git a/test/com/tailrecursion/blacktide/jumps_test.clj b/test/com/tailrecursion/blacktide/jumps_test.clj
index c751675..cdd7e2e 100644
--- a/test/com/tailrecursion/blacktide/jumps_test.clj
+++ b/test/com/tailrecursion/blacktide/jumps_test.clj
@@ -5,7 +5,7 @@
             [clojure.test.check.generators :as gen]
             [clojure.test.check.properties :as prop]
             [com.tailrecursion.blacktide :refer [loophole label goto setq psetq]]
-            [com.tailrecursion.blacktide.source-test :as source]))
+            [com.tailrecursion.blacktide.backend-fixture :as backend]))
 
 (defn step [n] (dec n))
 (defn more? [n] (pos? n))
@@ -43,7 +43,7 @@
                        ['(setq x (do (goto done) 1)) :marker-position]
                        ['(loophole [] 1) :unsupported-form]]
           prefix [[] ['(goto done)]]]
-    (is (= kind (:kind (source/rejection (list* 'loophole '[x 0] (concat prefix [body '(label done)])))))
+    (is (= kind (:kind (backend/rejection (list* 'loophole '[x 0] (concat prefix [body '(label done)])))))
         (pr-str [prefix body]))))
 
 (defn sum-through [input]
diff --git a/test/com/tailrecursion/blacktide/jvm_fixture.clj b/test/com/tailrecursion/blacktide/jvm_fixture.clj
index 874a34d..7db9df1 100644
--- a/test/com/tailrecursion/blacktide/jvm_fixture.clj
+++ b/test/com/tailrecursion/blacktide/jvm_fixture.clj
@@ -1,18 +1,57 @@
 (ns com.tailrecursion.blacktide.jvm-fixture
-  "Compile the small Java-only syntax fixture once into a temporary directory."
-  (:require [clojure.java.io :as io]))
+  "Shared JVM dispatch and lookup fixtures, with a native Clojure outcome oracle."
+  (:require [clojure.java.io :as io]
+            [com.tailrecursion.blacktide :refer [loophole setq psetq label goto]]
+            [com.tailrecursion.blacktide.backend-fixture :as backend]))
 
 (def fixture-class
   (let [directory (.toFile (java.nio.file.Files/createTempDirectory "blacktide-jvm-fixture-"
                            (make-array java.nio.file.attribute.FileAttribute 0)))
         source (io/file directory "HostFixture.java")
         compiler (javax.tools.ToolProvider/getSystemJavaCompiler)]
-    (with-open [input (io/input-stream (io/resource "com/tailrecursion/blacktide/fixtures/HostFixture.java"))]
-      (io/copy input source))
-    (when-not (zero? (.run compiler nil nil nil
-                           (into-array String ["--release" "17" "-d" (.getPath directory) (.getPath source)])))
-      (throw (ex-info "Java fixture compilation failed" {:directory directory})))
-    (.defineClass ^clojure.lang.DynamicClassLoader (clojure.lang.RT/baseLoader)
-                "com.tailrecursion.blacktide.fixtures.HostFixture"
-                (java.nio.file.Files/readAllBytes
-                  (.toPath (io/file directory "com/tailrecursion/blacktide/fixtures/HostFixture.class"))) nil)))
+    (try
+      (with-open [input (io/input-stream (io/resource "com/tailrecursion/blacktide/fixtures/HostFixture.java"))]
+        (io/copy input source))
+      (when-not (zero? (.run compiler nil nil nil
+                             (into-array String ["--release" "17" "-d" (.getPath directory) (.getPath source)])))
+        (throw (ex-info "Java fixture compilation failed" {:directory directory})))
+      (.defineClass ^clojure.lang.DynamicClassLoader (clojure.lang.RT/baseLoader)
+                   "com.tailrecursion.blacktide.fixtures.HostFixture"
+                   (java.nio.file.Files/readAllBytes
+                     (.toPath (io/file directory "com/tailrecursion/blacktide/fixtures/HostFixture.class"))) nil)
+      (finally (doseq [file (reverse (file-seq directory))] (io/delete-file file))))))
+
+(definterface Choice
+  (choose [^boolean x]) (choose [^long x]) (choose [^int x]) (choose [^double x])
+  (choose [^String x]) (choose [^chars x]) (choose [^Object x]) (choose [^Number x]))
+(def chooser
+  (reify Choice
+    (choose [_ ^boolean x] :boolean) (choose [_ ^long x] :long) (choose [_ ^int x] :int) (choose [_ ^double x] :double)
+    (choose [_ ^String x] :string) (choose [_ ^chars x] :chars)
+    (choose [_ ^Object x] :object) (choose [_ ^Number x] :number)))
+(defn ^String text [] "x")
+(defn primitive ^long [] 1)
+(defn outcome [form]
+  (try
+    {:value (binding [*ns* (the-ns 'com.tailrecursion.blacktide.jvm-fixture)]
+              (eval (list (list 'fn [] form))))}
+    (catch Throwable error
+      {:error (class (backend/root-error error))})))
+
+(deftype ObservedLookup [events entries]
+  clojure.lang.ILookup
+  (valAt [_ key] (swap! events conj [:val key]) (get entries key))
+  (valAt [_ key default] (swap! events conj [:default key]) (get entries key default))
+  clojure.lang.IKeywordLookup
+  (getLookupThunk [_ key]
+    (swap! events conj [:install key])
+    (reify clojure.lang.ILookupThunk
+      (get [thunk target]
+        (if (instance? ObservedLookup target)
+          (do (swap! (.-events ^ObservedLookup target) conj [:get key])
+              (get (.-entries ^ObservedLookup target) key))
+          thunk)))))
+
+(def java-map {:on 'java.util.Map :method-map {:map-compute :compute}})
+(defn map-compute [_ k f] (f k :fallback))
+(alter-meta! #'map-compute assoc :protocol #'java-map)
diff --git a/test/com/tailrecursion/blacktide/jvm_test.clj b/test/com/tailrecursion/blacktide/jvm_test.clj
index 2a73e6d..4a6312b 100644
--- a/test/com/tailrecursion/blacktide/jvm_test.clj
+++ b/test/com/tailrecursion/blacktide/jvm_test.clj
@@ -5,31 +5,15 @@
             [clojure.test.check.clojure-test :refer [defspec]]
             [clojure.test.check.generators :as gen]
             [clojure.test.check.properties :as prop]
-            [com.tailrecursion.blacktide.jvm-fixture]
-            [com.tailrecursion.blacktide :refer [loophole setq psetq label goto]]))
-
-(definterface Choice
-  (choose [^boolean x]) (choose [^long x]) (choose [^int x]) (choose [^double x])
-  (choose [^String x]) (choose [^chars x]) (choose [^Object x]) (choose [^Number x]))
-(def chooser
-  (reify Choice
-    (choose [_ ^boolean x] :boolean) (choose [_ ^long x] :long) (choose [_ ^int x] :int) (choose [_ ^double x] :double)
-    (choose [_ ^String x] :string) (choose [_ ^chars x] :chars)
-    (choose [_ ^Object x] :object) (choose [_ ^Number x] :number)))
-(defn ^String text [] "x")
-(defn primitive ^long [] 1)
+            [com.tailrecursion.blacktide.jvm-fixture :refer [chooser text primitive outcome]]
+            [com.tailrecursion.blacktide :refer [loophole setq psetq label goto]])
+  (:import [com.tailrecursion.blacktide.jvm_fixture Choice]))
+
 (def ^:dynamic *value* :root)
 (defmacro .answer [] 42)
 (defmacro constructed. [] 43)
 (defmacro inferred-tag [sym] (list 'quote (:tag (get &env sym))))
 
-(defn outcome [form]
-  (try
-    {:value (binding [*ns* (the-ns 'com.tailrecursion.blacktide.jvm-test)]
-              (eval (list (list 'fn [] form))))}
-    (catch Throwable error
-      {:error (class (loop [e error] (if-let [cause (ex-cause e)] (recur cause) e)))})))
-
 (deftest overloads-preserve-source-types
   (doseq [expr '[1 1.5 1N 1M 1/2 "x" nil false \a
                  (int 1) (long 1) (double 1) (char-array [\a])
diff --git a/test/com/tailrecursion/blacktide/lookup_test.clj b/test/com/tailrecursion/blacktide/lookup_test.clj
index 875dba5..67dc025 100644
--- a/test/com/tailrecursion/blacktide/lookup_test.clj
+++ b/test/com/tailrecursion/blacktide/lookup_test.clj
@@ -2,21 +2,9 @@
   "Keyword call syntax determines cached IKeywordLookup versus ordinary ILookup
   dispatch. Native emission must preserve call sites, effects and cache faults."
   (:require [clojure.test :refer [deftest is]]
-            [com.tailrecursion.blacktide.backend-fixture :as backend]))
-
-(deftype ObservedLookup [events entries]
-  clojure.lang.ILookup
-  (valAt [_ key] (swap! events conj [:val key]) (get entries key))
-  (valAt [_ key default] (swap! events conj [:default key]) (get entries key default))
-  clojure.lang.IKeywordLookup
-  (getLookupThunk [_ key]
-    (swap! events conj [:install key])
-    (reify clojure.lang.ILookupThunk
-      (get [thunk target]
-        (if (instance? ObservedLookup target)
-          (do (swap! (.-events ^ObservedLookup target) conj [:get key])
-              (get (.-entries ^ObservedLookup target) key))
-          thunk)))))
+            [com.tailrecursion.blacktide.backend-fixture :as backend]
+            [com.tailrecursion.blacktide.jvm-fixture])
+  (:import [com.tailrecursion.blacktide.jvm_fixture ObservedLookup]))
 
 (deftype UncachedLookup [events entries]
   clojure.lang.ILookup
diff --git a/test/com/tailrecursion/blacktide/loops_test.clj b/test/com/tailrecursion/blacktide/loops_test.clj
index 7291329..b8d58ef 100644
--- a/test/com/tailrecursion/blacktide/loops_test.clj
+++ b/test/com/tailrecursion/blacktide/loops_test.clj
@@ -6,8 +6,8 @@
             [clojure.test.check.generators :as gen]
             [clojure.test.check.properties :as prop]
             [com.tailrecursion.blacktide :refer [loophole label goto setq psetq]]
-            [com.tailrecursion.blacktide.source-test :as source]
-            [com.tailrecursion.blacktide.jvm-test :as jvm]))
+            [com.tailrecursion.blacktide.backend-fixture :as backend]
+            [com.tailrecursion.blacktide.jvm-fixture :as jvm]))
 
 (deftest source-recursion-and-macro-loops
   (is (= [2 1] (loophole [] (loop [n 3 a 1 b 2] (if (zero? n) [a b] (recur (dec n) b a))))))
@@ -49,13 +49,13 @@
                  (recur (dec n)))) x))))
 
 (deftest native-loop-inference
-  (doseq [form '[(loop [i 1] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice chooser i))
-                (loop [i (int 1)] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice chooser i))
-                (loop [i (float 1)] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice chooser i))
+  (doseq [form '[(loop [i 1] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice chooser i))
+                (loop [i (int 1)] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice chooser i))
+                (loop [i (float 1)] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice chooser i))
                 (loop [n 1 value 0] (if (pos? n) (recur (dec n) "string") value))
                 (loop [n 1 value 0] (if (pos? n) (recur (dec n) 1.5) (class value)))
                 (loop [n 1 value 0] (if (pos? n) (recur (dec n) 1.5)
-                                     (.choose ^com.tailrecursion.blacktide.jvm_test.Choice chooser value)))
+                                     (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice chooser value)))
                 (loop [a (java.util.ArrayList. [1 2 3]) i (int 1)] (.remove a i) (vec a))
                 (loop [x "abc"] (let [snapshot x] (String/.substring snapshot 1)))
                 (loop [n 0] (if (zero? n) (recur 1) ^{:value n} [n]))
@@ -66,7 +66,7 @@
     (is (= (jvm/outcome form) (jvm/outcome (list 'loophole '[touched nil] body))) (pr-str [lowered? form])))
   (is (= :long (loophole [x 1]
                  (loop [n 2] (setq x n)
-                   (loop [n 1] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser n))))))
+                   (loop [n 1] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser n))))))
   ;; DSL writes retain the same initializer promotions as ordinary Clojure.
   (is (= (loop [i (int 1)] (class i)) (loophole [x nil] (loop [i (int 1)] (setq x true) (class i)))))
   (is (= (loop [i (float 1)] (class i)) (loophole [x nil] (loop [i (float 1)] (setq x true) (class i))))))
@@ -77,7 +77,7 @@
 (defmacro make-constant [] (alter-meta! #'changing-value assoc :const true) nil)
 
 (deftest native-types-survive-later-macro-expansion
-  (let [body '[(loop [] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser tagged-value)) (erase-tag)]
+  (let [body '[(loop [] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser tagged-value)) (erase-tag)]
         evaluate (fn [wrap]
                    (alter-meta! #'tagged-value assoc :tag 'String)
                    (binding [*ns* (the-ns 'com.tailrecursion.blacktide.loops-test)]
@@ -99,19 +99,19 @@
                (alter-var-root #'changing-value (constantly :old))))))
 
 (deftest lowered-loop-interop-boundary
-  (doseq [body '[(.choose ^com.tailrecursion.blacktide.jvm_test.Choice com.tailrecursion.blacktide.jvm-test/chooser i)
-                (let [copy i] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice com.tailrecursion.blacktide.jvm-test/chooser copy))
-                (.choose ^com.tailrecursion.blacktide.jvm_test.Choice com.tailrecursion.blacktide.jvm-test/chooser (inc i))
-                (loop [] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice com.tailrecursion.blacktide.jvm-test/chooser i))
-                (loop [copy i] (.choose ^com.tailrecursion.blacktide.jvm_test.Choice com.tailrecursion.blacktide.jvm-test/chooser copy))]]
+  (doseq [body '[(.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice com.tailrecursion.blacktide.jvm-fixture/chooser i)
+                (let [copy i] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice com.tailrecursion.blacktide.jvm-fixture/chooser copy))
+                (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice com.tailrecursion.blacktide.jvm-fixture/chooser (inc i))
+                (loop [] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice com.tailrecursion.blacktide.jvm-fixture/chooser i))
+                (loop [copy i] (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice com.tailrecursion.blacktide.jvm-fixture/chooser copy))]]
     (let [form (list 'loophole '[x 0] (list 'loop '[i 1] '(setq x 1) body))]
       (is (= (jvm/outcome (list 'loop '[i 1] body)) (jvm/outcome form)) (pr-str form))))
   (is (= :int (loophole [x 0] (loop [i 1] (setq x 1)
-                               (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser (int i))))))
+                               (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser (int i))))))
   (is (= :object (loophole [x 0] (loop [i (identity 1)] (setq x 1)
-                                  (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser i)))))
+                                  (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser i)))))
   (is (= :long (loophole [x 0] (loop [i 1] (setq x 1)
-                                (^{:param-tags [long]} com.tailrecursion.blacktide.jvm_test.Choice/.choose jvm/chooser i))))))
+                                (^{:param-tags [long]} com.tailrecursion.blacktide.jvm_fixture.Choice/.choose jvm/chooser i))))))
 
 (deftest recur-legality-and-loop-scopes
   (doseq [[form kind]
@@ -128,7 +128,7 @@
            ['(loophole [] (goto body) (loop [] (label body))) :marker-position]
            ['(loophole [] (loop [] (goto other)) (loop [] (label other))) :marker-position]
            ['(loophole [] (goto done) (loop [] (recur 1)) (label done)) :recur-arity]]]
-    (is (= kind (:kind (source/rejection form))) (pr-str form))))
+    (is (= kind (:kind (backend/rejection form))) (pr-str form))))
 
 (deftest illegal-primitive-recur-conversions
   (doseq [cast '[byte short char]]
@@ -136,7 +136,7 @@
           lowered (list 'loop '[n 0] '(setq x n) (list 'recur (list cast 1)))]
       (is (= IllegalArgumentException (:error (jvm/outcome body))))
       (is (= :invalid-recur-type
-             (:kind (source/rejection (list 'loophole '[x nil] lowered))))))))
+             (:kind (backend/rejection (list 'loophole '[x nil] lowered))))))))
 
 (defn loop-model [{:keys [limit cut width a b weight]}]
   (loop [i 0 a a b b history []]
@@ -161,7 +161,7 @@
      (label ~'done) ~'result))
 
 (defn loop-outcome [config wrap]
-  (try {:value (source/run-form (wrap (loop-program config)))}
+  (try {:value (backend/run-form (wrap (loop-program config)))}
        (catch Throwable error {:error (class error) :message (ex-message error)})))
 
 (defspec generated-nested-loops {:num-tests 240 :seed 161803}
diff --git a/test/com/tailrecursion/blacktide/native_sites_test.clj b/test/com/tailrecursion/blacktide/native_sites_test.clj
index a028312..2372b13 100644
--- a/test/com/tailrecursion/blacktide/native_sites_test.clj
+++ b/test/com/tailrecursion/blacktide/native_sites_test.clj
@@ -8,8 +8,7 @@
             [com.tailrecursion.blacktide.compiler.passes.structure :as structure]
             [com.tailrecursion.blacktide.backend-fixture :as backend]
             [com.tailrecursion.blacktide :refer [loophole setq label goto]]
-            [com.tailrecursion.blacktide.invocation-test]
-            [com.tailrecursion.blacktide.lookup-test :as lookup]))
+            [com.tailrecursion.blacktide.jvm-fixture :as jvm]))
 
 (def flags [true false false true])
 (defn identity-pattern [values]
@@ -27,7 +26,7 @@
               #(.apply ^java.util.function.BiFunction % :left :right)]
              ['(java.util.Map/.compute target :key callback)
               #(.apply ^java.util.function.BiFunction % :left :right)]
-             ['(com.tailrecursion.blacktide.invocation-test/map-compute target :key callback)
+             ['(com.tailrecursion.blacktide.jvm-fixture/map-compute target :key callback)
               #(.apply ^java.util.function.BiFunction % :left :right)]]
             mode [:default :dispatcher]]
       (let [ordinary (list 'let ['values '(if flag [1 2] [3 4])] (list 'vector 'values tail))
@@ -43,7 +42,7 @@
 (defn cached-lookups [mode wrapped?]
   (let [lookup '(:k target)
         body `(do (if ~'flag (swap! ~'events conj :yes) (swap! ~'events conj :no)) ~lookup)
-        events (atom []) target (lookup/->ObservedLookup events {:k 42})
+        events (atom []) target (jvm/->ObservedLookup events {:k 42})
         f ((backend/compile-program (list 'fn '[flag target] (if wrapped? (list 'loophole [] body) body)) mode) events)
         values (mapv #(f % target) flags)]
     [values @events]))
diff --git a/test/com/tailrecursion/blacktide/source_loops_test.clj b/test/com/tailrecursion/blacktide/source_loops_test.clj
index 61c941d..4159d51 100644
--- a/test/com/tailrecursion/blacktide/source_loops_test.clj
+++ b/test/com/tailrecursion/blacktide/source_loops_test.clj
@@ -8,9 +8,8 @@
             [clojure.walk :as walk]
             [com.tailrecursion.blacktide :refer [loophole]]
             [com.tailrecursion.blacktide.backend-fixture :as backend]
-            [com.tailrecursion.blacktide.jvm-test :as jvm]))
+            [com.tailrecursion.blacktide.jvm-fixture :as jvm]))
 
-(defn root-error [e] (if-let [cause (ex-cause e)] (recur cause) e))
 (defn canonical [value]
   (cond
     (instance? Double value) [(class value) (Double/doubleToLongBits value)]
@@ -40,7 +39,7 @@
   (try
     (binding [*ns* (the-ns 'com.tailrecursion.blacktide.source-loops-test)]
       (backend/with-backend mode #(eval (list 'fn '[events input n] (enclose body mode)))))
-    (catch Throwable e {:compile (class (root-error e))})))
+    (catch Throwable e {:compile (class (backend/root-error e))})))
 (defn outcome [f input n]
   (if (map? f) f
     (let [events (atom [])]
@@ -92,8 +91,8 @@
                         (loop [~'i 0 ~'x ~init ~'y (+ ~'x 1)]
                           (if (< ~'i ~'n)
                             (recur (inc ~'i) (~'step ~'x) (~'step ~'y))
-                            [(.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser ~'x)
-                             (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser ~'y)
+                            [(.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser ~'x)
+                             (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser ~'y)
                              ~'x ~'y])))
                 ordinary (compile-body body :ordinary)]]
     (doseq [mode [:default :dispatcher]
@@ -128,20 +127,20 @@
   (doseq [body '[(let [step (fn ^long [^long x] (inc x))]
                    (loop [i 0 x 1000]
                      (if (< i n) (recur (inc i) (step x))
-                       [(.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser x)
-                        (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser i)])))
+                       [(.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser x)
+                        (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser i)])))
                 (let [step (fn ^long [^long x] (inc x))]
                    (loop [i 0 x 1000 y x]
                      (if (< i n) (recur (inc i) (step x) x)
-                       (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser y))))
+                       (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser y))))
                 (let [step (fn ^double [^double x] (+ x 0.5))]
                    (loop [i 0 x 1000.0]
                      (if (< i n) (recur (inc i) (step x))
-                       (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser x))))
+                       (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser x))))
                 (let [step (fn [x] input)]
                    (loop [i 0 x 1000]
                      (if (< i n) (recur (inc i) (step x))
-                       (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser x))))
+                       (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser x))))
                 (let [step (fn ^long [^long x] (inc x))]
                    (loop [i 0 x 1000]
                      (if (< i n) (recur (inc i) (step x)) (Math/abs x))))
@@ -180,8 +179,8 @@
         initial (if literal? (if double? 1.5 1000) (list cast 'input))
         result (if scalar? '(- y y)
                  '[x y (class x) (class y) (identical? x x) (identical? x y) (identical? i i)
-                   (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser x)
-                   (.choose ^com.tailrecursion.blacktide.jvm_test.Choice jvm/chooser y)])
+                   (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser x)
+                   (.choose ^com.tailrecursion.blacktide.jvm_fixture.Choice jvm/chooser y)])
         body `(let [~'bias ~(if double? (double k) k)
                     ~'step (fn ~parameters ~compute)]
                 (loop [~'i ~(if double? 0.0 0)
diff --git a/test/com/tailrecursion/blacktide/source_test.clj b/test/com/tailrecursion/blacktide/source_test.clj
index 8dd4ab5..36456cc 100644
--- a/test/com/tailrecursion/blacktide/source_test.clj
+++ b/test/com/tailrecursion/blacktide/source_test.clj
@@ -6,7 +6,8 @@
             [clojure.test.check.generators :as gen]
             [clojure.test.check.properties :as prop]
             [com.tailrecursion.blacktide :as lh :refer [loophole label goto setq psetq]]
-            [com.tailrecursion.blacktide.source-fixture :as fixture]))
+            [com.tailrecursion.blacktide.source-fixture :as fixture]
+            [com.tailrecursion.blacktide.backend-fixture :as backend]))
 
 (defn sum [a b] (+ a b))
 (defn record! [events tag value] (swap! events conj tag) value)
@@ -20,11 +21,9 @@
 (defn change-operator [x] (set! *operator* (constantly :new)) x)
 
 (defn run-form [form]
-  (binding [*ns* (the-ns 'com.tailrecursion.blacktide.source-test)] (eval form)))
+  (backend/run-form 'com.tailrecursion.blacktide.source-test form))
 (defn rejection [form]
-  (try (run-form form) nil
-       (catch Throwable e
-         (loop [e e] (if-let [cause (ex-cause e)] (recur cause) (ex-data e))))))
+  (backend/rejection 'com.tailrecursion.blacktide.source-test form))
 
 (deftest sequential-scopes-and-results
   (is (nil? (loophole [])))
diff --git a/test/com/tailrecursion/blacktide/subset_test.clj b/test/com/tailrecursion/blacktide/subset_test.clj
index 6b4db4a..ae73295 100644
--- a/test/com/tailrecursion/blacktide/subset_test.clj
+++ b/test/com/tailrecursion/blacktide/subset_test.clj
@@ -3,7 +3,7 @@
   callers may freely use Clojure outside the region. Quoted data stays opaque."
   (:require [clojure.test :refer [deftest is]]
             [com.tailrecursion.blacktide :refer [loophole label goto setq]]
-            [com.tailrecursion.blacktide.source-test :as source]))
+            [com.tailrecursion.blacktide.backend-fixture :as backend]))
 
 (defmacro hidden-function [] `(fn [] :hidden))
 (defmacro hidden-label [] `(label ~'hidden))
@@ -18,7 +18,7 @@
                 (com.tailrecursion.blacktide.subset-test/hidden-function)]
           prefix [[] ['(goto done)]]]
     (is (= :unsupported-form
-           (:kind (source/rejection (list* 'loophole '[x 0] (concat prefix [body '(label done)])))))
+           (:kind (backend/rejection (list* 'loophole '[x 0] (concat prefix [body '(label done)])))))
         (pr-str [prefix body]))))
 
 (deftest operands-cannot-hide-transfers
@@ -34,7 +34,7 @@
                 (identity (loop [] (goto done)))
                 ^{:note (do (goto done) 1)} [1]
                 (let [] (com.tailrecursion.blacktide.subset-test/hidden-label))]]
-    (is (= :marker-position (:kind (source/rejection (list 'loophole '[x 0] body '(label done))))) (pr-str body)))
+    (is (= :marker-position (:kind (backend/rejection (list 'loophole '[x 0] body '(label done))))) (pr-str body)))
   (is (= :ok (loophole [] (do (label a) (do (goto b))) :wrong (label b) :ok)))
   (is (= '(fn [] (goto missing)) (loophole [] '(fn [] (goto missing))))))