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