source: trunk/src/tester.erl @ 92

Revision 92, 5.0 KB checked in by dom, 2 years ago (diff)

File and line is now provided for test failures

Line 
1%%% Copyright (C) Dominic Williams, Nicolas Charpentier
2%%% All rights reserved.
3%%% See file COPYING.
4
5-module (tester).
6-export ([init/2]).
7
8init (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
16loop (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
26load (Modules, State) ->
27    lists: foldl (fun load_aux/2, State, Modules).
28
29load_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
41test (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
55stop_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
64launch_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
70unload (Modules, State) ->
71    lists: foldl (fun unload_aux/2, State, Modules).
72
73unload_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
84flatten (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
93new_tests_first ({_, _ ,new}, {_, _, failed})  ->
94    true;
95new_tests_first ({_, _ ,failed}, {_, _, new})  ->
96    false;
97new_tests_first ({_, _ ,new}, _)  ->
98    true;
99new_tests_first ({_, _, failed}, _) ->
100    true;
101new_tests_first ({_,_,_},{_,_,new}) ->
102    false;
103new_tests_first ({_,_,_},{_,_,failed}) ->
104    false;
105new_tests_first (A,B) ->
106    A<B.
107   
108test_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
137first_test_success (Before, Now) when is_atom (Before) ->
138    Now;
139first_test_success (Stamp, _) ->
140    Stamp.
141
142update_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).
Note: See TracBrowser for help on using the repository browser.