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