1%% Licensed under the Apache License, Version 2.0 (the "License"); you may
2%% not use this file except in compliance with the License. You may obtain
3%% a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>
4%%
5%% Unless required by applicable law or agreed to in writing, software
6%% distributed under the License is distributed on an "AS IS" BASIS,
7%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8%% See the License for the specific language governing permissions and
9%% limitations under the License.
10%%
11%% Alternatively, you may use this file under the terms of the GNU Lesser
12%% General Public License (the "LGPL") as published by the Free Software
13%% Foundation; either version 2.1, or (at your option) any later version.
14%% If you wish to allow use of your version of this file only under the
15%% terms of the LGPL, you should delete the provisions above and replace
16%% them with the notice and other provisions required by the LGPL; see
17%% <http://www.gnu.org/licenses/>. If you do not delete the provisions
18%% above, a recipient may use your version of this file under the terms of
19%% either the Apache License or the LGPL.
20%%
21%% @copyright 2004-2009 Mickaël Rémond, Richard Carlsson
22%% @author Mickaël Rémond <mickael.remond@process-one.net>
23%%   [http://www.process-one.net/]
24%% @author Richard Carlsson <carlsson.richard@gmail.com>
25%% @version {@version}, {@date} {@time}
26%% @doc This module is the main EUnit user interface.
27
28-module(eunit).
29
30-include("eunit.hrl").
31-include("eunit_internal.hrl").
32
33%% Official exports
34-export([start/0, stop/0, test/1, test/2]).
35
36%% Experimental; may be removed or relocated
37-export([start/1, stop/1, test/3, submit/1, submit/2, submit/3, watch/1,
38	 watch/2, watch/3, watch_path/1, watch_path/2, watch_path/3,
39	 watch_regexp/1, watch_regexp/2, watch_regexp/3, watch_app/1,
40	 watch_app/2, watch_app/3]).
41
42%% EUnit entry points
43
44%% TODO: Command line interface similar to that of edoc?
45
46%% @doc Starts the EUnit server. Normally, you don't need to call this
47%% function; it is started automatically.
48start() ->
49    start(?SERVER).
50
51%% @private
52%% @doc See {@link start/0}.
53start(Server) ->
54    eunit_server:start(Server).
55
56%% @doc Stops the EUnit server. Normally, you don't need to call this
57%% function.
58stop() ->
59    stop(?SERVER).
60
61%% @private
62%% @doc See {@link stop/0}.
63stop(Server) ->
64    eunit_server:stop(Server).
65
66%% @private
67watch(Target) ->
68    watch(Target, []).
69
70%% @private
71watch(Target, Options) ->
72    watch(?SERVER, Target, Options).
73
74%% @private
75watch(Server, Target, Options) ->
76    eunit_server:watch(Server, Target, Options).
77
78%% @private
79watch_path(Target) ->
80    watch_path(Target, []).
81
82%% @private
83watch_path(Target, Options) ->
84    watch_path(?SERVER, Target, Options).
85
86%% @private
87watch_path(Server, Target, Options) ->
88    eunit_server:watch_path(Server, Target, Options).
89
90%% @private
91watch_regexp(Target) ->
92    watch_regexp(Target, []).
93
94%% @private
95watch_regexp(Target, Options) ->
96    watch_regexp(?SERVER, Target, Options).
97
98%% @private
99watch_regexp(Server, Target, Options) ->
100    eunit_server:watch_regexp(Server, Target, Options).
101
102%% @private
103watch_app(Name) ->
104    watch_app(Name, []).
105
106%% @private
107watch_app(Name, Options) ->
108    watch_app(?SERVER, Name, Options).
109
110%% @private
111watch_app(Server, Name, Options) ->
112    case code:lib_dir(Name) of
113	Path when is_list(Path) ->
114	    watch_path(Server, filename:join(Path, "ebin"), Options);
115	_ ->
116	    error
117    end.
118
119%% @equiv test(Tests, [])
120test(Tests) ->
121    test(Tests, []).
122
123%% @spec test(Tests::term(), Options::[term()]) -> ok | {error, term()}
124%% @doc Runs a set of tests. The format of `Tests' is described in the
125%% section <a
126%% href="overview-summary.html#EUnit_test_representation">EUnit test
127%% representation</a> of the overview.
128%%
129%% Example: ```eunit:test(fred)''' runs all tests in the module `fred'
130%% and also any tests in the module `fred_tests', if that module exists.
131%%
132%% Options:
133%% <dl>
134%% <dt>`verbose'</dt>
135%% <dd>Displays more details about the running tests.</dd>
136%% <dt>`print_depth'</dt>
137%% <dd>Maximum depth to which terms are printed in case of error.</dd>
138%% </dl>
139%%
140%% Options in the environment variable EUNIT are also included last in
141%% the option list, i.e., have lower precedence than those in `Options'.
142%% @see test/1
143test(Tests, Options) ->
144    test(?SERVER, Tests, all_options(Options)).
145
146%% @private
147%% @doc See {@link test/2}.
148test(Server, Tests, Options) ->
149    Listeners = listeners(Options),
150    Serial = eunit_serial:start(Listeners),
151    case eunit_server:start_test(Server, Serial, Tests, Options) of
152	{ok, Reference} -> test_run(Reference, Listeners);
153	{error, R} -> {error, R}
154    end.
155
156test_run(Reference, Listeners) ->
157    receive
158	{start, Reference} ->
159	    cast(Listeners, {start, Reference})
160    end,
161    receive
162	{done, Reference} ->
163	    cast(Listeners, {stop, Reference, self()}),
164            wait_until_listeners_have_terminated(Listeners),
165	    receive
166		{result, Reference, Result} ->
167		    Result
168	    end
169    end.
170
171cast([P | Ps], Msg) ->
172    P ! Msg,
173    cast(Ps, Msg);
174cast([], _Msg) ->
175    ok.
176
177wait_until_listeners_have_terminated([P | Ps]) ->
178    MRef = erlang:monitor(process, P),
179    receive
180        {'DOWN', MRef, process, P, _} ->
181            wait_until_listeners_have_terminated(Ps)
182    end;
183wait_until_listeners_have_terminated([]) ->
184    ok.
185
186%% TODO: functions that run tests on a given node, not a given server
187%% TODO: maybe some functions could check for a globally registered server?
188%% TODO: some synchronous but completely quiet interface function
189
190%% @private
191submit(T) ->
192    submit(T, []).
193
194%% @private
195submit(T, Options) ->
196    submit(?SERVER, T, Options).
197
198%% @private
199submit(Server, T, Options) ->
200    Dummy = spawn(fun devnull/0),
201    eunit_server:start_test(Server, Dummy, T, Options).
202
203listeners(Options) ->
204    %% note that eunit_tty must always run, because it sends the final
205    %% {result,...} message that the test_run() function is waiting for
206    Ls = [{eunit_tty, Options} | proplists:get_all_values(report, Options)],
207    Ps = start_listeners(Ls),
208    %% the event_log option is for debugging, to view the raw events
209    case proplists:get_value(event_log, Options) of
210	undefined ->
211	    Ps;
212	X ->
213	    LogFile = if is_list(X) -> X;
214			 true -> "eunit-events.log"
215		      end,
216	    [spawn_link(fun () -> event_logger(LogFile) end) | Ps]
217    end.
218
219start_listeners([P | Ps]) when is_pid(P) ; is_atom(P) ->
220    [P | start_listeners(Ps)];
221start_listeners([{Mod, Opts} | Ps]) when is_atom(Mod) ->
222    [Mod:start(Opts) | start_listeners(Ps)];
223start_listeners([]) ->
224    [].
225
226%% TODO: make this report file errors
227event_logger(LogFile) ->
228    case file:open(LogFile, [write]) of
229	{ok, FD} ->
230	    receive
231		{start, Reference} ->
232		    event_logger_loop(Reference, FD)
233	    end;
234	Error ->
235	    exit(Error)
236    end.
237
238event_logger_loop(Reference, FD) ->
239    receive
240	{status, _Id, _Info}=Msg ->
241	    io:fwrite(FD, "~tp.\n", [Msg]),
242	    event_logger_loop(Reference, FD);
243	{stop, Reference, _ReplyTo} ->
244	    %% no need to reply, just exit
245	    file:close(FD),
246	    exit(normal)
247    end.
248
249%% TODO: make a proper logger for asynchronous execution with submit/3
250
251devnull() ->
252    receive _ -> devnull() end.
253
254%% including options from EUNIT environment variable
255
256all_options(Opts) ->
257    try os:getenv("EUNIT") of
258	false -> Opts;
259	S ->
260	    {ok, Ts, _} = erl_scan:string(S),
261	    {ok, V} = erl_parse:parse_term(Ts ++ [{dot,erl_anno:new(1)}]),
262	    if is_list(V) -> Opts ++ V;
263	       true -> Opts ++ [V]
264	    end
265    catch
266	_:_ -> Opts
267    end.
268