mnesia_rocksdb/test/mnesia_rocksdb_proper_semantics_test.erl
2018-02-06 09:43:57 +01:00

162 lines
5.0 KiB
Erlang

%%----------------------------------------------------------------
%% 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 <ulf.wiger@feuerlabs.com>
-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).