(ns com.tailrecursion.blacktide.control-test
"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.backend-fixture :as backend]))
(defn record! [events tag value] (swap! events conj tag) value)
(deftest clojure-conditions-and-joins
(doseq [condition [nil false true 0 "" []]]
(is (= (if condition :yes :no) (loophole [] (if condition :yes :no)))))
(is (nil? (loophole [] (if false :unused))))
(is (= 20 (loophole [x 0] (if (setq x false) (setq x 10) (setq x 20)) x)))
(doseq [condition [true false]]
(is (= (if condition [10 2] [1 "changed"])
(loophole [x 1 y 2] (if condition (setq x 10) (setq y "changed")) [x y]))))
(is (= [3 4] (loophole [x 1] [(if true (if false 2 (setq x 3)) 9) (setq x 4)]))))
(deftest operands-survive-partial-evaluation
(is (= [1 10 10 20]
(loophole [x 1] [x (if true (setq x 10) 0) x (if false 0 (setq x 20))])))
(is (= [1 :changed 9]
(loophole [f vector x 1]
(f x (if true (do (setq f list x 9) :changed) :unused) x))))
(is (= [nil 2 1]
(loophole [x 1 y 2]
[(psetq x (if true y 99) y (if false 99 x)) x y])))
(is (= [1 3 3]
(loophole [x 1]
[(let [before x after (if true (setq x 3) 4)] before) x x]))))
(deftest selected-effects-and-short-circuit-macros
(let [events (atom [])]
(is (= [:yes nil false 7]
(loophole [x 1]
[(if (record! events :test true) (record! events :yes :yes) (record! events :no :no))
(and (setq x nil) (record! events :unused 4))
(or (setq x false) (record! events :or false))
(cond (setq x false) (record! events :unused 6) :else (record! events :cond 7))])))
(is (= [:test :yes :or :cond] @events)))
(is (= [3 3] (loophole [x nil] (when-not x (setq x 3)) [(when x x) x])))
(is (= [9 nil] (loophole [x 9] [(if-let [y x] y :absent) (when-let [y (setq x nil)] y)]))))
(deftest throwing-branches-and-dead-source
(let [one (ex-info "one" {}) two (ex-info "two" {})]
(doseq [condition [true false]]
(is (identical? (if condition one two)
(try (loophole [] (if condition (throw one) (throw two))) (catch Throwable e e))))))
(let [events (atom []) error (ex-info "stop" {})]
(is (identical? error
(try (loophole [x 1 y 2]
(psetq x (record! events :first 10)
y (if (record! events x true) (throw error) 9))
(record! events :after :bad))
(catch Throwable e e))))
(is (= [:first 1] @events)))
(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 (backend/rejection (list 'loophole [] form)))))))