Reputation: 1681
I think I may have a problem with the correct order of nesting Specs within a function - specifically s/with-gen
and s/or
...
I have this function and Spec:
(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
(if (nil? g-hist)
[{:giver :none, :givee :none}]
(assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
:args (s/with-gen
(s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
#(gen/let [hist (s/gen :unq/gift-history)
year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
pair (s/gen :unq/gift-pair)]
[hist year pair]))
:ret :unq/gift-history)
Which tests correctly:
(stest/check `set-gift-pair-in-gift-history)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
0x729d93b6
"clojure.spec.alpha$fspec_impl$reify__2451@729d93b6"],
:clojure.spec.test.check/ret {:result true,
:num-tests 1000,
:seed 1531413555637},
:sym clojure-redpoint.roster/set-gift-pair-in-gift-history})
And its parameters conform correctly:
(s/conform (s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
[[{:giver :GeoHar, :givee :JohLen}] 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-hist
{:g-hist [{:giver :GeoHar, :givee :JohLen}],
:g-year 0,
:g-pair {:giver :RinStaXX, :givee :PauMccXX}}]
(s/conform (s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
[nil 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-nil
{:g-hist nil, :g-year 0, :g-pair {:giver :RinStaXX, :givee :PauMccXX}}]
But when this "correct" function is consumed by a second function:
(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
(let [plr (get-player-in-roster plrs-map plr-sym)
gh (get-gift-history-in-player plr)
ngh (set-gift-pair-in-gift-history gh g-year g-pair)
nplr (set-gift-history-in-player ngh plr)]
(assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
:args (s/cat :plrs-map ::plr-map
:plr-sym keyword?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
:ret ::plr-map)
The consumed function becomes a source of error for the consuming function (the nil
case - which I thought was handled):
(stest/check `set-gift-pair-in-roster)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
0x3bbc704a
"clojure.spec.alpha$fspec_impl$reify__2451@3bbc704a"],
:clojure.spec.test.check/ret {:result #error{:cause "Call to #'clojure-redpoint.roster/set-gift-pair-in-gift-history did not conform to spec:
In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
val: {:g-hist nil, :g-year 1, :g-pair {:givee :_+, :giver :RJK/Y24}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))
I have tried changing the order and grouping (nesting) of the specs in the consumed function - but then it fails the tests that it used to pass, before even getting to testing the consuming function.
Any thoughts on what is going wrong here?
Thank you!
EDIT:
As suggested, here is the full code for a better understanding:
;Here is an example of The Beatles keeping track of the Xmas gifts
;they give to each other (:giver and :givee) each year over time:
(ns clojure-redpoint.roster2
(:require [clojure.spec.alpha :as s]
[orchestra.spec.test :as st]
[clojure.test.check.generators :as gen]
[clojure.spec.test.alpha :as stest]))
(s/def ::givee keyword?)
(s/def ::giver keyword?)
(s/def :unq/gift-pair (s/keys :req-un [::givee ::giver]))
(s/def ::name string?)
(s/def :unq/gift-history (s/coll-of :unq/gift-pair :kind vector?))
(s/def :unq/player (s/keys :req-un [::name :unq/gift-history]))
(s/def ::plr-map (s/map-of keyword? :unq/player))
(defn- get-player-in-roster [plrs-map plr-sym]
(get plrs-map plr-sym))
(s/fdef get-player-in-roster
:args (s/cat :plrs-map ::plr-map :plr-sym keyword?)
:ret (s/or :found :unq/player
:not-found nil?))
(defn- get-gift-history-in-player [plr]
(get plr :gift-history))
(s/fdef get-gift-history-in-player
:args (s/or :input-plr (s/cat :plr :unq/player)
:input-nil (s/cat :plr nil?))
:ret (s/or :found :unq/gift-history
:not-found nil?))
(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
(if (nil? g-hist)
[{:giver :none, :givee :none}]
(assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
:args (s/with-gen
(s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
#(gen/let [hist (s/gen :unq/gift-history)
year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
pair (s/gen :unq/gift-pair)]
[hist year pair]))
:ret :unq/gift-history)
(defn set-gift-history-in-player [g-hist plr]
(if (or (nil? g-hist) (nil? plr))
{:name "none", :gift-history [{:giver :none, :givee :none}]}
(assoc plr :gift-history g-hist)))
(s/fdef set-gift-history-in-player
:args (s/or :input-good (s/cat :g-hist :unq/gift-history
:plr :unq/player)
:input-hist-nil (s/cat :g-hist nil?
:plr :unq/player)
:input-plr-nil (s/cat :g-hist :unq/gift-history
:plr nil?)
:input-both-nil (s/cat :g-hist nil?
:plr nil?))
:ret :unq/player)
(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
(let [plr (get-player-in-roster plrs-map plr-sym)
gh (get-gift-history-in-player plr)
ngh (set-gift-pair-in-gift-history gh g-year g-pair)
nplr (set-gift-history-in-player ngh plr)]
(assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
:args (s/cat :plrs-map ::plr-map
:plr-sym keyword?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
:ret ::plr-map)
(st/instrument)
(def roster-map
{:RinSta {:name "Ringo Starr", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
:JohLen {:name "John Lennon", :gift-history [{:giver :JohLen, :givee :GeoHar}]},
:GeoHar {:name "George Harrison", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
:PauMcc {:name "Paul McCartney", :gift-history [{:giver :PauMcc, :givee :RinSta}]}})
(s/conform ::plr-map
(set-gift-pair-in-roster roster-map :PauMcc 0 {:giver :JohLenXXX, :givee :GeoHarXXX}))
;=>
;{:RinSta {:name "Ringo Starr", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
; :JohLen {:name "John Lennon", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
; :GeoHar {:name "George Harrison",
; :gift-history [{:giver :PauMcc, :givee :RinSta}]},
; :PauMcc {:name "Paul McCartney",
; :gift-history [{:giver :JohLenXXX, :givee :GeoHarXXX}]}}
;(stest/check `set-gift-pair-in-roster)
Unfortunately, it did not help me find my error...
Upvotes: 0
Views: 471
Reputation: 16194
The problem is that one of your instrumented functions set-gift-pair-in-gift-history
is being called with invalid arguments when you (stest/check `set-gift-pair-in-roster)
:
CompilerException clojure.lang.ExceptionInfo: Call to #'playground.so/set-gift-pair-in-gift-history did not conform to spec:
In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
val: {:g-hist nil, :g-year 1, :g-pair {:givee :A, :giver :A}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))
The check
output gives us a minimal input to reproduce the error:
(set-gift-pair-in-roster {} :A 1 {:givee :A, :giver :A})
We can see the first argument to the failing function is nil. Looking at set-gift-pair-in-gift-history
's function spec, there's a suspect spec that covers that case:
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
This will only conform when g-hist
is nil and g-year
is 0
, but the generator for :g-year
is going to generate many numbers besides 0
. That's why the calls to it fail when it's instrument
ed.
instrument
and check
have shown a discrepancy between how the program is specified to behave and how it actually behaves. I'd start by thinking about how set-gift-pair-in-gift-history
should be spec'd when its first argument is nil. The implementation doesn't care about the other arguments when the first arg is nil, so you could adjust the function spec to reflect that:
:input-nil (s/cat :g-hist nil? :g-year any? :g-pair any?)
With that change, your top-level function should check
successfully.
Upvotes: 2