diff src/clojure/test_clojure/protocols.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
line wrap: on
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/clojure/test_clojure/protocols.clj	Sat Aug 21 06:25:44 2010 -0400
     1.3 @@ -0,0 +1,300 @@
     1.4 +;   Copyright (c) Rich Hickey. All rights reserved.
     1.5 +;   The use and distribution terms for this software are covered by the
     1.6 +;   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
     1.7 +;   which can be found in the file epl-v10.html at the root of this distribution.
     1.8 +;   By using this software in any fashion, you are agreeing to be bound by
     1.9 +;   the terms of this license.
    1.10 +;   You must not remove this notice, or any other, from this software.
    1.11 +
    1.12 +; Author: Stuart Halloway
    1.13 +
    1.14 +(ns clojure.test-clojure.protocols
    1.15 +  (:use clojure.test clojure.test-clojure.protocols.examples)
    1.16 +  (:require [clojure.test-clojure.protocols.more-examples :as other]
    1.17 +            [clojure.set :as set]
    1.18 +            clojure.test-clojure.helpers)
    1.19 +  (:import [clojure.test_clojure.protocols.examples ExampleInterface]))
    1.20 +
    1.21 +;; temporary hack until I decide how to cleanly reload protocol
    1.22 +(defn reload-example-protocols
    1.23 +  []
    1.24 +  (alter-var-root #'clojure.test-clojure.protocols.examples/ExampleProtocol
    1.25 +                  assoc :impls {})
    1.26 +  (alter-var-root #'clojure.test-clojure.protocols.more-examples/SimpleProtocol
    1.27 +                  assoc :impls {})
    1.28 +  (require :reload
    1.29 +           'clojure.test-clojure.protocols.examples
    1.30 +           'clojure.test-clojure.protocols.more-examples))
    1.31 +
    1.32 +(defn method-names
    1.33 +  "return sorted list of method names on a class"
    1.34 +  [c]
    1.35 +  (->> (.getMethods c)
    1.36 +     (map #(.getName %))
    1.37 +     (sort)))
    1.38 +
    1.39 +(defrecord EmptyRecord [])
    1.40 +(defrecord TestRecord [a b])
    1.41 +(defn r
    1.42 +  ([a b] (TestRecord. a b))
    1.43 +  ([a b meta ext] (TestRecord. a b meta ext)))
    1.44 +(defrecord MapEntry [k v]
    1.45 +  java.util.Map$Entry
    1.46 +  (getKey [_] k)
    1.47 +  (getValue [_] v))
    1.48 +
    1.49 +(deftest protocols-test
    1.50 +  (testing "protocol fns have useful metadata"
    1.51 +    (let [common-meta {:ns (find-ns 'clojure.test-clojure.protocols.examples)
    1.52 +                       :protocol #'ExampleProtocol}]
    1.53 +      (are [m f] (= (merge (quote m) common-meta)
    1.54 +                    (meta (var f)))
    1.55 +           {:name foo :arglists ([a]) :doc "method with one arg"} foo
    1.56 +           {:name bar :arglists ([a b]) :doc "method with two args"} bar
    1.57 +           {:name baz :arglists ([a] [a b]) :doc "method with multiple arities" :tag String} baz
    1.58 +           {:name with-quux :arglists ([a]) :doc "method name with a hyphen"} with-quux)))
    1.59 +  (testing "protocol fns throw IllegalArgumentException if no impl matches"
    1.60 +    (is (thrown-with-msg?
    1.61 +          IllegalArgumentException
    1.62 +          #"No implementation of method: :foo of protocol: #'clojure.test-clojure.protocols.examples/ExampleProtocol found for class: java.lang.Integer"
    1.63 +          (foo 10))))
    1.64 +  (testing "protocols generate a corresponding interface using _ instead of - for method names"
    1.65 +    (is (= ["bar" "baz" "baz" "foo" "with_quux"] (method-names clojure.test_clojure.protocols.examples.ExampleProtocol))))
    1.66 +  (testing "protocol will work with instances of its interface (use for interop, not in Clojure!)"
    1.67 +    (let [obj (proxy [clojure.test_clojure.protocols.examples.ExampleProtocol] []
    1.68 +                (foo [] "foo!"))]
    1.69 +      (is (= "foo!" (.foo obj)) "call through interface")
    1.70 +      (is (= "foo!" (foo obj)) "call through protocol")))
    1.71 +  (testing "you can implement just part of a protocol if you want"
    1.72 +    (let [obj (reify ExampleProtocol
    1.73 +                     (baz [a b] "two-arg baz!"))]
    1.74 +      (is (= "two-arg baz!" (baz obj nil)))
    1.75 +      (is (thrown? AbstractMethodError (baz obj)))))
    1.76 +  (testing "you can redefine a protocol with different methods"
    1.77 +    (eval '(defprotocol Elusive (old-method [x])))
    1.78 +    (eval '(defprotocol Elusive (new-method [x])))
    1.79 +    (is (= :new-method (eval '(new-method (reify Elusive (new-method [x] :new-method))))))
    1.80 +    (is (fails-with-cause? IllegalArgumentException #"No method of interface: user\.Elusive found for function: old-method of protocol: Elusive \(The protocol method may have been defined before and removed\.\)"
    1.81 +          (eval '(old-method (reify Elusive (new-method [x] :new-method))))))))
    1.82 +
    1.83 +(deftype ExtendTestWidget [name])
    1.84 +(deftype HasProtocolInline []
    1.85 +  ExampleProtocol
    1.86 +  (foo [this] :inline))
    1.87 +(deftest extend-test
    1.88 +  (testing "you can extend a protocol to a class"
    1.89 +    (extend String ExampleProtocol
    1.90 +            {:foo identity})
    1.91 +    (is (= "pow" (foo "pow"))))
    1.92 +  (testing "you can have two methods with the same name. Just use namespaces!"
    1.93 +    (extend String other/SimpleProtocol
    1.94 +     {:foo (fn [s] (.toUpperCase s))})
    1.95 +    (is (= "POW" (other/foo "pow"))))
    1.96 +  (testing "you can extend deftype types"
    1.97 +    (extend
    1.98 +     ExtendTestWidget
    1.99 +     ExampleProtocol
   1.100 +     {:foo (fn [this] (str "widget " (.name this)))})
   1.101 +    (is (= "widget z" (foo (ExtendTestWidget. "z"))))))
   1.102 +
   1.103 +(deftest illegal-extending
   1.104 +  (testing "you cannot extend a protocol to a type that implements the protocol inline"
   1.105 +    (is (fails-with-cause? IllegalArgumentException #".*HasProtocolInline already directly implements interface"
   1.106 +          (eval '(extend clojure.test-clojure.protocols.HasProtocolInline
   1.107 +                         clojure.test-clojure.protocols.examples/ExampleProtocol
   1.108 +                         {:foo (fn [_] :extended)})))))
   1.109 +  (testing "you cannot extend to an interface"
   1.110 +    (is (fails-with-cause? IllegalArgumentException #"interface clojure.test_clojure.protocols.examples.ExampleProtocol is not a protocol"
   1.111 +          (eval '(extend clojure.test-clojure.protocols.HasProtocolInline
   1.112 +                         clojure.test_clojure.protocols.examples.ExampleProtocol
   1.113 +                         {:foo (fn [_] :extended)}))))))
   1.114 +
   1.115 +(deftype ExtendsTestWidget []
   1.116 +  ExampleProtocol)
   1.117 +(deftest extends?-test
   1.118 +  (reload-example-protocols)
   1.119 +  (testing "returns false if a type does not implement the protocol at all"
   1.120 +    (is (false? (extends? other/SimpleProtocol ExtendsTestWidget))))
   1.121 +  (testing "returns true if a type implements the protocol directly" ;; semantics changed 4/15/2010
   1.122 +    (is (true? (extends? ExampleProtocol ExtendsTestWidget))))
   1.123 +  (testing "returns true if a type explicitly extends protocol"
   1.124 +    (extend
   1.125 +     ExtendsTestWidget
   1.126 +     other/SimpleProtocol
   1.127 +     {:foo identity})
   1.128 +    (is (true? (extends? other/SimpleProtocol ExtendsTestWidget)))))
   1.129 +
   1.130 +(deftype ExtendersTestWidget [])
   1.131 +(deftest extenders-test
   1.132 +  (reload-example-protocols)
   1.133 +  (testing "a fresh protocol has no extenders"
   1.134 +    (is (nil? (extenders ExampleProtocol))))
   1.135 +  (testing "extending with no methods doesn't count!"
   1.136 +    (deftype Something [])
   1.137 +    (extend ::Something ExampleProtocol)
   1.138 +    (is (nil? (extenders ExampleProtocol))))
   1.139 +  (testing "extending a protocol (and including an impl) adds an entry to extenders"
   1.140 +    (extend ExtendersTestWidget ExampleProtocol {:foo identity})
   1.141 +    (is (= [ExtendersTestWidget] (extenders ExampleProtocol)))))
   1.142 +
   1.143 +(deftype SatisfiesTestWidget []
   1.144 +  ExampleProtocol)
   1.145 +(deftest satisifies?-test
   1.146 +  (reload-example-protocols)
   1.147 +  (let [whatzit (SatisfiesTestWidget.)]
   1.148 +    (testing "returns false if a type does not implement the protocol at all"
   1.149 +      (is (false? (satisfies? other/SimpleProtocol whatzit))))
   1.150 +    (testing "returns true if a type implements the protocol directly"
   1.151 +      (is (true? (satisfies? ExampleProtocol whatzit))))
   1.152 +    (testing "returns true if a type explicitly extends protocol"
   1.153 +      (extend
   1.154 +       SatisfiesTestWidget
   1.155 +       other/SimpleProtocol
   1.156 +       {:foo identity})
   1.157 +      (is (true? (satisfies? other/SimpleProtocol whatzit)))))  )
   1.158 +
   1.159 +(deftype ReExtendingTestWidget [])
   1.160 +(deftest re-extending-test
   1.161 +  (reload-example-protocols)
   1.162 +  (extend
   1.163 +   ReExtendingTestWidget
   1.164 +   ExampleProtocol
   1.165 +   {:foo (fn [_] "first foo")
   1.166 +    :baz (fn [_] "first baz")})
   1.167 +  (testing "if you re-extend, the old implementation is replaced (not merged!)"
   1.168 +    (extend
   1.169 +     ReExtendingTestWidget
   1.170 +     ExampleProtocol
   1.171 +     {:baz (fn [_] "second baz")
   1.172 +      :bar (fn [_ _] "second bar")})
   1.173 +    (let [whatzit (ReExtendingTestWidget.)]
   1.174 +      (is (thrown? IllegalArgumentException (foo whatzit)))
   1.175 +      (is (= "second bar" (bar whatzit nil)))
   1.176 +      (is (= "second baz" (baz whatzit))))))
   1.177 +
   1.178 +(defrecord DefrecordObjectMethodsWidgetA [a])
   1.179 +(defrecord DefrecordObjectMethodsWidgetB [a])
   1.180 +(deftest defrecord-object-methods-test
   1.181 +  (testing "= depends on fields and type"
   1.182 +    (is (true? (= (DefrecordObjectMethodsWidgetA. 1) (DefrecordObjectMethodsWidgetA. 1))))
   1.183 +    (is (false? (= (DefrecordObjectMethodsWidgetA. 1) (DefrecordObjectMethodsWidgetA. 2))))
   1.184 +    (is (false? (= (DefrecordObjectMethodsWidgetA. 1) (DefrecordObjectMethodsWidgetB. 1))))))
   1.185 +
   1.186 +(deftest defrecord-acts-like-a-map
   1.187 +  (let [rec (r 1 2)]
   1.188 +    (is (.equals (r 1 3 {} {:c 4}) (merge rec {:b 3 :c 4})))
   1.189 +    (is (.equals {:foo 1 :b 2} (set/rename-keys rec {:a :foo})))
   1.190 +    (is (.equals {:a 11 :b 2 :c 10} (merge-with + rec {:a 10 :c 10})))))
   1.191 +
   1.192 +(deftest degenerate-defrecord-test
   1.193 +  (let [empty (EmptyRecord.)]
   1.194 +    (is (nil? (seq empty)))
   1.195 +    (is (not (.containsValue empty :a)))))
   1.196 +
   1.197 +(deftest defrecord-interfaces-test
   1.198 +  (testing "java.util.Map"
   1.199 +    (let [rec (r 1 2)]
   1.200 +      (is (= 2 (.size rec)))
   1.201 +      (is (= 3 (.size (assoc rec :c 3))))
   1.202 +      (is (not (.isEmpty rec)))
   1.203 +      (is (.isEmpty (EmptyRecord.)))
   1.204 +      (is (.containsKey rec :a))
   1.205 +      (is (not (.containsKey rec :c)))
   1.206 +      (is (.containsValue rec 1))
   1.207 +      (is (not (.containsValue rec 3)))
   1.208 +      (is (= 1 (.get rec :a)))
   1.209 +      (is (thrown? UnsupportedOperationException (.put rec :a 1)))
   1.210 +      (is (thrown? UnsupportedOperationException (.remove rec :a)))
   1.211 +      (is (thrown? UnsupportedOperationException (.putAll rec {})))
   1.212 +      (is (thrown? UnsupportedOperationException (.clear rec)))
   1.213 +      (is (= #{:a :b} (.keySet rec)))
   1.214 +      (is (= #{1 2} (set (.values rec))))
   1.215 +      (is (= #{[:a 1] [:b 2]} (.entrySet rec)))
   1.216 +      
   1.217 +      ))
   1.218 +  (testing "IPersistentCollection"
   1.219 +    (testing ".cons"
   1.220 +      (let [rec (r 1 2)]
   1.221 +        (are [x] (= rec (.cons rec x))
   1.222 +             nil {})
   1.223 +        (is (= (r 1 3) (.cons rec {:b 3})))
   1.224 +        (is (= (r 1 4) (.cons rec [:b 4])))
   1.225 +        (is (= (r 1 5) (.cons rec (MapEntry. :b 5))))))))
   1.226 +
   1.227 +(defrecord RecordWithSpecificFieldNames [this that k m o])
   1.228 +(deftest defrecord-with-specific-field-names
   1.229 +  (let [rec (new RecordWithSpecificFieldNames 1 2 3 4 5)]
   1.230 +    (is (= rec rec))
   1.231 +    (is (= 1 (:this (with-meta rec {:foo :bar}))))
   1.232 +    (is (= 3 (get rec :k)))
   1.233 +    (is (= (seq rec) '([:this 1] [:that 2] [:k 3] [:m 4] [:o 5])))
   1.234 +    (is (= (dissoc rec :k) {:this 1, :that 2, :m 4, :o 5}))))
   1.235 +
   1.236 +(deftest reify-test
   1.237 +  (testing "of an interface"
   1.238 +    (let [s :foo
   1.239 +          r (reify
   1.240 +             java.util.List
   1.241 +             (contains [_ o] (= s o)))]
   1.242 +      (testing "implemented methods"
   1.243 +        (is (true? (.contains r :foo)))
   1.244 +        (is (false? (.contains r :bar))))
   1.245 +      (testing "unimplemented methods"
   1.246 +        (is (thrown? AbstractMethodError (.add r :baz))))))
   1.247 +  (testing "of two interfaces"
   1.248 +    (let [r (reify
   1.249 +             java.util.List
   1.250 +             (contains [_ o] (= :foo o))
   1.251 +             java.util.Collection
   1.252 +             (isEmpty [_] false))]
   1.253 +      (is (true? (.contains r :foo)))
   1.254 +      (is (false? (.contains r :bar)))
   1.255 +      (is (false? (.isEmpty r)))))
   1.256 +  (testing "you can't define a method twice"
   1.257 +    (is (fails-with-cause?
   1.258 +         java.lang.ClassFormatError #"^(Repetitive|Duplicate) method name"
   1.259 +         (eval '(reify
   1.260 +                 java.util.List
   1.261 +                 (size [_] 10)
   1.262 +                 java.util.Collection
   1.263 +                 (size [_] 20))))))
   1.264 +  (testing "you can't define a method not on an interface/protocol/j.l.Object"
   1.265 +    (is (fails-with-cause? 
   1.266 +         IllegalArgumentException #"^Can't define method not in interfaces: foo"
   1.267 +         (eval '(reify java.util.List (foo [_]))))))
   1.268 +  (testing "of a protocol"
   1.269 +    (let [r (reify
   1.270 +             ExampleProtocol
   1.271 +             (bar [this o] o)
   1.272 +             (baz [this] 1)
   1.273 +             (baz [this o] 2))]
   1.274 +      (= :foo (.bar r :foo))
   1.275 +      (= 1 (.baz r))
   1.276 +      (= 2 (.baz r nil))))
   1.277 +  (testing "destructuring in method def"
   1.278 +    (let [r (reify
   1.279 +             ExampleProtocol
   1.280 +             (bar [this [_ _ item]] item))]
   1.281 +      (= :c (.bar r [:a :b :c]))))
   1.282 +  (testing "methods can recur"
   1.283 +    (let [r (reify
   1.284 +             java.util.List
   1.285 +             (get [_ index]
   1.286 +                  (if (zero? index)
   1.287 +                    :done
   1.288 +                    (recur (dec index)))))]
   1.289 +      (is (= :done (.get r 0)))
   1.290 +      (is (= :done (.get r 1)))))
   1.291 +  (testing "disambiguating with type hints"
   1.292 +    (testing "you must hint an overloaded method"
   1.293 +      (is (fails-with-cause?
   1.294 +            IllegalArgumentException #"Must hint overloaded method: hinted"
   1.295 +            (eval '(reify clojure.test_clojure.protocols.examples.ExampleInterface (hinted [_ o]))))))
   1.296 +    (testing "hinting"
   1.297 +      (let [r (reify
   1.298 +               ExampleInterface
   1.299 +               (hinted [_ ^int i] (inc i))
   1.300 +               (hinted [_ ^String s] (str s s)))]
   1.301 +        (is (= 2 (.hinted r 1)))
   1.302 +        (is (= "xoxo" (.hinted r "xo")))))))
   1.303 +