From 51357ab9516c7dbabff9438a22157459308370a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 20 Apr 2015 14:25:02 +0300 Subject: Add many features taken from Cowboy's test suite --- src/ct_helper.erl | 78 ++++++++++++++++++++++++- src/ct_helper_error_h.erl | 145 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/ct_helper_error_h.erl diff --git a/src/ct_helper.erl b/src/ct_helper.erl index 475f088..7516e9b 100644 --- a/src/ct_helper.erl +++ b/src/ct_helper.erl @@ -12,18 +12,47 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -%% @doc Helper functions for common_test suites. -module(ct_helper). +-export([all/1]). +-export([config/2]). -export([create_static_dir/1]). -export([delete_static_dir/1]). +-export([doc/1]). +-export([get_certs_from_ets/0]). -export([get_loopback_mtu/0]). +-export([ignore/3]). -export([make_certs/0]). +-export([make_certs_in_ets/0]). +-export([start/1]). -type der_encoded() :: binary(). -type key() :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'PrivateKeyInfo', der_encoded()}. +%% @doc List all test cases in the suite. +%% +%% Functions test and do_* are considered internal and are ignored. + +all(Suite) -> + lists:usort([F || {F, 1} <- Suite:module_info(exports), + F =/= module_info, + F =/= test, %% This is leftover from the eunit parse_transform... + F =/= all, + F =/= groups, + string:substr(atom_to_list(F), 1, 5) =/= "init_", + string:substr(atom_to_list(F), 1, 4) =/= "end_", + string:substr(atom_to_list(F), 1, 3) =/= "do_" + ]). + +%% @doc Quick configuration value retrieval. + +config(Key, Config) -> + {_, Value} = lists:keyfind(Key, 1, Config), + Value. + +%% @doc Create a directory with various useful files for testing. + create_static_dir(Path) -> ok = file:make_dir(Path), ok = file:make_dir(Path ++ "/directory"), @@ -35,6 +64,8 @@ create_static_dir(Path) -> ok = file:change_mode(Path ++ "/unreadable", 8#0333), ok. +%% @doc Delete the directory created with create_static_dir/1 + delete_static_dir(Path) -> ok = file:delete(Path ++ "/unreadable"), ok = file:delete(Path ++ "/index.html"), @@ -44,6 +75,19 @@ delete_static_dir(Path) -> ok = file:del_dir(Path), ok. +%% @doc Test case description. + +doc(String) -> + ct:comment(String), + ct:log(String). + +%% @doc Retrieve previously created certificates from the ets table. + +get_certs_from_ets() -> + ets:lookup_element(?MODULE, cert_opts, 2). + +%% @doc Return the MTU for the loopback interface. + get_loopback_mtu() -> {ok, Interfaces} = inet:getiflist(), [LocalInterface | _ ] = lists:filter(fun(Interface) -> @@ -53,10 +97,42 @@ get_loopback_mtu() -> {ok, [{mtu, MTU}]} = inet:ifget(LocalInterface, [mtu]), MTU. +%% @doc Ignore crashes from Pid occuring in M:F/A. + +ignore(M, F, A) -> + ct_helper_error_h:ignore(M, F, A). + %% @doc Create a set of certificates. + -spec make_certs() -> {CaCert::der_encoded(), Cert::der_encoded(), Key::key()}. make_certs() -> CaInfo = {CaCert, _} = erl_make_certs:make_cert([{key, dsa}]), {Cert, {Asn1Type, Der, _}} = erl_make_certs:make_cert([{key, dsa}, {issuer, CaInfo}]), {CaCert, Cert, {Asn1Type, Der}}. + +%% @doc Create a set of certificates and store them in an ets table. + +make_certs_in_ets() -> + {_, Cert, Key} = ct_helper:make_certs(), + CertOpts = [{cert, Cert}, {key, Key}], + Pid = spawn(fun() -> receive after infinity -> ok end end), + ?MODULE = ets:new(?MODULE, [ordered_set, public, named_table, + {heir, Pid, undefined}]), + ets:insert(?MODULE, {cert_opts, CertOpts}), + ok. + +%% @doc Start and stop applications and their dependencies. + +start(Apps) -> + _ = [do_start(App) || App <- Apps], + ok. + +do_start(App) -> + case application:start(App) of + ok -> + ok; + {error, {not_started, Dep}} -> + do_start(Dep), + do_start(App) + end. diff --git a/src/ct_helper_error_h.erl b/src/ct_helper_error_h.erl new file mode 100644 index 0000000..f2074fa --- /dev/null +++ b/src/ct_helper_error_h.erl @@ -0,0 +1,145 @@ +%% Copyright (c) 2014, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(ct_helper_error_h). +-behaviour(gen_event). + +%% Public interface. +-export([ignore/3]). + +%% gen_event. +-export([init/1]). +-export([handle_event/2]). +-export([handle_call/2]). +-export([handle_info/2]). +-export([terminate/2]). +-export([code_change/3]). + +%% Public interface. + +%% Ignore crashes from Pid occuring in M:F/A. +ignore(M, F, A) -> + gen_event:call(error_logger, ?MODULE, {expect, {self(), M, F, A}}). + +%% gen_event. + +init(_) -> + spawn(fun() -> error_logger:tty(false) end), + {ok, []}. + +%% Ignore supervisor and progress reports. +handle_event({info_report, _, {_, progress, _}}, State) -> + {ok, State}; +handle_event({info_report, _, {_, std_info, _}}, State) -> + {ok, State}; +handle_event({error_report, _, {_, supervisor_report, _}}, State) -> + {ok, State}; +%% Ignore gun retry failures. +handle_event({error_report, _, {_, crash_report, + [[{initial_call, {gun, init, _}}, _, _, + {error_info, {error, gone, _}}|_]|_]}}, + State) -> + {ok, State}; +%% Ignore emulator reports that are a duplicate of what Ranch gives us. +%% +%% The emulator always sends strings for errors, which makes it very +%% difficult to extract the information we need, hence the regexps. +handle_event(Event = {error, GL, {emulator, _, Msg}}, State) + when node(GL) =:= node() -> + Result = re:run(Msg, + "Error in process ([^\s]+).+? with exit value: " + ".+?{stacktrace,\\[{([^,]+),([^,]+),(.+)", + [{capture, all_but_first, list}]), + case Result of + nomatch -> + write_event(Event), + {ok, State}; + {match, [PidStr, MStr, FStr, Rest]} -> + A = case Rest of + "[]" ++ _ -> + 0; + "[" ++ Rest2 -> + count_args(Rest2, 1, 0); + _ -> + {match, [AStr]} = re:run(Rest, "([^,]+).+", + [{capture, all_but_first, list}]), + list_to_integer(AStr) + end, + Crash = {list_to_pid(PidStr), list_to_existing_atom(MStr), + list_to_existing_atom(FStr), A}, + case lists:member(Crash, State) of + true -> + {ok, lists:delete(Crash, State)}; + false -> + write_event(Event), + {ok, State} + end + end; +handle_event(Event = {error, GL, + {_, "Ranch listener" ++ _, [_, _, Pid, {[_, _, + {stacktrace, [{M, F, A, _}|_]}|_], _}]}}, + State) when node(GL) =:= node() -> + A2 = if is_list(A) -> length(A); true -> A end, + Crash = {Pid, M, F, A2}, + case lists:member(Crash, State) of + true -> + {ok, lists:delete(Crash, State)}; + false -> + write_event(Event), + {ok, State} + end; +handle_event(Event = {_, GL, _}, State) when node(GL) =:= node() -> + write_event(Event), + {ok, State}; +handle_event(_, State) -> + {ok, State}. + +handle_call({expect, Crash}, State) -> + {ok, ok, [Crash, Crash|State]}; +handle_call(_, State) -> + {ok, {error, bad_query}, State}. + +handle_info(_, State) -> + {ok, State}. + +terminate(_, _) -> + spawn(fun() -> error_logger:tty(true) end), + ok. + +code_change(_, State, _) -> + {ok, State}. + +%% Internal. + +write_event(Event) -> + error_logger_tty_h:write_event( + {erlang:universaltime(), Event}, + io). + +count_args("]" ++ _, N, 0) -> + N; +count_args("]" ++ Tail, N, Levels) -> + count_args(Tail, N, Levels - 1); +count_args("[" ++ Tail, N, Levels) -> + count_args(Tail, N, Levels + 1); +count_args("}" ++ Tail, N, Levels) -> + count_args(Tail, N, Levels - 1); +count_args("{" ++ Tail, N, Levels) -> + count_args(Tail, N, Levels + 1); +count_args("," ++ Tail, N, Levels = 0) -> + count_args(Tail, N + 1, Levels); +count_args("," ++ Tail, N, Levels) -> + count_args(Tail, N, Levels); +count_args([_|Tail], N, Levels) -> + count_args(Tail, N, Levels). -- cgit v1.2.3