cljrs-stdlib 0.1.51

Built-in standard library namespaces for clojurust (clojure.string, clojure.set, clojure.test, …)
Documentation
;; clojure.test — minimal compatible implementation for clojurust.
;; Covers: deftest, is, are, testing, run-tests, test-var.

(ns clojure.test
  (:require [clojure.template]))

;; ── Dynamic state ─────────────────────────────────────────────────────────────

(def ^:dynamic *testing-vars* '())
(def ^:dynamic *testing-contexts* [])
(def ^:dynamic *report-counters* nil)
(def ^:dynamic *test-out* nil)
(def ^:dynamic *verbose* false)

;; ── Reporting ─────────────────────────────────────────────────────────────────

(defmulti report :type)

(defmethod report :default [m]
  (println "Unknown report type:" (:type m)))

(defmethod report :pass [m]
  (when *verbose*
    (println "\nPASS in" (str (first *testing-vars*)))
    (doseq [ctx *testing-contexts*]
      (println "  " ctx))
    (println "expected:" (pr-str (:expected m))))
  (when *report-counters*
    (set! *report-counters* (update *report-counters* :pass inc))))

(defmethod report :fail [m]
  (println "\nFAIL in" (str (first *testing-vars*)))
  (doseq [ctx *testing-contexts*]
    (println "  " ctx))
  (when (:message m)
    (println "Message:" (:message m)))
  (println "expected:" (pr-str (:expected m)))
  (println "  actual:" (pr-str (:actual m)))
  (when *report-counters*
    (set! *report-counters* (update *report-counters* :fail inc))))

(defmethod report :error [m]
  (println "\nERROR in" (str (first *testing-vars*)))
  (doseq [ctx *testing-contexts*]
    (println "  " ctx))
  (when (:message m)
    (println "Message:" (:message m)))
  (println "expected:" (pr-str (:expected m)))
  (println "  actual:" (str (:actual m)))
  (when *report-counters*
    (set! *report-counters* (update *report-counters* :error inc))))

(defmethod report :begin-test-ns [m]
  (println "\nTesting" (ns-name (:ns m))))

(defmethod report :end-test-ns [_m])

(defmethod report :summary [m]
  (println "\nRan" (:test m) "tests containing"
           (+ (:pass m) (:fail m) (:error m)) "assertions.")
  (println (:fail m) "failures," (:error m) "errors."))

;; ── Assertion macro ───────────────────────────────────────────────────────────
;; Note: uses __is-result__ / __is-e__ instead of gensym to avoid reader issues.

(defmacro is
  ([form] `(is ~form nil))
  ([form msg]
   (cond
     ;; (is (thrown? ExClass expr)) or (is (thrown? expr)) — expect an exception.
     (and (seq? form) (= 'thrown? (first form)))
     (let [expr (if (= 3 (count form))
                  (nth form 2)
                  (nth form 1))]
       `(try
          ~expr
          (do (report {:type :fail :expected '~form :actual "no exception thrown" :message ~msg})
              false)
          (catch Exception __is-e__
            (report {:type :pass :expected '~form :actual __is-e__ :message ~msg})
            true)))

     ;; (is (<cmp> a b ...)) — evaluate each argument once and report the
     ;; individual values. A failing (is (= (f) :bar)) reports its actual as
     ;; (not (= <value-of-f> :bar)) rather than just `false`, so the user can
     ;; see both sides of the comparison without re-running anything.
     (and (seq? form)
          (contains? '#{= not= == < <= > >= identical?} (first form)))
     (let [pred  (first form)
           args  (rest form)
           gsyms (map (fn [_] (gensym "__is-arg__")) args)
           binds (vec (mapcat (fn [s a] [s a]) gsyms args))]
       `(try
          (let ~binds
            (if (~pred ~@gsyms)
              (do (report {:type :pass
                           :expected '~form
                           :actual   (list '~pred ~@gsyms)
                           :message  ~msg})
                  true)
              (do (report {:type :fail
                           :expected '~form
                           :actual   (list 'not (list '~pred ~@gsyms))
                           :message  ~msg})
                  false)))
          (catch Exception __is-e__
            (report {:type :error :expected '~form :actual __is-e__ :message ~msg})
            false)))

     ;; Normal boolean assertion — no structural introspection available.
     :else
     `(try
        (let [__is-result__ ~form]
          (if __is-result__
            (do (report {:type :pass :expected '~form :actual __is-result__ :message ~msg})
                true)
            (do (report {:type :fail :expected '~form :actual __is-result__ :message ~msg})
                false)))
        (catch Exception __is-e__
          (report {:type :error :expected '~form :actual __is-e__ :message ~msg})
          false)))))

;; ── are macro ─────────────────────────────────────────────────────────────────

(defmacro are
  [argv expr & args]
  (if (or (and (empty? args) (not (empty? argv)))
          (and (not (empty? args)) (empty? argv)))
    `(do)
    `(clojure.template/do-template ~argv (is ~expr) ~@args)))

;; ── Context macro ─────────────────────────────────────────────────────────────

(defmacro testing [string & body]
  `(binding [*testing-contexts* (conj *testing-contexts* ~string)]
     ~@body))

;; ── Test definition ───────────────────────────────────────────────────────────

(defmacro deftest [name & body]
  `(do
     (def ~name (fn [] ~@body))
     (alter-meta! (var ~name) assoc :test (fn [] ~@body))
     (var ~name)))

;; ── Test runner ───────────────────────────────────────────────────────────────

(defn test-var [v]
  (let [test-fn (get (meta v) :test)]
    (when test-fn
      (binding [*testing-vars* (conj *testing-vars* v)]
        (try
          (test-fn)
          (catch Exception __tv-e__
            (report {:type    :error
                     :expected nil
                     :actual  __tv-e__
                     :message nil})))))))

(defn run-tests
  ([] (run-tests *ns*))
  ([ns]
   (let [ns (if (symbol? ns) (find-ns ns) ns)]
   (binding [*report-counters* {:test 0 :pass 0 :fail 0 :error 0}]
     (report {:type :begin-test-ns :ns ns})
     (let [test-vars (filter (fn [v] (get (meta v) :test))
                             (vals (ns-publics ns)))]
       (doseq [v test-vars]
         (set! *report-counters* (update *report-counters* :test inc))
         (test-var v)))
     (report {:type :end-test-ns :ns ns})
     (report {:type  :summary
              :test  (:test *report-counters*)
              :pass  (:pass *report-counters*)
              :fail  (:fail *report-counters*)
              :error (:error *report-counters*)})
     *report-counters*))))