You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2017/06/19 21:59:29 UTC
[couchdb] 05/05: ss - Initial test suite check in
This is an automated email from the ASF dual-hosted git repository.
davisp pushed a commit to branch optimize-ddoc-cache
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 54a048475ce5132e60fb2e043a6500ba70a89de7
Author: Paul J. Davis <pa...@gmail.com>
AuthorDate: Mon Jun 19 16:58:36 2017 -0500
ss - Initial test suite check in
Will round out testing the cache entry refreshing tomorrow and then
should end up with nearly 100% coverage.
---
src/ddoc_cache/rebar.config | 2 +
src/ddoc_cache/src/ddoc_cache.erl | 8 +-
src/ddoc_cache/src/ddoc_cache_entry.erl | 15 +-
src/ddoc_cache/src/ddoc_cache_lru.erl | 34 ++--
src/ddoc_cache/src/ddoc_cache_opener.erl | 2 +-
src/ddoc_cache/src/ddoc_cache_sup.erl | 2 +-
src/ddoc_cache/src/ddoc_cache_tables.erl | 7 +-
src/ddoc_cache/test/ddoc_cache_basic_test.erl | 111 +++++++++++++
src/ddoc_cache/test/ddoc_cache_coverage_test.erl | 74 +++++++++
src/ddoc_cache/test/ddoc_cache_disabled_test.erl | 52 ++++++
src/ddoc_cache/test/ddoc_cache_ev.erl | 22 +++
src/ddoc_cache/test/ddoc_cache_eviction_test.erl | 180 +++++++++++++++++++++
src/ddoc_cache/test/ddoc_cache_no_cache_test.erl | 75 +++++++++
src/ddoc_cache/test/ddoc_cache_open_error_test.erl | 46 ++++++
src/ddoc_cache/test/ddoc_cache_test.hrl | 20 +++
src/ddoc_cache/test/ddoc_cache_tutil.erl | 70 ++++++++
16 files changed, 687 insertions(+), 33 deletions(-)
diff --git a/src/ddoc_cache/rebar.config b/src/ddoc_cache/rebar.config
new file mode 100644
index 0000000..e0d1844
--- /dev/null
+++ b/src/ddoc_cache/rebar.config
@@ -0,0 +1,2 @@
+{cover_enabled, true}.
+{cover_print_enabled, true}.
diff --git a/src/ddoc_cache/src/ddoc_cache.erl b/src/ddoc_cache/src/ddoc_cache.erl
index f9eea9f..9100954 100644
--- a/src/ddoc_cache/src/ddoc_cache.erl
+++ b/src/ddoc_cache/src/ddoc_cache.erl
@@ -21,6 +21,7 @@
open_doc/2,
open_doc/3,
open_validation_funs/1,
+ open_custom/2,
evict/2,
%% deprecated
@@ -54,9 +55,12 @@ open_custom(DbName, Mod) ->
ddoc_cache_opener:open(Key).
-evict(ShardDbName, DDocIds) ->
+evict(ShardDbName, DDocIds) when is_list(DDocIds) ->
DbName = mem3:dbname(ShardDbName),
- ddoc_cache_lru:evict(DbName, DDocIds).
+ ddoc_cache_lru:evict(DbName, DDocIds);
+
+evict(ShardDbName, DDocId) ->
+ evict(ShardDbName, [DDocId]).
open(DbName, validation_funs) ->
diff --git a/src/ddoc_cache/src/ddoc_cache_entry.erl b/src/ddoc_cache/src/ddoc_cache_entry.erl
index 80b8659..c19df90 100644
--- a/src/ddoc_cache/src/ddoc_cache_entry.erl
+++ b/src/ddoc_cache/src/ddoc_cache_entry.erl
@@ -18,8 +18,7 @@
ddocid/1,
spawn_link/1,
handle_resp/1,
- open/1,
- recover/1
+ open/1
]).
-export([
@@ -27,6 +26,7 @@
do_open/2
]).
+
dbname({Mod, Arg}) ->
Mod:dbname(Arg).
@@ -36,17 +36,14 @@ ddocid({Mod, Arg}) ->
spawn_link(Key) ->
- erlang:spawn_link(?MODULE, do_open, [Key]).
+ erlang:spawn_link(?MODULE, do_open, [Key, true]).
handle_resp({open_ok, _Key, Resp}) ->
Resp;
handle_resp({open_error, _Key, Type, Reason, Stack}) ->
- erlang:raise(Type, Reason, Stack);
-
-handle_resp(Else) ->
- erlang:error({ddoc_cache_entry, Else}).
+ erlang:raise(Type, Reason, Stack).
open(Key) ->
@@ -57,10 +54,6 @@ open(Key) ->
end.
-recover({Mod, Arg}) ->
- Mod:recover(Arg).
-
-
do_open(Key) ->
do_open(Key, false).
diff --git a/src/ddoc_cache/src/ddoc_cache_lru.erl b/src/ddoc_cache/src/ddoc_cache_lru.erl
index dbcaa28..fecf780 100644
--- a/src/ddoc_cache/src/ddoc_cache_lru.erl
+++ b/src/ddoc_cache/src/ddoc_cache_lru.erl
@@ -46,7 +46,6 @@
keys, % key -> time
dbs, % dbname -> docid -> key -> []
time,
- max_size,
evictor
}).
@@ -82,12 +81,10 @@ init(_) ->
{ok, Evictor} = couch_event:link_listener(
?MODULE, handle_db_event, nil, [all_dbs]
),
- MaxSize = config:get_integer("ddoc_cache", "max_size", 1000),
{ok, #st{
keys = Keys,
dbs = Dbs,
time = 0,
- max_size = MaxSize,
evictor = Evictor
}}.
@@ -107,12 +104,14 @@ handle_call({insert, Key, Val}, _From, St) ->
time = Time
} = St,
NewTime = Time + 1,
+ NewSt = St#st{time = NewTime},
Pid = ddoc_cache_refresher:spawn(Key),
true = ets:insert(?CACHE, #entry{key = Key, val = Val, pid = Pid}),
true = ets:insert(?ATIMES, {NewTime, Key}),
- ok = khash:put(Keys, NewTime),
+ ok = khash:put(Keys, Key, NewTime),
store_key(Dbs, Key),
- {reply, ok, trim(St#st{time = NewTime})};
+ trim(NewSt),
+ {reply, ok, NewSt};
handle_call({refresh, Key, Val}, _From, St) ->
#st{
@@ -168,7 +167,7 @@ handle_cast({accessed, Key}, St) ->
true = is_process_alive(Pid),
true = ets:delete(?ATIMES, OldTime),
true = ets:insert(?ATIMES, {NewTime, Key}),
- ok = khash:put(Keys, NewTime);
+ ok = khash:put(Keys, Key, NewTime);
not_found ->
% Likely a client read from the cache while an
% eviction message was in our mailbox
@@ -176,12 +175,12 @@ handle_cast({accessed, Key}, St) ->
end,
{noreply, St};
-handle_cast({evict, _} = Msg, St) ->
- gen_server:abcast(mem3:nodes(), ?MODULE, Msg),
+handle_cast({evict, DbName}, St) ->
+ gen_server:abcast(mem3:nodes(), ?MODULE, {do_evict, DbName}),
{noreply, St};
-handle_cast({evict, _, _} = Msg, St) ->
- gen_server:abcast(mem3:nodes(), ?MODULE, Msg),
+handle_cast({evict, DbName, DDocIds}, St) ->
+ gen_server:abcast(mem3:nodes(), ?MODULE, {do_evict, DbName, DDocIds}),
{noreply, St};
handle_cast({do_evict, DbName}, St) ->
@@ -201,9 +200,10 @@ handle_cast({do_evict, DbName}, St) ->
not_found ->
ok
end,
+ ddoc_cache_ev:evicted(DbName, all),
{noreply, St};
-handle_cast({do_evict, DbName, DDocIds}, St) ->
+handle_cast({do_evict, DbName, DDocIdList}, St) ->
#st{
dbs = Dbs
} = St,
@@ -221,7 +221,7 @@ handle_cast({do_evict, DbName, DDocIds}, St) ->
ok
end,
khash:del(DDocIds, DDocId)
- end, [no_ddocid | DDocIds]),
+ end, [no_ddocid | DDocIdList]),
case khash:size(DDocIds) of
0 -> khash:del(Dbs, DbName);
_ -> ok
@@ -229,14 +229,14 @@ handle_cast({do_evict, DbName, DDocIds}, St) ->
not_found ->
ok
end,
+ ddoc_cache_ev:evicted(DbName, DDocIdList),
{noreply, St};
handle_cast(Msg, St) ->
{stop, {invalid_cast, Msg}, St}.
-handle_info({'EXIT', Pid, Reason}, #st{evictor=Pid}=St) ->
- couch_log:error("ddoc_cache_opener evictor died ~w", [Reason]),
+handle_info({'EXIT', Pid, _Reason}, #st{evictor=Pid}=St) ->
{ok, Evictor} = couch_event:link_listener(
?MODULE, handle_db_event, nil, [all_dbs]
),
@@ -277,15 +277,15 @@ store_key(Dbs, Key) ->
not_found ->
{ok, Keys} = khash:from_list([{Key, []}]),
{ok, DDocIds} = khash:from_list([{DDocId, Keys}]),
- khash:put(Dbs, DDocId, DDocIds)
+ khash:put(Dbs, DbName, DDocIds)
end.
trim(St) ->
#st{
- keys = Keys,
- max_size = MaxSize
+ keys = Keys
} = St,
+ MaxSize = config:get_integer("ddoc_cache", "max_size", 1000),
case khash:size(Keys) > MaxSize of
true ->
case ets:first(?ATIMES) of
diff --git a/src/ddoc_cache/src/ddoc_cache_opener.erl b/src/ddoc_cache/src/ddoc_cache_opener.erl
index f6a4438..18524d3 100644
--- a/src/ddoc_cache/src/ddoc_cache_opener.erl
+++ b/src/ddoc_cache/src/ddoc_cache_opener.erl
@@ -106,7 +106,7 @@ handle_info({'EXIT', _Pid, {open_ok, OpenerKey, Resp}}, St) ->
{noreply, St};
handle_info({'EXIT', _Pid, {open_error, OpenerKey, Type, Reason, Stack}}, St) ->
- respond(OpenerKey, {open_error, Type, Reason, Stack}),
+ respond(OpenerKey, {open_error, OpenerKey, Type, Reason, Stack}),
{noreply, St};
handle_info({'EXIT', Pid, Reason}, St) ->
diff --git a/src/ddoc_cache/src/ddoc_cache_sup.erl b/src/ddoc_cache/src/ddoc_cache_sup.erl
index ddb1232..35393e1 100644
--- a/src/ddoc_cache/src/ddoc_cache_sup.erl
+++ b/src/ddoc_cache/src/ddoc_cache_sup.erl
@@ -51,4 +51,4 @@ init([]) ->
[ddoc_cache_opener]
}
],
- {ok, {{one_for_all, 5, 10}, Children}}.
+ {ok, {{one_for_all, 25, 1}, Children}}.
diff --git a/src/ddoc_cache/src/ddoc_cache_tables.erl b/src/ddoc_cache/src/ddoc_cache_tables.erl
index e097129..2d57163 100644
--- a/src/ddoc_cache/src/ddoc_cache_tables.erl
+++ b/src/ddoc_cache/src/ddoc_cache_tables.erl
@@ -38,7 +38,12 @@ start_link() ->
init(_) ->
BaseOpts = [public, named_table],
- ets:new(?CACHE, [set, {read_concurrency, true}] ++ BaseOpts),
+ CacheOpts = [
+ set,
+ {read_concurrency, true},
+ {keypos, #entry.key}
+ ] ++ BaseOpts,
+ ets:new(?CACHE, CacheOpts),
ets:new(?ATIMES, [ordered_set] ++ BaseOpts),
ets:new(?OPENERS, [set, {keypos, #opener.key}] ++ BaseOpts),
{ok, nil}.
diff --git a/src/ddoc_cache/test/ddoc_cache_basic_test.erl b/src/ddoc_cache/test/ddoc_cache_basic_test.erl
new file mode 100644
index 0000000..ea114bd
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_basic_test.erl
@@ -0,0 +1,111 @@
+% 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(ddoc_cache_basic_test).
+
+
+-export([
+ recover/1
+]).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include("ddoc_cache_test.hrl").
+
+
+recover(DbName) ->
+ {ok, {DbName, totes_custom}}.
+
+
+check_basic_test_() ->
+ {
+ setup,
+ fun ddoc_cache_tutil:start_couch/0,
+ fun ddoc_cache_tutil:stop_couch/1,
+ {with, [
+ fun cache_ddoc/1,
+ fun cache_ddoc_rev/1,
+ fun cache_vdu/1,
+ fun cache_custom/1,
+ fun cache_ddoc_refresher_unchanged/1,
+ fun dont_cache_not_found/1
+ ]}
+ }.
+
+
+cache_ddoc({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+ Resp1 = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ ?assertMatch({ok, #doc{id = ?FOOBAR}}, Resp1),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ Resp2 = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ ?assertEqual(Resp1, Resp2),
+ ?assertEqual(1, ets:info(?CACHE, size)).
+
+
+cache_ddoc_rev({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ Rev = ddoc_cache_tutil:get_rev(DbName, ?FOOBAR),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+ Resp1 = ddoc_cache:open_doc(DbName, ?FOOBAR, Rev),
+ ?assertMatch({ok, #doc{id = ?FOOBAR}}, Resp1),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ Resp2 = ddoc_cache:open_doc(DbName, ?FOOBAR, Rev),
+ ?assertEqual(Resp1, Resp2),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+
+ % Assert that the non-rev cache entry is separate
+ Resp3 = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ ?assertMatch({ok, #doc{id = ?FOOBAR}}, Resp3),
+ ?assertEqual(2, ets:info(?CACHE, size)).
+
+
+cache_vdu({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+ Resp1 = ddoc_cache:open_validation_funs(DbName),
+ ?assertMatch({ok, [_]}, Resp1),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ Resp2 = ddoc_cache:open_validation_funs(DbName),
+ ?assertEqual(Resp1, Resp2),
+ ?assertEqual(1, ets:info(?CACHE, size)).
+
+
+cache_custom({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+ Resp1 = ddoc_cache:open_custom(DbName, ?MODULE),
+ ?assertMatch({ok, {DbName, totes_custom}}, Resp1),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ Resp2 = ddoc_cache:open_custom(DbName, ?MODULE),
+ ?assertEqual(Resp1, Resp2),
+ ?assertEqual(1, ets:info(?CACHE, size)).
+
+
+cache_ddoc_refresher_unchanged({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+ ddoc_cache:open_doc(DbName, ?FOOBAR),
+ [Entry1] = ets:lookup(?CACHE, ets:first(?CACHE)),
+ ddoc_cache:open_doc(DbName, ?FOOBAR),
+ [Entry2] = ets:lookup(?CACHE, ets:first(?CACHE)),
+ ?assertEqual(Entry1, Entry2).
+
+
+dont_cache_not_found({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ Resp = ddoc_cache:open_doc(DbName, <<"_design/not_found">>),
+ ?assertEqual({not_found, missing}, Resp),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+ ?assertEqual(0, ets:info(?ATIMES, size)).
diff --git a/src/ddoc_cache/test/ddoc_cache_coverage_test.erl b/src/ddoc_cache/test/ddoc_cache_coverage_test.erl
new file mode 100644
index 0000000..0cc4cdf
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_coverage_test.erl
@@ -0,0 +1,74 @@
+% 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(ddoc_cache_coverage_test).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include("ddoc_cache_test.hrl").
+
+
+check_basic_test_() ->
+ {
+ setup,
+ fun ddoc_cache_tutil:start_couch/0,
+ fun ddoc_cache_tutil:stop_couch/1,
+ [
+ fun restart_opener/0,
+ fun restart_lru/0,
+ fun restart_tables/0
+ ]
+ }.
+
+
+restart_opener() ->
+ send_bad_messages(ddoc_cache_opener),
+ wait_for_restart(ddoc_cache_opener, fun() ->
+ whereis(ddoc_cache_opener) ! {'EXIT', a_pid, because}
+ end),
+ ?assertEqual(ok, ddoc_cache_opener:terminate(bang, bable)),
+ ?assertEqual({ok, foo}, ddoc_cache_opener:code_change(1, foo, [])).
+
+
+restart_lru() ->
+ send_bad_messages(ddoc_cache_lru),
+ ?assertEqual(ok, ddoc_cache_lru:terminate(bang, {st, a, b, c, d})),
+ ?assertEqual({ok, foo}, ddoc_cache_lru:code_change(1, foo, [])).
+
+
+restart_tables() ->
+ send_bad_messages(ddoc_cache_tables),
+ ?assertEqual(ok, ddoc_cache_tables:terminate(bang, baz)),
+ ?assertEqual({ok, foo}, ddoc_cache_tables:code_change(1, foo, [])).
+
+
+send_bad_messages(Name) ->
+ wait_for_restart(Name, fun() ->
+ ?assertEqual({invalid_call, foo}, gen_server:call(Name, foo))
+ end),
+ wait_for_restart(Name, fun() ->
+ gen_server:cast(Name, foo)
+ end),
+ wait_for_restart(Name, fun() ->
+ whereis(Name) ! foo
+ end).
+
+
+wait_for_restart(Server, Fun) ->
+ Ref = erlang:monitor(process, whereis(Server)),
+ Fun(),
+ receive
+ {'DOWN', Ref, _, _, _} ->
+ ok
+ end,
+ ?assert(is_pid(test_util:wait_process(Server))).
diff --git a/src/ddoc_cache/test/ddoc_cache_disabled_test.erl b/src/ddoc_cache/test/ddoc_cache_disabled_test.erl
new file mode 100644
index 0000000..3e412c7
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_disabled_test.erl
@@ -0,0 +1,52 @@
+% 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(ddoc_cache_disabled_test).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include("ddoc_cache_test.hrl").
+
+
+start_couch() ->
+ Ctx = ddoc_cache_tutil:start_couch(),
+ config:set("ddoc_cache", "max_size", "0", false),
+ Ctx.
+
+
+check_disabled_test_() ->
+ {
+ setup,
+ fun start_couch/0,
+ fun ddoc_cache_tutil:stop_couch/1,
+ {with, [
+ fun resp_ok/1,
+ fun resp_not_found/1
+ ]}
+ }.
+
+
+resp_ok({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ Resp = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ ?assertMatch({ok, #doc{id = ?FOOBAR}}, Resp),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+ ?assertEqual(0, ets:info(?ATIMES, size)).
+
+
+resp_not_found({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ Resp = ddoc_cache:open_doc(DbName, <<"_design/not_found">>),
+ ?assertEqual({not_found, missing}, Resp),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+ ?assertEqual(0, ets:info(?ATIMES, size)).
diff --git a/src/ddoc_cache/test/ddoc_cache_ev.erl b/src/ddoc_cache/test/ddoc_cache_ev.erl
new file mode 100644
index 0000000..a9ea475
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_ev.erl
@@ -0,0 +1,22 @@
+% 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(ddoc_cache_ev).
+
+-export([
+ evicted/2
+]).
+
+
+evicted(_DbName, _DDocIds) ->
+ couch_log:error("EVICTION EVENT: ~p ~p~n", [_DbName, _DDocIds]),
+ ok.
diff --git a/src/ddoc_cache/test/ddoc_cache_eviction_test.erl b/src/ddoc_cache/test/ddoc_cache_eviction_test.erl
new file mode 100644
index 0000000..d36157d
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_eviction_test.erl
@@ -0,0 +1,180 @@
+% 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(ddoc_cache_eviction_test).
+
+
+-export([
+ recover/1
+]).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include("ddoc_cache_test.hrl").
+
+
+recover(DbName) ->
+ {ok, {DbName, totes_custom}}.
+
+
+start_couch() ->
+ Ctx = ddoc_cache_tutil:start_couch(),
+ meck:new(ddoc_cache_ev, [passthrough]),
+ Ctx.
+
+
+stop_couch(Ctx) ->
+ meck:unload(ddoc_cache_ev),
+ ddoc_cache_tutil:stop_couch(Ctx).
+
+
+check_eviction_test_() ->
+ {
+ setup,
+ fun start_couch/0,
+ fun stop_couch/1,
+ {with, [
+ fun evict_ddoc/1,
+ fun evict_ddoc_rev/1,
+ fun evict_vdu/1,
+ fun evict_custom/1,
+ fun evict_vdu_unrelated/1,
+ fun evict_custom_unrelated/1,
+ fun evict_all/1,
+ fun dont_evict_unrelated/1,
+ fun dont_evict_all_unrelated/1,
+ fun check_upgrade_clauses/1
+ ]}
+ }.
+
+
+evict_ddoc({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ ddoc_cache:evict(DbName, [?FOOBAR]),
+ wait_for_eviction(DbName, [?FOOBAR]),
+ ?assertEqual(0, ets:info(?CACHE, size)).
+
+
+evict_ddoc_rev({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ Rev = ddoc_cache_tutil:get_rev(DbName, ?FOOBAR),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?FOOBAR, Rev),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ ddoc_cache:evict(DbName, [?FOOBAR]),
+ wait_for_eviction(DbName, [?FOOBAR]),
+ ?assertEqual(0, ets:info(?CACHE, size)).
+
+
+evict_vdu({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ {ok, _} = ddoc_cache:open_validation_funs(DbName),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ ddoc_cache:evict(DbName, ?VDU),
+ wait_for_eviction(DbName, [?VDU]),
+ ?assertEqual(0, ets:info(?CACHE, size)).
+
+
+evict_custom({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ {ok, _} = ddoc_cache:open_custom(DbName, ?MODULE),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ ddoc_cache:evict(DbName, ?CUSTOM),
+ wait_for_eviction(DbName, [?CUSTOM]),
+ ?assertEqual(0, ets:info(?CACHE, size)).
+
+
+evict_vdu_unrelated({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ {ok, _} = ddoc_cache:open_validation_funs(DbName),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ ddoc_cache:evict(DbName, [?FOOBAR]),
+ wait_for_eviction(DbName, [?FOOBAR]),
+ ?assertEqual(0, ets:info(?CACHE, size)).
+
+
+evict_custom_unrelated({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ {ok, _} = ddoc_cache:open_custom(DbName, ?MODULE),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ ddoc_cache:evict(DbName, [?FOOBAR]),
+ wait_for_eviction(DbName, [?FOOBAR]),
+ ?assertEqual(0, ets:info(?CACHE, size)).
+
+
+evict_all({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ Rev = ddoc_cache_tutil:get_rev(DbName, ?FOOBAR),
+ ShardName = element(2, hd(mem3:shards(DbName))),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?FOOBAR, Rev),
+ {ok, _} = ddoc_cache:open_validation_funs(DbName),
+ {ok, _} = ddoc_cache:open_custom(DbName, ?MODULE),
+ ?assertEqual(4, ets:info(?CACHE, size)),
+ {ok, _} = ddoc_cache_lru:handle_db_event(ShardName, deleted, foo),
+ wait_for_eviction(DbName, all),
+ ?assertEqual(0, ets:info(?CACHE, size)).
+
+
+dont_evict_unrelated({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?VDU),
+ ?assertEqual(2, ets:info(?CACHE, size)),
+ ddoc_cache:evict(DbName, [?FOOBAR]),
+ wait_for_eviction(DbName, [?FOOBAR]),
+ ?assertEqual(1, ets:info(?CACHE, size)).
+
+
+dont_evict_all_unrelated({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ Rev = ddoc_cache_tutil:get_rev(DbName, ?FOOBAR),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?FOOBAR, Rev),
+ {ok, _} = ddoc_cache:open_validation_funs(DbName),
+ {ok, _} = ddoc_cache:open_custom(DbName, ?MODULE),
+ ?assertEqual(4, ets:info(?CACHE, size)),
+ ShardName = <<"shards/00000000-ffffffff/test.1384769918">>,
+ {ok, _} = ddoc_cache_lru:handle_db_event(ShardName, deleted, foo),
+ wait_for_eviction(<<"test">>, all),
+ ?assertEqual(4, ets:info(?CACHE, size)).
+
+
+check_upgrade_clauses({DbName, _}) ->
+ ddoc_cache_tutil:clear(),
+ ShardName = element(2, hd(mem3:shards(DbName))),
+ {ok, _} = ddoc_cache:open_doc(DbName, ?FOOBAR),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ gen_server:cast(ddoc_cache_opener, {do_evict, DbName}),
+ wait_for_eviction(DbName, all),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+
+ {ok, _} = ddoc_cache:open_doc(DbName, ?VDU),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ gen_server:cast(ddoc_cache_opener, {do_evict, DbName, [?VDU]}),
+ wait_for_eviction(DbName, [?VDU]),
+ ?assertEqual(0, ets:info(?CACHE, size)),
+
+ % Make sure evict all vs specific doc ids works
+ {ok, _} = ddoc_cache:open_doc(DbName, ?CUSTOM),
+ ?assertEqual(1, ets:info(?CACHE, size)),
+ gen_server:cast(ddoc_cache_opener, {do_evict, DbName, [?FOOBAR]}),
+ wait_for_eviction(DbName, [?FOOBAR]),
+ ?assertEqual(1, ets:info(?CACHE, size)).
+
+
+
+wait_for_eviction(DbName, DDocIds) ->
+ meck:reset(ddoc_cache_ev),
+ meck:wait(ddoc_cache_ev, evicted, [DbName, DDocIds], 1000).
diff --git a/src/ddoc_cache/test/ddoc_cache_no_cache_test.erl b/src/ddoc_cache/test/ddoc_cache_no_cache_test.erl
new file mode 100644
index 0000000..43891c8
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_no_cache_test.erl
@@ -0,0 +1,75 @@
+% 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(ddoc_cache_no_cache_test).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+
+ddoc(DDocId) ->
+ {ok, #doc{
+ id = DDocId,
+ body = {[
+ {<<"ohai">>, null}
+ ]}
+ }}.
+
+
+not_found(_DDocId) ->
+ {not_found, missing}.
+
+
+return_error(_DDocId) ->
+ {error, timeout}.
+
+
+start(Resp) ->
+ meck:new(fabric),
+ meck:expect(fabric, open_doc, fun(_, DDocId, _) ->
+ Resp(DDocId)
+ end).
+
+
+stop() ->
+ meck:unload().
+
+
+no_cache_open_ok_test() ->
+ start(fun ddoc/1),
+ try
+ Resp = ddoc_cache:open_doc(<<"foo">>, <<"bar">>),
+ ?assertEqual(ddoc(<<"bar">>), Resp)
+ after
+ stop()
+ end.
+
+
+no_cache_open_not_found_test() ->
+ start(fun not_found/1),
+ try
+ Resp = ddoc_cache:open_doc(<<"foo">>, <<"bar">>),
+ ?assertEqual(not_found(<<"bar">>), Resp)
+ after
+ stop()
+ end.
+
+
+no_cache_open_error_test() ->
+ start(fun return_error/1),
+ try
+ Resp = ddoc_cache:open_doc(<<"foo">>, <<"bar">>),
+ ?assertEqual(return_error(<<"bar">>), Resp)
+ after
+ stop()
+ end.
diff --git a/src/ddoc_cache/test/ddoc_cache_open_error_test.erl b/src/ddoc_cache/test/ddoc_cache_open_error_test.erl
new file mode 100644
index 0000000..0ac2390
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_open_error_test.erl
@@ -0,0 +1,46 @@
+% 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(ddoc_cache_open_error_test).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include("ddoc_cache_test.hrl").
+
+
+start_couch() ->
+ Ctx = ddoc_cache_tutil:start_couch(),
+ meck:expect(fabric, open_doc, fun(_, ?FOOBAR, _) ->
+ erlang:error(test_kaboom)
+ end),
+ Ctx.
+
+
+stop_couch(Ctx) ->
+ meck:unload(),
+ ddoc_cache_tutil:stop_couch(Ctx).
+
+
+check_basic_test_() ->
+ {
+ setup,
+ fun start_couch/0,
+ fun stop_couch/1,
+ {with, [
+ fun handle_open_error/1
+ ]}
+ }.
+
+
+handle_open_error({DbName, _}) ->
+ ?assertError(test_kaboom, ddoc_cache:open_doc(DbName, ?FOOBAR)).
diff --git a/src/ddoc_cache/test/ddoc_cache_test.hrl b/src/ddoc_cache/test/ddoc_cache_test.hrl
new file mode 100644
index 0000000..1a09804
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_test.hrl
@@ -0,0 +1,20 @@
+% 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.
+
+
+-define(CACHE, ddoc_cache_entries).
+-define(ATIMES, ddoc_cache_atimes).
+-define(OPENERS, ddoc_cache_openers).
+
+-define(FOOBAR, <<"_design/foobar">>).
+-define(VDU, <<"_design/vdu">>).
+-define(CUSTOM, <<"_design/custom">>).
\ No newline at end of file
diff --git a/src/ddoc_cache/test/ddoc_cache_tutil.erl b/src/ddoc_cache/test/ddoc_cache_tutil.erl
new file mode 100644
index 0000000..0ac68fa
--- /dev/null
+++ b/src/ddoc_cache/test/ddoc_cache_tutil.erl
@@ -0,0 +1,70 @@
+% 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(ddoc_cache_tutil).
+
+
+-compile(export_all).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+
+start_couch() ->
+ Ctx = test_util:start_couch(?CONFIG_CHAIN, [chttpd, ddoc_cache]),
+ TmpDb = ?tempdb(),
+ ok = fabric:create_db(TmpDb, [{q, "1"}, {n, "1"}]),
+ {ok, _} = fabric:update_docs(TmpDb, ddocs(), [?ADMIN_CTX]),
+ {TmpDb, Ctx}.
+
+
+stop_couch({_TmpDb, Ctx}) ->
+ test_util:stop_couch(Ctx).
+
+
+clear() ->
+ application:stop(ddoc_cache),
+ application:start(ddoc_cache).
+
+
+get_rev(DbName, DDocId) ->
+ {_, Ref} = erlang:spawn_monitor(fun() ->
+ {ok, #doc{revs = Revs}} = fabric:open_doc(DbName, DDocId, [?ADMIN_CTX]),
+ {Depth, [RevId]} = Revs,
+ exit({Depth, RevId})
+ end),
+ receive
+ {'DOWN', Ref, _, _, Rev} -> Rev
+ end.
+
+
+ddocs() ->
+ FooBar = #doc{
+ id = <<"_design/foobar">>,
+ body = {[
+ {<<"foo">>, <<"bar">>}
+ ]}
+ },
+ VDU = #doc{
+ id = <<"_design/vdu">>,
+ body = {[
+ {<<"validate_doc_update">>, <<"function(doc) {return;}">>}
+ ]}
+ },
+ Custom = #doc{
+ id = <<"_design/custom">>,
+ body = {[
+ {<<"custom">>, <<"hotrod">>}
+ ]}
+ },
+ [FooBar, VDU, Custom].
--
To stop receiving notification emails like this one, please contact
"commits@couchdb.apache.org" <co...@couchdb.apache.org>.