|
924 | 924 | {:type :invoke :f :read :value :stale}) |
925 | 925 | [:type :f :value])))))) |
926 | 926 |
|
| 927 | +(defn index-history [history] |
| 928 | + (mapv (fn [index op] (cond-> op (nil? (:index op)) (assoc :index index))) (range) history)) |
| 929 | + |
927 | 930 | (defn check-txn-history [history] |
928 | | - (checker/check (epaxos/txn-atomic-checker) nil history nil)) |
| 931 | + (checker/check (epaxos/txn-atomic-checker) |
| 932 | + {:name "txn-atomic-checker-test" :start-time 0} |
| 933 | + (index-history history) |
| 934 | + nil)) |
| 935 | + |
| 936 | +(defn txn-read-state [values-by-group] |
| 937 | + (into {} (map (fn [{:keys [group keys]}] |
| 938 | + [group (vec (repeat (count keys) (get values-by-group group)))]) |
| 939 | + epaxos/txn-key-groups))) |
929 | 940 |
|
930 | 941 | (deftest txn-atomic-checker-checks-each-group-independently |
931 | 942 | (testing "values may differ across groups when each group is internally atomic" |
932 | | - (let [result (check-txn-history [{:type :ok |
933 | | - :f :txn-read |
934 | | - :value {:tx-a [1 1] |
935 | | - :tx-b [2 2 2] |
936 | | - :tx-c [nil nil]}}])] |
| 943 | + (let [result (check-txn-history [{:type :invoke :process 0 :f :txn-write :value {:group :tx-a :value 1}} |
| 944 | + {:type :ok :process 0 :f :txn-write :value {:group :tx-a :value 1}} |
| 945 | + {:type :invoke :process 1 :f :txn-write :value {:group :tx-b :value 2}} |
| 946 | + {:type :ok :process 1 :f :txn-write :value {:group :tx-b :value 2}} |
| 947 | + {:type :invoke :process 2 :f :txn-read :value nil} |
| 948 | + {:type :ok :process 2 :f :txn-read :value (txn-read-state {:tx-a 1 :tx-b 2})}])] |
937 | 949 | (is (= {:valid? true :checked 1 :bad-count 0} |
938 | 950 | (select-keys result [:valid? :checked :bad-count]))))) |
939 | 951 | (testing "fully deleted transaction groups are atomic reads" |
940 | | - (let [result (check-txn-history [{:type :ok |
| 952 | + (let [result (check-txn-history [{:type :invoke :process 0 :f :txn-read :value nil} |
| 953 | + {:type :ok |
| 954 | + :process 0 |
941 | 955 | :f :txn-read |
942 | 956 | :value {:tx-a [nil nil] |
943 | 957 | :tx-b [nil nil nil] |
|
946 | 960 | (select-keys result [:valid? :checked :bad-count]))))) |
947 | 961 | (testing "mixed or partial values inside any one group make the read invalid" |
948 | 962 | (let [read-op {:type :ok |
| 963 | + :process 0 |
949 | 964 | :f :txn-read |
950 | 965 | :value {:tx-a [1 2] |
951 | 966 | :tx-b [9 9 9] |
952 | 967 | :tx-c [nil 3]}} |
953 | | - result (check-txn-history [read-op]) |
| 968 | + result (check-txn-history [{:type :invoke :process 0 :f :txn-read :value nil} |
| 969 | + read-op]) |
954 | 970 | bad-op (first (:bad result))] |
955 | 971 | (is (= {:valid? false :checked 1 :bad-count 1} |
956 | 972 | (select-keys result [:valid? :checked :bad-count]))) |
957 | 973 | (is (= {:tx-a [1 2] :tx-c [nil 3]} |
958 | 974 | (:bad-groups bad-op))))) |
959 | 975 | (testing "mixed delete and write visibility inside a group is invalid" |
960 | 976 | (let [read-op {:type :ok |
| 977 | + :process 0 |
961 | 978 | :f :txn-read |
962 | 979 | :value {:tx-a [4 4] |
963 | 980 | :tx-b [nil 5 nil] |
964 | 981 | :tx-c [6 6]}} |
965 | | - result (check-txn-history [read-op]) |
| 982 | + result (check-txn-history [{:type :invoke :process 0 :f :txn-read :value nil} |
| 983 | + read-op]) |
966 | 984 | bad-op (first (:bad result))] |
967 | 985 | (is (= {:valid? false :checked 1 :bad-count 1} |
968 | 986 | (select-keys result [:valid? :checked :bad-count]))) |
969 | 987 | (is (= {:tx-b [nil 5 nil]} |
970 | 988 | (:bad-groups bad-op)))))) |
971 | 989 |
|
| 990 | +(deftest txn-atomic-checker-enforces-completed-write-visibility |
| 991 | + (testing "a later read observing the completed write remains valid" |
| 992 | + (let [history [{:type :invoke :process 0 :f :txn-write :value {:group :tx-a :value :committed}} |
| 993 | + {:type :ok :process 0 :f :txn-write :value {:group :tx-a :value :committed}} |
| 994 | + {:type :invoke :process 1 :f :txn-read :value nil} |
| 995 | + {:type :ok :process 1 :f :txn-read :value (txn-read-state {:tx-a :committed})}] |
| 996 | + result (check-txn-history history)] |
| 997 | + (is (= true (:valid? result))))) |
| 998 | + (testing "a later read of an older value after the completed write is invalid" |
| 999 | + (let [history [{:type :invoke :process 0 :f :txn-write :value {:group :tx-a :value :old}} |
| 1000 | + {:type :ok :process 0 :f :txn-write :value {:group :tx-a :value :old}} |
| 1001 | + {:type :invoke :process 1 :f :txn-write :value {:group :tx-a :value :committed}} |
| 1002 | + {:type :ok :process 1 :f :txn-write :value {:group :tx-a :value :committed}} |
| 1003 | + {:type :invoke :process 2 :f :txn-read :value nil} |
| 1004 | + {:type :ok :process 2 :f :txn-read :value (txn-read-state {:tx-a :old})}] |
| 1005 | + result (check-txn-history history)] |
| 1006 | + (is (= false (:valid? result)))))) |
| 1007 | + |
| 1008 | +(deftest txn-atomic-checker-enforces-completed-delete-visibility |
| 1009 | + (testing "a later read observing the completed delete remains valid" |
| 1010 | + (let [history [{:type :invoke :process 0 :f :txn-write :value {:group :tx-b :value :old}} |
| 1011 | + {:type :ok :process 0 :f :txn-write :value {:group :tx-b :value :old}} |
| 1012 | + {:type :invoke :process 1 :f :txn-delete :value {:group :tx-b}} |
| 1013 | + {:type :ok :process 1 :f :txn-delete :value {:group :tx-b}} |
| 1014 | + {:type :invoke :process 2 :f :txn-read :value nil} |
| 1015 | + {:type :ok :process 2 :f :txn-read :value (txn-read-state {})}] |
| 1016 | + result (check-txn-history history)] |
| 1017 | + (is (= true (:valid? result))))) |
| 1018 | + (testing "a later read of the deleted value is invalid" |
| 1019 | + (let [history [{:type :invoke :process 0 :f :txn-write :value {:group :tx-b :value :old}} |
| 1020 | + {:type :ok :process 0 :f :txn-write :value {:group :tx-b :value :old}} |
| 1021 | + {:type :invoke :process 1 :f :txn-delete :value {:group :tx-b}} |
| 1022 | + {:type :ok :process 1 :f :txn-delete :value {:group :tx-b}} |
| 1023 | + {:type :invoke :process 2 :f :txn-read :value nil} |
| 1024 | + {:type :ok :process 2 :f :txn-read :value (txn-read-state {:tx-b :old})}] |
| 1025 | + result (check-txn-history history)] |
| 1026 | + (is (= false (:valid? result)))))) |
| 1027 | + |
| 1028 | +(deftest txn-atomic-checker-rejects-never-written-equal-transaction-value |
| 1029 | + (testing "equal values across every key in a group still require a prior write" |
| 1030 | + (let [history [{:type :invoke :process 0 :f :txn-read :value nil} |
| 1031 | + {:type :ok :process 0 :f :txn-read :value (txn-read-state {:tx-c :phantom})}] |
| 1032 | + result (check-txn-history history)] |
| 1033 | + (is (= false (:valid? result)))))) |
| 1034 | + |
| 1035 | +(deftest txn-atomic-checker-accepts-observed-indeterminate-write |
| 1036 | + (testing "an info write may be linearized when a later transaction read observes it" |
| 1037 | + (let [history [{:type :invoke :process 0 :f :txn-write :value {:group :tx-a :value :maybe}} |
| 1038 | + {:type :info :process 0 :f :txn-write :value {:group :tx-a :value :maybe} :error 503} |
| 1039 | + {:type :invoke :process 1 :f :txn-read :value nil} |
| 1040 | + {:type :ok :process 1 :f :txn-read :value (txn-read-state {:tx-a :maybe})}] |
| 1041 | + result (check-txn-history history)] |
| 1042 | + (is (= true (:valid? result)))))) |
| 1043 | + |
972 | 1044 | (deftest txn-atomic-checker-rejects-missing-and-wrong-sized-groups |
973 | 1045 | (testing "every transaction read must contain exactly the configured groups with one value per key" |
974 | 1046 | (let [missing-group {:type :ok |
| 1047 | + :process 0 |
975 | 1048 | :f :txn-read |
976 | 1049 | :value {:tx-a [1 1] |
977 | 1050 | :tx-c [3 3]}} |
978 | 1051 | wrong-lengths {:type :ok |
| 1052 | + :process 1 |
979 | 1053 | :f :txn-read |
980 | 1054 | :value {:tx-a [7] |
981 | 1055 | :tx-b [8 8] |
982 | 1056 | :tx-c [9 9 9]}} |
983 | | - result (check-txn-history [missing-group wrong-lengths])] |
| 1057 | + result (check-txn-history [{:type :invoke :process 0 :f :txn-read :value nil} |
| 1058 | + missing-group |
| 1059 | + {:type :invoke :process 1 :f :txn-read :value nil} |
| 1060 | + wrong-lengths])] |
984 | 1061 | (is (= {:valid? false :checked 2 :bad-count 2} |
985 | 1062 | (select-keys result [:valid? :checked :bad-count]))) |
986 | 1063 | (is (= [(:value missing-group) (:value wrong-lengths)] |
|
0 commit comments