diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13d9e04..97e79f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,4 +29,4 @@ jobs: - run: make test - run: make ct - run: make dialyzer - if: ${{ matrix.otp == '27' }} + if: ${{ matrix.otp_vsn == '27' }} diff --git a/src/escalus.app.src b/src/escalus.app.src index e43c97e..dd87cd5 100644 --- a/src/escalus.app.src +++ b/src/escalus.app.src @@ -6,6 +6,7 @@ {applications, [ kernel, stdlib, + public_key, ssl, exml, gun, diff --git a/src/escalus.erl b/src/escalus.erl index 04efab4..553868a 100644 --- a/src/escalus.erl +++ b/src/escalus.erl @@ -136,46 +136,60 @@ story(Config, ResourceCounts, Story) -> %% Assertions +-spec assert(atom(), term()) -> ok | no_return(). assert(PredSpec, Arg) -> escalus_new_assert:assert(PredSpec, Arg). +-spec assert(atom(), [term()], term()) -> ok | no_return(). assert(PredSpec, Params, Arg) -> escalus_new_assert:assert(PredSpec, Params, Arg). +-spec assert_many([atom()], [exml:element()]) -> ok | no_return(). assert_many(Predicates, Stanzas) -> escalus_new_assert:assert_many(Predicates, Stanzas). %% Client API +-spec send(client(), exml:element()) -> ok. send(Client, Packet) -> escalus_client:send(Client, Packet). +-spec send_and_wait(client(), exml:element()) -> exml:element(). send_and_wait(Client, Packet) -> escalus_client:send_and_wait(Client, Packet). +-spec wait_for_stanza(client()) -> exml:element(). wait_for_stanza(Client) -> escalus_client:wait_for_stanza(Client). +-spec wait_for_stanza(client(), timeout()) -> exml:element(). wait_for_stanza(Client, Timeout) -> escalus_client:wait_for_stanza(Client, Timeout). +-spec wait_for_stanzas(client(), non_neg_integer()) -> [exml:element()]. wait_for_stanzas(Client, Count) -> escalus_client:wait_for_stanzas(Client, Count). +-spec wait_for_stanzas(client(), non_neg_integer(), timeout()) -> [exml:element()]. wait_for_stanzas(Client, Count, Timeout) -> escalus_client:wait_for_stanzas(Client, Count, Timeout). +-spec peek_stanzas(client()) -> [exml:element()]. peek_stanzas(Client) -> escalus_client:peek_stanzas(Client). +-spec send_iq_and_wait_for_result(client(), exml:element()) -> exml:element() | no_return(). send_iq_and_wait_for_result(Client, Iq) -> escalus_client:send_iq_and_wait_for_result(Client, Iq). +-spec send_iq_and_wait_for_result(client(), exml:element(), timeout()) -> + exml:element() | no_return(). send_iq_and_wait_for_result(Client, Iq, Timeout) -> escalus_client:send_iq_and_wait_for_result(Client, Iq, Timeout). %% Other functions +-spec override(config(), atom(), {atom(), atom()}) -> config(). override(Config, OverrideName, NewValue) -> escalus_overridables:override(Config, OverrideName, NewValue). diff --git a/src/escalus_assert.erl b/src/escalus_assert.erl index cdc2e9c..ed0cba0 100644 --- a/src/escalus_assert.erl +++ b/src/escalus_assert.erl @@ -15,6 +15,7 @@ %%============================================================================== -module(escalus_assert). +-compile(nowarn_missing_spec). -export([is_chat_message/2, has_no_stanzas/1, diff --git a/src/escalus_bosh.erl b/src/escalus_bosh.erl index f7eb829..a49a35e 100644 --- a/src/escalus_bosh.erl +++ b/src/escalus_bosh.erl @@ -412,8 +412,8 @@ handle_info({http_reply, Ref, Body, _Transport} = HttpReply, {_, true} -> S1 = handle_http_reply(Ref, XmlBody, S0, Timestamp), S1#state{ pending_replies = [] }; - {{value, {Ref, _Rid, _Pid}}, _} -> - {{value, {Ref, _Rid, _Pid}}, NewRequests} = queue:out(S0#state.requests), + {{value, {Ref, Rid, Pid}}, _} -> + {{value, {Ref, Rid, Pid}}, NewRequests} = queue:out(S0#state.requests), S1 = handle_http_reply(Ref, XmlBody, S0#state{ requests = NewRequests }, Timestamp), lists:foreach(fun(PendingReply) -> self() ! PendingReply end, S1#state.pending_replies), diff --git a/src/escalus_bosh_gun.erl b/src/escalus_bosh_gun.erl index f4dccda..65caa55 100644 --- a/src/escalus_bosh_gun.erl +++ b/src/escalus_bosh_gun.erl @@ -11,8 +11,7 @@ handle_call/3, handle_cast/2, handle_info/2, - terminate/2, - code_change/3]). + terminate/2]). -record(state, {destination, options, @@ -28,9 +27,11 @@ start_link(Args) -> gen_server:start_link(?MODULE, [Args], []). +-spec stop(pid()) -> ok. stop(Pool) -> gen_server:cast(Pool, stop). +-spec request(pid(), iodata(), gun:req_headers(), iodata()) -> term(). request(Pool, Path, Hdrs, Body) -> case get_client(Pool) of {error, _} = Error -> @@ -62,6 +63,8 @@ init([Args]) -> queue = queue:new() }, 0}. +-spec handle_call(get_client, gen_server:from(), state()) -> + {reply, term(), state()} | {noreply, state()}. handle_call(get_client, _From, State = #state{free = [Client | Free], busy = Busy}) -> {reply, Client, State#state{free = Free, @@ -83,6 +86,8 @@ handle_call(get_client, From, State = #state{free = [], when M == T -> {noreply, State#state{queue = queue:in(From, Queue)}}. +-spec handle_cast({free_client, pid()} | stop, state()) -> + {noreply, state()} | {stop, term(), state()}. handle_cast({free_client, Pid}, State = #state{free = Free, busy = Busy, queue = Queue}) -> @@ -98,6 +103,7 @@ handle_cast({free_client, Pid}, State = #state{free = Free, handle_cast(stop, State) -> {stop, normal, State}. +-spec handle_info(term(), state()) -> {noreply, state()}. handle_info({'EXIT', From, _Reason}, State = #state{free = Free, busy = Busy, total = Total}) -> @@ -113,14 +119,12 @@ handle_info(_Info, State) -> ct:pal("Unknown Info in bosh_gun: ~p", [_Info]), {noreply, State}. +-spec terminate(term(), state()) -> ok. terminate(_Reason, #state{free = Free, busy = Busy}) -> [gun:close(F) || F <- Free], [gun:close(B) || B <- Busy], ok. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - connect({Host, Port}, Options) -> {ok, Pid} = gun:open(Host, Port, Options#{protocols => [http]}), {ok, http} = gun:await_up(Pid), diff --git a/src/escalus_compat.erl b/src/escalus_compat.erl index f6e0cc6..8d00081 100644 --- a/src/escalus_compat.erl +++ b/src/escalus_compat.erl @@ -31,6 +31,7 @@ %% Public API %%-------------------------------------------------------------------- +-spec bin(binary() | string() | atom() | integer()) -> binary() | no_return(). bin(Arg) when is_binary(Arg) -> Arg; bin(Arg) when is_list(Arg) -> @@ -46,17 +47,21 @@ bin(Other) -> type_complain("???", Other), error(badarg, [Other]). +-spec deprecated(atom(), atom(), T) -> T. deprecated(Old, New, Result) -> error_logger:info_msg("calling deprecated function ~p, use ~p instead~n~p~n", [Old, New, backtrace(1)]), Result. +-spec unimplemented() -> no_return(). unimplemented() -> throw({unimplemented, backtrace(1)}). +-spec complain(term()) -> ok. complain(What) -> error_logger:info_msg("~s at ~p~n", [What, backtrace(1)]). +-spec backtrace(non_neg_integer()) -> list(). backtrace(N) -> {current_stacktrace, Stacktrace} = erlang:process_info(self(), current_stacktrace), lists:nthtail(N + 1, Stacktrace). diff --git a/src/escalus_component.erl b/src/escalus_component.erl index 37b1133..c1dd981 100644 --- a/src/escalus_component.erl +++ b/src/escalus_component.erl @@ -153,6 +153,7 @@ handle_info(Info, #component_state{module = M, client = C, user_state = S} = Sta {noreply, NewState, ?WAIT_AFTER_STANZA}. +-spec terminate(atom(), state()) -> any(). terminate(Reason, #component_state{client = C, module = M, user_state = S}) -> catch escalus_connection:stop(C), case erlang:function_exported(M, terminate, 2) of diff --git a/src/escalus_connection.erl b/src/escalus_connection.erl index 1a69cbf..474922d 100644 --- a/src/escalus_connection.erl +++ b/src/escalus_connection.erl @@ -430,6 +430,7 @@ upgrade_to_tls(#client{module = Mod, rcv_pid = Pid, props = Props}) -> SSLOpts = proplists:get_value(ssl_opts, Props, DefSslOpts), Mod:upgrade_to_tls(Pid, SSLOpts). +-spec wait_for_close(client()) -> boolean(). wait_for_close(Client) -> wait_for_close(Client, default_timeout()). @@ -476,6 +477,8 @@ maybe_forward_to_owner(_, State, Stanzas, Fun, Timestamp) -> stanza_msg(Stanza, Metadata) -> {stanza, self(), Stanza, Metadata}. +-spec separate_ack_requests({boolean(), non_neg_integer(), term()}, [exml_stream:element()]) -> + {{boolean(), non_neg_integer(), term()}, [exml_stream:element()], [exml_stream:element()]}. separate_ack_requests({false, H0, A}, Stanzas) -> %% Don't keep track of H {{false, H0, A}, [], Stanzas}; diff --git a/src/escalus_ct.erl b/src/escalus_ct.erl index a32e1cb..0d51332 100644 --- a/src/escalus_ct.erl +++ b/src/escalus_ct.erl @@ -163,5 +163,6 @@ ct_log_timestamp({MS, S, US}) -> "~2.10.0B:~2.10.0B:~2.10.0B.~3.10.0B", [Year, Month, Day, Hour, Min, Sec, MilliSec])). +-spec log_error(term(), [any()]) -> ok. log_error(Format, Args) -> ct:pal(error, Format, Args). diff --git a/src/escalus_ejabberd.erl b/src/escalus_ejabberd.erl index 96be561..6c80ca9 100644 --- a/src/escalus_ejabberd.erl +++ b/src/escalus_ejabberd.erl @@ -53,21 +53,26 @@ %%% Business API %%% +-spec rpc(atom(), atom(), [any()], timeout()) -> any(). rpc(M, F, A, Timeout) -> Node = escalus_ct:get_config(ejabberd_node), Cookie = escalus_ct:get_config(ejabberd_cookie), escalus_rpc:call(Node, M, F, A, Timeout, Cookie). +-spec rpc(atom(), atom(), [any()]) -> any(). rpc(M, F, A) -> rpc(M, F, A, 3000). +-spec remote_display(string()) -> true. remote_display(String) -> Line = [$\n, [$- || _ <- String], $\n], remote_format("~s~s~s", [Line, String, Line]). +-spec remote_format(string()) -> true. remote_format(Format) -> remote_format(Format, []). +-spec remote_format(string(), [any()]) -> true. remote_format(Format, Args) -> group_leader(rpc(erlang, whereis, [user]), self()), io:format(Format, Args), @@ -106,14 +111,17 @@ with_local_option(Option, Value, Fun) -> end, lists:zip(Hosts, OldValues)) end. -get_c2s_status(#client{jid=Jid}) -> +-spec get_c2s_status(escalus:client()) -> term(). +get_c2s_status(#client{jid = Jid}) -> {match, USR} = re:run(Jid, <<"([^@]*)@([^/]*)/(.*)">>, [{capture, all_but_first, list}]), Pid = rpc(ejabberd_sm, get_session_pid, USR), rpc(sys, get_status, [Pid]). +-spec wait_for_session_count(escalus:config(), non_neg_integer()) -> ok | no_return(). wait_for_session_count(Config, Count) -> wait_for_session_count(Config, Count, 0). +-spec get_remote_sessions(escalus:config()) -> term(). get_remote_sessions(Config) -> escalus_overridables:do(Config, get_remote_sessions, [], {?MODULE, default_get_remote_sessions}). @@ -203,8 +211,13 @@ reset_option({Option, _, Set, _}, Config) -> %% escalus_user_db callbacks %%-------------------------------------------------------------------- -start(_) -> ok. -stop(_) -> ok. +-spec start(_) -> ok. +start(_) -> + ok. + +-spec stop(_) -> ok. +stop(_) -> + ok. -spec create_users(escalus:config(), [escalus_users:named_user()]) -> escalus:config(). create_users(Config, Users) -> @@ -220,11 +233,17 @@ delete_users(Config, Users) -> %% escalus_server callbacks %%-------------------------------------------------------------------- -pre_story(Config) -> Config. +-spec pre_story(escalus:config()) -> escalus:config(). +pre_story(Config) -> + Config. -post_story(Config) -> Config. +-spec post_story(escalus:config()) -> escalus:config(). +post_story(Config) -> + Config. -name() -> ?MODULE. +-spec name() -> atom(). +name() -> + ?MODULE. %%-------------------------------------------------------------------- %% Helpers @@ -245,16 +264,20 @@ unregister_user(Config, {_UserName, UserSpec}) -> [U, S, _P] = USP, rpc(ejabberd_admin, unregister, [U, S], 30000). +-spec default_get_remote_sessions() -> any(). default_get_remote_sessions() -> rpc(ejabberd_sm, get_full_session_list, []). +-spec legacy_get_remote_sessions() -> any(). legacy_get_remote_sessions() -> rpc(ejabberd_sm, dirty_get_sessions_list, []). +-spec unify_str_arg(any()) -> any(). unify_str_arg(Arg) -> StrFormat = escalus_ct:get_config(ejabberd_string_format), unify_str_arg(Arg, StrFormat). +-spec unify_str_arg(any(), str | string()) -> any(). unify_str_arg(Arg, str) when is_binary(Arg) -> binary_to_list(Arg); unify_str_arg(Arg, _) -> diff --git a/src/escalus_event.erl b/src/escalus_event.erl index 4cf2e61..156a53c 100644 --- a/src/escalus_event.erl +++ b/src/escalus_event.erl @@ -22,7 +22,6 @@ -include_lib("exml/include/exml.hrl"). --type config() :: escalus_config:config(). -type event_client() :: list({atom(), any()}). -type manager() :: pid(). -type resource() :: binary(). @@ -41,18 +40,23 @@ add_handler(Mgr, Handler, Args) -> delete_handler(Mgr, Handler, Args) -> gen_event:delete_handler(Mgr, Handler, Args). +-spec incoming_stanza(event_client(), exml_stream:element()) -> ok. incoming_stanza(Client, Stanza) -> notify_stanza(Client, incoming_stanza, Stanza). +-spec pop_incoming_stanza(event_client(), exml_stream:element()) -> ok. pop_incoming_stanza(Client, Stanza) -> notify_stanza(Client, pop_incoming_stanza, Stanza). +-spec outgoing_stanza(event_client(), exml_stream:element()) -> ok. outgoing_stanza(Client, Stanza) -> notify_stanza(Client, outgoing_stanza, Stanza). +-spec story_start(escalus_config:config()) -> ok. story_start(Config) -> gen_event:notify(manager(Config), story_start). +-spec story_end(escalus_config:config()) -> ok. story_end(Config) -> gen_event:notify(manager(Config), story_end). @@ -60,21 +64,23 @@ story_end(Config) -> %% ==================================================================== %% @doc Start the event manager -%% @end +-spec start(escalus_config:config()) -> escalus_config:config(). start(Config) -> {ok, Mgr} = gen_event:start_link(), add_handler(Mgr, escalus_history_h, []), [{escalus_event_mgr, Mgr} | Config]. %% @doc Stop the event manager -%% @end +-spec stop(escalus_config:config()) -> escalus_config:config(). stop(Config) -> gen_event:stop(manager(Config)), Config. +-spec get_history(escalus_config:config()) -> [term()]. get_history(Config) -> escalus_history_h:get_history(manager(Config)). +-spec print_history(escalus_config:config()) -> ok. print_history(Config) -> CaseName = proplists:get_value(tc_name, Config), PrivDir = proplists:get_value(priv_dir, Config), @@ -179,7 +185,7 @@ manager(Config) -> %% @doc Create a new event emitter. -spec new_client(Config, User, MaybeResource) -> undefined | EventClient when - Config :: config(), + Config :: escalus_config:config(), User :: escalus_users:user_name() | escalus_users:user_spec(), MaybeResource :: undefined | resource(), EventClient :: event_client(). @@ -206,7 +212,6 @@ new_client_1(Mgr, UserSpec, Resource) -> %% @doc Notify the event system of an event %%

The system accepts any term as the event.

-%% @end notify_stanza(undefined, _, _) -> ok; notify_stanza(Client, EventName, Stanza) -> diff --git a/src/escalus_fresh.erl b/src/escalus_fresh.erl index 7a89423..0e62292 100644 --- a/src/escalus_fresh.erl +++ b/src/escalus_fresh.erl @@ -117,9 +117,12 @@ create_fresh_user(Config, UserName) when is_atom(UserName) -> %%% Stateful API %%% Required if we expect to be able to clean up autogenerated users. +-spec start(escalus:config()) -> ok. start(_Config) -> application:ensure_all_started(worker_pool), ensure_table_present(nasty_global_table()). + +-spec stop(escalus:config()) -> ok | no_return(). stop(_) -> case whereis(nasty_global_table()) of undefined -> @@ -176,6 +179,7 @@ collect_deletion_results(Pending, Failed) -> %%% Internals nasty_global_table() -> escalus_fresh_db. +-spec work_on_deleting_users(term(), {term(), term()}, pid()) -> ok. work_on_deleting_users(Ord, {_Suffix, Conf} = _Item, CollectingPid) -> try do_delete_users(Conf) of _ -> diff --git a/src/escalus_history_h.erl b/src/escalus_history_h.erl index 7109876..c485847 100644 --- a/src/escalus_history_h.erl +++ b/src/escalus_history_h.erl @@ -1,4 +1,5 @@ -module(escalus_history_h). + -behaviour(gen_event). -export([get_history/1]). @@ -7,23 +8,26 @@ terminate/2, handle_info/2, handle_call/2, - handle_event/2, - code_change/3]). + handle_event/2]). -record(state, { events :: list() }). +-type state() :: #state{}. + -spec get_history(escalus_event:manager()) -> list(). get_history(Mgr) -> gen_event:call(Mgr, escalus_history_h, get_history). +-spec init([]) -> {ok, state()}. init([]) -> S = #state{ events = [] }, {ok, S}. +-spec handle_event(term(), state()) -> {ok, state()}. handle_event({incoming_stanza, Jid, Stanza}, State) -> {ok, save_stanza(incoming_stanza, Jid, Stanza, State)}; handle_event({outgoing_stanza, Jid, Stanza}, State) -> @@ -37,15 +41,15 @@ handle_event(story_end, State) -> handle_event(_Event, State) -> {ok, State}. +-spec handle_info(term(), state()) -> {ok, state()}. handle_info(_, State) -> {ok, State}. +-spec handle_call(get_history, state()) -> {ok, list(), state()}. handle_call(get_history, State=#state{events=Events}) -> {ok, lists:reverse(Events), State}. -code_change(_, _, State) -> - {ok, State}. - +-spec terminate(term(), state()) -> ok. terminate(_, _) -> ok. diff --git a/src/escalus_mongooseim.erl b/src/escalus_mongooseim.erl index 4478978..5cdc6a3 100644 --- a/src/escalus_mongooseim.erl +++ b/src/escalus_mongooseim.erl @@ -100,6 +100,7 @@ metric_type(Metric) -> [{_, Type, _} | _] = escalus_ejabberd:rpc(exometer, find_entries, [Metric]), Type. +-spec check_metric_change(T, [T]) -> [T]. check_metric_change({{Metric, {MinChange, MaxChange}}, Before, After}, Acc) -> Change = After - Before, case {Change < MinChange, Change > MaxChange} of diff --git a/src/escalus_new_assert.erl b/src/escalus_new_assert.erl index 9e572f4..1a6af6e 100644 --- a/src/escalus_new_assert.erl +++ b/src/escalus_new_assert.erl @@ -25,18 +25,21 @@ %% API functions %%============================================================================== +-spec assert(atom(), term()) -> ok | no_return(). assert(PredSpec, Arg) -> Fun = predspec_to_fun(PredSpec), StanzaStr = arg_to_list(Arg), assert_true(Fun(Arg), {assertion_failed, assert, PredSpec, Arg, StanzaStr}). +-spec assert(atom(), [term()], term()) -> ok | no_return(). assert(PredSpec, Params, Arg) -> Fun = predspec_to_fun(PredSpec, length(Params) + 1), StanzaStr = arg_to_list(Arg), assert_true(apply(Fun, Params ++ [Arg]), {assertion_failed, assert, PredSpec, Params, Arg, StanzaStr}). +-spec assert_many([atom()], [exml:element()]) -> ok | no_return(). assert_many(Predicates, Stanzas) -> AllStanzas = length(Predicates) == length(Stanzas), Ok = escalus_utils:mix_match(fun predspec_to_fun/1, Predicates, Stanzas), @@ -49,6 +52,7 @@ assert_many(Predicates, Stanzas) -> assert_true(Ok and AllStanzas, {assertion_failed, assert_many, AllStanzas, Predicates, Stanzas, StanzasStr}). +-spec mix_match([atom()], [exml:element()]) -> ok | no_return(). mix_match(Predicates, Stanzas) -> assert_many(Predicates, Stanzas). diff --git a/src/escalus_pred.erl b/src/escalus_pred.erl index 74ac412..e5b042c 100644 --- a/src/escalus_pred.erl +++ b/src/escalus_pred.erl @@ -110,8 +110,11 @@ %% Deprecation support %%-------------------------------------------------------------------- +-spec is_presence_stanza(any()) -> any(). ?DEPRECATED1(is_presence_stanza, is_presence). +-spec is_presence_type(any(), any()) -> any(). ?DEPRECATED2(is_presence_type, is_presence_with_type). +-spec is_result(any()) -> any(). ?DEPRECATED1(is_result, is_iq_result). %%-------------------------------------------------------------------- @@ -307,6 +310,7 @@ is_iq_set(Stanza) -> is_iq(<<"set">>, Stanza). -spec is_iq_get(exml:element()) -> boolean(). is_iq_get(Stanza) -> is_iq(<<"get">>, Stanza). +-spec is_iq_result_or_error(exml:element(), exml:element()) -> boolean(). is_iq_result_or_error(QueryStanza, ResultStanza) -> (is_iq_error(ResultStanza) orelse is_iq_result(ResultStanza)) andalso has_same_id(QueryStanza, ResultStanza). @@ -540,6 +544,7 @@ has_identity(Category, Type, Stanza) -> Idents). %% TODO: Remove as duplicates escalus_assert:has_no_stanzas/1 functionality. +-spec stanza_timeout(tuple()) -> boolean(). stanza_timeout(Arg) -> case element(1, Arg) of 'EXIT' -> @@ -671,8 +676,7 @@ is_bind_result(#xmlel{} = Stanza) -> %% Functors %%-------------------------------------------------------------------- -%% Not supported by erlang 15 :( -%%-spec 'not'( fun((...) -> boolean()) ) -> fun((...) -> boolean()). +-spec 'not'(fun((any()) -> boolean())) -> fun((any()) -> boolean()). 'not'(Pred) when is_function(Pred, 1) -> fun (Arg) -> not Pred(Arg) end; 'not'(Pred) when is_function(Pred, 2) -> diff --git a/src/escalus_pubsub_stanza.erl b/src/escalus_pubsub_stanza.erl index bd8bbe8..06febf0 100644 --- a/src/escalus_pubsub_stanza.erl +++ b/src/escalus_pubsub_stanza.erl @@ -173,10 +173,13 @@ get_user_subscriptions(User, Id, Node) -> end, pubsub_iq(<<"get">>, User, Id, NodeAddr, [Element]). +-spec get_subscription_options(escalus_utils:jid_spec(), binary(), {_, _}) -> exml:element(). get_subscription_options(User, Id, {NodeAddr, NodeName}) -> Element = subscription_options(NodeName, User), pubsub_iq(<<"get">>, User, Id, NodeAddr, [Element]). +-spec set_subscription_options(escalus_utils:jid_spec(), binary(), {_, _}, [tuple()]) -> + exml:element(). set_subscription_options(User, Id, {NodeAddr, NodeName}, Options) -> FormType = form_type_field_element(<<"subscribe_options">>), EncodedOptions = [FormType | lists:map(fun encode_form_field/1, Options)], @@ -283,22 +286,30 @@ purge_all_items(User, Id, {NodeAddr, NodeName}) -> %% Whole stanzas +-spec iq(binary(), escalus_utils:jid_spec(), binary(), pep | escalus_utils:jid_spec(), + [exml:cdata() | exml:element()]) -> exml:element(). iq(Type, From, Id, pep, Elements) -> Stanza = escalus_stanza:iq(Type, Elements), StanzaWithId = escalus_stanza:set_id(Stanza, Id), - escalus_stanza:from(StanzaWithId, escalus_utils:get_jid(From)); + escalus_stanza:from(StanzaWithId, From); iq(Type, From, Id, To, Elements) -> Stanza = escalus_stanza:iq(To, Type, Elements), StanzaWithId = escalus_stanza:set_id(Stanza, Id), - escalus_stanza:from(StanzaWithId, escalus_utils:get_jid(From)). + escalus_stanza:from(StanzaWithId, From). +-spec pubsub_iq(binary(), escalus_utils:jid_spec(), binary(), pep | escalus_utils:jid_spec(), + [exml:cdata() | exml:element()]) -> exml:element(). pubsub_iq(Type, User, Id, NodeAddr, Elements) -> pubsub_iq(Type, User, Id, NodeAddr, Elements, ?NS_PUBSUB). +-spec pubsub_iq(binary(), escalus_utils:jid_spec(), binary(), pep | escalus_utils:jid_spec(), + [exml:cdata() | exml:element()], binary()) -> exml:element(). pubsub_iq(Type, User, Id, NodeAddr, Elements, NS) -> PubSubElement = pubsub_element(Elements, NS), iq(Type, User, Id, NodeAddr, [PubSubElement]). +-spec pubsub_owner_iq(binary(), escalus_utils:jid_spec(), binary(), pep | escalus_utils:jid_spec(), + [exml:cdata() | exml:element()]) -> exml:element(). pubsub_owner_iq(Type, User, Id, NodeAddr, Elements) -> pubsub_iq(Type, User, Id, NodeAddr, Elements, ?NS_PUBSUB_OWNER). @@ -322,6 +333,7 @@ optional_form(FormName, NodeName, Type, Fields) -> create_node_element(NodeName) -> #xmlel{name = <<"create">>, attrs = #{<<"node">> => NodeName}}. +-spec pubsub_element([exml:cdata() | exml:element()], binary()) -> exml:element(). pubsub_element(Children, NS) -> #xmlel{name = <<"pubsub">>, attrs = #{<<"xmlns">> => NS}, diff --git a/src/escalus_rpc.erl b/src/escalus_rpc.erl index ab112c4..ebcb3dd 100644 --- a/src/escalus_rpc.erl +++ b/src/escalus_rpc.erl @@ -19,6 +19,7 @@ %% in a concurrent environment as it gets/sets the cookie %% with `erlang:get_cookie/0' and `erlang:set_cookie/1'. %% Interleaving these calls in concurrent processes is prone to race conditions. +-spec call(node(), module(), atom(), [term()], timeout(), atom()) -> term(). call(Node, Module, Function, Args, TimeOut, Cookie) -> call_with_cookie_match(Node, Module, Function, Args, TimeOut, Cookie). @@ -43,5 +44,5 @@ set_the_cookie([]) -> []; set_the_cookie(Cookie) -> Cookie0 = erlang:get_cookie(), - erlang:set_cookie(node(),Cookie), + erlang:set_cookie(node(), Cookie), Cookie0. diff --git a/src/escalus_server.erl b/src/escalus_server.erl index 47cc0b3..4bb0d39 100644 --- a/src/escalus_server.erl +++ b/src/escalus_server.erl @@ -33,9 +33,11 @@ pre_story(Config) -> post_story(Config) -> call_server(get_server(Config), post_story, [Config]). +-spec name(escalus:config()) -> any(). name(Config) -> call_server(get_server(Config), name, []). +-spec get_server(escalus:config()) -> any(). get_server(Config) -> escalus_config:get_config(escalus_xmpp_server, Config, undefined). diff --git a/src/escalus_stanza.erl b/src/escalus_stanza.erl index ce6b358..a2bdf03 100644 --- a/src/escalus_stanza.erl +++ b/src/escalus_stanza.erl @@ -86,7 +86,7 @@ search_iq/2]). %% XEP-0280: Message Carbons --export([carbons_disable/0,carbons_enable/0]). +-export([carbons_disable/0, carbons_enable/0]). %% XEP-0313: Message Archive Management -export([mam_archive_query/1, @@ -148,6 +148,7 @@ %% Stream - related functions %%-------------------------------------------------------------------- +-spec stream_start(binary(), binary()) -> exml_stream:start(). stream_start(Server, XMLNS) -> #xmlstreamstart{name = <<"stream:stream">>, attrs = #{<<"to">> => Server, @@ -156,43 +157,51 @@ stream_start(Server, XMLNS) -> <<"xmlns">> => XMLNS, <<"xmlns:stream">> => <<"http://etherx.jabber.org/streams">>}}. +-spec stream_end() -> exml_stream:stop(). stream_end() -> #xmlstreamend{name = <<"stream:stream">>}. +-spec ws_open(binary()) -> exml:element(). ws_open(Server) -> #xmlel{name= <<"open">>, attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-framing">>, <<"to">> => Server, <<"version">> => <<"1.0">>}}. -ws_close()-> +-spec ws_close() -> exml:element(). +ws_close() -> #xmlel{name= <<"close">>, attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-framing">>}}. +-spec starttls() -> exml:element(). starttls() -> #xmlel{name = <<"starttls">>, attrs = #{<<"xmlns">> => <<"urn:ietf:params:xml:ns:xmpp-tls">>}}. +-spec compress(binary()) -> exml:element(). compress(Method) -> #xmlel{name = <<"compress">>, attrs = #{<<"xmlns">> => <<"http://jabber.org/protocol/compress">>}, children = [#xmlel{name = <<"method">>, children = [#xmlcdata{content = Method}]}]}. --spec iq(binary(), [exml:element()]) -> exml:element(). +-spec iq(binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq(Type, Body) -> #xmlel{name = <<"iq">>, attrs = #{<<"type">> => Type, <<"id">> => id()}, children = Body}. +-spec iq(escalus_utils:jid_spec(), binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq(To, Type, Body) -> IQ = iq(Type, Body), - IQ#xmlel{attrs = maps:put(<<"to">>, To, IQ#xmlel.attrs)}. + to(IQ, To). %% slightly naughty, this isn't a stanza but it will go inside an +-spec query_el(binary(), [exml:cdata() | exml:element()]) -> exml:element(). query_el(NS, Children) -> query_el(NS, #{}, Children). +-spec query_el(binary(), exml:attrs(), [exml:cdata() | exml:element()]) -> exml:element(). query_el(NS, Attrs, Children) -> #xmlel{name = <<"query">>, attrs = maps:put(<<"xmlns">>, NS, Attrs), @@ -200,6 +209,7 @@ query_el(NS, Attrs, Children) -> %% http://xmpp.org/extensions/xep-0004.html %% slightly naughty - this isn't a stanza but can be a child of various stanza types +-spec x_data_form(binary(), [exml:cdata() | exml:element()]) -> exml:element(). x_data_form(Type, Children) -> #xmlel{name = <<"x">>, attrs = #{<<"xmlns">> => ?NS_DATA_FORMS, <<"type">> => Type}, @@ -223,27 +233,33 @@ session() -> iq(<<"set">>, [#xmlel{name = <<"session">>, attrs = #{<<"xmlns">> => NS}}]). +-spec to(exml:element(), escalus_utils:jid_spec()) -> exml:element(). to(Stanza, Recipient) when is_binary(Recipient) -> setattr(Stanza, <<"to">>, Recipient); to(Stanza, Recipient) -> setattr(Stanza, <<"to">>, escalus_utils:get_jid(Recipient)). +-spec from(exml:element(), escalus_utils:jid_spec()) -> exml:element(). from(Stanza, Recipient) when is_binary(Recipient) -> setattr(Stanza, <<"from">>, Recipient); from(Stanza, Recipient) -> setattr(Stanza, <<"from">>, escalus_utils:get_jid(Recipient)). +-spec set_id(exml:element(), binary()) -> exml:element(). set_id(Stanza, ID) -> setattr(Stanza, <<"id">>, ID). +-spec setattr(exml:element(), binary(), binary()) -> exml:element(). setattr(Stanza, Key, Val) -> NewAttrs = maps:put(Key, Val, Stanza#xmlel.attrs), Stanza#xmlel{attrs = NewAttrs}. +-spec tags([{binary(), binary()}]) -> [exml:element()]. tags(KVs) -> [#xmlel{name = K, children = [#xmlcdata{content = V}]} || {K, V} <- KVs]. +-spec presence(binary()) -> exml:element(). presence(Type) -> presence(Type, []). @@ -255,9 +271,12 @@ presence(Type, Children) -> attrs = #{<<"type">> => bin(Type)}, children = Children}. +-spec presence_direct(escalus_utils:jid_spec(), binary()) -> exml:element(). presence_direct(Recipient, Type) -> presence_direct(Recipient, Type, []). +-spec presence_direct( + escalus_utils:jid_spec(), binary(), [exml:element() | exml:cdata()]) -> exml:element(). presence_direct(#client{} = Recipient, Type, Body) -> %% FIXME: this clause is only for backwards compatibility, %% remove at some point @@ -276,11 +295,13 @@ presence_direct(#client{} = Recipient, Type, Body) -> presence_direct(Recipient, Type, Body) -> to(presence(Type, Body), Recipient). +-spec presence_show(binary()) -> exml:element(). presence_show(Show) -> presence(<<"available">>, [#xmlel{name = <<"show">>, children = [#xmlcdata{content = Show}]}]). +-spec error_element(binary(), binary()) -> exml:element(). error_element(Type, Condition) -> #xmlel{name = <<"error">>, attrs = #{<<"type">> => Type}, @@ -325,15 +346,18 @@ message(Text, Attrs) -> children = [#xmlcdata{content = Text}]}]}, Attrs). +-spec chat_to(escalus_utils:jid_spec(), binary()) -> exml:element(). chat_to(Recipient, Msg) -> message(Msg, #{type => <<"chat">>, to => Recipient}). +-spec chat(escalus_utils:jid_spec(), escalus_utils:jid_spec(), binary()) -> exml:element(). chat(Sender, Recipient, Msg) -> message(Msg, #{type => <<"chat">>, from => Sender, to => Recipient}). +-spec chat_to_short_jid(escalus_utils:jid_spec(), binary()) -> exml:element(). chat_to_short_jid(Recipient, Msg) -> message(Msg, #{type => <<"chat">>, to => escalus_utils:get_short_jid(Recipient)}). @@ -351,12 +375,14 @@ chat_to_with_id_and_timestamp(Recipient, Msg) -> id => uuid_v4(), timestamp => integer_to_binary(os:system_time(microsecond))}). +-spec chat_without_carbon_to(escalus_utils:jid_spec(), binary()) -> exml:element(). chat_without_carbon_to(Recipient, Msg) -> Stanza = #xmlel{children = Children} = chat_to(Recipient, Msg), Stanza#xmlel{children = Children ++ [#xmlel{name = <<"private">>, attrs = #{<<"xmlns">> => ?NS_CARBONS_2}}]}. +-spec receipt_req(exml:element()) -> exml:element(). receipt_req(#xmlel{name = <<"message">>, attrs = Attrs, children = Children } = Msg) -> ReqStanza = receipt_req_elem(), Msg2 = case maps:find(<<"id">>, Attrs) of @@ -367,6 +393,7 @@ receipt_req(#xmlel{name = <<"message">>, attrs = Attrs, children = Children } = end, Msg2#xmlel{ children = [ReqStanza | Children] }. +-spec receipt_conf(exml:element()) -> exml:element(). receipt_conf(#xmlel{attrs = #{<<"id">> := ID, <<"from">> := From} = Attrs, children = Children}) -> Type = case maps:find(<<"type">>, Attrs) of error -> <<"chat">>; @@ -405,26 +432,32 @@ receipt_conf_elem(ID) -> children = [] }. +-spec groupchat_to(escalus_utils:jid_spec(), binary()) -> exml:element(). groupchat_to(Recipient, Msg) -> message(undefined, Recipient, <<"groupchat">>, Msg). +-spec get_registration_fields() -> exml:element(). get_registration_fields() -> iq(<<"get">>, [#xmlel{name = <<"query">>, attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}}]). +-spec register_account([exml:cdata() | exml:element()]) -> exml:element(). register_account(Body) -> iq(<<"set">>, [#xmlel{name = <<"query">>, attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}, children = Body}]). +-spec remove_account() -> exml:element(). remove_account() -> iq(<<"set">>, [#xmlel{name = <<"query">>, attrs = #{<<"xmlns">> => <<"jabber:iq:register">>}, children = [#xmlel{name = <<"remove">>}]}]). +-spec iq_result(exml:element()) -> exml:element(). iq_result(Request) -> iq_result(Request, []). +-spec iq_result(exml:element(), [exml:cdata() | exml:element()]) -> exml:element(). iq_result(Request, Payload) -> ToAttr = case exml_query:attr(Request, <<"from">>) of undefined -> @@ -437,16 +470,20 @@ iq_result(Request, Payload) -> attrs = ToAttr#{<<"id">> => Id, <<"type">> => <<"result">>}, children = Payload}. +-spec iq_get(binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq_get(NS, Payload) -> iq_with_type(<<"get">>, NS, Payload). +-spec iq_set(binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq_set(NS, Payload) -> iq_with_type(<<"set">>, NS, Payload). +-spec iq_set_nonquery(binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq_set_nonquery(NS, Payload) -> %% Don't wrap payload with iq_with_type(<<"set">>, NS, Payload, nonquery). +-spec iq_with_type(binary(), binary(), [exml:cdata() | exml:element()]) -> exml:element(). iq_with_type(Type, NS, Payload) -> iq(Type, [#xmlel{name = <<"query">>, attrs = #{<<"xmlns">> => NS}, @@ -459,14 +496,17 @@ iq_with_type(Type, NS, Payload, nonquery) -> <<"id">> => id()}, children = Payload}. +-spec roster_get() -> exml:element(). roster_get() -> iq_get(?NS_ROSTER, []). +-spec roster_get(binary()) -> exml:element(). roster_get(Ver) -> #xmlel{children = [Query]} = Stanza = iq_get(?NS_ROSTER, []), NewQuery = Query#xmlel{attrs = maps:put(<<"ver">>, Ver, Query#xmlel.attrs)}, Stanza#xmlel{children = [NewQuery]}. +-spec roster_add_contacts([{escalus_utils:jid_spec(), list(), binary()}]) -> exml:element(). roster_add_contacts(ItemSpecs) -> iq_set(?NS_ROSTER, lists:map(fun contact_item/1, ItemSpecs)). @@ -481,47 +521,59 @@ contact_item({User, Groups, Nick}) -> children = [#xmlcdata{content = bin(Group)}]} || Group <- Groups]}. +-spec roster_add_contact(binary(), [binary()], binary()) -> exml:element(). roster_add_contact(User, Groups, Nick) -> roster_add_contacts([{User, Groups, Nick}]). %% FIXME: see contact_item/1 comment +-spec roster_remove_contact(escalus_utils:jid_spec()) -> exml:element(). roster_remove_contact(User) -> iq_set(?NS_ROSTER, [#xmlel{name = <<"item">>, attrs = #{<<"jid">> => escalus_utils:get_short_jid(User), <<"subscription">> => <<"remove">>}}]). +-spec private_set(exml:element()) -> exml:element(). private_set(Element) -> iq_set(?NS_PRIVATE, [Element]). +-spec private_get(term(), term()) -> exml:element(). private_get(NS, Name) -> Element = #xmlel{name = bin(Name), attrs = #{<<"xmlns">> => bin(NS)}}, iq_get(?NS_PRIVATE, [Element]). +-spec last_activity(escalus_utils:jid_spec()) -> exml:element(). last_activity(User) -> to(iq_get(?NS_LAST_ACTIVITY, []), User). +-spec privacy_get_all() -> exml:element(). privacy_get_all() -> iq_get(?NS_PRIVACY, []). +-spec privacy_get_lists([term()]) -> exml:element(). privacy_get_lists(ListNames) -> iq_get(?NS_PRIVACY, [#xmlel{name = <<"list">>, attrs = #{<<"name">> => bin(Name)}} || Name <- ListNames]). +-spec privacy_set_list(exml:element()) -> exml:element(). privacy_set_list(PrivacyList) -> iq_set(?NS_PRIVACY, [PrivacyList]). +-spec privacy_activate(term()) -> exml:element(). privacy_activate(ListName) -> privacy_set(<<"active">>, #{<<"name">> => bin(ListName)}). +-spec privacy_deactivate() -> exml:element(). privacy_deactivate()-> privacy_set(<<"active">>, #{}). +-spec privacy_set_default(term()) -> exml:element(). privacy_set_default(ListName) -> privacy_set(<<"default">>, #{<<"name">> => bin(ListName)}). +-spec privacy_no_default() -> exml:element(). privacy_no_default()-> privacy_set(<<"default">>, #{}). @@ -529,17 +581,20 @@ privacy_set(What, Attrs) -> iq_set(?NS_PRIVACY, [#xmlel{name = What, attrs = Attrs}]). %% Create empty list element with given name. +-spec privacy_list(binary(), [exml:cdata() | exml:element()]) -> exml:element(). privacy_list(Name, Items) -> #xmlel{name = <<"list">>, attrs = #{<<"name">> => Name}, children = Items}. +-spec privacy_list_item(binary(), binary(), [binary()]) -> exml:element(). privacy_list_item(Order, Action, Content) -> #xmlel{name = <<"item">>, attrs = #{<<"order">> => Order, <<"action">> => Action}, children = [#xmlel{name = C} || C <- Content]}. +-spec privacy_list_item(binary(), binary(), binary(), binary(), [binary()]) -> exml:element(). privacy_list_item(Order, Action, Type, Value, Content) -> #xmlel{name = <<"item">>, attrs = #{<<"order">> => Order, @@ -548,25 +603,33 @@ privacy_list_item(Order, Action, Type, Value, Content) -> <<"action">> => Action}, children = [#xmlel{name = C} || C <- Content]}. +-spec privacy_list_jid_item(binary(), binary(), escalus_utils:jid_spec(), [binary()]) -> + exml:element(). privacy_list_jid_item(Order, Action, Who, Contents) -> privacy_list_item(Order, Action, <<"jid">>, escalus_utils:get_jid(Who), Contents). +-spec disco_info(escalus_utils:jid_spec()) -> exml:element(). disco_info(JID) -> Query = query_el(?NS_DISCO_INFO, []), iq(JID, <<"get">>, [Query]). +-spec disco_info(escalus_utils:jid_spec(), binary()) -> exml:element(). disco_info(JID, Node) -> Query = query_el(?NS_DISCO_INFO, #{<<"node">> => Node}, []), iq(JID, <<"get">>, [Query]). +-spec disco_items(escalus_utils:jid_spec()) -> exml:element(). disco_items(JID) -> ItemsQuery = query_el(?NS_DISCO_ITEMS, []), iq(JID, <<"get">>, [ItemsQuery]). + +-spec disco_items(escalus_utils:jid_spec(), binary()) -> exml:element(). disco_items(JID, Node) -> ItemsQuery = query_el(?NS_DISCO_ITEMS, #{<<"node">> => Node}, []), iq(JID, <<"get">>, [ItemsQuery]). +-spec search_fields([null | {binary(), binary()} | term()]) -> [exml:element()]. search_fields([]) -> []; search_fields([null|Rest]) -> @@ -578,24 +641,30 @@ search_fields([{Key, Val}|Rest]) -> children = [#xmlcdata{content = Val}]}]} | search_fields(Rest)]. +-spec search_fields_iq(escalus_utils:jid_spec()) -> exml:element(). search_fields_iq(JID) -> iq(JID, <<"get">>, [ query_el(?NS_SEARCH, [])]). +-spec search_iq(escalus_utils:jid_spec(), [exml:cdata() | exml:element()]) -> exml:element(). search_iq(JID, Fields) -> Form = x_data_form(<<"submit">>, Fields), Query = query_el(?NS_SEARCH, [Form]), iq(JID, <<"set">>, [Query]). +-spec vcard_request() -> exml:element(). vcard_request() -> iq(<<"get">>, [vcard([])]). +-spec vcard_request(binary()) -> exml:element(). vcard_request(JID) -> iq(JID, <<"get">>, [vcard([])]). +-spec vcard_update(list()) -> exml:element(). vcard_update(Fields) -> iq(<<"set">>, [vcard(Fields)]). +-spec vcard_update(binary(), list()) -> exml:element(). vcard_update(JID, Fields) -> iq(JID, <<"set">>, [vcard(Fields)]). @@ -620,9 +689,11 @@ tuples_to_fields([{Name, Children}|Rest]) when is_list(Children) -> [field(Name, tuples_to_fields(Children)) | tuples_to_fields(Rest)]. +-spec adhoc_request(binary()) -> exml:element(). adhoc_request(Node) -> adhoc_request(Node, []). +-spec adhoc_request(binary(), [exml:cdata() | exml:element()]) -> exml:element(). adhoc_request(Node, Payload) -> iq(<<"set">>, [#xmlel{name = <<"command">>, attrs = #{<<"xmlns">> => ?NS_ADHOC, @@ -630,6 +701,7 @@ adhoc_request(Node, Payload) -> <<"action">> => <<"execute">>}, children = Payload}]). +-spec ping_request(escalus_utils:jid_spec()) -> exml:element(). ping_request(To) -> IQ = iq(<<"get">>, [#xmlel{name = <<"ping">>, attrs = #{<<"xmlns">> => ?NS_PING} @@ -637,34 +709,38 @@ ping_request(To) -> to(IQ, To). --spec service_discovery(binary()) -> #xmlel{}. +-spec service_discovery(binary()) -> exml:element(). service_discovery(Server) -> escalus_stanza:setattr(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), <<"to">>, Server). --spec auth(binary()) -> #xmlel{}. +-spec auth(binary()) -> exml:element(). auth(Mechanism) -> auth(Mechanism, []). --spec auth(binary(), [#xmlcdata{}]) -> #xmlel{}. +-spec auth(binary(), [exml:cdata() | exml:element()]) -> exml:element(). auth(Mechanism, Children) -> #xmlel{name = <<"auth">>, attrs = #{<<"xmlns">> => ?NS_SASL, <<"mechanism">> => Mechanism}, children = Children}. +-spec auth_response() -> exml:element(). auth_response() -> auth_response([]). +-spec auth_response([exml:cdata() | exml:element()]) -> exml:element(). auth_response(Children) -> #xmlel{name = <<"response">>, attrs = #{<<"xmlns">> => ?NS_SASL}, children = Children}. +-spec enable_sm() -> exml:element(). enable_sm() -> #xmlel{name = <<"enable">>, attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3}}. +-spec enable_sm(proplists:proplist()) -> exml:element(). enable_sm(Opts) -> case proplists:is_defined(resume, Opts) of true -> @@ -674,15 +750,18 @@ enable_sm(Opts) -> enable_sm() end. +-spec sm_request() -> exml:element(). sm_request() -> #xmlel{name = <<"r">>, attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3}}. +-spec sm_ack(integer()) -> exml:element(). sm_ack(H) -> #xmlel{name = <<"a">>, attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3, <<"h">> => integer_to_binary(H)}}. +-spec resume(binary(), integer()) -> exml:element(). resume(SMID, PrevH) -> #xmlel{name = <<"resume">>, attrs = #{<<"xmlns">> => ?NS_STREAM_MGNT_3, @@ -695,11 +774,11 @@ resume(SMID, PrevH) -> %% @TODO: move the stanza constructors from %% tests/mam_SUITE.erl into here. -spec field_el(binary(), binary(), undefined | binary() | [binary()]) -> - exml:element(). + undefined | exml:element(). field_el(_Name, _Type, undefined) -> undefined; field_el(Name, Type, Values) when is_list(Values) -> - Fields = lists:map(fun (E) -> + Fields = lists:map(fun(E) -> #xmlel{name = <<"value">>, children = [#xmlcdata{content = E}]} end, Values), @@ -762,8 +841,8 @@ mam_lookup_messages_iq(QueryId, Start, End, WithJID, DirectionWMessageId) -> -spec mam_lookup_messages_iq(binary(), binary(), binary(), binary(), term(), boolean()) -> exml:element(). mam_lookup_messages_iq(QueryId, Start, End, WithJID, DirectionWMessageId, Simple) -> - IQ = #xmlel{children=[Q]} = mam_lookup_messages_iq(QueryId, Start, End, - WithJID, Simple), + IQ = #xmlel{children = [Q]} = mam_lookup_messages_iq(QueryId, Start, End, + WithJID, Simple), RSM = defined([fmapM(fun rsm_after_or_before/1, DirectionWMessageId)]), Other = Q#xmlel.children, Q2 = Q#xmlel{children = Other ++ RSM}, @@ -797,9 +876,11 @@ max(_) -> %% XEP-0280 Carbons %% +-spec carbons_enable() -> exml:element(). carbons_enable() -> iq_set_nonquery(?NS_JABBER_CLIENT, [enable_carbons_el()]). +-spec carbons_disable() -> exml:element(). carbons_disable() -> iq_set_nonquery(?NS_JABBER_CLIENT, [disable_carbons_el()]). diff --git a/src/escalus_story.erl b/src/escalus_story.erl index 196c895..4752507 100644 --- a/src/escalus_story.erl +++ b/src/escalus_story.erl @@ -39,6 +39,7 @@ %% end). %% %% @end +-spec story(escalus:config(), term(), fun()) -> term(). story(ConfigIn, ResourceCounts, Story) -> story(ConfigIn, ResourceCounts, Story, []). @@ -59,9 +60,11 @@ story(ConfigIn, ResourceCounts, Story) -> %% See carboncopy_SUITE test properties for an example: %% https://github.com/esl/MongooseIM/blob/f0ed90a93e17f7f3d5baf4d51bbb3f8b19826dd8/test.disabled/ejabberd_tests/tests/carboncopy_SUITE.erl#L183-L185 %% @end +-spec story_with_client_list(escalus:config(), term(), fun()) -> term(). story_with_client_list(ConfigIn, ResourceCounts, Story) -> story(ConfigIn, ResourceCounts, Story, [clients_as_list]). +-spec story(escalus:config(), term(), fun(), proplists:proplist()) -> term(). story(ConfigIn, ResourceCounts, Story, Opts) -> ClientDescs = clients_from_resource_counts(ConfigIn, ResourceCounts), try @@ -86,6 +89,7 @@ make_everyone_friends(Config) -> Users = escalus_config:get_config(escalus_users, Config), make_everyone_friends(Config, Users). +-spec make_everyone_friends(escalus:config(), [{_, _}]) -> escalus:config(). make_everyone_friends(Config0, Users) -> % start the clients Config1 = escalus_cleaner:start(Config0), @@ -124,6 +128,7 @@ call_start_ready_clients(Config, UserCDs) -> escalus_overridables:do(Config, start_ready_clients, [Config, UserCDs], {?MODULE, start_ready_clients}). +-spec start_ready_clients(escalus:config(), [term()]) -> [escalus:client()]. start_ready_clients(Config, FlatCDs) -> {_, RClients} = lists:foldl(fun({UserSpec, BaseResource}, {N, Acc}) -> Resource = escalus_overridables:do(Config, modify_resource, [BaseResource], @@ -144,6 +149,7 @@ start_ready_clients(Config, FlatCDs) -> ensure_all_clean(Clients), Clients. +-spec send_initial_presence(escalus:client()) -> ok. send_initial_presence(Client) -> escalus_client:send(Client, escalus_stanza:presence(<<"available">>)). diff --git a/src/escalus_sup.erl b/src/escalus_sup.erl index 115e2fc..3ca7576 100644 --- a/src/escalus_sup.erl +++ b/src/escalus_sup.erl @@ -24,13 +24,11 @@ %% Supervisor callbacks -export([init/1]). -%% Helper macro for declaring children of supervisor --define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). - %% =================================================================== %% API functions %% =================================================================== +-spec start_link() -> supervisor:startlink_ret(). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). @@ -38,6 +36,7 @@ start_link() -> %% Supervisor callbacks %% =================================================================== +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. init([]) -> {ok, { {one_for_one, 5, 10}, []} }. diff --git a/src/escalus_utils.erl b/src/escalus_utils.erl index 5595bb9..369bb3a 100644 --- a/src/escalus_utils.erl +++ b/src/escalus_utils.erl @@ -46,16 +46,17 @@ -type jid_spec() :: #client{} | atom() | binary() | string(). -export_type([jid_spec/0]). --spec log_stanzas(iolist(), [#xmlel{}]) -> any(). +-spec log_stanzas(iolist(), [exml:element()]) -> any(). log_stanzas(Comment, Stanzas) -> error_logger:info_msg("~s:~s~n", [Comment, stanza_lines("\n * ", Stanzas)]). --spec pretty_stanza_list([#xmlel{}]) -> string(). +-spec pretty_stanza_list([exml:element()]) -> string(). pretty_stanza_list(Stanzas) -> binary_to_list(list_to_binary(stanza_lines(" ", Stanzas))). %% calls Fun(A, B) on each distinct (A =/= B) pair of elements in List %% if Fun(A, B) was called, then Fun(B, A) won't +-spec distinct_pairs(fun((T, T) -> any()), [T]) -> integer(). distinct_pairs(Fun, List) -> K = each_with_index(fun(A, N) -> each_with_index(fun(B, M) -> @@ -69,6 +70,7 @@ distinct_pairs(Fun, List) -> K * (K - 1) div 2. %% calls Fun(A, B) on each distinct (A =/= B) ordered pair of elements in List +-spec distinct_ordered_pairs(fun((T, T) -> any()), [T]) -> integer(). distinct_ordered_pairs(Fun, List) -> K = each_with_index(fun(A, N) -> each_with_index(fun(B, M) -> @@ -82,29 +84,35 @@ distinct_ordered_pairs(Fun, List) -> K * (K - 1). %% Calls Fun(Element, Index) for indices (starting from Start) and elements of List +-spec each_with_index(fun((T, integer()) -> any()), _, [T]) -> integer(). each_with_index(Fun, Start, List) -> lists:foldl(fun(Element, N) -> Fun(Element, N), N + 1 end, Start, List). +-spec all_true([boolean()]) -> boolean(). all_true(List) -> lists:foldl(fun erlang:'and'/2, true, List). +-spec any_true([boolean()]) -> boolean(). any_true(List) -> lists:foldl(fun erlang:'or'/2, false, List). +-spec identity(A) -> A. identity(X) -> X. %% Does for each Case in Cases exist a Cond in Conds such that %% (Predgen(Cond))(Case) == true? +-spec mix_match(fun((A) -> fun((B) -> boolean())), [A], [B]) -> boolean(). mix_match(Predgen, Conds, Cases) -> [] == lists:foldl(fun(Cond, CasesLeft) -> Pred = Predgen(Cond), drop_first_such(Pred, CasesLeft) end, Cases, Conds). +-spec drop_first_such(fun((A) -> boolean()), [A]) -> [A]. drop_first_such(Pred, List) -> drop_first_such(Pred, List, []). @@ -113,7 +121,7 @@ drop_first_such(_, [], Acc) -> drop_first_such(Pred, [H|T], Acc) -> case Pred(H) of true -> - lists:reverse(Acc) ++ T; + lists:reverse(Acc, T); false -> drop_first_such(Pred, T, [H|Acc]) end. @@ -121,6 +129,7 @@ drop_first_such(Pred, [H|T], Acc) -> stanza_lines(Prefix, Stanzas) -> [[Prefix, exml:to_iolist(S)] || S <- Stanzas]. +-spec show_backtrace() -> any(). show_backtrace() -> try throw(catch_me) catch _:_:S -> @@ -170,13 +179,16 @@ get_server(UserOrClient) -> get_resource(JID) -> regexp_get(JID, <<"^[^/]+/(.*)$">>). +-spec is_prefix(binary(), binary()) -> boolean(). is_prefix(Prefix, Full) when is_binary(Prefix), is_binary(Full) -> LCP = binary:longest_common_prefix([Prefix, Full]), size(Prefix) =< size(Full) andalso LCP == size(Prefix). +-spec start_clients([{_, _}]) -> {pid(), list(pid())}. start_clients(Clients) -> start_clients([], Clients). +-spec start_clients(escalus_config:config(), [{_, _}]) -> {pid(), list(pid())}. start_clients(Config0, ClientRecipes) -> AllCDs = escalus_config:get_config(escalus_users, Config0), FlatCDs = [{CD, Res} || {Username, Resources} <- ClientRecipes, @@ -188,6 +200,7 @@ start_clients(Config0, ClientRecipes) -> {escalus_story, start_ready_clients}), {Cleaner, Clients}. +-spec regexp_get(binary(), binary()) -> binary(). regexp_get(Jid, Regex) -> {match, [ShortJid]} = re:run(Jid, Regex, [{capture, all_but_first, binary}]),