Start test.check tests on vector instructions

This is a start on using `test.check` to write tests for the vector instructions. We currently have tests for:

   * `vector/_emptyvector`
   * `vector/_indexof`
   * `vector/_subvec`

There are _lots_ of other functions still to be tested.

This did reveal errors in `vector/_subvec`, which will be addressed in the next commit.

We used macros to make it easy to generate tests for each of the four vector types; this should be extensible to additional vector types in the future if needed.
This commit is contained in:
Nic McPhee 2020-12-17 15:56:27 -06:00
parent a6e195eb0e
commit d1e863a23a
2 changed files with 114 additions and 1 deletions

View File

@ -4,6 +4,7 @@
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.10.0"]
[org.clojure/clojurescript "1.9.946"]]
[org.clojure/clojurescript "1.9.946"]
[org.clojure/test.check "1.1.0"]]
:main ^:skip-aot propeller.core
:repl-options {:init-ns propeller.core})

View File

@ -0,0 +1,112 @@
(ns propeller.push.instructions.vector-spec
(:require
[clojure.test.check.generators :as gen]
[clojure.test.check.properties :as prop]
[clojure.test.check.clojure-test :as ct :refer [defspec]]
[propeller.push.state :as state]
[propeller.push.instructions.vector :as vector]))
(defn check-empty-vector
[generator value-type]
(let [stack-type (keyword (str "vector_" value-type))]
(prop/for-all [vect (gen/vector generator)]
(let [start-state (state/push-to-stack state/empty-state
stack-type
vect)
end-state (vector/_emptyvector stack-type start-state)]
(= (empty? vect)
(state/peek-stack end-state :boolean))))))
(defmacro empty-vector-spec
[generator value-type]
`(defspec ~(symbol (str "empty-vector-spec-" value-type))
100
(check-empty-vector ~generator ~value-type)))
(empty-vector-spec gen/small-integer "integer")
(empty-vector-spec gen/double "float")
(empty-vector-spec gen/boolean "boolean")
(empty-vector-spec gen/string "string")
(defn check-expected-index
"Creates an otherwise empty Push state with the given vector on the
appropriate vector stack (assumed to be :vector_<value-type>), and
the given value on the appropriate stack (determined by value-type).
It then runs the vector/_indexof instruction, and confirms that the
result (on the :integer stack) is the expected value."
[vect value value-type]
(let [stack-type (keyword (str "vector_" value-type))
start-state (state/push-to-stack
(state/push-to-stack state/empty-state
stack-type
vect)
(keyword value-type) value)
end-state (vector/_indexof stack-type start-state)
expected-index (.indexOf vect value)]
(= expected-index
(state/peek-stack end-state :integer))))
(defmacro indexof-spec
[generator value-type]
`(do
(defspec ~(symbol (str "indexof-spec-" value-type))
; Should this be smaller for booleans? (Ditto for below.)
100
(prop/for-all [vect# (gen/vector ~generator)
value# ~generator]
(check-expected-index vect# value# ~value-type)))
; For float and string vectors, it's rather rare to actually have a random value that
; appears in the vector, so we don't consistently test the case where it should
; return -1. So maybe we do need a separate test for those?
(defspec ~(symbol (str "indexof-spec-has-value-" value-type))
100
(prop/for-all [vect# (gen/not-empty (gen/vector ~generator))]
(check-expected-index vect# (rand-nth vect#) ~value-type)))))
(indexof-spec gen/small-integer "integer")
(indexof-spec gen/double "float")
(indexof-spec gen/boolean "boolean")
(indexof-spec gen/string "string")
(defn clean-subvec-bounds
[start stop vect-size]
(let [start (max 0 start)
stop (max 0 stop)
start (min start vect-size)
stop (min stop vect-size)
stop (max start stop)]
[start stop]))
(defn check-subvec
"Creates an otherwise empty Push state with the given vector on the
appropriate vector stack (assumed to be :vector_<value-type>), and
the given values on the integer stack.
It then runs the vector/_subvec instruction, and confirms that the
result (on the :vector_<value-type> stack) is the expected value."
[vect start stop value-type]
(let [stack-type (keyword (str "vector_" value-type))
start-state (state/push-to-stack
(state/push-to-stack
(state/push-to-stack state/empty-state
stack-type
vect)
:integer start)
:integer stop)
end-state (vector/_subvec stack-type start-state)
[cleaned-start cleaned-stop] (clean-subvec-bounds start stop (count vect))
expected-subvec (subvec vect cleaned-start cleaned-stop)]
(= expected-subvec
(state/peek-stack end-state stack-type))))
(defmacro subvec-spec
[generator value-type]
`(defspec ~(symbol (str "subvec-spec-" value-type))
(prop/for-all [vect# (gen/vector ~generator)
start# gen/small-integer
stop# gen/small-integer]
(check-subvec vect# start# stop# ~value-type))))
(subvec-spec gen/small-integer "integer")
(subvec-spec gen/double "float")
(subvec-spec gen/boolean "boolean")
(subvec-spec gen/string "string")