From 2fe0c7e1c1b701e8aee7631ad548085c9eaac55b Mon Sep 17 00:00:00 2001 From: "H. Diedrich" Date: Fri, 28 Nov 2014 16:08:12 +0100 Subject: [PATCH 1/7] Dynamic endpoints handlers chttpd hardcoded handlers are replaced with with dynamic url handlers, which are functions in a dynamically created module, assembled from priv/chttpd_handler.cfg files from all interested applications. For couched these are currently: chttpd, mem3 and global_changes. This is a special branch that pull the prepared branches (of the same name) of chttpd, mem3 and global_changes, which have the config files. Run 'make check' to test the handlers. They are implicitly tested by other tests and there is a whitebox callback test chttpd_handler_callback_test, which calls the dynamic functions directly and mocks the funs that they return. This tests precisely the relationship between the *_handler function clauses and the returned function. There are more tests to come, both testing the endpoints via http, and the configuration assembly. BugzID: 27037 --- src/chttpd.app.src | 4 +- src/chttpd.erl | 44 +--- src/chttpd_handler.erl | 202 +++++++++++++++++ test/chttpd_handler_callback_test.erl | 308 ++++++++++++++++++++++++++ 4 files changed, 517 insertions(+), 41 deletions(-) create mode 100644 src/chttpd_handler.erl create mode 100644 test/chttpd_handler_callback_test.erl diff --git a/src/chttpd.app.src b/src/chttpd.app.src index 2230849..2374143 100644 --- a/src/chttpd.app.src +++ b/src/chttpd.app.src @@ -41,7 +41,9 @@ couch, ets_lru, fabric, - cassim + cassim, + mem3, + global_changes ]}, {mod, {chttpd_app,[]}} ]}. diff --git a/src/chttpd.erl b/src/chttpd.erl index 8945d0a..1ff86fc 100644 --- a/src/chttpd.erl +++ b/src/chttpd.erl @@ -100,6 +100,7 @@ start_link(https) -> start_link(https, Options). start_link(Name, Options) -> + chttpd_handler:build(), Options1 = Options ++ [ {loop, fun ?MODULE:handle_request/1}, {name, Name}, @@ -187,8 +188,8 @@ handle_request(MochiReq) -> method = Method, path_parts = [list_to_binary(chttpd:unquote(Part)) || Part <- string:tokens(Path, "/")], - db_url_handlers = db_url_handlers(), - design_url_handlers = design_url_handlers() + db_url_handlers = chttpd_handler:db_url_handlers(), + design_url_handlers = chttpd_handler:design_url_handlers() }, % put small token on heap to keep requests synced to backend calls @@ -204,7 +205,7 @@ handle_request(MochiReq) -> #httpd{} -> case authenticate_request(HttpReq, AuthenticationFuns) of #httpd{} = Req -> - HandlerFun = url_handler(HandlerKey), + HandlerFun = chttpd_handler:url_handler(HandlerKey), HandlerFun(chttpd_auth_request:authorize_request(possibly_hack(Req))); Response -> Response @@ -357,43 +358,6 @@ authenticate_request(Response, _AuthFuns) -> increment_method_stats(Method) -> couch_stats:increment_counter([couchdb, httpd_request_methods, Method]). -url_handler("") -> fun chttpd_misc:handle_welcome_req/1; -url_handler("favicon.ico") -> fun chttpd_misc:handle_favicon_req/1; -url_handler("_utils") -> fun chttpd_misc:handle_utils_dir_req/1; -url_handler("_all_dbs") -> fun chttpd_misc:handle_all_dbs_req/1; -url_handler("_active_tasks") -> fun chttpd_misc:handle_task_status_req/1; -url_handler("_config") -> fun chttpd_misc:handle_config_req/1; -url_handler("_reload_query_servers") -> fun chttpd_misc:handle_reload_query_servers_req/1; -url_handler("_replicate") -> fun chttpd_misc:handle_replicate_req/1; -url_handler("_uuids") -> fun chttpd_misc:handle_uuids_req/1; -url_handler("_sleep") -> fun chttpd_misc:handle_sleep_req/1; -url_handler("_session") -> fun chttpd_auth:handle_session_req/1; -url_handler("_oauth") -> fun couch_httpd_oauth:handle_oauth_req/1; -url_handler("_up") -> fun chttpd_misc:handle_up_req/1; -url_handler("_membership") -> fun mem3_httpd:handle_membership_req/1; -url_handler("_db_updates") -> fun global_changes_httpd:handle_global_changes_req/1; -url_handler(_) -> fun chttpd_db:handle_request/1. - -db_url_handlers() -> - [ - {<<"_view_cleanup">>, fun chttpd_db:handle_view_cleanup_req/2}, - {<<"_compact">>, fun chttpd_db:handle_compact_req/2}, - {<<"_design">>, fun chttpd_db:handle_design_req/2}, - {<<"_temp_view">>, fun chttpd_view:handle_temp_view_req/2}, - {<<"_changes">>, fun chttpd_db:handle_changes_req/2}, - {<<"_shards">>, fun mem3_httpd:handle_shards_req/2} - ]. - -design_url_handlers() -> - [ - {<<"_view">>, fun chttpd_view:handle_view_req/3}, - {<<"_show">>, fun chttpd_show:handle_doc_show_req/3}, - {<<"_list">>, fun chttpd_show:handle_view_list_req/3}, - {<<"_update">>, fun chttpd_show:handle_doc_update_req/3}, - {<<"_info">>, fun chttpd_db:handle_design_info_req/3}, - {<<"_rewrite">>, fun chttpd_rewrite:handle_rewrite_req/3} - ]. - % Utilities partition(Path) -> diff --git a/src/chttpd_handler.erl b/src/chttpd_handler.erl new file mode 100644 index 0000000..beb7c36 --- /dev/null +++ b/src/chttpd_handler.erl @@ -0,0 +1,202 @@ +%% 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. + +%% @doc Configurable, dynamic creation of endpoint handler callback indirections. + +-module(chttpd_handler). + +-export([build/0, build/1, url_handler/1, db_url_handlers/0, + design_url_handlers/0]). + +-vsn(4). + +%% @doc a complete configuration data set +-type config() :: [Function::{Name::atom(), clauses|list, [bind()]}]. + +%% @doc one essential pair of a pattern and the fun to be returned for it +-type bind() :: {Endpoint::term(), MFA::{atom(), atom(), integer()}}. + +-spec url_handler(Endpoint::list()) -> Handler::fun(). +%% @doc Dispatch endpoint to fun, wrapper to hide dynamic module. +url_handler(Endpoint) -> + chttpd_dyn_handler:url_handler(Endpoint). + +-spec db_url_handlers() -> [{Endpoint::list(), Handler::fun()}]. +%% @doc Get a list of endpoints and handler funs, wrapper to hide dyn module. +db_url_handlers() -> + chttpd_dyn_handler:db_url_handlers(). + +-spec design_url_handlers() -> [{Endpoint::list(), Handler::fun()}]. +%% @doc Get a list of endpoints and handler funs, wrapper to hide dyn module. +design_url_handlers() -> + chttpd_dyn_handler:design_url_handlers(). + +-spec build() -> ok | []. +%% @doc Create the dynamic handler functions from ini file. +build() -> + build(load_defs()). + +-spec build(HandlerCfg::config()) -> ok. +%% @doc Compile the complete syntax tree, purge and load the dynamic module +build(Cfg) when is_list(Cfg) -> + Opts = [verbose, report_errors], + {ok, Mod, Bin} = compile:forms(forms(chttpd_dyn_handler, Cfg), Opts), + % don't code:purge(Mod), + {module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin), + ok. + +-spec load_defs() -> CombinedHandlerCfg::config(). +%% @doc assemble the configuration from the chttpd_handler.cfg of all apps. +load_defs() -> + {AllURLHandlers, AllDBHandlers, AllDesignHandlers} = lists:foldl( + fun(App, {URLHandlers, DBHandlers, DesignHandlers}) -> + Defs = load_defs(App), + {URLHandlers ++ [ B || {docs, clauses, B} <- Defs ], + DBHandlers ++ [ B || {db, list, B} <- Defs ], + DesignHandlers ++ [ B || {design, list, B} <- Defs ]} + end, + {[],[],[]}, + [element(1, A) || A <- application:loaded_applications()]), + [{url_handler, clauses, lists:flatten(AllURLHandlers)}, + {db_url_handlers, list, lists:flatten(AllDBHandlers)}, + {design_url_handlers, list, lists:flatten(AllDesignHandlers)}]. + +-spec load_defs(AppName::atom()) -> OneAppsHandlerCfg::config(). +%% @doc assemble the configuration from the chttpd_handler.cfg of all apps. +load_defs(App) -> + case code:priv_dir(App) of + {error, _Error} -> + []; + Dir -> + Path = Dir ++ "/chttpd_handler.cfg", + case file:consult(Path) of + {ok, Defs} -> + [ check_def(Def, Path) || Def <- Defs ]; + {error, _Error} -> + [] + end + end. + +check_def({_, _, []}=Def, Path) -> + throw({no_defs_error, Def, Path}); +check_def({docs, clauses, B}, Path) -> + {docs, clauses, sort(check_dupes(check_bindings(B, list, 1, Path), Path))}; +check_def({db, list, B}, Path) -> + {db, list, check_dupes(check_bindings(B, binary, 2, Path), Path)}; +check_def({design, list, B}, Path) -> + {design, list, check_dupes(check_bindings(B, binary, 3, Path), Path)}; +check_def(Def, Path) -> + throw({tag_error, Def, Path}). + +check_bindings([{Endpoint, {M, F, Arity}}=Good | More], list, Arity, Path) + when is_list(Endpoint), is_atom(M), is_atom(F), is_integer(Arity) -> + [Good | check_bindings(More, list, Arity, Path)]; +check_bindings([{Endpoint, {M, F, Arity}}=Good | More], binary, Arity, Path) + when is_binary(Endpoint), is_atom(M), is_atom(F), is_integer(Arity) -> + [Good | check_bindings(More, binary, Arity, Path)]; +check_bindings([{'_', {M, F, Arity}}=Good | More], Type, Arity, Path) + when is_atom(M), is_atom(F), is_integer(Arity) -> + [Good | check_bindings(More, Type, Arity, Path)]; +check_bindings([Bad | _], _, Arity, Path) -> + throw({syntax_or_arity_error, Bad, exptected_arity, Arity, Path}); +check_bindings([], _, _, _) -> + []. + +-spec sort(Cfg::config()) -> config(). +%% @doc make sure that any _ is the last clause of a generated function +sort(Cfg) -> + lists:sort( + fun ({'_',_}, _) -> false; + (_, {'_',_}) -> true; + (_, _) -> true + end, + Cfg). + +-spec check_dupes(Cfg::config(), Path::list()) -> config() | []. +%% @doc crash if an endpoint is defined twice +check_dupes(Cfg, Path) -> + lists:sort( + fun ({E,_}=Def1, {E, _}=Def2) -> + throw({duplicate_endpoint, E, Def1, Def2, Path}); + (_, _) -> true + end, + Cfg). + +-spec forms(Mod::atom(), Defs::[bind()]) -> erl_syntax:syntaxTree(). +%% @doc The complete syntax tree of the dynamic module +forms(Mod, Defs) -> + Statements = [ + module_stmt(Mod), + export_stmt( + [{Name, case Lay of clauses -> 1; list -> 0 end} + || {Name, Lay, _} <- Defs ]) + | [ binding_function(Name, Lay, Def) || {Name, Lay, Def} <- Defs ]], + [ erl_syntax:revert(X) || X <- Statements]. + +-spec module_stmt(ModuleName::atom()) -> erl_syntax:syntaxTree(). +%% @doc Create syntax tree for the module statement of the dynamic module +module_stmt(Name) -> + erl_syntax:attribute( + erl_syntax:atom(module), + [erl_syntax:atom(Name)]). + +-spec export_stmt([Exports::{Name::atom(), Arity::integer()}]) -> + erl_syntax:syntaxTree(). +%% @doc Create syntax tree for the export statement of the dynamic module +export_stmt(Exports) -> + erl_syntax:attribute( + erl_syntax:atom(export), + [erl_syntax:list( + [ erl_syntax:arity_qualifier( + erl_syntax:atom(Name), + erl_syntax:integer(Arity)) + || {Name, Arity} <- Exports ] + )]). + +-spec binding_function(Name::atom(), clauses | list, [bind()]) -> + erl_syntax:syntaxTree(). +%% @doc Create syntax subtree for a function that either has multiple clauses +%% or returns a list of tuples of a tag and a fun. +binding_function(Name, list, Defs) -> + erl_syntax:function( + erl_syntax:atom(Name), + [erl_syntax:clause([], none, + [erl_syntax:list( + [erl_syntax:tuple([ + erl_syntax:abstract(P), + create_fun(Def)]) + || {P, Def} <- Defs ])])]); +binding_function(Name, clauses, Defs) -> + erl_syntax:function( + erl_syntax:atom(Name), + [create_fun_clause(Def) || Def <- sort(Defs)]). + +-spec create_fun_clause(bind()) -> erl_syntax:syntaxTree(). +%% @doc Create syntax subtree for a function clause with one implicit fun call. +create_fun_clause({P, MFA}) -> + erl_syntax:clause( + [case P of + '_' -> erl_syntax:underscore(); + _ -> erl_syntax:abstract(P) + end], + none, + [create_fun(MFA)]). + +-spec create_fun(MFA::{Module::atom(), Function::atom(), Arity::integer()}) -> + erl_syntax:syntaxTree(). +%% @doc Create syntax subtree for an implicit fun call. +create_fun({M, F, A}) -> + erl_syntax:implicit_fun( + erl_syntax:atom(M), + erl_syntax:atom(F), + erl_syntax:integer(A)). + diff --git a/test/chttpd_handler_callback_test.erl b/test/chttpd_handler_callback_test.erl new file mode 100644 index 0000000..1e2387a --- /dev/null +++ b/test/chttpd_handler_callback_test.erl @@ -0,0 +1,308 @@ +%% 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. +%% +%% @doc +%% These whitebox tests call the endpoints that are implemented by dynamic funs, +%% which replace the previously hardwired handlers, with a dynamically created +%% module. This test mocks everything except the proper relationship between +%% handler function clause and returned fun. The handler functions are called +%% directly. +%% +%% This can look like mocks testing themselves. In this case, in other words: +%% think of the former url_handler function in chttpd.erl: The relationship +%% between a function clause and the specific returned fun() is exactly what is +%% established by the dynamically created module. And it's precisely what this +%% test verifies, for every endpoint. +%% +%% This test was part of chttpd-cloudant dbcore-2.7.7, verifying the original +%% hardcoded endpoints. It has since been reworked but remains functionally +%% mostly identical; the config source and processing has been replaced. + +-module(chttpd_handler_callback_test). + +-include_lib("eunit/include/eunit.hrl"). + +all_test_() -> + io:format(user, "~nEndpoint handler callbacks:~n", []), + {setup, + fun() -> chttpd_handler:build(test_cfg()) end, + [ + + % url handlers + fun() -> + assertReturns("", chttpd_misc, handle_welcome_req) + end, + fun() -> + assertReturns("favicon.ico", chttpd_misc, handle_favicon_req) + end, + fun() -> + assertReturns("_utils", chttpd_misc, handle_utils_dir_req) + end, + fun() -> + assertReturns("_all_dbs", chttpd_misc, handle_all_dbs_req) + end, + fun() -> + assertReturns("_active_tasks", chttpd_misc, handle_task_status_req) + end, + fun() -> + assertReturns("_config", chttpd_misc, handle_config_req) + end, + fun() -> + assertReturns("_reload_query_servers", chttpd_misc, + handle_reload_query_servers_req) + end, + fun() -> + assertReturns("_replicate", chttpd_misc, handle_replicate_req) + end, + fun() -> + assertReturns("_uuids", chttpd_misc, handle_uuids_req) + end, + fun() -> + assertReturns("_log", chttpd_misc, handle_log_req) + end, + fun() -> + assertReturns("_sleep", chttpd_misc, handle_sleep_req) + end, + fun() -> + assertReturns("_session", chttpd_auth, handle_session_req) + end, + fun() -> + assertReturns("_user", chttpd_auth, handle_user_req) + end, + fun() -> + assertReturns("_oauth", couch_httpd_oauth, handle_oauth_req) + end, + fun() -> + assertReturns("_system", chttpd_misc, handle_system_req) + end, + fun() -> + assertReturns("_pid", chttpd_misc, handle_pid_req) + end, + fun() -> + assertReturns("_up", chttpd_misc, handle_up_req) + end, + fun() -> + assertReturns("_membership", mem3_httpd, handle_membership_req) + end, + fun() -> + assertReturns("_cloudant", showroom_httpd_admin, handle_cloudant_req) + end, + fun() -> + assertReturns("_ioq", chttpd_misc, handle_ioq_req) + end, + fun() -> + assertReturns("_sauth", delegated_auth, handle_delegated_auth_req) + end, + fun() -> + assertReturns("_search_analyze", dreyfus_httpd, handle_analyze_req) + end, + fun() -> + assertReturns("_db_updates", global_changes_httpd, + handle_global_changes_req) end, + fun() -> + assertReturns("anything", chttpd_db, handle_request) + end, + + % Test the tests: if the final target function is missing, the call must + % fail, with any parameter. All parameters are valid. + fun() -> + assertUnmockedFails("", chttpd_misc) + end, + fun() -> + assertUnmockedFails("favicon.ico", chttpd_misc) + end, + fun() -> + assertUnmockedFails(anything, chttpd_db) + end, + + + % db url handler tests + + fun() -> + assertReturns(db_url_handlers, <<"_view_cleanup">>, chttpd_db, + handle_view_cleanup_req, 2) end, + fun() -> + assertReturns(db_url_handlers, <<"_compact">>, chttpd_db, + handle_compact_req, 2) + end, + fun() -> + assertReturns(db_url_handlers, <<"_design">>, chttpd_db, + handle_design_req, 2) + end, + fun() -> + assertReturns(db_url_handlers, <<"_temp_view">>, chttpd_view, + handle_temp_view_req, 2) end, + fun() -> + assertReturns(db_url_handlers, <<"_changes">>, chttpd_db, + handle_changes_req, 2) end, + fun() -> + assertReturns(db_url_handlers, <<"_search_cleanup">>, dreyfus_httpd, + handle_cleanup_req, 2) + end, + fun() -> + assertReturns(db_url_handlers, <<"_shards">>, mem3_httpd, + handle_shards_req, 2) end, + fun() -> + assertReturns(db_url_handlers, <<"_index">>, mango_httpd, handle_req, 2) + end, + fun() -> + assertReturns(db_url_handlers, <<"_find">>, mango_httpd, handle_req, 2) + end, + + % Test the test: when the final target function is missing, the Fun call + % must fail, with any argument, including valid ones. + fun() -> + assertUnmockedFails(db_url_handlers, <<"_view_cleanup">>, chttpd_db, 2) + end, + + % Test the test: when the argument is unknown, the Fun call must fail. + fun() -> + assertUnknownFails(db_url_handlers, <<"_something">>) + end, + + + % design url handler tests + + fun() -> + assertReturns(design_url_handlers, <<"_view">>, chttpd_view, + handle_view_req, 3) end, + fun() -> + assertReturns(design_url_handlers, <<"_show">>, chttpd_show, + handle_doc_show_req, 3) end, + fun() -> + assertReturns(design_url_handlers, <<"_list">>, chttpd_show, + handle_view_list_req, 3) end, + fun() -> + assertReturns(design_url_handlers, <<"_update">>, chttpd_show, + handle_doc_update_req, 3) end, + fun() -> + assertReturns(design_url_handlers, <<"_info">>, chttpd_db, + handle_design_info_req, 3) end, + fun() -> + assertReturns(design_url_handlers, <<"_rewrite">>, chttpd_rewrite, + handle_rewrite_req, 3) end, + fun() -> + assertReturns(design_url_handlers, <<"_search">>, dreyfus_httpd, + handle_search_req, 3) end, + fun() -> + assertReturns(design_url_handlers, <<"_search_info">>, dreyfus_httpd, + handle_info_req, 3) end, + + % Test the test: when the final target function is missing, the Fun call + % must fail with any argument, including valid ones. + fun() -> + assertUnmockedFails(design_url_handlers, <<"_view">>, chttpd_view, 3) + end, + % Test the test: when the argument is unknown, the Fun call must fail. + fun() -> + assertUnknownFails(design_url_handlers, <<"_something">>) + end]}. + + +%% Call the dynamic function with a parameter known to trigger a specific +%% clause. Then call the returned fun and get confirmation that this called +%% the right implicit fun, which is mocked, and returns the expected tuple. +assertReturns(Endpoint, M, F) -> + meck:new(M, [passthrough, non_strict]), + try + io:format(user, "~-47...s ", [Endpoint]), + meck:expect(M, F, fun(X) -> {return, Endpoint, X} end), + Fun = chttpd_handler:url_handler(Endpoint), + ?assertEqual({return, Endpoint, x}, Fun(x)), + io:format(user, "ok~n", []) + after + meck:unload(M) + end. + +%% Test that the indirection does NOT work when the function does not exist. +assertUnmockedFails(Endpoint, M) -> + meck:new(M, [non_strict]), + try + Fun = chttpd_handler:url_handler(Endpoint), + ?assertError(undef, Fun(x)) + after + meck:unload(M) + end. + +%% Same as assertReturns/3 but for dynamic functions that return a list of funs. +assertReturns(HandlerLister, Endpoint, M, F, Arity) -> + meck:new(M, [passthrough, non_strict]), + try + io:format(user, "~-47...s ", [Endpoint]), + case Arity of + 2 -> + meck:expect(M, F, fun(X, Y) -> {return, Endpoint, X, Y} end), + {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), + ?assertEqual({return, Endpoint, x, y}, Fun(x, y)); + 3 -> + meck:expect(M, F, fun(X, Y, Z) -> {return, Endpoint, X, Y, Z} end), + {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), + ?assertEqual({return, Endpoint, x, y, z}, Fun(x, y, z)) + end, + io:format(user, "ok~n", []) + after + meck:unload(M) + end. + +%% Test that the indirection does NOT work when the function does not exist. +assertUnmockedFails(HandlerLister, Endpoint, M, Arity) -> + meck:new(M, [non_strict]), + try + {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), + case Arity of + 2 -> ?assertError(undef, Fun(x, y)); + 3 -> ?assertError(undef, Fun(x, y, z)) + end + after + meck:unload(M) + end. + +%% Make sure that a wrong parameter also really fails. +assertUnknownFails(HandlerLister, Endpoint) -> + false = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()). + +test_cfg() -> +[{url_handler, clauses, [ + {"", {chttpd_misc, handle_welcome_req, 1}}, + {"favicon.ico", {chttpd_misc, handle_favicon_req, 1}}, + {"_utils", {chttpd_misc, handle_utils_dir_req, 1}}, + {"_all_dbs", {chttpd_misc, handle_all_dbs_req, 1}}, + {"_active_tasks", {chttpd_misc, handle_task_status_req, 1}}, + {"_config", {chttpd_misc, handle_config_req, 1}}, + {"_reload_query_servers", {chttpd_misc, + handle_reload_query_servers_req, 1}}, + {"_replicate", {chttpd_misc, handle_replicate_req, 1}}, + {"_uuids", {chttpd_misc, handle_uuids_req, 1}}, + {"_sleep", {chttpd_misc, handle_sleep_req, 1}}, + {"_session", {chttpd_auth, handle_session_req, 1}}, + {"_oauth", {couch_httpd_oauth, handle_oauth_req, 1}}, + {"_up", {chttpd_misc, handle_up_req, 1}}, + {"_membership", {mem3_httpd, handle_membership_req, 1}}, + {"_db_updates", {global_changes_httpd, + handle_global_changes_req, 1}}, + {'_', {chttpd_db, handle_request, 1}}]}, +{db_url_handlers, list, [ + {<<"_view_cleanup">>, {chttpd_db, handle_view_cleanup_req, 2}}, + {<<"_compact">>, {chttpd_db, handle_compact_req, 2}}, + {<<"_design">>, {chttpd_db, handle_design_req, 2}}, + {<<"_temp_view">>, {chttpd_view, handle_temp_view_req, 2}}, + {<<"_changes">>, {chttpd_db, handle_changes_req, 2}}, + {<<"_shards">>, {mem3_httpd, handle_shards_req, 2}} +{design_url_handlers, list, [ + {<<"_view">>, {chttpd_view, handle_view_req, 3}}, + {<<"_show">>, {chttpd_show, handle_doc_show_req, 3}}, + {<<"_list">>, {chttpd_show, handle_view_list_req, 3}}, + {<<"_update">>, {chttpd_show, handle_doc_update_req, 3}}, + {<<"_info">>, {chttpd_db, handle_design_info_req, 3}}, + {<<"_rewrite">>, {chttpd_rewrite, handle_rewrite_req, 3}} +]}]. + From 4a2cb8195346833385d7f08daca0d90bf14b49e6 Mon Sep 17 00:00:00 2001 From: "H. Diedrich" Date: Fri, 28 Nov 2014 19:15:14 +0100 Subject: [PATCH 2/7] Delete inappropriate endpoint tests BugzID: 27037 --- test/chttpd_handler_callback_test.erl | 42 +-------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/test/chttpd_handler_callback_test.erl b/test/chttpd_handler_callback_test.erl index 1e2387a..94a5adc 100644 --- a/test/chttpd_handler_callback_test.erl +++ b/test/chttpd_handler_callback_test.erl @@ -66,45 +66,21 @@ all_test_() -> fun() -> assertReturns("_uuids", chttpd_misc, handle_uuids_req) end, - fun() -> - assertReturns("_log", chttpd_misc, handle_log_req) - end, fun() -> assertReturns("_sleep", chttpd_misc, handle_sleep_req) end, fun() -> assertReturns("_session", chttpd_auth, handle_session_req) end, - fun() -> - assertReturns("_user", chttpd_auth, handle_user_req) - end, fun() -> assertReturns("_oauth", couch_httpd_oauth, handle_oauth_req) end, - fun() -> - assertReturns("_system", chttpd_misc, handle_system_req) - end, - fun() -> - assertReturns("_pid", chttpd_misc, handle_pid_req) - end, fun() -> assertReturns("_up", chttpd_misc, handle_up_req) end, fun() -> assertReturns("_membership", mem3_httpd, handle_membership_req) end, - fun() -> - assertReturns("_cloudant", showroom_httpd_admin, handle_cloudant_req) - end, - fun() -> - assertReturns("_ioq", chttpd_misc, handle_ioq_req) - end, - fun() -> - assertReturns("_sauth", delegated_auth, handle_delegated_auth_req) - end, - fun() -> - assertReturns("_search_analyze", dreyfus_httpd, handle_analyze_req) - end, fun() -> assertReturns("_db_updates", global_changes_httpd, handle_global_changes_req) end, @@ -144,19 +120,9 @@ all_test_() -> fun() -> assertReturns(db_url_handlers, <<"_changes">>, chttpd_db, handle_changes_req, 2) end, - fun() -> - assertReturns(db_url_handlers, <<"_search_cleanup">>, dreyfus_httpd, - handle_cleanup_req, 2) - end, fun() -> assertReturns(db_url_handlers, <<"_shards">>, mem3_httpd, handle_shards_req, 2) end, - fun() -> - assertReturns(db_url_handlers, <<"_index">>, mango_httpd, handle_req, 2) - end, - fun() -> - assertReturns(db_url_handlers, <<"_find">>, mango_httpd, handle_req, 2) - end, % Test the test: when the final target function is missing, the Fun call % must fail, with any argument, including valid ones. @@ -190,12 +156,6 @@ all_test_() -> fun() -> assertReturns(design_url_handlers, <<"_rewrite">>, chttpd_rewrite, handle_rewrite_req, 3) end, - fun() -> - assertReturns(design_url_handlers, <<"_search">>, dreyfus_httpd, - handle_search_req, 3) end, - fun() -> - assertReturns(design_url_handlers, <<"_search_info">>, dreyfus_httpd, - handle_info_req, 3) end, % Test the test: when the final target function is missing, the Fun call % must fail with any argument, including valid ones. @@ -296,7 +256,7 @@ test_cfg() -> {<<"_design">>, {chttpd_db, handle_design_req, 2}}, {<<"_temp_view">>, {chttpd_view, handle_temp_view_req, 2}}, {<<"_changes">>, {chttpd_db, handle_changes_req, 2}}, - {<<"_shards">>, {mem3_httpd, handle_shards_req, 2}} + {<<"_shards">>, {mem3_httpd, handle_shards_req, 2}}]}, {design_url_handlers, list, [ {<<"_view">>, {chttpd_view, handle_view_req, 3}}, {<<"_show">>, {chttpd_show, handle_doc_show_req, 3}}, From 4e11fd287b14bd8b08fa1c779fb4163d797fb618 Mon Sep 17 00:00:00 2001 From: "H. Diedrich" Date: Fri, 28 Nov 2014 19:41:37 +0100 Subject: [PATCH 3/7] Deleted line breaking ok in tty --- test/chttpd_handler_callback_test.erl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/chttpd_handler_callback_test.erl b/test/chttpd_handler_callback_test.erl index 94a5adc..5af8d9d 100644 --- a/test/chttpd_handler_callback_test.erl +++ b/test/chttpd_handler_callback_test.erl @@ -177,8 +177,7 @@ assertReturns(Endpoint, M, F) -> io:format(user, "~-47...s ", [Endpoint]), meck:expect(M, F, fun(X) -> {return, Endpoint, X} end), Fun = chttpd_handler:url_handler(Endpoint), - ?assertEqual({return, Endpoint, x}, Fun(x)), - io:format(user, "ok~n", []) + ?assertEqual({return, Endpoint, x}, Fun(x)) after meck:unload(M) end. @@ -207,8 +206,7 @@ assertReturns(HandlerLister, Endpoint, M, F, Arity) -> meck:expect(M, F, fun(X, Y, Z) -> {return, Endpoint, X, Y, Z} end), {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), ?assertEqual({return, Endpoint, x, y, z}, Fun(x, y, z)) - end, - io:format(user, "ok~n", []) + end after meck:unload(M) end. From e69362fbe15bbd01f0f4cdf90da335377c8d458e Mon Sep 17 00:00:00 2001 From: "H. Diedrich" Date: Fri, 28 Nov 2014 19:59:30 +0100 Subject: [PATCH 4/7] Add reload test from original hitch app --- test/chttpd_handler_reload_test.erl | 111 ++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 test/chttpd_handler_reload_test.erl diff --git a/test/chttpd_handler_reload_test.erl b/test/chttpd_handler_reload_test.erl new file mode 100644 index 0000000..6fe3b54 --- /dev/null +++ b/test/chttpd_handler_reload_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(chttpd_handler_reload_test). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +%% for testing + +a() -> a. +b() -> b. +c() -> c. +d() -> d. +e() -> e. +f() -> f. +g() -> g. +h() -> h. + +-spec reload_test() -> ok. +%% @doc verifies that there are no side effects from prior builds. +reload_test() -> + + % undo any previously loaded test builds + + code:purge(chttpd_dyn_handler), % (must for delete) + code:delete(chttpd_dyn_handler), + ?assertError(undef, _F0 = chttpd_dyn_handler:clause_test("A")), + + % first build + + Cfg = + [{clause_test, clauses, [ + {"A", {chttpd_handler_reload_test, a, 0}}, + {"B", {chttpd_handler_reload_test, b, 0}}, + {'_', {chttpd_handler_reload_test, c, 0}} + ]}, + {list_test, list, [ + {<<"1">>, {chttpd_handler_reload_test, a, 0}}, + {<<"2">>, {chttpd_handler_reload_test, b, 0}} + ]}], + + chttpd_handler:build(Cfg), + + F1 = chttpd_dyn_handler:clause_test("A"), + a = F1(), + + F2 = chttpd_dyn_handler:clause_test("B"), + b = F2(), + + F3 = chttpd_dyn_handler:clause_test(xxx), + c = F3(), + + {_, F4} = lists:keyfind(<<"1">>, 1, chttpd_dyn_handler:list_test()), + a = F4(), + + {_, F5} = lists:keyfind(<<"2">>, 1, chttpd_dyn_handler:list_test()), + b = F5(), + + % second build, expected to overwrite the earlier + + Cfg2 = + [{clause_test, clauses, [ + {"D", {chttpd_handler_reload_test, d, 0}}, + {"E", {chttpd_handler_reload_test, e, 0}}, + {'_', {chttpd_handler_reload_test, f, 0}} + ]}, + {list_test, list, [ + {<<"3">>, {chttpd_handler_reload_test, g, 0}}, + {<<"4">>, {chttpd_handler_reload_test, h, 0}} + ]}], + + chttpd_handler:build(Cfg2), + + F6 = chttpd_dyn_handler:clause_test("A"), + io:format("~p~n", [F6]), + ?assertError({badmatch, f}, a = F6()), + + F7 = chttpd_dyn_handler:clause_test("B"), + ?assertError({badmatch, f}, b = F7()), + + F8 = chttpd_dyn_handler:clause_test("D"), + d = F8(), + + F9 = chttpd_dyn_handler:clause_test("E"), + e = F9(), + + F10 = chttpd_dyn_handler:clause_test(yyy), + f = F10(), + + false = lists:keyfind(<<"1">>, 1, chttpd_dyn_handler:list_test()), + + false = lists:keyfind(<<"2">>, 1, chttpd_dyn_handler:list_test()), + + {_, F11} = lists:keyfind(<<"3">>, 1, chttpd_dyn_handler:list_test()), + g = F11(), + + {_, F12} = lists:keyfind(<<"4">>, 1, chttpd_dyn_handler:list_test()), + h = F12(), + + ok. From ecd4f772e90af695041569e2835e9580e16ed744 Mon Sep 17 00:00:00 2001 From: "H. Diedrich" Date: Mon, 1 Dec 2014 13:22:19 +0100 Subject: [PATCH 5/7] Altered and blanketed use of ?assert and ?_assert As requested. Added module comment justifying reload tests. BugzID: 27037 --- test/chttpd_handler_callback_test.erl | 12 ++++----- test/chttpd_handler_reload_test.erl | 39 ++++++++++++++++----------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/test/chttpd_handler_callback_test.erl b/test/chttpd_handler_callback_test.erl index 5af8d9d..80c20f3 100644 --- a/test/chttpd_handler_callback_test.erl +++ b/test/chttpd_handler_callback_test.erl @@ -177,7 +177,7 @@ assertReturns(Endpoint, M, F) -> io:format(user, "~-47...s ", [Endpoint]), meck:expect(M, F, fun(X) -> {return, Endpoint, X} end), Fun = chttpd_handler:url_handler(Endpoint), - ?assertEqual({return, Endpoint, x}, Fun(x)) + ?_assertEqual({return, Endpoint, x}, Fun(x)) after meck:unload(M) end. @@ -187,7 +187,7 @@ assertUnmockedFails(Endpoint, M) -> meck:new(M, [non_strict]), try Fun = chttpd_handler:url_handler(Endpoint), - ?assertError(undef, Fun(x)) + ?_assertError(undef, Fun(x)) after meck:unload(M) end. @@ -201,11 +201,11 @@ assertReturns(HandlerLister, Endpoint, M, F, Arity) -> 2 -> meck:expect(M, F, fun(X, Y) -> {return, Endpoint, X, Y} end), {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), - ?assertEqual({return, Endpoint, x, y}, Fun(x, y)); + ?_assertEqual({return, Endpoint, x, y}, Fun(x, y)); 3 -> meck:expect(M, F, fun(X, Y, Z) -> {return, Endpoint, X, Y, Z} end), {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), - ?assertEqual({return, Endpoint, x, y, z}, Fun(x, y, z)) + ?_assertEqual({return, Endpoint, x, y, z}, Fun(x, y, z)) end after meck:unload(M) @@ -217,8 +217,8 @@ assertUnmockedFails(HandlerLister, Endpoint, M, Arity) -> try {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), case Arity of - 2 -> ?assertError(undef, Fun(x, y)); - 3 -> ?assertError(undef, Fun(x, y, z)) + 2 -> ?_assertError(undef, Fun(x, y)); + 3 -> ?_assertError(undef, Fun(x, y, z)) end after meck:unload(M) diff --git a/test/chttpd_handler_reload_test.erl b/test/chttpd_handler_reload_test.erl index 6fe3b54..52dcaf0 100644 --- a/test/chttpd_handler_reload_test.erl +++ b/test/chttpd_handler_reload_test.erl @@ -10,14 +10,20 @@ % License for the specific language governing permissions and limitations under % the License. +% @doc This tests that chttpd_handler does not have lingering side effects from +% a prior configuration after a configuration reload. Note that creating a +% dynamic module has a different persistence of state than most anything else. +% It superseeds runtime, survives restarts and by nature also re-compilation, +% and it would in this case be wrong to purge the 'old' code and kill the +% processes executing it thereby on a reload, which should instead finish their +% run in the old code, as is often desired for reloads. + -module(chttpd_handler_reload_test). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). -%% for testing - a() -> a. b() -> b. c() -> c. @@ -35,7 +41,7 @@ reload_test() -> code:purge(chttpd_dyn_handler), % (must for delete) code:delete(chttpd_dyn_handler), - ?assertError(undef, _F0 = chttpd_dyn_handler:clause_test("A")), + ?assertError(undef, chttpd_dyn_handler:clause_test("A")), % first build @@ -53,19 +59,19 @@ reload_test() -> chttpd_handler:build(Cfg), F1 = chttpd_dyn_handler:clause_test("A"), - a = F1(), + ?assertEqual(a, F1()), F2 = chttpd_dyn_handler:clause_test("B"), - b = F2(), + ?assertEqual(b, F2()), F3 = chttpd_dyn_handler:clause_test(xxx), - c = F3(), + ?assertEqual(c, F3()), {_, F4} = lists:keyfind(<<"1">>, 1, chttpd_dyn_handler:list_test()), - a = F4(), + ?assertEqual(a, F4()), {_, F5} = lists:keyfind(<<"2">>, 1, chttpd_dyn_handler:list_test()), - b = F5(), + ?assertEqual(b, F5()), % second build, expected to overwrite the earlier @@ -76,36 +82,37 @@ reload_test() -> {'_', {chttpd_handler_reload_test, f, 0}} ]}, {list_test, list, [ - {<<"3">>, {chttpd_handler_reload_test, g, 0}}, - {<<"4">>, {chttpd_handler_reload_test, h, 0}} + {<<"3">>, {chttpd_handler_reload_test, g, 0}}, + {<<"4">>, {chttpd_handler_reload_test, h, 0}} ]}], chttpd_handler:build(Cfg2), + % verify that the first config is gone + F6 = chttpd_dyn_handler:clause_test("A"), - io:format("~p~n", [F6]), ?assertError({badmatch, f}, a = F6()), F7 = chttpd_dyn_handler:clause_test("B"), ?assertError({badmatch, f}, b = F7()), F8 = chttpd_dyn_handler:clause_test("D"), - d = F8(), + ?assertEqual(d, F8()), F9 = chttpd_dyn_handler:clause_test("E"), - e = F9(), + ?assertEqual(e, F9()), F10 = chttpd_dyn_handler:clause_test(yyy), - f = F10(), + ?assertEqual(f, F10()), false = lists:keyfind(<<"1">>, 1, chttpd_dyn_handler:list_test()), false = lists:keyfind(<<"2">>, 1, chttpd_dyn_handler:list_test()), {_, F11} = lists:keyfind(<<"3">>, 1, chttpd_dyn_handler:list_test()), - g = F11(), + ?assertEqual(g, F11()), {_, F12} = lists:keyfind(<<"4">>, 1, chttpd_dyn_handler:list_test()), - h = F12(), + ?assertEqual(h, F12()), ok. From 291836e7811127a3eea0a2c842d0c3d9bbf6b6c9 Mon Sep 17 00:00:00 2001 From: "H. Diedrich" Date: Mon, 1 Dec 2014 15:24:41 +0100 Subject: [PATCH 6/7] Replace anonymous test functions w/real ones. BugzID: 27037 --- test/chttpd_handler_callback_test.erl | 276 +++++++++++++++----------- 1 file changed, 157 insertions(+), 119 deletions(-) diff --git a/test/chttpd_handler_callback_test.erl b/test/chttpd_handler_callback_test.erl index 80c20f3..d1f340d 100644 --- a/test/chttpd_handler_callback_test.erl +++ b/test/chttpd_handler_callback_test.erl @@ -35,137 +35,175 @@ all_test_() -> io:format(user, "~nEndpoint handler callbacks:~n", []), {setup, fun() -> chttpd_handler:build(test_cfg()) end, - [ % url handlers - fun() -> - assertReturns("", chttpd_misc, handle_welcome_req) - end, - fun() -> - assertReturns("favicon.ico", chttpd_misc, handle_favicon_req) - end, - fun() -> - assertReturns("_utils", chttpd_misc, handle_utils_dir_req) - end, - fun() -> - assertReturns("_all_dbs", chttpd_misc, handle_all_dbs_req) - end, - fun() -> - assertReturns("_active_tasks", chttpd_misc, handle_task_status_req) - end, - fun() -> - assertReturns("_config", chttpd_misc, handle_config_req) - end, - fun() -> - assertReturns("_reload_query_servers", chttpd_misc, - handle_reload_query_servers_req) - end, - fun() -> - assertReturns("_replicate", chttpd_misc, handle_replicate_req) - end, - fun() -> - assertReturns("_uuids", chttpd_misc, handle_uuids_req) - end, - fun() -> - assertReturns("_sleep", chttpd_misc, handle_sleep_req) - end, - fun() -> - assertReturns("_session", chttpd_auth, handle_session_req) - end, - fun() -> - assertReturns("_oauth", couch_httpd_oauth, handle_oauth_req) - end, - fun() -> - assertReturns("_up", chttpd_misc, handle_up_req) - end, - fun() -> - assertReturns("_membership", mem3_httpd, handle_membership_req) - end, - fun() -> - assertReturns("_db_updates", global_changes_httpd, - handle_global_changes_req) end, - fun() -> - assertReturns("anything", chttpd_db, handle_request) - end, + [fun test_empty_string/0, + fun test_favicon/0, + fun test_utils/0, + fun test_all_dbs/0, + fun test_active_tasks/0, + fun test_config/0, + fun test_reload_query_servers/0, + fun test_replicate/0, + fun test_uuids/0, + fun test_sleep/0, + fun test_session/0, + fun test_oauth/0, + fun test_up/0, + fun test_membership/0, + fun test_db_updates/0, + fun test_anything/0, % Test the tests: if the final target function is missing, the call must % fail, with any parameter. All parameters are valid. - fun() -> - assertUnmockedFails("", chttpd_misc) - end, - fun() -> - assertUnmockedFails("favicon.ico", chttpd_misc) - end, - fun() -> - assertUnmockedFails(anything, chttpd_db) - end, - - - % db url handler tests - - fun() -> - assertReturns(db_url_handlers, <<"_view_cleanup">>, chttpd_db, - handle_view_cleanup_req, 2) end, - fun() -> - assertReturns(db_url_handlers, <<"_compact">>, chttpd_db, - handle_compact_req, 2) - end, - fun() -> - assertReturns(db_url_handlers, <<"_design">>, chttpd_db, - handle_design_req, 2) - end, - fun() -> - assertReturns(db_url_handlers, <<"_temp_view">>, chttpd_view, - handle_temp_view_req, 2) end, - fun() -> - assertReturns(db_url_handlers, <<"_changes">>, chttpd_db, - handle_changes_req, 2) end, - fun() -> - assertReturns(db_url_handlers, <<"_shards">>, mem3_httpd, - handle_shards_req, 2) end, - + fun verify_unmocked_failing_empty_string/0, + fun verify_unmocked_failing_favicon/0, + fun verify_unmocked_failing_anything/0, + + % db url handler tests, + fun test_view_cleanup/0, + fun test_compact/0, + fun test_design/0, + fun test_temp_view/0, + fun test_changes/0, + fun test_shards/0, + % Test the test: when the final target function is missing, the Fun call % must fail, with any argument, including valid ones. - fun() -> - assertUnmockedFails(db_url_handlers, <<"_view_cleanup">>, chttpd_db, 2) - end, - - % Test the test: when the argument is unknown, the Fun call must fail. - fun() -> - assertUnknownFails(db_url_handlers, <<"_something">>) - end, - + fun verify_unmocked_failing_view_cleanup/0, + fun verify_unmocked_db_failing_something/0, % design url handler tests - - fun() -> - assertReturns(design_url_handlers, <<"_view">>, chttpd_view, - handle_view_req, 3) end, - fun() -> - assertReturns(design_url_handlers, <<"_show">>, chttpd_show, - handle_doc_show_req, 3) end, - fun() -> - assertReturns(design_url_handlers, <<"_list">>, chttpd_show, - handle_view_list_req, 3) end, - fun() -> - assertReturns(design_url_handlers, <<"_update">>, chttpd_show, - handle_doc_update_req, 3) end, - fun() -> - assertReturns(design_url_handlers, <<"_info">>, chttpd_db, - handle_design_info_req, 3) end, - fun() -> - assertReturns(design_url_handlers, <<"_rewrite">>, chttpd_rewrite, - handle_rewrite_req, 3) end, + fun test_view/0, + fun test_show/0, + fun test_list/0, + fun test_update/0, + fun test_info/0, + fun test_rewrite/0, % Test the test: when the final target function is missing, the Fun call % must fail with any argument, including valid ones. - fun() -> - assertUnmockedFails(design_url_handlers, <<"_view">>, chttpd_view, 3) - end, - % Test the test: when the argument is unknown, the Fun call must fail. - fun() -> - assertUnknownFails(design_url_handlers, <<"_something">>) - end]}. + fun verify_unmocked_failing_view/0, + fun verify_unmocked_design_failing_something/0]}. + +test_empty_string() -> + assertReturns("", chttpd_misc, handle_welcome_req). + +test_favicon() -> + assertReturns("favicon.ico", chttpd_misc, handle_favicon_req). + +test_utils() -> + assertReturns("_utils", chttpd_misc, handle_utils_dir_req). + +test_all_dbs() -> + assertReturns("_all_dbs", chttpd_misc, handle_all_dbs_req). + +test_active_tasks() -> + assertReturns("_active_tasks", chttpd_misc, handle_task_status_req). + +test_config() -> + assertReturns("_config", chttpd_misc, handle_config_req). + +test_reload_query_servers() -> + assertReturns("_reload_query_servers", chttpd_misc, + handle_reload_query_servers_req). + +test_replicate() -> + assertReturns("_replicate", chttpd_misc, handle_replicate_req). + +test_uuids() -> + assertReturns("_uuids", chttpd_misc, handle_uuids_req). + +test_sleep() -> + assertReturns("_sleep", chttpd_misc, handle_sleep_req). + +test_session() -> + assertReturns("_session", chttpd_auth, handle_session_req). + +test_oauth() -> + assertReturns("_oauth", couch_httpd_oauth, handle_oauth_req). + +test_up() -> + assertReturns("_up", chttpd_misc, handle_up_req). + +test_membership() -> + assertReturns("_membership", mem3_httpd, handle_membership_req). + +test_db_updates() -> + assertReturns("_db_updates", global_changes_httpd, + handle_global_changes_req). + +test_anything() -> + assertReturns("anything", chttpd_db, handle_request). + +verify_unmocked_failing_empty_string() -> + assertUnmockedFails("", chttpd_misc). + +verify_unmocked_failing_favicon() -> + assertUnmockedFails("favicon.ico", chttpd_misc). + +verify_unmocked_failing_anything() -> + assertUnmockedFails(anything, chttpd_db). + +test_view_cleanup() -> + assertReturns(db_url_handlers, <<"_view_cleanup">>, chttpd_db, + handle_view_cleanup_req, 2). + +test_compact() -> + assertReturns(db_url_handlers, <<"_compact">>, chttpd_db, + handle_compact_req, 2). + +test_design() -> + assertReturns(db_url_handlers, <<"_design">>, chttpd_db, + handle_design_req, 2). + +test_temp_view() -> + assertReturns(db_url_handlers, <<"_temp_view">>, chttpd_view, + handle_temp_view_req, 2). + +test_changes() -> + assertReturns(db_url_handlers, <<"_changes">>, chttpd_db, + handle_changes_req, 2). + +test_shards() -> + assertReturns(db_url_handlers, <<"_shards">>, mem3_httpd, + handle_shards_req, 2). + +verify_unmocked_failing_view_cleanup() -> + assertUnmockedFails(db_url_handlers, <<"_view_cleanup">>, chttpd_db, 2). + +verify_unmocked_db_failing_something() -> + assertUnknownFails(db_url_handlers, <<"_something">>). + +test_view() -> + assertReturns(design_url_handlers, <<"_view">>, chttpd_view, + handle_view_req, 3). + +test_show() -> + assertReturns(design_url_handlers, <<"_show">>, chttpd_show, + handle_doc_show_req, 3). + +test_list() -> + assertReturns(design_url_handlers, <<"_list">>, chttpd_show, + handle_view_list_req, 3). + +test_update() -> + assertReturns(design_url_handlers, <<"_update">>, chttpd_show, + handle_doc_update_req, 3). + +test_info() -> + assertReturns(design_url_handlers, <<"_info">>, chttpd_db, + handle_design_info_req, 3). + +test_rewrite() -> + assertReturns(design_url_handlers, <<"_rewrite">>, chttpd_rewrite, + handle_rewrite_req, 3). + +verify_unmocked_failing_view() -> + assertUnmockedFails(design_url_handlers, <<"_view">>, chttpd_view, 3). + +verify_unmocked_design_failing_something() -> + assertUnknownFails(design_url_handlers, <<"_something">>). %% Call the dynamic function with a parameter known to trigger a specific From cf05b93058b301612ee46765ab3cd3393ef7113f Mon Sep 17 00:00:00 2001 From: "H. Diedrich" Date: Tue, 2 Dec 2014 16:18:31 +0100 Subject: [PATCH 7/7] Test output changed as advised. --- test/chttpd_handler_callback_test.erl | 258 +++++++++++++------------- 1 file changed, 128 insertions(+), 130 deletions(-) diff --git a/test/chttpd_handler_callback_test.erl b/test/chttpd_handler_callback_test.erl index d1f340d..7774675 100644 --- a/test/chttpd_handler_callback_test.erl +++ b/test/chttpd_handler_callback_test.erl @@ -31,179 +31,179 @@ -include_lib("eunit/include/eunit.hrl"). + all_test_() -> - io:format(user, "~nEndpoint handler callbacks:~n", []), - {setup, + {foreach, fun() -> chttpd_handler:build(test_cfg()) end, % url handlers - [fun test_empty_string/0, - fun test_favicon/0, - fun test_utils/0, - fun test_all_dbs/0, - fun test_active_tasks/0, - fun test_config/0, - fun test_reload_query_servers/0, - fun test_replicate/0, - fun test_uuids/0, - fun test_sleep/0, - fun test_session/0, - fun test_oauth/0, - fun test_up/0, - fun test_membership/0, - fun test_db_updates/0, - fun test_anything/0, + [fun test_empty_string/1, + fun test_favicon/1, + fun test_utils/1, + fun test_all_dbs/1, + fun test_active_tasks/1, + fun test_config/1, + fun test_reload_query_servers/1, + fun test_replicate/1, + fun test_uuids/1, + fun test_sleep/1, + fun test_session/1, + fun test_oauth/1, + fun test_up/1, + fun test_membership/1, + fun test_db_updates/1, + fun test_anything/1, % Test the tests: if the final target function is missing, the call must % fail, with any parameter. All parameters are valid. - fun verify_unmocked_failing_empty_string/0, - fun verify_unmocked_failing_favicon/0, - fun verify_unmocked_failing_anything/0, + fun verify_unmocked_failing_empty_string/1, + fun verify_unmocked_failing_favicon/1, + fun verify_unmocked_failing_anything/1, % db url handler tests, - fun test_view_cleanup/0, - fun test_compact/0, - fun test_design/0, - fun test_temp_view/0, - fun test_changes/0, - fun test_shards/0, + fun test_view_cleanup/1, + fun test_compact/1, + fun test_design/1, + fun test_temp_view/1, + fun test_changes/1, + fun test_shards/1, % Test the test: when the final target function is missing, the Fun call % must fail, with any argument, including valid ones. - fun verify_unmocked_failing_view_cleanup/0, - fun verify_unmocked_db_failing_something/0, + fun verify_unmocked_failing_view_cleanup/1, + fun verify_unmocked_db_failing_something/1, % design url handler tests - fun test_view/0, - fun test_show/0, - fun test_list/0, - fun test_update/0, - fun test_info/0, - fun test_rewrite/0, + fun test_view/1, + fun test_show/1, + fun test_list/1, + fun test_update/1, + fun test_info/1, + fun test_rewrite/1, % Test the test: when the final target function is missing, the Fun call % must fail with any argument, including valid ones. - fun verify_unmocked_failing_view/0, - fun verify_unmocked_design_failing_something/0]}. + fun verify_unmocked_failing_view/1, + fun verify_unmocked_design_failing_something/1]}. -test_empty_string() -> - assertReturns("", chttpd_misc, handle_welcome_req). +test_empty_string(_) -> + ?_test(assertReturns("", chttpd_misc, handle_welcome_req)). -test_favicon() -> - assertReturns("favicon.ico", chttpd_misc, handle_favicon_req). +test_favicon(_) -> + ?_test(assertReturns("favicon.ico", chttpd_misc, handle_favicon_req)). -test_utils() -> - assertReturns("_utils", chttpd_misc, handle_utils_dir_req). +test_utils(_) -> + ?_test(assertReturns("_utils", chttpd_misc, handle_utils_dir_req)). -test_all_dbs() -> - assertReturns("_all_dbs", chttpd_misc, handle_all_dbs_req). +test_all_dbs(_) -> + ?_test(assertReturns("_all_dbs", chttpd_misc, handle_all_dbs_req)). -test_active_tasks() -> - assertReturns("_active_tasks", chttpd_misc, handle_task_status_req). +test_active_tasks(_) -> + ?_test(assertReturns("_active_tasks", chttpd_misc, handle_task_status_req)). -test_config() -> - assertReturns("_config", chttpd_misc, handle_config_req). +test_config(_) -> + ?_test(assertReturns("_config", chttpd_misc, handle_config_req)). -test_reload_query_servers() -> - assertReturns("_reload_query_servers", chttpd_misc, - handle_reload_query_servers_req). +test_reload_query_servers(_) -> + ?_test(assertReturns("_reload_query_servers", chttpd_misc, + handle_reload_query_servers_req)). -test_replicate() -> - assertReturns("_replicate", chttpd_misc, handle_replicate_req). +test_replicate(_) -> + ?_test(assertReturns("_replicate", chttpd_misc, handle_replicate_req)). -test_uuids() -> - assertReturns("_uuids", chttpd_misc, handle_uuids_req). +test_uuids(_) -> + ?_test(assertReturns("_uuids", chttpd_misc, handle_uuids_req)). -test_sleep() -> - assertReturns("_sleep", chttpd_misc, handle_sleep_req). +test_sleep(_) -> + ?_test(assertReturns("_sleep", chttpd_misc, handle_sleep_req)). -test_session() -> - assertReturns("_session", chttpd_auth, handle_session_req). +test_session(_) -> + ?_test(assertReturns("_session", chttpd_auth, handle_session_req)). -test_oauth() -> - assertReturns("_oauth", couch_httpd_oauth, handle_oauth_req). +test_oauth(_) -> + ?_test(assertReturns("_oauth", couch_httpd_oauth, handle_oauth_req)). -test_up() -> - assertReturns("_up", chttpd_misc, handle_up_req). +test_up(_) -> + ?_test(assertReturns("_up", chttpd_misc, handle_up_req)). -test_membership() -> - assertReturns("_membership", mem3_httpd, handle_membership_req). +test_membership(_) -> + ?_test(assertReturns("_membership", mem3_httpd, handle_membership_req)). -test_db_updates() -> - assertReturns("_db_updates", global_changes_httpd, - handle_global_changes_req). +test_db_updates(_) -> + ?_test(assertReturns("_db_updates", global_changes_httpd, + handle_global_changes_req)). -test_anything() -> - assertReturns("anything", chttpd_db, handle_request). +test_anything(_) -> + ?_test(assertReturns("anything", chttpd_db, handle_request)). -verify_unmocked_failing_empty_string() -> - assertUnmockedFails("", chttpd_misc). +verify_unmocked_failing_empty_string(_) -> + ?_test(assertUnmockedFails("", chttpd_misc)). -verify_unmocked_failing_favicon() -> - assertUnmockedFails("favicon.ico", chttpd_misc). +verify_unmocked_failing_favicon(_) -> + ?_test(assertUnmockedFails("favicon.ico", chttpd_misc)). -verify_unmocked_failing_anything() -> - assertUnmockedFails(anything, chttpd_db). +verify_unmocked_failing_anything(_) -> + ?_test(assertUnmockedFails(anything, chttpd_db)). -test_view_cleanup() -> - assertReturns(db_url_handlers, <<"_view_cleanup">>, chttpd_db, - handle_view_cleanup_req, 2). +test_view_cleanup(_) -> + ?_test(assertReturns(db_url_handlers, <<"_view_cleanup">>, chttpd_db, + handle_view_cleanup_req, 2)). -test_compact() -> - assertReturns(db_url_handlers, <<"_compact">>, chttpd_db, - handle_compact_req, 2). +test_compact(_) -> + ?_test(assertReturns(db_url_handlers, <<"_compact">>, chttpd_db, + handle_compact_req, 2)). -test_design() -> - assertReturns(db_url_handlers, <<"_design">>, chttpd_db, - handle_design_req, 2). +test_design(_) -> + ?_test(assertReturns(db_url_handlers, <<"_design">>, chttpd_db, + handle_design_req, 2)). -test_temp_view() -> - assertReturns(db_url_handlers, <<"_temp_view">>, chttpd_view, - handle_temp_view_req, 2). +test_temp_view(_) -> + ?_test(assertReturns(db_url_handlers, <<"_temp_view">>, chttpd_view, + handle_temp_view_req, 2)). -test_changes() -> - assertReturns(db_url_handlers, <<"_changes">>, chttpd_db, - handle_changes_req, 2). +test_changes(_) -> + ?_test(assertReturns(db_url_handlers, <<"_changes">>, chttpd_db, + handle_changes_req, 2)). -test_shards() -> - assertReturns(db_url_handlers, <<"_shards">>, mem3_httpd, - handle_shards_req, 2). +test_shards(_) -> + ?_test(assertReturns(db_url_handlers, <<"_shards">>, mem3_httpd, + handle_shards_req, 2)). -verify_unmocked_failing_view_cleanup() -> - assertUnmockedFails(db_url_handlers, <<"_view_cleanup">>, chttpd_db, 2). +verify_unmocked_failing_view_cleanup(_) -> + ?_test(assertUnmockedFails(db_url_handlers, <<"_view_cleanup">>, chttpd_db, 2)). -verify_unmocked_db_failing_something() -> - assertUnknownFails(db_url_handlers, <<"_something">>). +verify_unmocked_db_failing_something(_) -> + ?_test(assertUnknownFails(db_url_handlers, <<"_something">>)). -test_view() -> - assertReturns(design_url_handlers, <<"_view">>, chttpd_view, - handle_view_req, 3). +test_view(_) -> + ?_test(assertReturns(design_url_handlers, <<"_view">>, chttpd_view, + handle_view_req, 3)). -test_show() -> - assertReturns(design_url_handlers, <<"_show">>, chttpd_show, - handle_doc_show_req, 3). +test_show(_) -> + ?_test(assertReturns(design_url_handlers, <<"_show">>, chttpd_show, + handle_doc_show_req, 3)). -test_list() -> - assertReturns(design_url_handlers, <<"_list">>, chttpd_show, - handle_view_list_req, 3). +test_list(_) -> + ?_test(assertReturns(design_url_handlers, <<"_list">>, chttpd_show, + handle_view_list_req, 3)). -test_update() -> - assertReturns(design_url_handlers, <<"_update">>, chttpd_show, - handle_doc_update_req, 3). +test_update(_) -> + ?_test(assertReturns(design_url_handlers, <<"_update">>, chttpd_show, + handle_doc_update_req, 3)). -test_info() -> - assertReturns(design_url_handlers, <<"_info">>, chttpd_db, - handle_design_info_req, 3). +test_info(_) -> + ?_test(assertReturns(design_url_handlers, <<"_info">>, chttpd_db, + handle_design_info_req, 3)). -test_rewrite() -> - assertReturns(design_url_handlers, <<"_rewrite">>, chttpd_rewrite, - handle_rewrite_req, 3). +test_rewrite(_) -> + ?_test(assertReturns(design_url_handlers, <<"_rewrite">>, chttpd_rewrite, + handle_rewrite_req, 3)). -verify_unmocked_failing_view() -> - assertUnmockedFails(design_url_handlers, <<"_view">>, chttpd_view, 3). +verify_unmocked_failing_view(_) -> + ?_test(assertUnmockedFails(design_url_handlers, <<"_view">>, chttpd_view, 3)). -verify_unmocked_design_failing_something() -> - assertUnknownFails(design_url_handlers, <<"_something">>). +verify_unmocked_design_failing_something(_) -> + ?_test(assertUnknownFails(design_url_handlers, <<"_something">>)). %% Call the dynamic function with a parameter known to trigger a specific @@ -212,10 +212,9 @@ verify_unmocked_design_failing_something() -> assertReturns(Endpoint, M, F) -> meck:new(M, [passthrough, non_strict]), try - io:format(user, "~-47...s ", [Endpoint]), meck:expect(M, F, fun(X) -> {return, Endpoint, X} end), Fun = chttpd_handler:url_handler(Endpoint), - ?_assertEqual({return, Endpoint, x}, Fun(x)) + ?assertEqual({return, Endpoint, x}, Fun(x)) after meck:unload(M) end. @@ -225,7 +224,7 @@ assertUnmockedFails(Endpoint, M) -> meck:new(M, [non_strict]), try Fun = chttpd_handler:url_handler(Endpoint), - ?_assertError(undef, Fun(x)) + ?assertError(undef, Fun(x)) after meck:unload(M) end. @@ -234,16 +233,15 @@ assertUnmockedFails(Endpoint, M) -> assertReturns(HandlerLister, Endpoint, M, F, Arity) -> meck:new(M, [passthrough, non_strict]), try - io:format(user, "~-47...s ", [Endpoint]), case Arity of 2 -> meck:expect(M, F, fun(X, Y) -> {return, Endpoint, X, Y} end), {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), - ?_assertEqual({return, Endpoint, x, y}, Fun(x, y)); + ?assertEqual({return, Endpoint, x, y}, Fun(x, y)); 3 -> meck:expect(M, F, fun(X, Y, Z) -> {return, Endpoint, X, Y, Z} end), {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), - ?_assertEqual({return, Endpoint, x, y, z}, Fun(x, y, z)) + ?assertEqual({return, Endpoint, x, y, z}, Fun(x, y, z)) end after meck:unload(M) @@ -255,8 +253,8 @@ assertUnmockedFails(HandlerLister, Endpoint, M, Arity) -> try {_, Fun} = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()), case Arity of - 2 -> ?_assertError(undef, Fun(x, y)); - 3 -> ?_assertError(undef, Fun(x, y, z)) + 2 -> ?assertError(undef, Fun(x, y)); + 3 -> ?assertError(undef, Fun(x, y, z)) end after meck:unload(M) @@ -264,7 +262,7 @@ assertUnmockedFails(HandlerLister, Endpoint, M, Arity) -> %% Make sure that a wrong parameter also really fails. assertUnknownFails(HandlerLister, Endpoint) -> - false = lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister()). + ?assertEqual(false, lists:keyfind(Endpoint, 1, chttpd_handler:HandlerLister())). test_cfg() -> [{url_handler, clauses, [