%%---------------------------------------------------------------- %% Copyright (c) 2013-2016 Klarna AB %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %%---------------------------------------------------------------- %% @doc Verify dirty vs transaction semantics against rocksdb mnesia backend %% @author Ulf Wiger -module(mnesia_rocksdb_proper_semantics_test). %% This module uses the proper_statem pattern to generate random %% sequences of commands, mixing dirty and transaction operations %% (including dirty ops from within transactions). Each sequence is run %% against a disc_copies table and a rocksdb_copies table, after %% which the result of each operation in the sequence is compared between %% the two runs. The postcondition is that every command in every sequence %% should yield the same value against both backends. -export([test/1, prop_seq/0]). %% statem callbacks -export([initial_state/0, command/1, precondition/2, postcondition/3, next_state/3]). %% command callbacks -export([activity/2]). -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). -record(st, {}). -define(KEYS, [a,b,c]). basic_test_() -> {timeout, 60000, [fun() -> test(100) end]}. test(N) -> setup_mnesia(), true = proper:quickcheck(?MODULE:prop_seq(), N), ok. prop_seq() -> ?FORALL(Cmds, proper_statem:commands(?MODULE), begin setup(), {H, S, Res} = proper_statem:run_commands(?MODULE, Cmds), cleanup(), ?WHENFAIL( io:fwrite("History: ~w~n" "State : ~w~n" "Result : ~w~n", [H, S, Res]), proper:aggregate( proper_statem:command_names(Cmds), Res =:= ok)) end). %% Note that this requires the rocksdb application to be in the path, %% and obviously an OTP patched with the backend plugin behavior. setup_mnesia() -> stopped = mnesia:stop(), ok = mnesia:delete_schema([node()]), ok = mnesia:create_schema([node()]), ok = mnesia:start(), {ok, rocksdb_copies} = mnesia_rocksdb:register(). setup() -> {atomic,ok} = mnesia:create_table(d, [{disc_copies, [node()]}, {record_name, x}]), {atomic,ok} = mnesia:create_table(l, [{rocksdb_copies, [node()]}, {record_name, x}]), ok = mnesia:wait_for_tables([d, l], 30000), ok. cleanup() -> {atomic, ok} = mnesia:delete_table(d), {atomic, ok} = mnesia:delete_table(l), ok. initial_state() -> #st{}. command(#st{}) -> ?LET(Type, type(), {call, ?MODULE, activity, [Type, sequence()]}). type() -> proper_types:oneof([async_dirty, transaction]). precondition(_, _) -> true. postcondition(_, {call,?MODULE,activity,_}, {A, B}) -> A == B; postcondition(_, _, _) -> false. next_state(St, _, _) -> St. sequence() -> proper_types:list(db_cmd()). db_cmd() -> ?LET(Type, type(), proper_types:oneof([{Type, read, key()}, {Type, write, key(), value()}, {Type, delete, key()}])). key() -> proper_types:oneof([a,b,c]). value() -> proper_types:oneof([1,2,3]). activity(Type, Seq) -> {mnesia:activity(Type, fun() -> apply_seq(Type, d, Seq) end), mnesia:activity(Type, fun() -> apply_seq(Type, l, Seq) end)}. apply_seq(Type, Tab, Seq) -> apply_seq(Type, Tab, Seq, []). apply_seq(transaction=X, Tab, [H|T], Acc) -> Res = case H of {X,read, K} -> mnesia:read(Tab, K, read); {_,read, K} -> mnesia:dirty_read(Tab,K); {X,write,K,V} -> mnesia:write(Tab, {x, K, V}, write); {_,write,K,V} -> mnesia:dirty_write(Tab, {x,K,V}); {X,delete,K} -> mnesia:delete(Tab, K, write); {_,delete,K} -> mnesia:dirty_delete(Tab,K) end, apply_seq(X, Tab, T, [Res|Acc]); apply_seq(X, Tab, [H|T], Acc) -> Res = case H of {_,read, K} -> mnesia:read(Tab, K, read); {_,write,K,V} -> mnesia:write(Tab, {x, K, V}, write); {_,delete,K} -> mnesia:delete(Tab, K, write) end, apply_seq(X, Tab, T, [Res|Acc]); apply_seq(_, _, [], Acc) -> lists:reverse(Acc).