diff --git a/.travis.yml b/.travis.yml index 1f42a74..45a717a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,7 @@ sudo: false language: erlang otp_release: - - 19.2 - - 18.3 + - 19.3 before_install: - ./ci before_install "${PWD:?}"/rebar3 install: diff --git a/config/sys.config b/config/sys.config new file mode 100644 index 0000000..5a66407 --- /dev/null +++ b/config/sys.config @@ -0,0 +1,41 @@ +[ + {sumo_db, [ + {wpool_opts, [{overrun_warning, 100}]}, + {verbose, true}, + {query_timeout, 30000}, + {storage_backends, [ + {sumo_test_backend_mnesia, sumo_backend_mnesia, []} + ]}, + {stores, [ + {sumo_test_mnesia, sumo_store_mnesia, [ + {workers, 10}, + {ram_copies, here}, + {majority, false} + ]}, + {sumo_test_store1, sumo_test_store1, [ + {workers, 10}, + {ram_copies, here}, + {majority, false} + ]}, + {sumo_test_store2, sumo_test_store2, [ + {workers, 10}, + {ram_copies, here}, + {majority, false} + ]}, + {sumo_test_store3, sumo_test_store3, [ + {workers, 10}, + {ram_copies, here}, + {majority, false} + ]} + ]}, + {docs, [ + {people, sumo_test_mnesia, #{module => sumo_test_people_mnesia}}, + {people1, sumo_test_store1, #{module => sumo_test_people_mnesia}}, + {people2, sumo_test_store2, #{module => sumo_test_people_mnesia}}, + {people3, sumo_test_store3, #{module => sumo_test_people_mnesia}} + ]}, + {events, [ + {people, sumo_test_people_events_manager} + ]} + ]} +]. diff --git a/rebar.config b/rebar.config index b1caf5b..1c404b1 100644 --- a/rebar.config +++ b/rebar.config @@ -17,7 +17,6 @@ strict_validation, warn_export_vars, warn_exported_vars, - warn_missing_spec, warn_untyped_record, debug_info ]}. @@ -62,13 +61,12 @@ strict_validation, warn_export_vars, warn_exported_vars, - warn_missing_spec, warn_untyped_record, debug_info ]}. {ct_opts, [ - {sys_config, ["test/test.config"]} + {sys_config, ["config/sys.config"]} ]}. %% == Cover == diff --git a/src/sumo_backend_sup.erl b/src/sumo_backend_sup.erl index ec3c8a3..a02d33c 100644 --- a/src/sumo_backend_sup.erl +++ b/src/sumo_backend_sup.erl @@ -41,7 +41,7 @@ start_link() -> %%% Supervisor callbacks %%%============================================================================= --spec init(any()) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% hidden init([]) -> {ok, Backends} = application:get_env(sumo_db, storage_backends), Children = lists:map(fun({Name, Module, Options}) -> diff --git a/src/sumo_event.erl b/src/sumo_event.erl index 8cfb4b9..bc9f60e 100644 --- a/src/sumo_event.erl +++ b/src/sumo_event.erl @@ -46,8 +46,12 @@ dispatch(DocName, Event, Args) -> dispatch(DocName, EventId, Event, Args). %% @doc Dispatch an event through gen_event:notify/2. --spec dispatch(sumo:schema_name(), event_id(), term(), term()) -> - event_id() | no_event_managers. +-spec dispatch(DocName, EventId, Event, Args) -> Res when + DocName :: sumo:schema_name(), + EventId :: event_id(), + Event :: term(), + Args :: term(), + Res :: event_id() | no_event_managers. dispatch(DocName, EventId, Event, Args) -> case sumo_config:get_event_managers(DocName) of [] -> diff --git a/src/sumo_event_manager_sup.erl b/src/sumo_event_manager_sup.erl index 762aab4..53194ae 100644 --- a/src/sumo_event_manager_sup.erl +++ b/src/sumo_event_manager_sup.erl @@ -41,7 +41,7 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %%% Supervisor callbacks %%%============================================================================= --spec init(any()) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% hidden init([]) -> EventManagers = sumo_config:get_event_managers(), ManagersList = [manager(EventManager) || EventManager <- EventManagers], diff --git a/src/sumo_store.erl b/src/sumo_store.erl index 52fd760..04c4dc0 100644 --- a/src/sumo_store.erl +++ b/src/sumo_store.erl @@ -60,20 +60,34 @@ handler_state = undefined :: any() }). --type state() :: #state{}. - -%%%============================================================================= -%%% Callbacks -%%%============================================================================= - -type result(R, S) :: {ok, R, S} | {error, term(), S}. -type result(S) :: {ok, S} | {error, term(), S}. -type affected_rows() :: unknown | non_neg_integer(). -export_type([result/2, result/1, affected_rows/0]). +%%%============================================================================= +%%% gen_server callbacks +%%%============================================================================= + -callback init(term()) -> {ok, term()}. +-callback handle_info(Info, State) -> Res when + Info :: term(), + State :: term(), + Res :: {noreply, State} + | {noreply, State, timeout()} + | {noreply, State, hibernate} + | {stop, Reason :: term(), State}. + +-callback terminate(Reason, State) -> ok when + Reason :: normal | shutdown | {shutdown, term()} | term(), + State :: term(). + +%%%============================================================================= +%%% API callbacks +%%%============================================================================= + -callback create_schema(Schema, State) -> Res when Schema :: sumo:schema(), Res :: result(State). @@ -136,6 +150,8 @@ Conditions :: sumo:conditions(), Res :: result(non_neg_integer(), State). +-optional_callbacks([handle_info/2, terminate/2]). + %%%============================================================================= %%% API %%%============================================================================= @@ -293,14 +309,12 @@ call(Name, DocName, Function, Args) -> %% @doc Called by start_link. %% @hidden --spec init([term()]) -> {ok, state()}. init([Module, Options]) -> {ok, HState} = Module:init(Options), {ok, #state{handler=Module, handler_state=HState}}. %% @doc handles calls. %% @hidden --spec handle_call(term(), _, state()) -> {reply, tuple(), state()}. handle_call( {create_schema, Schema}, _From, #state{handler = Handler, handler_state = HState} = State @@ -403,29 +417,49 @@ handle_call( {reply, {OkOrError, Reply}, State#state{handler_state=NewState}}. %% @hidden --spec handle_cast(term(), state()) -> - {noreply, state()} | - {noreply, state(), non_neg_integer()} | - {noreply, state(), hibernate} | - {stop, term(), state()}. handle_cast(_Msg, State) -> {noreply, State}. %% @hidden --spec handle_info(term(), state()) -> - {noreply, state()} | - {noreply, state(), non_neg_integer()} | - {noreply, state(), hibernate} | - {stop, term(), state()}. -handle_info(_Info, State) -> - {noreply, State}. +handle_info(Info, #state{handler = Handler, handler_state = HState} = State) -> + case erlang:function_exported(Handler, handle_info, 2) of + true -> + try Handler:handle_info(Info, HState) of + Response -> handle_info_response(Response, State) + catch + throw:Response -> handle_info_response(Response, State) + end; + false -> + {noreply, State} + end. %% @hidden --spec terminate(term(), state()) -> ok. -terminate(_Reason, _State) -> - ok. +terminate(Reason, #state{handler = Handler, handler_state = HState}) -> + case erlang:function_exported(Handler, terminate, 2) of + true -> + try + Handler:terminate(Reason, HState) + catch + throw:Response -> Response + end; + false -> + ok + end. %% @hidden --spec code_change(term(), state(), term()) -> {ok, state()} | {error, term()}. code_change(_OldVsn, State, _Extra) -> {ok, State}. + +%%%============================================================================= +%%% Internal Functions +%%%============================================================================= + +%% @private +handle_info_response({noreply, NewHState}, State) -> + {noreply, State#state{handler_state = NewHState}}; +handle_info_response({noreply, NewHState, hibernate}, State) -> + {noreply, State#state{handler_state = NewHState}, hibernate}; +handle_info_response({noreply, NewHState, Timeout}, State) -> + {noreply, State#state{handler_state = NewHState}, Timeout}; +handle_info_response({stop, Reason, NewHState}, State) -> + {stop, Reason, State#state{handler_state = NewHState}}. diff --git a/src/sumo_store_sup.erl b/src/sumo_store_sup.erl index 4b498ea..a9e223a 100644 --- a/src/sumo_store_sup.erl +++ b/src/sumo_store_sup.erl @@ -42,7 +42,7 @@ start_link() -> %%% Supervisor callbacks %%%============================================================================= --spec init(any()) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% @hidden init([]) -> {ok, Stores} = application:get_env(sumo_db, stores), Children = lists:map(fun({Name, Module, Options}) -> diff --git a/test/conditional_logic_SUITE.erl b/test/sumo_conditionals_SUITE.erl similarity index 96% rename from test/conditional_logic_SUITE.erl rename to test/sumo_conditionals_SUITE.erl index 081640a..2a90ec8 100644 --- a/test/conditional_logic_SUITE.erl +++ b/test/sumo_conditionals_SUITE.erl @@ -1,4 +1,4 @@ --module(conditional_logic_SUITE). +-module(sumo_conditionals_SUITE). %% CT -export([ diff --git a/test/sumo_meta_SUITE.erl b/test/sumo_meta_SUITE.erl index ed30149..cc39162 100644 --- a/test/sumo_meta_SUITE.erl +++ b/test/sumo_meta_SUITE.erl @@ -10,9 +10,14 @@ ]} ]). --export([init_per_suite/1]). +-export([init_per_suite/1, end_per_suite/1]). -type config() :: [{atom(), term()}]. -spec init_per_suite(config()) -> config(). -init_per_suite(Config) -> [{application, sumo_db} | Config]. +init_per_suite(Config) -> + [{application, sumo_db} | Config]. + +-spec end_per_suite(config()) -> config(). +end_per_suite(Config) -> + Config. diff --git a/test/sumo_optional_callbacks_SUITE.erl b/test/sumo_optional_callbacks_SUITE.erl new file mode 100644 index 0000000..27579b4 --- /dev/null +++ b/test/sumo_optional_callbacks_SUITE.erl @@ -0,0 +1,67 @@ +-module(sumo_optional_callbacks_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +%% CT +-export([ + all/0, + init_per_suite/1, + end_per_suite/1 +]). + +%% Test Cases +-export([ + t_optional_callbacks/1 +]). + +-define(EXCLUDED_FUNS, [ + module_info, + all, + init_per_suite, + end_per_suite +]). + +%%%============================================================================= +%%% Common Test +%%%============================================================================= + +%% @hidden +all() -> + Exports = ?MODULE:module_info(exports), + [F || {F, _} <- Exports, not lists:member(F, ?EXCLUDED_FUNS)]. + +%% @hidden +init_per_suite(Config) -> + ok = sumo_test_utils:start_apps(), + [{schemas, [people1, people2, people3]} | Config]. + +%% @hidden +end_per_suite(Config) -> + _ = application:stop(sumo_db), + Config. + +%%%============================================================================= +%%% Test Cases +%%%============================================================================= + +%% @hidden +t_optional_callbacks(Config) -> + run_for_all_schemas(Config, fun t_optional_callbacks_/1). + +%% @hidden +t_optional_callbacks_(SchemaName) -> + _ = sumo:call(SchemaName, send_msg, [noreply]), + _ = sumo:call(SchemaName, send_msg, [hibernate]), + _ = sumo:call(SchemaName, send_msg, [timeout]), + _ = sumo:call(SchemaName, send_msg, [stop]), + _ = sumo:call(SchemaName, send_msg, [throw]), + ok. + +%%%============================================================================= +%%% Internal Functions +%%%============================================================================= + +%% @private +run_for_all_schemas(Config, Fun) -> + {_, Schemas} = lists:keyfind(schemas, 1, Config), + lists:foreach(Fun, Schemas). diff --git a/test/support/sumo_test_store1.erl b/test/support/sumo_test_store1.erl new file mode 100644 index 0000000..aab02d1 --- /dev/null +++ b/test/support/sumo_test_store1.erl @@ -0,0 +1,43 @@ +-module(sumo_test_store1). + +-behaviour(sumo_store). + +%% @todo remove this once mixer migrates specs better +-dialyzer([no_behaviours]). + +-include_lib("mixer/include/mixer.hrl"). +-mixin([ + {sumo_store_mnesia, [ + init/1, + create_schema/2, + persist/2, + fetch/3, + delete_by/3, + delete_all/2, + find_all/2, find_all/5, + find_by/3, find_by/5, find_by/6, + count/2, + count_by/3 + ]} +]). + +-export([send_msg/3]). + +%% @hidden +send_msg(noreply, _DocName, State) -> + {ok, {raw, send({noreply, State})}, State}; +send_msg(hibernate, _DocName, State) -> + {ok, {raw, send({noreply, State, hibernate})}, State}; +send_msg(timeout, _DocName, State) -> + {ok, {raw, send({noreply, State, 1})}, State}; +send_msg(stop, DocName, State) -> + {ok, {raw, send({stop, DocName, State})}, State}; +send_msg(throw, _DocName, State) -> + {ok, {raw, send(throw)}, State}; +send_msg(terminate, _DocName, State) -> + {ok, {raw, send(terminate)}, State}. + +%% @private +send(Msg) -> + self() ! Msg, + ok. diff --git a/test/support/sumo_test_store2.erl b/test/support/sumo_test_store2.erl new file mode 100644 index 0000000..ac3af53 --- /dev/null +++ b/test/support/sumo_test_store2.erl @@ -0,0 +1,39 @@ +-module(sumo_test_store2). + +-behaviour(sumo_store). + +%% @todo remove this once mixer migrates specs better +-dialyzer([no_behaviours]). + +-include_lib("mixer/include/mixer.hrl"). +-mixin([ + {sumo_store_mnesia, [ + init/1, + create_schema/2, + persist/2, + fetch/3, + delete_by/3, + delete_all/2, + find_all/2, find_all/5, + find_by/3, find_by/5, find_by/6, + count/2, + count_by/3 + ]}, + {sumo_test_store1, [ + send_msg/3 + ]} +]). + +-export([handle_info/2, terminate/2]). + +%% @hidden +handle_info(throw, State) -> + throw({stop, throw, State}); +handle_info(Info, _State) -> + Info. + +%% @hidden +terminate(throw, _State) -> + throw(ok); +terminate(_Info, _State) -> + ok. diff --git a/test/support/sumo_test_store3.erl b/test/support/sumo_test_store3.erl new file mode 100644 index 0000000..d3d2367 --- /dev/null +++ b/test/support/sumo_test_store3.erl @@ -0,0 +1,31 @@ +-module(sumo_test_store3). + +-behaviour(sumo_store). + +%% @todo remove this once mixer migrates specs better +-dialyzer([no_behaviours]). + +-include_lib("mixer/include/mixer.hrl"). +-mixin([ + {sumo_store_mnesia, [ + init/1, + create_schema/2, + persist/2, + fetch/3, + delete_by/3, + delete_all/2, + find_all/2, find_all/5, + find_by/3, find_by/5, find_by/6, + count/2, + count_by/3 + ]}, + {sumo_test_store1, [ + send_msg/3 + ]} +]). + +-export([handle_info/2]). + +%% @hidden +handle_info(_Info, State) -> + {stop, normal, State}. diff --git a/test/test.config b/test/test.config deleted file mode 100644 index 19d3808..0000000 --- a/test/test.config +++ /dev/null @@ -1,23 +0,0 @@ -[ - {sumo_db, [ - {wpool_opts, [{overrun_warning, 100}]}, - {verbose, true}, - {query_timeout, 30000}, - {storage_backends, [ - {sumo_test_backend_mnesia, sumo_backend_mnesia, []} - ]}, - {stores, [ - {sumo_test_mnesia, sumo_store_mnesia, [ - {workers, 10}, - {ram_copies, here}, - {majority, false} - ]} - ]}, - {docs, [ - {people, sumo_test_mnesia, #{module => sumo_test_people_mnesia}} - ]}, - {events, [ - {people, sumo_test_people_events_manager} - ]} - ]} -].