diff --git a/src/jackdaw/test.clj b/src/jackdaw/test.clj index 03121885..c38bf76c 100644 --- a/src/jackdaw/test.clj +++ b/src/jackdaw/test.clj @@ -20,6 +20,7 @@ kafka cluster shared with other users." (:require [clojure.tools.logging :as log] + [clojure.spec.alpha :as s] [jackdaw.streams :as k] [jackdaw.test.commands :refer [with-handler command-handler]] [jackdaw.test.commands.base] @@ -64,6 +65,7 @@ ;; resources when the test machine is closed. (def +default-executor+ (-> (fn [machine cmd] + (s/assert :jackdaw.test.commands/test-event cmd) ((:command-handler machine) machine cmd)) with-status with-timing diff --git a/src/jackdaw/test/commands.clj b/src/jackdaw/test/commands.clj index 8dfbf379..2cd4171f 100644 --- a/src/jackdaw/test/commands.clj +++ b/src/jackdaw/test/commands.clj @@ -5,7 +5,7 @@ [jackdaw.test.commands.base :as base] [jackdaw.test.commands.write :as write] [jackdaw.test.commands.watch :as watch]) - (:refer-clojure :exclude [do])) + (:refer-clojure :exclude [do println pprint inspect])) (def base-commands base/command-map) (def write-command write/command-map) @@ -42,11 +42,55 @@ ;; Test Command API +(defmulti test-event first) + (s/def ::topic-id (s/or :keyword keyword? :string string?)) +(s/def ::timeout int?) +(s/def ::key any?) +(s/def ::key-fn ifn?) +(s/def ::partition int?) +(s/def ::partition-fn ifn?) +(s/def ::info string?) (s/def ::test-message any?) -(s/def ::write-options map?) -(s/def ::watch-options map?) +(s/def ::write-options (s/keys :opt-un [::key ::key-fn ::partition ::partition-fn])) +(s/def ::watch-options (s/keys :opt-un [::timeout ::info])) +(s/def ::test-event (s/multi-spec test-event first)) + +(defmethod test-event :do [_] (s/cat :op #{:do} + :do-fn ifn?)) + +(defmethod test-event :do! [_] (s/cat :op #{:do!} + :do-fn ifn?)) + +(defmethod test-event :inspect [_] (s/cat :op #{:do!} + :inspect-fn ifn?)) + +(defmethod test-event :write! [_] (s/cat :op #{:write!} + :topic-id ::topic-id + :message ::test-message + :options (s/? ::write-options))) + +(defmethod test-event :watch [_] (s/cat :op #{:watch} + :watch-fn ifn? + :option (s/? ::watch-options))) + +;; Deprecated test events +;; +;; Keeping these around to ensure existing test-sequences continue to be valid +;; but `:stop` is a relic of when the implementation required an explicit stop +;; command and the others are all expressible as a simple `:do`. + +(defmethod test-event :stop [_] (s/cat :op #{:stop})) + +(defmethod test-event :println [_] (s/cat :op #{:println} + :print-args (s/? any?))) + +(defmethod test-event :pprint [_] (s/cat :op #{:pprint} + :print-args (s/? any?))) + +(defmethod test-event :sleep [_] (s/cat :op #{:sleep} + :sleep-args int?)) (defn do "Invoke the provided function, passing a snapshot of the test journal @@ -56,8 +100,8 @@ `[:do ~do-fn]) (s/fdef do - :args ifn? - :ret vector?) + :args (s/cat :do-fn ifn?) + :ret ::test-event) (defn do! "Invoke the provided function, passing the journal `ref` @@ -69,8 +113,20 @@ `[:do! ~do-fn]) (s/fdef do! - :args ifn? - :ret vector?) + :args (s/cat :do-fn ifn?) + :ret ::test-event) + +(defn inspect + "Invoke the provided function, passing the entire test-machine + + Can be useful while learning about how the test-machine works to inspect the state + of the test-machine." + [inspect-fn] + `[:do! ~inspect-fn]) + +(s/fdef inspect + :args (s/cat :inspect-fn ifn?) + :ret ::test-event) (defn write! "Write a message to the topic identified in the topic-metadata by `topic-id` @@ -94,7 +150,7 @@ :args (s/cat :topic-id ::topic-id :message ::test-message :options (s/? ::write-options)) - :ret vector?) + :ret ::test-event) (defn watch "Watch the test-journal until the `watch-fn` predicate returns true @@ -115,4 +171,5 @@ (s/fdef watch :args (s/cat :watch-fn ifn? :options (s/? ::watch-options)) - :ret vector?) + :ret ::test-event) + diff --git a/test/jackdaw/test/commands_test.clj b/test/jackdaw/test/commands_test.clj index 1ff29ffb..359ca402 100644 --- a/test/jackdaw/test/commands_test.clj +++ b/test/jackdaw/test/commands_test.clj @@ -1,5 +1,6 @@ (ns jackdaw.test.commands-test (:require + [clojure.spec.alpha :as s] [clojure.test :refer :all] [jackdaw.test.commands :as cmd])) @@ -16,3 +17,44 @@ (is (thrown-with-msg? clojure.lang.ExceptionInfo #"Unknown command: :not-found" (cmd/command-handler {} [:not-found]))))) + +(defn valid-command? + [cmd] + (s/valid? ::cmd/test-event cmd)) + +(deftest test-command-specs + (testing "base commands" + (is (not (valid-command? [:yolo]))) + (is (valid-command? (cmd/do #(println "yolo" %)))) + (is (valid-command? (cmd/do! #(println "yolo" %)))) + (is (valid-command? [:stop])) + (is (valid-command? [:println "yolo"])) + (is (valid-command? [:pprint {:foo "yolo"}])) + (is (valid-command? [:sleep 420]))) + + (testing "write commands" + (is (valid-command? (cmd/write! :foo {:id 1 :payload "yolo"}))) + + (is (valid-command? (cmd/write! :foo {:id 1 :payload "yolo"} + {:key 1 + :partition 1}))) + + (is (valid-command? (cmd/write! :foo {:id 1 :payload "yolo"} + {:key-fn :id + :partition-fn (constantly 1)}))) + + (is (not (valid-command? [:write! :foo {:id 1 :payload "yolo"} + {:key-fn "not a fn" + :partition-fn "not a fn"}])))) + + (testing "watch commands" + (is (valid-command? (cmd/watch #(= % :expected)))) + (is (valid-command? (cmd/watch #(= % :expected) + {:info "error hint"}))) + (is (valid-command? (cmd/watch #(= % :expected) + {:timeout 420}))) + + (is (not (valid-command? [:watch #(= % :expected) + {:timeout "not an int"}]))) + (is (not (valid-command? [:watch #(= % :expected) + {:info :not-a-string}])))))