| 1 | %%% Copyright (C) Dominic Williams, Nicolas Charpentier |
|---|
| 2 | %%% All rights reserved. |
|---|
| 3 | %%% See file COPYING. |
|---|
| 4 | |
|---|
| 5 | -module (tester). |
|---|
| 6 | -export ([init/2]). |
|---|
| 7 | |
|---|
| 8 | init (Notify, Node) -> |
|---|
| 9 | process_flag (trap_exit, true), |
|---|
| 10 | With_notify = dict: store (notify, Notify, dict: new ()), |
|---|
| 11 | With_tests = dict: store (tests, dict: new (), With_notify), |
|---|
| 12 | With_binaries = dict: store (binaries, dict: new (), With_tests), |
|---|
| 13 | State = dict: store (node, Node, With_binaries), |
|---|
| 14 | loop (State). |
|---|
| 15 | |
|---|
| 16 | loop (State) -> |
|---|
| 17 | receive |
|---|
| 18 | {delete, Modules} -> |
|---|
| 19 | test (unload (Modules, State)); |
|---|
| 20 | {run, Modules} -> |
|---|
| 21 | test (load (Modules, State)); |
|---|
| 22 | {Pid, stop} -> |
|---|
| 23 | Pid ! {self (), bye} |
|---|
| 24 | end. |
|---|
| 25 | |
|---|
| 26 | load (Modules, State) -> |
|---|
| 27 | lists: foldl (fun load_aux/2, State, Modules). |
|---|
| 28 | |
|---|
| 29 | load_aux (Binary, State) -> |
|---|
| 30 | {File_name, Module, Tests} = tests: filter_by_attribute (Binary), |
|---|
| 31 | Node = dict: fetch (node, State), |
|---|
| 32 | Load_args = [Module, File_name, Binary], |
|---|
| 33 | {module, Module} = rpc: call (Node, code, load_binary, Load_args), |
|---|
| 34 | Time_stamped_tests = [{Test, new} || Test <- Tests], |
|---|
| 35 | Test_dict = dict: fetch (tests, State), |
|---|
| 36 | New_tests = dict: store (Module, Time_stamped_tests, Test_dict), |
|---|
| 37 | Binaries_dict = dict: fetch (binaries, State), |
|---|
| 38 | New_binaries = dict: store (Module, Binary, Binaries_dict), |
|---|
| 39 | dict: store (binaries, New_binaries, dict: store (tests, New_tests, State)). |
|---|
| 40 | |
|---|
| 41 | test (State) -> |
|---|
| 42 | State_without_pid = stop_running_session (State), |
|---|
| 43 | Test_dict = dict: fetch (tests, State_without_pid), |
|---|
| 44 | {Total, Tests} = flatten (Test_dict), |
|---|
| 45 | Notify = dict: fetch (notify, State_without_pid), |
|---|
| 46 | Notify ({Total, 0, 0}), |
|---|
| 47 | Self = self(), |
|---|
| 48 | Test_session_fun = fun () -> |
|---|
| 49 | launch_test (Self, Total, Tests, State_without_pid) |
|---|
| 50 | end, |
|---|
| 51 | Test_session = spawn (Test_session_fun), |
|---|
| 52 | dict: store (test_session, Test_session, State_without_pid), |
|---|
| 53 | loop (receive {runner_end, New_state} -> New_state end). |
|---|
| 54 | |
|---|
| 55 | stop_running_session (State) -> |
|---|
| 56 | case dict: find (test_session, State) of |
|---|
| 57 | {ok, Session} -> |
|---|
| 58 | exit(Session, kill), |
|---|
| 59 | dict: erase (test_session, State); |
|---|
| 60 | _ -> |
|---|
| 61 | State |
|---|
| 62 | end. |
|---|
| 63 | |
|---|
| 64 | launch_test (Parent, Total, Tests, State) -> |
|---|
| 65 | process_flag (trap_exit, true), |
|---|
| 66 | Run = lists: foldl (test_fun (State, Total), {State,{0, 0}}, Tests), |
|---|
| 67 | {New_state, _} = Run, |
|---|
| 68 | Parent ! {runner_end, New_state}. |
|---|
| 69 | |
|---|
| 70 | unload (Modules, State) -> |
|---|
| 71 | lists: foldl (fun unload_aux/2, State, Modules). |
|---|
| 72 | |
|---|
| 73 | unload_aux (Module, State) -> |
|---|
| 74 | Node = dict: fetch (node, State), |
|---|
| 75 | rpc: call (Node, code, purge, [Module]), |
|---|
| 76 | rpc: call (Node, code, delete, [Module]), |
|---|
| 77 | false = rpc: call (Node, code, is_loaded, [Module]), |
|---|
| 78 | Tests = dict: fetch (tests, State), |
|---|
| 79 | New_tests = dict: erase (Module, Tests), |
|---|
| 80 | Binaries = dict: fetch (binaries, State), |
|---|
| 81 | New_binaries = dict: erase (Module, Binaries), |
|---|
| 82 | dict: store (binaries, New_binaries, dict :store (tests, New_tests, State)). |
|---|
| 83 | |
|---|
| 84 | flatten (Tests) -> |
|---|
| 85 | F = fun (Module, Ts, {Count, List}) -> |
|---|
| 86 | Tests_by_module = [{Module, T, Stamp} || {T, Stamp} <- Ts], |
|---|
| 87 | {Count + length (Ts), [ Tests_by_module | List]} |
|---|
| 88 | end, |
|---|
| 89 | {Total, All} = dict: fold (F, {0, []}, Tests), |
|---|
| 90 | Sorted_tests = lists: sort (fun new_tests_first/2, lists: flatten (All)), |
|---|
| 91 | {Total, Sorted_tests}. |
|---|
| 92 | |
|---|
| 93 | new_tests_first ({_, _ ,new}, {_, _, failed}) -> |
|---|
| 94 | true; |
|---|
| 95 | new_tests_first ({_, _ ,failed}, {_, _, new}) -> |
|---|
| 96 | false; |
|---|
| 97 | new_tests_first ({_, _ ,new}, _) -> |
|---|
| 98 | true; |
|---|
| 99 | new_tests_first ({_, _, failed}, _) -> |
|---|
| 100 | true; |
|---|
| 101 | new_tests_first ({_,_,_},{_,_,new}) -> |
|---|
| 102 | false; |
|---|
| 103 | new_tests_first ({_,_,_},{_,_,failed}) -> |
|---|
| 104 | false; |
|---|
| 105 | new_tests_first (A,B) -> |
|---|
| 106 | A<B. |
|---|
| 107 | |
|---|
| 108 | test_fun (State, Total) -> |
|---|
| 109 | Node = dict: fetch (node, State), |
|---|
| 110 | Notify = dict: fetch (notify, State), |
|---|
| 111 | Binaries = dict: fetch (binaries, State), |
|---|
| 112 | fun (Test_definition, {Tester_state, {Run, Passed}}) -> |
|---|
| 113 | {Module, Function, Stamp} = Test_definition, |
|---|
| 114 | Pid = spawn_link (Node, Module, Function, []), |
|---|
| 115 | {New_passed, New_stamp} = |
|---|
| 116 | receive |
|---|
| 117 | {'EXIT', Pid, normal} -> |
|---|
| 118 | {Passed + 1, first_test_success (Stamp, now())}; |
|---|
| 119 | {'EXIT', Pid, {Error, Stack_trace}} -> |
|---|
| 120 | Binary = dict: fetch (Module, Binaries), |
|---|
| 121 | MFA = {Module, Function, 0}, |
|---|
| 122 | {File, Line} = modules: locate (MFA, Binary), |
|---|
| 123 | Location = {Module, Function, 0, File, Line}, |
|---|
| 124 | List = [{error, Error}, |
|---|
| 125 | {stack_trace, Stack_trace}, |
|---|
| 126 | {location, Location}], |
|---|
| 127 | Reason = dict: from_list (List), |
|---|
| 128 | Notify (Reason), |
|---|
| 129 | {Passed, failed} |
|---|
| 130 | end, |
|---|
| 131 | New_run = Run + 1, |
|---|
| 132 | Notify ({Total, New_run, New_passed}), |
|---|
| 133 | New_state = update_state (Tester_state, Module, Function, New_stamp), |
|---|
| 134 | {New_state, {New_run, New_passed}} |
|---|
| 135 | end. |
|---|
| 136 | |
|---|
| 137 | first_test_success (Before, Now) when is_atom (Before) -> |
|---|
| 138 | Now; |
|---|
| 139 | first_test_success (Stamp, _) -> |
|---|
| 140 | Stamp. |
|---|
| 141 | |
|---|
| 142 | update_state (State, Module, Function, Stamp) -> |
|---|
| 143 | Test_dict = dict: fetch (tests, State), |
|---|
| 144 | Module_tests = dict:fetch (Module, Test_dict), |
|---|
| 145 | Filtered_tests = proplists: delete (Function, Module_tests), |
|---|
| 146 | Updated_tests = [{Function, Stamp} | Filtered_tests], |
|---|
| 147 | New_tests = dict: store (Module, Updated_tests, Test_dict), |
|---|
| 148 | dict: store (tests, New_tests, State). |
|---|