aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStuart Sierra <mail@stuartsierra.com>2009-01-16 18:33:45 +0000
committerStuart Sierra <mail@stuartsierra.com>2009-01-16 18:33:45 +0000
commit3bb93410973f5f19e0bc9fe5d9ac3d181a6b3444 (patch)
tree12a4293c94073fd79a8146d7ee04dea32ecac0ef
parent8397dbff88f6bf86688c1be7cc07f20d2bfbb6af (diff)
test_is.clj: Regained fancy error reports for functional predicates.
This time does not break tests using macros or Java method calls. Can also now test unbound vars.
-rw-r--r--src/clojure/contrib/test_is.clj91
-rw-r--r--src/clojure/contrib/test_is/tests.clj10
2 files changed, 70 insertions, 31 deletions
diff --git a/src/clojure/contrib/test_is.clj b/src/clojure/contrib/test_is.clj
index 3e42ba34..b729c884 100644
--- a/src/clojure/contrib/test_is.clj
+++ b/src/clojure/contrib/test_is.clj
@@ -1,7 +1,7 @@
;;; test_is.clj: test framework for Clojure
;; by Stuart Sierra, http://stuartsierra.com/
-;; January 15, 2009
+;; January 16, 2009
;; Thanks to Chas Emerick, Allen Rohner, and Stuart Halloway for
;; contributions and suggestions.
@@ -97,7 +97,7 @@
-;;; UTILITIES USED BY TEST REPORTING FUNCTIONS
+;;; UTILITIES FOR REPORTING FUNCTIONS
(defn file-position
"Returns a vector [filename line-number] for the nth call up the
@@ -124,7 +124,7 @@
[]
(apply str (interpose " " (reverse *testing-contexts*))))
-(defn report-count
+(defn inc-report-counter
"Increments the named counter in *report-counters*, a ref to a map.
Does nothing if *report-counters* is nil."
[name]
@@ -154,10 +154,10 @@
(println msg))
(defmethod report :pass [event msg expected actual]
- (report-count :pass))
+ (inc-report-counter :pass))
(defmethod report :fail [event msg expected actual]
- (report-count :fail)
+ (inc-report-counter :fail)
(println "\nFAIL in" (testing-vars-str))
(when (seq *testing-contexts*) (println (testing-contexts-str)))
(when msg (println msg))
@@ -165,7 +165,7 @@
(println " actual:" (pr-str actual)))
(defmethod report :error [event msg expected actual]
- (report-count :error)
+ (inc-report-counter :error)
(println "\nERROR in" (testing-vars-str))
(when (seq *testing-contexts*) (println (testing-contexts-str)))
(when msg (println msg))
@@ -177,6 +177,54 @@
+;;; UTILITIES FOR ASSERTIONS
+
+(defn get-possibly-unbound-var
+ "Like var-get but returns nil if the var is unbound."
+ [v]
+ (try (var-get v)
+ (catch IllegalStateException e
+ nil)))
+
+(defn function?
+ "Returns true if argument is a function or a symbol that resolves to
+ a function (not a macro)."
+ [x]
+ (if (symbol? x)
+ (when-let [v (resolve x)]
+ (when-let [value (get-possibly-unbound-var v)]
+ (and (fn? value)
+ (not (:macro (meta v))))))
+ (fn? x)))
+
+(defn assert-predicate
+ "Returns generic assertion code for any functional predicate. The
+ 'expected' argument to 'report' will contains the original form, the
+ 'actual' argument will contain the form with all its sub-forms
+ evaluated. If the predicate returns false, the 'actual' form will
+ be wrapped in (not...)."
+ [msg form]
+ (let [args (rest form)
+ pred (first form)]
+ `(let [values# (list ~@args)
+ result# (apply ~pred values#)]
+ (if result#
+ (report :pass ~msg '~form (cons ~pred values#))
+ (report :fail ~msg '~form (list '~'not (cons '~pred values#))))
+ result#)))
+
+(defn assert-any
+ "Returns generic assertion code for any test, including macros, Java
+ method calls, or isolated symbols."
+ [msg form]
+ `(let [value# ~form]
+ (if value#
+ (report :pass ~msg '~form value#)
+ (report :fail ~msg '~form value#))
+ value#))
+
+
+
;;; ASSERTION METHODS
;; You don't call these, but you can add methods to extend the 'is'
@@ -188,36 +236,16 @@
(cond
(nil? form) :always-fail
(seq? form) (first form)
- :else :single)))
-
-;; Not currently used -- how can we determine if predicate is a normal
-;; function and not a macro or method call?
-(defmethod assert-expr :predicate [msg form]
- ;; Generic assertion for any functional predicate. The 'expected'
- ;; argument to 'report' contains the original form, the 'actual'
- ;; argument contains the form with all its sub-forms evaluated. If
- ;; the predicate returns false, the 'actual' form is wrapped in
- ;; (not...).
- (let [args (rest form)
- pred (first form)]
- `(let [values# (list ~@args)
- result# (apply ~pred values#)]
- (if result#
- (report :pass ~msg '~form (cons ~pred values#))
- (report :fail ~msg '~form (list '~'not (cons '~pred values#))))
- result#)))
+ :else :default)))
(defmethod assert-expr :always-fail [msg form]
;; nil test: always fail
`(report :fail ~msg nil nil))
(defmethod assert-expr :default [msg form]
- ;; Evaluate any expression and pass if it is logical true.
- `(let [value# ~form]
- (if value#
- (report :pass ~msg '~form value#)
- (report :fail ~msg '~form value#))
- value#))
+ (if (and (sequential? form) (function? (first form)))
+ (assert-predicate msg form)
+ (assert-any msg form)))
(defmethod assert-expr 'instance? [msg form]
;; Test if x is an instance of y.
@@ -245,6 +273,7 @@
;; * thrown-with-msg?
+
;;; CATCHING UNEXPECTED EXCEPTIONS
(defmacro try-expr
@@ -340,7 +369,7 @@
[v]
(when-let [t (:test (meta v))]
(binding [*testing-vars* (conj *testing-vars* v)]
- (report-count :test)
+ (inc-report-counter :test)
(try (t)
(catch Throwable e
(report :error "Uncaught exception, not in assertion."
diff --git a/src/clojure/contrib/test_is/tests.clj b/src/clojure/contrib/test_is/tests.clj
index 5b04c91a..2589cc77 100644
--- a/src/clojure/contrib/test_is/tests.clj
+++ b/src/clojure/contrib/test_is/tests.clj
@@ -62,6 +62,16 @@
(is (re-find #"cd" "abbabba") "Should fail"))
+;; still have to declare the symbol before testing unbound symbols
+(declare does-not-exist)
+
+(deftest can-test-unbound-symbol
+ (is (= nil does-not-exist) "Should error"))
+
+(deftest can-test-unbound-function
+ (is (does-not-exist) "Should error"))
+
+
;; Here, we create an alternate version of test-is/report, that
;; compares the event with the message, then calls the original
;; 'report' with modified arguments.