From d1e863a23a5b960b61d46b714d228eb4b0bc9168 Mon Sep 17 00:00:00 2001 From: Nic McPhee Date: Thu, 17 Dec 2020 15:56:27 -0600 Subject: [PATCH] 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. --- project.clj | 3 +- .../push/instructions/vector_spec.clj | 112 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 test/propeller/push/instructions/vector_spec.clj diff --git a/project.clj b/project.clj index ae2d7d2..e941433 100644 --- a/project.clj +++ b/project.clj @@ -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}) diff --git a/test/propeller/push/instructions/vector_spec.clj b/test/propeller/push/instructions/vector_spec.clj new file mode 100644 index 0000000..bec11b0 --- /dev/null +++ b/test/propeller/push/instructions/vector_spec.clj @@ -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_), 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_), and + the given values on the integer stack. + It then runs the vector/_subvec instruction, and confirms that the + result (on the :vector_ 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") \ No newline at end of file