64
64
{false, fun run_interactive/3}
66
HandleTopcase = case member(keep_topcase, Options) of
67
true -> [fun copy_topcase/3];
68
false -> [fun remove_original_topcase/3,
71
MakefileHooks = [fun make_make/3,
72
fun add_make_testcase/3],
73
MakeLoop = fun(V, Sp, St) -> make_loop(MakefileHooks, V, Sp, St) end,
74
66
Hooks = [fun init_state/3,
75
fun read_spec_file/3] ++
80
fun make_test_suite/3,
81
fun add_topcase_to_spec/3,
82
fun write_spec_file/3,
83
68
fun make_command/3,
85
Args = make_test_server_args(Args0,Options,Vars),
70
Args = make_common_test_args(Args0,Options,Vars),
86
71
St = #state{file=File,test_server_args=Args,batch=Batch},
87
72
R = execute(Hooks, Vars, [], St),
89
true -> ts_reports:make_index();
90
false -> ok % ts_reports:make_index() is run on the test_server node
97
make_loop(Hooks, Vars0, Spec0, St0) ->
98
case St0#state.makefiles of
100
case execute(Hooks, Vars0, Spec0, St0#state{makefile=Makefile}) of
103
{ok, Vars, Spec, St} ->
104
make_loop(Hooks, Vars, Spec, St#state{makefiles=Rest})
107
{ok, Vars0, Spec0, St0}
110
78
execute([Hook|Rest], Vars0, Spec0, St0) ->
111
79
case Hook(Vars0, Spec0, St0) of
157
125
{error,{no_test_directory,TestDir}}
160
%% Read the spec file for the test suite.
162
read_spec_file(Vars, _, St) ->
163
TestDir = St#state.test_dir,
164
File = St#state.file,
165
{SpecFile,Res} = get_spec_filename(Vars, TestDir, File),
169
{error,Atom} when is_atom(Atom) ->
170
{error,{no_spec,SpecFile}};
172
{error,{bad_spec,lists:flatten(file:format_error(Reason))}}
175
get_spec_filename(Vars, TestDir, File) ->
176
DynSpec = filename:join(TestDir, File ++ ".dynspec"),
177
case filelib:is_file(DynSpec) of
179
Bs0 = erl_eval:new_bindings(),
180
Bs1 = erl_eval:add_binding('Target', ts_lib:var(target, Vars), Bs0),
181
Bs2 = erl_eval:add_binding('Os', ts_lib:var(os, Vars), Bs1),
182
TCCStr = ts_lib:var(test_c_compiler, Vars),
184
{ok, Toks, _} = erl_scan:string(TCCStr ++ "."),
185
{ok, Tcc} = erl_parse:parse_term(Toks),
190
Bs = erl_eval:add_binding('TestCCompiler', TCC, Bs2),
191
{DynSpec,file:script(DynSpec, Bs)};
193
SpecFile = get_spec_filename_1(Vars, TestDir, File),
194
{SpecFile,file:consult(SpecFile)}
197
get_spec_filename_1(Vars, TestDir, File) ->
198
case ts_lib:var(os, Vars) of
200
check_spec_filename(TestDir, File, ".spec.vxworks");
202
check_spec_filename(TestDir, File, ".spec.ose");
204
check_spec_filename(TestDir, File, ".spec.win");
206
filename:join(TestDir, File ++ ".spec")
209
check_spec_filename(TestDir, File, Ext) ->
210
Spec = filename:join(TestDir, File ++ Ext),
211
case filelib:is_file(Spec) of
213
false -> filename:join(TestDir, File ++ ".spec")
216
%% Remove the top case from the spec file. We will add our own
219
remove_original_topcase(Vars, Spec, St) ->
220
{ok,Vars,filter(fun ({topcase,_}) -> false;
221
(_Other) -> true end, Spec),St}.
223
%% Initialize our new top case. We'll keep in it the state to be
224
%% able to add more to it.
226
init_topcase(Vars, Spec, St) ->
227
TestDir = St#state.test_dir,
230
Mod when is_atom(Mod) ->
231
ModStr = atom_to_list(Mod),
232
case filelib:is_file(filename:join(TestDir,ModStr++".erl")) of
235
Wc = filename:join(TestDir, ModStr ++ "*_SUITE.erl"),
236
[{list_to_atom(filename:basename(M, ".erl")),all} ||
237
M <- filelib:wildcard(Wc)]
240
%% Here we used to return {dir,TestDir}. Now we instead
241
%% list all suites in TestDir, so we can add make testcases
242
%% around it later (see add_make_testcase) without getting
243
%% duplicates of the suite. (test_server_ctrl does no longer
244
%% check for duplicates of testcases)
245
Wc = filename:join(TestDir, "*_SUITE.erl"),
246
[{list_to_atom(filename:basename(M, ".erl")),all} ||
247
M <- filelib:wildcard(Wc)]
249
{ok,Vars,Spec,St#state{topcase=TopCase}}.
251
%% Or if option keep_topcase was given, eh... keep the topcase
252
copy_topcase(Vars, Spec, St) ->
253
{value,{topcase,Tc}} = lists:keysearch(topcase,1,Spec),
254
{ok, Vars, lists:keydelete(topcase,1,Spec),St#state{topcase=Tc}}.
257
128
%% Run any "Makefile.first" files first.
258
129
%% XXX We should fake a failing test case if the make fails.
281
152
{error,_Reason}=Error -> Error
284
%% Search for `Makefile.src' in each *_SUITE_data directory.
286
find_makefiles(Vars, Spec, St) ->
287
Wc = filename:join(St#state.data_wc, "Makefile.src"),
288
Makefiles = reverse(del_skipped_suite_data_dir(filelib:wildcard(Wc), Spec)),
289
{ok,Vars,Spec,St#state{makefiles=Makefiles}}.
291
%% Create "Makefile" from "Makefile.src".
293
make_make(Vars, Spec, State) ->
294
Src = State#state.makefile,
295
Dest = filename:rootname(Src),
296
ts_lib:progress(Vars, 1, "Making ~s...\n", [Dest]),
297
case ts_lib:subst_file(Src, Dest, Vars) of
299
{ok, Vars, Spec, State#state{makefile=Dest}};
301
{error, {Src, Reason}}
304
%% Add a testcase which will do the making of the stuff in the data directory.
306
add_make_testcase(Vars, Spec, St) ->
307
Makefile = St#state.makefile,
308
Dir = filename:dirname(Makefile),
309
case ts_lib:var(os, Vars) of
311
%% For OSE, C code in datadir must be linked in the image file,
312
%% and erlang code is sent as binaries from test_server_ctrl
313
%% Making erlang code here because the Makefile.src probably won't
315
Erl_flags=[{i, "../../test_server"}|ts_lib:var(erl_flags,Vars)],
316
{ok, Cwd} = file:get_cwd(),
317
ok = file:set_cwd(Dir),
318
Result = (catch make:all(Erl_flags)),
319
ok = file:set_cwd(Cwd),
321
up_to_date -> {ok, Vars, Spec, St};
322
_error -> {error, {erlang_make_failed,Dir}}
325
Shortname = filename:basename(Makefile),
326
Suite = filename:basename(Dir, "_data"),
327
Config = [{data_dir,Dir},{makefile,Shortname}],
328
MakeModule = Suite ++ "_make",
329
MakeModuleSrc = filename:join(filename:dirname(Dir),
330
MakeModule ++ ".erl"),
331
MakeMod = list_to_atom(MakeModule),
332
case filelib:is_file(MakeModuleSrc) of
334
false -> generate_make_module(ts_lib:var(make_command, Vars),
340
{ok,Vars,Spec,St#state{all={MakeMod,Config}}};
342
%% Avoid duplicates of testcases. There is no longer
343
%% a check for this in test_server_ctrl.
344
TestCase = {list_to_atom(Suite),all},
345
TopCase0 = case St#state.topcase of
346
List when is_list(List) ->
351
TopCase = [{make,{MakeMod,make,[Config]},
353
{MakeMod,unmake,[Config]}}|TopCase0],
354
{ok,Vars,Spec,St#state{topcase=TopCase}}
358
generate_make_module(MakeCmd, Name, ModuleString) ->
359
{ok,Host} = inet:gethostname(),
360
file:write_file(Name,
361
["-module(",ModuleString,").\n",
363
"-export([make/1,unmake/1]).\n",
365
"make(Config) when is_list(Config) ->\n",
366
" Mins = " ++ integer_to_list(?DEFAULT_MAKE_TIMETRAP_MINUTES) ++ ",\n"
367
" test_server:format(\"=== Setting timetrap to ~p minutes ===~n\", [Mins]),\n"
368
" TimeTrap = test_server:timetrap(test_server:minutes(Mins)),\n"
369
" Res = ts_make:make([{make_command, \""++MakeCmd++"\"},{cross_node,\'ts@" ++ Host ++ "\'}|Config]),\n",
370
" test_server:timetrap_cancel(TimeTrap),\n"
373
"unmake(Config) when is_list(Config) ->\n",
374
" Mins = " ++ integer_to_list(?DEFAULT_UNMAKE_TIMETRAP_MINUTES) ++ ",\n"
375
" test_server:format(\"=== Setting timetrap to ~p minutes ===~n\", [Mins]),\n"
376
" TimeTrap = test_server:timetrap(test_server:minutes(Mins)),\n"
377
" Res = ts_make:unmake([{make_command, \""++MakeCmd++"\"}|Config]),\n"
378
" test_server:timetrap_cancel(TimeTrap),\n"
383
make_test_suite(Vars, _Spec, State) ->
384
TestDir = State#state.test_dir,
386
Erl_flags=[{i, "../test_server"}|ts_lib:var(erl_flags,Vars)],
388
case code:is_loaded(test_server_line) of
389
false -> code:load_file(test_server_line);
393
{ok, Cwd} = file:get_cwd(),
394
ok = file:set_cwd(TestDir),
395
Result = (catch make:all(Erl_flags)),
396
ok = file:set_cwd(Cwd),
401
%% If I return an error here, the test will be stopped
402
%% and it will not show up in the top index page. Instead
403
%% I return ok - the test will run for all existing suites.
404
%% It might be that there are old suites that are run, but
405
%% at least one suite is missing, and that is reported on the
407
io:format("~s: {error,{make_crashed,~p}\n",
408
[State#state.file,Reason]),
412
io:format("~s: {error,make_of_test_suite_failed}\n",
417
%% Add topcase to spec.
419
add_topcase_to_spec(Vars, Spec, St) ->
420
Tc = case St#state.all of
422
[{make,{MakeMod,make,[Config]},
424
{MakeMod,unmake,[Config]}}];
425
undefined -> St#state.topcase
427
{ok,Vars,Spec++[{topcase,Tc}],St}.
429
%% Writes the (possibly transformed) spec file.
431
write_spec_file(Vars, Spec, _State) ->
432
F = fun(Term) -> io_lib:format("~p.~n", [Term]) end,
433
SpecFile = map(F, Spec),
435
case lists:keysearch(hosts, 1, Vars) of
438
{value, {hosts, HostList}} ->
439
io_lib:format("{hosts,~p}.~n",[HostList])
442
case lists:keysearch(diskless, 1, Vars) of
445
{value, {diskless, How}} ->
446
io_lib:format("{diskless, ~p}.~n",[How])
448
Conf = consult_config(),
449
MoreConfig = io_lib:format("~p.\n", [{config,Conf}]),
450
file:write_file("current.spec", [DiskLess,Hosts,MoreConfig,SpecFile]).
453
{ok,Conf} = file:consult("ts.config"),
455
{unix,_} -> consult_config("ts.unix.config", Conf);
456
{win32,_} -> consult_config("ts.win32.config", Conf);
457
vxworks -> consult_config("ts.vxworks.config", Conf);
461
consult_config(File, Conf0) ->
462
case file:consult(File) of
463
{ok,Conf} -> Conf++Conf0;
464
{error,enoent} -> Conf0
155
get_config_files() ->
156
TSConfig = "ts.config",
157
[TSConfig | case os:type() of
158
{unix,_} -> ["ts.unix.config"];
159
{win32,_} -> ["ts.win32.config"];
160
vxworks -> ["ts.vxworks.config"];
467
164
%% Makes the command to start up the Erlang node to run the tests.
626
make_test_server_args(Args0,Options,Vars) ->
628
case ts_lib:var(os, Vars) of
630
F = write_parameterfile(vxworks,Vars),
633
F = write_parameterfile(ose,Vars),
330
make_common_test_args(Args0, Options, _Vars) ->
639
332
case lists:keysearch(trace,1,Options) of
640
333
{value,{trace,TI}} when is_tuple(TI); is_tuple(hd(TI)) ->
641
334
ok = file:write_file(?tracefile,io_lib:format("~p.~n",[TI])),
642
" TRACE " ++ ?tracefile;
335
[{ct_trace,?tracefile}];
643
336
{value,{trace,TIFile}} when is_atom(TIFile) ->
644
" TRACE " ++ atom_to_list(TIFile);
337
[{ct_trace,atom_to_list(TIFile)}];
645
338
{value,{trace,TIFile}} ->
651
344
case lists:keysearch(cover,1,Options) of
652
{value,{cover,App,File,Analyse}} ->
653
" COVER " ++ to_list(App) ++ " " ++ to_list(File) ++ " " ++
345
{value,{cover, App, none, _Analyse}} ->
346
io:format("No cover file found for ~p~n",[App]),
348
{value,{cover,_App,File,_Analyse}} ->
349
[{cover,to_list(File)}];
659
case ts_lib:var(ts_testcase_callback, Vars) of
663
io:format("Function ~w:~w/4 will be called before and "
664
"after each test case.\n", [Mod,Func]),
665
" TESTCASE_CALLBACK " ++ to_list(Mod) ++ " " ++ to_list(Func);
666
ModFunc when is_list(ModFunc) ->
667
[Mod,Func]=string:tokens(ModFunc," "),
668
io:format("Function ~s:~s/4 will be called before and "
669
"after each test case.\n", [Mod,Func]),
670
" TESTCASE_CALLBACK " ++ ModFunc;
674
Args0 ++ Parameters ++ Trace ++ Cover ++ TCCallback.
354
Logdir = case lists:keysearch(logdir, 1, Options) of
355
{value,{logdir, _}} ->
358
[{logdir,"../test_server"}]
361
ConfigPath = case {os:getenv("TEST_CONFIG_PATH"),
362
lists:keysearch(config, 1, Options)} of
363
{false,{value, {config, Path}}} ->
370
ConfigFiles = [{config,[filename:join(ConfigPath,File)
371
|| File <- get_config_files()]}],
373
io_lib:format("~100000p",[Args0++Trace++Cover++Logdir++
374
ConfigFiles++Options]).
676
376
to_list(X) when is_atom(X) ->
678
378
to_list(X) when is_list(X) ->
681
write_parameterfile(Type,Vars) ->
682
Cross_host = ts_lib:var(target_host, Vars),
683
SlaveTargets = case lists:keysearch(slavetargets,1,Vars) of
689
Master = case lists:keysearch(master,1,Vars) of
693
ToWrite = [{type,Type},
694
{target, list_to_atom(Cross_host)}] ++ SlaveTargets ++ Master,
696
Crossfile = atom_to_list(Type) ++ "parameters" ++ os:getpid(),
697
ok = file:write_file(Crossfile,io_lib:format("~p.~n", [ToWrite])),
701
382
%% Paths and spaces handling for w2k and XP