You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2015/10/01 17:03:51 UTC
[2/7] couchdb-couch-epi git commit: Refactor couch_epi to simplify it
http://git-wip-us.apache.org/repos/asf/couchdb-couch-epi/blob/d6c5081e/test/couch_epi_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_epi_tests.erl b/test/couch_epi_tests.erl
index a43ade9..fc2daa8 100644
--- a/test/couch_epi_tests.erl
+++ b/test/couch_epi_tests.erl
@@ -17,12 +17,18 @@
-define(DATA_FILE1, ?ABS_PATH("test/fixtures/app_data1.cfg")).
-define(DATA_FILE2, ?ABS_PATH("test/fixtures/app_data2.cfg")).
--export([notify_cb/5, save/3]).
+-export([notify_cb/4, save/3]).
--record(ctx, {file, handle, pid, kv, key}).
+-record(ctx, {file, handle, pid, kv, key, modules = []}).
-define(TIMEOUT, 5000).
+-define(temp_atom,
+ fun() ->
+ {A, B, C} = erlang:now(),
+ list_to_atom(lists:flatten(io_lib:format("~p~p~p", [A, B, C])))
+ end).
+
-define(MODULE1(Name), "
-export([inc/2, fail/2]).
@@ -76,72 +82,142 @@
].
").
-notify_cb(App, Key, OldData, Data, KV) ->
- save(KV, is_called, {App, Key, OldData, Data}).
+%% ------------------------------------------------------------------
+%% couch_epi_plugin behaviour
+%% ------------------------------------------------------------------
+
+plugin_module([KV, Spec]) ->
+ SpecStr = io_lib:format("~w", [Spec]),
+ KVStr = "'" ++ atom_to_list(KV) ++ "'",
+ "
+ -compile([export_all]).
+
+ app() -> test_app.
+ providers() ->
+ [].
+
+ services() ->
+ [].
+
+ data_providers() ->
+ [
+ {{test_app, descriptions}, " ++ SpecStr ++ ", [{interval, 100}]}
+ ].
+
+ data_subscriptions() ->
+ [
+ {test_app, descriptions}
+ ].
+
+ processes() -> [].
+
+ notify(Key, OldData, Data) ->
+ couch_epi_tests:notify_cb(Key, OldData, Data, " ++ KVStr ++ ").
+ ";
+plugin_module([KV]) ->
+ KVStr = "'" ++ atom_to_list(KV) ++ "'",
+ "
+ -compile([export_all]).
+
+ app() -> test_app.
+ providers() ->
+ [
+ {my_service, provider1},
+ {my_service, provider2}
+ ].
+
+ services() ->
+ [
+ {my_service, provider1}
+ ].
+
+ data_providers() ->
+ [].
+
+ data_subscriptions() ->
+ [].
+ processes() -> [].
-setup(couch_epi_data_source) ->
+ notify(Key, OldData, Data) ->
+ couch_epi_tests:notify_cb(Key, OldData, Data, " ++ KVStr ++ ").
+ ".
+
+
+notify_cb(Key, OldData, Data, KV) ->
+ save(KV, is_called, {Key, OldData, Data}).
+
+start_epi(Plugins) ->
+ application:load(couch_epi),
+ PluginsModules = lists:map(fun({Module, Body}) ->
+ ok = generate_module(Module, Body),
+ Module
+ end, Plugins),
+ application:set_env(couch_epi, plugins, PluginsModules),
+ application:start(couch_epi).
+
+setup(data_file) ->
error_logger:tty(false),
Key = {test_app, descriptions},
File = ?tempfile(),
{ok, _} = file:copy(?DATA_FILE1, File),
- application:start(couch_epi),
- {ok, Pid} = couch_epi_data_source:start_link(
- test_app, {epi_key, Key}, {file, File}, [{interval, 100}]),
- ok = couch_epi_data_source:wait(Pid),
- KV = state_storage(),
- ok = couch_epi:register_service(Key),
+ KV = start_state_storage(),
+
+ ok = start_epi([{provider_epi, plugin_module([KV, {file, File}])}]),
+
+ Pid = whereis(couch_epi:get_handle(Key)),
+
+
#ctx{
file = File,
key = Key,
handle = couch_epi:get_handle(Key),
kv = KV,
pid = Pid};
-setup(couch_epi_data) ->
+setup(data_module) ->
error_logger:tty(false),
Key = {test_app, descriptions},
- application:start(couch_epi),
+
ok = generate_module(provider, ?DATA_MODULE1(provider)),
+ KV = start_state_storage(),
+
+ ok = start_epi([{provider_epi, plugin_module([KV, {module, provider}])}]),
+
+ Pid = whereis(couch_epi:get_handle(Key)),
+ Handle = couch_epi:get_handle(Key),
- {ok, Pid} = couch_epi_data:start_link(
- test_app, {epi_key, Key}, provider, []),
- ok = couch_epi_data:wait(Pid),
- KV = state_storage(),
- ok = couch_epi:register_service(Key),
#ctx{
key = Key,
- handle = couch_epi:get_handle(Key),
+ handle = Handle,
+ modules = [Handle, provider],
kv = KV,
pid = Pid};
-setup(couch_epi_functions) ->
+setup(functions) ->
Key = my_service,
error_logger:tty(false),
- application:start(couch_epi),
ok = generate_module(provider1, ?MODULE1(provider1)),
ok = generate_module(provider2, ?MODULE2(provider2)),
- {ok, Pid} = couch_epi_functions:start_link(
- test_app, {epi_key, Key}, {modules, [provider1, provider2]},
- [{interval, 100}]),
- ok = couch_epi_functions:wait(Pid),
- KV = state_storage(),
- ok = couch_epi:register_service(Key),
+ KV = start_state_storage(),
+
+ ok = start_epi([{provider_epi, plugin_module([KV])}]),
+
+ Pid = whereis(couch_epi:get_handle(Key)),
+ Handle = couch_epi:get_handle(Key),
+
#ctx{
key = Key,
- handle = couch_epi:get_handle(Key),
+ handle = Handle,
+ modules = [Handle, provider1, provider2],
kv = KV,
pid = Pid};
-setup(_Opts) ->
- setup(couch_epi_functions).
-
-teardown(Module, #ctx{pid = Pid} = Ctx) when is_atom(Module) ->
- Module:stop(Pid),
- teardown(Ctx);
-teardown(_Opts, #ctx{pid = Pid} = Ctx) ->
- couch_epi_functions:stop(Pid),
+setup({options, _Opts}) ->
+ setup(functions).
+
+teardown(_Case, #ctx{} = Ctx) ->
teardown(Ctx).
teardown(#ctx{file = File} = Ctx) when File /= undefined ->
@@ -152,26 +228,25 @@ teardown(#ctx{kv = KV}) ->
application:stop(couch_epi),
ok.
-upgrade_release(Pid, Module) ->
+upgrade_release(Pid, Modules) ->
sys:suspend(Pid),
- 'ok' = sys:change_code(Pid, Module, 'undefined', []),
+ [ok = sys:change_code(Pid, M, undefined, []) || M <- Modules],
sys:resume(Pid),
ok.
epi_config_update_test_() ->
Funs = [
fun ensure_notified_when_changed/2,
- fun ensure_not_notified_when_no_change/2,
- fun ensure_not_notified_when_unsubscribed/2
+ fun ensure_not_notified_when_no_change/2
],
- Modules= [
- couch_epi_data,
- couch_epi_data_source,
- couch_epi_functions
+ Cases = [
+ data_file,
+ data_module,
+ functions
],
{
"config update tests",
- [make_case("Check notifications for: ", Modules, Funs)]
+ [make_case("Check notifications for: ", Cases, Funs)]
}.
epi_data_source_test_() ->
@@ -184,13 +259,13 @@ epi_data_source_test_() ->
fun check_keys/2,
fun check_subscribers/2
],
- Modules= [
- couch_epi_data,
- couch_epi_data_source
+ Cases = [
+ data_file,
+ data_module
],
{
"epi data API tests",
- [make_case("Check query API for: ", Modules, Funs)]
+ [make_case("Check query API for: ", Cases, Funs)]
}.
@@ -199,7 +274,7 @@ epi_apply_test_() ->
"epi dispatch tests",
{
foreach,
- fun() -> setup(couch_epi_functions) end,
+ fun() -> setup(functions) end,
fun teardown/1,
[
fun check_pipe/1,
@@ -210,27 +285,11 @@ epi_apply_test_() ->
}
}.
-
-epi_subscription_test_() ->
- Funs = [
- fun ensure_unsubscribe_when_caller_die/2
- ],
- Modules= [
- couch_epi_data,
- couch_epi_data_source,
- couch_epi_functions
- ],
- {
- "epi subscription tests",
- [make_case("Check subscription API for: ", Modules, Funs)]
- }.
-
-
epi_reload_test_() ->
- Modules= [
- couch_epi_data,
- couch_epi_data_source,
- couch_epi_functions
+ Cases = [
+ data_file,
+ data_module,
+ functions
],
Funs = [
fun ensure_reload_if_manually_triggered/2,
@@ -239,30 +298,39 @@ epi_reload_test_() ->
],
{
"epi reload tests",
- {
- foreachx,
- fun setup/1,
- fun teardown/2,
- [{M, Fun} || M <- Modules, Fun <- Funs]
- }
+ [make_case("Check reload for: ", Cases, Funs)]
}.
-
apply_options_test_() ->
Funs = [fun ensure_apply_is_called/2],
- make_case("Apply with options: ", valid_options_permutations(), Funs).
+ Setups = {options, valid_options_permutations()},
+ {
+ "apply options tests",
+ [make_case("Apply with options: ", Setups, Funs)]
+ }.
+make_case(Msg, {Tag, P}, Funs) ->
+ Cases = [{Tag, Case} || Case <- P],
+ make_case(Msg, Cases, Funs);
make_case(Msg, P, Funs) ->
[{format_case_name(Msg, Case), [
{
foreachx, fun setup/1, fun teardown/2,
[
- {Case, Fun} || Fun <- Funs
+ {Case, make_fun(Fun, 2)} || Fun <- Funs
]
}
]} || Case <- P].
+make_fun(Fun, Arity) ->
+ {arity, A} = lists:keyfind(arity, 1, erlang:fun_info(Fun)),
+ make_fun(Fun, Arity, A).
+
+make_fun(Fun, A, A) -> Fun;
+make_fun(Fun, 2, 1) -> fun(_, A) -> Fun(A) end;
+make_fun(Fun, 1, 2) -> fun(A) -> Fun(undefined, A) end.
+
format_case_name(Msg, Case) ->
lists:flatten(Msg ++ io_lib:format("~p", [Case])).
@@ -276,61 +344,54 @@ valid_options_permutations() ->
[concurrent, ignore_errors]
].
-ensure_notified_when_changed(couch_epi_functions, #ctx{key = Key} = Ctx) ->
+ensure_notified_when_changed(functions, #ctx{key = Key} = Ctx) ->
?_test(begin
subscribe(Ctx, test_app, Key),
- update(couch_epi_functions, Ctx),
+ update(functions, Ctx),
timer:sleep(200),
Result = get(Ctx, is_called),
- Expected = {test_app, Key,
- {modules, [provider1, provider2]},
- {modules, [provider1, provider2]}},
- ?assertMatch({ok, Expected}, Result),
+ ExpectedDefs = [
+ {provider1,[{inc,2},{fail,2}]},
+ {provider2,[{inc,2},{fail,2}]}
+ ],
+ ?assertEqual({ok, {Key, ExpectedDefs, ExpectedDefs}}, Result),
ok
end);
-ensure_notified_when_changed(Module, #ctx{key = Key} = Ctx) ->
+ensure_notified_when_changed(Case, #ctx{key = Key} = Ctx) ->
?_test(begin
subscribe(Ctx, test_app, Key),
- update(Module, Ctx),
+ update(Case, Ctx),
timer:sleep(200),
ExpectedData = lists:usort([
{[complex, key, 1], [{type, counter}, {desc, updated_foo}]},
{[complex, key, 2], [{type, counter}, {desc, bar}]}
]),
Result = get(Ctx, is_called),
- ?assertMatch({ok, {test_app, Key, {data, _}, {data, _}}}, Result),
- {ok, {test_app, Key, {data, OldData}, {data, Data}}} = Result,
+ ?assertMatch({ok, {Key, _OldData, _Data}}, Result),
+ {ok, {Key, OldData, Data}} = Result,
?assertMatch(ExpectedData, lists:usort(Data)),
?assertMatch(
[{[complex, key, 1], [{type, counter}, {desc, foo}]}],
lists:usort(OldData))
end).
-ensure_not_notified_when_no_change(_Module, #ctx{key = Key} = Ctx) ->
+ensure_not_notified_when_no_change(_Case, #ctx{key = Key} = Ctx) ->
?_test(begin
subscribe(Ctx, test_app, Key),
timer:sleep(200),
?assertMatch(error, get(Ctx, is_called))
end).
-ensure_not_notified_when_unsubscribed(Module, #ctx{key = Key} = Ctx) ->
- ?_test(begin
- SubscriptionId = subscribe(Ctx, test_app, Key),
- couch_epi:unsubscribe(SubscriptionId),
- timer:sleep(100),
- update(Module, Ctx),
- timer:sleep(200),
- ?assertMatch(error, get(Ctx, is_called))
- end).
-
-ensure_apply_is_called(Opts, #ctx{handle = Handle, kv = KV, key = Key} = Ctx) ->
+ensure_apply_is_called({options, Opts}, #ctx{handle = Handle, kv = KV, key = Key} = Ctx) ->
?_test(begin
couch_epi:apply(Handle, Key, inc, [KV, 2], Opts),
maybe_wait(Opts),
?assertMatch({ok, _}, get(Ctx, inc1)),
?assertMatch({ok, _}, get(Ctx, inc2)),
ok
- end).
+ end);
+ensure_apply_is_called(undefined, #ctx{} = Ctx) ->
+ ensure_apply_is_called({options, []}, Ctx).
check_pipe(#ctx{handle = Handle, kv = KV, key = Key}) ->
?_test(begin
@@ -361,42 +422,32 @@ ensure_fail(#ctx{handle = Handle, kv = KV, key = Key}) ->
ok
end).
-ensure_unsubscribe_when_caller_die(_Module, #ctx{key = Key} = Ctx) ->
- ?_test(begin
- spawn(fun() ->
- subscribe(Ctx, test_app, Key)
- end),
- timer:sleep(200),
- ?assertMatch(error, get(Ctx, is_called))
- end).
-
-
pipe_state(Ctx) ->
Trace = [get(Ctx, inc1), get(Ctx, inc2)],
lists:usort([State || {ok, State} <- Trace]).
-check_dump(_Module, #ctx{handle = Handle}) ->
+check_dump(_Case, #ctx{handle = Handle}) ->
?_test(begin
?assertMatch(
[[{type, counter}, {desc, foo}]],
couch_epi:dump(Handle))
end).
-check_get(_Module, #ctx{handle = Handle}) ->
+check_get(_Case, #ctx{handle = Handle}) ->
?_test(begin
?assertMatch(
[[{type, counter}, {desc, foo}]],
couch_epi:get(Handle, [complex,key, 1]))
end).
-check_get_value(_Module, #ctx{handle = Handle}) ->
+check_get_value(_Case, #ctx{handle = Handle}) ->
?_test(begin
?assertMatch(
[{type, counter}, {desc, foo}],
couch_epi:get_value(Handle, test_app, [complex,key, 1]))
end).
-check_by_key(_Module, #ctx{handle = Handle}) ->
+check_by_key(_Case, #ctx{handle = Handle}) ->
?_test(begin
?assertMatch(
[{[complex, key, 1],
@@ -407,7 +458,7 @@ check_by_key(_Module, #ctx{handle = Handle}) ->
couch_epi:by_key(Handle, [complex, key, 1]))
end).
-check_by_source(_Module, #ctx{handle = Handle}) ->
+check_by_source(_Case, #ctx{handle = Handle}) ->
?_test(begin
?assertMatch(
[{test_app,
@@ -418,65 +469,60 @@ check_by_source(_Module, #ctx{handle = Handle}) ->
couch_epi:by_source(Handle, test_app))
end).
-check_keys(_Module, #ctx{handle = Handle}) ->
+check_keys(_Case, #ctx{handle = Handle}) ->
?_assertMatch([[complex,key,1]], couch_epi:keys(Handle)).
-check_subscribers(_Module, #ctx{handle = Handle}) ->
+check_subscribers(_Case, #ctx{handle = Handle}) ->
?_assertMatch([test_app], couch_epi:subscribers(Handle)).
-ensure_reload_if_manually_triggered(Module, #ctx{pid = Pid, key = Key} = Ctx) ->
+ensure_reload_if_manually_triggered(Case, #ctx{pid = Pid, key = Key} = Ctx) ->
?_test(begin
subscribe(Ctx, test_app, Key),
- update_definitions(Module, Ctx),
- Module:reload(Pid),
+ update_definitions(Case, Ctx),
+ couch_epi_module_keeper:reload(Pid),
timer:sleep(50),
- Result = get(Ctx, is_called),
- ?assertNotMatch(error, Result)
+ ?assertNotEqual(error, get(Ctx, is_called))
end).
-ensure_reload_if_changed(couch_epi_data_source = Module,
+ensure_reload_if_changed(data_file = Case,
#ctx{key = Key, handle = Handle} = Ctx) ->
?_test(begin
Version = Handle:version(),
subscribe(Ctx, test_app, Key),
- update_definitions(Module, Ctx),
+ update_definitions(Case, Ctx),
timer:sleep(250),
?assertNotEqual(Version, Handle:version()),
- Result = get(Ctx, is_called),
- ?assertNotMatch(error, Result)
+ ?assertNotEqual(error, get(Ctx, is_called))
end);
-ensure_reload_if_changed(Module,
+ensure_reload_if_changed(Case,
#ctx{key = Key, handle = Handle} = Ctx) ->
?_test(begin
Version = Handle:version(),
subscribe(Ctx, test_app, Key),
- update(Module, Ctx),
+ update(Case, Ctx),
?assertNotEqual(Version, Handle:version()),
timer:sleep(100), %% Allow some time for notify to be called
- Result = get(Ctx, is_called),
- ?assertNotMatch(error, Result)
+ ?assertNotEqual(error, get(Ctx, is_called))
end).
-ensure_no_reload_when_no_change(couch_epi_functions = Module,
- #ctx{pid = Pid, key = Key, handle = Handle} = Ctx) ->
+ensure_no_reload_when_no_change(functions,
+ #ctx{pid = Pid, key = Key, handle = Handle, modules = Modules} = Ctx) ->
?_test(begin
Version = Handle:version(),
subscribe(Ctx, test_app, Key),
- upgrade_release(Pid, Module),
+ upgrade_release(Pid, Modules),
?assertEqual(Version, Handle:version()),
- Result = get(Ctx, is_called),
- ?assertMatch(error, Result)
+ ?assertEqual(error, get(Ctx, is_called))
end);
-ensure_no_reload_when_no_change(Module,
+ensure_no_reload_when_no_change(_Case,
#ctx{key = Key, handle = Handle} = Ctx) ->
?_test(begin
Version = Handle:version(),
subscribe(Ctx, test_app, Key),
timer:sleep(450),
?assertEqual(Version, Handle:version()),
- Result = get(Ctx, is_called),
- ?assertMatch(error, Result)
+ ?assertEqual(error, get(Ctx, is_called))
end).
@@ -488,24 +534,21 @@ generate_module(Name, Body) ->
Tokens = couch_epi_codegen:scan(Body),
couch_epi_codegen:generate(Name, Tokens).
-update(Module, #ctx{pid = Pid} = Ctx) ->
- update_definitions(Module, Ctx),
- upgrade_release(Pid, Module).
+update(Case, #ctx{pid = Pid, modules = Modules} = Ctx) ->
+ update_definitions(Case, Ctx),
+ upgrade_release(Pid, Modules).
-update_definitions(couch_epi_data_source, #ctx{file = File}) ->
+update_definitions(data_file, #ctx{file = File}) ->
{ok, _} = file:copy(?DATA_FILE2, File),
ok;
-update_definitions(couch_epi_data, #ctx{}) ->
+update_definitions(data_module, #ctx{}) ->
ok = generate_module(provider, ?DATA_MODULE2(provider));
-update_definitions(couch_epi_functions, #ctx{}) ->
+update_definitions(functions, #ctx{}) ->
ok = generate_module(provider1, ?MODULE2(provider1)).
-
-
-subscribe(#ctx{kv = Kv}, App, Key) ->
- {ok, Pid} = couch_epi:subscribe(App, Key, ?MODULE, notify_cb, Kv),
+subscribe(#ctx{kv = Kv}, _App, _Key) ->
call(Kv, empty),
- Pid.
+ ok.
maybe_wait(Opts) ->
case lists:member(concurrent, Opts) of
@@ -537,6 +580,12 @@ call(Server, Msg) ->
reply({Ref, From}, Msg) ->
From ! {reply, Ref, Msg}.
+start_state_storage() ->
+ Pid = state_storage(),
+ Name = ?temp_atom(),
+ register(Name, Pid),
+ Name.
+
state_storage() ->
spawn_link(fun() -> state_storage(dict:new()) end).