From 855c2ea9ca99c517bbcaa85da6febfbf4792c487 Mon Sep 17 00:00:00 2001 From: antonio2368 Date: Fri, 18 Dec 2020 12:19:30 +0100 Subject: [PATCH] Add sequential test (#66) --- tests/jepsen/src/jepsen/memgraph/core.clj | 4 +- .../jepsen/src/jepsen/memgraph/sequential.clj | 161 ++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 tests/jepsen/src/jepsen/memgraph/sequential.clj diff --git a/tests/jepsen/src/jepsen/memgraph/core.clj b/tests/jepsen/src/jepsen/memgraph/core.clj index f3f0353ad..9a6675a68 100644 --- a/tests/jepsen/src/jepsen/memgraph/core.clj +++ b/tests/jepsen/src/jepsen/memgraph/core.clj @@ -12,6 +12,7 @@ [slingshot.slingshot :refer [try+ throw+]] [jepsen.memgraph [basic :as basic] [bank :as bank] + [sequential :as sequential] [support :as s] [nemesis :as nemesis] [edn :as e]])) @@ -19,7 +20,8 @@ (def workloads "A map of workload names to functions that can take opts and construct workloads." - {:bank bank/workload}) + {:bank bank/workload + :sequential sequential/workload}) (def nemesis-configuration "Nemesis configuration" diff --git a/tests/jepsen/src/jepsen/memgraph/sequential.clj b/tests/jepsen/src/jepsen/memgraph/sequential.clj new file mode 100644 index 000000000..b8f772cf2 --- /dev/null +++ b/tests/jepsen/src/jepsen/memgraph/sequential.clj @@ -0,0 +1,161 @@ +(ns jepsen.memgraph.sequential + "Sequential test" + (:require [neo4j-clj.core :as dbclient] + [clojure.tools.logging :refer [info]] + [jepsen [client :as client] + [checker :as checker] + [generator :as gen]] + [jepsen.checker.timeline :as timeline] + [jepsen.memgraph.client :as c])) + +(dbclient/defquery get-all-nodes + "MATCH (n:Node) RETURN n;") + +(dbclient/defquery get-max-id + "MATCH (n:Node) + RETURN n.id AS id + ORDER BY id DESC + LIMIT 1;") + +(dbclient/defquery get-min-id + "MATCH (n:Node) + RETURN n.id AS id + ORDER BY id + LIMIT 1;") + +(dbclient/defquery create-node + "CREATE (n:Node {id: $id});") + +(dbclient/defquery delete-node-with-id + "MATCH (n:Node {id: $id}) DELETE n;") + +(defn add-next-node + "Add a new node with its id set to the next highest" + [conn] + (dbclient/with-transaction conn tx + (let [max-id (-> (get-max-id tx) first :id)] + (create-node tx {:id (inc max-id)})))) + +(defn delete-oldest-node + "Delete a node with the lowest id" + [conn] + (dbclient/with-transaction conn tx + (let [min-id (-> (get-min-id tx) first :id)] + (delete-node-with-id tx {:id min-id})))) + +(c/replication-client Client [] + (open! [this test node] + (c/replication-open-connection this node node-config)) + (setup! [this test] + (when (= replication-role :main) + (c/with-session conn session + (c/detach-delete-all session) + (create-node session {:id 0})))) + (invoke! [this test op] + (c/replication-invoke-case (:f op) + :read (c/with-session conn session + (assoc op + :type :ok + :value {:ids (->> (get-all-nodes session) + (map #(-> % :n :id)) + (reduce conj [])) + :node node})) + :add (if (= replication-role :main) + (try + (assoc op :type (if (add-next-node conn) :ok :fail)) + (catch Exception e + ; Transaction can fail on serialization errors + (assoc op :type :fail :info (str e)))) + (assoc op :type :fail)) + :delete (if (= replication-role :main) + (try + (assoc op :type (if (delete-oldest-node conn) :ok :fail)) + (catch Exception e + ; Transaction can fail on serialization errors + (assoc op :type :fail :info (str e)))) + (assoc op :type :fail)))) + (teardown! [this test] + (when (= replication-role :main) + (c/with-session conn session + (c/detach-delete-all session)))) + (close! [_ est] + (dbclient/disconnect conn))) + +(defn add-node + "Add node with id set to current_max_id + 1" + [test process] + {:type :invoke :f :add :value nil}) + +(defn read-ids + "Read all current ids of nodes" + [test process] + {:type :invoke :f :read :value nil}) + +(defn delete-node + "Delete node with the lowest id" + [test process] + {:type :invoke :f :delete :value nil}) + +(defn strictly-increasing + [coll] + (every? + #(< (first %) (second %)) + (partition 2 1 coll))) + +(defn increased-by-1 + [coll] + (every? + #(= (inc (first %)) (second %)) + (partition 2 1 coll))) + +(defn sequential-checker + "Check if all nodes have nodes with ids that are strictly increasing by 1. + All nodes need to have at leas 1 non-empty read." + [] + (reify checker/Checker + (check [this test history opts] + (let [ok-reads (->> history + (filter #(= :ok (:type %))) + (filter #(= :read (:f %)))) + bad-reads (->> ok-reads + (map (fn [op] + (let [ids (-> op :value :ids)] + (when (not-empty ids) + (cond ((complement strictly-increasing) ids) + {:type :not-increasing-ids + :op op} + + ((complement increased-by-1) ids) + {:type :ids-missing + :op op}))))) + (filter identity) + (into [])) + empty-nodes (let [all-nodes (->> ok-reads + (map #(-> % :value :node)) + (reduce conj #{}))] + (->> all-nodes + (filter (fn [node] + (every? + empty? + (->> ok-reads + (map :value) + (filter #(= node (:node %))) + (map :ids))))) + (filter identity) + (into [])))] + {:valid? (and + (empty? bad-reads) + (empty? empty-nodes)) + :empty-nodes empty-nodes + :bad-reads bad-reads})))) + +(defn workload + [opts] + {:client (Client. nil nil nil (:node-config opts)) + :checker (checker/compose + {:sequential (sequential-checker) + :timeline (timeline/html)}) + :generator (c/replication-gen + (gen/phases (cycle [(gen/time-limit 1 (gen/mix [read-ids add-node])) + (gen/once delete-node)]))) + :final-generator (gen/once read-ids)})