1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22%%% Author: Hakan Mattsson  hakan@erix.ericsson.se
23%%% Purpose: Test case support library
24%%%
25%%% This test suite may be run as a part of the Grand Test Suite
26%%% of Erlang.  The Mnesia test suite is structured in a hierarchy.
27%%% Each test case is implemented as an exported function with arity 1.
28%%% Test case identifiers must have the following syntax: {Module, Function}.
29%%%
30%%% The driver of the test suite runs in two passes as follows:
31%%% first the test case function is invoked with the atom 'suite' as
32%%% single argument. The returned value is treated as a list of sub
33%%% test cases. If the list of sub test cases is [] the test case
34%%% function is invoked again, this time with a list of nodes as
35%%% argument. If the list of sub test cases is not empty, the test
36%%% case driver applies the algorithm recursively on each element
37%%% in the list.
38%%%
39%%% All test cases are written in such a manner
40%%% that they start to invoke ?acquire_nodes(X, Config)
41%%% in order to prepare the test case execution. When that is
42%%% done, the test machinery ensures that at least X number
43%%% of nodes are connected to each other. If too few nodes was
44%%% specified in the Config, the test case is skipped. If there
45%%% was enough node names in the Config, X of them are selected
46%%% and if some of them happens to be down they are restarted
47%%% via the slave module. When all nodes are up and running a
48%%% disk resident schema is created on all nodes and Mnesia is
49%%% started a on all nodes. This means that all test cases may
50%%% assume that Mnesia is up and running on all acquired nodes.
51%%%
52%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
53%%%
54%%% doc(TestCases)
55%%%
56%%%    Generates a test spec from parts of the test case structure
57%%%
58%%% struct(TestCases)
59%%%
60%%%    Prints out the test case structure
61%%%
62%%% test(TestCases)
63%%%
64%%%    Run parts of the test suite. Uses test/2.
65%%%    Reads Config from mnesia_test.config and starts them if neccessary.
66%%%    Kills Mnesia and wipes out the Mnesia directories as a starter.
67%%%
68%%% test(TestCases, Config)
69%%%
70%%%    Run parts of the test suite on the given Nodes,
71%%%    assuming that the nodes are up and running.
72%%%    Kills Mnesia and wipes out the Mnesia directories as a starter.
73%%%
74%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
75
76-module(mnesia_test_lib).
77-author('hakan@erix.ericsson.se').
78-export([
79	 log/2,
80	 log/4,
81	 verbose/4,
82	 default_config/0,
83	 diskless/1,
84	 eval_test_case/3,
85	 test_driver/2,
86	 test_case_evaluator/3,
87	 activity_evaluator/1,
88	 flush/0,
89	 pick_msg/0,
90	 start_activities/1,
91	 start_transactions/1,
92	 start_transactions/2,
93	 start_sync_transactions/1,
94	 start_sync_transactions/2,
95	 sync_trans_tid_serial/1,
96	 prepare_test_case/5,
97	 select_nodes/4,
98	 init_nodes/3,
99	 error/4,
100	 slave_start_link/0,
101	 slave_start_link/1,
102	 slave_sup/0,
103
104	 start_mnesia/1,
105	 start_mnesia/2,
106	 start_appls/2,
107	 start_appls/3,
108	 start_wait/2,
109	 storage_type/2,
110	 stop_mnesia/1,
111	 stop_appls/2,
112	 sort/1,
113	 kill_mnesia/1,
114	 kill_appls/2,
115	 verify_mnesia/4,
116	 shutdown/0,
117	 verify_replica_location/5,
118	 lookup_config/2,
119	 sync_tables/2,
120	 remote_start/3,
121	 remote_stop/1,
122	 remote_kill/1,
123
124	 reload_appls/2,
125
126	 remote_activate_debug_fun/6,
127	 do_remote_activate_debug_fun/6,
128
129	 test/1,
130	 test/2,
131	 doc/1,
132	 struct/1,
133	 init_per_testcase/2,
134	 end_per_testcase/2,
135	 kill_tc/2
136	]).
137
138-include("mnesia_test_lib.hrl").
139
140%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
141
142%% included for test server compatibility
143%% assume that all test cases only takes Config as sole argument
144init_per_testcase(_Func, Config) ->
145    Env = application:get_all_env(mnesia),
146    [application:unset_env(mnesia, Key, [{timeout, infinity}]) ||
147	{Key, _} <- Env, Key /= included_applications],
148    global:register_name(mnesia_global_logger, group_leader()),
149    Config.
150
151end_per_testcase(_Func, Config) ->
152    global:unregister_name(mnesia_global_logger),
153    %% Nodes = select_nodes(all, Config, ?FILE, ?LINE),
154    %% rpc:multicall(Nodes, mnesia, lkill, []),
155    Config.
156
157%% Use ?log(Format, Args) as wrapper
158log(Format, Args, LongFile, Line) ->
159    File = filename:basename(LongFile),
160    Format2 = lists:concat([File, "(", Line, ")", ": ", Format]),
161    log(Format2, Args).
162
163log(Format, Args) ->
164    case global:whereis_name(mnesia_global_logger) of
165	undefined ->
166	    io:format(user, Format, Args);
167	Pid ->
168	    io:format(Pid, Format, Args)
169    end.
170
171verbose(Format, Args, File, Line) ->
172    Arg = mnesia_test_verbose,
173    case get(Arg) of
174	false ->
175	    ok;
176	true ->
177	    log(Format, Args, File, Line);
178	undefined ->
179	    case init:get_argument(Arg) of
180		{ok, List} when is_list(List) ->
181		    case lists:last(List) of
182			["true"] ->
183			    put(Arg, true),
184			    log(Format, Args, File, Line);
185			_ ->
186			    put(Arg, false),
187			    ok
188		    end;
189		_ ->
190		    put(Arg, false),
191		    ok
192	    end
193    end.
194
195-record('REASON', {file, line, desc}).
196
197error(Format, Args, File, Line) ->
198    global:send(mnesia_global_logger, {failed, File, Line}),
199    Fail = #'REASON'{file = filename:basename(File),
200		     line = Line,
201		     desc = Args},
202    case global:whereis_name(mnesia_test_case_sup) of
203	undefined ->
204	    ignore;
205	Pid ->
206	    Pid ! Fail
207%% 	    global:send(mnesia_test_case_sup, Fail),
208    end,
209    log("<>ERROR<>~n" ++ Format, Args, File, Line).
210
211storage_type(Default, Config) ->
212    case diskless(Config) of
213	true ->
214	    ram_copies;
215	false ->
216	    Default
217    end.
218
219%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
220
221default_config() ->
222    [{nodes, default_nodes()}].
223
224default_nodes() ->
225    mk_nodes(3, []).
226
227mk_nodes(0, Nodes) ->
228    Nodes;
229mk_nodes(N, []) ->
230    mk_nodes(N - 1, [node()]);
231mk_nodes(N, Nodes) when N > 0 ->
232    Head = hd(Nodes),
233    [Name, Host] = node_to_name_and_host(Head),
234    Nodes ++ [mk_node(I, Name, Host) || I <- lists:seq(1, N)].
235
236mk_node(N, Name, Host) ->
237    list_to_atom(lists:concat([Name ++ integer_to_list(N) ++ "@" ++ Host])).
238
239slave_start_link() ->
240    slave_start_link(node()).
241
242slave_start_link(Node) ->
243    [Local, Host] = node_to_name_and_host(Node),
244    Count = erlang:unique_integer([positive]),
245    List = [Local, "_", Count],
246    Name = list_to_atom(lists:concat(List)),
247    slave_start_link(list_to_atom(Host), Name).
248
249slave_start_link(Host, Name) ->
250    slave_start_link(Host, Name, 10).
251
252slave_start_link(Host, Name, Retries) ->
253    Debug = atom_to_list(mnesia:system_info(debug)),
254    Args = "-mnesia debug " ++ Debug ++
255	" -pa " ++
256	filename:dirname(code:which(?MODULE)) ++
257	" -pa " ++
258	filename:dirname(code:which(mnesia)),
259    case starter(Host, Name, Args) of
260	{ok, NewNode} ->
261	    ?match(pong, net_adm:ping(NewNode)),
262	    {ok, Cwd} = file:get_cwd(),
263	    Path = code:get_path(),
264	    ok = rpc:call(NewNode, file, set_cwd, [Cwd]),
265	    true = rpc:call(NewNode, code, set_path, [Path]),
266	    ok = rpc:call(NewNode, error_logger, tty, [false]),
267	    spawn_link(NewNode, ?MODULE, slave_sup, []),
268	    rpc:multicall([node() | nodes()], global, sync, []),
269	    {ok, NewNode};
270	{error, Reason} when Retries == 0->
271	    {error, Reason};
272	{error, Reason} ->
273	    io:format("Could not start slavenode ~p ~p retrying~n",
274		      [{Host, Name, Args}, Reason]),
275	    timer:sleep(500),
276	    slave_start_link(Host, Name, Retries - 1)
277    end.
278
279starter(Host, Name, Args) ->
280    slave:start(Host, Name, Args).
281
282slave_sup() ->
283    process_flag(trap_exit, true),
284    receive
285	{'EXIT', _, _} ->
286	    ignore
287    end.
288
289%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
290%% Index the test case structure
291
292doc(TestCases) when is_list(TestCases) ->
293    test(TestCases, suite),
294    SuiteFname = "index.html",
295    io:format("Generating HTML test specification to file: ~s~n",
296	      [SuiteFname]),
297    {ok, Fd} = file:open(SuiteFname, [write]),
298    io:format(Fd, "<TITLE>Test specification for ~p</TITLE>.~n", [TestCases]),
299    io:format(Fd, "<H1>Test specification for ~p</H1>~n", [TestCases]),
300    io:format(Fd, "Test cases which not are implemented yet are written in <B>bold face</B>.~n~n", []),
301
302    io:format(Fd, "<BR><BR>~n", []),
303    io:format(Fd, "~n<DL>~n", []),
304    do_doc(Fd, TestCases, []),
305    io:format(Fd, "</DL>~n", []),
306    file:close(Fd);
307doc(TestCases) ->
308    doc([TestCases]).
309
310do_doc(Fd, [H | T], List) ->
311    case H of
312	{Module, TestCase} when is_atom(Module), is_atom(TestCase) ->
313	    do_doc(Fd, Module, TestCase, List);
314	TestCase when is_atom(TestCase), List == [] ->
315	    do_doc(Fd, mnesia_SUITE, TestCase, List);
316	TestCase when is_atom(TestCase) ->
317	    do_doc(Fd, hd(List), TestCase, List)
318    end,
319    do_doc(Fd, T, List);
320do_doc(_, [], _) ->
321    ok.
322
323do_doc(Fd, Module, TestCase, List) ->
324    case get_suite(Module, TestCase) of
325	[] ->
326	    %% Implemented leaf test case
327	    Head = ?flat_format("<A HREF=~p.html#~p_1>{~p, ~p}</A>}",
328				[Module, TestCase, Module, TestCase]),
329	    print_doc(Fd, Module, TestCase, Head);
330	Suite when is_list(Suite) ->
331	    %% Test suite
332	    Head = ?flat_format("{~p, ~p}", [Module, TestCase]),
333	    print_doc(Fd, Module, TestCase, Head),
334	    io:format(Fd, "~n<DL>~n", []),
335	    do_doc(Fd, Suite, [Module | List]),
336	    io:format(Fd, "</DL>~n", []);
337	'NYI' ->
338	    %% Not yet implemented
339	    Head = ?flat_format("<B>{~p, ~p}</B>", [Module, TestCase]),
340	    print_doc(Fd, Module, TestCase, Head)
341    end.
342
343print_doc(Fd, Mod, Fun, Head) ->
344    case catch (apply(Mod, Fun, [doc])) of
345	{'EXIT', _} ->
346	    io:format(Fd, "<DT>~s</DT>~n", [Head]);
347	Doc when is_list(Doc) ->
348	    io:format(Fd, "<DT><U>~s</U><BR><DD>~n", [Head]),
349	    print_rows(Fd, Doc),
350	    io:format(Fd, "</DD><BR><BR>~n", [])
351    end.
352
353print_rows(_Fd, []) ->
354    ok;
355print_rows(Fd, [H | T]) when is_list(H) ->
356    io:format(Fd, "~s~n", [H]),
357    print_rows(Fd, T);
358print_rows(Fd, [H | T]) when is_integer(H) ->
359    io:format(Fd, "~s~n", [[H | T]]).
360
361%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
362%% Show the test case structure
363
364struct(TestCases) ->
365    T = test(TestCases, suite),
366    struct(T, "").
367
368struct({Module, TestCase}, Indentation)
369        when is_atom(Module), is_atom(TestCase) ->
370    log("~s{~p, ~p} ...~n", [Indentation, Module, TestCase]);
371struct({Module, TestCase, Other}, Indentation)
372        when is_atom(Module), is_atom(TestCase) ->
373    log("~s{~p, ~p} ~p~n", [Indentation, Module, TestCase, Other]);
374struct([], _) ->
375    ok;
376struct([TestCase | TestCases], Indentation) ->
377    struct(TestCase, Indentation),
378    struct(TestCases, Indentation);
379struct({TestCase, []}, Indentation) ->
380    struct(TestCase, Indentation);
381struct({TestCase, SubTestCases}, Indentation) when is_list(SubTestCases) ->
382    struct(TestCase, Indentation),
383    struct(SubTestCases, Indentation ++ "  ").
384
385%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
386%% Execute the test cases
387
388test(TestCases) ->
389    test(TestCases, []).
390
391test(TestCases, suite) when is_list(TestCases) ->
392    test_driver(TestCases, suite);
393test(TestCases, Config) when is_list(TestCases) ->
394    D1 = lists:duplicate(10, $=),
395    D2 =  lists:duplicate(10, $ ),
396    log("~n~s TEST CASES: ~p~n ~sCONFIG: ~p~n~n", [D1, TestCases, D2, Config]),
397    test_driver(TestCases, Config);
398test(TestCase, Config) ->
399    test([TestCase], Config).
400
401test_driver([], _Config) ->
402    [];
403test_driver([T|TestCases], Config) ->
404    L1 = test_driver(T, Config),
405    L2 = test_driver(TestCases, Config),
406    [L1|L2];
407test_driver({Module, TestCases}, Config) when is_list(TestCases)->
408    test_driver(default_module(Module, TestCases), Config);
409test_driver({Module, all}, Config) ->
410    get_suite(Module, all, Config);
411test_driver({Module, G={group, _}}, Config) ->
412    get_suite(Module, G, Config);
413test_driver({_, {group, Module, Group}}, Config) ->
414    get_suite(Module, {group, Group}, Config);
415
416test_driver({Module, TestCase}, Config) ->
417    Sec = timer:seconds(1) * 1000,
418    case Config of
419	suite ->
420	    {Module, TestCase, 'IMPL'};
421	_ ->
422	    log("Eval test case: ~w~n", [{Module, TestCase}]),
423	    try timer:tc(?MODULE, eval_test_case, [Module, TestCase, Config]) of
424		{T, Res} ->
425		    log("Tested ~w in ~w sec~n", [TestCase, T div Sec]),
426		    {T div Sec, Res}
427	    catch error:function_clause ->
428		    log("<WARNING> Test case ~w NYI~n", [{Module, TestCase}]),
429		    {0, {skip, {Module, TestCase}, "NYI"}}
430	    end
431    end;
432test_driver(TestCase, Config) ->
433    DefaultModule = mnesia_SUITE,
434    log("<>WARNING<> Missing module in test case identifier. "
435	"{~w, ~w} assumed~n", [DefaultModule, TestCase]),
436    test_driver({DefaultModule, TestCase}, Config).
437
438default_module(DefaultModule, TestCases) when is_list(TestCases) ->
439    Fun = fun(T) ->
440		  case T of
441		      {group, _} -> {true, {DefaultModule, T}};
442		      {_, _} -> true;
443		      T -> {true, {DefaultModule, T}}
444		  end
445	  end,
446    lists:zf(Fun, TestCases).
447
448get_suite(Module, TestCase, Config) ->
449    case get_suite(Module, TestCase) of
450	Suite when is_list(Suite), Config == suite ->
451	    Res = test_driver(default_module(Module, Suite), Config),
452	    {{Module, TestCase}, Res};
453	Suite when is_list(Suite) ->
454	    log("Expand test case ~w~n", [{Module, TestCase}]),
455	    Def = default_module(Module, Suite),
456	    {T, Res} = timer:tc(?MODULE, test_driver, [Def, Config]),
457	    Sec = timer:seconds(1) * 1000,
458	    {T div Sec, {{Module, TestCase}, Res}};
459	'NYI' when Config == suite ->
460	    {Module, TestCase, 'NYI'};
461	'NYI' ->
462      	    log("<WARNING> Test case ~w NYI~n", [{Module, TestCase}]),
463	    {0, {skip, {Module, TestCase}, "NYI"}}
464    end.
465
466%% Returns a list (possibly empty) or the atom 'NYI'
467get_suite(Mod, {group, Suite}) ->
468    try
469	Groups = Mod:groups(),
470	{_, _, TCList} = lists:keyfind(Suite, 1, Groups),
471	TCList
472    catch
473	_:Reason:Stacktrace ->
474	    io:format("Not implemented ~p ~p (~p ~p)~n",
475		      [Mod,Suite,Reason,Stacktrace]),
476	    'NYI'
477    end;
478get_suite(Mod, all) ->
479    case catch (apply(Mod, all, [])) of
480	{'EXIT', _} -> 'NYI';
481	List when is_list(List) -> List
482    end;
483get_suite(_Mod, _Fun) ->
484    [].
485
486%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
487
488eval_test_case(Mod, Fun, Config) ->
489    flush(),
490    global:register_name(mnesia_test_case_sup, self()),
491    Flag = process_flag(trap_exit, true),
492    Pid = spawn_link(?MODULE, test_case_evaluator, [Mod, Fun, [Config]]),
493    R = wait_for_evaluator(Pid, Mod, Fun, Config),
494    global:unregister_name(mnesia_test_case_sup),
495    process_flag(trap_exit, Flag),
496    R.
497
498flush() ->
499    receive Msg -> [Msg | flush()]
500    after 0 -> []
501    end.
502
503wait_for_evaluator(Pid, Mod, Fun, Config) ->
504    receive
505	{'EXIT', Pid, {test_case_ok, _PidRes}} ->
506	    Errors = flush(),
507	    Res =
508		case Errors of
509		    [] -> ok;
510		    Errors -> failed
511		end,
512	    {Res, {Mod, Fun}, Errors};
513	{'EXIT', Pid, {skipped, Reason}} ->
514	    log("<WARNING> Test case ~w skipped, because ~p~n",
515		[{Mod, Fun}, Reason]),
516	    Mod:end_per_testcase(Fun, Config),
517	    {skip, {Mod, Fun}, Reason};
518	{'EXIT', Pid, Reason} ->
519	    log("<>ERROR<> Eval process ~w exited, because ~p~n",
520		[{Mod, Fun}, Reason]),
521	    Mod:end_per_testcase(Fun, Config),
522	    {crash, {Mod, Fun}, Reason}
523    end.
524
525test_case_evaluator(Mod, Fun, [Config]) ->
526    NewConfig = Mod:init_per_testcase(Fun, Config),
527    try
528	R = apply(Mod, Fun, [NewConfig]),
529	Mod:end_per_testcase(Fun, NewConfig),
530	exit({test_case_ok, R})
531    catch error:function_clause ->
532	    exit({skipped, 'NYI'})
533    end.
534
535activity_evaluator(Coordinator) ->
536    activity_evaluator_loop(Coordinator),
537    exit(normal).
538
539activity_evaluator_loop(Coordinator) ->
540    receive
541	begin_trans ->
542	    transaction(Coordinator, 0);
543	{begin_trans, MaxRetries} ->
544	    transaction(Coordinator, MaxRetries);
545	end_trans ->
546	    end_trans;
547	Fun when is_function(Fun) ->
548	    Coordinator ! {self(), Fun()},
549	    activity_evaluator_loop(Coordinator);
550%	{'EXIT', Coordinator, Reason} ->
551%	    Reason;
552	ExitExpr ->
553%	    ?error("activity_evaluator_loop ~p ~p: exit(~p)~n}", [Coordinator, self(), ExitExpr]),
554	    exit(ExitExpr)
555    end.
556
557transaction(Coordinator, MaxRetries) ->
558    Fun = fun() ->
559		  Coordinator ! {self(), begin_trans},
560		  activity_evaluator_loop(Coordinator)
561	  end,
562    Coordinator ! {self(), mnesia:transaction(Fun, MaxRetries)},
563    activity_evaluator_loop(Coordinator).
564
565pick_msg() ->
566    receive
567	Message -> Message
568    after 4000 -> timeout
569    end.
570
571start_activities(Nodes) ->
572    Fun = fun(N) -> spawn_link(N, ?MODULE, activity_evaluator, [self()]) end,
573    Pids = mapl(Fun, Nodes),
574    {success, Pids}.
575
576mapl(Fun, [H|T]) ->
577    Res = Fun(H),
578    [Res|mapl(Fun, T)];
579mapl(_Fun, []) ->
580    [].
581
582diskless(Config) ->
583    case lists:keysearch(diskless, 1, Config) of
584	{value, {diskless, true}} ->
585	    true;
586	_Else ->
587	    false
588    end.
589
590
591start_transactions(Pids) ->
592    Fun = fun(Pid) ->
593		  Pid ! begin_trans,
594		  ?match_receive({Pid, begin_trans})
595	  end,
596    mapl(Fun, Pids).
597
598start_sync_transactions(Pids) ->
599    Nodes = [node(Pid) || Pid <- Pids],
600    Fun = fun(Pid) ->
601		  sync_trans_tid_serial(Nodes),
602		  Pid ! begin_trans,
603		  ?match_receive({Pid, begin_trans})
604	  end,
605    mapl(Fun, Pids).
606
607
608start_transactions(Pids, MaxRetries) ->
609    Fun = fun(Pid) ->
610		  Pid ! {begin_trans, MaxRetries},
611		  ?match_receive({Pid, begin_trans})
612	  end,
613    mapl(Fun, Pids).
614
615start_sync_transactions(Pids, MaxRetries) ->
616    Nodes = [node(Pid) || Pid <- Pids],
617    Fun = fun(Pid) ->
618		  sync_trans_tid_serial(Nodes),
619		  Pid ! {begin_trans, MaxRetries},
620		  ?match_receive({Pid, begin_trans})
621	  end,
622    mapl(Fun, Pids).
623
624sync_trans_tid_serial(Nodes) ->
625    Fun = fun() -> mnesia:write_lock_table(schema) end,
626    rpc:multicall(Nodes, mnesia, transaction, [Fun]).
627
628select_nodes(N, Config, File, Line) ->
629    prepare_test_case([], N, Config, File, Line).
630
631prepare_test_case(Actions, N, Config, File, Line) ->
632    NodeList1 = lookup_config(nodes, Config),
633    NodeList2 = lookup_config(nodenames, Config), %% For testserver
634    NodeList3 = append_unique(NodeList1, NodeList2),
635    This = node(),
636    All = [This | lists:delete(This, NodeList3)],
637    Selected = pick_nodes(N, All, File, Line),
638    case diskless(Config) of
639	true ->
640	    ok;
641	false ->
642	    rpc:multicall(Selected, application, set_env,[mnesia, schema_location, opt_disc])
643    end,
644    do_prepare(Actions, Selected, All, Config, File, Line).
645
646do_prepare([], Selected, _All, _Config, _File, _Line) ->
647    Selected;
648do_prepare([{init_test_case, Appls} | Actions], Selected, All, Config, File, Line) ->
649    set_kill_timer(Config),
650    Started = init_nodes(Selected, File, Line),
651    All2 = append_unique(Started, All),
652    Alive = mnesia_lib:intersect(nodes() ++ [node()], All2),
653    kill_appls(Appls, Alive),
654    process_flag(trap_exit, true),
655    do_prepare(Actions, Started, All2, Config, File, Line);
656do_prepare([delete_schema | Actions], Selected, All, Config, File, Line) ->
657    Alive = mnesia_lib:intersect(nodes() ++ [node()], All),
658    case diskless(Config) of
659	true ->
660	    skip;
661	false ->
662	    Del = fun(Node) ->
663			  case mnesia:delete_schema([Node]) of
664			      ok -> ok;
665			      {error, {"All nodes not running",_}} ->
666				  ok;
667			      Else ->
668				  ?log("Delete schema error ~p ~n", [Else])
669			  end
670		  end,
671	    lists:foreach(Del, Alive)
672    end,
673    do_prepare(Actions, Selected, All, Config, File, Line);
674do_prepare([create_schema | Actions], Selected, All, Config, File, Line) ->
675    Ext = proplists:get_value(default_properties, Config, ?BACKEND),
676    case diskless(Config) of
677	true ->
678	    rpc:multicall(Selected, application, set_env, [mnesia, schema, Ext]),
679	    skip;
680	_Else ->
681	    case mnesia:create_schema(Selected, Ext) of
682		ok ->
683		    ignore;
684		BadNodes ->
685		    ?fatal("Cannot create Mnesia schema on ~p~n", [BadNodes])
686	    end
687    end,
688    do_prepare(Actions, Selected, All, Config, File, Line);
689do_prepare([{start_appls, Appls} | Actions], Selected, All, Config, File, Line) ->
690    case start_appls(Appls, Selected, Config) of
691	[] -> ok;
692	Bad -> ?fatal("Cannot start appls ~p: ~p~n", [Appls, Bad])
693    end,
694    do_prepare(Actions, Selected, All, Config, File, Line);
695do_prepare([{reload_appls, Appls} | Actions], Selected, All, Config, File, Line) ->
696    reload_appls(Appls, Selected),
697    do_prepare(Actions, Selected, All, Config, File, Line).
698
699set_kill_timer(Config) ->
700    case init:get_argument(mnesia_test_timeout) of
701	{ok, _ } -> ok;
702	_ ->
703	    Time0 =
704		case lookup_config(tc_timeout, Config) of
705		    [] -> timer:minutes(5);
706		    ConfigTime when is_integer(ConfigTime) -> ConfigTime
707		end,
708	    Mul = try
709		      test_server:timetrap_scale_factor()
710		  catch _:_ -> 1 end,
711	    (catch test_server:timetrap(Mul*Time0 + 1000)),
712	    spawn_link(?MODULE, kill_tc, [self(),Time0*Mul])
713    end.
714
715kill_tc(Pid, Time) ->
716    receive
717    after Time ->
718	    case process_info(Pid) of
719		undefined ->  ok;
720		_ ->
721		    ?error("Watchdog in test case timed out "
722			   "in ~p min~n", [Time div (1000*60)]),
723		    Files = mnesia_lib:dist_coredump(),
724		    ?log("Cores dumped to:~n ~p~n", [Files]),
725		    %% Genarate erlang crashdumps.
726		    %% GenDump = fun(Node) ->
727		    %% 		      File = "CRASH_" ++ atom_to_list(Node) ++ ".dump",
728		    %% 		      rpc:call(Node, os, putenv, ["ERL_CRASH_DUMP", File]),
729		    %% 		      rpc:cast(Node, erlang, halt, ["RemoteTimeTrap"])
730		    %% 	      end,
731		    %% [GenDump(Node) || Node <- nodes()],
732
733		    %% erlang:halt("DebugTimeTrap"),
734		    exit(Pid, kill)
735	    end
736    end.
737
738
739append_unique([], List) -> List;
740append_unique([H|R], List) ->
741    case lists:member(H, List) of
742	true -> append_unique(R, List);
743	false -> [H | append_unique(R, List)]
744    end.
745
746pick_nodes(all, Nodes, File, Line) ->
747    pick_nodes(length(Nodes), Nodes, File, Line);
748pick_nodes(N, [H | T], File, Line) when N > 0 ->
749    [H | pick_nodes(N - 1, T, File, Line)];
750pick_nodes(0, _Nodes, _File, _Line) ->
751    [];
752pick_nodes(N, [], File, Line) ->
753    ?skip("Test case (~p(~p)) ignored: ~p nodes missing~n",
754	  [File, Line, N]).
755
756init_nodes([Node | Nodes], File, Line) ->
757    case net_adm:ping(Node) of
758	pong ->
759	    [Node | init_nodes(Nodes, File, Line)];
760	pang ->
761	    [Name, Host] = node_to_name_and_host(Node),
762	    case slave_start_link(Host, Name) of
763		{ok, Node1} ->
764		    Path = code:get_path(),
765		    true = rpc:call(Node1, code, set_path, [Path]),
766		    [Node1 | init_nodes(Nodes, File, Line)];
767		Other ->
768		    ?skip("Test case (~p(~p)) ignored: cannot start node ~p: ~p~n",
769			  [File, Line, Node, Other])
770	    end
771    end;
772init_nodes([], _File, _Line) ->
773    [].
774
775%% Returns [Name, Host]
776node_to_name_and_host(Node) ->
777    string:lexemes(atom_to_list(Node), [$@]).
778
779lookup_config(Key,Config) ->
780    case lists:keysearch(Key,1,Config) of
781	{value,{Key,Val}} ->
782	    Val;
783	_ ->
784	    []
785    end.
786
787%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
788
789start_appls(Appls, Nodes) ->
790    start_appls(Appls, Nodes, [],  [schema]).
791
792start_appls(Appls, Nodes, Config) ->
793    start_appls(Appls, Nodes, Config, [schema]).
794
795start_appls([Appl | Appls], Nodes, Config, Tabs) ->
796    {Started, BadStarters} =
797	rpc:multicall(Nodes, ?MODULE, remote_start, [Appl, Config, Nodes]),
798    BadS = [{Node, Appl, Res} || {Node, Res} <- Started, Res /= ok],
799    BadN = [{BadNode, Appl, bad_start} || BadNode <- BadStarters],
800    Bad = BadS ++ BadN,
801    case Appl of
802	mnesia when Bad == [] ->
803	    sync_tables(Nodes, Tabs);
804	_ ->
805	    ignore
806    end,
807    Bad ++ start_appls(Appls, Nodes, Config, Tabs);
808start_appls([], _Nodes, _Config, _Tabs) ->
809    [].
810
811remote_start(mnesia, Config, Nodes) ->
812    case diskless(Config) of
813	true ->
814	    application_controller:set_env(mnesia,
815					   extra_db_nodes,
816					   Nodes -- [node()]),
817	    application_controller:set_env(mnesia,
818					   schema_location,
819					   ram);
820	false ->
821	    application_controller:set_env(mnesia,
822					   schema_location,
823					   opt_disc),
824	    ignore
825    end,
826    {node(), mnesia:start()};
827remote_start(Appl, _Config, _Nodes) ->
828    Res =
829	case application:start(Appl) of
830	    {error, {already_started, Appl}} ->
831		ok;
832	    Other ->
833		Other
834	end,
835    {node(), Res}.
836
837%% Start Mnesia on all given nodes and wait for specified
838%% tables to be accessible on each node. The atom all means
839%% that we should wait for all tables to be loaded
840%%
841%% Returns a list of error tuples {BadNode, mnesia, Reason}
842start_mnesia(Nodes) ->
843    start_appls([mnesia], Nodes).
844start_mnesia(Nodes, Tabs) when is_list(Nodes) ->
845    start_appls([mnesia], Nodes, [], Tabs).
846
847%% Wait for the tables to be accessible from all nodes in the list
848%% and that all nodes are aware of that the other nodes also ...
849sync_tables(Nodes, Tabs) ->
850    Res = send_wait(Nodes, Tabs, []),
851    if
852	Res == 	[] ->
853	    mnesia:transaction(fun() -> mnesia:write_lock_table(schema) end),
854	    Res;
855	true ->
856	    Res
857    end.
858
859send_wait([Node | Nodes], Tabs, Pids) ->
860    Pid = spawn_link(Node, ?MODULE, start_wait, [self(), Tabs]),
861    send_wait(Nodes, Tabs, [Pid | Pids]);
862send_wait([], _Tabs, Pids) ->
863    rec_wait(Pids, []).
864
865rec_wait([Pid | Pids], BadRes) ->
866    receive
867	{'EXIT', Pid, R} ->
868	    rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes]);
869	{Pid, ok} ->
870	    rec_wait(Pids, BadRes);
871	{Pid, {error, R}} ->
872	    rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes])
873    end;
874rec_wait([], BadRes) ->
875    BadRes.
876
877start_wait(Coord, Tabs) ->
878    process_flag(trap_exit, true),
879    Mon = whereis(mnesia_monitor),
880    case catch link(Mon) of
881	{'EXIT', _} ->
882	    unlink(Coord),
883	    Coord ! {self(), {error, {node_not_running, node()}}};
884	_ ->
885	    Res = start_wait_loop(Tabs),
886	    unlink(Mon),
887	    unlink(Coord),
888	    Coord ! {self(), Res}
889    end.
890
891start_wait_loop(Tabs) ->
892    receive
893	{'EXIT', Pid, Reason} ->
894	    {error, {start_wait, Pid, Reason}}
895    after 0 ->
896	    case mnesia:wait_for_tables(Tabs, timer:seconds(30)) of
897		ok ->
898		    verify_nodes(Tabs);
899		{timeout, BadTabs} ->
900		    log("<>WARNING<> Wait for tables ~p: ~p~n", [node(), Tabs]),
901		    start_wait_loop(BadTabs);
902		{error, Reason} ->
903		    {error, {start_wait, Reason}}
904	    end
905    end.
906
907verify_nodes(Tabs) ->
908    verify_nodes(Tabs, 0).
909
910verify_nodes([], _) ->
911    ok;
912
913verify_nodes([Tab| Tabs], N) ->
914    ?match(X when is_atom(X), mnesia_lib:val({Tab, where_to_read})),
915    Nodes = mnesia:table_info(Tab, where_to_write),
916    Copies =
917	mnesia:table_info(Tab, disc_copies) ++
918        mnesia:table_info(Tab, disc_only_copies) ++
919	mnesia:table_info(Tab, ram_copies),
920    Local = mnesia:table_info(Tab, local_content),
921    case Copies -- Nodes of
922	[] ->
923	    verify_nodes(Tabs, 0);
924	_Else when Local == true, Nodes /= [] ->
925	    verify_nodes(Tabs, 0);
926        Else ->
927	    N2 =
928		if
929		    N > 20 ->
930			log("<>WARNING<> ~w Waiting for table: ~p on ~p ~n",
931				 [node(), Tab, Else]),
932			0;
933		    true -> N+1
934		end,
935	    timer:sleep(500),
936	    verify_nodes([Tab| Tabs], N2)
937    end.
938
939
940%% Nicely stop Mnesia on all given nodes
941%%
942%% Returns a list of error tuples {BadNode, Reason}
943stop_mnesia(Nodes) when is_list(Nodes) ->
944    stop_appls([mnesia], Nodes).
945
946stop_appls([Appl | Appls], Nodes) when is_list(Nodes) ->
947    {Stopped, BadNodes} = rpc:multicall(Nodes, ?MODULE, remote_stop, [Appl]),
948    BadS =[{Node, Appl, Res} || {Node, Res} <- Stopped, Res /= stopped],
949    BadN =[{BadNode, Appl, bad_node} || BadNode <- BadNodes],
950    BadS ++ BadN ++ stop_appls(Appls, Nodes);
951stop_appls([], _Nodes) ->
952    [].
953
954remote_stop(mnesia) ->
955    {node(), mnesia:stop()};
956remote_stop(Appl) ->
957    {node(), application:stop(Appl)}.
958
959remote_kill([Appl | Appls]) ->
960    catch Appl:lkill(),
961    application:stop(Appl),
962    remote_kill(Appls);
963remote_kill([]) ->
964    ok.
965
966%% Abruptly kill Mnesia on all given nodes
967%% Returns []
968kill_appls(Appls, Nodes) when is_list(Nodes) ->
969    verbose("<>WARNING<> Intentionally killing ~p: ~w...~n",
970	    [Appls, Nodes], ?FILE, ?LINE),
971    rpc:multicall(Nodes, ?MODULE, remote_kill, [Appls]),
972    [].
973
974kill_mnesia(Nodes) when is_list(Nodes) ->
975    kill_appls([mnesia], Nodes).
976
977reload_appls([Appl | Appls], Selected) ->
978    kill_appls([Appl], Selected),
979    timer:sleep(1000),
980    Ok = {[ok || _N <- Selected], []},
981    {Ok2temp, Empty} = rpc:multicall(Selected, application, unload, [Appl]),
982    Conv = fun({error,{not_loaded,mnesia}}) -> ok; (Else) -> Else end,
983    Ok2 = {lists:map(Conv, Ok2temp), Empty},
984    Ok3 = rpc:multicall(Selected, application, load, [Appl]),
985    if
986	Ok /= Ok2 ->
987	    ?fatal("Cannot unload appl ~p: ~p~n", [Appl, Ok2]);
988	Ok /= Ok3 ->
989	    ?fatal("Cannot load appl ~p: ~p~n", [Appl, Ok3]);
990	true ->
991	    ok
992    end,
993    reload_appls(Appls, Selected);
994reload_appls([], _Selected) ->
995    ok.
996
997shutdown() ->
998    log("<>WARNING<> Intentionally shutting down all nodes... ~p~n",
999	 [nodes() ++ [node()]]),
1000    rpc:multicall(nodes(), erlang, halt, []),
1001    erlang:halt().
1002
1003verify_mnesia(Ups, Downs, File, Line) when is_list(Ups), is_list(Downs) ->
1004    BadUps =
1005	[N || N <- Ups, rpc:call(N, mnesia, system_info, [is_running]) /= yes],
1006    BadDowns =
1007	[N || N <- Downs, rpc:call(N, mnesia, system_info, [is_running]) == yes],
1008    if
1009	BadUps == [] ->
1010	    ignore;
1011	true ->
1012	    error("Mnesia is not running as expected: ~p~n",
1013		  [BadUps], File, Line)
1014    end,
1015    if
1016	BadDowns == [] ->
1017	    ignore;
1018	true ->
1019	    error("Mnesia is not stopped as expected: ~p~n",
1020		  [BadDowns], File, Line)
1021    end,
1022    ok.
1023
1024%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1025
1026verify_replica_location(Tab, [], [], [], _) ->
1027    ?match({'EXIT', _}, mnesia:table_info(Tab, ram_copies)),
1028    ?match({'EXIT', _}, mnesia:table_info(Tab, disc_copies)),
1029    ?match({'EXIT', _}, mnesia:table_info(Tab, disc_only_copies)),
1030    ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_write)),
1031    ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_read)),
1032    [];
1033
1034verify_replica_location(Tab, DiscOnly0, Ram0, Disc0, AliveNodes0) ->
1035%%    sync_tables(AliveNodes0, [Tab]),
1036    AliveNodes = lists:sort(AliveNodes0),
1037    DiscOnly = lists:sort(DiscOnly0),
1038    Ram = lists:sort(Ram0),
1039    Disc = lists:sort(Disc0),
1040    Write = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes),
1041    Read = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes),
1042    This = node(),
1043
1044    timer:sleep(100),
1045
1046    S1 = ?match(AliveNodes, lists:sort(mnesia:system_info(running_db_nodes))),
1047    S2 = ?match(DiscOnly, lists:sort(mnesia:table_info(Tab, disc_only_copies))),
1048    S3 = ?match(Ram, lists:sort(mnesia:table_info(Tab, ram_copies) ++
1049				    mnesia:table_info(Tab, ext_ets))),
1050    S4 = ?match(Disc, lists:sort(mnesia:table_info(Tab, disc_copies))),
1051    S5 = ?match(Write, lists:sort(mnesia:table_info(Tab, where_to_write))),
1052    S6 = case lists:member(This, Read) of
1053	     true ->
1054		 ?match(This, mnesia:table_info(Tab, where_to_read));
1055	     false ->
1056		 ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read))
1057	 end,
1058    lists:filter(fun({success,_}) -> false; (_) -> true end, [S1,S2,S3,S4,S5,S6]).
1059
1060ignore_dead(Nodes, AliveNodes) ->
1061    Filter = fun(Node) -> lists:member(Node, AliveNodes) end,
1062    lists:sort(lists:zf(Filter, Nodes)).
1063
1064
1065remote_activate_debug_fun(N, I, F, C, File, Line) ->
1066    Pid = spawn_link(N, ?MODULE, do_remote_activate_debug_fun, [self(), I, F, C, File, Line]),
1067    receive
1068	{activated, Pid} -> ok;
1069	{'EXIT', Pid, Reason} -> {error, Reason}
1070    end.
1071
1072do_remote_activate_debug_fun(From, I, F, C, File, Line) ->
1073    mnesia_lib:activate_debug_fun(I, F, C, File, Line),
1074    From ! {activated, self()},
1075    timer:sleep(infinity).  % Dies whenever the test process dies !!
1076
1077
1078sort(L) when is_list(L) ->
1079    lists:sort(L);
1080sort({atomic, L}) when is_list(L) ->
1081    {atomic, lists:sort(L)};
1082sort({ok, L}) when is_list(L) ->
1083    {ok, lists:sort(L)};
1084sort(W) ->
1085    W.
1086