aboutsummaryrefslogtreecommitdiffstats
path: root/lib/snmp/test/snmp_test_server.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/snmp/test/snmp_test_server.erl')
-rw-r--r--lib/snmp/test/snmp_test_server.erl416
1 files changed, 416 insertions, 0 deletions
diff --git a/lib/snmp/test/snmp_test_server.erl b/lib/snmp/test/snmp_test_server.erl
new file mode 100644
index 0000000000..d0a5185452
--- /dev/null
+++ b/lib/snmp/test/snmp_test_server.erl
@@ -0,0 +1,416 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Lightweight test server
+%%----------------------------------------------------------------------
+
+-module(snmp_test_server).
+
+-compile(export_all).
+
+-export([
+ run/1, run/2,
+
+ error/3,
+ skip/3,
+ fatal_skip/3,
+
+ init_per_testcase/2,
+ fin_per_testcase/2
+ ]).
+
+-include("snmp_test_lib.hrl").
+
+-define(GLOBAL_LOGGER, snmp_global_logger).
+-define(TEST_CASE_SUP, snmp_test_case_supervisor).
+
+-define(d(F,A),d(F,A,?LINE)).
+
+-ifndef(snmp_priv_dir).
+-define(snmp_priv_dir, "run-" ++ timestamp()).
+-endif.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Evaluates a test case or test suite
+%% Returns a list of failing test cases:
+%%
+%% {Mod, Fun, ExpectedRes, ActualRes}
+%%----------------------------------------------------------------------
+
+run([Mod, Fun]) when is_atom(Mod) andalso is_atom(Fun) ->
+ Res = run({Mod, Fun}, default_config(Mod)),
+ display_result(Res),
+ Res;
+run({Mod, _Fun} = Case) when is_atom(Mod) ->
+ io:format("~n", []),
+ Res = run(Case, default_config(Mod)),
+ display_result(Res),
+ Res;
+run(Mod) when is_atom(Mod) ->
+ io:format("~n", []),
+ Res = run(Mod, default_config(Mod)),
+ display_result(Res),
+ Res;
+run([Mod]) when is_atom(Mod) ->
+ io:format("~n", []),
+ Res = run(Mod, default_config(Mod)),
+ display_result(Res),
+ Res.
+
+
+run({Mod, Fun}, Config) when is_atom(Mod) andalso
+ is_atom(Fun) andalso
+ is_list(Config) ->
+ ?d("run(~p,~p) -> entry", [Mod, Fun]),
+ case (catch apply(Mod, Fun, [suite])) of
+ [] ->
+ io:format("~n~n*** Eval: ~p ***************~n",
+ [{Mod, Fun}]),
+ case eval(Mod, Fun, Config) of
+ {ok, _, _} ->
+ [];
+ Other ->
+ [Other]
+ end;
+
+ Cases when is_list(Cases) ->
+ io:format("~n*** Expand: ~p ...~n", [{Mod, Fun}]),
+ Map = fun(Case) when is_atom(Case) -> {Mod, Case};
+ (Case) -> Case
+ end,
+ run(lists:map(Map, Cases), Config);
+
+ {conf, InitSuite, Cases, FinishSuite} when is_atom(InitSuite) andalso
+ is_list(Cases) andalso
+ is_atom(FinishSuite) ->
+ ?d("run -> conf: "
+ "~n InitSuite: ~p"
+ "~n Cases: ~p"
+ "~n FinishSuite: ~p", [InitSuite, Cases, FinishSuite]),
+ do_suite(Mod, InitSuite, Cases, FinishSuite, Config);
+
+ {req, _, SubCases} when is_list(SubCases) ->
+ ?d("run -> req: "
+ "~n SubCases: ~p", [SubCases]),
+ do_subcases(Mod, Fun, SubCases, Config, []);
+
+ {req, _, Conf} ->
+ ?d("run -> req: "
+ "~n Conf: ~p", [Conf]),
+ do_subcases(Mod, Fun, [Conf], Config, []);
+
+ {'EXIT', {undef, _}} ->
+ io:format("~n*** Undefined: ~p~n", [{Mod, Fun}]),
+ [{nyi, {Mod, Fun}, ok}];
+
+ Error ->
+ io:format("~n*** Ignoring: ~p: ~p~n", [{Mod, Fun}, Error]),
+ [{failed, {Mod, Fun}, Error}]
+ end;
+
+run(Mod, Config) when is_atom(Mod) andalso is_list(Config) ->
+ run({Mod, all}, Config);
+
+run(Cases, Config) when is_list(Cases) andalso is_list(Config) ->
+ Errors = [run(Case, Config) || Case <- Cases],
+ lists:append(Errors);
+
+run(Bad, _Config) ->
+ [{badarg, Bad, ok}].
+
+
+do_suite(Mod, Init, Cases, Finish, Config0) ->
+ ?d("do_suite -> entry with"
+ "~n Mod: ~p"
+ "~n Init: ~p"
+ "~n Cases: ~p"
+ "~n Finish: ~p"
+ "~n Config0: ~p", [Mod, Init, Cases, Finish, Config0]),
+ case (catch apply(Mod, Init, [Config0])) of
+ Config when is_list(Config) ->
+ io:format("~n*** Expand: ~p ...~n", [Mod]),
+ Map = fun(Case) when is_atom(Case) -> {Mod, Case};
+ (Case) -> Case
+ end,
+ Res = run(lists:map(Map, Cases), Config),
+ (catch apply(Mod, Finish, [Config])),
+ Res;
+
+ {'EXIT', {skipped, Reason}} ->
+ io:format(" => skipping: ~p~n", [Reason]),
+ SkippedCases =
+ [{skipped, {Mod, Case}, suite_init_skipped} || Case <- Cases],
+ lists:flatten([[{skipped, {Mod, Init}, Reason}],
+ SkippedCases,
+ [{skipped, {Mod, Finish}, suite_init_skipped}]]);
+
+ Error ->
+ io:format(" => init (~p) failed: ~n~p~n", [Init, Error]),
+ InitResult =
+ [{failed, {Mod, Init}, Error}],
+ SkippedCases =
+ [{skipped, {Mod, Case}, suite_init_failed} || Case <- Cases],
+ FinResult =
+ case (catch apply(Mod, Finish, [Config0])) of
+ ok ->
+ [];
+ FinConfig when is_list(FinConfig) ->
+ [];
+ FinError ->
+ [{failed, {Mod, Finish}, FinError}]
+ end,
+ lists:flatten([InitResult, SkippedCases, FinResult])
+
+ end.
+
+
+do_subcases(_Mod, _Fun, [], _Config, Acc) ->
+ lists:flatten(lists:reverse(Acc));
+do_subcases(Mod, Fun, [{conf, Init, Cases, Finish}|SubCases], Config, Acc)
+ when is_atom(Init) andalso is_list(Cases) andalso is_atom(Finish) ->
+ R = case (catch apply(Mod, Init, [Config])) of
+ Conf when is_list(Conf) ->
+ io:format("~n*** Expand: ~p ...~n", [{Mod, Fun}]),
+ Map = fun(Case) when is_atom(Case) -> {Mod, Case};
+ (Case) -> Case
+ end,
+ Res = run(lists:map(Map, Cases), Conf),
+ (catch apply(Mod, Finish, [Conf])),
+ Res;
+
+ {'EXIT', {skipped, Reason}} ->
+ io:format(" => skipping: ~p~n", [Reason]),
+ [{skipped, {Mod, Fun}, Reason}];
+
+ Error ->
+ io:format(" => init (~p) failed: ~n~p~n", [Init, Error]),
+ (catch apply(Mod, Finish, [Config])),
+ [{failed, {Mod, Fun}, Error}]
+ end,
+ do_subcases(Mod, Fun, SubCases, Config, [R|Acc]);
+do_subcases(Mod, Fun, [SubCase|SubCases], Config, Acc) when atom(SubCase) ->
+ R = do_case(Mod, SubCase, Config),
+ do_subcases(Mod, Fun, SubCases,Config, [R|Acc]).
+
+
+do_case(M, F, C) ->
+ io:format("~n~n*** Eval: ~p ***************~n", [{M, F}]),
+ case eval(M, F, C) of
+ {ok, _, _} ->
+ [];
+ Other ->
+ [Other]
+ end.
+
+
+eval(Mod, Fun, Config) ->
+ Flag = process_flag(trap_exit, true),
+ global:register_name(?TEST_CASE_SUP, self()),
+ Config2 = Mod:init_per_testcase(Fun, Config),
+ Self = self(),
+ Eval = fun() -> do_eval(Self, Mod, Fun, Config2) end,
+ Pid = spawn_link(Eval),
+ R = wait_for_evaluator(Pid, Mod, Fun, Config2, []),
+ Mod:fin_per_testcase(Fun, Config2),
+ global:unregister_name(?TEST_CASE_SUP),
+ process_flag(trap_exit, Flag),
+ R.
+
+wait_for_evaluator(Pid, Mod, Fun, Config, Errors) ->
+ Pre = lists:concat(["TEST CASE: ", Fun]),
+ receive
+ {'EXIT', _Watchdog, watchdog_timeout} ->
+ io:format("*** ~s WATCHDOG TIMEOUT~n", [Pre]),
+ exit(Pid, kill),
+ {failed, {Mod, Fun}, watchdog_timeout};
+ {done, Pid, ok} when Errors =:= [] ->
+ io:format("*** ~s OK~n", [Pre]),
+ {ok, {Mod, Fun}, Errors};
+ {done, Pid, {ok, _}} when Errors =:= [] ->
+ io:format("*** ~s OK~n", [Pre]),
+ {ok, {Mod, Fun}, Errors};
+ {done, Pid, Fail} ->
+ io:format("*** ~s FAILED~n~p~n", [Pre, Fail]),
+ {failed, {Mod, Fun}, Fail};
+ {'EXIT', Pid, {skipped, Reason}} ->
+ io:format("*** ~s SKIPPED~n~p~n", [Pre, Reason]),
+ {skipped, {Mod, Fun}, Errors};
+ {'EXIT', Pid, Reason} ->
+ io:format("*** ~s CRASHED~n~p~n", [Pre, Reason]),
+ {crashed, {Mod, Fun}, [{'EXIT', Reason} | Errors]};
+ {fail, Pid, Reason} ->
+ io:format("*** ~s FAILED~n~p~n", [Pre, Reason]),
+ wait_for_evaluator(Pid, Mod, Fun, Config, Errors ++ [Reason])
+ end.
+
+do_eval(ReplyTo, Mod, Fun, Config) ->
+ case (catch apply(Mod, Fun, [Config])) of
+ {'EXIT', {skipped, Reason}} ->
+ ReplyTo ! {'EXIT', self(), {skipped, Reason}};
+ Other ->
+ ReplyTo ! {done, self(), Other}
+ end,
+ unlink(ReplyTo),
+ exit(shutdown).
+
+
+display_result([]) ->
+ io:format("TEST OK~n", []);
+
+display_result(Errors) when is_list(Errors) ->
+ Nyi = [MF || {nyi, MF, _} <- Errors],
+ Skipped = [{MF, Reason} || {skipped, MF, Reason} <- Errors],
+ Crashed = [{MF, Reason} || {crashed, MF, Reason} <- Errors],
+ Failed = [{MF, Reason} || {failed, MF, Reason} <- Errors],
+ display_skipped(Skipped),
+ display_crashed(Crashed),
+ display_failed(Failed),
+ display_summery(Nyi, Skipped, Crashed, Failed).
+
+display_summery(Nyi, Skipped, Crashed, Failed) ->
+ io:format("~nTest case summery:~n", []),
+ display_summery(Nyi, "not yet implemented"),
+ display_summery(Skipped, "skipped"),
+ display_summery(Crashed, "crashed"),
+ display_summery(Failed, "failed"),
+ io:format("~n", []).
+
+display_summery([], _) ->
+ ok;
+display_summery(Res, Info) ->
+ io:format(" ~w test cases ~s~n", [length(Res), Info]).
+
+display_skipped([]) ->
+ io:format("Skipped test cases: -~n", []);
+display_skipped(Skipped) ->
+ io:format("Skipped test cases:~n", []),
+ [io:format(" ~p => ~p~n", [MF, Reason]) || {MF, Reason} <- Skipped],
+ io:format("~n", []).
+
+display_crashed([]) ->
+ io:format("Crashed test cases: -~n", []);
+display_crashed(Crashed) ->
+ io:format("Crashed test cases:~n", []),
+ [io:format(" ~p => ~p~n", [MF, Reason]) || {MF, Reason} <- Crashed],
+ io:format("~n", []).
+
+display_failed([]) ->
+ io:format("Failed test cases: -~n", []);
+display_failed(Failed) ->
+ io:format("Failed test cases:~n", []),
+ [io:format(" ~p => ~p~n", [MF, Reason]) || {MF, Reason} <- Failed],
+ io:format("~n", []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Verify that the actual result of a test case matches the exected one
+%% Returns the actual result
+%% Stores the result in the process dictionary if mismatch
+
+error(Actual, Mod, Line) ->
+ global:send(?GLOBAL_LOGGER, {failed, Mod, Line}),
+ log("<ERROR> Bad result: ~p~n", [Actual], Mod, Line),
+ case global:whereis_name(?TEST_CASE_SUP) of
+ undefined ->
+ ignore;
+ Pid ->
+ Pid ! {fail, self(), {Actual, Mod, Line}}
+ end,
+ Actual.
+
+skip(Actual, Mod, Line) ->
+ log("Skipping test case~n", [], Mod, Line),
+ exit({skipped, {Actual, Mod, Line}}).
+
+fatal_skip(Actual, Mod, Line) ->
+ error(Actual, Mod, Line),
+ exit(shutdown).
+
+
+log(Format, Args, Mod, Line) ->
+ case global:whereis_name(?GLOBAL_LOGGER) of
+ undefined ->
+ io:format(user, "~p(~p): " ++ Format, [Mod, Line] ++ Args);
+ Pid ->
+ io:format(Pid, "~p(~p): " ++ Format, [Mod, Line] ++ Args)
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Test server callbacks
+
+init_per_testcase(_Case, Config) ->
+ global:register_name(?GLOBAL_LOGGER, group_leader()),
+ Config.
+
+fin_per_testcase(_Case, _Config) ->
+ global:unregister_name(?GLOBAL_LOGGER),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Internal utility functions
+
+default_config(Mod) ->
+ PrivDir0 = ?snmp_priv_dir,
+ case filename:pathtype(PrivDir0) of
+ absolute ->
+ ok;
+ _ ->
+ case file:make_dir(Mod) of
+ ok ->
+ ok;
+ {error, eexist} ->
+ ok
+ end,
+ PrivDir = filename:join(Mod, PrivDir0),
+ case file:make_dir(PrivDir) of
+ ok ->
+ ok;
+ {error, eexist} ->
+ ok;
+ Error ->
+ ?FAIL({failed_creating_subsuite_top_dir, Error})
+ end,
+ [{priv_dir, PrivDir}]
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+d(F, A, L) ->
+ d(true, F, A, L).
+ %% d(get(dbg), F, A, L).
+
+d(true, F, A, L) ->
+ io:format("STS[~w] ~p " ++ F ++ "~n", [L,self()|A]);
+d(_, _, _, _) ->
+ ok.
+
+timestamp() ->
+ {Date, Time} = calendar:now_to_datetime( now() ),
+ {YYYY, MM, DD} = Date,
+ {Hour, Min, Sec} = Time,
+ FormatDate =
+ io_lib:format("~.4w-~.2.0w-~.2.0w_~.2.0w.~.2.0w.~.2.0w",
+ [YYYY,MM,DD,Hour,Min,Sec]),
+ lists:flatten(FormatDate).
+