diff options
author | Robert Newson <rnewson@apache.org> | 2023-03-24 11:26:26 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-24 11:26:26 +0000 |
commit | 35b1adc4892077ab1e58f64cd67c91289b9af0c5 (patch) | |
tree | 7abcfc5ea961921c1a54dacfeeff4ccfe86bc4fc | |
parent | 27af79c4a55b238d7fa768cc86962ccd011c7382 (diff) | |
parent | 3f0d86f99e3a9ccfa46508bc8958e4a6f836232e (diff) | |
download | couchdb-35b1adc4892077ab1e58f64cd67c91289b9af0c5.tar.gz |
Merge pull request #4495 from apache/add_db_event_crash_test
eunit test to assert ddoc_updated clause doesn't throw
-rw-r--r-- | src/couch_index/test/eunit/couch_index_crash_tests.erl | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/src/couch_index/test/eunit/couch_index_crash_tests.erl b/src/couch_index/test/eunit/couch_index_crash_tests.erl new file mode 100644 index 000000000..88f8dc845 --- /dev/null +++ b/src/couch_index/test/eunit/couch_index_crash_tests.erl @@ -0,0 +1,233 @@ +% 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(couch_index_crash_tests). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + +-define(TEST_INDEX, test_index). + +start() -> + meck:new(couch_index_server, [passthrough]), + meck:new(couch_index, [passthrough]), + Ctx = test_util:start_couch([mem3, fabric]), + DbName = ?tempdb(), + ok = fabric:create_db(DbName, [?ADMIN_CTX, {q, 1}, {n, 1}]), + {Ctx, DbName}. + +stop({Ctx, DbName}) -> + meck:unload(), + ok = fabric:delete_db(DbName, [?ADMIN_CTX]), + DbDir = config:get("couchdb", "database_dir", "."), + WaitFun = fun() -> + filelib:fold_files( + DbDir, + <<".*", DbName/binary, "\.[0-9]+.*">>, + true, + fun(_F, _A) -> wait end, + ok + ) + end, + ok = test_util:wait(WaitFun), + test_util:stop_couch(Ctx), + ok. + +db_event_crash_test() -> + % mock st record from couch_index_server + St = { + st, + "", + couch_index_server:server_name(1), + couch_index_server:by_sig(1), + couch_index_server:by_pid(1), + couch_index_server:by_db(1), + couch_index_server:openers(1) + }, + %% Assert that we get back what we sent in, and implicitly didn't crash instead. + ?assertEqual( + {ok, St}, + couch_index_server:handle_db_event( + <<"shards/fake">>, {ddoc_updated, <<"fakeddoc">>}, St + ) + ). + +index_crash_test_() -> + { + "Simulate index crashing", + { + foreach, + fun start/0, + fun stop/1, + [ + ?TDEF_FE(t_can_open_mock_index), + ?TDEF_FE(t_index_open_returns_error), + ?TDEF_FE(t_index_open_raises_error), + ?TDEF_FE(t_index_open_exits_with_error), + ?TDEF_FE(t_index_process_dies) + ] + } + }. + +t_can_open_mock_index({_Ctx, DbName}) -> + failing_index(dontfail), + + [DbShard1] = open_shards(DbName), + + % create a DDoc on Db1 + {DDoc, DbShard} = create_ddoc(DbShard1, <<"idx_name">>), + + meck:reset(couch_index_server), + %% fetch the fake index works + ?assertMatch({ok, _}, get_index(DbShard, DDoc)), + + %% assert openers ETS table is empty + lists:foreach(fun(I) -> ?assertEqual([], openers(I)) end, seq()), + + ?assert(meck:called(couch_index_server, handle_call, [{async_open, '_', '_'}, '_', '_'])). + +t_index_open_returns_error({_Ctx, DbName}) -> + failing_index({return, idxerr}), + + [DbShard1] = open_shards(DbName), + + % create a DDoc on Db1 + {DDoc, DbShard} = create_ddoc(DbShard1, <<"idx_name">>), + + meck:reset(couch_index_server), + %% fetch the index and confirm it fails + ?assertEqual({error, idxerr}, get_index(DbShard, DDoc)), + + %% assert openers ETS table is empty + lists:foreach(fun(I) -> ?assertEqual([], openers(I)) end, seq()), + + ?assert(meck:called(couch_index_server, handle_call, [{async_error, '_', '_'}, '_', '_'])). + +t_index_open_raises_error({_Ctx, DbName}) -> + failing_index({raise, idxerr}), + + [DbShard1] = open_shards(DbName), + + % create a DDoc on Db1 + {DDoc, DbShard} = create_ddoc(DbShard1, <<"idx_name">>), + + meck:reset(couch_index_server), + %% fetch the index and confirm it fails + ?assertEqual({meck_raise, error, idxerr}, get_index(DbShard, DDoc)), + + %% assert openers ETS table is empty + lists:foreach(fun(I) -> ?assertEqual([], openers(I)) end, seq()), + + ?assert(meck:called(couch_index_server, handle_call, [{async_error, '_', '_'}, '_', '_'])). + +t_index_open_exits_with_error({_Ctx, DbName}) -> + failing_index({exit, idxerr}), + + [DbShard1] = open_shards(DbName), + + % create a DDoc on Db1 + {DDoc, DbShard} = create_ddoc(DbShard1, <<"idx_name">>), + + meck:reset(couch_index_server), + %% fetch the index and confirm it fails + ?assertEqual({meck_raise, exit, idxerr}, get_index(DbShard, DDoc)), + + %% assert openers ETS table is empty + lists:foreach(fun(I) -> ?assertEqual([], openers(I)) end, seq()), + + ?assert(meck:called(couch_index_server, handle_call, [{async_error, '_', '_'}, '_', '_'])). + +t_index_process_dies({_Ctx, DbName}) -> + failing_index(dontfail), + + [DbShard1] = open_shards(DbName), + + % create a DDoc on Db1 + {DDoc, DbShard} = create_ddoc(DbShard1, <<"idx_name">>), + + meck:reset(couch_index_server), + {ok, IdxPid} = get_index(DbShard, DDoc), + ?assert(is_pid(IdxPid)), + + % Save index servers before so we can assert a dying index won't take any + % of them down. + ServerPids = lists:sort([whereis(N) || N <- couch_index_server:names()]), + + meck:reset(couch_index_server), + exit(IdxPid, boom), + meck:wait(couch_index_server, handle_info, [{'EXIT', IdxPid, boom}, '_'], 1000), + + {ok, IdxPid2} = get_index(DbShard, DDoc), + ?assert(is_pid(IdxPid2)), + + % Same index servers are still up + ServerPids2 = lists:sort([whereis(N) || N <- couch_index_server:names()]), + ?assertEqual(ServerPids, ServerPids2). + +create_ddoc(Db, DDocID) -> + DDocJson = couch_doc:from_json_obj( + {[ + {<<"_id">>, DDocID}, + {<<"value">>, 1} + ]} + ), + {ok, _Rev} = couch_db:update_doc(Db, DDocJson, []), + {ok, Db1} = couch_db:reopen(Db), + {ok, DDoc} = couch_db:open_doc(Db1, DDocID, [ejson_body, ?ADMIN_CTX]), + {DDoc, Db1}. + +open_shards(DbName) -> + lists:map( + fun(Sh) -> + {ok, ShardDb} = couch_db:open(mem3:name(Sh), []), + ShardDb + end, + mem3:local_shards(mem3:dbname(DbName)) + ). + +get_index(ShardDb, DDoc) -> + couch_index_server:get_index(?TEST_INDEX, ShardDb, DDoc). + +openers(I) -> + ets:tab2list(couch_index_server:openers(I)). + +failing_index(Error) -> + ok = meck:new([?TEST_INDEX], [non_strict]), + ok = meck:expect(?TEST_INDEX, init, fun(Db, DDoc) -> + {ok, {couch_db:name(Db), DDoc}} + end), + ok = meck:expect(?TEST_INDEX, open, fun(_Db, State) -> + case Error of + dontfail -> + {ok, State}; + {return, Err} -> + {error, Err}; + {raise, Err} -> + meck:raise(error, Err); + {exit, Err} -> + meck:raise(exit, Err) + end + end), + ok = meck:expect(?TEST_INDEX, get, fun + (db_name, {DbName, _DDoc}) -> + DbName; + (idx_name, {_DbName, DDoc}) -> + DDoc#doc.id; + (signature, {_DbName, DDoc}) -> + couch_hash:md5_hash(term_to_binary(DDoc)); + (update_seq, Seq) -> + Seq + end), + ok = meck:expect(?TEST_INDEX, shutdown, ['_'], ok). + +seq() -> + lists:seq(1, couch_index_server:num_servers()). |