summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/clj/clojure/contrib/stacktrace.clj75
-rw-r--r--src/clj/clojure/contrib/template.clj55
-rw-r--r--src/clj/clojure/contrib/test_is.clj923
-rw-r--r--src/clj/clojure/contrib/test_is/tap.clj112
-rw-r--r--src/clj/clojure/contrib/walk.clj134
5 files changed, 1299 insertions, 0 deletions
diff --git a/src/clj/clojure/contrib/stacktrace.clj b/src/clj/clojure/contrib/stacktrace.clj
new file mode 100644
index 00000000..7330ef47
--- /dev/null
+++ b/src/clj/clojure/contrib/stacktrace.clj
@@ -0,0 +1,75 @@
+;;; stacktrace.clj: print Clojure-centric stack traces
+
+;; by Stuart Sierra, http://stuartsierra.com/
+;; January 6, 2009
+
+;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use
+;; and distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+
+(ns
+ #^{:author "Stuart Sierra",
+ :doc "Print Clojure-centric stack traces"}
+ clojure.contrib.stacktrace)
+
+(defn root-cause
+ "Returns the last 'cause' Throwable in a chain of Throwables."
+ [tr]
+ (if-let [cause (.getCause tr)]
+ (recur cause)
+ tr))
+
+(defn print-trace-element
+ "Prints a Clojure-oriented view of one element in a stack trace."
+ [e]
+ (let [class (.getClassName e)
+ method (.getMethodName e)]
+ (let [match (re-matches #"^([A-Za-z0-9_.-]+)\$(\w+)__\d+$" class)]
+ (if (and match (= "invoke" method))
+ (apply printf "%s/%s" (rest match))
+ (printf "%s.%s" class method))))
+ (printf " (%s:%d)" (or (.getFileName e) "") (.getLineNumber e)))
+
+(defn print-throwable
+ "Prints the class and message of a Throwable."
+ [tr]
+ (printf "%s: %s" (.getName (class tr)) (.getMessage tr)))
+
+(defn print-stack-trace
+ "Prints a Clojure-oriented stack trace of tr, a Throwable.
+ Prints a maximum of n stack frames (default: unlimited).
+ Does not print chained exceptions (causes)."
+ ([tr] (print-stack-trace tr nil))
+ ([tr n]
+ (let [st (.getStackTrace tr)]
+ (print-throwable tr)
+ (newline)
+ (print " at ")
+ (print-trace-element (first st))
+ (newline)
+ (doseq [e (if (nil? n)
+ (rest st)
+ (take (dec n) (rest st)))]
+ (print " ")
+ (print-trace-element e)
+ (newline)))))
+
+(defn print-cause-trace
+ "Like print-stack-trace but prints chained exceptions (causes)."
+ ([tr] (print-cause-trace tr nil))
+ ([tr n]
+ (print-stack-trace tr n)
+ (when-let [cause (.getCause tr)]
+ (print "Caused by: " )
+ (recur cause n))))
+
+(defn e
+ "REPL utility. Prints a brief stack trace for the root cause of the
+ most recent exception."
+ []
+ (print-stack-trace (root-cause *e) 8))
diff --git a/src/clj/clojure/contrib/template.clj b/src/clj/clojure/contrib/template.clj
new file mode 100644
index 00000000..748ad4a4
--- /dev/null
+++ b/src/clj/clojure/contrib/template.clj
@@ -0,0 +1,55 @@
+;;; template.clj - anonymous functions that pre-evaluate sub-expressions
+
+;; By Stuart Sierra, http://stuartsierra.com/
+;; June 23, 2009
+
+;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use
+;; and distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+;; CHANGE LOG
+;;
+;; June 23, 2009: complete rewrite, eliminated _1,_2,... argument
+;; syntax
+;;
+;; January 20, 2009: added "template?" and checks for valid template
+;; expressions.
+;;
+;; December 15, 2008: first version
+
+
+(ns #^{:author "Stuart Sierra"
+ :doc "Macros that expand to repeated copies of a template expression."}
+ clojure.contrib.template
+ (:require [clojure.contrib.walk :as walk]))
+
+(defn apply-template
+ "For use in macros. argv is an argument list, as in defn. expr is
+ a quoted expression using the symbols in argv. values is a sequence
+ of values to be used for the arguments.
+
+ apply-template will recursively replace argument symbols in expr
+ with their corresponding values, returning a modified expr.
+
+ Example: (apply-template '[x] '(+ x x) '[2])
+ ;=> (+ 2 2)"
+ [argv expr values]
+ (assert (vector? argv))
+ (assert (every? symbol? argv))
+ (walk/prewalk-replace (zipmap argv values) expr))
+
+(defmacro do-template
+ "Repeatedly copies expr (in a do block) for each group of arguments
+ in values. values are automatically partitioned by the number of
+ arguments in argv, an argument vector as in defn.
+
+ Example: (macroexpand '(do-template [x y] (+ y x) 2 4 3 5))
+ ;=> (do (+ 4 2) (+ 5 3))"
+ [argv expr & values]
+ (let [c (count argv)]
+ `(do ~@(map (fn [a] (apply-template argv expr a))
+ (partition c values)))))
diff --git a/src/clj/clojure/contrib/test_is.clj b/src/clj/clojure/contrib/test_is.clj
new file mode 100644
index 00000000..8138ce3e
--- /dev/null
+++ b/src/clj/clojure/contrib/test_is.clj
@@ -0,0 +1,923 @@
+;;; test_is.clj: test framework for Clojure
+
+;; by Stuart Sierra, http://stuartsierra.com/
+;; March 28, 2009
+
+;; Thanks to Chas Emerick, Allen Rohner, and Stuart Halloway for
+;; contributions and suggestions.
+
+;; Copyright (c) Stuart Sierra, 2008. All rights reserved. The use
+;; and distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+
+
+(comment
+ ;; Inspired by many Common Lisp test frameworks and clojure/test,
+ ;; this file is a Clojure test framework.
+ ;;
+ ;;
+ ;;
+ ;; ASSERTIONS
+ ;;
+ ;; The core of the library is the "is" macro, which lets you make
+ ;; assertions of any arbitrary expression:
+
+ (is (= 4 (+ 2 2)))
+ (is (instance? Integer 256))
+ (is (.startsWith "abcde" "ab"))
+
+ ;; You can type an "is" expression directly at the REPL, which will
+ ;; print a message if it fails.
+ ;;
+ ;; user> (is (= 5 (+ 2 2)))
+ ;;
+ ;; FAIL in (:1)
+ ;; expected: (= 5 (+ 2 2))
+ ;; actual: (not (= 5 4))
+ ;; false
+ ;;
+ ;; The "expected:" line shows you the original expression, and the
+ ;; "actual:" shows you what actually happened. In this case, it
+ ;; shows that (+ 2 2) returned 4, which is not = to 5. Finally, the
+ ;; "false" on the last line is the value returned from the
+ ;; expression. The "is" macro always returns the result of the
+ ;; inner expression.
+ ;;
+ ;; There are two special assertions for testing exceptions. The
+ ;; "(is (thrown? c ...))" form tests if an exception of class c is
+ ;; thrown:
+
+ (is (thrown? ArithmeticException (/ 1 0)))
+
+ ;; "(is (thrown-with-msg? c re ...))" does the same thing and also
+ ;; tests that the message on the exception matches the regular
+ ;; expression re:
+
+ (is (thrown-with-msg? ArithmeticException #"Divide by zero"
+ (/ 1 0)))
+
+ ;;
+ ;;
+ ;;
+ ;; DOCUMENTING TESTS
+ ;;
+ ;; "is" takes an optional second argument, a string describing the
+ ;; assertion. This message will be included in the error report.
+
+ (is (= 5 (+ 2 2)) "Crazy arithmetic")
+
+ ;; In addition, you can document groups of assertions with the
+ ;; "testing" macro, which takes a string followed by any number of
+ ;; assertions. The string will be included in failure reports.
+ ;; Calls to "testing" may be nested, and all of the strings will be
+ ;; joined together with spaces in the final report, in a style
+ ;; similar to RSpec <http://rspec.info/>
+
+ (testing "Arithmetic"
+ (testing "with positive integers"
+ (is (= 4 (+ 2 2)))
+ (is (= 7 (+ 3 4))))
+ (testing "with negative integers"
+ (is (= -4 (+ -2 -2)))
+ (is (= -1 (+ 3 -4)))))
+
+ ;; Note that, unlike RSpec, the "testing" macro may only be used
+ ;; INSIDE a "deftest" or "with-test" form (see below).
+ ;;
+ ;;
+ ;;
+ ;; DEFINING TESTS
+ ;;
+ ;; There are two ways to define tests. The "with-test" macro takes
+ ;; a defn or def form as its first argument, followed by any number
+ ;; of assertions. The tests will be stored as metadata on the
+ ;; definition.
+
+ (with-test
+ (defn my-function [x y]
+ (+ x y))
+ (is (= 4 (my-function 2 2)))
+ (is (= 7 (my-function 3 4))))
+
+ ;; As of Clojure SVN rev. 1221, this does not work with defmacro.
+ ;; See http://code.google.com/p/clojure/issues/detail?id=51
+ ;;
+ ;; The other way lets you define tests separately from the rest of
+ ;; your code, even in a different namespace:
+
+ (deftest addition
+ (is (= 4 (+ 2 2)))
+ (is (= 7 (+ 3 4))))
+
+ (deftest subtraction
+ (is (= 1 (- 4 3)))
+ (is (= 3 (- 7 4))))
+
+ ;; This creates functions named "addition" and "subtraction", which
+ ;; can be called like any other function. Therefore, tests can be
+ ;; grouped and composed, in a style similar to the test framework in
+ ;; Peter Seibel's "Practical Common Lisp"
+ ;; <http://www.gigamonkeys.com/book/practical-building-a-unit-test-framework.html>
+
+ (deftest arithmetic
+ (addition)
+ (subtraction))
+
+ ;; The names of the nested tests will be joined in a list, like
+ ;; "(arithmetic addition)", in failure reports. You can use nested
+ ;; tests to set up a context shared by several tests.
+ ;;
+ ;;
+ ;;
+ ;; RUNNING TESTS
+ ;;
+ ;; Run tests with the function "(run-tests namespaces...)":
+
+ (run-tests 'your.namespace 'some.other.namespace)
+
+ ;; If you don't specify any namespaces, the current namespace is
+ ;; used. To run all tests in all namespaces, use "(run-all-tests)".
+ ;;
+ ;; By default, these functions will search for all tests defined in
+ ;; a namespace and run them in an undefined order. However, if you
+ ;; are composing tests, as in the "arithmetic" example above, you
+ ;; probably do not want the "addition" and "subtraction" tests run
+ ;; separately. In that case, you must define a special function
+ ;; named "test-ns-hook" that runs your tests in the correct order:
+
+ (defn test-ns-hook []
+ (arithmetic))
+
+ ;;
+ ;;
+ ;;
+ ;; OMITTING TESTS FROM PRODUCTION CODE
+ ;;
+ ;; You can bind the variable "*load-tests*" to false when loading or
+ ;; compiling code in production. This will prevent any tests from
+ ;; being created by "with-test" or "deftest".
+ ;;
+ ;;
+ ;;
+ ;; FIXTURES (new)
+ ;;
+ ;; Fixtures allow you to run code before and after tests, to set up
+ ;; the context in which tests should be run.
+ ;;
+ ;; A fixture is just a function that calls another function passed as
+ ;; an argument. It looks like this:
+ (defn my-fixture [f]
+ ;; Perform setup, establish bindings, whatever.
+ (f) ;; Then call the function we were passed.
+ ;; Tear-down / clean-up code here.
+ )
+
+ ;; Fixtures are attached to namespaces in one of two ways. "each"
+ ;; fixtures are run repeatedly, once for each test function created
+ ;; with "deftest" or "with-test". "each" fixtures are useful for
+ ;; establishing a consistent before/after state for each test, like
+ ;; clearing out database tables.
+ ;;
+ ;; "each" fixtures can be attached to the current namespace like this:
+ (use-fixtures :each fixture1 fixture2 ...)
+ ;; The fixture1, fixture2 are just functions like the example above.
+ ;; They can also be anonymous functions, like this:
+ (use-fixtures :each (fn [f] setup... (f) cleanup...))
+ ;;
+ ;; The other kind of fixture, a "once" fixture, is only run once,
+ ;; around ALL the tests in the namespace. "once" fixtures are useful
+ ;; for tasks that only need to be performed once, like establishing
+ ;; database connections, or for time-consuming tasks.
+ ;;
+ ;; Attach "once" fixtures to the current namespace like this:
+ (use-fixtures :once fixture1 fixture2 ...)
+ ;;
+ ;;
+ ;;
+ ;; SAVING TEST OUTPUT TO A FILE
+ ;;
+ ;; All the test reporting functions write to the var *test-out*. By
+ ;; default, this is the same as *out*, but you can rebind it to any
+ ;; PrintWriter. For example, it could be a file opened with
+ ;; clojure.contrib.duck-streams/writer.
+ ;;
+ ;;
+ ;;
+ ;; EXTENDING TEST-IS (ADVANCED)
+ ;;
+ ;; You can extend the behavior of the "is" macro by defining new
+ ;; methods for the "assert-expr" multimethod. These methods are
+ ;; called during expansion of the "is" macro, so they should return
+ ;; quoted forms to be evaluated.
+ ;;
+ ;; You can plug in your own test-reporting framework by rebinding
+ ;; the "report" function: (report event)
+ ;;
+ ;; The 'event' argument is a map. It will always have a :type key,
+ ;; whose value will be a keyword signaling the type of event being
+ ;; reported. Standard events with :type value of :pass, :fail, and
+ ;; :error are called when an assertion passes, fails, and throws an
+ ;; exception, respectively. In that case, the event will also have
+ ;; the following keys:
+ ;;
+ ;; :expected The form that was expected to be true
+ ;; :actual A form representing what actually occurred
+ ;; :message The string message given as an argument to 'is'
+ ;;
+ ;; The "testing" strings will be a list in "*testing-contexts*", and
+ ;; the vars being tested will be a list in "*testing-vars*".
+ ;;
+ ;; Your "report" function should wrap any printing calls in the
+ ;; "with-test-out" macro, which rebinds *out* to the current value
+ ;; of *test-out*.
+ ;;
+ ;; For additional event types, see the examples in the code below.
+
+ ) ;; end comment
+
+
+
+(ns
+ #^{:author "Stuart Sierra, with contributions and suggestions by
+Chas Emerick, Allen Rohner, and Stuart Halloway",
+ :doc "Inspired by many Common Lisp test frameworks and clojure/test,
+ this file is a Clojure test framework.
+
+ ASSERTIONS
+
+ The core of the library is the \"is\" macro, which lets you make
+ assertions of any arbitrary expression:
+
+ (is (= 4 (+ 2 2)))
+ (is (instance? Integer 256))
+ (is (.startsWith \"abcde\" \"ab\"))
+
+ You can type an \"is\" expression directly at the REPL, which will
+ print a message if it fails.
+
+ user> (is (= 5 (+ 2 2)))
+
+ FAIL in (:1)
+ expected: (= 5 (+ 2 2))
+ actual: (not (= 5 4))
+ false
+
+ The \"expected:\" line shows you the original expression, and the
+ \"actual:\" shows you what actually happened. In this case, it
+ shows that (+ 2 2) returned 4, which is not = to 5. Finally, the
+ \"false\" on the last line is the value returned from the
+ expression. The \"is\" macro always returns the result of the
+ inner expression.
+
+ There are two special assertions for testing exceptions. The
+ \"(is (thrown? c ...))\" form tests if an exception of class c is
+ thrown:
+
+ (is (thrown? ArithmeticException (/ 1 0)))
+
+ \"(is (thrown-with-msg? c re ...))\" does the same thing and also
+ tests that the message on the exception matches the regular
+ expression re:
+
+ (is (thrown-with-msg? ArithmeticException #\"Divide by zero\"
+ (/ 1 0)))
+
+ DOCUMENTING TESTS
+
+ \"is\" takes an optional second argument, a string describing the
+ assertion. This message will be included in the error report.
+
+ (is (= 5 (+ 2 2)) \"Crazy arithmetic\")
+
+ In addition, you can document groups of assertions with the
+ \"testing\" macro, which takes a string followed by any number of
+ assertions. The string will be included in failure reports.
+ Calls to \"testing\" may be nested, and all of the strings will be
+ joined together with spaces in the final report, in a style
+ similar to RSpec <http://rspec.info/>
+
+ (testing \"Arithmetic\"
+ (testing \"with positive integers\"
+ (is (= 4 (+ 2 2)))
+ (is (= 7 (+ 3 4))))
+ (testing \"with negative integers\"
+ (is (= -4 (+ -2 -2)))
+ (is (= -1 (+ 3 -4)))))
+
+ Note that, unlike RSpec, the \"testing\" macro may only be used
+ INSIDE a \"deftest\" or \"with-test\" form (see below).
+
+
+ DEFINING TESTS
+
+ There are two ways to define tests. The \"with-test\" macro takes
+ a defn or def form as its first argument, followed by any number
+ of assertions. The tests will be stored as metadata on the
+ definition.
+
+ (with-test
+ (defn my-function [x y]
+ (+ x y))
+ (is (= 4 (my-function 2 2)))
+ (is (= 7 (my-function 3 4))))
+
+ As of Clojure SVN rev. 1221, this does not work with defmacro.
+ See http://code.google.com/p/clojure/issues/detail?id=51
+
+ The other way lets you define tests separately from the rest of
+ your code, even in a different namespace:
+
+ (deftest addition
+ (is (= 4 (+ 2 2)))
+ (is (= 7 (+ 3 4))))
+
+ (deftest subtraction
+ (is (= 1 (- 4 3)))
+ (is (= 3 (- 7 4))))
+
+ This creates functions named \"addition\" and \"subtraction\", which
+ can be called like any other function. Therefore, tests can be
+ grouped and composed, in a style similar to the test framework in
+ Peter Seibel's \"Practical Common Lisp\"
+ <http://www.gigamonkeys.com/book/practical-building-a-unit-test-framework.html>
+
+ (deftest arithmetic
+ (addition)
+ (subtraction))
+
+ The names of the nested tests will be joined in a list, like
+ \"(arithmetic addition)\", in failure reports. You can use nested
+ tests to set up a context shared by several tests.
+
+
+ RUNNING TESTS
+
+ Run tests with the function \"(run-tests namespaces...)\":
+
+ (run-tests 'your.namespace 'some.other.namespace)
+
+ If you don't specify any namespaces, the current namespace is
+ used. To run all tests in all namespaces, use \"(run-all-tests)\".
+
+ By default, these functions will search for all tests defined in
+ a namespace and run them in an undefined order. However, if you
+ are composing tests, as in the \"arithmetic\" example above, you
+ probably do not want the \"addition\" and \"subtraction\" tests run
+ separately. In that case, you must define a special function
+ named \"test-ns-hook\" that runs your tests in the correct order:
+
+ (defn test-ns-hook []
+ (arithmetic))
+
+
+ OMITTING TESTS FROM PRODUCTION CODE
+
+ You can bind the variable \"*load-tests*\" to false when loading or
+ compiling code in production. This will prevent any tests from
+ being created by \"with-test\" or \"deftest\".
+
+
+ FIXTURES (new)
+
+ Fixtures allow you to run code before and after tests, to set up
+ the context in which tests should be run.
+
+ A fixture is just a function that calls another function passed as
+ an argument. It looks like this:
+
+ (defn my-fixture [f]
+ Perform setup, establish bindings, whatever.
+ (f) Then call the function we were passed.
+ Tear-down / clean-up code here.
+ )
+
+ Fixtures are attached to namespaces in one of two ways. \"each\"
+ fixtures are run repeatedly, once for each test function created
+ with \"deftest\" or \"with-test\". \"each\" fixtures are useful for
+ establishing a consistent before/after state for each test, like
+ clearing out database tables.
+
+ \"each\" fixtures can be attached to the current namespace like this:
+ (use-fixtures :each fixture1 fixture2 ...)
+ The fixture1, fixture2 are just functions like the example above.
+ They can also be anonymous functions, like this:
+ (use-fixtures :each (fn [f] setup... (f) cleanup...))
+
+ The other kind of fixture, a \"once\" fixture, is only run once,
+ around ALL the tests in the namespace. \"once\" fixtures are useful
+ for tasks that only need to be performed once, like establishing
+ database connections, or for time-consuming tasks.
+
+ Attach \"once\" fixtures to the current namespace like this:
+ (use-fixtures :once fixture1 fixture2 ...)
+
+
+ SAVING TEST OUTPUT TO A FILE
+
+ All the test reporting functions write to the var *test-out*. By
+ default, this is the same as *out*, but you can rebind it to any
+ PrintWriter. For example, it could be a file opened with
+ clojure.contrib.duck-streams/writer.
+
+
+ EXTENDING TEST-IS (ADVANCED)
+
+ You can extend the behavior of the \"is\" macro by defining new
+ methods for the \"assert-expr\" multimethod. These methods are
+ called during expansion of the \"is\" macro, so they should return
+ quoted forms to be evaluated.
+
+ You can plug in your own test-reporting framework by rebinding
+ the \"report\" function: (report event)
+
+ The 'event' argument is a map. It will always have a :type key,
+ whose value will be a keyword signaling the type of event being
+ reported. Standard events with :type value of :pass, :fail, and
+ :error are called when an assertion passes, fails, and throws an
+ exception, respectively. In that case, the event will also have
+ the following keys:
+
+ :expected The form that was expected to be true
+ :actual A form representing what actually occurred
+ :message The string message given as an argument to 'is'
+
+ The \"testing\" strings will be a list in \"*testing-contexts*\", and
+ the vars being tested will be a list in \"*testing-vars*\".
+
+ Your \"report\" function should wrap any printing calls in the
+ \"with-test-out\" macro, which rebinds *out* to the current value
+ of *test-out*.
+
+ For additional event types, see the examples in the code.
+"}
+ clojure.contrib.test-is
+ (:require [clojure.contrib.template :as temp]
+ [clojure.contrib.stacktrace :as stack]))
+
+;; Nothing is marked "private" here, so you can rebind things to plug
+;; in your own testing or reporting frameworks.
+
+
+;;; USER-MODIFIABLE GLOBALS
+
+(defonce
+ #^{:doc "True by default. If set to false, no test functions will
+ be created by deftest, set-test, or with-test. Use this to omit
+ tests when compiling or loading production code."}
+ *load-tests* true)
+
+(def
+ #^{:doc "The maximum depth of stack traces to print when an Exception
+ is thrown during a test. Defaults to nil, which means print the
+ complete stack trace."}
+ *stack-trace-depth* nil)
+
+
+;;; GLOBALS USED BY THE REPORTING FUNCTIONS
+
+(def *report-counters* nil) ; bound to a ref of a map in test-ns
+
+(def *initial-report-counters* ; used to initialize *report-counters*
+ {:test 0, :pass 0, :fail 0, :error 0})
+
+(def *testing-vars* (list)) ; bound to hierarchy of vars being tested
+
+(def *testing-contexts* (list)) ; bound to hierarchy of "testing" strings
+
+(def *test-out* *out*) ; PrintWriter for test reporting output
+
+(defmacro with-test-out
+ "Runs body with *out* bound to the value of *test-out*."
+ [& body]
+ `(binding [*out* *test-out*]
+ ~@body))
+
+
+
+;;; UTILITIES FOR REPORTING FUNCTIONS
+
+(defn file-position
+ "Returns a vector [filename line-number] for the nth call up the
+ stack."
+ [n]
+ (let [s (nth (.getStackTrace (new java.lang.Throwable)) n)]
+ [(.getFileName s) (.getLineNumber s)]))
+
+(defn testing-vars-str
+ "Returns a string representation of the current test. Renders names
+ in *testing-vars* as a list, then the source file and line of
+ current assertion."
+ []
+ (let [[file line] (file-position 4)]
+ (str
+ ;; Uncomment to include namespace in failure report:
+ ;;(ns-name (:ns (meta (first *testing-vars*)))) "/ "
+ (reverse (map #(:name (meta %)) *testing-vars*))
+ " (" file ":" line ")")))
+
+(defn testing-contexts-str
+ "Returns a string representation of the current test context. Joins
+ strings in *testing-contexts* with spaces."
+ []
+ (apply str (interpose " " (reverse *testing-contexts*))))
+
+(defn inc-report-counter
+ "Increments the named counter in *report-counters*, a ref to a map.
+ Does nothing if *report-counters* is nil."
+ [name]
+ (when *report-counters*
+ (dosync (commute *report-counters* assoc name
+ (inc (or (*report-counters* name) 0))))))
+
+
+
+;;; TEST RESULT REPORTING
+
+(defmulti
+ #^{:doc "Generic reporting function, may be overridden to plug in
+ different report formats (e.g., TAP, JUnit). Assertions such as
+ 'is' call 'report' to indicate results. The argument given to
+ 'report' will be a map with a :type key. See the documentation at
+ the top of test_is.clj for more information on the types of
+ arguments for 'report'."}
+ report :type)
+
+(defmethod report :default [m]
+ (with-test-out (prn m)))
+
+(defmethod report :pass [m]
+ (with-test-out (inc-report-counter :pass)))
+
+(defmethod report :fail [m]
+ (with-test-out
+ (inc-report-counter :fail)
+ (println "\nFAIL in" (testing-vars-str))
+ (when (seq *testing-contexts*) (println (testing-contexts-str)))
+ (when-let [message (:message m)] (println message))
+ (println "expected:" (pr-str (:expected m)))
+ (println " actual:" (pr-str (:actual m)))))
+
+(defmethod report :error [m]
+ (with-test-out
+ (inc-report-counter :error)
+ (println "\nERROR in" (testing-vars-str))
+ (when (seq *testing-contexts*) (println (testing-contexts-str)))
+ (when-let [message (:message m)] (println message))
+ (println "expected:" (pr-str (:expected m)))
+ (print " actual: ")
+ (let [actual (:actual m)]
+ (if (instance? Throwable actual)
+ (stack/print-cause-trace actual *stack-trace-depth*)
+ (prn actual)))))
+
+(defmethod report :summary [m]
+ (with-test-out
+ (println "\nRan" (:test m) "tests containing"
+ (+ (:pass m) (:fail m) (:error m)) "assertions.")
+ (println (:fail m) "failures," (:error m) "errors.")))
+
+(defmethod report :begin-test-ns [m]
+ (with-test-out
+ (println "\nTesting" (ns-name (:ns m)))))
+
+;; Ignore these message types:
+(defmethod report :end-test-ns [m])
+(defmethod report :begin-test-var [m])
+(defmethod report :end-test-var [m])
+
+
+
+;;; 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 {:type :pass, :message ~msg,
+ :expected '~form, :actual (cons ~pred values#)})
+ (report {:type :fail, :message ~msg,
+ :expected '~form, :actual (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 {:type :pass, :message ~msg,
+ :expected '~form, :actual value#})
+ (report {:type :fail, :message ~msg,
+ :expected '~form, :actual value#}))
+ value#))
+
+
+
+;;; ASSERTION METHODS
+
+;; You don't call these, but you can add methods to extend the 'is'
+;; macro. These define different kinds of tests, based on the first
+;; symbol in the test expression.
+
+(defmulti assert-expr
+ (fn [msg form]
+ (cond
+ (nil? form) :always-fail
+ (seq? form) (first form)
+ :else :default)))
+
+(defmethod assert-expr :always-fail [msg form]
+ ;; nil test: always fail
+ `(report {:type :fail, :message ~msg}))
+
+(defmethod assert-expr :default [msg form]
+ (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.
+ `(let [klass# ~(nth form 1)
+ object# ~(nth form 2)]
+ (let [result# (instance? klass# object#)]
+ (if result#
+ (report {:type :pass, :message ~msg,
+ :expected '~form, :actual (class object#)})
+ (report {:type :fail, :message ~msg,
+ :expected '~form, :actual (class object#)}))
+ result#)))
+
+(defmethod assert-expr 'thrown? [msg form]
+ ;; (is (thrown? c expr))
+ ;; Asserts that evaluating expr throws an exception of class c.
+ ;; Returns the exception thrown.
+ (let [klass (second form)
+ body (nthnext form 2)]
+ `(try ~@body
+ (report {:type :fail, :message ~msg,
+ :expected '~form, :actual nil})
+ (catch ~klass e#
+ (report {:type :pass, :message ~msg,
+ :expected '~form, :actual e#})
+ e#))))
+
+(defmethod assert-expr 'thrown-with-msg? [msg form]
+ ;; (is (thrown-with-msg? c re expr))
+ ;; Asserts that evaluating expr throws an exception of class c.
+ ;; Also asserts that the message string of the exception matches
+ ;; (with re-matches) the regular expression re.
+ (let [klass (nth form 1)
+ re (nth form 2)
+ body (nthnext form 3)]
+ `(try ~@body
+ (report {:type :fail, :message ~msg, :expected '~form, :actual nil})
+ (catch ~klass e#
+ (let [m# (.getMessage e#)]
+ (if (re-matches ~re m#)
+ (report {:type :pass, :message ~msg,
+ :expected '~form, :actual e#})
+ (report {:type :fail, :message ~msg,
+ :expected '~form, :actual e#})))
+ e#))))
+
+
+(defmacro try-expr
+ "Used by the 'is' macro to catch unexpected exceptions.
+ You don't call this."
+ [msg form]
+ `(try ~(assert-expr msg form)
+ (catch Throwable t#
+ (report {:type :error, :message ~msg,
+ :expected '~form, :actual t#}))))
+
+
+
+;;; ASSERTION MACROS
+
+;; You use these in your tests.
+
+(defmacro is
+ "Generic assertion macro. 'form' is any predicate test.
+ 'msg' is an optional message to attach to the assertion.
+
+ Example: (is (= 4 (+ 2 2)) \"Two plus two should be 4\")
+
+ Special forms:
+
+ (is (thrown? c body)) checks that an instance of c is thrown from
+ body, fails if not; then returns the thing thrown.
+
+ (is (thrown-with-msg? c re body)) checks that an instance of c is
+ thrown AND that the message on the exception matches (with
+ re-matches) the regular expression re."
+ ([form] `(is ~form nil))
+ ([form msg] `(try-expr ~msg ~form)))
+
+(defmacro are
+ "Checks multiple assertions with a template expression.
+ See clojure.contrib.template/do-template for an explanation of
+ templates.
+
+ Example: (are [x y] (= x y)
+ 2 (+ 1 1)
+ 4 (* 2 2))
+ Expands to:
+ (do (is (= 2 (+ 1 1)))
+ (is (= 4 (* 2 2))))
+
+ Note: This breaks some reporting features, such as line numbers."
+ [argv expr & args]
+ `(temp/do-template ~argv (is ~expr) ~@args))
+
+(defmacro testing
+ "Adds a new string to the list of testing contexts. May be nested,
+ but must occur inside a test function (deftest)."
+ [string & body]
+ `(binding [*testing-contexts* (conj *testing-contexts* ~string)]
+ ~@body))
+
+
+
+;;; DEFINING TESTS
+
+(defmacro with-test
+ "Takes any definition form (that returns a Var) as the first argument.
+ Remaining body goes in the :test metadata function for that Var.
+
+ When *load-tests* is false, only evaluates the definition, ignoring
+ the tests."
+ [definition & body]
+ (if *load-tests*
+ `(doto ~definition (alter-meta! assoc :test (fn [] ~@body)))
+ definition))
+
+
+(defmacro deftest
+ "Defines a test function with no arguments. Test functions may call
+ other tests, so tests may be composed. If you compose tests, you
+ should also define a function named test-ns-hook; run-tests will
+ call test-ns-hook instead of testing all vars.
+
+ Note: Actually, the test body goes in the :test metadata on the var,
+ and the real function (the value of the var) calls test-var on
+ itself.
+
+ When *load-tests* is false, deftest is ignored."
+ [name & body]
+ (when *load-tests*
+ `(def ~(with-meta name {:test `(fn [] ~@body)})
+ (fn [] (test-var (var ~name))))))
+
+(defmacro deftest-
+ "Like deftest but creates a private var."
+ [name & body]
+ (when *load-tests*
+ `(def ~(with-meta name {:test `(fn [] ~@body), :private true})
+ (fn [] (test-var (var ~name))))))
+
+
+(defmacro set-test
+ "Experimental.
+ Sets :test metadata of the named var to a fn with the given body.
+ The var must already exist. Does not modify the value of the var.
+
+ When *load-tests* is false, set-test is ignored."
+ [name & body]
+ (when *load-tests*
+ `(alter-meta! (var ~name) assoc :test (fn [] ~@body))))
+
+
+
+;;; DEFINING FIXTURES
+
+(defn- add-ns-meta
+ "Adds elements in coll to the current namespace metadata as the
+ value of key."
+ [key coll]
+ (alter-meta! *ns* assoc key (concat (key (meta *ns*)) coll)))
+
+(defmulti use-fixtures (fn [fixture-type & args] fixture-type))
+
+(defmethod use-fixtures :each [fixture-type & args]
+ (add-ns-meta ::each-fixtures args))
+
+(defmethod use-fixtures :once [fixture-type & args]
+ (add-ns-meta ::once-fixtures args))
+
+(defn- default-fixture
+ "The default, empty, fixture function. Just calls its argument."
+ [f]
+ (f))
+
+(defn compose-fixtures
+ "Composes two fixture functions, creating a new fixture function
+ that combines their behavior."
+ [f1 f2]
+ (fn [g] (f1 (fn [] (f2 g)))))
+
+(defn join-fixtures
+ "Composes a collection of fixtures, in order. Always returns a valid
+ fixture function, even if the collection is empty."
+ [fixtures]
+ (reduce compose-fixtures default-fixture fixtures))
+
+
+
+
+;;; RUNNING TESTS: LOW-LEVEL FUNCTIONS
+
+(defn test-var
+ "If v has a function in its :test metadata, calls that function,
+ with *testing-vars* bound to (conj *testing-vars* v)."
+ [v]
+ (when-let [t (:test (meta v))]
+ (binding [*testing-vars* (conj *testing-vars* v)]
+ (report {:type :begin-test-var, :var v})
+ (inc-report-counter :test)
+ (try (t)
+ (catch Throwable e
+ (report {:type :error, :message "Uncaught exception, not in assertion."
+ :expected nil, :actual e})))
+ (report {:type :end-test-var, :var v}))))
+
+(defn test-all-vars
+ "Calls test-var on every var interned in the namespace, with fixtures."
+ [ns]
+ (let [once-fixture-fn (join-fixtures (::once-fixtures (meta ns)))
+ each-fixture-fn (join-fixtures (::each-fixtures (meta ns)))]
+ (once-fixture-fn
+ (fn []
+ (doseq [v (vals (ns-interns ns))]
+ (when (:test (meta v))
+ (each-fixture-fn (fn [] (test-var v)))))))))
+
+(defn test-ns
+ "If the namespace defines a function named test-ns-hook, calls that.
+ Otherwise, cal