1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2007-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21-module(tftp_test_lib).
22
23-compile(export_all).
24
25-include("tftp_test_lib.hrl").
26
27%%
28%% -----
29%%
30
31init_per_testcase(_Case, Config) when is_list(Config) ->
32    io:format("\n ", []),
33    ?IGNORE(application:stop(tftp)),
34    Config.
35
36end_per_testcase(_Case, Config) when is_list(Config) ->
37    ?IGNORE(application:stop(tftp)),
38    Config.
39
40%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
41%% Infrastructure for test suite
42%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43
44error(Actual, Mod, Line) ->
45    (catch global:send(tftp_global_logger, {failed, Mod, Line})),
46    log("<ERROR> Bad result: ~p\n", [Actual], Mod, Line),
47    Label = lists:concat([Mod, "(", Line, ") unexpected result"]),
48    et:report_event(60, Mod, Mod, Label,
49			[{line, Mod, Line}, {error, Actual}]),
50    case global:whereis_name(tftp_test_case_sup) of
51	undefined ->
52	    ignore;
53	Pid ->
54	    Fail = #'REASON'{mod = Mod, line = Line, desc = Actual},
55	    Pid ! {fail, self(), Fail}
56    end,
57    Actual.
58
59log(Format, Args, Mod, Line) ->
60    case global:whereis_name(tftp_global_logger) of
61	undefined ->
62	    io:format(user, "~p(~p): " ++ Format,
63		      [Mod, Line] ++ Args);
64	Pid ->
65	    io:format(Pid, "~p(~p): " ++ Format,
66		      [Mod, Line] ++ Args)
67    end.
68
69default_config() ->
70    [].
71
72t() ->
73    t([{?MODULE, all}]).
74
75t(Cases) ->
76    t(Cases, default_config()).
77
78t(Cases, Config) ->
79    process_flag(trap_exit, true),
80    Res = lists:flatten(do_test(Cases, Config)),
81    io:format("Res: ~p\n", [Res]),
82    display_result(Res),
83    Res.
84
85do_test({Mod, Fun}, Config) when is_atom(Mod), is_atom(Fun) ->
86    case catch apply(Mod, Fun, [suite]) of
87	[] ->
88	    io:format("Eval:   ~p:", [{Mod, Fun}]),
89	    Res = eval(Mod, Fun, Config),
90	    {R, _, _} = Res,
91	    io:format(" ~p\n", [R]),
92	    Res;
93
94	Cases when is_list(Cases) ->
95	    io:format("Expand: ~p ...\n", [{Mod, Fun}]),
96	    Map = fun(Case) when is_atom(Case)-> {Mod, Case};
97		     (Case) -> Case
98		  end,
99	    do_test(lists:map(Map, Cases), Config);
100
101        {req, _, {conf, Init, Cases, Finish}} ->
102	    case (catch apply(Mod, Init, [Config])) of
103		Conf when is_list(Conf) ->
104		    io:format("Expand: ~p ...\n", [{Mod, Fun}]),
105		    Map = fun(Case) when is_atom(Case)-> {Mod, Case};
106			     (Case) -> Case
107			  end,
108		    Res = do_test(lists:map(Map, Cases), Conf),
109		    (catch apply(Mod, Finish, [Conf])),
110		    Res;
111
112		{'EXIT', {skipped, Reason}} ->
113		    io:format(" => skipping: ~p\n", [Reason]),
114		    [{skipped, {Mod, Fun}, Reason}];
115
116		Error ->
117		    io:format(" => failed: ~p\n", [Error]),
118		    [{failed, {Mod, Fun}, Error}]
119	    end;
120
121        {'EXIT', {undef, _}} ->
122	    io:format("Undefined:   ~p\n", [{Mod, Fun}]),
123	    [{nyi, {Mod, Fun}, ok}];
124
125        Error ->
126	    io:format("Ignoring:   ~p: ~p\n", [{Mod, Fun}, Error]),
127	    [{failed, {Mod, Fun}, Error}]
128    end;
129do_test(Mod, Config) when is_atom(Mod) ->
130    Res = do_test({Mod, all}, Config),
131    Res;
132do_test(Cases, Config) when is_list(Cases) ->
133    [do_test(Case, Config) || Case <- Cases];
134do_test(Bad, _Config) ->
135    [{badarg, Bad, ok}].
136
137eval(Mod, Fun, Config) ->
138    TestCase = {?MODULE, Mod, Fun},
139    Label = lists:concat(["TEST CASE: ", Fun]),
140    et:report_event(40, ?MODULE, Mod, Label ++ " started",
141			[TestCase, Config]),
142    global:register_name(tftp_test_case_sup, self()),
143    Flag = process_flag(trap_exit, true),
144    Config2 = Mod:init_per_testcase(Fun, Config),
145    Pid = spawn_link(?MODULE, do_eval, [self(), Mod, Fun, Config2]),
146    R = wait_for_evaluator(Pid, Mod, Fun, Config2, []),
147    Mod:end_per_testcase(Fun, Config2),
148    global:unregister_name(tftp_test_case_sup),
149    process_flag(trap_exit, Flag),
150    R.
151
152wait_for_evaluator(Pid, Mod, Fun, Config, Errors) ->
153    TestCase = {?MODULE, Mod, Fun},
154    Label = lists:concat(["TEST CASE: ", Fun]),
155    receive
156	{done, Pid, ok} when Errors == [] ->
157	    et:report_event(40, Mod, ?MODULE, Label ++ " ok",
158				[TestCase, Config]),
159	    {ok, {Mod, Fun}, Errors};
160	{done, Pid, {ok, _}} when Errors == [] ->
161	    et:report_event(40, Mod, ?MODULE, Label ++ " ok",
162				[TestCase, Config]),
163	    {ok, {Mod, Fun}, Errors};
164	{done, Pid, Fail} ->
165	    et:report_event(20, Mod, ?MODULE, Label ++ " failed",
166				[TestCase, Config, {return, Fail}, Errors]),
167	    {failed, {Mod,Fun}, Fail};
168	{'EXIT', Pid, {skipped, Reason}} ->
169	    et:report_event(20, Mod, ?MODULE, Label ++ " skipped",
170				[TestCase, Config, {skipped, Reason}]),
171	    {skipped, {Mod, Fun}, Errors};
172	{'EXIT', Pid, Reason} ->
173	    et:report_event(20, Mod, ?MODULE, Label ++ " crashed",
174				[TestCase, Config, {'EXIT', Reason}]),
175	    {crashed, {Mod, Fun}, [{'EXIT', Reason} | Errors]};
176	{fail, Pid, Reason} ->
177	    wait_for_evaluator(Pid, Mod, Fun, Config, Errors ++ [Reason])
178    end.
179
180do_eval(ReplyTo, Mod, Fun, Config) ->
181    case (catch apply(Mod, Fun, [Config])) of
182	{'EXIT', {skipped, Reason}} ->
183	    ReplyTo ! {'EXIT', self(), {skipped, Reason}};
184	Other ->
185	    ReplyTo ! {done, self(), Other}
186    end,
187    unlink(ReplyTo),
188    exit(shutdown).
189
190display_result([]) ->
191    io:format("OK\n", []);
192display_result(Res) when is_list(Res) ->
193    Ok      = [MF || {ok, MF, _}  <- Res],
194    Nyi     = [MF || {nyi, MF, _} <- Res],
195    Skipped = [{MF, Reason} || {skipped, MF, Reason} <- Res],
196    Failed  = [{MF, Reason} || {failed, MF, Reason} <- Res],
197    Crashed = [{MF, Reason} || {crashed, MF, Reason} <- Res],
198    display_summary(Ok, Nyi, Skipped, Failed, Crashed),
199    display_skipped(Skipped),
200    display_failed(Failed),
201    display_crashed(Crashed).
202
203display_summary(Ok, Nyi, Skipped, Failed, Crashed) ->
204    io:format("\nTest case summary:\n", []),
205    display_summary(Ok,      "successful"),
206    display_summary(Nyi,     "not yet implemented"),
207    display_summary(Skipped, "skipped"),
208    display_summary(Failed,  "failed"),
209    display_summary(Crashed, "crashed"),
210    io:format("\n", []).
211
212display_summary(Res, Info) ->
213    io:format("  ~w test cases ~s\n", [length(Res), Info]).
214
215display_skipped([]) ->
216    ok;
217display_skipped(Skipped) ->
218    io:format("Skipped test cases:\n", []),
219    F = fun({MF, Reason}) -> io:format("  ~p => ~p\n", [MF, Reason]) end,
220    lists:foreach(F, Skipped),
221    io:format("\n", []).
222
223
224display_failed([]) ->
225    ok;
226display_failed(Failed) ->
227    io:format("Failed test cases:\n", []),
228    F = fun({MF, Reason}) -> io:format("  ~p => ~p\n", [MF, Reason]) end,
229    lists:foreach(F, Failed),
230    io:format("\n", []).
231
232display_crashed([]) ->
233    ok;
234display_crashed(Crashed) ->
235    io:format("Crashed test cases:\n", []),
236    F = fun({MF, Reason}) -> io:format("  ~p => ~p\n", [MF, Reason]) end,
237    lists:foreach(F, Crashed),
238    io:format("\n", []).
239
240%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
241%% generic callback
242%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
243
244-record(generic_state, {state, prepare, open, read, write, abort}).
245
246prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
247    State   = lookup_option(state,   mandatory, Initial),
248    Prepare = lookup_option(prepare, mandatory, Initial),
249    Open    = lookup_option(open,    mandatory, Initial),
250    Read    = lookup_option(read,    mandatory, Initial),
251    Write   = lookup_option(write,   mandatory, Initial),
252    Abort   = lookup_option(abort,   mandatory, Initial),
253    case Prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, State) of
254	{ok, AcceptedOptions, NewState} ->
255	    {ok,
256	     AcceptedOptions,
257	     #generic_state{state   = NewState,
258			    prepare = Prepare,
259			    open    = Open,
260			    read    = Read,
261			    write   = Write,
262			    abort   = Abort}};
263	Other ->
264	    Other
265    end.
266
267open(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
268    case prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) of
269 	{ok, SuggestedOptions2, GenericState} ->
270	    open(Peer, Access, LocalFilename, Mode, SuggestedOptions2, GenericState);
271	Other ->
272	    Other
273    end;
274open(Peer, Access, LocalFilename, Mode, SuggestedOptions, #generic_state{state = State, open = Open} = GenericState) ->
275    case Open(Peer, Access, LocalFilename, Mode, SuggestedOptions, State) of
276	{ok, SuggestedOptions2, NewState} ->
277	    {ok, SuggestedOptions2, GenericState#generic_state{state = NewState}};
278	Other ->
279	    Other
280    end.
281
282read(#generic_state{state = State, read = Read} = GenericState) ->
283    case Read(State) of
284	{more, DataBlock, NewState} ->
285	    {more, DataBlock, GenericState#generic_state{state = NewState}};
286	Other ->
287	    Other
288    end.
289
290write(DataBlock, #generic_state{state = State, write = Write} = GenericState) ->
291    case Write(DataBlock, State) of
292	{more, NewState} ->
293	    {more, GenericState#generic_state{state = NewState}};
294	Other ->
295	    Other
296    end.
297
298abort(Code, Text, #generic_state{state = State, abort = Abort}) ->
299    Abort(Code, Text, State).
300
301lookup_option(Key, Default, Options) ->
302    case lists:keysearch(Key, 1, Options) of
303	{value, {_, Val}} ->
304	    Val;
305	false ->
306	    Default
307    end.
308
309