diff --git a/src/speculative/instrument.cljc b/src/speculative/instrument.cljc index ed0541f..600143b 100644 --- a/src/speculative/instrument.cljc +++ b/src/speculative/instrument.cljc @@ -3,6 +3,7 @@ instrument and unstruments specs provided by speculative. Alpha, subject to change." (:require + [clojure.set :as set] [clojure.string :as str] [speculative.core] [speculative.set] @@ -45,7 +46,20 @@ ;; clojure.string str/starts-with? str/ends-with? - ]) + + ;; clojure.set + set/union + set/intersection + set/difference + set/select + set/project + set/rename-keys + set/rename + set/index + set/map-invert + set/join + set/subset? + set/superset?]) (deftime diff --git a/src/speculative/set.cljc b/src/speculative/set.cljc index ef87317..850d6c6 100644 --- a/src/speculative/set.cljc +++ b/src/speculative/set.cljc @@ -1,4 +1,102 @@ (ns speculative.set - (:require [speculative.specs :as ss])) + (:require [clojure.set :as set] + [clojure.spec.alpha :as s] + [speculative.specs :as ss])) -;; TODO +(s/def ::nilable-set + (s/nilable ::ss/set)) + +(s/def ::nilable-map + (s/nilable ::ss/map)) + +(s/def ::rel + (s/nilable (s/coll-of ::nilable-map))) + +(s/def ::rel* + (s/nilable (s/coll-of ::ss/seqable-of-map-entry))) + +(s/def ::seqable-of-pairs + (s/coll-of (s/or :map-entry ::ss/map-entry + :pair (s/coll-of ::ss/any :count 2)) + :kind seqable?)) + +(s/def ::nullary + (s/cat)) + +(s/def ::unary + (s/cat :s1 ::nilable-set)) + +(s/def ::binary + (s/cat :s1 ::nilable-set + :s2 ::nilable-set)) + +(s/def ::variadic + (s/cat :s1 ::nilable-set + :s2 ::nilable-set + :sets (s/* ::nilable-set))) + +(s/fdef set/union + :args (s/alt :nullary ::nullary + :unary ::unary + :binary ::binary + :variadic ::variadic) + :ret ::nilable-set) + +(s/fdef set/intersection + :args (s/alt :unary ::unary + :binary ::binary + :variadic ::variadic) + :ret ::nilable-set) + +(s/fdef set/difference + :args (s/alt :unary ::unary + :binary ::binary + :variadic ::variadic) + :ret ::nilable-set) + +(s/fdef set/select + :args (s/cat :pred ::ss/predicate + :xset ::nilable-set) + :ret ::nilable-set) + +(s/fdef set/project + :args (s/cat :xrel ::rel* + :ks ::ss/sequential) + :ret ::ss/set) + +(s/fdef set/rename-keys + :args (s/cat :map ::nilable-map + :kmap ::nilable-map) + :ret ::nilable-map) + +(s/fdef set/rename + :args (s/cat :xrel ::rel + :kmap ::nilable-map) + :ret ::ss/set) + +(s/fdef set/index + :args (s/cat :xrel ::rel* + :ks ::ss/sequential) + :ret ::ss/map) + +(s/fdef set/map-invert + :args (s/cat :m (s/nilable ::seqable-of-pairs)) + :ret ::ss/map) + +(s/fdef set/join + :args (s/alt :binary (s/cat :xrel ::rel + :yrel ::rel*) + :ternary (s/cat :xrel ::rel + :yrel ::rel* + :km ::nilable-map)) + :ret ::ss/set) + +(s/fdef set/subset? + :args (s/cat :set1 ::nilable-set + :set2 ::nilable-set) + :ret ::ss/boolean) + +(s/fdef set/superset? + :args (s/cat :set1 ::nilable-set + :set2 ::nilable-set) + :ret ::ss/boolean) diff --git a/src/speculative/specs.cljc b/src/speculative/specs.cljc index 44e6a30..ffe671e 100644 --- a/src/speculative/specs.cljc +++ b/src/speculative/specs.cljc @@ -38,10 +38,12 @@ (fn [] (gen/fmap first (s/gen (s/and ::map seq)))))) +(s/def ::set set?) (s/def ::nil nil?) (s/def ::number number?) (s/def ::reducible reducible?) (s/def ::seqable seqable?) +(s/def ::sequential sequential?) (s/def ::some some?) (s/def ::string string?) #?(:clj (s/def ::char-sequence diff --git a/test/speculative/core_test.cljc b/test/speculative/core_test.cljc index ed52b4c..d97ba56 100644 --- a/test/speculative/core_test.cljc +++ b/test/speculative/core_test.cljc @@ -18,7 +18,7 @@ (deftest instrument-test (testing "speculative specs should be instrumentable and unstrumentable" - (let [spec-count #?(:clj 29 :cljs 26) + (let [spec-count #?(:clj 41 :cljs 38) instrumented (speculative.instrument/instrument) unstrumented (speculative.instrument/unstrument)] (is (= spec-count (count instrumented))) diff --git a/test/speculative/set_test.cljc b/test/speculative/set_test.cljc new file mode 100644 index 0000000..842f9e8 --- /dev/null +++ b/test/speculative/set_test.cljc @@ -0,0 +1,164 @@ +(ns speculative.set-test + (:require [clojure.set :as set] + [clojure.test :refer [are deftest is]] + [speculative.set] + [speculative.test :refer [with-instrumentation + with-unstrumentation + throws + check-call]])) + +(deftest union + (is (= #{1 2 3} (check-call `set/union [#{1 2} #{3}]))) + (is (= #{1 2} (check-call `set/union [#{1 2} nil]))) + (is (= nil (check-call `set/union [nil nil]))) + (with-instrumentation `set/union + (throws `set/union (set/union #{1 2} [3])) + (throws `set/union (set/union [1 2] #{3})) + (throws `set/union (set/union {1 2} #{3})))) + +(deftest intersection + (is (= #{1} (check-call `set/intersection [#{1 2} #{1}]))) + (is (= nil (check-call `set/intersection [nil #{1}]))) + (is (= nil (check-call `set/intersection [nil nil]))) + (with-instrumentation `set/intersection + (throws `set/intersection (set/intersection #{1 2} [1])) + (throws `set/intersection (set/intersection [1 2] #{1})) + (throws `set/intersection (set/intersection {1 2} #{1})))) + +(deftest difference + (is (= #{2} (check-call `set/difference [#{1 2} #{1}]))) + (is (= nil (check-call `set/difference [nil #{1}]))) + (is (= #{1 2} (check-call `set/difference [#{1 2} nil]))) + (is (= nil (check-call `set/difference [nil nil]))) + (with-instrumentation `set/difference + (throws `set/difference (set/difference #{1 2} [1])) + (throws `set/difference (set/difference [1 2] #{1})) + (throws `set/difference (set/difference {1 2} [1])))) + +(deftest select + (is (= #{1 2} (check-call `set/select [int? #{1 2 :a}]))) + (is (= nil (check-call `set/select [int? nil]))) + (with-instrumentation `set/select + (throws `set/select (set/select int? [1 2 :a])) + (throws `set/select (set/select int? {:a 1})))) + +(deftest project + (is (= #{{:a 1}} (check-call `set/project [[{:a 1 :b 2}] [:a]]))) + (is (= #{{:a 1}} (check-call `set/project [[{:a 1 :b 2}] '(:a)]))) + #?(:clj (is (= #{{:a 1}} (check-call `set/project [#{(java.util.HashMap. {:a 1 :b 2})} [:a]])))) + (is (= #{{:a 1}} (check-call `set/project [#{{:a 1 :b 2}} [:a]]))) + (is (= #{} (check-call `set/project [nil [:a]]))) + (is (= #{{}} (check-call `set/project [#{nil} [:a]]))) + (with-instrumentation `set/project + (throws `set/project (set/project nil nil)) + (throws `set/project (set/project #{[:a 1 :b 2]} {:a nil})) + (throws `set/project (set/project {:a 1 :b 2} [:a])))) + +(deftest rename-keys + (is (= {:c 1 :d 2} (check-call `set/rename-keys [{:a 1 :b 2} {:a :c :b :d}]))) + (is (= nil (check-call `set/rename-keys [nil {:a :c :b :d}]))) + (is (= {:a 1 :b 2} (check-call `set/rename-keys [{:a 1 :b 2} nil]))) + (with-instrumentation `set/rename-keys + (throws `set/rename-keys (set/rename-keys [:a 1 :b 2] {:a :c :b :d})) + (throws `set/rename-keys (set/rename-keys {:a 1 :b 2} [:a :c :b :d])) + #?(:clj (throws `set/rename-keys (set/rename-keys (java.util.HashMap. {:a 1 :b 2}) {:a :c :b :d}))))) + +(deftest rename + (is (= #{{:b 1 :c 1} {:b 2 :c 2}} (check-call `set/rename [#{{:a 1 :b 1} {:a 2 :b 2}} {:a :c}]))) + (is (= #{{:b 1 :c 1} {:b 2 :c 2}} (check-call `set/rename [[{:a 1 :b 1} {:a 2 :b 2}] {:a :c}]))) + (is (= #{} (check-call `set/rename [nil {:a :c}]))) + (is (= #{nil} (check-call `set/rename [[nil] {:a :c}]))) + (is (= #{} (check-call `set/rename [nil nil]))) + (is (= #{nil} (check-call `set/rename [[nil] nil]))) + (with-instrumentation `set/rename + (throws `set/rename (set/rename [[:a 1 :b 1]] {:a :c})) + (throws `set/rename (set/rename [{:a 1 :b 1}] [:a :c])) + #?(:clj (throws `set/rename (set/rename [(java.util.HashMap. {:a 1 :b 1})] [:a :c]))))) + +(deftest index + (is (= {{:a 1} #{{:a 1 :b 2}}} (check-call `set/index [#{{:a 1 :b 2}} [:a]]))) + (is (= {{:a 1} #{{:a 1 :b 2}}} (check-call `set/index [#{{:a 1 :b 2}} '(:a)]))) + (is (= {{:a 1} #{{:a 1 :b 2}}} (check-call `set/index [[{:a 1 :b 2}] [:a]]))) + #?(:clj (is (= {{:a 1} #{(java.util.HashMap. {:a 1 :b 2})}} + (check-call `set/index [#{(java.util.HashMap. {:a 1 :b 2})} [:a]])))) + (is (= {} (check-call `set/index [nil [:a]]))) + (is (= {{} #{nil}} (check-call `set/index [[nil] [:a]]))) + (with-instrumentation `set/index + (throws `set/index (set/index [[:a 1 :b 2]] [:a])) + (throws `set/index (set/index [{:a 1 :b 2}] :a)))) + +(deftest map-invert + (is (= {:b :a} (check-call `set/map-invert [{:a :b}]))) + (is (= {1 :a 2 :b} (check-call `set/map-invert ['([:a 1] [:b 2])]))) + (is (= {1 :a 2 :b} (check-call `set/map-invert [[[:a 1] [:b 2]]]))) + (is (= {} (check-call `set/map-invert [nil]))) + #?(:clj (is (= {1 :a 2 :b} (check-call `set/map-invert [(java.util.HashMap. {:a 1 :b 2})])))) + (with-instrumentation `set/map-invert + (throws `set/map-invert (set/map-invert [[:a :b :c]])) + (throws `set/map-invert (set/map-invert [:a :b])) + (throws `set/map-invert (set/map-invert #{:a :b})))) + +(deftest join + (are [x y] (= x y) + #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [#{{:a 1} {:a 2}} #{{:a 1 :b 1} {:a 2 :b 2}}]) + + #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [#{{:a 1} {:a 2}} [{:a 1 :b 1} {:a 2 :b 2}]]) + + #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [[{:a 1} {:a 2}] #{{:a 1 :b 1} {:a 2 :b 2}}]) + + #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [[{:a 1} {:a 2}] [{:a 1 :b 1} {:a 2 :b 2}]]) + + #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [#{{:a 1} {:a 2}} #{{:b 1} {:b 2}} {:a :b}]) + + #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [#{{:a 1} {:a 2}} [{:b 1} {:b 2}] {:a :b}]) + + #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [[{:a 1} {:a 2}] #{{:b 1} {:b 2}} {:a :b}]) + + #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [[{:a 1} {:a 2}] [{:b 1} {:b 2}] {:a :b}]) + + #{} + (check-call `set/join [nil [{:b 1} {:b 2}] {:a :b}]) + + #{} + (check-call `set/join [[{:a 1} {:a 2}] nil {:a :b}]) + + #{} + (check-call `set/join [[{:a 1} {:a 2}] nil nil]) + + #{nil} + (check-call `set/join [[nil] [nil] nil])) + + #?(:clj (is (= #{{:a 1 :b 1} {:a 2 :b 2}} + (check-call `set/join [[{:a 1} {:a 2}] [(java.util.HashMap. {:b 1}) {:b 2}] {:a :b}])))) + + (with-instrumentation `set/join + (throws `set/join (set/join [{:a 1}] [{:b 2}] [:a :b])) + (throws `set/join (set/join [[:a 1]] [{:b 2}] {:a :b})) + (throws `set/join (set/join [{:a 1}] [[:b 2]] {:a :b})) + #?(:clj (throws `set/join (set/join [(java.util.HashMap. {:a 1})] [{:b 2}] {:a :b}))))) + +(deftest subset? + (is (true? (check-call `set/subset? [#{:a} #{:a :b}]))) + (is (true? (check-call `set/subset? [nil #{:a :b}]))) + (is (false? (check-call `set/subset? [#{:a} nil]))) + (with-instrumentation `set/subset? + (throws `set/subset? (set/subset? #{:a} [:a :b])) + (throws `set/subset? (set/subset? [:a] #{:a :b})) + (throws `set/subset? (set/subset? [:a] [:a :b])))) + +(deftest superset? + (is (true? (check-call `set/superset? [#{:a :b} #{:a}]))) + (is (false? (check-call `set/superset? [nil #{:a}]))) + (is (true? (check-call `set/superset? [#{:a :b} nil]))) + (with-instrumentation `set/superset? + (throws `set/superset? (set/superset? [:a :b] #{:a})) + (throws `set/superset? (set/superset? #{:a :b} [:a])) + (throws `set/superset? (set/superset? [:a :b] [:a])))) diff --git a/test/speculative/test_runner.cljc b/test/speculative/test_runner.cljc index 55100cc..1d1089f 100644 --- a/test/speculative/test_runner.cljc +++ b/test/speculative/test_runner.cljc @@ -4,6 +4,7 @@ [clojure.test :as t :refer [run-tests]] [clojure.test] [speculative.core-test] + [speculative.set-test] [speculative.string-test] [speculative.test :refer [planck-env?]] [speculative.test-test])) @@ -52,6 +53,7 @@ (defn -main [& args] (run-tests 'speculative.test-test 'speculative.core-test - 'speculative.string-test)) + 'speculative.string-test + 'speculative.set-test)) #?(:cljs (set! *main-cli-fn* -main))