diff options
author | Carlo Zancanaro <carlo@zancanaro.id.au> | 2021-02-23 13:33:02 +1100 |
---|---|---|
committer | Carlo Zancanaro <carlo@zancanaro.id.au> | 2021-02-23 23:24:38 +1100 |
commit | 11919c0ebde7c071c5d126cef3d04775d8b450fe (patch) | |
tree | 2da90e651130a5cefa060472e30655b2a2a8634f /test |
Initial commit
Diffstat (limited to 'test')
-rw-r--r-- | test/jester/types_test.clj | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/test/jester/types_test.clj b/test/jester/types_test.clj new file mode 100644 index 0000000..84b28fe --- /dev/null +++ b/test/jester/types_test.clj @@ -0,0 +1,281 @@ +(ns jester.types-test + (:require [clojure.test :refer [deftest testing is are]] + [clojure.test.check :refer [quick-check]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :refer [for-all]] + [jester.types :as sut])) + +(def string-enum-gen (gen/fmap #(cons 'enum %) (gen/vector gen/string))) +(def number-enum-gen (gen/fmap #(cons 'enum %) (gen/vector gen/double))) +(def boolean-enum-gen (gen/fmap #(cons 'enum %) (gen/vector gen/boolean))) +(def enum-gen (gen/one-of [string-enum-gen + number-enum-gen + boolean-enum-gen])) + +(def atomic-type-gen + (gen/one-of [(gen/return 'any) + (gen/return 'none) + (gen/return 'string) + (gen/return 'number) + (gen/return 'boolean) + enum-gen])) + +(defn optional-type-gen [subgen] + (gen/fmap #(list 'optional %) subgen)) + +(defn list-type-gen [subgen] + (gen/fmap #(list 'list %) subgen)) + +(defn array-type-gen [subgen] + (gen/recursive-gen (fn [gen] + (gen/fmap (fn [[main rest]] + (into main (when rest ['& rest]))) + (gen/tuple (gen/vector gen) + (gen/one-of + [(gen/return nil) + (array-type-gen gen) + (list-type-gen gen)])))) + subgen)) + +(defn map-type-gen [keygen valgen] + (gen/fmap #(cons 'map %) (gen/tuple keygen valgen))) + +(defn func-type-gen [argsgen resultgen] + (gen/fmap #(cons '-> %) (gen/tuple argsgen resultgen))) + +(defn record-type-gen [subgen] + (gen/map gen/symbol subgen)) + +(defn compound-type-gen [subgen] + (gen/one-of [(optional-type-gen subgen) + (list-type-gen subgen) + (array-type-gen subgen) + (map-type-gen subgen subgen) + (func-type-gen (array-type-gen subgen) subgen) + (record-type-gen subgen)])) + +(gen/sample (compound-type-gen atomic-type-gen)) + +(deftest assert-type + (testing "happy paths" + (are [object type] + (nil? (sut/assert-type 'object 'type)) + + nil any + "a" any + 10 any + true any + + "b" string + 12 number + false boolean + + "b" (enum "b") + 14 (enum 14) + true (enum true) + + nil (optional string) + "a" (optional string) + + nil (optional number) + 16 (optional number) + + nil (optional boolean) + false (optional boolean) + + ["a" "b"] [string string] + ["a" false] [string boolean] + ["a" false] [string & (list boolean)] + + [true false] (list boolean) + [10 20] (list number) + + {"a" 30} (map string number) + {a 30} {a number})) + + (testing "failure cases" + (are [object type msg] + (thrown-with-msg? clojure.lang.ExceptionInfo msg (sut/assert-type 'object 'type)) + + nil none #"No value can satisfy none" + "a" none #"No value can satisfy none" + 10 none #"No value can satisfy none" + true none #"No value can satisfy none" + + "b" number #"Expected a number, but got \"b\"" + 12 boolean #"Expected a boolean, but got 12" + true string #"Expected a string, but got true" + + "b" (enum "a") #"Expected \"a\", but got \"b\"" + 2 (enum 1) #"Expected 1, but got 2" + false (enum true) #"Expected true, but got false" + + "b" (enum "a" "c" "d") #"Expected \"a\", \"c\", or \"d\", but got \"b\"" + 2 (enum 1 3) #"Expected 1 or 3, but got 2" + + "a" (optional number) #"Expected a number, but got \"a\"" + + ["a" "b"] [string number] #"Expected a number, but got \"b\"" + ["a" "b"] [string & (list number)] #"Expected a number, but got \"b\"" + ["a" "b"] [string] #"Expected at most 1 item, but got 2" + ["a" "b" "c"] [string string] #"Expected at most 2 items, but got 3" + [] [string] #"Expected at least 1 item, but got 0" + ["a"] [string string] #"Expected at least 2 items, but got 1" + + ["a" 2] (list string) #"Expected a string, but got 2" + + {"a" 20} (map number number) #"Expected a number, but got \"a\"" + {"a" 20} (map string string) #"Expected a string, but got 20" + + {a 10} {a string} #"Expected a string, but got 10"))) + + +(defn ^:privat grounded-bounds + "Return the current [lower upper] bounds of `type`." + [type] + [(sut/ground-type type :argument) + (sut/ground-type type :return)]) + +(deftest any-type + (testing 'assert-type + (are [object] (nil? (sut/assert-type 'object 'any)) + nil + 1 + "a" + true + [1] + {a "a"} + {"a" "b"})) + (testing 'subtype? + (are [type] (sut/subtype? 'type 'any) + none + string + number + boolean + (list string) + (map number boolean) + (-> [string] number) + {a number}) + (are [type] (not (sut/subtype? 'any 'type)) + none + string + number + boolean + (list string) + (map number boolean) + (-> [string] number) + {a number})) + (testing '(constrain ground) + (sut/in-constraint-environment + (sut/constrain '(var n) 'any) + (is (= ['any 'any] (grounded-bounds '(var n))))) + (sut/in-constraint-environment + (sut/constrain 'any '(var n)) + (is (= ['any 'any] (grounded-bounds '(var n))))) + (sut/in-constraint-environment + (is (= ['any 'any] (grounded-bounds '(var n))))))) + +(deftest enum-type + (testing 'assert-type + (are [object type] (nil? (sut/assert-type 'object 'type)) + "a" (enum "a") + "a" (enum "a" "b") + 1 (enum 1) + 1 (enum 1 2) + true (enum true) + true (enum false true)) + (are [object type msg] + (thrown-with-msg? clojure.lang.ExceptionInfo msg + (sut/assert-type 'object 'type)) + "a" (enum "b" "c" "d") #"Expected \"b\", \"c\", or \"d\", but got \"a\"" + 1 (enum 2 3) #"Expected 2 or 3, but got 1" + true (enum false) #"Expected false, but got true" + nil (enum 1) #"Expected 1, but got nil" + [] (enum 1) #"Expected 1, but got \[\]" + {} (enum 1) #"Expected 1, but got \{\}")) + (testing 'subtype? + (are [sub super] (sut/subtype? 'sub 'super) + none (enum) + (enum) any + (enum "a") (enum "a") + (enum "a") (enum "a" "b") + (enum 1) (enum 1) + (enum 1) (enum 1 2) + (enum true) (enum true) + (enum true) (enum true false) + (enum) (enum "a")) + (are [sub super] (not (sut/subtype? 'sub 'super)) + (enum "a") (enum "b") + (enum 1) (enum 2) + (enum true) (enum false) + (enum "a") (enum))) + (testing '(constrain ground) + (sut/in-constraint-environment + (sut/constrain '(var a) '(enum "a" "b" "c")) + (sut/constrain '(var a) '(enum "a")) + (is (= '[(enum "a") any] (grounded-bounds '(var a))))) + (sut/in-constraint-environment + (sut/constrain '(enum "a" "b" "c") '(var a)) + (sut/constrain '(enum "a") '(var a)) + (is (= '[(optional string) (enum "b" "c" "a")] (grounded-bounds '(var a))))) + (sut/in-constraint-environment + (sut/constrain '(var a) '(enum "a" "b" "c")) + (sut/constrain '(var a) '(enum "x")) + (is (= '[none any] (grounded-bounds '(var a))))))) + +(deftest subtype? + (testing "when there is a subtyping relationship" + (are [subtype supertype] + (is (sut/subtype? 'subtype 'supertype)) + string any + none string + + string string + number number + boolean boolean + (enum "a") (enum "a") + (enum 1) (enum 1) + (enum 2) (enum 2) + + (enum "a") string + (enum 1) number + (enum false) boolean + + string (optional string) + (enum "a") (optional string) + number (optional number) + (enum 1) (optional number) + boolean (optional boolean) + (enum true) (optional boolean) + + (optional string) (optional string) + (optional (enum "a")) (optional string) + (optional number) (optional number) + (optional (enum 1)) (optional number) + (optional boolean) (optional boolean) + (optional (enum true)) (optional boolean) + + [string] [string] + [string string] [string string] + [string] [string & (list string)] + [string string] [string & (list string)] + [number string] [number & (list string)] + [number & (list string)] [number & (list string)] + [number string & (list string)] [number & (list string)] + (map string number) (map string number) + (map string (enum 1)) (map (enum "a") number) + + (-> [string number] boolean) (-> [string number] boolean) + (-> [string & (list number)] (enum true)) (-> [string number] boolean) + + {a string} {a string} + {a string, b string} {a string} + {a string, b string} {a (optional string)})) + (testing "when there isn't a subtyping relationship" + )) + +(deftest failing-case + (sut/in-constraint-environment + (sut/constrain '[(var a) & (var n)] '[(enum 1) & [(enum 3) (enum 4)]]) + (is (= '[(enum 1) any] (grounded-bounds '(var a)))) + (is (= '[[(enum 3) (enum 4)] any] (grounded-bounds '(var n)))))) |