blob: 37ba62664b9d739ad513a2e0060ee8e9498b8078 [file] [log] [blame]
% Licensed 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.
-module(fabric2_snapshot_tests).
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("eunit/include/eunit.hrl").
-include("fabric2.hrl").
-include("fabric2_test.hrl").
fdb_ss_test_() ->
{
"Test snapshot usage",
setup,
fun setup/0,
fun cleanup/1,
with([
?TDEF(retry_without_snapshot),
?TDEF(no_retry_with_snapshot)
])
}.
setup() ->
Ctx = test_util:start_couch([fabric]),
{ok, Db} = fabric2_db:create(?tempdb(), [{user_ctx, ?ADMIN_USER}]),
{Db, Ctx}.
cleanup({Db, Ctx}) ->
ok = fabric2_db:delete(fabric2_db:name(Db), []),
test_util:stop_couch(Ctx).
retry_without_snapshot({Db, _}) ->
DbName = fabric2_db:name(Db),
put(retry_count, 0),
erase(conflict_pid),
InitDbSeq = fabric2_db:get_update_seq(Db),
DbSeq = fabric2_fdb:transactional(Db, fun(TxDb) ->
put(retry_count, get(retry_count) + 1),
% Fetch the update_seq
Seq = fabric2_db:get_update_seq(TxDb),
% Generate a no-op write so that we don't hit the
% optimization to skip commits on read-only
% transactions
bump_view_size(TxDb),
% Generate a conflicting transaction while
% we're not yet committed
case get(conflict_pid) of
undefined ->
{Pid, Ref} = spawn_monitor(fun() -> generate_conflict(DbName) end),
receive {'DOWN', Ref, _, _, normal} -> ok end,
put(conflict_pid, Pid);
Pid when is_pid(Pid) ->
ok
end,
Seq
end),
?assertEqual(2, get(retry_count)),
?assertNotEqual(InitDbSeq, DbSeq).
no_retry_with_snapshot({Db, _}) ->
DbName = fabric2_db:name(Db),
put(retry_count, 0),
erase(conflict_pid),
InitDbSeq = fabric2_db:get_update_seq(Db),
DbSeq = fabric2_fdb:transactional(Db, fun(TxDb) ->
put(retry_count, get(retry_count) + 1),
% Fetch the update_seq
Seq = fabric2_fdb:with_snapshot(TxDb, fun(SSDb) ->
fabric2_db:get_update_seq(SSDb)
end),
% Generate a no-op write so that we don't hit the
% optimization to skip commits on read-only
% transactions
bump_view_size(TxDb),
% Generate a conflicting transaction while
% we're not yet committed
case get(conflict_pid) of
undefined ->
{Pid, Ref} = spawn_monitor(fun() -> generate_conflict(DbName) end),
receive {'DOWN', Ref, _, _, normal} -> ok end,
put(conflict_pid, Pid);
Pid when is_pid(Pid) ->
ok
end,
Seq
end),
?assertEqual(1, get(retry_count)),
?assertEqual(InitDbSeq, DbSeq).
bump_view_size(TxDb) ->
#{
tx := Tx,
db_prefix := DbPrefix
} = TxDb,
DbTuple = {?DB_STATS, <<"sizes">>, <<"views">>},
DbKey = erlfdb_tuple:pack(DbTuple, DbPrefix),
erlfdb:add(Tx, DbKey, 0).
generate_conflict(DbName) ->
{ok, Db} = fabric2_db:open(DbName, [{user_ctx, ?ADMIN_USER}]),
Doc = #doc{
id = fabric2_util:uuid(),
body = {[{<<"foo">>, <<"bar">>}]}
},
{ok, _} = fabric2_db:update_doc(Db, Doc).