1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2003-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(ct).
22
23-include("ct.hrl").
24-include("ct_util.hrl").
25
26%% Command line user interface for running tests
27-export([install/1, run/1, run/2, run/3,
28	 run_test/1, run_testspec/1, step/3, step/4,
29	 start_interactive/0, stop_interactive/0]).
30
31%% Test suite API
32-export([require/1, require/2,
33	 get_config/1, get_config/2, get_config/3,
34	 reload_config/1,
35	 escape_chars/1, escape_chars/2,
36	 log/1, log/2, log/3, log/4, log/5,
37	 print/1, print/2, print/3, print/4, print/5,
38	 pal/1, pal/2, pal/3, pal/4, pal/5,
39         set_verbosity/2, get_verbosity/1,
40	 capture_start/0, capture_stop/0, capture_get/0, capture_get/1,
41	 fail/1, fail/2, comment/1, comment/2, make_priv_dir/0,
42	 testcases/2, userdata/2, userdata/3,
43	 timetrap/1, get_timetrap_info/0, sleep/1,
44	 notify/2, sync_notify/2,
45	 break/1, break/2, continue/0, continue/1]).
46
47%% New API for manipulating with config handlers
48-export([add_config/2, remove_config/2]).
49
50%% Other interface functions
51-export([get_status/0, abort_current_testcase/1,
52	 get_event_mgr_ref/0,
53	 get_testspec_terms/0, get_testspec_terms/1,
54	 encrypt_config_file/2, encrypt_config_file/3,
55	 decrypt_config_file/2, decrypt_config_file/3]).
56
57-export([get_target_name/1]).
58-export([get_progname/0]).
59-export([parse_table/1, listenv/1]).
60
61-export([remaining_test_procs/0]).
62
63%%----------------------------------------------------------------------
64%% Exported types
65%%----------------------------------------------------------------------
66%% For ct_gen_conn
67-export_type([config_key/0,
68	      target_name/0,
69	      key_or_name/0]).
70
71%% For cth_conn_log
72-export_type([conn_log_options/0,
73	      conn_log_type/0,
74	      conn_log_mod/0]).
75
76%%------------------------------------------------------------------
77%% Type declarations
78%% ------------------------------------------------------------------
79-type config_key() :: atom(). % Config key which exists in a config file
80-type target_name() :: atom().% Name associated to a config_key() though 'require'
81-type key_or_name() :: config_key() | target_name().
82
83%% Types used when logging connections with the 'cth_conn_log' hook
84-type conn_log_options() :: [conn_log_option()].
85-type conn_log_option() :: {log_type,conn_log_type()} |
86                           {hosts,[key_or_name()]}.
87-type conn_log_type() :: raw | pretty | html | silent.
88-type conn_log_mod() :: ct_netconfc | ct_telnet.
89%%----------------------------------------------------------------------
90
91
92install(Opts) ->
93    ct_run:install(Opts).
94
95run(TestDir,Suite,Cases) ->
96    ct_run:run(TestDir,Suite,Cases).
97
98run(TestDir,Suite) ->
99    ct_run:run(TestDir,Suite).
100
101run(TestDirs) ->
102    ct_run:run(TestDirs).
103
104run_test(Opts) ->
105    ct_run:run_test(Opts).
106
107run_testspec(TestSpec) ->
108    ct_run:run_testspec(TestSpec).
109
110step(TestDir,Suite,Case) ->
111    ct_run:step(TestDir,Suite,Case).
112
113step(TestDir,Suite,Case,Opts) ->
114    ct_run:step(TestDir,Suite,Case,Opts).
115
116start_interactive() ->
117    _ = ct_util:start(interactive),
118    ok.
119
120stop_interactive() ->
121    ct_util:stop(normal),
122    ok.
123
124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
125%%% MISC INTERFACE
126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
127
128require(Required) ->
129    ct_config:require(Required).
130
131require(Name,Required) ->
132    ct_config:require(Name,Required).
133
134get_config(Required) ->
135    ct_config:get_config(Required,undefined,[]).
136
137get_config(Required,Default) ->
138    ct_config:get_config(Required,Default,[]).
139
140get_config(Required,Default,Opts) ->
141    ct_config:get_config(Required,Default,Opts).
142
143reload_config(Required)->
144    ct_config:reload_config(Required).
145
146get_testspec_terms() ->
147    case ct_util:get_testdata(testspec) of
148	undefined ->
149	    undefined;
150	CurrSpecRec ->
151	    ct_testspec:testspec_rec2list(CurrSpecRec)
152    end.
153
154get_testspec_terms(Tags) ->
155    case ct_util:get_testdata(testspec) of
156	undefined ->
157	    undefined;
158	CurrSpecRec ->
159	    ct_testspec:testspec_rec2list(Tags, CurrSpecRec)
160    end.
161
162escape_chars(IoList) ->
163    ct_logs:escape_chars(IoList).
164
165escape_chars(Format, Args) ->
166    try io_lib:format(Format, Args) of
167	IoList ->
168	    ct_logs:escape_chars(IoList)
169    catch
170	_:Reason ->
171	    {error,Reason}
172    end.
173
174log(Format) ->
175    log(default,?STD_IMPORTANCE,Format,[],[]).
176
177log(X1,X2) ->
178    {Category,Importance,Format,Args} =
179	if is_atom(X1)    -> {X1,?STD_IMPORTANCE,X2,[]};
180	   is_integer(X1) -> {default,X1,X2,[]};
181	   is_list(X1)    -> {default,?STD_IMPORTANCE,X1,X2}
182	end,
183    log(Category,Importance,Format,Args,[]).
184
185log(X1,X2,X3) ->
186    {Category,Importance,Format,Args,Opts} =
187	if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
188	   is_atom(X1), is_list(X2)    -> {X1,?STD_IMPORTANCE,X2,X3,[]};
189	   is_integer(X1)              -> {default,X1,X2,X3,[]};
190	   is_list(X1), is_list(X2)    -> {default,?STD_IMPORTANCE,X1,X2,X3}
191	end,
192    log(Category,Importance,Format,Args,Opts).
193
194log(X1,X2,X3,X4) ->
195    {Category,Importance,Format,Args,Opts} =
196	if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
197	   is_atom(X1), is_list(X2)    -> {X1,?STD_IMPORTANCE,X2,X3,X4};
198	   is_integer(X1)              -> {default,X1,X2,X3,X4}
199	end,
200    log(Category,Importance,Format,Args,Opts).
201
202log(Category,Importance,Format,Args,Opts) ->
203    ct_logs:tc_log(Category,Importance,Format,Args,Opts).
204
205print(Format) ->
206    print(default,?STD_IMPORTANCE,Format,[],[]).
207
208print(X1,X2) ->
209    {Category,Importance,Format,Args} =
210	if is_atom(X1)    -> {X1,?STD_IMPORTANCE,X2,[]};
211	   is_integer(X1) -> {default,X1,X2,[]};
212	   is_list(X1)    -> {default,?STD_IMPORTANCE,X1,X2}
213	end,
214    print(Category,Importance,Format,Args,[]).
215
216print(X1,X2,X3) ->
217    {Category,Importance,Format,Args,Opts} =
218	if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
219	   is_atom(X1), is_list(X2)    -> {X1,?STD_IMPORTANCE,X2,X3,[]};
220	   is_integer(X1)              -> {default,X1,X2,X3,[]};
221	   is_list(X1), is_list(X2)    -> {default,?STD_IMPORTANCE,X1,X2,X3}
222	end,
223    print(Category,Importance,Format,Args,Opts).
224
225print(X1,X2,X3,X4) ->
226    {Category,Importance,Format,Args,Opts} =
227	if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
228	   is_atom(X1), is_list(X2)    -> {X1,?STD_IMPORTANCE,X2,X3,X4};
229	   is_integer(X1)              -> {default,X1,X2,X3,X4}
230	end,
231    print(Category,Importance,Format,Args,Opts).
232
233print(Category,Importance,Format,Args,Opts) ->
234    ct_logs:tc_print(Category,Importance,Format,Args,Opts).
235
236pal(Format) ->
237    pal(default,?STD_IMPORTANCE,Format,[]).
238
239pal(X1,X2) ->
240    {Category,Importance,Format,Args} =
241	if is_atom(X1)    -> {X1,?STD_IMPORTANCE,X2,[]};
242	   is_integer(X1) -> {default,X1,X2,[]};
243	   is_list(X1)    -> {default,?STD_IMPORTANCE,X1,X2}
244	end,
245    pal(Category,Importance,Format,Args,[]).
246
247pal(X1,X2,X3) ->
248    {Category,Importance,Format,Args,Opts} =
249	if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
250	   is_atom(X1), is_list(X2)    -> {X1,?STD_IMPORTANCE,X2,X3,[]};
251	   is_integer(X1)              -> {default,X1,X2,X3,[]};
252	   is_list(X1), is_list(X2)    -> {default,?STD_IMPORTANCE,X1,X2,X3}
253	end,
254    pal(Category,Importance,Format,Args,Opts).
255
256pal(X1,X2,X3,X4) ->
257    {Category,Importance,Format,Args,Opts} =
258	if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
259	   is_atom(X1), is_list(X2)    -> {X1,?STD_IMPORTANCE,X2,X3,X4};
260	   is_integer(X1)              -> {default,X1,X2,X3,X4}
261	end,
262    pal(Category,Importance,Format,Args,Opts).
263
264pal(Category,Importance,Format,Args,Opts) ->
265    ct_logs:tc_pal(Category,Importance,Format,Args,Opts).
266
267set_verbosity(Category, Level) ->
268    ct_util:set_verbosity({Category,Level}).
269
270get_verbosity(Category) ->
271    ct_util:get_verbosity(Category).
272
273capture_start() ->
274    test_server:capture_start().
275
276capture_stop() ->
277    test_server:capture_stop().
278
279capture_get() ->
280    %% remove default log printouts (e.g. ct:log/2 printouts)
281    capture_get([default]).
282
283capture_get([ExclCat | ExclCategories]) ->
284    Strs = test_server:capture_get(),
285    CatsStr = [atom_to_list(ExclCat) |
286	       [[$| | atom_to_list(EC)] || EC <- ExclCategories]],
287    {ok,MP} = re:compile("<div class=\"(" ++ lists:flatten(CatsStr) ++ ")\">.*",
288                         [unicode]),
289    lists:flatmap(fun(Str) ->
290			  case re:run(Str, MP) of
291			      {match,_} -> [];
292			      nomatch -> [Str]
293			  end
294		  end, Strs);
295
296capture_get([]) ->
297    test_server:capture_get().
298
299fail(Reason) ->
300    try
301	exit({test_case_failed,Reason})
302    catch
303	Class:R:S ->
304	    case S of
305		[{?MODULE,fail,1,_}|Stk] -> ok;
306		Stk -> ok
307	    end,
308	    erlang:raise(Class, R, Stk)
309    end.
310
311fail(Format, Args) ->
312    try io_lib:format(Format, Args) of
313	Str ->
314	    try
315		exit({test_case_failed,lists:flatten(Str)})
316	    catch
317		Class:R:S ->
318		    case S of
319			[{?MODULE,fail,2,_}|Stk] -> ok;
320			Stk -> ok
321		    end,
322		    erlang:raise(Class, R, Stk)
323	    end
324    catch
325	_:BadArgs ->
326	    exit({BadArgs,{?MODULE,fail,[Format,Args]}})
327    end.
328
329comment(Comment) when is_list(Comment) ->
330    Formatted =
331	case (catch io_lib:format("~ts",[Comment])) of
332	    {'EXIT',_} ->  % it's a list not a string
333		io_lib:format("~tp",[Comment]);
334	    String ->
335		String
336	end,
337    send_html_comment(lists:flatten(Formatted));
338comment(Comment) ->
339    Formatted = io_lib:format("~tp",[Comment]),
340    send_html_comment(lists:flatten(Formatted)).
341
342comment(Format, Args) when is_list(Format), is_list(Args) ->
343    Formatted =
344	case (catch io_lib:format(Format, Args)) of
345	    {'EXIT',Reason} ->  % bad args
346		exit({Reason,{?MODULE,comment,[Format,Args]}});
347	    String ->
348		lists:flatten(String)
349	end,
350    send_html_comment(Formatted).
351
352send_html_comment(Comment) ->
353    Html = "<font color=\"green\">" ++ Comment ++ "</font>",
354    ct_util:set_testdata({{comment,group_leader()},Html}),
355    test_server:comment(Html).
356
357make_priv_dir() ->
358    test_server:make_priv_dir().
359
360get_target_name(Handle) ->
361    ct_util:get_target_name(Handle).
362
363-spec get_progname() -> string().
364
365get_progname() ->
366    case init:get_argument(progname) of
367	{ok, [[Prog]]} ->
368	    Prog;
369	_Other ->
370	    "no_prog_name"
371    end.
372
373parse_table(Data) ->
374    ct_util:parse_table(Data).
375
376listenv(Telnet) ->
377    ct_util:listenv(Telnet).
378
379testcases(TestDir, Suite) ->
380    case make_and_load(TestDir, Suite) of
381	E = {error,_} ->
382	    E;
383	_ ->
384	    case (catch Suite:all()) of
385		{'EXIT',Reason} ->
386		    {error,Reason};
387		TCs ->
388		    TCs
389	    end
390    end.
391
392make_and_load(Dir, Suite) ->
393    EnvInclude = string:lexemes(os:getenv("CT_INCLUDE_PATH", ""), [$:,$ ,$,]),
394    StartInclude =
395	case init:get_argument(include) of
396	    {ok,[Dirs]} -> Dirs;
397	    _ -> []
398	end,
399    UserInclude = EnvInclude ++ StartInclude,
400    case ct_run:run_make(Dir, Suite, UserInclude) of
401	MErr = {error,_} ->
402	    MErr;
403	_ ->
404	    TestDir = ct_util:get_testdir(Dir, Suite),
405	    File = filename:join(TestDir, atom_to_list(Suite)),
406	    case code:soft_purge(Suite) of
407		true ->
408		    code:load_abs(File);
409		false ->			% will use loaded
410		    {module,Suite}
411	    end
412    end.
413
414userdata(TestDir, Suite) ->
415    case make_and_load(TestDir, Suite) of
416	E = {error,_} ->
417	    E;
418	_ ->
419	    Info = (catch Suite:suite()),
420	    get_userdata(Info, "suite/0")
421    end.
422
423get_userdata({'EXIT',{Undef,_}}, Spec) when Undef == undef;
424					     Undef == function_clause ->
425    {error,list_to_atom(Spec ++ " is not defined")};
426get_userdata({'EXIT',Reason}, Spec) ->
427    {error,{list_to_atom("error in " ++ Spec),Reason}};
428get_userdata(List, _) when is_list(List) ->
429    Fun = fun({userdata,Data}, Acc) -> [Data | Acc];
430	     (_, Acc) -> Acc
431	  end,
432    case lists:foldl(Fun, [], List) of
433	Terms ->
434	    lists:flatten(lists:reverse(Terms))
435    end;
436get_userdata(_BadTerm, Spec) ->
437    {error,list_to_atom(Spec ++ " must return a list")}.
438
439userdata(TestDir, Suite, {group,GroupName}) ->
440    case make_and_load(TestDir, Suite) of
441	E = {error,_} ->
442	    E;
443	_ ->
444	    Info = (catch apply(Suite, group, [GroupName])),
445	    get_userdata(Info, "group("++atom_to_list(GroupName)++")")
446    end;
447
448userdata(TestDir, Suite, Case) when is_atom(Case) ->
449    case make_and_load(TestDir, Suite) of
450	E = {error,_} ->
451	    E;
452	_ ->
453	    Info = (catch apply(Suite, Case, [])),
454	    get_userdata(Info, atom_to_list(Case)++"/0")
455    end.
456
457get_status() ->
458    case get_testdata(curr_tc) of
459	{ok,TestCase} ->
460	    case get_testdata(stats) of
461		{ok,{Ok,Failed,Skipped={UserSkipped,AutoSkipped}}} ->
462		    [{current,TestCase},
463		     {successful,Ok},
464		     {failed,Failed},
465		     {skipped,Skipped},
466		     {total,Ok+Failed+UserSkipped+AutoSkipped}];
467		Err1 -> Err1
468	    end;
469	Err2 -> Err2
470    end.
471
472get_testdata(Key) ->
473    case catch ct_util:get_testdata(Key) of
474	{error,ct_util_server_not_running} ->
475	    no_tests_running;
476	Error = {error,_Reason} ->
477	    Error;
478	{'EXIT',_Reason} ->
479	    no_tests_running;
480	undefined ->
481	    {error,no_testdata};
482	[CurrTC] when Key == curr_tc ->
483	    {ok,CurrTC};
484	Data ->
485	    {ok,Data}
486    end.
487
488abort_current_testcase(Reason) ->
489    test_server_ctrl:abort_current_testcase(Reason).
490
491get_event_mgr_ref() ->
492    ?CT_EVMGR_REF.
493
494encrypt_config_file(SrcFileName, EncryptFileName) ->
495    ct_config:encrypt_config_file(SrcFileName, EncryptFileName).
496
497encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) ->
498    ct_config:encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile).
499
500decrypt_config_file(EncryptFileName, TargetFileName) ->
501    ct_config:decrypt_config_file(EncryptFileName, TargetFileName).
502
503decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) ->
504    ct_config:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile).
505
506add_config(Callback, Config)->
507    ct_config:add_config(Callback, Config).
508
509remove_config(Callback, Config) ->
510    ct_config:remove_config(Callback, Config).
511
512timetrap(Time) ->
513    test_server:timetrap_cancel(),
514    test_server:timetrap(Time).
515
516get_timetrap_info() ->
517    test_server:get_timetrap_info().
518
519sleep({hours,Hs}) ->
520    sleep(trunc(Hs * 1000 * 60 * 60));
521sleep({minutes,Ms}) ->
522    sleep(trunc(Ms * 1000 * 60));
523sleep({seconds,Ss}) ->
524    sleep(trunc(Ss * 1000));
525sleep(Time) ->
526    test_server:adjusted_sleep(Time).
527
528notify(Name,Data) ->
529    ct_event:notify(Name, Data).
530
531sync_notify(Name,Data) ->
532    ct_event:sync_notify(Name, Data).
533
534break(Comment) ->
535    case {ct_util:get_testdata(starter),
536	  ct_util:get_testdata(release_shell)} of
537	{ct,ReleaseSh} when ReleaseSh /= true ->
538	    Warning = "ct:break/1 can only be used if release_shell == true.\n",
539	    ct_logs:log("Warning!", Warning, []),
540	    io:format(user, "Warning! " ++ Warning, []),
541	    {error,'enable break with release_shell option'};
542	_ ->
543	    case get_testdata(curr_tc) of
544		{ok,{_,_TestCase}} ->
545		    test_server:break(?MODULE, Comment);
546		{ok,Cases} when is_list(Cases) ->
547		    {error,{'multiple cases running',
548			    [TC || {_,TC} <- Cases]}};
549		Error = {error,_} ->
550		    Error;
551		Error ->
552		    {error,Error}
553	    end
554    end.
555
556break(TestCase, Comment) ->
557    case {ct_util:get_testdata(starter),
558	  ct_util:get_testdata(release_shell)} of
559	{ct,ReleaseSh} when ReleaseSh /= true ->
560	    Warning = "ct:break/2 can only be used if release_shell == true.\n",
561	    ct_logs:log("Warning!", Warning, []),
562	    io:format(user, "Warning! " ++ Warning, []),
563	    {error,'enable break with release_shell option'};
564	_ ->
565	    case get_testdata(curr_tc) of
566		{ok,Cases} when is_list(Cases) ->
567		    case lists:keymember(TestCase, 2, Cases) of
568			true ->
569			    test_server:break(?MODULE, TestCase, Comment);
570			false ->
571			    {error,'test case not running'}
572		    end;
573		{ok,{_,TestCase}} ->
574		    test_server:break(?MODULE, TestCase, Comment);
575		Error = {error,_} ->
576		    Error;
577		Error ->
578		    {error,Error}
579	    end
580    end.
581
582continue() ->
583    test_server:continue().
584
585continue(TestCase) ->
586    test_server:continue(TestCase).
587
588
589remaining_test_procs() ->
590    ct_util:remaining_test_procs().
591