Skip to content

Commit 9f9f44d

Browse files
more expressive tests for derive/underive
Signed-off-by: Stuart Halloway <[email protected]>
1 parent b578c69 commit 9f9f44d

File tree

1 file changed

+157
-104
lines changed

1 file changed

+157
-104
lines changed

test/clojure/test_clojure/multimethods.clj

Lines changed: 157 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
; Author: Frantisek Sodomka, Robert Lachlan
1010

1111
(ns clojure.test-clojure.multimethods
12-
(:use clojure.test))
12+
(:use clojure.test)
13+
(:require [clojure.set :as set]))
1314

1415
; http://clojure.org/multimethods
1516

@@ -20,107 +21,159 @@
2021
; methods
2122
; prefers
2223

23-
24-
;hierarchies for tests below, generated and literal
25-
(def h1 (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
26-
[[:p1 :a1] [:p1 :a2] [:p2 :a2] [:c :p2] [:c :p1]]))
27-
(def h2 (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
28-
[[:p1 :a1] [:p1 :a2] [:p2 :a2] [:c :p2]]))
29-
(def h3 (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
30-
[[:p1 :a1] [:p2 :a2] [:c :p2] [:c :p1]]))
31-
(def h4 {:parents {:x8 #{:x6 :x7}, :x7 #{:x5}, :x6 #{:x5}, :x5 #{:x4},
32-
:x4 #{:x3 :x2}, :x3 #{:x1}, :x2 #{:x1}},
33-
:ancestors {:x8 #{:x4 :x5 :x6 :x7 :x3 :x2 :x1},
34-
:x7 #{:x4 :x5 :x3 :x2 :x1}, :x6 #{:x4 :x5 :x3 :x2 :x1},
35-
:x5 #{:x4 :x3 :x2 :x1}, :x4 #{:x3 :x2 :x1}, :x3 #{:x1},
36-
:x2 #{:x1}},
37-
:descendants {:x7 #{:x8}, :x6 #{:x8}, :x5 #{:x8 :x6 :x7},
38-
:x4 #{:x8 :x5 :x6 :x7}, :x3 #{:x8 :x4 :x5 :x6 :x7},
39-
:x2 #{:x8 :x4 :x5 :x6 :x7},
40-
:x1 #{:x8 :x4 :x5 :x6 :x7 :x3 :x2}}})
41-
(def h5 {:parents {:x2 #{:x1}, :x3 #{:x1}, :x4 #{:x3 :x2}, :x6 #{:x5},
42-
:x7 #{:x5}, :x8 #{:x6 :x7}},
43-
:ancestors {:x2 #{:x1}, :x3 #{:x1}, :x4 #{:x3 :x2 :x1}, :x6 #{:x5},
44-
:x7 #{:x5}, :x8 #{:x5 :x6 :x7}},
45-
:descendants {:x1 #{:x4 :x3 :x2}, :x2 #{:x4}, :x3 #{:x4},
46-
:x5 #{:x8 :x6 :x7}, :x7 #{:x8}, :x6 #{:x8}}})
47-
(def h6 {:parents {:a #{:b}}, :ancestors {:a #{:b}}, :descendants {:b #{:a}}})
48-
(def h7 {:parents {java.util.Map #{::maps}},
49-
:ancestors {java.util.Map #{::maps}},
50-
:descendants {::maps #{java.util.Map}}})
51-
52-
53-
; derive, [underive]
54-
(deftest derive-test
55-
(is (= (derive h5 :x5 :x4) h4))
56-
(is (= (derive (make-hierarchy) :a :b) h6))
57-
(is (= (derive (make-hierarchy) java.util.Map ::maps) h7)))
58-
59-
60-
61-
(deftest underive-test
62-
(is (= (underive (make-hierarchy) :x :y) (make-hierarchy)))
63-
(is (= (underive (derive (make-hierarchy) ::a ::b) ::a ::b)
64-
(make-hierarchy)))
65-
(is (= (underive h1 :c :p1) h2))
66-
(is (= (underive h1 :p1 :a2) h3))
67-
(is (= (underive h4 :x5 :x4) h5))
68-
(is (= (underive h5 :x5 :x4) h5))
69-
(is (= (underive h4 :x8 :x1) h4))
70-
(is (= (underive h4 :x9 :x4) h4))
71-
(is (= (underive h4 :x5 :x10) h4))
72-
(is (= (underive h7 java.util.Map ::maps) (make-hierarchy)))
73-
(is (= (underive h7 java.util.HashMap ::maps) h7)))
74-
75-
76-
77-
; isa?
78-
(deftest isa-test
79-
(is (isa? h4 :x5 :x4))
80-
(is (not (isa? h5 :x5 :x4)))
81-
(is (isa? h4 :x8 :x1))
82-
(is (not (isa? h5 :x8 :x1)))
83-
(is (isa? java.util.HashMap java.util.Map))
84-
(is (isa? h7 java.util.Map ::maps))
85-
(is (not (isa? (make-hierarchy) java.util.Map ::a))))
86-
87-
88-
89-
; parents, ancestors, descendants
90-
(deftest family-relation
91-
(is (= (parents h4 :x1) nil))
92-
(is (= (parents h4 :x4) #{:x2 :x3}))
93-
(is (= (ancestors h5 :x1) nil))
94-
(is (= (ancestors h4 :x4) #{:x1 :x2 :x3}))
95-
(is (= (descendants h4 :y) nil))
96-
(is (= (descendants h5 :x5) #{:x6 :x7 :x8})))
97-
98-
; some simple global hierarchy tests
99-
100-
(derive ::y1 ::y2)
101-
(derive ::y3 ::y4)
102-
103-
(deftest global-isa1
104-
(derive ::y4 ::y1)
105-
(is (isa? ::y1 ::y2))
106-
(not (isa? ::y3 ::y2)))
107-
108-
109-
(derive java.util.HashMap ::y4)
110-
111-
(deftest global-isa2
112-
(is (isa? ::y3 ::y2))
113-
(is (isa? java.util.HashMap ::y2)))
114-
115-
116-
(deftest global-underive
117-
(derive ::y4 ::y1)
118-
(underive ::y4 ::y1)
119-
(is (not (isa? ::y3 ::y1)))
120-
(is (not (isa? java.util.HashMap ::y2))))
121-
122-
123-
; make-hierarchy
124-
(deftest make-hierarchy-test
125-
(is (= {:parents {} :descendants {} :ancestors {}} (make-hierarchy))))
24+
(defn set-var-roots
25+
[maplike]
26+
(doseq [[var val] maplike]
27+
(alter-var-root var (fn [_] val))))
28+
29+
(defn with-var-roots*
30+
"Temporarily set var roots, run block, then put original roots back."
31+
[root-map f & args]
32+
(let [originals (doall (map (fn [[var _]] [var @var]) root-map))]
33+
(set-var-roots root-map)
34+
(try
35+
(apply f args)
36+
(finally
37+
(set-var-roots originals)))))
38+
39+
(defmacro with-var-roots
40+
[root-map & body]
41+
`(with-var-roots* ~root-map (fn [] ~@body)))
42+
43+
(defmacro for-all
44+
[& args]
45+
`(dorun (for ~@args)))
46+
47+
(defn hierarchy-tags
48+
"Return all tags in a derivation hierarchy"
49+
[h]
50+
(set/select
51+
#(instance? clojure.lang.Named %)
52+
(reduce into #{} (map keys (vals h)))))
53+
54+
(defn transitive-closure
55+
"Return all objects reachable by calling f starting with o,
56+
not including o itself. f should return a collection."
57+
[o f]
58+
(loop [results #{}
59+
more #{o}]
60+
(let [new-objects (set/difference more results)]
61+
(if (seq new-objects)
62+
(recur (set/union results more) (reduce into #{} (map f new-objects)))
63+
(disj results o)))))
64+
65+
(defn tag-descendants
66+
"Set of descedants which are tags (i.e. Named)."
67+
[& args]
68+
(set/select
69+
#(instance? clojure.lang.Named %)
70+
(or (apply descendants args) #{})))
71+
72+
(defn assert-valid-hierarchy
73+
[h]
74+
(let [tags (hierarchy-tags h)]
75+
(testing "ancestors are the transitive closure of parents"
76+
(for-all [tag tags]
77+
(is (= (transitive-closure tag #(parents h %))
78+
(or (ancestors h tag) #{})))))
79+
(testing "ancestors are transitive"
80+
(for-all [tag tags]
81+
(is (= (transitive-closure tag #(ancestors h %))
82+
(or (ancestors h tag) #{})))))
83+
(testing "tag descendants are transitive"
84+
(for-all [tag tags]
85+
(is (= (transitive-closure tag #(tag-descendants h %))
86+
(or (tag-descendants h tag) #{})))))
87+
(testing "a tag isa? all of its parents"
88+
(for-all [tag tags
89+
:let [parents (parents h tag)]
90+
parent parents]
91+
(is (isa? h tag parent))))
92+
(testing "a tag isa? all of its ancestors"
93+
(for-all [tag tags
94+
:let [ancestors (ancestors h tag)]
95+
ancestor ancestors]
96+
(is (isa? h tag ancestor))))
97+
(testing "all my descendants have me as an ancestor"
98+
(for-all [tag tags
99+
:let [descendants (descendants h tag)]
100+
descendant descendants]
101+
(is (isa? h descendant tag))))
102+
(testing "there are no cycles in parents"
103+
(for-all [tag tags]
104+
(is (not (contains? (transitive-closure tag #(parents h %)) tag)))))
105+
(testing "there are no cycles in descendants"
106+
(for-all [tag tags]
107+
(is (not (contains? (descendants h tag) tag)))))))
108+
109+
(def family
110+
(reduce #(apply derive (cons %1 %2)) (make-hierarchy)
111+
[[::parent-1 ::ancestor-1]
112+
[::parent-1 ::ancestor-2]
113+
[::parent-2 ::ancestor-2]
114+
[::child ::parent-2]
115+
[::child ::parent-1]]))
116+
117+
(deftest cycles-are-forbidden
118+
(testing "a tag cannot be its own parent"
119+
(is (thrown-with-msg? Throwable #"\(not= tag parent\)"
120+
(derive family ::child ::child))))
121+
(testing "a tag cannot be its own ancestor"
122+
(is (thrown-with-msg? Throwable #"Cyclic derivation: :clojure.test-clojure.multimethods/child has :clojure.test-clojure.multimethods/ancestor-1 as ancestor"
123+
(derive family ::ancestor-1 ::child)))))
124+
125+
(deftest using-diamond-inheritance
126+
(let [diamond (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
127+
[[::mammal ::animal]
128+
[::bird ::animal]
129+
[::griffin ::mammal]
130+
[::griffin ::bird]])
131+
bird-no-more (underive diamond ::griffin ::bird)]
132+
(assert-valid-hierarchy diamond)
133+
(assert-valid-hierarchy bird-no-more)
134+
(testing "a griffin is a mammal, indirectly through mammal and bird"
135+
(is (isa? diamond ::griffin ::animal)))
136+
(testing "a griffin is a bird"
137+
(is (isa? diamond ::griffin ::bird)))
138+
(testing "after underive, griffin is no longer a bird"
139+
(is (not (isa? bird-no-more ::griffin ::bird))))
140+
(testing "but it is still an animal, via mammal"
141+
(is (isa? bird-no-more ::griffin ::animal)))))
142+
143+
(deftest derivation-world-bridges-to-java-inheritance
144+
(let [h (derive (make-hierarchy) java.util.Map ::map)]
145+
(testing "a Java class can be isa? a tag"
146+
(is (isa? h java.util.Map ::map)))
147+
(testing "if a Java class isa? a tag, so are its subclasses..."
148+
(is (isa? h java.util.HashMap ::map)))
149+
(testing "...but not its superclasses!"
150+
(is (not (isa? h java.util.Collection ::map))))))
151+
152+
(deftest global-hierarchy-test
153+
(with-var-roots {#'clojure.core/global-hierarchy (make-hierarchy)}
154+
(assert-valid-hierarchy @#'clojure.core/global-hierarchy)
155+
(testing "when you add some derivations..."
156+
(derive ::lion ::cat)
157+
(derive ::manx ::cat)
158+
(assert-valid-hierarchy @#'clojure.core/global-hierarchy))
159+
(testing "...isa? sees the derivations"
160+
(is (isa? ::lion ::cat))
161+
(is (not (isa? ::cat ::lion))))
162+
(testing "... you can traverse the derivations"
163+
(is (= #{::manx ::lion} (descendants ::cat)))
164+
(is (= #{::cat} (parents ::manx)))
165+
(is (= #{::cat} (ancestors ::manx))))
166+
(testing "then, remove a derivation..."
167+
(underive ::manx ::cat))
168+
(testing "... traversals update accordingly"
169+
(is (= #{::lion} (descendants ::cat)))
170+
(is (nil? (parents ::manx)))
171+
(is (nil? (ancestors ::manx))))))
172+
173+
#_(defmacro for-all
174+
"Better than the actual for-all, if only it worked."
175+
[& args]
176+
`(reduce
177+
#(and %1 %2)
178+
(map true? (for ~@args))))
126179

0 commit comments

Comments
 (0)