test(spec): collections contract (sequences, vectors, lists, maps, sets)
Add behavioral spec suites for the collection API (~180 cases). Writing them surfaced and fixed real bugs: - (count nil) errored; now 0 (Clojure semantics) - (repeat x) and (repeatedly f) — the infinite 1-arg arities — were unsupported; now return lazy infinite seqs - set equality used deep= (representation-sensitive), so a set of map literals could differ from an equal set built differently; now value-based (each element value-equal to some element of the other) Known limitation filed: phm-typed maps (from hash-map) used as set elements / map keys hash by identity (map *literals* are structs and work). jpm test green.
This commit is contained in:
parent
16428179fa
commit
b353d625f1
7 changed files with 303 additions and 15 deletions
28
test/spec/lists-spec.janet
Normal file
28
test/spec/lists-spec.janet
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Specification: lists (persistent singly-linked).
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "list / construct & predicate"
|
||||
["list" "[1 2 3]" "(list 1 2 3)"]
|
||||
["empty list" "[]" "(list)"]
|
||||
["quoted list" "[1 2 3]" "(quote (1 2 3))"]
|
||||
["list? true" "true" "(list? (list 1 2))"]
|
||||
["list? on conj result" "true" "(list? (conj (list 1) 0))"]
|
||||
["count" "3" "(count (list 1 2 3))"]
|
||||
["empty? true" "true" "(empty? (list))"]
|
||||
["list = vector elts" "true" "(= (list 1 2 3) [1 2 3])"])
|
||||
|
||||
(defspec "list / access & update"
|
||||
["first" "1" "(first (list 1 2 3))"]
|
||||
["rest" "[2 3]" "(rest (list 1 2 3))"]
|
||||
["peek is first" "1" "(peek (list 1 2 3))"]
|
||||
["pop drops first" "[2 3]" "(pop (list 1 2 3))"]
|
||||
["conj prepends" "[0 1 2]" "(conj (list 1 2) 0)"]
|
||||
["conj many prepends" "[4 3 1 2]" "(conj (list 1 2) 3 4)"]
|
||||
["cons prepends" "[0 1 2]" "(cons 0 (list 1 2))"]
|
||||
["nth" "20" "(nth (list 10 20 30) 1)"])
|
||||
|
||||
(defspec "list / immutability & performance"
|
||||
["conj does not mutate" "true" "(let [l (list 1 2 3) m (conj l 0)] (and (= l [1 2 3]) (= m [0 1 2 3])))"]
|
||||
["reduce conj builds" "[2 1 0]" "(reduce conj (list) (range 3))"]
|
||||
["O(1) conj at scale" "200000" "(count (reduce conj (list) (range 200000)))"]
|
||||
["scale head correct" "199999" "(first (reduce conj (list) (range 200000)))"])
|
||||
56
test/spec/maps-spec.janet
Normal file
56
test/spec/maps-spec.janet
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Specification: maps (associative).
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "map / construct & predicate"
|
||||
["literal" "{:a 1}" "{:a 1}"]
|
||||
["hash-map" "{:a 1, :b 2}" "(hash-map :a 1 :b 2)"]
|
||||
["empty" "{}" "{}"]
|
||||
["map? true" "true" "(map? {:a 1})"]
|
||||
["map? false on vector" "false" "(map? [1 2])"]
|
||||
["count" "2" "(count {:a 1 :b 2})"]
|
||||
["empty? true" "true" "(empty? {})"]
|
||||
["equality order-indep" "true" "(= {:a 1 :b 2} {:b 2 :a 1})"])
|
||||
|
||||
(defspec "map / access"
|
||||
["get" "1" "(get {:a 1} :a)"]
|
||||
["get missing nil" "nil" "(get {:a 1} :z)"]
|
||||
["get default" ":x" "(get {:a 1} :z :x)"]
|
||||
["keyword as fn" "1" "(:a {:a 1})"]
|
||||
["keyword fn default" ":x" "(:z {:a 1} :x)"]
|
||||
["map as fn" "1" "({:a 1} :a)"]
|
||||
["get-in" "2" "(get-in {:a {:b 2}} [:a :b])"]
|
||||
["get-in missing" "nil" "(get-in {:a {}} [:a :b])"]
|
||||
["contains? key" "true" "(contains? {:a 1} :a)"]
|
||||
["contains? missing" "false" "(contains? {:a 1} :z)"]
|
||||
["find returns entry" "[:a 1]" "(find {:a 1} :a)"]
|
||||
["keys" "true" "(= #{:a :b} (set (keys {:a 1 :b 2})))"]
|
||||
["vals" "true" "(= #{1 2} (set (vals {:a 1 :b 2})))"])
|
||||
|
||||
(defspec "map / update"
|
||||
["assoc adds" "{:a 1, :b 2}" "(assoc {:a 1} :b 2)"]
|
||||
["assoc overwrites" "{:a 9}" "(assoc {:a 1} :a 9)"]
|
||||
["assoc many" "{:a 1, :b 2}" "(assoc {} :a 1 :b 2)"]
|
||||
["dissoc" "{:a 1}" "(dissoc {:a 1 :b 2} :b)"]
|
||||
["dissoc many" "{:a 1}" "(dissoc {:a 1 :b 2 :c 3} :b :c)"]
|
||||
["merge" "{:a 1, :b 2}" "(merge {:a 1} {:b 2})"]
|
||||
["merge overwrites" "{:a 2}" "(merge {:a 1} {:a 2})"]
|
||||
["merge-with" "{:a 3}" "(merge-with + {:a 1} {:a 2})"]
|
||||
["update" "{:a 2}" "(update {:a 1} :a inc)"]
|
||||
["update missing w/ fnil" "{:a 1}" "(update {} :a (fnil inc 0))"]
|
||||
["update-in" "{:a {:b 2}}" "(update-in {:a {:b 1}} [:a :b] inc)"]
|
||||
["assoc-in" "{:a {:b 1}}" "(assoc-in {} [:a :b] 1)"]
|
||||
["select-keys" "{:a 1}" "(select-keys {:a 1 :b 2} [:a])"]
|
||||
["into onto map" "{:a 1, :b 2}" "(into {:a 1} [[:b 2]])"]
|
||||
["zipmap" "{:a 1, :b 2}" "(zipmap [:a :b] [1 2])"])
|
||||
|
||||
(defspec "map / iteration & entries"
|
||||
["map over entries" "true" "(= #{1 2} (set (map val {:a 1 :b 2})))"]
|
||||
["map keys" "true" "(= #{:a :b} (set (map key {:a 1 :b 2})))"]
|
||||
["reduce over entries" "6" "(reduce (fn [a e] (+ a (val e))) 0 {:a 1 :b 2 :c 3})"]
|
||||
["reduce-kv" "6" "(reduce-kv (fn [a k v] (+ a v)) 0 {:a 1 :b 2 :c 3})"]
|
||||
["destructure entry" "true" "(= [[:a 2]] (into [] (map (fn [[k v]] [k (inc v)]) {:a 1})))"]
|
||||
["first of map is entry" "true" "(let [e (first {:a 1})] (and (= (key e) :a) (= (val e) 1)))"]
|
||||
["map-entry?" "true" "(map-entry? (first {:a 1}))"]
|
||||
["count of nil map" "0" "(count nil)"]
|
||||
["get from nil" "nil" "(get nil :a)"]
|
||||
["immutability" "true" "(let [m {:a 1} n (assoc m :b 2)] (and (= m {:a 1}) (= n {:a 1 :b 2})))"])
|
||||
105
test/spec/sequences-spec.janet
Normal file
105
test/spec/sequences-spec.janet
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Specification: the sequence abstraction (clojure.core).
|
||||
# Sequential expecteds use vector literals — Jolt's `=` treats vectors and lists
|
||||
# with the same elements as equal, so [2 3 4] matches a (2 3 4) seq result.
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "seq / access"
|
||||
["first of vector" "1" "(first [1 2 3])"]
|
||||
["first of list" "1" "(first (list 1 2 3))"]
|
||||
["first of empty is nil" "nil" "(first [])"]
|
||||
["first of nil is nil" "nil" "(first nil)"]
|
||||
["second" "2" "(second [1 2 3])"]
|
||||
["last" "3" "(last [1 2 3])"]
|
||||
["rest of vector" "[2 3]" "(rest [1 2 3])"]
|
||||
["rest of single" "[]" "(rest [1])"]
|
||||
["rest of empty" "[]" "(rest [])"]
|
||||
["next of single is nil" "nil" "(next [1])"]
|
||||
["next of empty is nil" "nil" "(next [])"]
|
||||
["nth" "30" "(nth [10 20 30] 2)"]
|
||||
["nth with default" "99" "(nth [10] 5 99)"]
|
||||
["nth out of range" :throws "(nth [10] 5)"]
|
||||
["ffirst" "1" "(ffirst [[1 2] [3 4]])"]
|
||||
["fnext" "2" "(fnext [1 2 3])"]
|
||||
["nnext" "[3 4]" "(nnext [1 2 3 4])"])
|
||||
|
||||
(defspec "seq / construction"
|
||||
["cons onto list" "[0 1 2]" "(cons 0 (list 1 2))"]
|
||||
["cons onto vector" "[0 1 2]" "(cons 0 [1 2])"]
|
||||
["cons onto nil" "[0]" "(cons 0 nil)"]
|
||||
["conj vector appends" "[1 2 3]" "(conj [1 2] 3)"]
|
||||
["conj list prepends" "[0 1 2]" "(conj (list 1 2) 0)"]
|
||||
["conj multiple on vec" "[1 2 3 4]" "(conj [1 2] 3 4)"]
|
||||
["conj multiple on list" "[4 3 1 2]" "(conj (list 1 2) 3 4)"]
|
||||
["seq of empty is nil" "nil" "(seq [])"]
|
||||
["seq of nil is nil" "nil" "(seq nil)"]
|
||||
["seq of string" "[\\a \\b]" "(seq \"ab\")"]
|
||||
["empty?" "true" "(empty? [])"]
|
||||
["not empty?" "false" "(empty? [1])"]
|
||||
["count" "3" "(count [1 2 3])"]
|
||||
["count of nil" "0" "(count nil)"]
|
||||
["count of string" "3" "(count \"abc\")"])
|
||||
|
||||
(defspec "seq / map filter reduce"
|
||||
["map" "[2 3 4]" "(map inc [1 2 3])"]
|
||||
["map two colls" "[5 7 9]" "(map + [1 2 3] [4 5 6])"]
|
||||
["map stops at shortest" "[5 7]" "(map + [1 2] [4 5 6])"]
|
||||
["map-indexed" "[[0 :a] [1 :b]]" "(map-indexed vector [:a :b])"]
|
||||
["mapv" "[2 3 4]" "(mapv inc [1 2 3])"]
|
||||
["filter" "[2 4]" "(filter even? [1 2 3 4])"]
|
||||
["filterv" "[2 4]" "(filterv even? [1 2 3 4])"]
|
||||
["remove" "[1 3]" "(remove even? [1 2 3 4])"]
|
||||
["reduce" "10" "(reduce + [1 2 3 4])"]
|
||||
["reduce with init" "20" "(reduce + 10 [1 2 3 4])"]
|
||||
["reduce empty with init" "0" "(reduce + 0 [])"]
|
||||
["reduce single no init" "5" "(reduce + [5])"]
|
||||
["reduced short-circuits" "3" "(reduce (fn [a x] (if (> a 2) (reduced a) (+ a x))) 0 [1 2 3 4 5])"]
|
||||
["reduce-kv" "6" "(reduce-kv (fn [a k v] (+ a v)) 0 {:a 1 :b 2 :c 3})"]
|
||||
["reductions" "[1 3 6]" "(reductions + [1 2 3])"]
|
||||
["mapcat" "[1 1 2 2]" "(mapcat (fn [x] [x x]) [1 2])"]
|
||||
["keep" "[1 3]" "(keep (fn [x] (if (odd? x) x nil)) [1 2 3 4])"]
|
||||
["some truthy" "true" "(some even? [1 2 3])"]
|
||||
["some nil" "nil" "(some even? [1 3 5])"]
|
||||
["every? true" "true" "(every? pos? [1 2 3])"]
|
||||
["every? false" "false" "(every? pos? [1 -2 3])"])
|
||||
|
||||
(defspec "seq / take drop slice"
|
||||
["take" "[1 2 3]" "(take 3 [1 2 3 4 5])"]
|
||||
["take more than size" "[1 2]" "(take 5 [1 2])"]
|
||||
["drop" "[4 5]" "(drop 3 [1 2 3 4 5])"]
|
||||
["take-while" "[1 2]" "(take-while (fn [x] (< x 3)) [1 2 3 1])"]
|
||||
["drop-while" "[3 1]" "(drop-while (fn [x] (< x 3)) [1 2 3 1])"]
|
||||
["take-last" "[4 5]" "(take-last 2 [1 2 3 4 5])"]
|
||||
["drop-last" "[1 2 3]" "(drop-last [1 2 3 4])"]
|
||||
["take-nth" "[1 3 5]" "(take-nth 2 [1 2 3 4 5])"]
|
||||
["partition" "[[1 2] [3 4]]" "(partition 2 [1 2 3 4 5])"]
|
||||
["partition-all" "[[1 2] [3]]" "(partition-all 2 [1 2 3])"]
|
||||
["split-at" "[[1 2] [3 4]]" "(split-at 2 [1 2 3 4])"])
|
||||
|
||||
(defspec "seq / transform"
|
||||
["reverse" "[3 2 1]" "(reverse [1 2 3])"]
|
||||
["sort" "[1 2 3]" "(sort [3 1 2])"]
|
||||
["sort with comparator" "[3 2 1]" "(sort > [1 3 2])"]
|
||||
["sort-by" "[[1] [2 2]]" "(sort-by count [[2 2] [1]])"]
|
||||
["distinct" "[1 2 3]" "(distinct [1 1 2 3 3])"]
|
||||
["dedupe" "[1 2 1]" "(dedupe [1 1 2 1])"]
|
||||
["interpose" "[1 0 2 0 3]" "(interpose 0 [1 2 3])"]
|
||||
["interleave" "[1 :a 2 :b]" "(interleave [1 2] [:a :b])"]
|
||||
["flatten" "[1 2 3 4]" "(flatten [1 [2 [3 [4]]]])"]
|
||||
["concat" "[1 2 3 4]" "(concat [1 2] [3 4])"]
|
||||
["into vector" "[1 2 3 4]" "(into [1 2] [3 4])"]
|
||||
["into list" "[3 2 1]" "(into (list) [1 2 3])"]
|
||||
["frequencies" "{1 2, 2 1}" "(frequencies [1 1 2])"]
|
||||
["group-by" "{false [1 3], true [2 4]}" "(group-by even? [1 2 3 4])"]
|
||||
["zipmap" "{:a 1, :b 2}" "(zipmap [:a :b] [1 2])"]
|
||||
["mapcat seqs" "[1 2 3 4]" "(mapcat identity [[1 2] [3 4]])"])
|
||||
|
||||
(defspec "seq / generators"
|
||||
["range n" "[0 1 2 3]" "(range 4)"]
|
||||
["range from to" "[2 3 4]" "(range 2 5)"]
|
||||
["range with step" "[0 2 4]" "(range 0 6 2)"]
|
||||
["take repeat" "[:x :x :x]" "(take 3 (repeat :x))"]
|
||||
["repeat n" "[5 5]" "(repeat 2 5)"]
|
||||
["take iterate" "[1 2 4 8]" "(take 4 (iterate (fn [x] (* x 2)) 1))"]
|
||||
["take cycle" "[1 2 1 2 1]" "(take 5 (cycle [1 2]))"]
|
||||
["take repeatedly" "3" "(count (take 3 (repeatedly (fn [] 1))))"]
|
||||
["take-last of range" "[8 9]" "(take-last 2 (range 10))"])
|
||||
35
test/spec/sets-spec.janet
Normal file
35
test/spec/sets-spec.janet
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Specification: sets, including clojure.set.
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "set / construct & predicate"
|
||||
["literal" "#{1 2 3}" "#{1 2 3}"]
|
||||
["hash-set" "#{1 2 3}" "(hash-set 1 2 3)"]
|
||||
["set from vector" "#{1 2 3}" "(set [1 2 3 1])"]
|
||||
["empty" "#{}" "#{}"]
|
||||
["set? true" "true" "(set? #{1})"]
|
||||
["set? false on vector" "false" "(set? [1])"]
|
||||
["count dedups" "3" "(count (set [1 1 2 3]))"]
|
||||
["equality order-indep" "true" "(= #{1 2 3} #{3 2 1})"])
|
||||
|
||||
(defspec "set / operations"
|
||||
["conj adds" "#{1 2 3}" "(conj #{1 2} 3)"]
|
||||
["conj dup no-op" "#{1 2}" "(conj #{1 2} 1)"]
|
||||
["disj removes" "#{1 2}" "(disj #{1 2 3} 3)"]
|
||||
["disj missing no-op" "#{1 2}" "(disj #{1 2} 9)"]
|
||||
["contains?" "true" "(contains? #{1 2} 1)"]
|
||||
["contains? missing" "false" "(contains? #{1 2} 9)"]
|
||||
["get present" "1" "(get #{1 2} 1)"]
|
||||
["get missing nil" "nil" "(get #{1 2} 9)"]
|
||||
["set as fn present" "2" "(#{1 2 3} 2)"]
|
||||
["set as fn missing" "nil" "(#{1 2 3} 9)"])
|
||||
|
||||
(defspec "clojure.set"
|
||||
["union" "#{1 2 3 4}" "(do (require (quote [clojure.set :as s])) (s/union #{1 2} #{3 4}))"]
|
||||
["intersection" "#{2}" "(do (require (quote [clojure.set :as s])) (s/intersection #{1 2} #{2 3}))"]
|
||||
["difference" "#{1}" "(do (require (quote [clojure.set :as s])) (s/difference #{1 2} #{2 3}))"]
|
||||
["subset? true" "true" "(do (require (quote [clojure.set :as s])) (s/subset? #{1} #{1 2}))"]
|
||||
["superset? true" "true" "(do (require (quote [clojure.set :as s])) (s/superset? #{1 2} #{1}))"]
|
||||
["select" "#{2 4}" "(do (require (quote [clojure.set :as s])) (s/select even? #{1 2 3 4}))"]
|
||||
["join" "#{{:a 1, :b 2, :c 3}}" "(do (require (quote [clojure.set :as s])) (s/join #{{:a 1 :b 2}} #{{:b 2 :c 3}}))"]
|
||||
["map-invert" "{1 :a}" "(do (require (quote [clojure.set :as s])) (s/map-invert {:a 1}))"]
|
||||
["rename-keys" "{:b 1}" "(do (require (quote [clojure.set :as s])) (s/rename-keys {:a 1} {:a :b}))"])
|
||||
49
test/spec/vectors-spec.janet
Normal file
49
test/spec/vectors-spec.janet
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Specification: vectors (persistent, indexed).
|
||||
(use ../support/harness)
|
||||
|
||||
(defspec "vector / construct & predicate"
|
||||
["literal" "[1 2 3]" "[1 2 3]"]
|
||||
["vector" "[1 2 3]" "(vector 1 2 3)"]
|
||||
["vector zero args" "[]" "(vector)"]
|
||||
["vec from list" "[1 2 3]" "(vec (list 1 2 3))"]
|
||||
["vec from range" "[0 1 2]" "(vec (range 3))"]
|
||||
["vec of map yields entries" "[[:a 1]]" "(vec {:a 1})"]
|
||||
["vector? true" "true" "(vector? [1])"]
|
||||
["vector? false on list" "false" "(vector? (list 1))"]
|
||||
["vector = list elts" "true" "(= [1 2 3] (list 1 2 3))"])
|
||||
|
||||
(defspec "vector / access"
|
||||
["nth" ":b" "(nth [:a :b :c] 1)"]
|
||||
["nth default" ":x" "(nth [:a] 5 :x)"]
|
||||
["get by index" ":b" "(get [:a :b] 1)"]
|
||||
["get out of range nil" "nil" "(get [:a] 5)"]
|
||||
["get default" ":x" "(get [:a] 5 :x)"]
|
||||
["first" "1" "(first [1 2 3])"]
|
||||
["last" "3" "(last [1 2 3])"]
|
||||
["peek is last" "3" "(peek [1 2 3])"]
|
||||
["count" "3" "(count [1 2 3])"]
|
||||
["contains? index" "true" "(contains? [:a :b] 1)"]
|
||||
["contains? past end" "false" "(contains? [:a] 3)"]
|
||||
["vector as fn" ":b" "([:a :b :c] 1)"])
|
||||
|
||||
(defspec "vector / update (persistent)"
|
||||
["conj appends" "[1 2 3]" "(conj [1 2] 3)"]
|
||||
["conj many" "[1 2 3 4]" "(conj [1 2] 3 4)"]
|
||||
["assoc index" "[1 9 3]" "(assoc [1 2 3] 1 9)"]
|
||||
["assoc at count appends" "[1 2 3]" "(assoc [1 2] 2 3)"]
|
||||
["update" "[1 3 3]" "(update [1 2 3] 1 inc)"]
|
||||
["pop drops last" "[1 2]" "(pop [1 2 3])"]
|
||||
["subvec start end" "[2 3]" "(subvec [1 2 3 4] 1 3)"]
|
||||
["subvec to end" "[3 4]" "(subvec [1 2 3 4] 2)"]
|
||||
["mapv" "[2 3 4]" "(mapv inc [1 2 3])"]
|
||||
["filterv" "[2 4]" "(filterv even? [1 2 3 4])"])
|
||||
|
||||
(defspec "vector / immutability & nesting"
|
||||
["conj does not mutate" "true" "(let [v [1 2] w (conj v 3)] (and (= v [1 2]) (= w [1 2 3])))"]
|
||||
["assoc does not mutate" "true" "(let [v [1 2 3] w (assoc v 0 9)] (and (= v [1 2 3]) (= w [9 2 3])))"]
|
||||
["get-in" "2" "(get-in [[1 2] [3 4]] [0 1])"]
|
||||
["assoc-in" "[[1 9]]" "(assoc-in [[1 2]] [0 1] 9)"]
|
||||
["update-in" "[[1 3]]" "(update-in [[1 2]] [0 1] inc)"]
|
||||
["large vector nth" "1500" "(nth (vec (range 2000)) 1500)"]
|
||||
["large vector count" "2000" "(count (vec (range 2000)))"]
|
||||
["large conj immutable" "true" "(let [v (vec (range 1000)) w (conj v :end)] (and (= 1000 (count v)) (= 1001 (count w))))"])
|
||||
Loading…
Add table
Add a link
Reference in a new issue