aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.xml2
-rw-r--r--src/clojure/contrib/expect.clj276
-rw-r--r--src/clojure/contrib/expect/test_adapter.clj37
-rw-r--r--src/clojure/contrib/java_utils.clj1
-rw-r--r--src/clojure/contrib/ns_utils.clj17
-rw-r--r--src/clojure/contrib/seq_utils.clj10
-rw-r--r--src/clojure/contrib/test_contrib.clj3
-rw-r--r--src/clojure/contrib/test_contrib/expect_test.clj131
-rw-r--r--src/clojure/contrib/test_contrib/expect_test/test_adapter_test.clj18
-rw-r--r--src/clojure/contrib/test_contrib/seq_utils_test.clj11
-rw-r--r--src/clojure/contrib/test_contrib/test_java_utils.clj8
11 files changed, 513 insertions, 1 deletions
diff --git a/build.xml b/build.xml
index fc5fa65f..b7992d54 100644
--- a/build.xml
+++ b/build.xml
@@ -122,6 +122,8 @@
<arg value="clojure.contrib.duck-streams"/>
<arg value="clojure.contrib.error-kit"/>
<arg value="clojure.contrib.except"/>
+ <arg value="clojure.contrib.expect"/>
+ <arg value="clojure.contrib.expect.test-is-adapter"/>
<arg value="clojure.contrib.fcase"/>
<arg value="clojure.contrib.find-namespaces"/>
<arg value="clojure.contrib.fnmap"/>
diff --git a/src/clojure/contrib/expect.clj b/src/clojure/contrib/expect.clj
new file mode 100644
index 00000000..1d78e939
--- /dev/null
+++ b/src/clojure/contrib/expect.clj
@@ -0,0 +1,276 @@
+;;; clojure.contrib.expect.clj: mocking/expectation framework for Clojure
+
+;; by Matt Clark
+
+;; Copyright (c) Matt Clark, 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).
+;; 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
+ ;; This is a simple function mocking library I accidentally wrote as a side
+ ;; effect of trying to write an opengl library in clojure. This is loosely
+ ;; based on various ruby and java mocking frameworks I have used in the past
+ ;; such as mockito, easymock, and whatever rspec uses.
+ ;;
+ ;; expect uses bindings to wrap the functions that are being tested and
+ ;; then validates the invocation count at the end. The expect macro is the
+ ;; main entry point and it is given a vector of binding pairs.
+ ;; The first of each pair names the dependent function you want to override,
+ ;; while the second is a hashmap containing the mock description, usually
+ ;; created via the simple helper methods described below.
+ ;;
+ ;; Usage:
+ ;;
+ ;; there are one or more dependent functions:
+
+ (defn dep-fn1 [] "time consuming calculation in 3rd party library")
+ (defn dep-fn2 [x] "function with undesirable side effects while testing")
+
+ ;; then we have the code under test that calls these other functions:
+
+ (defn my-code-under-test [] (dep-fn1) (dep-fn2 "a") (+ 2 2))
+
+ ;; to test this code, we simply surround it with an expect macro within
+ ;; the test:
+
+ (expect [dep-fn1 (times 1)
+ dep-fn2 (times 1 (has-args [#(= "a" %)]))]
+ (my-code-under-test))
+
+ ;; When an expectation fails during execution of the function under test,
+ ;; an error condition function is called with the name of the function
+ ;; being mocked, the expected form and the actual value. These
+ ;; error functions can be overridden to allow easy integration into
+ ;; test frameworks such as test-is by reporting errors in the function
+ ;; overrides.
+
+ ) ;; end comment
+
+(ns clojure.contrib.expect
+ (:use [clojure.contrib.seq-utils :only (positions)]
+ [clojure.contrib.def :only (defmacro-)]))
+
+
+;;------------------------------------------------------------------------------
+;; These are the error condition functions. Override them to integrate into
+;; the test framework of your choice, or to simply customize error handling.
+
+(defn report-problem
+ ([function expected actual]
+ (report-problem function expected actual "Expectation not met."))
+ ([function expected actual message]
+ (prn (str message " Function name: " function
+ " expected: " expected " actual: " actual))))
+
+(defn no-matching-function-signature [function expected actual]
+ (report-problem function expected actual
+ "No matching real function signature for given argument count."))
+
+(defn unexpected-args [function expected actual i]
+ (report-problem function expected actual
+ (str "Argument " i " has an unexpected value for function.")))
+
+(defn incorrect-invocation-count [function expected actual]
+ (report-problem function expected actual "Unexpected invocation count."))
+
+
+;;------------------------------------------------------------------------------
+;; Internal Functions - ignore these
+
+
+(defn- has-arg-count-match?
+ "Given the sequence of accepted argument vectors for a function,
+returns true if at least one matches the given-count value."
+ [arg-lists given-count]
+ (some #(let [[ind] (positions #{'&} %)]
+ (if ind
+ (>= given-count ind)
+ (= (count %) given-count)))
+ arg-lists))
+
+
+(defn has-matching-signature?
+ "Calls no-matching-function-signature if no match is found for the given
+function. If no argslist meta data is available for the function, it is
+not called."
+ [fn-name args]
+ (let [arg-count (count args)
+ arg-lists (:arglists (meta (resolve fn-name)))]
+ (if (and arg-lists (not (has-arg-count-match? arg-lists arg-count)))
+ (no-matching-function-signature fn-name arg-lists args))))
+
+
+(defn make-arg-checker
+ "Creates the argument verifying function for a replaced dependency within
+the expectation bound scope. These functions take the additional argument
+of the name of the replaced function, then the rest of their args. It is
+designed to be called from the mock function generated in the first argument
+of the mock info object created by make-mock."
+ [arg-preds arg-pred-forms]
+ (let [sanitized-preds (map (fn [v] (if (fn? v) v #(= v %))) arg-preds)]
+ (fn [fn-name & args]
+ (every? true?
+ (map (fn [pred arg pred-form i] (if (pred arg) true
+ (unexpected-args fn-name pred-form arg i)))
+ sanitized-preds args arg-pred-forms (iterate inc 0))))))
+
+
+(defn make-count-checker
+ "creates the count checker that is invoked at the end of an expectation, after
+the code under test has all been executed. The function returned takes the
+name of the associated dependency and the invocation count as arguments."
+ [pred pred-form]
+ (let [pred-fn (if (integer? pred) #(= pred %) pred)]
+ (fn [fn-name v] (if (pred-fn v) true
+ (incorrect-invocation-count fn-name pred-form v)))))
+
+; Borrowed from clojure core. Remove if this ever becomes public there.
+(defmacro- assert-args
+ [fnname & pairs]
+ `(do (when-not ~(first pairs)
+ (throw (IllegalArgumentException.
+ ~(str fnname " requires " (second pairs)))))
+ ~(let [more (nnext pairs)]
+ (when more
+ (list* `assert-args fnname more)))))
+
+(defn make-mock
+ "creates a vector containing the following information for the named function:
+1. dependent function replacement - verifies signature, calls arg checker,
+increases count, returns return value.
+2. an atom containing the invocation count
+3. the invocation count checker function
+4. a symbol of the name of the function being replaced."
+ [fn-name expectation-hash]
+ (assert-args make-mock
+ (map? expectation-hash) "a map of expectations")
+ (let [arg-checker (or (expectation-hash :has-args) (fn [& args] true))
+ count-atom (atom 0)
+ ret-fn (or
+ (expectation-hash :calls)
+ (fn [& args] (expectation-hash :returns)))]
+ [(fn [& args]
+ (has-matching-signature? fn-name args)
+ (apply arg-checker fn-name args)
+ (swap! count-atom inc)
+ (apply ret-fn args))
+ count-atom
+ (or (expectation-hash :times) (fn [fn-name v] true))
+ fn-name]))
+
+
+(defn validate-counts
+ "given the sequence of all mock data for the expectation, simply calls the
+count checker for each dependency."
+ [mock-data] (doseq [[mfn i checker fn-name] mock-data] (checker fn-name @i)))
+
+(defn #^{:private true} make-bindings [expect-bindings mock-data-sym]
+ `[~@(interleave (map #(first %) (partition 2 expect-bindings))
+ (map (fn [i] `(nth (nth ~mock-data-sym ~i) 0))
+ (range (quot (count expect-bindings) 2))))])
+
+
+;;------------------------------------------------------------------------------
+;; These are convenience functions to improve the readability and use of this
+;; library. Useful in expressions such as:
+;; (expect [dep-fn1 (times (more-than 1) (returns 15)) etc)
+
+(defn once [x] (= 1 x))
+
+(defn never [x] (zero? x))
+
+(defn more-than [x] #(< x %))
+
+(defn less-than [x] #(> x %))
+
+(defn between [x y] #(and (< x %) (> y %)))
+
+
+;;------------------------------------------------------------------------------
+;; The following functions can be used to build up the expectation hash.
+
+(defn returns
+ "Creates or associates to an existing expectation hash the :returns key with
+a value to be returned by the expectation after a successful invocation
+matching its expected arguments (if applicable).
+Usage:
+(returns ret-value expectation-hash?)"
+
+ ([val] (returns val {}))
+ ([val expectation-hash] (assoc expectation-hash :returns val)))
+
+
+(defn calls
+ "Creates or associates to an existing expectation hash the :calls key with a
+function that will be called with the given arguments. The return value from
+this function will be returned returned by the expected function. If both this
+and returns are specified, the return value of \"calls\" will have precedence.
+Usage:
+(calls some-fn expectation-hash?)"
+
+ ([val] (calls val {}))
+ ([val expectation-hash] (assoc expectation-hash :calls val)))
+
+
+(defmacro has-args
+ "Creates or associates to an existing expectation hash the :has-args key with
+a value corresponding to a function that will either return true if its
+argument expectations are met or throw an exception with the details of the
+first failed argument it encounters.
+Only specify as many predicates as you are interested in verifying. The rest
+of the values are safely ignored.
+Usage:
+(has-args [arg-pred-1 arg-pred-2 ... arg-pred-n] expectation-hash?)"
+
+ ([arg-pred-forms] `(has-args ~arg-pred-forms {}))
+ ([arg-pred-forms expect-hash-form]
+ (assert-args has-args
+ (vector? arg-pred-forms) "a vector of argument predicates")
+ `(assoc ~expect-hash-form :has-args
+ (make-arg-checker ~arg-pred-forms '~arg-pred-forms))))
+
+
+(defmacro times
+ "Creates or associates to an existing expectation hash the :times key with a
+value corresponding to a predicate function which expects an integer value.
+This function can either be specified as the first argument to times or can be
+the result of calling times with an integer argument, in which case the
+predicate will default to being an exact match. This predicate is called at
+the end of an expect expression to validate that an expected dependency
+function was called the expected number of times.
+Usage:
+(times n)
+(times #(> n %))
+(times n expectation-hash)"
+ ([times-fn] `(times ~times-fn {}))
+ ([times-fn expectation-hash]
+ `(assoc ~expectation-hash :times (make-count-checker ~times-fn '~times-fn))))
+
+
+;-------------------------------------------------------------------------------
+; The main expect macro.
+(defmacro expect
+ "Use expect to redirect calls to dependent functions that are made within the
+code under test. Instead of calling the functions that would normally be used,
+temporary stubs are used, which can verify function parameters and call counts.
+Return values can also be specified as needed.
+Usage:
+(expect [dep-fn (has-args [arg-pred1] (times n (returns x)))]
+ (function-under-test a b c))"
+
+ [expect-bindings & body]
+ (assert-args expect
+ (vector? expect-bindings) "a vector of expectation bindings"
+ (even? (count expect-bindings))
+ "an even number of forms in expectation bindings")
+ (let [mock-data (gensym "mock-data_")]
+ `(let [~mock-data (map (fn [args#]
+ (apply clojure.contrib.expect/make-mock args#))
+ ~(cons 'list (map (fn [[n m]] (vector (list 'quote n) m))
+ (partition 2 expect-bindings))))]
+ (binding ~(make-bindings expect-bindings mock-data) ~@body)
+ (clojure.contrib.expect/validate-counts ~mock-data) true)))
diff --git a/src/clojure/contrib/expect/test_adapter.clj b/src/clojure/contrib/expect/test_adapter.clj
new file mode 100644
index 00000000..50e50125
--- /dev/null
+++ b/src/clojure/contrib/expect/test_adapter.clj
@@ -0,0 +1,37 @@
+;;; test_adapter.clj: clojure.test adapter for mocking/expectation framework for Clojure
+
+;; by Matt Clark
+
+;; Copyright (c) Matt Clark, 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).
+;; 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 clojure.contrib.expect.test-adapter
+ (:require [clojure.contrib.expect :as expect])
+ (:use clojure.test
+ clojure.contrib.ns-utils))
+
+(immigrate 'clojure.contrib.expect)
+
+(defn report-problem
+ "This function is designed to be used in a binding macro to override
+the report-problem function in the test-expect namespace. Instead of printing
+the error to the console, the error is logged via test-is."
+ [fn-name expected actual msg]
+ (report {:type :fail,
+ :message (str msg " Function name: " fn-name),
+ :expected expected,
+ :actual actual}))
+
+
+(defmacro expect [& body]
+ "Use this macro instead of the standard test-expect expect macro to have
+failures reported through test-is."
+ `(binding [expect/report-problem report-problem]
+ (expect/expect ~@body)))
+
+
+
diff --git a/src/clojure/contrib/java_utils.clj b/src/clojure/contrib/java_utils.clj
index ce2b7777..579d1128 100644
--- a/src/clojure/contrib/java_utils.clj
+++ b/src/clojure/contrib/java_utils.clj
@@ -186,3 +186,4 @@
(defmethod as-url String [#^String x] (URL. x))
+(defmethod as-url File [#^File x] (.toURL x))
diff --git a/src/clojure/contrib/ns_utils.clj b/src/clojure/contrib/ns_utils.clj
index 7a866d5a..d77b3217 100644
--- a/src/clojure/contrib/ns_utils.clj
+++ b/src/clojure/contrib/ns_utils.clj
@@ -22,6 +22,9 @@
;; 'print-docs' prints documentation for the public vars in a
;; namespace
;;
+;; 'immigrate' Create a public var in this namespace for each
+;; public var in the namespaces named by ns-names.
+;; From James Reeves
;; Convenience
;;
;; 'vars' returns a sorted seq of symbols naming public vars
@@ -87,3 +90,17 @@
"Prints documentation for the public vars in a namespace"
[nsname]
`(print-docs (get-ns '~nsname)))
+
+(defn immigrate
+ "Create a public var in this namespace for each public var in the
+ namespaces named by ns-names. The created vars have the same name, root
+ binding, and metadata as the original except that their :ns metadata
+ value is this namespace."
+ [& ns-names]
+ (doseq [ns ns-names]
+ (require ns)
+ (doseq [[sym var] (ns-publics ns)]
+ (let [sym (with-meta sym (assoc (meta var) :ns *ns*))]
+ (if (.hasRoot var)
+ (intern *ns* sym (.getRoot var))
+ (intern *ns* sym))))))
diff --git a/src/clojure/contrib/seq_utils.clj b/src/clojure/contrib/seq_utils.clj
index d4c29c46..ad913f70 100644
--- a/src/clojure/contrib/seq_utils.clj
+++ b/src/clojure/contrib/seq_utils.clj
@@ -211,3 +211,13 @@
(cons (if (identical? x NIL) nil x)
(drain))))))))))
+(defn positions
+ "Returns a lazy sequence containing the positions at which pred
+ is true for items in coll."
+ [pred coll]
+ (for [[idx elt] (indexed coll) :when (pred elt)] idx))
+
+
+
+
+
diff --git a/src/clojure/contrib/test_contrib.clj b/src/clojure/contrib/test_contrib.clj
index d7b2597b..8402b7ba 100644
--- a/src/clojure/contrib/test_contrib.clj
+++ b/src/clojure/contrib/test_contrib.clj
@@ -20,7 +20,8 @@
[:complex-numbers :fnmap :macro-utils :monads :pprint.pretty
:pprint.cl-format :str-utils :shell-out :test-graph
:test-dataflow :test-java-utils :test-lazy-seqs
- :test-trace :test-jmx :java-utils])
+ :test-trace :test-jmx :java-utils :expect-test :expect-test.test-adapter-test
+ :seq-utils-test])
(def test-namespaces
(map #(symbol (str "clojure.contrib.test-contrib." (name %)))
diff --git a/src/clojure/contrib/test_contrib/expect_test.clj b/src/clojure/contrib/test_contrib/expect_test.clj
new file mode 100644
index 00000000..52f37741
--- /dev/null
+++ b/src/clojure/contrib/test_contrib/expect_test.clj
@@ -0,0 +1,131 @@
+(ns clojure.contrib.test-contrib.expect-test
+ (:use clojure.test)
+ (:require [clojure.contrib.expect :as expect]))
+
+; Used as dummy dependency functions
+(defn fn1 [x] :ignore)
+(defn fn2 [x y] :ignore)
+(defn fn3 ([x] :ignore)
+ ([x y z] :ignore))
+(defn fn4 [x y & r] :ignore)
+
+;functions created using fn directly lack the argslist meta data
+(def deffed-differently (fn [x] :ignore))
+
+(defmacro assert-called [fn-name called? & body]
+ `(let [called-status?# (atom false)]
+ (binding [~fn-name (fn [& args#] (reset! called-status?# true))] ~@body)
+ (is (= ~called? @called-status?#))))
+
+(deftest test-convenience
+ (testing "once"
+ (is (false? (expect/once 0)))
+ (is (false? (expect/once 123)))
+ (is (true? (expect/once 1))))
+
+ (testing "never"
+ (is (false? (expect/never 4)))
+ (is (true? (expect/never 0))))
+
+ (testing "more-than"
+ (is (false? ((expect/more-than 5) 3)))
+ (is (true? ((expect/more-than 5) 9))))
+
+ (testing "less-than"
+ (is (true? ((expect/less-than 5) 3)))
+ (is (false? ((expect/less-than 5) 9))))
+
+ (testing "between"
+ (is (true? ((expect/between 5 8) 6)))
+ (is (false? ((expect/between 5 8) 5)))))
+
+
+(deftest test-returns
+ (is (= {:returns 5} (expect/returns 5)))
+ (is (= {:other-key "test" :returns nil} (expect/returns nil {:other-key "test"}))))
+
+
+(deftest test-has-args
+ (let [ex (:has-args (expect/has-args [1]))]
+ (is (fn? ex))
+ (is (ex 'fn1 1))
+ (is (ex 'fn1 1 5 6))
+ (assert-called expect/unexpected-args true (ex 'fn1 5)))
+ (is (contains? (expect/has-args [] {:pre-existing-key "test"}) :pre-existing-key))
+ (is (true? (((expect/has-args [5]) :has-args)'fn1 5))))
+
+
+(deftest test-has-matching-signature
+ (assert-called expect/no-matching-function-signature true
+ (expect/has-matching-signature? 'clojure.contrib.test-contrib.expect-test/fn2 [1]))
+ (assert-called expect/no-matching-function-signature true
+ (expect/has-matching-signature? 'clojure.contrib.test-contrib.expect-test/fn3 [1 3]))
+ (assert-called expect/no-matching-function-signature false
+ (expect/has-matching-signature? 'clojure.contrib.test-contrib.expect-test/fn3 [1 3 5]))
+ (assert-called expect/no-matching-function-signature false
+ (expect/has-matching-signature? 'clojure.contrib.test-contrib.expect-test/fn4 [1 3 5 7 9]))
+ (assert-called expect/no-matching-function-signature false
+ (expect/has-matching-signature? 'clojure.contrib.test-contrib.expect-test/fn4 [1 3]))
+ (assert-called expect/no-matching-function-signature true
+ (expect/has-matching-signature? 'clojure.contrib.test-contrib.expect-test/fn4 [1]))
+ (assert-called expect/no-matching-function-signature false
+ (expect/has-matching-signature? 'clojure.contrib.test-contrib.expect-test/deffed-differently [1])))
+
+
+(deftest test-times
+ (is (fn? ((expect/times #(= 1 %)) :times)))
+ (is (contains? (expect/times #(= 1 %) {:existing-key "test"}) :existing-key)))
+
+(deftest test-make-mock
+ (testing "invalid arguments"
+ (is (thrown? IllegalArgumentException (expect/make-mock [5]))))
+
+ (testing "valid counter and unevaluated returns"
+ (let [[mock counter count-checker] (expect/make-mock 'fn1 (expect/returns 5 (expect/times 1)))]
+ (is (fn? mock))
+ (is (= 0 @counter))
+ (is (= 5 (mock :ignore-me)))
+ (is (= 1 @counter))))
+
+ (testing "returns as expected"
+ (let [[mock] (expect/make-mock 'fn1 (expect/returns 5))]
+ (is (= 5 (mock :ignore))))
+ (let [[mock] (expect/make-mock 'fn1 (expect/returns #(* 2 %)))]
+ (is (= 10 ((mock :ignore) 5)) ":returns a function should not automatically
+ evaluate it.")))
+
+ (testing "calls replacement-fn and returns the result"
+ (let [[mock] (expect/make-mock 'fn1 (expect/calls #(* 3 %)))]
+ (is (= 15 (mock 5))))
+ (let [[mock] (expect/make-mock 'fn1 (expect/calls #(* 2 %) (expect/returns 3)))]
+ (is (= 10 (mock 5)))))
+
+ (testing "argument validation"
+ (let [[mock] (expect/make-mock 'fn1 (expect/has-args [#(= 5 %)]))]
+ (assert-called expect/unexpected-args true (mock "test"))
+ (is (nil? (mock 5))))))
+
+
+(deftest test-make-count-checker
+ (let [checker (expect/make-count-checker 5 5)]
+ (assert-called expect/incorrect-invocation-count false (checker 'fn1 5))
+ (assert-called expect/incorrect-invocation-count true (checker 'fn1 3))))
+
+
+(deftest test-validate-counts
+ (assert-called expect/incorrect-invocation-count false
+ (expect/validate-counts (list [(fn []) (atom 0) (expect/make-count-checker #(< % 6) '#(< % 6)) 'fn1])))
+ (assert-called expect/incorrect-invocation-count true
+ (expect/validate-counts (list [(fn []) (atom 0) (expect/make-count-checker 4 4) 'fn1]))))
+
+
+(deftest test-expect-macro
+ (let [under-test (fn [x] (fn1 x))]
+ (is (true? (expect/expect [fn1 (expect/times 1 (expect/has-args [#(= 3 %)]))]
+ (under-test 3))))
+ (assert-called expect/unexpected-args true (expect/expect [fn1 (expect/times 1 (expect/has-args [#(= 4 %)]))]
+ (under-test 3))))
+ (let [under-test (fn [] (fn2 (fn1 1) 3))]
+ (is (true? (expect/expect [fn1 (expect/times 1 (expect/has-args [#(= 1 %)] (expect/returns 2)))
+ fn2 (expect/times 1 (expect/has-args [#(= 2 %) #(= 3 %)] (expect/returns 5)))]
+ (under-test)))))) \ No newline at end of file
diff --git a/src/clojure/contrib/test_contrib/expect_test/test_adapter_test.clj b/src/clojure/contrib/test_contrib/expect_test/test_adapter_test.clj
new file mode 100644
index 00000000..df74404e
--- /dev/null
+++ b/src/clojure/contrib/test_contrib/expect_test/test_adapter_test.clj
@@ -0,0 +1,18 @@
+(ns clojure.contrib.test-contrib.expect-test.test-adapter-test
+ (:use clojure.contrib.expect.test-adapter
+ [clojure.contrib.test-contrib.expect-test :only (assert-called)]
+ clojure.test))
+
+(deftest test-report-problem-called
+ (def #^{:private true} fn1 (fn [x] "dummy code"))
+ (def #^{:private true} fn2 (fn [x y] "dummy code2"))
+ (let [under-test (fn [x] (fn1 x))]
+ (assert-called clojure.contrib.expect.test-adapter/report-problem
+ true (expect [fn1 (times 5)] (under-test "hi")))))
+
+(deftest test-is-report-called
+ (assert-called clojure.test/report true
+ (clojure.contrib.expect.test-adapter/report-problem
+ 'fn-name 5 6 "fake problem")))
+
+
diff --git a/src/clojure/contrib/test_contrib/seq_utils_test.clj b/src/clojure/contrib/test_contrib/seq_utils_test.clj
new file mode 100644
index 00000000..f8815041
--- /dev/null
+++ b/src/clojure/contrib/test_contrib/seq_utils_test.clj
@@ -0,0 +1,11 @@
+(ns clojure.contrib.test-contrib.seq-utils-test
+ (:use clojure.test
+ clojure.contrib.seq-utils))
+
+
+(deftest test-positions
+ (are [expected pred coll] (= expected (positions pred coll))
+ [2] string? [:a :b "c"]
+ () :d [:a :b :c]
+ [0 2] #{:d} [:d :a :d :a]))
+ \ No newline at end of file
diff --git a/src/clojure/contrib/test_contrib/test_java_utils.clj b/src/clojure/contrib/test_contrib/test_java_utils.clj
index 8a56b197..abf1b9a6 100644
--- a/src/clojure/contrib/test_contrib/test_java_utils.clj
+++ b/src/clojure/contrib/test_contrib/test_java_utils.clj
@@ -3,6 +3,7 @@
[clojure.contrib.duck-streams :only (spit)]
clojure.contrib.java-utils)
(:import [java.io File]
+ [java.net URL URI]
[java.util Properties]))
(deftest test-relative-path-string
@@ -23,6 +24,13 @@
(is (= (File. "bar") (as-file (File. "bar")))))
)
+(deftest test-as-url
+ (are [result expr] (= result expr)
+ (URL. "http://foo") (as-url (URL. "http://foo"))
+ (URL. "http://foo") (as-url "http://foo")
+ (URL. "http://foo") (as-url (URI. "http://foo"))
+ (URL. "file:/foo") (as-url (File. "/foo"))))
+
(deftest test-file
(testing "single argument"
(is (= (File. "foo") (file "foo"))))