1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2004-2020. 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_run).
22
23%% Script interface
24-export([script_start/0,script_usage/0]).
25
26%% User interface
27-export([install/1,install/2,run/1,run/2,run/3,run_test/1,
28	 run_testspec/1,step/3,step/4,refresh_logs/1]).
29
30%% Misc internal API functions
31-export([variables_file_name/1,script_start1/2,run_test2/1, run_make/3]).
32
33-include("ct.hrl").
34-include("ct_event.hrl").
35-include("ct_util.hrl").
36
37-define(abs(Name), filename:absname(Name)).
38-define(testdir(Name, Suite), ct_util:get_testdir(Name, Suite)).
39
40-define(EXIT_STATUS_TEST_SUCCESSFUL, 0).
41-define(EXIT_STATUS_TEST_CASE_FAILED, 1).
42-define(EXIT_STATUS_TEST_RUN_FAILED, 2).
43
44-define(default_verbosity, [{default,?MAX_VERBOSITY},
45			    {'$unspecified',?MAX_VERBOSITY}]).
46
47-record(opts, {label,
48	       profile,
49	       shell,
50	       cover,
51	       cover_stop,
52	       coverspec,
53	       step,
54	       logdir,
55	       logopts = [],
56	       basic_html,
57	       esc_chars = true,
58	       verbosity = [],
59	       config = [],
60	       event_handlers = [],
61	       ct_hooks = [],
62	       enable_builtin_hooks,
63	       include = [],
64	       auto_compile,
65	       abort_if_missing_suites,
66	       silent_connections = [],
67	       stylesheet,
68	       multiply_timetraps,
69	       scale_timetraps,
70	       create_priv_dir,
71	       testspec_files = [],
72	       current_testspec,
73	       tests,
74	       starter}).
75
76script_start() ->
77    process_flag(trap_exit, true),
78    Init = init:get_arguments(),
79    CtArgs = lists:takewhile(fun({ct_erl_args,_}) -> false;
80				(_) -> true end, Init),
81
82    %% convert relative dirs added with pa or pz (pre erl_args on
83    %% the ct_run command line) to absolute so that app modules
84    %% can be found even after CT changes CWD to logdir
85    rel_to_abs(CtArgs),
86
87    Args =
88	case application:get_env(common_test, run_test_start_opts) of
89	    {ok,EnvStartOpts} ->
90		FlagFilter = fun(Flags) ->
91				     lists:filter(fun({root,_}) -> false;
92						     ({progname,_}) -> false;
93						     ({home,_}) -> false;
94						     ({noshell,_}) -> false;
95						     ({noinput,_}) -> false;
96						     (_) -> true
97						  end, Flags)
98			     end,
99		%% used for purpose of testing the run_test interface
100		io:format(user, "~n-------------------- START ARGS "
101			  "--------------------~n", []),
102		io:format(user, "--- Init args:~n~tp~n", [FlagFilter(Init)]),
103		io:format(user, "--- CT args:~n~tp~n", [FlagFilter(CtArgs)]),
104		EnvArgs = opts2args(EnvStartOpts),
105		io:format(user, "--- Env opts -> args:~n~tp~n   =>~n~tp~n",
106			  [EnvStartOpts,EnvArgs]),
107		Merged = merge_arguments(CtArgs ++ EnvArgs),
108		io:format(user, "--- Merged args:~n~tp~n", [FlagFilter(Merged)]),
109		io:format(user, "-----------------------------------"
110			  "-----------------~n~n", []),
111		Merged;
112	    _ ->
113		merge_arguments(CtArgs)
114	end,
115    case proplists:get_value(help, Args) of
116	undefined -> script_start(Args);
117	_ -> script_usage()
118    end.
119
120script_start(Args) ->
121    Tracing = start_trace(Args),
122    case ct_repeat:loop_test(script, Args) of
123	false ->
124	    {ok,Cwd} = file:get_cwd(),
125	    CTVsn =
126		case filename:basename(code:lib_dir(common_test)) of
127		    CTBase when is_list(CTBase) ->
128			case string:lexemes(CTBase, "-") of
129			    ["common_test",Vsn] -> " v"++Vsn;
130			    _ -> ""
131			end
132		end,
133	    io:format("~nCommon Test~s starting (cwd is ~ts)~n~n",
134	              [CTVsn,Cwd]),
135	    Self = self(),
136	    Pid = spawn_link(fun() -> script_start1(Self, Args) end),
137	    receive
138		{'EXIT',Pid,Reason} ->
139		    case Reason of
140			{user_error,What} ->
141			    io:format("\nTest run failed!\nReason: ~tp\n\n\n",
142                                      [What]),
143			    finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args);
144			_ ->
145			    io:format("Test run crashed! "
146                                      "This could be an internal error "
147				      "- please report!\n\n"
148				      "~tp\n\n\n", [Reason]),
149			    finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args)
150		    end;
151		{Pid,{error,Reason}} ->
152		    io:format("\nTest run failed! Reason:\n~tp\n\n\n",[Reason]),
153		    finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args);
154		{Pid,Result} ->
155		    io:nl(),
156		    finish(Tracing, analyze_test_result(Result, Args), Args)
157	    end;
158	{error,_LoopReason} ->
159	    finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args);
160	Result ->
161	    io:nl(),
162	    finish(Tracing, analyze_test_result(Result, Args), Args)
163    end.
164
165%% analyze the result of one test run, or many (in case of looped test)
166analyze_test_result(ok, _) ->
167    ?EXIT_STATUS_TEST_SUCCESSFUL;
168analyze_test_result({error,_Reason}, _) ->
169    ?EXIT_STATUS_TEST_RUN_FAILED;
170analyze_test_result({_Ok,Failed,{_UserSkipped,AutoSkipped}}, Args) ->
171    if Failed > 0 ->
172	    ?EXIT_STATUS_TEST_CASE_FAILED;
173       true ->
174	    case AutoSkipped of
175		0 ->
176		    ?EXIT_STATUS_TEST_SUCCESSFUL;
177		_ ->
178		    case get_start_opt(exit_status,
179				       fun([ExitOpt]) -> ExitOpt end,
180				       Args) of
181			undefined ->
182			    ?EXIT_STATUS_TEST_CASE_FAILED;
183			"ignore_config" ->
184		    	    ?EXIT_STATUS_TEST_SUCCESSFUL
185		    end
186	    end
187    end;
188analyze_test_result([Result|Rs], Args) ->
189    case analyze_test_result(Result, Args) of
190	?EXIT_STATUS_TEST_SUCCESSFUL ->
191	    analyze_test_result(Rs, Args);
192	Other ->
193	    Other
194    end;
195analyze_test_result([], _) ->
196    ?EXIT_STATUS_TEST_SUCCESSFUL;
197analyze_test_result(interactive_mode, _) ->
198    interactive_mode;
199analyze_test_result(Unknown, _) ->
200    io:format("\nTest run failed! Reason:\n~tp\n\n\n",[Unknown]),
201    ?EXIT_STATUS_TEST_RUN_FAILED.
202
203finish(Tracing, ExitStatus, Args) ->
204    stop_trace(Tracing),
205    timer:sleep(1000),
206    if ExitStatus == interactive_mode ->
207	    interactive_mode;
208       true ->
209            %% it's possible to tell CT to finish execution with a call
210            %% to a different function than the normal halt/1 BIF
211            %% (meant to be used mainly for reading the CT exit status)
212            case get_start_opt(halt_with,
213                               fun([HaltMod,HaltFunc]) ->
214                                       {list_to_atom(HaltMod),
215                                        list_to_atom(HaltFunc)} end,
216                               Args) of
217                undefined ->
218                    halt(ExitStatus);
219                {M,F} ->
220                    apply(M, F, [ExitStatus])
221            end
222    end.
223
224script_start1(Parent, Args) ->
225    %% tag this process
226    ct_util:mark_process(),
227    %% read general start flags
228    Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args),
229    Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args),
230    Shell = get_start_opt(shell, true, Args),
231    Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args),
232    CoverStop = get_start_opt(cover_stop,
233			      fun([CS]) -> list_to_atom(CS) end, Args),
234    LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args),
235    LogOpts = get_start_opt(logopts,
236			    fun(Os) -> [list_to_atom(O) || O <- Os] end,
237			    [], Args),
238    Verbosity = verbosity_args2opts(Args),
239    MultTT = get_start_opt(multiply_timetraps,
240			   fun([MT]) -> list_to_number(MT) end, Args),
241    ScaleTT = get_start_opt(scale_timetraps,
242			    fun([CT]) -> list_to_atom(CT);
243			       ([]) -> true
244			    end, Args),
245    CreatePrivDir = get_start_opt(create_priv_dir,
246				  fun([PD]) -> list_to_atom(PD);
247				     ([]) -> auto_per_tc
248				  end, Args),
249    EvHandlers = event_handler_args2opts(Args),
250    CTHooks = ct_hooks_args2opts(Args),
251    EnableBuiltinHooks = get_start_opt(enable_builtin_hooks,
252				       fun([CT]) -> list_to_atom(CT);
253					  ([]) -> undefined
254				       end, undefined, Args),
255
256    %% check flags and set corresponding application env variables
257
258    %% ct_decrypt_key | ct_decrypt_file
259    case proplists:get_value(ct_decrypt_key, Args) of
260	[DecryptKey] ->
261	    application:set_env(common_test, decrypt, {key,DecryptKey});
262	undefined ->
263	    case proplists:get_value(ct_decrypt_file, Args) of
264		[DecryptFile] ->
265		    application:set_env(common_test, decrypt,
266					{file,?abs(DecryptFile)});
267		undefined ->
268		    application:unset_env(common_test, decrypt)
269	    end
270    end,
271    %% no_auto_compile + include
272    {AutoCompile,IncludeDirs} =
273	case proplists:get_value(no_auto_compile, Args) of
274	    undefined ->
275		application:set_env(common_test, auto_compile, true),
276		InclDirs =
277		    case proplists:get_value(include, Args) of
278			Incls when is_list(hd(Incls)) ->
279			    [filename:absname(IDir) || IDir <- Incls];
280			Incl when is_list(Incl) ->
281			    [filename:absname(Incl)];
282			undefined ->
283			    []
284		    end,
285		case os:getenv("CT_INCLUDE_PATH") of
286		    false ->
287			application:set_env(common_test, include, InclDirs),
288			{undefined,InclDirs};
289		    CtInclPath ->
290			AllInclDirs =
291			    string:lexemes(CtInclPath,[$:,$ ,$,]) ++ InclDirs,
292			application:set_env(common_test, include, AllInclDirs),
293			{undefined,AllInclDirs}
294		end;
295	    _ ->
296		application:set_env(common_test, auto_compile, false),
297		{false,[]}
298	end,
299
300    %% abort test run if some suites can't be compiled
301    AbortIfMissing = get_start_opt(abort_if_missing_suites,
302				   fun([]) -> true;
303				      ([Bool]) -> list_to_atom(Bool)
304				   end, false, Args),
305    %% silent connections
306    SilentConns =
307	get_start_opt(silent_connections,
308		      fun(["all"]) -> [all];
309			 (Conns) -> [list_to_atom(Conn) || Conn <- Conns]
310		      end, [], Args),
311    %% stylesheet
312    Stylesheet = get_start_opt(stylesheet,
313			       fun([SS]) -> ?abs(SS) end, Args),
314    %% basic_html - used by ct_logs
315    BasicHtml = case proplists:get_value(basic_html, Args) of
316		    undefined ->
317			application:set_env(common_test, basic_html, false),
318			undefined;
319		    _ ->
320			application:set_env(common_test, basic_html, true),
321			true
322		end,
323    %% esc_chars - used by ct_logs
324    EscChars = case proplists:get_value(no_esc_chars, Args) of
325		   undefined ->
326		       application:set_env(common_test, esc_chars, true),
327		       undefined;
328		   _ ->
329		       application:set_env(common_test, esc_chars, false),
330		       false
331	       end,
332    %% disable_log_cache - used by ct_logs
333    case proplists:get_value(disable_log_cache, Args) of
334	undefined ->
335	    application:set_env(common_test, disable_log_cache, false);
336	_ ->
337	    application:set_env(common_test, disable_log_cache, true)
338    end,
339    %% log_cleanup - used by ct_logs
340    KeepLogs = get_start_opt(keep_logs,
341                             fun ct_logs:parse_keep_logs/1,
342                             all,
343                             Args),
344    application:set_env(common_test, keep_logs, KeepLogs),
345
346    Opts = #opts{label = Label, profile = Profile,
347		 shell = Shell,
348		 cover = Cover, cover_stop = CoverStop,
349		 logdir = LogDir, logopts = LogOpts,
350		 basic_html = BasicHtml,
351		 esc_chars = EscChars,
352		 verbosity = Verbosity,
353		 event_handlers = EvHandlers,
354		 ct_hooks = CTHooks,
355		 enable_builtin_hooks = EnableBuiltinHooks,
356		 auto_compile = AutoCompile,
357		 abort_if_missing_suites = AbortIfMissing,
358		 include = IncludeDirs,
359		 silent_connections = SilentConns,
360		 stylesheet = Stylesheet,
361		 multiply_timetraps = MultTT,
362		 scale_timetraps = ScaleTT,
363		 create_priv_dir = CreatePrivDir,
364		 starter = script},
365
366    %% check if log files should be refreshed or go on to run tests...
367    Result = run_or_refresh(Opts, Args),
368
369    %% send final results to starting process waiting in script_start/0
370    Parent ! {self(), Result}.
371
372run_or_refresh(Opts = #opts{logdir = LogDir}, Args) ->
373    case proplists:get_value(refresh_logs, Args) of
374	undefined ->
375	    script_start2(Opts, Args);
376	Refresh ->
377	    LogDir1 = case Refresh of
378			  [] -> which(logdir,LogDir);
379			  [RefreshDir] -> ?abs(RefreshDir)
380		      end,
381	    {ok,Cwd} = file:get_cwd(),
382	    ok = file:set_cwd(LogDir1),
383	    %% give the shell time to print version etc
384	    timer:sleep(500),
385	    io:nl(),
386	    case catch ct_logs:make_all_runs_index(refresh) of
387		{'EXIT',ARReason} ->
388		    ok = file:set_cwd(Cwd),
389		    {error,{all_runs_index,ARReason}};
390		_ ->
391		    case catch ct_logs:make_all_suites_index(refresh) of
392			{'EXIT',ASReason} ->
393			    ok = file:set_cwd(Cwd),
394			    {error,{all_suites_index,ASReason}};
395			_ ->
396			    ok = file:set_cwd(Cwd),
397			    io:format("Logs in ~ts refreshed!~n~n",
398				      [LogDir1]),
399			    timer:sleep(500), % time to flush io before quitting
400			    ok
401		    end
402	    end
403    end.
404
405script_start2(Opts = #opts{shell = undefined}, Args) ->
406    case proplists:get_value(spec, Args) of
407	Specs when Specs =/= [], Specs =/= undefined ->
408	    Specs1 = get_start_opt(join_specs, [Specs], Specs, Args),
409	    %% using testspec as input for test
410	    Relaxed = get_start_opt(allow_user_terms, true, false, Args),
411	    try ct_testspec:collect_tests_from_file(Specs1, Relaxed) of
412                TestSpecData ->
413		    execute_all_specs(TestSpecData, Opts, Args, [])
414            catch
415                throw:{error,Reason}:StackTrace ->
416		    {error,{invalid_testspec,{Reason,StackTrace}}};
417                _:Reason:StackTrace ->
418		    {error,{invalid_testspec,{Reason,StackTrace}}}
419            end;
420	[] ->
421	    {error,no_testspec_specified};
422	_ ->	    % no testspec used
423	    %% read config/userconfig from start flags
424	    InitConfig = ct_config:prepare_config_list(Args),
425	    TheLogDir = which(logdir, Opts#opts.logdir),
426	    case check_and_install_configfiles(InitConfig,
427					       TheLogDir,
428					       Opts) of
429		ok ->      % go on read tests from start flags
430		    script_start3(Opts#opts{config=InitConfig,
431					    logdir=TheLogDir}, Args);
432		Error ->
433		    Error
434	    end
435    end;
436
437script_start2(Opts, Args) ->
438    %% read config/userconfig from start flags
439    InitConfig = ct_config:prepare_config_list(Args),
440    LogDir = which(logdir, Opts#opts.logdir),
441    case check_and_install_configfiles(InitConfig, LogDir, Opts) of
442	ok ->      % go on read tests from start flags
443	    script_start3(Opts#opts{config=InitConfig,
444				    logdir=LogDir}, Args);
445	Error ->
446	    Error
447    end.
448
449execute_all_specs([], _, _, Result) ->
450    Result1 = lists:reverse(Result),
451    case lists:keysearch('EXIT', 1, Result1) of
452	{value,{_,_,ExitReason}} ->
453	    exit(ExitReason);
454	false ->
455	    case lists:keysearch(error, 1, Result1) of
456		{value,Error} ->
457		    Error;
458		false ->
459		    lists:foldl(fun({Ok,Fail,{UserSkip,AutoSkip}},
460				    {Ok1,Fail1,{UserSkip1,AutoSkip1}}) ->
461					{Ok1+Ok,Fail1+Fail,
462					 {UserSkip1+UserSkip,
463					  AutoSkip1+AutoSkip}}
464				end, {0,0,{0,0}}, Result1)
465	    end
466    end;
467
468execute_all_specs([{Specs,TS} | TSs], Opts, Args, Result) ->
469    CombinedOpts = combine_test_opts(TS, Specs, Opts),
470    try execute_one_spec(TS, CombinedOpts, Args) of
471	ExecResult ->
472	    execute_all_specs(TSs, Opts, Args, [ExecResult|Result])
473    catch
474	_ : ExitReason ->
475	    execute_all_specs(TSs, Opts, Args,
476			      [{'EXIT',self(),ExitReason}|Result])
477    end.
478
479execute_one_spec(TS, Opts, Args) ->
480    %% read config/userconfig from start flags
481    InitConfig = ct_config:prepare_config_list(Args),
482    TheLogDir = which(logdir, Opts#opts.logdir),
483    %% merge config from start flags with config from testspec
484    AllConfig = merge_vals([InitConfig, Opts#opts.config]),
485    case check_and_install_configfiles(AllConfig, TheLogDir, Opts) of
486	ok ->      % read tests from spec
487	    {Run,Skip} = ct_testspec:prepare_tests(TS, node()),
488	    Result = do_run(Run, Skip, Opts#opts{config=AllConfig,
489						 logdir=TheLogDir,
490						 current_testspec=TS}, Args),
491	    ct_util:delete_testdata(testspec),
492	    Result;
493	Error ->
494	    Error
495    end.
496
497combine_test_opts(TS, Specs, Opts) ->
498    TSOpts = get_data_for_node(TS, node()),
499
500    Label = choose_val(Opts#opts.label,
501		       TSOpts#opts.label),
502
503    Profile = choose_val(Opts#opts.profile,
504			 TSOpts#opts.profile),
505
506    LogDir = choose_val(Opts#opts.logdir,
507			TSOpts#opts.logdir),
508
509    AllLogOpts = merge_vals([Opts#opts.logopts,
510			     TSOpts#opts.logopts]),
511    AllVerbosity =
512	merge_keyvals([Opts#opts.verbosity,
513		       TSOpts#opts.verbosity]),
514    AllSilentConns =
515	merge_vals([Opts#opts.silent_connections,
516		    TSOpts#opts.silent_connections]),
517    Cover =
518	choose_val(Opts#opts.cover,
519		   TSOpts#opts.cover),
520    CoverStop =
521	choose_val(Opts#opts.cover_stop,
522		   TSOpts#opts.cover_stop),
523    MultTT =
524	choose_val(Opts#opts.multiply_timetraps,
525		   TSOpts#opts.multiply_timetraps),
526    ScaleTT =
527	choose_val(Opts#opts.scale_timetraps,
528		   TSOpts#opts.scale_timetraps),
529
530    CreatePrivDir =
531	choose_val(Opts#opts.create_priv_dir,
532		   TSOpts#opts.create_priv_dir),
533
534    AllEvHs =
535	merge_vals([Opts#opts.event_handlers,
536		    TSOpts#opts.event_handlers]),
537
538    AllCTHooks = merge_vals(
539		   [Opts#opts.ct_hooks,
540		    TSOpts#opts.ct_hooks]),
541
542    EnableBuiltinHooks =
543	choose_val(
544	  Opts#opts.enable_builtin_hooks,
545	  TSOpts#opts.enable_builtin_hooks),
546
547    Stylesheet =
548	choose_val(Opts#opts.stylesheet,
549		   TSOpts#opts.stylesheet),
550
551    AllInclude = merge_vals([Opts#opts.include,
552			     TSOpts#opts.include]),
553    application:set_env(common_test, include, AllInclude),
554
555    AutoCompile =
556	case choose_val(Opts#opts.auto_compile,
557			TSOpts#opts.auto_compile) of
558	    undefined ->
559		true;
560	    ACBool ->
561		application:set_env(common_test,
562				    auto_compile,
563				    ACBool),
564		ACBool
565	end,
566
567    AbortIfMissing = choose_val(Opts#opts.abort_if_missing_suites,
568				TSOpts#opts.abort_if_missing_suites),
569
570    BasicHtml =
571	case choose_val(Opts#opts.basic_html,
572			TSOpts#opts.basic_html) of
573	    undefined ->
574		false;
575	    BHBool ->
576		application:set_env(common_test, basic_html,
577				    BHBool),
578		BHBool
579	end,
580
581    EscChars =
582	case choose_val(Opts#opts.esc_chars,
583			TSOpts#opts.esc_chars) of
584	    undefined ->
585		true;
586	    ECBool ->
587		application:set_env(common_test, esc_chars,
588				    ECBool),
589		ECBool
590	end,
591
592    Opts#opts{label = Label,
593	      profile = Profile,
594	      testspec_files = Specs,
595	      cover = Cover,
596	      cover_stop = CoverStop,
597	      logdir = which(logdir, LogDir),
598	      logopts = AllLogOpts,
599	      basic_html = BasicHtml,
600	      esc_chars = EscChars,
601	      verbosity = AllVerbosity,
602	      silent_connections = AllSilentConns,
603	      config = TSOpts#opts.config,
604	      event_handlers = AllEvHs,
605	      ct_hooks = AllCTHooks,
606	      enable_builtin_hooks = EnableBuiltinHooks,
607	      stylesheet = Stylesheet,
608	      auto_compile = AutoCompile,
609	      abort_if_missing_suites = AbortIfMissing,
610	      include = AllInclude,
611	      multiply_timetraps = MultTT,
612	      scale_timetraps = ScaleTT,
613	      create_priv_dir = CreatePrivDir}.
614
615check_and_install_configfiles(
616  Configs, LogDir, #opts{
617	     event_handlers = EvHandlers,
618	     ct_hooks = CTHooks,
619	     enable_builtin_hooks = EnableBuiltinHooks} ) ->
620    case ct_config:check_config_files(Configs) of
621	false ->
622	    install([{config,Configs},
623		     {event_handler,EvHandlers},
624		     {ct_hooks,CTHooks},
625		     {enable_builtin_hooks,EnableBuiltinHooks}], LogDir);
626	{value,{error,{nofile,File}}} ->
627	    {error,{cant_read_config_file,File}};
628	{value,{error,{wrong_config,Message}}}->
629	    {error,{wrong_config,Message}};
630	{value,{error,{callback,Info}}} ->
631	    {error,{cant_load_callback_module,Info}}
632    end.
633
634script_start3(Opts, Args) ->
635    Opts1 = get_start_opt(step,
636			  fun(Step) ->
637				  Opts#opts{step = Step,
638					    cover = undefined}
639			  end, Opts, Args),
640    case {proplists:get_value(dir, Args),
641	  proplists:get_value(suite, Args),
642	  groups_and_cases(proplists:get_value(group, Args),
643			   proplists:get_value(testcase, Args))} of
644	%% flag specified without data
645	{_,_,Error={error,_}} ->
646	    Error;
647	{_,[],_} ->
648	    {error,no_suite_specified};
649	{[],_,_} ->
650	    {error,no_dir_specified};
651
652	{Dirs,undefined,[]} when is_list(Dirs) ->
653	    script_start4(Opts#opts{tests = tests(Dirs)}, Args);
654
655	{undefined,Suites,[]} when is_list(Suites) ->
656	    Ts = tests([suite_to_test(S) || S <- Suites]),
657	    script_start4(Opts1#opts{tests = Ts}, Args);
658
659	{undefined,Suite,GsAndCs} when is_list(Suite) ->
660	    case [suite_to_test(S) || S <- Suite] of
661		DirMods = [_] ->
662		    Ts = tests(DirMods, GsAndCs),
663		    script_start4(Opts1#opts{tests = Ts}, Args);
664		[_,_|_] ->
665		    {error,multiple_suites_and_cases};
666		_ ->
667		    {error,incorrect_start_options}
668	    end;
669
670	{[_,_|_],Suite,[]} when is_list(Suite) ->
671	    {error,multiple_dirs_and_suites};
672
673	{[Dir],Suite,GsAndCs} when is_list(Dir), is_list(Suite) ->
674	    case [suite_to_test(Dir,S) || S <- Suite] of
675		DirMods when GsAndCs == [] ->
676		    Ts = tests(DirMods),
677		    script_start4(Opts1#opts{tests = Ts}, Args);
678		DirMods = [_] when GsAndCs /= [] ->
679		    Ts = tests(DirMods, GsAndCs),
680		    script_start4(Opts1#opts{tests = Ts}, Args);
681		[_,_|_] when GsAndCs /= [] ->
682		    {error,multiple_suites_and_cases};
683		_ ->
684		    {error,incorrect_start_options}
685	    end;
686
687	{undefined,undefined,GsAndCs} when GsAndCs /= [] ->
688	    {error,incorrect_start_options};
689
690	{undefined,undefined,_} ->
691	    if Opts#opts.shell ->
692		    script_start4(Opts#opts{tests = []}, Args);
693	       true ->
694		    %% no start options, use default "-dir ./"
695		    {ok,Dir} = file:get_cwd(),
696		    io:format("ct_run -dir ~ts~n~n", [Dir]),
697		    script_start4(Opts#opts{tests = tests([Dir])}, Args)
698	    end
699    end.
700
701script_start4(#opts{label = Label, profile = Profile,
702		    shell = true, config = Config,
703		    event_handlers = EvHandlers,
704		    ct_hooks = CTHooks,
705		    logopts = LogOpts,
706		    verbosity = Verbosity,
707		    enable_builtin_hooks = EnableBuiltinHooks,
708		    logdir = LogDir, testspec_files = Specs}, _Args) ->
709
710    %% label - used by ct_logs
711    application:set_env(common_test, test_label, Label),
712
713    %% profile - used in ct_util
714    application:set_env(common_test, profile, Profile),
715
716    if Config == [] ->
717	    ok;
718       true ->
719	    io:format("\nInstalling: ~tp\n\n", [Config])
720    end,
721    case install([{config,Config},{event_handler,EvHandlers},
722		  {ct_hooks, CTHooks},
723		  {enable_builtin_hooks,EnableBuiltinHooks}]) of
724	ok ->
725	    _ = ct_util:start(interactive, LogDir,
726			      add_verbosity_defaults(Verbosity)),
727	    ct_util:set_testdata({logopts, LogOpts}),
728	    log_ts_names(Specs),
729	    io:nl(),
730	    interactive_mode;
731	Error ->
732	    Error
733    end;
734script_start4(Opts = #opts{tests = Tests}, Args) ->
735    do_run(Tests, [], Opts, Args).
736
737script_usage() ->
738    io:format("\nUsage:\n\n"),
739    io:format("Run tests from command line:\n\n"
740	      "\tct_run -dir TestDir1 TestDir2 .. TestDirN |"
741	      "\n\t  [-dir TestDir] -suite Suite1 Suite2 .. SuiteN"
742	      "\n\t   [-group Group1 Group2 .. GroupN] [-case Case1 Case2 .. CaseN]"
743	      "\n\t [-step [config | keep_inactive]]"
744	      "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
745	      "\n\t [-userconfig CallbackModule ConfigFile1 .. ConfigFileN]"
746	      "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]"
747	      "\n\t [-logdir LogDir]"
748	      "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]"
749	      "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
750	      "\n\t [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]"
751	      "\n\t [-stylesheet CSSFile]"
752	      "\n\t [-cover CoverCfgFile]"
753	      "\n\t [-cover_stop Bool]"
754	      "\n\t [-event_handler EvHandler1 EvHandler2 .. EvHandlerN]"
755	      "\n\t [-ct_hooks CTHook1 CTHook2 .. CTHookN]"
756	      "\n\t [-include InclDir1 InclDir2 .. InclDirN]"
757	      "\n\t [-no_auto_compile]"
758	      "\n\t [-abort_if_missing_suites]"
759	      "\n\t [-multiply_timetraps N]"
760	      "\n\t [-scale_timetraps]"
761	      "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
762	      "\n\t [-basic_html]"
763	      "\n\t [-no_esc_chars]"
764	      "\n\t [-repeat N] |"
765	      "\n\t [-duration HHMMSS [-force_stop [skip_rest]]] |"
766	      "\n\t [-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]"
767	      "\n\t [-exit_status ignore_config]"
768	      "\n\t [-help]\n\n"),
769    io:format("Run tests using test specification:\n\n"
770	      "\tct_run -spec TestSpec1 TestSpec2 .. TestSpecN"
771	      "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
772	      "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]"
773	      "\n\t [-logdir LogDir]"
774	      "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]"
775	      "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
776	      "\n\t [-allow_user_terms]"
777	      "\n\t [-join_specs]"
778	      "\n\t [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]"
779	      "\n\t [-stylesheet CSSFile]"
780	      "\n\t [-cover CoverCfgFile]"
781	      "\n\t [-cover_stop Bool]"
782	      "\n\t [-event_handler EvHandler1 EvHandler2 .. EvHandlerN]"
783	      "\n\t [-ct_hooks CTHook1 CTHook2 .. CTHookN]"
784	      "\n\t [-include InclDir1 InclDir2 .. InclDirN]"
785	      "\n\t [-no_auto_compile]"
786	      "\n\t [-abort_if_missing_suites]"
787	      "\n\t [-multiply_timetraps N]"
788	      "\n\t [-scale_timetraps]"
789	      "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
790	      "\n\t [-basic_html]"
791	      "\n\t [-no_esc_chars]"
792	      "\n\t [-repeat N] |"
793	      "\n\t [-duration HHMMSS [-force_stop [skip_rest]]] |"
794	      "\n\t [-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]\n\n"),
795    io:format("Refresh the HTML index files:\n\n"
796	      "\tct_run -refresh_logs [LogDir]"
797	      " [-logdir LogDir] "
798	      " [-basic_html]\n\n"),
799    io:format("Run CT in interactive mode:\n\n"
800	      "\tct_run -shell"
801	      "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
802	      "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"),
803    io:format("Run tests in web based GUI:\n\n"
804	      "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
805	      "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]"
806	      "\n\t [-dir TestDir1 TestDir2 .. TestDirN] |"
807	      "\n\t [-suite Suite [-case Case]]"
808	      "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]"
809	      "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
810	      "\n\t [-include InclDir1 InclDir2 .. InclDirN]"
811	      "\n\t [-no_auto_compile]"
812	      "\n\t [-abort_if_missing_suites]"
813	      "\n\t [-multiply_timetraps N]"
814	      "\n\t [-scale_timetraps]"
815	      "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
816	      "\n\t [-basic_html]"
817	      "\n\t [-no_esc_chars]\n\n").
818
819install(Opts) ->
820    install(Opts, ".").
821
822install(Opts, LogDir) ->
823
824    ConfOpts = ct_config:add_default_callback(Opts),
825
826    case application:get_env(common_test, decrypt) of
827	{ok,_} ->
828	    ok;
829	_ ->
830	    case lists:keysearch(decrypt, 1, Opts) of
831		{value,{_,KeyOrFile}} ->
832		    application:set_env(common_test, decrypt, KeyOrFile);
833		false ->
834		    application:unset_env(common_test, decrypt)
835	    end
836    end,
837    case whereis(ct_util_server) of
838	undefined ->
839	    VarFile = variables_file_name(LogDir),
840	    case file:open(VarFile, [write, {encoding,utf8}]) of
841		{ok,Fd} ->
842		    _ = [io:format(Fd, "~tp.\n", [Opt]) || Opt <- ConfOpts],
843		    ok = file:close(Fd);
844		{error,Reason} ->
845		    io:format("CT failed to install configuration data. Please "
846			      "verify that the log directory exists and that "
847			      "write permission is set.\n\n", []),
848		    {error,{VarFile,Reason}}
849	    end;
850	_ ->
851	    io:format("It is not possible to install CT while running "
852		      "in interactive mode.\n"
853		      "To exit this mode, run ct:stop_interactive().\n"
854		      "To enter the interactive mode again, "
855		      "run ct:start_interactive()\n\n", []),
856	    {error,interactive_mode}
857    end.
858
859variables_file_name(Dir) ->
860    filename:join(Dir, "variables-"++atom_to_list(node())).
861
862run_test(StartOpt) when is_tuple(StartOpt) ->
863    run_test([StartOpt]);
864
865run_test(StartOpts) when is_list(StartOpts) ->
866    CTPid = spawn(run_test1_fun(StartOpts)),
867    Ref = monitor(process, CTPid),
868    receive
869	{'DOWN',Ref,process,CTPid,{user_error,Error}} ->
870		    {error,Error};
871	{'DOWN',Ref,process,CTPid,Other} ->
872		    Other
873    end.
874
875-spec run_test1_fun(_) -> fun(() -> no_return()).
876
877run_test1_fun(StartOpts) ->
878    fun() ->
879            ct_util:mark_process(),
880            run_test1(StartOpts)
881    end.
882
883run_test1(StartOpts) when is_list(StartOpts) ->
884    case proplists:get_value(refresh_logs, StartOpts) of
885	undefined ->
886	    Tracing = start_trace(StartOpts),
887	    {ok,Cwd} = file:get_cwd(),
888	    io:format("~nCommon Test starting (cwd is ~ts)~n~n", [Cwd]),
889	    Res =
890		case ct_repeat:loop_test(func, StartOpts) of
891		    false ->
892			case catch run_test2(StartOpts) of
893			    {'EXIT',Reason} ->
894				ok = file:set_cwd(Cwd),
895				{error,Reason};
896			    Result ->
897				Result
898			end;
899		    Result ->
900			Result
901		end,
902	    stop_trace(Tracing),
903	    exit(Res);
904	RefreshDir ->
905            %% log_cleanup - used by ct_logs
906            KeepLogs = get_start_opt(keep_logs,
907                                     fun ct_logs:parse_keep_logs/1,
908                                     all,
909                                     StartOpts),
910            application:set_env(common_test, keep_logs, KeepLogs),
911	    ok = refresh_logs(?abs(RefreshDir)),
912	    exit(done)
913    end.
914
915run_test2(StartOpts) ->
916    %% label
917    Label = get_start_opt(label, fun(Lbl) when is_list(Lbl) -> Lbl;
918				    (Lbl) when is_atom(Lbl) -> atom_to_list(Lbl)
919				 end, StartOpts),
920    %% profile
921    Profile = get_start_opt(profile, fun(Prof) when is_list(Prof) ->
922					     Prof;
923					(Prof) when is_atom(Prof) ->
924					     atom_to_list(Prof)
925				     end, StartOpts),
926    %% logdir
927    LogDir = get_start_opt(logdir, fun(LD) when is_list(LD) -> LD end,
928			   StartOpts),
929    %% logopts
930    LogOpts = get_start_opt(logopts, value, [], StartOpts),
931
932    %% verbosity
933    Verbosity =
934	get_start_opt(verbosity,
935		      fun(VLvls) when is_list(VLvls) ->
936			      lists:map(fun(VLvl = {_Cat,_Lvl}) ->
937						VLvl;
938					   (Lvl) ->
939						{'$unspecified',Lvl}
940					end, VLvls);
941			 (VLvl) when is_integer(VLvl) ->
942			      [{'$unspecified',VLvl}]
943		      end, [], StartOpts),
944
945    %% config & userconfig
946    CfgFiles = ct_config:get_config_file_list(StartOpts),
947
948    %% event handlers
949    EvHandlers =
950	case proplists:get_value(event_handler, StartOpts) of
951	    undefined ->
952		[];
953	    H when is_atom(H) ->
954		[{H,[]}];
955	    H ->
956		Hs =
957		    if is_tuple(H) -> [H];
958		       is_list(H) -> H;
959		       true -> []
960		    end,
961		lists:flatten(
962		  lists:map(fun(EH) when is_atom(EH) ->
963				    {EH,[]};
964			       ({HL,Args}) when is_list(HL) ->
965				    [{EH,Args} || EH <- HL];
966			       ({EH,Args}) when is_atom(EH) ->
967				    {EH,Args};
968			       (_) ->
969				    []
970			    end, Hs))
971	end,
972
973    %% CT Hooks
974    CTHooks = get_start_opt(ct_hooks, value, [], StartOpts),
975    EnableBuiltinHooks = get_start_opt(enable_builtin_hooks,
976				       fun(EBH) when EBH == true;
977						     EBH == false ->
978					       EBH
979				       end, undefined, StartOpts),
980
981    %% silent connections
982    SilentConns = get_start_opt(silent_connections,
983				fun(all) -> [all];
984				   (Conns) -> Conns
985				end, [], StartOpts),
986    %% stylesheet
987    Stylesheet = get_start_opt(stylesheet,
988			       fun(SS) -> ?abs(SS) end,
989			       StartOpts),
990    %% code coverage
991    Cover = get_start_opt(cover,
992			  fun(CoverFile) -> ?abs(CoverFile) end, StartOpts),
993    CoverStop = get_start_opt(cover_stop, value, StartOpts),
994
995    %% timetrap manipulation
996    MultiplyTT = get_start_opt(multiply_timetraps, value, StartOpts),
997    ScaleTT = get_start_opt(scale_timetraps, value, StartOpts),
998
999    %% create unique priv dir names
1000    CreatePrivDir = get_start_opt(create_priv_dir, value, StartOpts),
1001
1002    %% auto compile & include files
1003    {AutoCompile,Include} =
1004	case proplists:get_value(auto_compile, StartOpts) of
1005	    undefined ->
1006		application:set_env(common_test, auto_compile, true),
1007		InclDirs =
1008		    case proplists:get_value(include, StartOpts) of
1009			undefined ->
1010			    [];
1011			Incls when is_list(hd(Incls)) ->
1012			    [filename:absname(IDir) || IDir <- Incls];
1013			Incl when is_list(Incl) ->
1014			    [filename:absname(Incl)]
1015		    end,
1016		case os:getenv("CT_INCLUDE_PATH") of
1017		    false ->
1018			application:set_env(common_test, include, InclDirs),
1019			{undefined,InclDirs};
1020		    CtInclPath ->
1021			InclDirs1 = string:lexemes(CtInclPath, [$:,$ ,$,]),
1022			AllInclDirs = InclDirs1++InclDirs,
1023			application:set_env(common_test, include, AllInclDirs),
1024			{undefined,AllInclDirs}
1025		end;
1026	    ACBool ->
1027		application:set_env(common_test, auto_compile, ACBool),
1028		{ACBool,[]}
1029	end,
1030
1031    %% abort test run if some suites can't be compiled
1032    AbortIfMissing = get_start_opt(abort_if_missing_suites, value, false,
1033				   StartOpts),
1034
1035    %% decrypt config file
1036    case proplists:get_value(decrypt, StartOpts) of
1037	undefined ->
1038	    application:unset_env(common_test, decrypt);
1039	Key={key,_} ->
1040	    application:set_env(common_test, decrypt, Key);
1041	{file,KeyFile} ->
1042	    application:set_env(common_test, decrypt, {file,?abs(KeyFile)})
1043    end,
1044
1045    %% basic html - used by ct_logs
1046    BasicHtml =
1047	case proplists:get_value(basic_html, StartOpts) of
1048	    undefined ->
1049		application:set_env(common_test, basic_html, false),
1050		undefined;
1051	    BasicHtmlBool ->
1052		application:set_env(common_test, basic_html, BasicHtmlBool),
1053		BasicHtmlBool
1054    end,
1055    %% esc_chars - used by ct_logs
1056    EscChars =
1057	case proplists:get_value(esc_chars, StartOpts) of
1058	    undefined ->
1059		application:set_env(common_test, esc_chars, true),
1060		undefined;
1061	    EscCharsBool ->
1062		application:set_env(common_test, esc_chars, EscCharsBool),
1063		EscCharsBool
1064    end,
1065    %% disable_log_cache - used by ct_logs
1066    case proplists:get_value(disable_log_cache, StartOpts) of
1067	undefined ->
1068	    application:set_env(common_test, disable_log_cache, false);
1069	DisableCacheBool ->
1070	    application:set_env(common_test, disable_log_cache, DisableCacheBool)
1071    end,
1072    %% log_cleanup - used by ct_logs
1073    KeepLogs = get_start_opt(keep_logs,
1074                             fun ct_logs:parse_keep_logs/1,
1075                             all,
1076                             StartOpts),
1077    application:set_env(common_test, keep_logs, KeepLogs),
1078
1079    %% stepped execution
1080    Step = get_start_opt(step, value, StartOpts),
1081
1082    Opts = #opts{label = Label, profile = Profile,
1083		 cover = Cover, cover_stop = CoverStop,
1084		 step = Step, logdir = LogDir,
1085		 logopts = LogOpts, basic_html = BasicHtml,
1086		 esc_chars = EscChars,
1087		 config = CfgFiles,
1088		 verbosity = Verbosity,
1089		 event_handlers = EvHandlers,
1090		 ct_hooks = CTHooks,
1091		 enable_builtin_hooks = EnableBuiltinHooks,
1092		 auto_compile = AutoCompile,
1093		 abort_if_missing_suites = AbortIfMissing,
1094		 include = Include,
1095		 silent_connections = SilentConns,
1096		 stylesheet = Stylesheet,
1097		 multiply_timetraps = MultiplyTT,
1098		 scale_timetraps = ScaleTT,
1099		 create_priv_dir = CreatePrivDir,
1100		 starter = ct},
1101
1102    %% test specification
1103    case proplists:get_value(spec, StartOpts) of
1104	undefined ->
1105	    case lists:keysearch(prepared_tests, 1, StartOpts) of
1106		{value,{_,{Run,Skip},Specs}} ->	% use prepared tests
1107		    run_prepared(Run, Skip, Opts#opts{testspec_files = Specs},
1108				 StartOpts);
1109		false ->
1110		    run_dir(Opts, StartOpts)
1111	    end;
1112	Specs ->
1113	    Relaxed = get_start_opt(allow_user_terms, value, false, StartOpts),
1114	    %% using testspec(s) as input for test
1115	    run_spec_file(Relaxed, Opts#opts{testspec_files = Specs}, StartOpts)
1116    end.
1117
1118run_spec_file(Relaxed,
1119	      Opts = #opts{testspec_files = Specs},
1120	      StartOpts) ->
1121    Specs1 = case Specs of
1122		 [X|_] when is_integer(X) -> [Specs];
1123		 _ -> Specs
1124	     end,
1125    AbsSpecs = lists:map(fun(SF) -> ?abs(SF) end, Specs1),
1126    AbsSpecs1 = get_start_opt(join_specs, [AbsSpecs], AbsSpecs, StartOpts),
1127    try ct_testspec:collect_tests_from_file(AbsSpecs1, Relaxed) of
1128	TestSpecData ->
1129	    run_all_specs(TestSpecData, Opts, StartOpts, [])
1130    catch
1131	throw:{error,CTReason}:StackTrace ->
1132	    exit({error,{invalid_testspec,{CTReason,StackTrace}}});
1133	_:CTReason:StackTrace ->
1134	    exit({error,{invalid_testspec,{CTReason,StackTrace}}})
1135    end.
1136
1137run_all_specs([], _, _, TotResult) ->
1138    TotResult1 = lists:reverse(TotResult),
1139    case lists:keysearch('EXIT', 1, TotResult1) of
1140	{value,{_,_,ExitReason}} ->
1141	    exit(ExitReason);
1142	false ->
1143	    case lists:keysearch(error, 1, TotResult1) of
1144		{value,Error} ->
1145		    Error;
1146		false ->
1147		    lists:foldl(fun({Ok,Fail,{UserSkip,AutoSkip}},
1148				    {Ok1,Fail1,{UserSkip1,AutoSkip1}}) ->
1149					{Ok1+Ok,Fail1+Fail,
1150					 {UserSkip1+UserSkip,
1151					  AutoSkip1+AutoSkip}};
1152				(Pid, Acc) when is_pid(Pid) -> Acc
1153				end, {0,0,{0,0}}, TotResult1)
1154	    end
1155    end;
1156
1157run_all_specs([{Specs,TS} | TSs], Opts, StartOpts, TotResult) ->
1158    Combined = #opts{config = TSConfig} = combine_test_opts(TS, Specs, Opts),
1159    AllConfig = merge_vals([Opts#opts.config, TSConfig]),
1160    try run_one_spec(TS,
1161		     Combined#opts{config = AllConfig,
1162				   current_testspec=TS},
1163		     StartOpts) of
1164	Result ->
1165	    run_all_specs(TSs, Opts, StartOpts, [Result | TotResult])
1166    catch
1167	_ : Reason ->
1168	    run_all_specs(TSs, Opts, StartOpts, [{error,Reason} | TotResult])
1169    end.
1170
1171run_one_spec(TS, CombinedOpts, StartOpts) ->
1172    #opts{logdir = Logdir, config = Config} = CombinedOpts,
1173    case check_and_install_configfiles(Config, Logdir, CombinedOpts) of
1174	ok ->
1175	    {Run,Skip} = ct_testspec:prepare_tests(TS, node()),
1176	    reformat_result(catch do_run(Run, Skip, CombinedOpts, StartOpts));
1177	Error ->
1178	    Error
1179    end.
1180
1181run_prepared(Run, Skip, Opts = #opts{logdir = LogDir,
1182				     config = CfgFiles},
1183	     StartOpts) ->
1184    LogDir1 = which(logdir, LogDir),
1185    case check_and_install_configfiles(CfgFiles, LogDir1, Opts) of
1186	ok ->
1187	    reformat_result(catch do_run(Run, Skip, Opts#opts{logdir = LogDir1},
1188					 StartOpts));
1189	{error,_Reason} = Error ->
1190	    exit(Error)
1191    end.
1192
1193check_config_file(Callback, File)->
1194    case code:is_loaded(Callback) of
1195	false ->
1196	    case code:load_file(Callback) of
1197		{module,_} -> ok;
1198		{error,Why} -> exit({error,{cant_load_callback_module,Why}})
1199	    end;
1200	_ ->
1201	    ok
1202    end,
1203    case Callback:check_parameter(File) of
1204	{ok,{file,File}}->
1205	    ?abs(File);
1206	{ok,{config,_}}->
1207	    File;
1208	{error,{wrong_config,Message}}->
1209	    exit({error,{wrong_config,{Callback,Message}}});
1210	{error,{nofile,File}}->
1211	    exit({error,{no_such_file,?abs(File)}})
1212    end.
1213
1214run_dir(Opts = #opts{logdir = LogDir,
1215		     config = CfgFiles,
1216		     event_handlers = EvHandlers,
1217		     ct_hooks = CTHook,
1218		     enable_builtin_hooks = EnableBuiltinHooks},
1219	StartOpts) ->
1220    LogDir1 = which(logdir, LogDir),
1221    Opts1 = Opts#opts{logdir = LogDir1},
1222    AbsCfgFiles =
1223	lists:map(fun({Callback,FileList})->
1224			  case code:is_loaded(Callback) of
1225			      {file,_Path}->
1226				  ok;
1227			      false ->
1228				  case code:load_file(Callback) of
1229				      {module,Callback}->
1230					  ok;
1231				      {error,_}->
1232					  exit({error,{no_such_module,
1233						       Callback}})
1234				  end
1235			  end,
1236			  {Callback,
1237			   lists:map(fun(File)->
1238					     check_config_file(Callback, File)
1239				     end, FileList)}
1240		  end, CfgFiles),
1241    case install([{config,AbsCfgFiles},
1242		  {event_handler,EvHandlers},
1243		  {ct_hooks, CTHook},
1244		  {enable_builtin_hooks,EnableBuiltinHooks}], LogDir1) of
1245	ok -> ok;
1246	{error,_IReason} = IError -> exit(IError)
1247    end,
1248    case {proplists:get_value(dir, StartOpts),
1249	  proplists:get_value(suite, StartOpts),
1250	  groups_and_cases(proplists:get_value(group, StartOpts),
1251			   proplists:get_value(testcase, StartOpts))} of
1252	%% flag specified without data
1253	{_,_,Error={error,_}} ->
1254	    Error;
1255	{_,[],_} ->
1256	    {error,no_suite_specified};
1257	{[],_,_} ->
1258	    {error,no_dir_specified};
1259
1260	{Dirs=[Hd|_],undefined,[]} when is_list(Dirs), not is_integer(Hd) ->
1261	    Dirs1 = [if is_atom(D) -> atom_to_list(D);
1262			true -> D end || D <- Dirs],
1263	    reformat_result(catch do_run(tests(Dirs1), [], Opts1, StartOpts));
1264
1265	{Dir=[Hd|_],undefined,[]} when is_list(Dir) and is_integer(Hd) ->
1266	    reformat_result(catch do_run(tests(Dir), [], Opts1, StartOpts));
1267
1268	{Dir,undefined,[]} when is_atom(Dir) and (Dir /= undefined) ->
1269	    reformat_result(catch do_run(tests(atom_to_list(Dir)),
1270					 [], Opts1, StartOpts));
1271
1272	{undefined,Suites=[Hd|_],[]} when not is_integer(Hd) ->
1273	    Suites1 = [suite_to_test(S) || S <- Suites],
1274	    reformat_result(catch do_run(tests(Suites1), [], Opts1, StartOpts));
1275
1276	{undefined,Suite,[]} when is_atom(Suite) and
1277				  (Suite /= undefined) ->
1278	    {Dir,Mod} = suite_to_test(Suite),
1279	    reformat_result(catch do_run(tests(Dir, Mod), [], Opts1, StartOpts));
1280
1281	{undefined,Suite,GsAndCs} when is_atom(Suite) and
1282				       (Suite /= undefined) ->
1283	    {Dir,Mod} = suite_to_test(Suite),
1284	    reformat_result(catch do_run(tests(Dir, Mod, GsAndCs),
1285					 [], Opts1, StartOpts));
1286
1287	{undefined,[Hd,_|_],_GsAndCs} when not is_integer(Hd) ->
1288	    exit({error,multiple_suites_and_cases});
1289
1290	{undefined,Suite=[Hd|Tl],GsAndCs} when is_integer(Hd) ;
1291					       (is_list(Hd) and	(Tl == [])) ;
1292					       (is_atom(Hd) and	(Tl == [])) ->
1293	    {Dir,Mod} = suite_to_test(Suite),
1294	    reformat_result(catch do_run(tests(Dir, Mod, GsAndCs),
1295					 [], Opts1, StartOpts));
1296
1297	{[Hd,_|_],_Suites,[]} when is_list(Hd) ; not is_integer(Hd) ->
1298	    exit({error,multiple_dirs_and_suites});
1299
1300	{undefined,undefined,GsAndCs} when GsAndCs /= [] ->
1301	    exit({error,incorrect_start_options});
1302
1303	{Dir,Suite,GsAndCs} when is_integer(hd(Dir)) ;
1304				 (is_atom(Dir) and (Dir /= undefined)) ;
1305				 ((length(Dir) == 1) and is_atom(hd(Dir))) ;
1306				 ((length(Dir) == 1) and is_list(hd(Dir))) ->
1307	    Dir1 = if is_atom(Dir) -> atom_to_list(Dir);
1308		      true -> Dir end,
1309	    if Suite == undefined ->
1310		  exit({error,incorrect_start_options});
1311
1312	       is_integer(hd(Suite)) ;
1313	       (is_atom(Suite) and (Suite /= undefined)) ;
1314	       ((length(Suite) == 1) and is_atom(hd(Suite))) ;
1315	       ((length(Suite) == 1) and is_list(hd(Suite))) ->
1316		    {Dir2,Mod} = suite_to_test(Dir1, Suite),
1317		    case GsAndCs of
1318			[] ->
1319			    reformat_result(catch do_run(tests(Dir2, Mod),
1320							 [], Opts1, StartOpts));
1321			_ ->
1322			    reformat_result(catch do_run(tests(Dir2, Mod,
1323							       GsAndCs),
1324							 [], Opts1, StartOpts))
1325		    end;
1326
1327		is_list(Suite) ->		% multiple suites
1328		    case [suite_to_test(Dir1, S) || S <- Suite] of
1329			[_,_|_] when GsAndCs /= [] ->
1330			    exit({error,multiple_suites_and_cases});
1331			[{Dir2,Mod}] when GsAndCs /= [] ->
1332			    reformat_result(catch do_run(tests(Dir2, Mod,
1333							       GsAndCs),
1334							 [], Opts1, StartOpts));
1335			DirMods ->
1336			    reformat_result(catch do_run(tests(DirMods),
1337							 [], Opts1, StartOpts))
1338		    end
1339	    end;
1340
1341	{undefined,undefined,[]} ->
1342	    {ok,Dir} = file:get_cwd(),
1343	    %% No start options, use default {dir,CWD}
1344	    reformat_result(catch do_run(tests(Dir), [], Opts1, StartOpts));
1345
1346	{Dir,Suite,GsAndCs} ->
1347	    exit({error,{incorrect_start_options,{Dir,Suite,GsAndCs}}})
1348    end.
1349
1350run_testspec(TestSpec) ->
1351    CTPid = spawn(run_testspec1_fun(TestSpec)),
1352    Ref = monitor(process, CTPid),
1353    receive
1354	{'DOWN',Ref,process,CTPid,{user_error,Error}} ->
1355		    Error;
1356	{'DOWN',Ref,process,CTPid,Other} ->
1357		    Other
1358    end.
1359
1360-spec run_testspec1_fun(_) -> fun(() -> no_return()).
1361
1362run_testspec1_fun(TestSpec) ->
1363    fun() ->
1364            ct_util:mark_process(),
1365            run_testspec1(TestSpec)
1366    end.
1367
1368run_testspec1(TestSpec) ->
1369    {ok,Cwd} = file:get_cwd(),
1370    io:format("~nCommon Test starting (cwd is ~ts)~n~n", [Cwd]),
1371    case catch run_testspec2(TestSpec) of
1372	{'EXIT',Reason} ->
1373	    ok = file:set_cwd(Cwd),
1374	    exit({error,Reason});
1375	Result ->
1376	    exit(Result)
1377    end.
1378
1379run_testspec2(File) when is_list(File), is_integer(hd(File)) ->
1380    case file:read_file_info(File) of
1381	{ok,_} ->
1382	    exit("Bad argument, "
1383		 "use ct:run_test([{spec," ++ File ++ "}])");
1384	_ ->
1385	    exit("Bad argument, list of tuples expected, "
1386		 "use ct:run_test/1 for test specification files")
1387    end;
1388
1389run_testspec2(TestSpec) ->
1390    case catch ct_testspec:collect_tests_from_list(TestSpec, false) of
1391	{E,CTReason}  when E == error ; E == 'EXIT' ->
1392	    exit({error,CTReason});
1393	TS ->
1394	    Opts = get_data_for_node(TS, node()),
1395
1396	    AllInclude =
1397		case os:getenv("CT_INCLUDE_PATH") of
1398		    false ->
1399			Opts#opts.include;
1400		    CtInclPath ->
1401			EnvInclude = string:lexemes(CtInclPath, [$:,$ ,$,]),
1402			EnvInclude++Opts#opts.include
1403		end,
1404	    application:set_env(common_test, include, AllInclude),
1405
1406	    LogDir1 = which(logdir,Opts#opts.logdir),
1407	    case check_and_install_configfiles(
1408		   Opts#opts.config, LogDir1, Opts) of
1409		ok ->
1410		    Opts1 = Opts#opts{testspec_files = [],
1411				      logdir = LogDir1,
1412				      include = AllInclude},
1413		    {Run,Skip} = ct_testspec:prepare_tests(TS, node()),
1414		    reformat_result(catch do_run(Run, Skip, Opts1, []));
1415		{error,_GCFReason} = GCFError ->
1416		    exit(GCFError)
1417	    end
1418    end.
1419
1420get_data_for_node(#testspec{label = Labels,
1421			    profile = Profiles,
1422			    logdir = LogDirs,
1423			    logopts = LogOptsList,
1424			    basic_html = BHs,
1425			    esc_chars = EscChs,
1426			    stylesheet = SSs,
1427			    verbosity = VLvls,
1428			    silent_connections = SilentConnsList,
1429			    cover = CoverFs,
1430			    cover_stop = CoverStops,
1431			    config = Cfgs,
1432			    userconfig = UsrCfgs,
1433			    event_handler = EvHs,
1434			    ct_hooks = CTHooks,
1435			    enable_builtin_hooks = EnableBuiltinHooks,
1436			    auto_compile = ACs,
1437			    abort_if_missing_suites = AiMSs,
1438			    include = Incl,
1439			    multiply_timetraps = MTs,
1440			    scale_timetraps = STs,
1441			    create_priv_dir = PDs}, Node) ->
1442    Label = proplists:get_value(Node, Labels),
1443    Profile = proplists:get_value(Node, Profiles),
1444    LogDir = case proplists:get_value(Node, LogDirs) of
1445		 undefined -> ".";
1446		 Dir -> Dir
1447	     end,
1448    LogOpts = case proplists:get_value(Node, LogOptsList) of
1449		  undefined -> [];
1450		  LOs -> LOs
1451	      end,
1452    BasicHtml = proplists:get_value(Node, BHs),
1453    EscChars = proplists:get_value(Node, EscChs),
1454    Stylesheet = proplists:get_value(Node, SSs),
1455    Verbosity = case proplists:get_value(Node, VLvls) of
1456		    undefined -> [];
1457		    Lvls -> Lvls
1458		end,
1459    SilentConns = case proplists:get_value(Node, SilentConnsList) of
1460		      undefined -> [];
1461		      SCs -> SCs
1462		  end,
1463    Cover = proplists:get_value(Node, CoverFs),
1464    CoverStop = proplists:get_value(Node, CoverStops),
1465    MT = proplists:get_value(Node, MTs),
1466    ST = proplists:get_value(Node, STs),
1467    CreatePrivDir = proplists:get_value(Node, PDs),
1468    ConfigFiles = [{?ct_config_txt,F} || {N,F} <- Cfgs, N==Node] ++
1469	[CBF || {N,CBF} <- UsrCfgs, N==Node],
1470    EvHandlers =  [{H,A} || {N,H,A} <- EvHs, N==Node],
1471    FiltCTHooks = [Hook || {N,Hook} <- CTHooks, N==Node],
1472    AutoCompile = proplists:get_value(Node, ACs),
1473    AbortIfMissing = proplists:get_value(Node, AiMSs),
1474    Include =  [I || {N,I} <- Incl, N==Node],
1475    #opts{label = Label,
1476	  profile = Profile,
1477	  logdir = LogDir,
1478	  logopts = LogOpts,
1479	  basic_html = BasicHtml,
1480	  esc_chars = EscChars,
1481	  stylesheet = Stylesheet,
1482	  verbosity = Verbosity,
1483	  silent_connections = SilentConns,
1484	  cover = Cover,
1485	  cover_stop = CoverStop,
1486	  config = ConfigFiles,
1487	  event_handlers = EvHandlers,
1488	  ct_hooks = FiltCTHooks,
1489	  enable_builtin_hooks = EnableBuiltinHooks,
1490	  auto_compile = AutoCompile,
1491	  abort_if_missing_suites = AbortIfMissing,
1492	  include = Include,
1493	  multiply_timetraps = MT,
1494	  scale_timetraps = ST,
1495	  create_priv_dir = CreatePrivDir}.
1496
1497refresh_logs(LogDir) ->
1498    {ok,Cwd} = file:get_cwd(),
1499    case file:set_cwd(LogDir) of
1500	E = {error,_Reason} ->
1501	    E;
1502	_ ->
1503	    case catch ct_logs:make_all_suites_index(refresh) of
1504		{'EXIT',ASReason} ->
1505		    ok = file:set_cwd(Cwd),
1506		    {error,{all_suites_index,ASReason}};
1507		_ ->
1508		    case catch ct_logs:make_all_runs_index(refresh) of
1509			{'EXIT',ARReason} ->
1510			    ok = file:set_cwd(Cwd),
1511			    {error,{all_runs_index,ARReason}};
1512			_ ->
1513			    ok = file:set_cwd(Cwd),
1514			    io:format("Logs in ~ts refreshed!~n",[LogDir]),
1515			    ok
1516		    end
1517	    end
1518    end.
1519
1520which(logdir, undefined) ->
1521    ".";
1522which(logdir, Dir) ->
1523    Dir.
1524
1525choose_val(undefined, V1) ->
1526    V1;
1527choose_val(V0, _V1) ->
1528    V0.
1529
1530merge_vals(Vs) ->
1531    lists:append(Vs).
1532
1533merge_keyvals(Vs) ->
1534    make_unique(lists:append(Vs)).
1535
1536make_unique([Elem={Key,_} | Elems]) ->
1537    [Elem | make_unique(proplists:delete(Key, Elems))];
1538make_unique([]) ->
1539    [].
1540
1541listify([C|_]=Str) when is_integer(C) -> [Str];
1542listify(L) when is_list(L) -> L;
1543listify(E) -> [E].
1544
1545delistify([E]) -> E;
1546delistify(E)   -> E.
1547
1548
1549run(TestDir, Suite, Cases) ->
1550    case install([]) of
1551	ok ->
1552	    reformat_result(catch do_run(tests(TestDir, Suite, Cases), []));
1553	Error ->
1554	    Error
1555    end.
1556
1557run(TestDir, Suite) when is_list(TestDir), is_integer(hd(TestDir)) ->
1558    case install([]) of
1559	ok ->
1560	    reformat_result(catch do_run(tests(TestDir, Suite), []));
1561	Error ->
1562	    Error
1563    end.
1564
1565run(TestDirs) ->
1566    case install([]) of
1567	ok ->
1568	    reformat_result(catch do_run(tests(TestDirs), []));
1569	Error ->
1570	    Error
1571    end.
1572
1573reformat_result({'EXIT',{user_error,Reason}}) ->
1574    {error,Reason};
1575reformat_result({user_error,Reason}) ->
1576    {error,Reason};
1577reformat_result(Result) ->
1578    Result.
1579
1580suite_to_test(Suite) when is_atom(Suite) ->
1581    suite_to_test(atom_to_list(Suite));
1582
1583suite_to_test(Suite) when is_list(Suite) ->
1584    {filename:dirname(Suite),
1585     list_to_atom(filename:rootname(filename:basename(Suite)))}.
1586
1587suite_to_test(Dir, Suite) when is_atom(Suite) ->
1588    suite_to_test(Dir, atom_to_list(Suite));
1589
1590suite_to_test(Dir, Suite) when is_list(Suite) ->
1591    case filename:dirname(Suite) of
1592	"." ->
1593	    {Dir,list_to_atom(filename:rootname(Suite))};
1594	DirName ->				% ignore Dir
1595	    File = filename:basename(Suite),
1596	    {DirName,list_to_atom(filename:rootname(File))}
1597    end.
1598
1599groups_and_cases(Gs, Cs) when ((Gs == undefined) or (Gs == [])) and
1600			      ((Cs == undefined) or (Cs == [])) ->
1601    [];
1602groups_and_cases(Gs, Cs) when Gs == undefined ; Gs == [] ->
1603    if (Cs == all) or (Cs == [all]) or (Cs == ["all"]) -> all;
1604       true -> [ensure_atom(C) || C <- listify(Cs)]
1605    end;
1606groups_and_cases(GOrGs, Cs) when (is_atom(GOrGs) orelse
1607				  (is_list(GOrGs) andalso
1608				   (is_atom(hd(GOrGs)) orelse
1609				    (is_list(hd(GOrGs)) andalso
1610				     is_atom(hd(hd(GOrGs))))))) ->
1611    if (Cs == undefined) or (Cs == []) or
1612       (Cs == all) or (Cs == [all]) or (Cs == ["all"]) ->
1613	    [{GOrGs,all}];
1614       true ->
1615	    [{GOrGs,[ensure_atom(C) || C <- listify(Cs)]}]
1616    end;
1617groups_and_cases(Gs, Cs) when is_integer(hd(hd(Gs))) ->
1618    %% if list of strings, this comes from 'ct_run -group G1 G2 ...' and
1619    %% we need to parse the strings
1620    Gs1 =
1621	if (Gs == [all]) or (Gs == ["all"]) ->
1622		all;
1623	   true ->
1624		lists:map(fun(G) ->
1625				  {ok,Ts,_} = erl_scan:string(G++"."),
1626				  {ok,Term} = erl_parse:parse_term(Ts),
1627				  Term
1628			  end, Gs)
1629	end,
1630    groups_and_cases(Gs1, Cs);
1631groups_and_cases(Gs, Cs) ->
1632    {error,{incorrect_group_or_case_option,Gs,Cs}}.
1633
1634tests(TestDir, Suites, []) when is_list(TestDir), is_integer(hd(TestDir)) ->
1635    [{?testdir(TestDir,Suites),ensure_atom(Suites),all}];
1636tests(TestDir, Suite, Cases) when is_list(TestDir), is_integer(hd(TestDir)) ->
1637    [{?testdir(TestDir,Suite),ensure_atom(Suite),Cases}];
1638tests([TestDir], Suite, Cases) when is_list(TestDir), is_integer(hd(TestDir)) ->
1639    [{?testdir(TestDir,Suite),ensure_atom(Suite),Cases}].
1640tests([{Dir,Suite}],Cases) ->
1641    [{?testdir(Dir,Suite),ensure_atom(Suite),Cases}];
1642tests(TestDir, Suite) when is_list(TestDir), is_integer(hd(TestDir)) ->
1643    tests(TestDir, ensure_atom(Suite), all);
1644tests([TestDir], Suite) when is_list(TestDir), is_integer(hd(TestDir)) ->
1645     tests(TestDir, ensure_atom(Suite), all).
1646tests(DirSuites) when is_list(DirSuites), is_tuple(hd(DirSuites)) ->
1647    [{?testdir(Dir,Suite),ensure_atom(Suite),all} || {Dir,Suite} <- DirSuites];
1648tests(TestDir) when is_list(TestDir), is_integer(hd(TestDir)) ->
1649    tests([TestDir]);
1650tests(TestDirs) when is_list(TestDirs), is_list(hd(TestDirs)) ->
1651    [{?testdir(TestDir,all),all,all} || TestDir <- TestDirs].
1652
1653do_run(Tests, Misc) when is_list(Misc) ->
1654    do_run(Tests, Misc, ".", []).
1655
1656do_run(Tests, Misc, LogDir, LogOpts) when is_list(Misc),
1657					  is_list(LogDir),
1658					  is_list(LogOpts) ->
1659    Opts =
1660	case proplists:get_value(step, Misc) of
1661	    undefined ->
1662		#opts{};
1663	    StepOpts ->
1664		#opts{step = StepOpts}
1665	end,
1666    do_run(Tests, [], Opts#opts{logdir = LogDir}, []);
1667
1668do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) ->
1669    #opts{label = Label, profile = Profile,
1670	  verbosity = VLvls} = Opts,
1671    %% label - used by ct_logs
1672    TestLabel =
1673	if Label == undefined -> undefined;
1674	   is_atom(Label)     -> atom_to_list(Label);
1675	   is_list(Label)     -> Label;
1676	   true               -> undefined
1677	end,
1678    application:set_env(common_test, test_label, TestLabel),
1679
1680    %% profile - used in ct_util
1681    TestProfile =
1682	if Profile == undefined -> undefined;
1683	   is_atom(Profile)     -> atom_to_list(Profile);
1684	   is_list(Profile)     -> Profile;
1685	   true                 -> undefined
1686	end,
1687    application:set_env(common_test, profile, TestProfile),
1688
1689    case code:which(test_server) of
1690	non_existing ->
1691	    {error,no_path_to_test_server};
1692	_ ->
1693	    %% This env variable is used by test_server to determine
1694	    %% which framework it runs under.
1695	    case os:getenv("TEST_SERVER_FRAMEWORK") of
1696		false ->
1697		    os:putenv("TEST_SERVER_FRAMEWORK", "ct_framework"),
1698		    os:putenv("TEST_SERVER_FRAMEWORK_NAME", "common_test");
1699		"ct_framework" ->
1700		    ok;
1701		Other ->
1702		    erlang:display(
1703		      list_to_atom(
1704			"Note: TEST_SERVER_FRAMEWORK = " ++ Other))
1705	    end,
1706	    Verbosity = add_verbosity_defaults(VLvls),
1707	    case ct_util:start(Opts#opts.logdir, Verbosity) of
1708		{error,interactive_mode} ->
1709		    io:format("CT is started in interactive mode. "
1710			      "To exit this mode, "
1711			      "run ct:stop_interactive().\n"
1712			      "To enter the interactive mode again, "
1713			      "run ct:start_interactive()\n\n",[]),
1714		    {error,interactive_mode};
1715		_Pid ->
1716		    ct_util:set_testdata({starter,Opts#opts.starter}),
1717		    compile_and_run(Tests, Skip,
1718                                    Opts#opts{verbosity=Verbosity}, Args)
1719	    end
1720    end.
1721
1722compile_and_run(Tests, Skip, Opts, Args) ->
1723    %% save stylesheet info
1724    ct_util:set_testdata({stylesheet,Opts#opts.stylesheet}),
1725    %% save logopts
1726    ct_util:set_testdata({logopts,Opts#opts.logopts}),
1727    %% save info about current testspec (testspec record or undefined)
1728    ct_util:set_testdata({testspec,Opts#opts.current_testspec}),
1729
1730    %% enable silent connections
1731    case Opts#opts.silent_connections of
1732	[] ->
1733	    ok;
1734	Conns ->
1735	    case lists:member(all, Conns) of
1736		true ->
1737		    Conns1 = ct_util:override_silence_all_connections(),
1738		    ct_logs:log("Silent connections", "~tp", [Conns1]);
1739		false ->
1740		    ct_util:override_silence_connections(Conns),
1741		    ct_logs:log("Silent connections", "~tp", [Conns])
1742	    end
1743    end,
1744    log_ts_names(Opts#opts.testspec_files),
1745    TestSuites = suite_tuples(Tests),
1746
1747    {_TestSuites1,SuiteMakeErrors,AllMakeErrors} =
1748	case application:get_env(common_test, auto_compile) of
1749	    {ok,false} ->
1750		{TestSuites1,SuitesNotFound} =
1751		    verify_suites(TestSuites),
1752		{TestSuites1,SuitesNotFound,SuitesNotFound};
1753	    _ ->
1754		{SuiteErrs,HelpErrs} = auto_compile(TestSuites),
1755		{TestSuites,SuiteErrs,SuiteErrs++HelpErrs}
1756	end,
1757
1758    case continue(AllMakeErrors, Opts#opts.abort_if_missing_suites) of
1759	true ->
1760	    SavedErrors = save_make_errors(SuiteMakeErrors),
1761	    ct_repeat:log_loop_info(Args),
1762
1763	    try final_tests(Tests,Skip,SavedErrors) of
1764		{Tests1,Skip1} ->
1765		    ReleaseSh = proplists:get_value(release_shell, Args),
1766		    ct_util:set_testdata({release_shell,ReleaseSh}),
1767		    TestResult =
1768			possibly_spawn(ReleaseSh == true, Tests1, Skip1, Opts),
1769		    case TestResult of
1770			{Ok,Errors,Skipped} ->
1771			    NoOfMakeErrors =
1772				lists:foldl(fun({_,BadMods}, X) ->
1773						    X + length(BadMods)
1774					    end, 0, SuiteMakeErrors),
1775			    {Ok,Errors+NoOfMakeErrors,Skipped};
1776			ErrorResult ->
1777			    ErrorResult
1778		    end
1779	    catch
1780		_:BadFormat ->
1781		    {error,BadFormat}
1782	    end;
1783	false ->
1784	    io:nl(),
1785	    ct_util:stop(clean),
1786	    BadMods =
1787		lists:foldr(
1788		  fun({{_,_},Ms}, Acc) ->
1789			  Ms ++ lists:foldl(
1790				  fun(M, Acc1) ->
1791					  lists:delete(M, Acc1)
1792				  end, Acc, Ms)
1793		  end, [], AllMakeErrors),
1794	    {error,{make_failed,BadMods}}
1795    end.
1796
1797%% keep the shell as the top controlling process
1798possibly_spawn(false, Tests, Skip, Opts) ->
1799    TestResult = (catch do_run_test(Tests, Skip, Opts)),
1800    case TestResult of
1801	{EType,_} = Error when EType == user_error;
1802			       EType == error ->
1803	    ct_util:stop(clean),
1804	    exit(Error);
1805	_ ->
1806	    ct_util:stop(normal),
1807	    TestResult
1808    end;
1809
1810%% we must return control to the shell now, so we spawn
1811%% a test supervisor process to keep an eye on the test run
1812possibly_spawn(true, Tests, Skip, Opts) ->
1813    CTUtilSrv = whereis(ct_util_server),
1814    Supervisor =
1815	fun() ->
1816                ct_util:mark_process(),
1817		process_flag(trap_exit, true),
1818		link(CTUtilSrv),
1819		TestRun =
1820		    fun() ->
1821                            ct_util:mark_process(),
1822			    TestResult = (catch do_run_test(Tests, Skip, Opts)),
1823			    case TestResult of
1824				{EType,_} = Error when EType == user_error;
1825						       EType == error ->
1826				    ct_util:stop(clean),
1827				    exit(Error);
1828				_ ->
1829				    ct_util:stop(normal),
1830				    exit({ok,TestResult})
1831			    end
1832		    end,
1833		TestRunPid = spawn_link(TestRun),
1834		receive
1835		    {'EXIT',TestRunPid,{ok,TestResult}} ->
1836			io:format(user, "~nCommon Test returned ~tp~n~n",
1837				  [TestResult]);
1838		    {'EXIT',TestRunPid,Error} ->
1839			exit(Error)
1840		end
1841	end,
1842    unlink(CTUtilSrv),
1843    SupPid = spawn(Supervisor),
1844    io:format(user, "~nTest control handed over to process ~w~n~n",
1845	      [SupPid]),
1846    SupPid.
1847
1848%% attempt to compile the modules specified in TestSuites
1849auto_compile(TestSuites) ->
1850    io:format("~nCommon Test: Running make in test directories...~n"),
1851    UserInclude =
1852	case application:get_env(common_test, include) of
1853	    {ok,UserInclDirs} when length(UserInclDirs) > 0 ->
1854		io:format("Including the following directories:~n"),
1855		[begin io:format("~tp~n",[UserInclDir]), {i,UserInclDir} end ||
1856		 UserInclDir <- UserInclDirs];
1857	    _ ->
1858		[]
1859	end,
1860    SuiteMakeErrors =
1861	lists:flatmap(fun({TestDir,Suite} = TS) ->
1862			      case run_make(suites, TestDir,
1863					    Suite, UserInclude,
1864                                            [nowarn_export_all]) of
1865				  {error,{make_failed,Bad}} ->
1866				      [{TS,Bad}];
1867				  {error,_} ->
1868				      [{TS,[filename:join(TestDir,
1869							  "*_SUITE")]}];
1870				  _ ->
1871				      []
1872			      end
1873		      end, TestSuites),
1874
1875    %% try to compile other modules than SUITEs in the test directories
1876    {_,HelpMakeErrors} =
1877	lists:foldl(
1878	  fun({Dir,Suite}, {Done,Failed}) ->
1879		  case lists:member(Dir, Done) of
1880		      false ->
1881			  Failed1 =
1882			      case run_make(helpmods, Dir, Suite, UserInclude, []) of
1883				  {error,{make_failed,BadMods}} ->
1884				      [{{Dir,all},BadMods}|Failed];
1885				  {error,_} ->
1886				      [{{Dir,all},[Dir]}|Failed];
1887				  _ ->
1888				      Failed
1889			      end,
1890			  {[Dir|Done],Failed1};
1891		      true ->		    % already visited
1892			  {Done,Failed}
1893		  end
1894	  end, {[],[]}, TestSuites),
1895    {SuiteMakeErrors,lists:reverse(HelpMakeErrors)}.
1896
1897%% verify that specified test suites exist (if auto compile is disabled)
1898verify_suites(TestSuites) ->
1899    io:nl(),
1900    Verify =
1901	fun({Dir,Suite}=DS,{Found,NotFound}) ->
1902		case locate_test_dir(Dir, Suite) of
1903		    {ok,TestDir} ->
1904			if Suite == all ->
1905				{[DS|Found],NotFound};
1906			   true ->
1907				Beam = filename:join(TestDir,
1908						     atom_to_list(Suite)++
1909							 ".beam"),
1910				case filelib:is_regular(Beam) of
1911				    true  ->
1912					{[DS|Found],NotFound};
1913				    false ->
1914					case code:is_loaded(Suite) of
1915					    {file,SuiteFile} ->
1916						%% test suite is already
1917						%% loaded and since
1918						%% auto_compile == false,
1919						%% let's assume the user has
1920						%% loaded the beam file
1921						%% explicitly
1922						ActualDir =
1923						    filename:dirname(SuiteFile),
1924						{[{ActualDir,Suite}|Found],
1925						 NotFound};
1926					    false ->
1927						Name =
1928						    filename:join(TestDir,
1929								  atom_to_list(
1930								    Suite)),
1931						io:format(user,
1932							  "Suite ~w not found "
1933							  "in directory ~ts~n",
1934							  [Suite,TestDir]),
1935						{Found,[{DS,[Name]}|NotFound]}
1936					end
1937				end
1938			end;
1939		    {error,_Reason} ->
1940			case code:is_loaded(Suite) of
1941			    {file,SuiteFile} ->
1942				%% test suite is already loaded and since
1943				%% auto_compile == false, let's assume the
1944				%% user has loaded the beam file explicitly
1945				ActualDir = filename:dirname(SuiteFile),
1946				{[{ActualDir,Suite}|Found],NotFound};
1947			    false ->
1948				io:format(user, "Directory ~ts is "
1949					  "invalid~n", [Dir]),
1950				Name = filename:join(Dir, atom_to_list(Suite)),
1951				{Found,[{DS,[Name]}|NotFound]}
1952			end
1953		end
1954	end,
1955    {ActualFound,Missing} = lists:foldl(Verify, {[],[]}, TestSuites),
1956    {lists:reverse(ActualFound),lists:reverse(Missing)}.
1957
1958save_make_errors([]) ->
1959    [];
1960save_make_errors(Errors) ->
1961    Suites = get_bad_suites(Errors,[]),
1962    ct_logs:log("MAKE RESULTS",
1963		"Error compiling or locating the "
1964		"following suites: ~n~p",[Suites]),
1965    %% save the info for logger
1966    ok = file:write_file(?missing_suites_info,term_to_binary(Errors)),
1967    Errors.
1968
1969get_bad_suites([{{_TestDir,_Suite},Failed}|Errors], BadSuites) ->
1970    get_bad_suites(Errors,BadSuites++Failed);
1971get_bad_suites([], BadSuites) ->
1972    BadSuites.
1973
1974
1975step(TestDir, Suite, Case) ->
1976    step(TestDir, Suite, Case, []).
1977
1978step(TestDir, Suite, Case, Opts) when is_list(TestDir),
1979				      is_atom(Suite), is_atom(Case),
1980				      Suite =/= all, Case =/= all ->
1981    do_run([{TestDir,Suite,Case}], [{step,Opts}]).
1982
1983
1984%%%-----------------------------------------------------------------
1985%%% Internal
1986suite_tuples([{TestDir,Suites,_} | Tests]) when is_list(Suites) ->
1987    lists:map(fun(S) -> {TestDir,S} end, Suites) ++ suite_tuples(Tests);
1988suite_tuples([{TestDir,Suite,_} | Tests]) when is_atom(Suite) ->
1989    [{TestDir,Suite} | suite_tuples(Tests)];
1990suite_tuples([]) ->
1991    [].
1992
1993final_tests(Tests, Skip, Bad) ->
1994    {Tests1,Skip1} = final_tests1(Tests, [], Skip, Bad),
1995    Skip2 = final_skip(Skip1, []),
1996    {Tests1,Skip2}.
1997
1998final_tests1([{TestDir,Suites,_}|Tests], Final, Skip, Bad) when
1999      is_list(Suites), is_atom(hd(Suites)) ->
2000    Skip1 = [{TD,S,make_failed} || {{TD,S},_} <- Bad, S1 <- Suites,
2001				     S == S1, TD == TestDir],
2002    Final1 = [{TestDir,S,all} || S <- Suites],
2003    final_tests1(Tests, lists:reverse(Final1)++Final, Skip++Skip1, Bad);
2004
2005final_tests1([{TestDir,all,all}|Tests], Final, Skip, Bad) ->
2006    MissingSuites =
2007	case lists:keysearch({TestDir,all}, 1, Bad) of
2008	    {value,{_,Failed}} ->
2009		[list_to_atom(filename:basename(F)) || F <- Failed];
2010	    false ->
2011		[]
2012	end,
2013    Missing = [{TestDir,S,make_failed} || S <- MissingSuites],
2014    Final1 = [{TestDir,all,all}|Final],
2015    final_tests1(Tests, Final1, Skip++Missing, Bad);
2016
2017final_tests1([{TestDir,Suite,Cases}|Tests], Final, Skip, Bad) when
2018      Cases==[]; Cases==all  ->
2019    final_tests1([{TestDir,[Suite],all}|Tests], Final, Skip, Bad);
2020
2021final_tests1([{TestDir,Suite,GrsOrCs}|Tests], Final, Skip, Bad) when
2022      is_list(GrsOrCs) ->
2023    case lists:keymember({TestDir,Suite}, 1, Bad) of
2024	true ->
2025	    Skip1 = Skip ++ [{TestDir,Suite,all,make_failed}],
2026	    final_tests1(Tests, [{TestDir,Suite,all}|Final], Skip1, Bad);
2027	false ->
2028	    GrsOrCs1 =
2029		lists:flatmap(
2030		  %% for now, only flat group defs are allowed as
2031		  %% start options and test spec terms
2032		  fun({all,all}) ->
2033			  [ct_groups:make_conf(TestDir, Suite, all, [], all)];
2034		     ({skipped,Group,TCs}) ->
2035			  [ct_groups:make_conf(TestDir, Suite,
2036					       Group, [skipped], TCs)];
2037		     ({skipped,TC}) ->
2038			  case lists:member(TC, GrsOrCs) of
2039			      true ->
2040				  [];
2041			      false ->
2042				  [TC]
2043			  end;
2044		     ({GrSpec = {GroupName,_},TCs}) ->
2045			  Props = [{override,GrSpec}],
2046			  [ct_groups:make_conf(TestDir, Suite,
2047					       GroupName, Props, TCs)];
2048		     ({GrSpec = {GroupName,_,_},TCs}) ->
2049			  Props = [{override,GrSpec}],
2050			  [ct_groups:make_conf(TestDir, Suite,
2051					       GroupName, Props, TCs)];
2052		     ({GroupOrGroups,TCs}) ->
2053			  [ct_groups:make_conf(TestDir, Suite,
2054					       GroupOrGroups, [], TCs)];
2055		     (TC) ->
2056			  [TC]
2057		  end, GrsOrCs),
2058	    Do = {TestDir,Suite,GrsOrCs1},
2059	    final_tests1(Tests, [Do|Final], Skip, Bad)
2060    end;
2061
2062final_tests1([], Final, Skip, _Bad) ->
2063    {lists:reverse(Final),Skip}.
2064
2065final_skip([{TestDir,Suite,{all,all},Reason}|Skips], Final) ->
2066    SkipConf = ct_groups:make_conf(TestDir, Suite, all, [], all),
2067    Skip = {TestDir,Suite,SkipConf,Reason},
2068    final_skip(Skips, [Skip|Final]);
2069
2070final_skip([{TestDir,Suite,{Group,TCs},Reason}|Skips], Final) ->
2071    Conf =  ct_groups:make_conf(TestDir, Suite, Group, [], TCs),
2072    Skip = {TestDir,Suite,Conf,Reason},
2073    final_skip(Skips, [Skip|Final]);
2074
2075final_skip([Skip|Skips], Final) ->
2076    final_skip(Skips, [Skip|Final]);
2077
2078final_skip([], Final) ->
2079    lists:reverse(Final).
2080
2081continue([], _) ->
2082    true;
2083continue(_MakeErrors, true) ->
2084    false;
2085continue(_MakeErrors, _AbortIfMissingSuites) ->
2086    io:nl(),
2087    OldGL = group_leader(),
2088    case set_group_leader_same_as_shell(OldGL) of
2089	true ->
2090	    S = self(),
2091	    io:format("Failed to compile or locate one "
2092		      "or more test suites\n"
2093		      "Press \'c\' to continue or \'a\' to abort.\n"
2094		      "Will continue in 15 seconds if no "
2095		      "answer is given!\n"),
2096	    Pid = spawn(fun() ->
2097				case io:get_line('(c/a) ') of
2098				    "c\n" ->
2099					S ! true;
2100				    _ ->
2101					S ! false
2102				end
2103			end),
2104	    group_leader(OldGL, self()),
2105	    receive R when R==true; R==false ->
2106		    R
2107	    after 15000 ->
2108		    exit(Pid, kill),
2109		    io:format("... timeout - continuing!!\n"),
2110		    true
2111	    end;
2112	false ->				% no shell process to use
2113	    true
2114    end.
2115
2116set_group_leader_same_as_shell(OldGL) ->
2117    %% find the group leader process on the node in a dirty fashion
2118    %% (check initial function call and look in the process dictionary)
2119    GS2or3 = fun(P) ->
2120    		     case process_info(P,initial_call) of
2121    			 {initial_call,{group,server,X}} when X == 2 ; X == 3 ->
2122    			     true;
2123    			 _ ->
2124    			     false
2125    		     end
2126    	     end,
2127    case [P || P <- processes(), GS2or3(P),
2128    	       true == lists:keymember(shell,1,
2129    				       element(2,process_info(P,dictionary)))] of
2130    	[GL|_] ->
2131            %% check if started from remote node (skip interaction)
2132            if node(OldGL) /= node(GL) -> false;
2133               true -> group_leader(GL, self())
2134            end;
2135    	[] ->
2136    	    false
2137    end.
2138
2139check_and_add([{TestDir0,M,_} | Tests], Added, PA) ->
2140    case locate_test_dir(TestDir0, M) of
2141	{ok,TestDir} ->
2142	    case lists:member(TestDir, Added) of
2143		true ->
2144		    check_and_add(Tests, Added, PA);
2145		false ->
2146		    case lists:member(rm_trailing_slash(TestDir),
2147				      code:get_path()) of
2148			false ->
2149			    true = code:add_patha(TestDir),
2150			    check_and_add(Tests, [TestDir|Added], [TestDir|PA]);
2151			true ->
2152			    check_and_add(Tests, [TestDir|Added], PA)
2153		    end
2154	    end;
2155	{error,_} ->
2156	    {error,{invalid_directory,TestDir0}}
2157    end;
2158check_and_add([], _, PA) ->
2159    {ok,PA}.
2160
2161do_run_test(Tests, Skip, Opts0) ->
2162    case check_and_add(Tests, [], []) of
2163	{ok,AddedToPath} ->
2164	    ct_util:set_testdata({stats,{0,0,{0,0}}}),
2165
2166	    %% test_server needs to know the include path too
2167	    InclPath = case application:get_env(common_test, include) of
2168			   {ok,Incls} -> Incls;
2169			   _          -> []
2170		       end,
2171	    application:set_env(test_server, include, InclPath),
2172
2173	    %% copy the escape characters setting to test_server
2174	    EscChars =
2175		case application:get_env(common_test, esc_chars) of
2176		    {ok,ECBool} -> ECBool;
2177		    _           -> true
2178		end,
2179	    application:set_env(test_server, esc_chars, EscChars),
2180
2181	    {ok, _} = test_server_ctrl:start_link(local),
2182
2183	    %% let test_server expand the test tuples and count no of cases
2184	    {Suites,NoOfCases} = count_test_cases(Tests, Skip),
2185	    Suites1 = delete_dups(Suites),
2186	    NoOfTests = length(Tests),
2187	    NoOfSuites = length(Suites1),
2188	    ct_util:warn_duplicates(Suites1),
2189	    {ok,Cwd} = file:get_cwd(),
2190	    io:format("~nCWD set to: ~tp~n", [Cwd]),
2191	    if NoOfCases == unknown ->
2192		    io:format("~nTEST INFO: ~w test(s), ~w suite(s)~n~n",
2193			      [NoOfTests,NoOfSuites]),
2194		    ct_logs:log("TEST INFO","~w test(s), ~w suite(s)",
2195				[NoOfTests,NoOfSuites]);
2196	       true ->
2197		    io:format("~nTEST INFO: ~w test(s), ~w case(s) "
2198			      "in ~w suite(s)~n~n",
2199			      [NoOfTests,NoOfCases,NoOfSuites]),
2200		    ct_logs:log("TEST INFO","~w test(s), ~w case(s) "
2201				"in ~w suite(s)",
2202				[NoOfTests,NoOfCases,NoOfSuites])
2203	    end,
2204	    %% if the verbosity level is set lower than ?STD_IMPORTANCE, tell
2205	    %% test_server to ignore stdout printouts to the test case log file
2206	    case proplists:get_value(default, Opts0#opts.verbosity) of
2207		VLvl when is_integer(VLvl), (?STD_IMPORTANCE < (100-VLvl)) ->
2208		    test_server_ctrl:reject_io_reqs(true);
2209		_Lower ->
2210		    ok
2211	    end,
2212
2213            case Opts0#opts.multiply_timetraps of
2214                undefined -> MultTT = 1;
2215                MultTT    -> MultTT
2216            end,
2217            case Opts0#opts.scale_timetraps of
2218                undefined -> ScaleTT = false;
2219                ScaleTT   -> ScaleTT
2220            end,
2221            ct_logs:log("TEST INFO","Timetrap time multiplier = ~w~n"
2222                        "Timetrap scaling enabled = ~w", [MultTT,ScaleTT]),
2223            test_server_ctrl:multiply_timetraps(MultTT),
2224	    test_server_ctrl:scale_timetraps(ScaleTT),
2225
2226	    test_server_ctrl:create_priv_dir(choose_val(
2227					       Opts0#opts.create_priv_dir,
2228					       auto_per_run)),
2229
2230	    {ok,LogDir} = ct_logs:get_log_dir(true),
2231	    {TsCoverInfo,Opts} = maybe_start_cover(Opts0, LogDir),
2232
2233	    ct_event:notify(#event{name=start_info,
2234				   node=node(),
2235				   data={NoOfTests,NoOfSuites,NoOfCases}}),
2236	    CleanUp = add_jobs(Tests, Skip, Opts, []),
2237	    unlink(whereis(test_server_ctrl)),
2238	    catch test_server_ctrl:wait_finish(),
2239
2240	    maybe_stop_cover(Opts, TsCoverInfo, LogDir),
2241
2242	    %% check if last testcase has left a "dead" trace window
2243	    %% behind, and if so, kill it
2244	    case ct_util:get_testdata(interpret) of
2245		{_What,kill,{TCPid,AttPid}} ->
2246		    ct_util:kill_attached(TCPid, AttPid);
2247		_ ->
2248		    ok
2249	    end,
2250	    lists:foreach(fun(Suite) ->
2251				  maybe_cleanup_interpret(Suite, Opts#opts.step)
2252			  end, CleanUp),
2253	    _ = [code:del_path(Dir) || Dir <- AddedToPath],
2254
2255	    %% If a severe error has occurred in the test_server,
2256	    %% we will generate an exception here.
2257	    case ct_util:get_testdata(severe_error) of
2258		undefined -> ok;
2259		SevereError ->
2260		    ct_logs:log("SEVERE ERROR", "~tp\n", [SevereError]),
2261		    exit(SevereError)
2262	    end,
2263
2264	    case ct_util:get_testdata(stats) of
2265		Stats = {_Ok,_Failed,{_UserSkipped,_AutoSkipped}} ->
2266		    Stats;
2267		_ ->
2268		    {error,test_result_unknown}
2269	    end;
2270	Error ->
2271	    exit(Error)
2272    end.
2273
2274maybe_start_cover(Opts=#opts{cover=Cover,cover_stop=CoverStop0},LogDir) ->
2275    if Cover == undefined ->
2276	    {undefined,Opts};
2277       true ->
2278	    case ct_cover:get_spec(Cover) of
2279		{error,Reason} ->
2280		    exit({error,Reason});
2281		CoverSpec ->
2282		    CoverStop =
2283			case CoverStop0 of
2284			    undefined -> true;
2285			    Stop -> Stop
2286			end,
2287		    start_cover(Opts#opts{coverspec=CoverSpec,
2288					  cover_stop=CoverStop},
2289				LogDir)
2290	    end
2291    end.
2292
2293start_cover(Opts=#opts{coverspec=CovData,cover_stop=CovStop},LogDir) ->
2294    {CovFile,
2295     CovNodes,
2296     CovImport,
2297     _CovExport,
2298     #cover{app        = CovApp,
2299            local_only = LocalOnly,
2300	    level      = CovLevel,
2301	    excl_mods  = CovExcl,
2302	    incl_mods  = CovIncl,
2303	    cross      = CovCross,
2304	    src        = _CovSrc}} = CovData,
2305    case LocalOnly of
2306        true -> cover:local_only();
2307        false -> ok
2308    end,
2309    ct_logs:log("COVER INFO",
2310		"Using cover specification file: ~ts~n"
2311		"App: ~w~n"
2312                "Local only: ~w~n"
2313		"Cross cover: ~w~n"
2314		"Including ~w modules~n"
2315		"Excluding ~w modules",
2316		[CovFile,CovApp,LocalOnly,CovCross,
2317		 length(CovIncl),length(CovExcl)]),
2318
2319    %% Tell test_server to print a link in its coverlog
2320    %% pointing to the real coverlog which will be written in
2321    %% maybe_stop_cover/2
2322    test_server_ctrl:cover({log,LogDir}),
2323
2324    %% Cover compile all modules
2325    {ok,TsCoverInfo} = test_server_ctrl:cover_compile(CovApp,CovFile,
2326						      CovExcl,CovIncl,
2327						      CovCross,CovLevel,
2328						      CovStop),
2329    ct_logs:log("COVER INFO",
2330		"Compilation completed - test_server cover info: ~tp",
2331		[TsCoverInfo]),
2332
2333    %% start cover on specified nodes
2334    if (CovNodes /= []) and (CovNodes /= undefined) ->
2335	    ct_logs:log("COVER INFO",
2336			"Nodes included in cover "
2337			"session: ~tw",
2338			[CovNodes]),
2339	    cover:start(CovNodes);
2340       true ->
2341	    ok
2342    end,
2343    lists:foreach(
2344      fun(Imp) ->
2345	      case cover:import(Imp) of
2346		  ok ->
2347		      ok;
2348		  {error,Reason} ->
2349		      ct_logs:log("COVER INFO",
2350				  "Importing cover data from: ~ts fails! "
2351				  "Reason: ~tp", [Imp,Reason])
2352	      end
2353      end, CovImport),
2354    {TsCoverInfo,Opts}.
2355
2356maybe_stop_cover(_,undefined,_) ->
2357    ok;
2358maybe_stop_cover(#opts{coverspec=CovData},TsCoverInfo,LogDir) ->
2359    {_CovFile,
2360     _CovNodes,
2361     _CovImport,
2362     CovExport,
2363     _AppData} = CovData,
2364    case CovExport of
2365	undefined -> ok;
2366	_ ->
2367	    ct_logs:log("COVER INFO","Exporting cover data to ~tp",[CovExport]),
2368	    cover:export(CovExport)
2369    end,
2370    ct_logs:log("COVER INFO","Analysing cover data to ~tp",[LogDir]),
2371    test_server_ctrl:cover_analyse(TsCoverInfo,LogDir),
2372    ct_logs:log("COVER INFO","Analysis completed.",[]),
2373    ok.
2374
2375
2376delete_dups([S | Suites]) ->
2377    Suites1 = lists:delete(S, Suites),
2378    [S | delete_dups(Suites1)];
2379delete_dups([]) ->
2380    [].
2381
2382count_test_cases(Tests, Skip) ->
2383    SendResult = fun(Me, Result) -> Me ! {no_of_cases,Result} end,
2384    TSPid = test_server_ctrl:start_get_totals(SendResult),
2385    Ref = erlang:monitor(process, TSPid),
2386    _ = add_jobs(Tests, Skip, #opts{}, []),
2387    Counted = (catch count_test_cases1(length(Tests), 0, [], Ref)),
2388    erlang:demonitor(Ref, [flush]),
2389    case Counted of
2390	{error,{test_server_died}} = Error ->
2391	    throw(Error);
2392	{error,Reason} ->
2393	    unlink(whereis(test_server_ctrl)),
2394	    test_server_ctrl:stop(),
2395	    throw({user_error,Reason});
2396	Result ->
2397	    test_server_ctrl:stop_get_totals(),
2398	    Result
2399    end.
2400
2401count_test_cases1(0, N, Suites, _) ->
2402    {lists:flatten(Suites), N};
2403count_test_cases1(Jobs, N, Suites, Ref) ->
2404    receive
2405	{_,{error,_Reason} = Error} ->
2406	    throw(Error);
2407	{no_of_cases,{Ss,N1}} ->
2408	    count_test_cases1(Jobs-1, add_known(N,N1), [Ss|Suites], Ref);
2409	{'DOWN', Ref, _, _, Info} ->
2410	    throw({error,{test_server_died,Info}})
2411    end.
2412
2413add_known(unknown, _) ->
2414    unknown;
2415add_known(_, unknown) ->
2416    unknown;
2417add_known(N, N1) ->
2418    N+N1.
2419
2420add_jobs([{TestDir,all,_}|Tests], Skip, Opts, CleanUp) ->
2421    Name = get_name(TestDir),
2422    case catch test_server_ctrl:add_dir_with_skip(Name, TestDir,
2423						  skiplist(TestDir,Skip)) of
2424	{'EXIT',_} ->
2425	    CleanUp;
2426	_ ->
2427	    case wait_for_idle() of
2428		ok ->
2429		    add_jobs(Tests, Skip, Opts, CleanUp);
2430		_ ->
2431		    CleanUp
2432	    end
2433    end;
2434add_jobs([{TestDir,[Suite],all}|Tests], Skip,
2435	 Opts, CleanUp) when is_atom(Suite) ->
2436    add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp);
2437add_jobs([{TestDir,Suites,all}|Tests], Skip,
2438	 Opts, CleanUp) when is_list(Suites) ->
2439    Name = get_name(TestDir) ++ ".suites",
2440    case catch test_server_ctrl:add_module_with_skip(Name, Suites,
2441						     skiplist(TestDir,Skip)) of
2442	{'EXIT',_} ->
2443	    CleanUp;
2444	_ ->
2445	    case wait_for_idle() of
2446		ok ->
2447		    add_jobs(Tests, Skip, Opts, CleanUp);
2448		_ ->
2449		    CleanUp
2450	    end
2451    end;
2452add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp) ->
2453    case maybe_interpret(Suite, all, Opts) of
2454	ok ->
2455	    Name =  get_name(TestDir) ++ "." ++ atom_to_list(Suite),
2456	    case catch test_server_ctrl:add_module_with_skip(Name, [Suite],
2457							     skiplist(TestDir,
2458								      Skip)) of
2459		{'EXIT',_} ->
2460		    CleanUp;
2461		_ ->
2462		    case wait_for_idle() of
2463			ok ->
2464			    add_jobs(Tests, Skip, Opts, [Suite|CleanUp]);
2465			_ ->
2466			    CleanUp
2467		    end
2468	    end;
2469	Error ->
2470	    Error
2471    end;
2472
2473%% group (= conf case in test_server)
2474add_jobs([{TestDir,Suite,Confs}|Tests], Skip, Opts, CleanUp) when
2475      element(1, hd(Confs)) == conf ->
2476    Group = fun(Conf) -> proplists:get_value(name, element(2, Conf)) end,
2477    TestCases = fun(Conf) -> element(4, Conf) end,
2478    TCTestName = fun(all) -> "";
2479		    ([C]) when is_atom(C) -> "." ++ atom_to_list(C);
2480		    (Cs) when is_list(Cs) -> ".cases"
2481		 end,
2482    GrTestName =
2483	case Confs of
2484	    [Conf] ->
2485		case Group(Conf) of
2486		    GrName when is_atom(GrName) ->
2487			"." ++ atom_to_list(GrName) ++
2488			    TCTestName(TestCases(Conf));
2489		    _ ->
2490			".groups" ++ TCTestName(TestCases(Conf))
2491		end;
2492	    _ ->
2493		".groups"
2494	end,
2495    TestName = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ GrTestName,
2496    case maybe_interpret(Suite, init_per_group, Opts) of
2497	ok ->
2498	    case catch test_server_ctrl:add_conf_with_skip(TestName,
2499							   Suite,
2500							   Confs,
2501							   skiplist(TestDir,
2502								    Skip)) of
2503		{'EXIT',_} ->
2504		    CleanUp;
2505		_ ->
2506		    case wait_for_idle() of
2507			ok ->
2508			    add_jobs(Tests, Skip, Opts, [Suite|CleanUp]);
2509			_ ->
2510			    CleanUp
2511		    end
2512	    end;
2513	Error ->
2514	    Error
2515    end;
2516
2517%% test case
2518add_jobs([{TestDir,Suite,[Case]}|Tests],
2519	 Skip, Opts, CleanUp) when is_atom(Case) ->
2520    add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp);
2521
2522add_jobs([{TestDir,Suite,Cases}|Tests],
2523	 Skip, Opts, CleanUp) when is_list(Cases) ->
2524    Cases1 = lists:map(fun({GroupName,_}) when is_atom(GroupName) -> GroupName;
2525			  (Case) -> Case
2526		       end, Cases),
2527    case maybe_interpret(Suite, Cases1, Opts) of
2528	ok ->
2529	    Name =  get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ ".cases",
2530	    case catch test_server_ctrl:add_cases_with_skip(Name, Suite, Cases1,
2531							    skiplist(TestDir,
2532								     Skip)) of
2533		{'EXIT',_} ->
2534		    CleanUp;
2535		_ ->
2536		    case wait_for_idle() of
2537			ok ->
2538			    add_jobs(Tests, Skip, Opts, [Suite|CleanUp]);
2539			_ ->
2540			    CleanUp
2541		    end
2542	    end;
2543	Error ->
2544	    Error
2545    end;
2546add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp) when is_atom(Case) ->
2547    case maybe_interpret(Suite, Case, Opts) of
2548	ok ->
2549	    Name = get_name(TestDir) ++	"." ++ atom_to_list(Suite) ++ "." ++
2550		atom_to_list(Case),
2551	    case catch test_server_ctrl:add_case_with_skip(Name, Suite, Case,
2552							   skiplist(TestDir,
2553								    Skip)) of
2554		{'EXIT',_} ->
2555		    CleanUp;
2556		_ ->
2557		    case wait_for_idle() of
2558			ok ->
2559			    add_jobs(Tests, Skip, Opts, [Suite|CleanUp]);
2560			_ ->
2561			    CleanUp
2562		    end
2563	    end;
2564	Error ->
2565	    Error
2566    end;
2567add_jobs([], _, _, CleanUp) ->
2568    CleanUp.
2569
2570wait_for_idle() ->
2571    ct_util:update_last_run_index(),
2572    Notify = fun(Me,IdleState) -> Me ! {idle,IdleState},
2573				  receive
2574				      {Me,proceed} -> ok
2575				  after
2576				      30000 -> ok
2577				  end
2578	     end,
2579    case catch test_server_ctrl:idle_notify(Notify) of
2580	{'EXIT',_} ->
2581	    error;
2582	TSPid ->
2583	    %% so we don't hang forever if test_server dies
2584	    Ref = erlang:monitor(process, TSPid),
2585	    Result = receive
2586			 {idle,abort}           -> aborted;
2587			 {idle,_}               -> ok;
2588			 {'DOWN', Ref, _, _, _} -> error
2589		     end,
2590	    erlang:demonitor(Ref, [flush]),
2591	    ct_util:update_last_run_index(),
2592	    %% let test_server_ctrl proceed (and possibly shut down) now
2593	    TSPid ! {self(),proceed},
2594	    Result
2595    end.
2596
2597skiplist(Dir, [{Dir,all,Cmt}|Skip]) ->
2598    %% we need to turn 'all' into list of modules since
2599    %% test_server doesn't do skips on Dir level
2600    Ss = filelib:wildcard(filename:join(Dir, "*_SUITE.beam")),
2601    [{list_to_atom(filename:basename(S,".beam")),Cmt} || S <- Ss] ++
2602	skiplist(Dir,Skip);
2603skiplist(Dir, [{Dir,S,Cmt}|Skip]) ->
2604    [{S,Cmt} | skiplist(Dir, Skip)];
2605skiplist(Dir, [{Dir,S,C,Cmt}|Skip]) ->
2606    [{S,C,Cmt} | skiplist(Dir, Skip)];
2607skiplist(Dir, [_|Skip]) ->
2608    skiplist(Dir, Skip);
2609skiplist(_Dir, []) ->
2610    [].
2611
2612get_name(Dir) ->
2613    TestDir =
2614	case filename:basename(Dir) of
2615	    "test" ->
2616		filename:dirname(Dir);
2617	    _ ->
2618		Dir
2619	end,
2620    Base = filename:basename(TestDir),
2621    case filename:basename(filename:dirname(TestDir)) of
2622	"" ->
2623	    Base;
2624	TopDir ->
2625	    TopDir ++ "." ++ Base
2626    end.
2627
2628run_make(TestDir, Mod, UserInclude) ->
2629    run_make(suites, TestDir, Mod, UserInclude, [nowarn_export_all]).
2630
2631run_make(Targets, TestDir0, Mod, UserInclude, COpts) when is_list(Mod) ->
2632    run_make(Targets, TestDir0, list_to_atom(Mod), UserInclude, COpts);
2633
2634run_make(Targets, TestDir0, Mod, UserInclude, COpts) ->
2635    case locate_test_dir(TestDir0, Mod) of
2636	{ok,TestDir} ->
2637	    %% send a start_make notification which may suspend
2638	    %% the process if some other node is compiling files
2639	    %% in the same directory
2640	    ct_event:sync_notify(#event{name=start_make,
2641					node=node(),
2642					data=TestDir}),
2643	    {ok,Cwd} = file:get_cwd(),
2644	    ok = file:set_cwd(TestDir),
2645	    CtInclude = get_dir(common_test, "include"),
2646	    XmerlInclude = get_dir(xmerl, "include"),
2647	    ErlFlags = UserInclude ++ [{i,CtInclude},
2648				       {i,XmerlInclude},
2649				       debug_info] ++ COpts,
2650	    Result =
2651		if Mod == all ; Targets == helpmods ->
2652			case (catch ct_make:all([noexec|ErlFlags])) of
2653			    {'EXIT',_} = Failure ->
2654				Failure;
2655			    MakeInfo ->
2656				FileTest = fun(F, suites) -> is_suite(F);
2657					      (F, helpmods) -> not is_suite(F)
2658					   end,
2659				Files =
2660				    lists:flatmap(fun({F,out_of_date}) ->
2661							  case FileTest(F,
2662									Targets) of
2663								  true -> [F];
2664								  false -> []
2665							      end;
2666							 (_) ->
2667							      []
2668						      end, MakeInfo),
2669				(catch ct_make:files(Files, [load|ErlFlags]))
2670			end;
2671		   true ->
2672			(catch ct_make:files([Mod], [load|ErlFlags]))
2673		end,
2674
2675	    ok = file:set_cwd(Cwd),
2676	    %% send finished_make notification
2677	    ct_event:notify(#event{name=finished_make,
2678				   node=node(),
2679				   data=TestDir}),
2680	    case Result of
2681		{up_to_date,_} ->
2682		    ok;
2683		{'EXIT',Reason} ->
2684		    io:format("{error,{make_crashed,~tp}\n", [Reason]),
2685		    {error,{make_crashed,TestDir,Reason}};
2686		{error,ModInfo} ->
2687		    io:format("{error,make_failed}\n", []),
2688		    Bad = [filename:join(TestDir, M) || {M,R} <- ModInfo,
2689							R == error],
2690		    {error,{make_failed,Bad}}
2691	    end;
2692	{error,_} ->
2693	    io:format("{error,{invalid_directory,~tp}}\n", [TestDir0]),
2694	    {error,{invalid_directory,TestDir0}}
2695    end.
2696
2697get_dir(App, Dir) ->
2698    filename:join(code:lib_dir(App), Dir).
2699
2700maybe_interpret(Suite, Cases, #opts{step = StepOpts}) when StepOpts =/= undefined ->
2701    %% if other suite has run before this one, check if last testcase
2702    %% has left a "dead" trace window behind, and if so, kill it
2703    case ct_util:get_testdata(interpret) of
2704	{_What,kill,{TCPid,AttPid}} ->
2705	    ct_util:kill_attached(TCPid, AttPid);
2706	_ ->
2707	    ok
2708    end,
2709    maybe_interpret1(Suite, Cases, StepOpts);
2710maybe_interpret(_, _, _) ->
2711    ok.
2712
2713maybe_interpret1(Suite, all, StepOpts) ->
2714    case i:ii(Suite) of
2715	{module,_} ->
2716	    i:iaa([break]),
2717	    case get_all_testcases(Suite) of
2718		{error,_} ->
2719		    {error,no_testcases_found};
2720		Cases ->
2721		    maybe_interpret2(Suite, Cases, StepOpts)
2722	    end;
2723	error ->
2724	    {error,could_not_interpret_module}
2725    end;
2726maybe_interpret1(Suite, Case, StepOpts) when is_atom(Case) ->
2727    maybe_interpret1(Suite, [Case], StepOpts);
2728maybe_interpret1(Suite, Cases, StepOpts) when is_list(Cases) ->
2729    case i:ii(Suite) of
2730	{module,_} ->
2731	    i:iaa([break]),
2732	    maybe_interpret2(Suite, Cases, StepOpts);
2733	error ->
2734	    {error,could_not_interpret_module}
2735    end.
2736
2737maybe_interpret2(Suite, Cases, StepOpts) ->
2738    set_break_on_config(Suite, StepOpts),
2739    _ = [begin try i:ib(Suite, Case, 1) of
2740	       _ -> ok
2741	   catch
2742	       _:_Error ->
2743		   io:format(user, "Invalid breakpoint: ~w:~tw/1~n",
2744			     [Suite,Case])
2745	   end
2746 	 end || Case <- Cases, is_atom(Case)],
2747    test_server_ctrl:multiply_timetraps(infinity),
2748    WinOp = case lists:member(keep_inactive, ensure_atom(StepOpts)) of
2749		true -> no_kill;
2750		false -> kill
2751	    end,
2752    ct_util:set_testdata({interpret,{{Suite,Cases},WinOp,
2753				     {undefined,undefined}}}),
2754    ok.
2755
2756set_break_on_config(Suite, StepOpts) ->
2757    case lists:member(config, ensure_atom(StepOpts)) of
2758	true ->
2759	    SetBPIfExists = fun(F,A) ->
2760				    case erlang:function_exported(Suite, F, A) of
2761					true -> i:ib(Suite, F, A);
2762					false -> ok
2763				    end
2764			    end,
2765	    ok = SetBPIfExists(init_per_suite, 1),
2766	    ok = SetBPIfExists(init_per_group, 2),
2767	    ok = SetBPIfExists(init_per_testcase, 2),
2768	    ok = SetBPIfExists(end_per_testcase, 2),
2769	    ok = SetBPIfExists(end_per_group, 2),
2770	    ok = SetBPIfExists(end_per_suite, 1);
2771	false ->
2772	    ok
2773    end.
2774
2775maybe_cleanup_interpret(_, undefined) ->
2776    ok;
2777maybe_cleanup_interpret(Suite, _) ->
2778    i:iq(Suite).
2779
2780log_ts_names([]) ->
2781    ok;
2782log_ts_names(Specs) ->
2783    List = lists:map(fun(Name) ->
2784			     Name ++ " "
2785		     end, Specs),
2786    ct_logs:log("Test Specification file(s)", "~ts",
2787		[lists:flatten(List)]).
2788
2789merge_arguments(Args) ->
2790    merge_arguments(Args, []).
2791
2792merge_arguments([LogDir={logdir,_}|Args], Merged) ->
2793    merge_arguments(Args, handle_arg(replace, LogDir, Merged));
2794
2795merge_arguments([CoverFile={cover,_}|Args], Merged) ->
2796    merge_arguments(Args, handle_arg(replace, CoverFile, Merged));
2797
2798merge_arguments([CoverStop={cover_stop,_}|Args], Merged) ->
2799    merge_arguments(Args, handle_arg(replace, CoverStop, Merged));
2800
2801merge_arguments([{'case',TC}|Args], Merged) ->
2802    merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged));
2803
2804merge_arguments([Arg|Args], Merged) ->
2805    merge_arguments(Args, handle_arg(merge, Arg, Merged));
2806
2807merge_arguments([], Merged) ->
2808    Merged.
2809
2810handle_arg(replace, {Key,Elems}, [{Key,_}|Merged]) ->
2811    [{Key,Elems}|Merged];
2812handle_arg(merge, {event_handler_init,Elems}, [{event_handler_init,PrevElems}|Merged]) ->
2813    [{event_handler_init,PrevElems++["add"|Elems]}|Merged];
2814handle_arg(merge, {userconfig,Elems}, [{userconfig,PrevElems}|Merged]) ->
2815    [{userconfig,PrevElems++["add"|Elems]}|Merged];
2816handle_arg(merge, {Key,Elems}, [{Key,PrevElems}|Merged]) ->
2817    [{Key,PrevElems++Elems}|Merged];
2818handle_arg(Op, Arg, [Other|Merged]) ->
2819    [Other|handle_arg(Op, Arg, Merged)];
2820handle_arg(_,Arg,[]) ->
2821    [Arg].
2822
2823get_start_opt(Key, IfExists, Args) ->
2824    get_start_opt(Key, IfExists, undefined, Args).
2825
2826get_start_opt(Key, IfExists, IfNotExists, Args) ->
2827    try try_get_start_opt(Key, IfExists, IfNotExists, Args) of
2828	Result ->
2829	    Result
2830    catch
2831	error:_ ->
2832	    exit({user_error,{bad_argument,Key}})
2833    end.
2834
2835try_get_start_opt(Key, IfExists, IfNotExists, Args) ->
2836    case lists:keysearch(Key, 1, Args) of
2837	{value,{Key,Val}} when is_function(IfExists) ->
2838	    IfExists(Val);
2839	{value,{Key,Val}} when IfExists == value ->
2840	    Val;
2841	{value,{Key,_Val}} ->
2842	    IfExists;
2843	_ ->
2844	    IfNotExists
2845    end.
2846
2847ct_hooks_args2opts(Args) ->
2848    lists:foldl(fun({ct_hooks,Hooks}, Acc) ->
2849			ct_hooks_args2opts(Hooks,Acc);
2850		   (_,Acc) ->
2851			Acc
2852		end,[],Args).
2853
2854ct_hooks_args2opts([CTH,Arg,Prio,"and"| Rest],Acc) when Arg /= "and" ->
2855    ct_hooks_args2opts(Rest,[{list_to_atom(CTH),
2856			      parse_cth_args(Arg),
2857			      parse_cth_args(Prio)}|Acc]);
2858ct_hooks_args2opts([CTH,Arg,"and"| Rest],Acc) ->
2859    ct_hooks_args2opts(Rest,[{list_to_atom(CTH),
2860			      parse_cth_args(Arg)}|Acc]);
2861ct_hooks_args2opts([CTH], Acc) ->
2862    ct_hooks_args2opts([CTH,"and"],Acc);
2863ct_hooks_args2opts([CTH, "and" | Rest], Acc) ->
2864    ct_hooks_args2opts(Rest,[list_to_atom(CTH)|Acc]);
2865ct_hooks_args2opts([CTH, Args], Acc) ->
2866    ct_hooks_args2opts([CTH, Args, "and"],Acc);
2867ct_hooks_args2opts([CTH, Args, Prio], Acc) ->
2868    ct_hooks_args2opts([CTH, Args, Prio, "and"],Acc);
2869ct_hooks_args2opts([],Acc) ->
2870    lists:reverse(Acc).
2871
2872parse_cth_args(String) ->
2873    try
2874	true = io_lib:printable_unicode_list(String),
2875	{ok,Toks,_} = erl_scan:string(String++"."),
2876	{ok, Args} = erl_parse:parse_term(Toks),
2877	Args
2878    catch _:_ ->
2879	    String
2880    end.
2881
2882event_handler_args2opts(Args) ->
2883    case proplists:get_value(event_handler, Args) of
2884	undefined ->
2885	    event_handler_args2opts([], Args);
2886	EHs ->
2887	    event_handler_args2opts([{list_to_atom(EH),[]} || EH <- EHs], Args)
2888    end.
2889event_handler_args2opts(Default, Args) ->
2890    case proplists:get_value(event_handler_init, Args) of
2891	undefined ->
2892	    Default;
2893	EHs ->
2894	    event_handler_init_args2opts(EHs)
2895    end.
2896event_handler_init_args2opts([EH, Arg, "and" | EHs]) ->
2897    [{list_to_atom(EH),lists:flatten(io_lib:format("~ts",[Arg]))} |
2898     event_handler_init_args2opts(EHs)];
2899event_handler_init_args2opts([EH, Arg]) ->
2900    [{list_to_atom(EH),lists:flatten(io_lib:format("~ts",[Arg]))}];
2901event_handler_init_args2opts([]) ->
2902    [].
2903
2904verbosity_args2opts(Args) ->
2905    case proplists:get_value(verbosity, Args) of
2906	undefined ->
2907	    [];
2908	VArgs ->
2909	    GetVLvls =
2910		fun("and", {new,SoFar}) when is_list(SoFar) ->
2911			{new,SoFar};
2912		   ("and", {Lvl,SoFar}) when is_list(SoFar) ->
2913			{new,[{'$unspecified',list_to_integer(Lvl)} | SoFar]};
2914		   (CatOrLvl, {new,SoFar}) when is_list(SoFar) ->
2915			{CatOrLvl,SoFar};
2916		   (Lvl, {Cat,SoFar}) ->
2917			{new,[{list_to_atom(Cat),list_to_integer(Lvl)} | SoFar]}
2918		end,
2919		case lists:foldl(GetVLvls, {new,[]}, VArgs) of
2920		    {new,Parsed} ->
2921			Parsed;
2922		    {Lvl,Parsed} ->
2923			[{'$unspecified',list_to_integer(Lvl)} | Parsed]
2924		end
2925    end.
2926
2927add_verbosity_defaults(VLvls) ->
2928    case {proplists:get_value('$unspecified', VLvls),
2929	  proplists:get_value(default, VLvls)} of
2930	{undefined,undefined} ->
2931	    ?default_verbosity ++ VLvls;
2932	{Lvl,undefined} ->
2933	    [{default,Lvl} | VLvls];
2934	{undefined,_Lvl} ->
2935	    [{'$unspecified',?MAX_VERBOSITY} | VLvls];
2936	_ ->
2937	    VLvls
2938    end.
2939
2940%% This function reads pa and pz arguments, converts dirs from relative
2941%% to absolute, and re-inserts them in the code path. The order of the
2942%% dirs in the code path remain the same. Note however that since this
2943%% function is only used for arguments "pre run_test erl_args", the order
2944%% relative dirs "post run_test erl_args" is not kept!
2945rel_to_abs(CtArgs) ->
2946    {PA,PZ} = get_pa_pz(CtArgs, [], []),
2947    _ = [begin
2948	 Dir = rm_trailing_slash(D),
2949	 Abs = make_abs(Dir),
2950	 _ = if Dir /= Abs ->
2951		 _ = code:del_path(Dir),
2952		 _ = code:del_path(Abs),
2953		 io:format(user, "Converting ~tp to ~tp and re-inserting "
2954			   "with add_pathz/1~n",
2955			   [Dir, Abs]);
2956	    true ->
2957		 _ = code:del_path(Dir)
2958	 end,
2959	 code:add_pathz(Abs)
2960     end || D <- PZ],
2961    _ = [begin
2962	 Dir = rm_trailing_slash(D),
2963	 Abs = make_abs(Dir),
2964	 _ = if Dir /= Abs ->
2965		 _ = code:del_path(Dir),
2966		 _ = code:del_path(Abs),
2967		 io:format(user, "Converting ~tp to ~tp and re-inserting "
2968			   "with add_patha/1~n",
2969			   [Dir, Abs]);
2970	    true ->
2971		 _ = code:del_path(Dir)
2972	 end,
2973	 code:add_patha(Abs)
2974     end || D <- PA],
2975    io:format(user, "~n", []).
2976
2977rm_trailing_slash(Dir) ->
2978    filename:join(filename:split(Dir)).
2979
2980get_pa_pz([{pa,Dirs} | Args], PA, PZ) ->
2981    get_pa_pz(Args, PA ++ Dirs, PZ);
2982get_pa_pz([{pz,Dirs} | Args], PA, PZ) ->
2983    get_pa_pz(Args, PA, PZ ++ Dirs);
2984get_pa_pz([_ | Args], PA, PZ) ->
2985    get_pa_pz(Args, PA, PZ);
2986get_pa_pz([], PA, PZ) ->
2987    {PA,PZ}.
2988
2989make_abs(RelDir) ->
2990    Tokens = filename:split(filename:absname(RelDir)),
2991    filename:join(lists:reverse(make_abs1(Tokens, []))).
2992
2993make_abs1([".."|Dirs], [_Dir|Path]) ->
2994    make_abs1(Dirs, Path);
2995make_abs1(["."|Dirs], Path) ->
2996    make_abs1(Dirs, Path);
2997make_abs1([Dir|Dirs], Path) ->
2998    make_abs1(Dirs, [Dir|Path]);
2999make_abs1([], Path) ->
3000    Path.
3001
3002%% This function translates ct:run_test/1 start options
3003%% to ct_run start arguments (on the init arguments format) -
3004%% this is useful mainly for testing the ct_run start functions.
3005opts2args(EnvStartOpts) ->
3006    lists:flatmap(fun({exit_status,ExitStatusOpt}) when is_atom(ExitStatusOpt) ->
3007			  [{exit_status,[atom_to_list(ExitStatusOpt)]}];
3008		     ({halt_with,{HaltM,HaltF}}) ->
3009			  [{halt_with,[atom_to_list(HaltM),
3010				       atom_to_list(HaltF)]}];
3011		     ({interactive_mode,true}) ->
3012			  [{shell,[]}];
3013		     ({config,CfgFile}) when is_integer(hd(CfgFile)) ->
3014			  [{ct_config,[CfgFile]}];
3015		     ({config,CfgFiles}) when is_list(hd(CfgFiles)) ->
3016			  [{ct_config,CfgFiles}];
3017		     ({userconfig,{CBM,CfgStr=[X|_]}}) when is_integer(X) ->
3018			  [{userconfig,[atom_to_list(CBM),CfgStr]}];
3019		     ({userconfig,{CBM,CfgStrs}}) when is_list(CfgStrs) ->
3020			  [{userconfig,[atom_to_list(CBM) | CfgStrs]}];
3021		     ({userconfig,UserCfg}) when is_list(UserCfg) ->
3022			  Strs =
3023			      lists:map(fun({CBM,CfgStr=[X|_]})
3024					      when is_integer(X) ->
3025						[atom_to_list(CBM),
3026						 CfgStr,"and"];
3027					   ({CBM,CfgStrs})
3028					      when is_list(CfgStrs) ->
3029						[atom_to_list(CBM) | CfgStrs] ++
3030						    ["and"]
3031					end, UserCfg),
3032			  [_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)),
3033			  [{userconfig,lists:reverse(StrsR)}];
3034		     ({group,G}) when is_atom(G) ->
3035			  [{group,[atom_to_list(G)]}];
3036		     ({group,Gs}) when is_list(Gs) ->
3037			  LOfGStrs = [lists:flatten(io_lib:format("~tw",[G])) ||
3038					 G <- Gs],
3039			  [{group,LOfGStrs}];
3040		     ({testcase,Case}) when is_atom(Case) ->
3041			  [{'case',[atom_to_list(Case)]}];
3042		     ({testcase,Cases}) ->
3043			  [{'case',[atom_to_list(C) || C <- Cases]}];
3044		     ({'case',Cases}) ->
3045			  [{'case',[atom_to_list(C) || C <- Cases]}];
3046		     ({allow_user_terms,true}) ->
3047			  [{allow_user_terms,[]}];
3048		     ({allow_user_terms,false}) ->
3049			  [];
3050		     ({join_specs,true}) ->
3051			  [{join_specs,[]}];
3052		     ({join_specs,false}) ->
3053			  [];
3054		     ({auto_compile,false}) ->
3055			  [{no_auto_compile,[]}];
3056		     ({auto_compile,true}) ->
3057			  [];
3058		     ({scale_timetraps,true}) ->
3059			  [{scale_timetraps,[]}];
3060		     ({scale_timetraps,false}) ->
3061			  [];
3062		     ({create_priv_dir,auto_per_run}) ->
3063			  [];
3064		     ({create_priv_dir,PD}) when is_atom(PD) ->
3065			  [{create_priv_dir,[atom_to_list(PD)]}];
3066		     ({force_stop,skip_rest}) ->
3067			  [{force_stop,["skip_rest"]}];
3068		     ({force_stop,true}) ->
3069			  [{force_stop,[]}];
3070		     ({force_stop,false}) ->
3071			  [];
3072		     ({decrypt,{key,Key}}) ->
3073			  [{ct_decrypt_key,[Key]}];
3074		     ({decrypt,{file,File}}) ->
3075			  [{ct_decrypt_file,[File]}];
3076		     ({basic_html,true}) ->
3077			  [{basic_html,[]}];
3078		     ({basic_html,false}) ->
3079			  [];
3080		     ({esc_chars,false}) ->
3081			  [{no_esc_chars,[]}];
3082		     ({esc_chars,true}) ->
3083			  [];
3084		     ({event_handler,EH}) when is_atom(EH) ->
3085			  [{event_handler,[atom_to_list(EH)]}];
3086		     ({event_handler,EHs}) when is_list(EHs) ->
3087			  [{event_handler,[atom_to_list(EH) || EH <- EHs]}];
3088		     ({event_handler,{EH,Arg}}) when is_atom(EH) ->
3089			  ArgStr = lists:flatten(io_lib:format("~tp", [Arg])),
3090			  [{event_handler_init,[atom_to_list(EH),ArgStr]}];
3091		     ({event_handler,{EHs,Arg}}) when is_list(EHs) ->
3092			  ArgStr = lists:flatten(io_lib:format("~tp", [Arg])),
3093			  Strs = lists:flatmap(fun(EH) ->
3094						       [atom_to_list(EH),
3095							ArgStr,"and"]
3096					       end, EHs),
3097			  [_LastAnd | StrsR] = lists:reverse(Strs),
3098			  [{event_handler_init,lists:reverse(StrsR)}];
3099		     ({logopts,LOs}) when is_list(LOs) ->
3100			  [{logopts,[atom_to_list(LO) || LO <- LOs]}];
3101		     ({verbosity,?default_verbosity}) ->
3102			  [];
3103		     ({verbosity,VLvl}) when is_integer(VLvl) ->
3104			  [{verbosity,[integer_to_list(VLvl)]}];
3105		     ({verbosity,VLvls}) when is_list(VLvls) ->
3106			  VLvlArgs =
3107			      lists:flatmap(fun({'$unspecified',Lvl}) ->
3108						    [integer_to_list(Lvl),
3109						     "and"];
3110					       ({Cat,Lvl}) ->
3111						    [atom_to_list(Cat),
3112						     integer_to_list(Lvl),
3113						     "and"];
3114					       (Lvl) ->
3115						    [integer_to_list(Lvl),
3116						     "and"]
3117					    end, VLvls),
3118			  [_LastAnd|VLvlArgsR] = lists:reverse(VLvlArgs),
3119			  [{verbosity,lists:reverse(VLvlArgsR)}];
3120		     ({ct_hooks,[]}) ->
3121			  [];
3122		     ({ct_hooks,CTHs}) when is_list(CTHs) ->
3123			  io:format(user,"ct_hooks: ~tp",[CTHs]),
3124			  Strs = lists:flatmap(
3125				   fun({CTH,Arg,Prio}) ->
3126					   [atom_to_list(CTH),
3127					    lists:flatten(
3128					      io_lib:format("~tp",[Arg])),
3129					    lists:flatten(
3130					      io_lib:format("~tp",[Prio])),
3131					    "and"];
3132				       ({CTH,Arg}) ->
3133					   [atom_to_list(CTH),
3134					    lists:flatten(
3135					      io_lib:format("~tp",[Arg])),
3136					    "and"];
3137				      (CTH) when is_atom(CTH) ->
3138					   [atom_to_list(CTH),"and"]
3139				   end,CTHs),
3140			  [_LastAnd|StrsR] = lists:reverse(Strs),
3141			  io:format(user,"return: ~tp",[lists:reverse(StrsR)]),
3142			  [{ct_hooks,lists:reverse(StrsR)}];
3143		     ({Opt,As=[A|_]}) when is_atom(A) ->
3144			  [{Opt,[atom_to_list(Atom) || Atom <- As]}];
3145		     ({Opt,Strs=[S|_]}) when is_list(S) ->
3146			  [{Opt,Strs}];
3147		     ({Opt,A}) when is_atom(A) ->
3148			  [{Opt,[atom_to_list(A)]}];
3149		     ({Opt,I}) when is_integer(I) ->
3150			  [{Opt,[integer_to_list(I)]}];
3151		     ({Opt,I}) when is_float(I) ->
3152			  [{Opt,[float_to_list(I)]}];
3153		     ({Opt,S}) when is_list(S) ->
3154			  [{Opt,[S]}];
3155		     (Opt) ->
3156			  Opt
3157		  end, EnvStartOpts).
3158
3159locate_test_dir(Dir, Suite) ->
3160    TestDir = case ct_util:is_test_dir(Dir) of
3161		  true  -> Dir;
3162		  false -> ct_util:get_testdir(Dir, Suite)
3163	      end,
3164    case filelib:is_dir(TestDir) of
3165	true  -> {ok,TestDir};
3166	false -> {error,invalid}
3167    end.
3168
3169is_suite(Mod) when is_atom(Mod) ->
3170    is_suite(atom_to_list(Mod));
3171is_suite(ModOrFile) when is_list(ModOrFile) ->
3172    case lists:reverse(filename:basename(ModOrFile, ".erl")) of
3173	[$E,$T,$I,$U,$S,$_|_] ->
3174	    true;
3175	_ ->
3176	    case lists:reverse(filename:basename(ModOrFile, ".beam")) of
3177		[$E,$T,$I,$U,$S,$_|_] ->
3178		    true;
3179		_ ->
3180		    false
3181	    end
3182    end.
3183
3184get_all_testcases(Suite) ->
3185    try ct_framework:get_all_cases(Suite) of
3186	{error,_Reason} = Error ->
3187	    Error;
3188	SuiteCases ->
3189	    Cases = [C || {_S,C} <- SuiteCases],
3190	    try Suite:sequences() of
3191		[] ->
3192		    Cases;
3193		Seqs ->
3194		    TCs1 = lists:flatten([TCs || {_,TCs} <- Seqs]),
3195		    lists:reverse(
3196		      lists:foldl(fun(TC, Acc) ->
3197					  case lists:member(TC, Acc) of
3198					      true  -> Acc;
3199					      false -> [TC | Acc]
3200					  end
3201				  end, [], Cases ++ TCs1))
3202	    catch
3203		_:_ ->
3204		    Cases
3205	    end
3206    catch
3207	_:Error ->
3208	    {error,Error}
3209    end.
3210
3211%% Internal tracing support. If {ct_trace,TraceSpec} is present, the
3212%% TraceSpec file will be consulted and dbg used to trace function
3213%% calls during test run. Expected terms in TraceSpec:
3214%% {m,Mod} or {f,Mod,Func}.
3215start_trace(Args) ->
3216    case lists:keysearch(ct_trace,1,Args) of
3217	{value,{ct_trace,File}} ->
3218	    TraceSpec = delistify(File),
3219	    case file:consult(TraceSpec) of
3220		{ok,Terms} ->
3221		    case catch do_trace(Terms) of
3222			ok ->
3223			    true;
3224			{_,Error} ->
3225			    io:format("Warning! Tracing not started. Reason: ~tp~n~n",
3226				      [Error]),
3227			    false
3228		    end;
3229		{_,Error} ->
3230		    io:format("Warning! Tracing not started. Reason: ~ts~n~n",
3231			      [file:format_error(Error)]),
3232		    false
3233	    end;
3234	false ->
3235	    false
3236    end.
3237
3238do_trace(Terms) ->
3239    dbg:tracer(),
3240    dbg:p(self(), [sos,call]),
3241    lists:foreach(fun({m,M}) ->
3242			  case dbg:tpl(M,x) of
3243			      {error,What} -> exit({error,{tracing_failed,What}});
3244			      _ -> ok
3245			  end;
3246		     ({me,M}) ->
3247			  case dbg:tp(M,[{'_',[],[{exception_trace},
3248						  {message,{caller}}]}]) of
3249			      {error,What} -> exit({error,{tracing_failed,What}});
3250			      _ -> ok
3251			  end;
3252		     ({f,M,F}) ->
3253			  case dbg:tpl(M,F,[{'_',[],[{exception_trace},
3254						     {message,{caller}}]}]) of
3255			      {error,What} -> exit({error,{tracing_failed,What}});
3256			      _ -> ok
3257			  end;
3258		     (Huh) ->
3259			  exit({error,{unrecognized_trace_term,Huh}})
3260		  end, Terms),
3261    ok.
3262
3263stop_trace(true) ->
3264    dbg:stop_clear();
3265stop_trace(false) ->
3266    ok.
3267
3268list_to_number(S) ->
3269    try list_to_integer(S)
3270    catch error:badarg -> list_to_float(S)
3271    end.
3272
3273ensure_atom(Atom) when is_atom(Atom) ->
3274    Atom;
3275ensure_atom(String) when is_list(String), is_integer(hd(String)) ->
3276    list_to_atom(String);
3277ensure_atom(List) when is_list(List) ->
3278    [ensure_atom(Item) || Item <- List];
3279ensure_atom(Other) ->
3280    Other.
3281