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%% @author Richard Carlsson <carlsson.richard@gmail.com>
22%% @copyright 2006 Richard Carlsson
23%% @private
24%% @see eunit
25%% @doc Interpretation of symbolic test representation
26
27-module(eunit_data).
28
29-include("eunit.hrl").
30-include("eunit_internal.hrl").
31
32-include_lib("kernel/include/file.hrl").
33
34-export([iter_init/2, iter_next/1, iter_prev/1, iter_id/1,
35	 enter_context/3, get_module_tests/1]).
36
37-import(lists, [foldr/3]).
38
39-define(TICKS_PER_SECOND, 1000).
40
41%% @type tests() =
42%%            SimpleTest
43%%          | [tests()]
44%%          | moduleName()
45%%          | {module, moduleName()}
46%%          | {application, appName()}
47%%          | {application, appName(), [term()]}
48%%          | fileName()
49%%          | {file, fileName()}
50%%          | {string(), tests()}
51%%          | {generator, () -> tests()}
52%%          | {generator, M::moduleName(), F::functionName()}
53%%          | {spawn, tests()}
54%%          | {spawn, Node::atom(), tests()}
55%%          | {timeout, T::number(), tests()}
56%%          | {inorder, tests()}
57%%          | {inparallel, tests()}
58%%          | {inparallel, N::integer(), tests()}
59%%          | {with, X::any(), [AbstractTestFunction]}
60%%          | {setup, Where::local | spawn | {spawn, Node::atom()},
61%%                    Setup::() -> (R::any()),
62%%                    Cleanup::(R::any()) -> any(),
63%%                    tests() | Instantiator
64%%            }
65%%          | {setup, Setup, Cleanup, tests() | Instantiator}
66%%          | {setup, Where, Setup, tests() | Instantiator}
67%%          | {setup, Setup, tests() | Instantiator}
68%%          | {foreach, Where::local | spawn | {spawn, Node::atom()},
69%%                      Setup::() -> (R::any()),
70%%                      Cleanup::(R::any()) -> any(),
71%%                      [tests() | Instantiator]
72%%            }
73%%          | {foreach, Setup, Cleanup, [tests() | Instantiator]}
74%%          | {foreach, Where, Setup, [tests() | Instantiator]}
75%%          | {foreach, Setup, [tests() | Instantiator]}
76%%          | {foreachx, Where::local | spawn | {spawn, Node::atom()},
77%%                       SetupX::(X::any()) -> (R::any()),
78%%                       CleanupX::(X::any(), R::any()) -> any(),
79%%                       Pairs::[{X::any(),
80%%                                (X::any(), R::any()) -> tests()}]
81%%            }
82%%          | {foreachx, SetupX, CleanupX, Pairs}
83%%          | {foreachx, Where, SetupX, Pairs}
84%%          | {foreachx, SetupX, Pairs}
85%%          | {node, Node::atom(), tests() | Instantiator}
86%%          | {node, Node, Args::string(), tests() | Instantiator}
87%%
88%% SimpleTest = TestFunction | {Line::integer(), SimpleTest}
89%%
90%% TestFunction = () -> any()
91%%              | {test, M::moduleName(), F::functionName()}
92%%              | {M::moduleName(), F::functionName()}.
93%%
94%% AbstractTestFunction = (X::any()) -> any()
95%%
96%% Instantiator = (R::any()) -> tests()
97%%              | {with, [AbstractTestFunction]}
98%%
99%% Note that `{string(), ...}' is a short-hand for `{string(), {...}}'
100%% if the tuple contains more than two elements.
101%%
102%% @type moduleName() = atom()
103%% @type functionName() = atom()
104%% @type appName() = atom()
105%% @type fileName() = string()
106
107%% TODO: Can we mark up tests as known-failures?
108%% TODO: Is it possible to handle known timout/setup failures?
109%% TODO: Add diagnostic tests which never fail, but may cause warnings?
110
111%% ---------------------------------------------------------------------
112%% Abstract test set iterator
113
114-record(iter,
115	{prev = [],
116	 next = [],
117	 tests = [],
118	 pos = 0,
119	 parent = []}).
120
121%% @spec (tests(), [integer()]) -> testIterator()
122%% @type testIterator()
123
124iter_init(Tests, ParentID) ->
125    #iter{tests = Tests, parent = lists:reverse(ParentID)}.
126
127%% @spec (testIterator()) -> [integer()]
128
129iter_id(#iter{pos = N, parent = Ns}) ->
130    lists:reverse(Ns, [N]).
131
132%% @spec (testIterator()) -> none | {testItem(), testIterator()}
133
134iter_next(I = #iter{next = []}) ->
135    case next(I#iter.tests) of
136	{T, Tests} ->
137	    {T, I#iter{prev = [T | I#iter.prev],
138		       tests = Tests,
139		       pos = I#iter.pos + 1}};
140	none ->
141	    none
142    end;
143iter_next(I = #iter{next = [T | Ts]}) ->
144    {T, I#iter{next = Ts,
145	       prev = [T | I#iter.prev],
146	       pos = I#iter.pos + 1}}.
147
148%% @spec (testIterator()) -> none | {testItem(), testIterator()}
149
150iter_prev(#iter{prev = []}) ->
151    none;
152iter_prev(#iter{prev = [T | Ts]} = I) ->
153    {T, I#iter{prev = Ts,
154	       next = [T | I#iter.next],
155		       pos = I#iter.pos - 1}}.
156
157
158%% ---------------------------------------------------------------------
159%% Concrete test set representation iterator
160
161%% @spec (tests()) -> none | {testItem(), tests()}
162%% @type testItem() = #test{} | #group{}
163%% @throws {bad_test, term()}
164%%       | {generator_failed, {{M::atom(),F::atom(),A::integer()},
165%%                             exception()}}
166%%       | {no_such_function, mfa()}
167%%       | {module_not_found, moduleName()}
168%%       | {application_not_found, appName()}
169%%       | {file_read_error, {Reason::atom(), Message::string(),
170%%                            fileName()}}
171
172next(Tests) ->
173    case eunit_lib:dlist_next(Tests) of
174	[T | Ts] ->
175	    case parse(T) of
176		{data, T1} ->
177		    next([T1 | Ts]);
178		T1 ->
179		    {T1, Ts}
180	    end;
181	[] ->
182	    none
183    end.
184
185%% this returns either a #test{} or #group{} record, or {data, T} to
186%% signal that T has been substituted for the given representation
187
188parse({foreach, S, Fs}) when is_function(S), is_list(Fs) ->
189    parse({foreach, S, fun ok/1, Fs});
190parse({foreach, S, C, Fs})
191  when is_function(S), is_function(C), is_list(Fs) ->
192    parse({foreach, ?DEFAULT_SETUP_PROCESS, S, C, Fs});
193parse({foreach, P, S, Fs})
194  when is_function(S), is_list(Fs) ->
195    parse({foreach, P, S, fun ok/1, Fs});
196parse({foreach, P, S, C, Fs} = T)
197  when is_function(S), is_function(C), is_list(Fs) ->
198    check_arity(S, 0, T),
199    check_arity(C, 1, T),
200    case Fs of
201	[F | Fs1] ->
202	    {data, [{setup, P, S, C, F}, {foreach, P, S, C, Fs1}]};
203	[] ->
204	    {data, []}
205    end;
206parse({foreachx, S1, Ps}) when is_function(S1), is_list(Ps) ->
207    parse({foreachx, S1, fun ok/2, Ps});
208parse({foreachx, S1, C1, Ps})
209  when is_function(S1), is_function(C1), is_list(Ps) ->
210    parse({foreachx, ?DEFAULT_SETUP_PROCESS, S1, C1, Ps});
211parse({foreachx, P, S1, Ps})
212  when is_function(S1), is_list(Ps) ->
213    parse({foreachx, P, S1, fun ok/2, Ps});
214parse({foreachx, P, S1, C1, Ps} = T)
215  when is_function(S1), is_function(C1), is_list(Ps) ->
216    check_arity(S1, 1, T),
217    check_arity(C1, 2, T),
218    case Ps of
219	[{X, F1} | Ps1] when is_function(F1) ->
220	    check_arity(F1, 2, T),
221	    S = fun () -> S1(X) end,
222	    C = fun (R) -> C1(X, R) end,
223	    F = fun (R) -> F1(X, R) end,
224	    {data, [{setup, P, S, C, F}, {foreachx, P, S1, C1, Ps1}]};
225	[_|_] ->
226	    bad_test(T);
227	[] ->
228	    {data, []}
229    end;
230parse({generator, F}) when is_function(F) ->
231    {module, M} = erlang:fun_info(F, module),
232    {name, N} = erlang:fun_info(F, name),
233    {arity, A} = erlang:fun_info(F, arity),
234    parse({generator, F, {M,N,A}});
235parse({generator, F, {M,N,A}} = T)
236  when is_function(F), is_atom(M), is_atom(N), is_integer(A) ->
237    check_arity(F, 0, T),
238    %% use run_testfun/1 to handle wrapper exceptions
239    case eunit_test:run_testfun(F) of
240	{ok, T1} ->
241            case eunit_lib:is_not_test(T1) of
242                true -> throw({bad_generator, {{M,N,A}, T1}});
243                false -> ok
244            end,
245	    {data, T1};
246	{error, {Class, Reason, Trace}} ->
247	    throw({generator_failed, {{M,N,A}, {Class, Reason, Trace}}})
248    end;
249parse({generator, M, F}) when is_atom(M), is_atom(F) ->
250    parse({generator, eunit_test:mf_wrapper(M, F), {M,F,0}});
251parse({inorder, T}) ->
252    group(#group{tests = T, order = inorder});
253parse({inparallel, T}) ->
254    parse({inparallel, 0, T});
255parse({inparallel, N, T}) when is_integer(N), N >= 0 ->
256    group(#group{tests = T, order = {inparallel, N}});
257parse({timeout, N, T}) when is_number(N), N >= 0 ->
258    group(#group{tests = T, timeout = round(N * ?TICKS_PER_SECOND)});
259parse({spawn, T}) ->
260    group(#group{tests = T, spawn = local});
261parse({spawn, N, T}) when is_atom(N) ->
262    group(#group{tests = T, spawn = {remote, N}});
263parse({setup, S, I}) when is_function(S); is_list(S) ->
264    parse({setup, ?DEFAULT_SETUP_PROCESS, S, I});
265parse({setup, S, C, I}) when is_function(S), is_function(C) ->
266    parse({setup, ?DEFAULT_SETUP_PROCESS, S, C, I});
267parse({setup, P, S, I}) when is_function(S) ->
268    parse({setup, P, S, fun ok/1, I});
269parse({setup, P, L, I} = T) when is_list(L) ->
270    check_setup_list(L, T),
271    {S, C} = eunit_test:multi_setup(L),
272    parse({setup, P, S, C, I});
273parse({setup, P, S, C, I} = T)
274  when is_function(S), is_function(C), is_function(I) ->
275    check_arity(S, 0, T),
276    check_arity(C, 1, T),
277    case erlang:fun_info(I, arity) of
278	{arity, 0} ->
279	    %% if I is nullary, it is a plain test
280	    parse({setup, S, C, fun (_) -> I end});
281	_ ->
282	    %% otherwise, I must be an instantiator function
283	    check_arity(I, 1, T),
284	    case P of
285		local -> ok;
286		spawn -> ok;
287		{spawn, N} when is_atom(N) -> ok;
288		_ -> bad_test(T)
289	    end,
290	    group(#group{tests = I,
291			 context = #context{setup = S, cleanup = C,
292					    process = P}})
293    end;
294parse({setup, P, S, C, {with, As}}) when is_list(As) ->
295    parse({setup, P, S, C, fun (X) -> {with, X, As} end});
296parse({setup, P, S, C, T}) when is_function(S), is_function(C) ->
297    parse({setup, P, S, C, fun (_) -> T end});
298parse({node, N, T}) when is_atom(N) ->
299    parse({node, N, "", T});
300parse({node, N, A, T1}=T) when is_atom(N) ->
301    case eunit_lib:is_string(A) of
302	true ->
303	    %% TODO: better stack traces for internal funs like these
304	    parse({setup,
305		   fun () ->
306			   %% TODO: auto-start net_kernel if needed
307 			   StartedNet = false,
308%% The following is commented out because of problems when running
309%% eunit as part of the init sequence (from the command line):
310%% 			   StartedNet =
311%% 			       case whereis(net_kernel) of
312%% 				   undefined ->
313%% 				       M = list_to_atom(atom_to_list(N)
314%% 							++ "_master"),
315%% 				       case net_kernel:start([M]) of
316%% 					   {ok, _} ->
317%% 					       true;
318%% 					   {error, E} ->
319%% 					       throw({net_kernel_start, E})
320%% 				       end;
321%% 				   _ -> false
322%% 			       end,
323%% 			   ?debugVal({started, StartedNet}),
324			   {Name, Host} = eunit_lib:split_node(N),
325			   {ok, Node} = slave:start_link(Host, Name, A),
326			   {Node, StartedNet}
327		   end,
328		   fun ({Node, StopNet}) ->
329%% 			   ?debugVal({stop, StopNet}),
330			   slave:stop(Node),
331			   case StopNet of
332			       true -> net_kernel:stop();
333			       false -> ok
334			   end
335		   end,
336		   T1});
337	false ->
338	    bad_test(T)
339    end;
340parse({module, M}) when is_atom(M) ->
341    {data, {"module '" ++ atom_to_list(M) ++ "'", get_module_tests(M)}};
342parse({application, A}) when is_atom(A) ->
343    try parse({file, atom_to_list(A)++".app"})
344    catch
345	{file_read_error,{enoent,_,_}} ->
346	    case code:lib_dir(A) of
347		Dir when is_list(Dir) ->
348		    %% add "ebin" if it exists, like code_server does
349		    BinDir = filename:join(Dir, "ebin"),
350		    case file:read_file_info(BinDir) of
351			{ok, #file_info{type=directory}} ->
352			    parse({dir, BinDir});
353			_ ->
354			    parse({dir, Dir})
355		    end;
356		_ ->
357		    throw({application_not_found, A})
358	    end
359    end;
360parse({application, A, Info}=T) when is_atom(A) ->
361    case proplists:get_value(modules, Info) of
362	Ms when is_list(Ms) ->
363	    case [M || M <- Ms, not is_atom(M)] of
364		[] ->
365		    {data, {"application '" ++ atom_to_list(A) ++ "'", Ms}};
366		_ ->
367		    bad_test(T)
368	    end;
369	_ ->
370	    bad_test(T)
371    end;
372parse({file, F} = T) when is_list(F) ->
373    case eunit_lib:is_string(F) of
374	true ->
375	    {data, {"file \"" ++ F ++ "\"", get_file_tests(F)}};
376	false ->
377	    bad_test(T)
378    end;
379parse({dir, D}=T) when is_list(D) ->
380    case eunit_lib:is_string(D) of
381	true ->
382	    {data, {"directory \"" ++ D ++ "\"",
383		    get_directory_module_tests(D)}};
384	false ->
385	    bad_test(T)
386    end;
387parse({with, X, As}=T) when is_list(As) ->
388    case As of
389	[A | As1] ->
390	    check_arity(A, 1, T),
391	    {data, [{eunit_lib:fun_parent(A), fun () -> A(X) end},
392		    {with, X, As1}]};
393	[] ->
394	    {data, []}
395    end;
396parse({S, T1} = T) when is_list(S) ->
397    case eunit_lib:is_string(S) of
398	true ->
399	    group(#group{tests = T1, desc = unicode:characters_to_binary(S)});
400	false ->
401	    bad_test(T)
402    end;
403parse({S, T1}) when is_binary(S) ->
404    group(#group{tests = T1, desc = S});
405parse(T) when is_tuple(T), size(T) > 2, is_list(element(1, T)) ->
406    [S | Es] = tuple_to_list(T),
407    parse({S, list_to_tuple(Es)});
408parse(T) when is_tuple(T), size(T) > 2, is_binary(element(1, T)) ->
409    [S | Es] = tuple_to_list(T),
410    parse({S, list_to_tuple(Es)});
411parse(M) when is_atom(M) ->
412    parse({module, M});
413parse(T) when is_list(T) ->
414    case eunit_lib:is_string(T) of
415	true ->
416	    try parse({dir, T})
417	    catch
418		{file_read_error,{R,_,_}}
419		  when R =:= enotdir; R =:= enoent ->
420		    parse({file, T})
421	    end;
422	false ->
423	    bad_test(T)
424    end;
425parse(T) ->
426    parse_simple(T).
427
428%% parse_simple always produces a #test{} record
429
430parse_simple({L, F}) when is_integer(L), L >= 0 ->
431    (parse_simple(F))#test{line = L};
432parse_simple({{M,N,A}=Loc, F}) when is_atom(M), is_atom(N), is_integer(A) ->
433    (parse_simple(F))#test{location = Loc};
434parse_simple(F) ->
435    parse_function(F).
436
437parse_function(F) when is_function(F) ->
438    check_arity(F, 0, F),
439    #test{f = F, location = eunit_lib:fun_parent(F)};
440parse_function({test, M, F}) when is_atom(M), is_atom(F) ->
441    #test{f = eunit_test:mf_wrapper(M, F), location = {M, F, 0}};
442parse_function({M, F}) when is_atom(M), is_atom(F) ->
443    %% {M,F} is now considered obsolete; use {test,M,F} instead
444    parse_function({test, M, F});
445parse_function(F) ->
446    bad_test(F).
447
448check_arity(F, N, _) when is_function(F, N) ->
449    ok;
450check_arity(_, _, T) ->
451    bad_test(T).
452
453check_setup_list([{Tag, S, C} | Es], T)
454  when is_atom(Tag), is_function(S), is_function(C) ->
455    check_arity(S, 0, T),
456    check_arity(C, 1, T),
457    check_setup_list(Es, T);
458check_setup_list([{Tag, S} | Es], T)
459  when is_atom(Tag), is_function(S) ->
460    check_arity(S, 0, T),
461    check_setup_list(Es, T);
462check_setup_list([], _T) ->
463    ok;
464check_setup_list(_, T) ->
465    bad_test(T).
466
467bad_test(T) ->
468    throw({bad_test, T}).
469
470ok(_) -> ok.
471ok(_, _) -> ok.
472
473%% This does some look-ahead and folds nested groups and tests where
474%% possible. E.g., {String, Test} -> Test#test{desc = String}.
475
476group(#group{context = #context{}} = G) ->
477    %% leave as it is - the test body is an instantiator, which is not
478    %% suitable for lookahead (and anyway, properties of the setup
479    %% should not be merged with properties of its body, e.g. spawn)
480    G;
481group(#group{tests = T0, desc = Desc, order = Order, context = Context,
482	     spawn = Spawn, timeout = Timeout} = G) ->
483    {T1, Ts} = lookahead(T0),
484    {T2, _} = lookahead(Ts),
485    case T1 of
486	#test{desc = Desc1, timeout = Timeout1}
487	when T2 =:= none, Spawn =:= undefined, Context =:= undefined,
488	     ((Desc =:= undefined) or (Desc1 =:= undefined)),
489	     ((Timeout =:= undefined) or (Timeout1 =:= undefined)) ->
490	    %% a single test within a non-spawn/setup group: put the
491	    %% information directly on the test; drop the order
492	    T1#test{desc = join_properties(Desc, Desc1),
493		    timeout = join_properties(Timeout, Timeout1)};
494
495	#test{timeout = undefined}
496	when T2 =:= none, Timeout =/= undefined, Context =:= undefined ->
497	    %% a single test without timeout, within a non-joinable
498	    %% group with a timeout and no fixture: push the timeout to
499	    %% the test
500	    G#group{tests = {timeout, (Timeout div ?TICKS_PER_SECOND), T0},
501		    timeout = undefined};
502
503	#group{desc = Desc1, order = Order1, context = Context1,
504	       spawn = Spawn1, timeout = Timeout1}
505	when T2 =:= none,
506	     ((Desc =:= undefined) or (Desc1 =:= undefined)),
507	     ((Order =:= undefined) or (Order1 =:= undefined)),
508	     ((Context =:= undefined) or (Context1 =:= undefined)),
509	     ((Spawn =:= undefined) or (Spawn1 =:= undefined)),
510	     ((Timeout =:= undefined) or (Timeout1 =:= undefined)) ->
511	    %% two nested groups with non-conflicting properties
512	    group(T1#group{desc = join_properties(Desc, Desc1),
513			   order = join_properties(Order, Order1),
514			   context = join_properties(Context, Context1),
515			   spawn = join_properties(Spawn, Spawn1),
516			   timeout = join_properties(Timeout, Timeout1)});
517
518	#group{order = Order1, timeout = Timeout1}
519	when T2 =:= none ->
520	    %% two nested groups that cannot be joined: try to push the
521	    %% timeout and ordering properties to the inner group
522	    push_order(Order, Order1, push_timeout(Timeout, Timeout1, G));
523
524	_ ->
525	    %% leave the group as it is and discard the lookahead
526	    G
527    end.
528
529lookahead(T) ->
530    case next(T) of
531	{T1, Ts} -> {T1, Ts};
532	none -> {none, []}
533    end.
534
535join_properties(undefined, X) -> X;
536join_properties(X, undefined) -> X.
537
538push_timeout(Timeout, undefined, G=#group{context=undefined})
539  when Timeout =/= undefined ->
540    %% A timeout on a context (fixture) includes the setup/cleanup time
541    %% and must not be propagated into the body
542    G#group{tests = {timeout, (Timeout div ?TICKS_PER_SECOND), G#group.tests},
543	    timeout = undefined};
544push_timeout(_, _, G) ->
545    G.
546
547push_order(inorder, undefined, G) ->
548    G#group{tests = {inorder, G#group.tests}, order = undefined};
549push_order({inparallel, N}, undefined, G) ->
550    G#group{tests = {inparallel, N, G#group.tests}, order = undefined};
551push_order(_, _, G) ->
552    G.
553
554%% ---------------------------------------------------------------------
555%% Extracting test funs from a module
556
557%% @throws {module_not_found, moduleName()}
558
559get_module_tests(M) ->
560    try M:module_info(exports) of
561	Es ->
562	    Fs = get_module_tests_1(M, Es),
563	    W = ?DEFAULT_MODULE_WRAPPER_NAME,
564	    case lists:member({W,1}, Es) of
565		false -> Fs;
566		true -> {generator, fun () -> M:W(Fs) end}
567	    end
568    catch
569	error:undef ->
570	    throw({module_not_found, M})
571    end.
572
573get_module_tests_1(M, Es) ->
574    Fs = testfuns(Es, M, ?DEFAULT_TEST_SUFFIX,
575		  ?DEFAULT_GENERATOR_SUFFIX),
576    Name = atom_to_list(M),
577    case lists:suffix(?DEFAULT_TESTMODULE_SUFFIX, Name) of
578	false ->
579	    Name1 = Name ++ ?DEFAULT_TESTMODULE_SUFFIX,
580	    M1 = list_to_atom(Name1),
581	    try get_module_tests(M1) of
582		Fs1 ->
583		    Fs ++ [{"module '" ++ Name1 ++ "'", Fs1}]
584	    catch
585		{module_not_found, M1} ->
586		    Fs
587	    end;
588	true ->
589	    Fs
590    end.
591
592testfuns(Es, M, TestSuffix, GeneratorSuffix) ->
593    foldr(fun ({F, 0}, Fs) ->
594		  N = atom_to_list(F),
595		  case lists:suffix(TestSuffix, N) of
596		      true ->
597			  [{test, M, F} | Fs];
598		      false ->
599			  case lists:suffix(GeneratorSuffix, N) of
600			      true ->
601				  [{generator, M, F} | Fs];
602			      false ->
603				  Fs
604			  end
605		  end;
606	      (_, Fs) ->
607		  Fs
608	  end,
609	  [],
610	  Es).
611
612
613%% ---------------------------------------------------------------------
614%% Getting a test set from a file (text file or object file)
615
616%% @throws {file_read_error, {Reason::atom(), Message::string(),
617%%                            fileName()}}
618
619get_file_tests(F) ->
620    case is_module_filename(F) of
621	true ->
622	    %% look relative to current dir first
623	    case file:read_file_info(F) of
624		{ok, #file_info{type=regular}} ->
625		    objfile_test(F);
626		_ ->
627		    %% (where_is_file/1 does not take a path argument)
628		    case code:where_is_file(F) of
629			non_existing ->
630			    %% this will produce a suitable error message
631			    objfile_test(F);
632			Path ->
633			    objfile_test(Path)
634		    end
635	    end;
636	false ->
637	    eunit_lib:consult_file(F)
638    end.
639
640is_module_filename(F) ->
641    filename:extension(F) =:= code:objfile_extension().
642
643objfile_test({M, File}) ->
644    {setup,
645     fun () ->
646	     %% TODO: better error/stacktrace for this internal fun
647	     code:purge(M),
648	     {module,M} = code:load_abs(filename:rootname(File)),
649	     ok
650     end,
651     {module, M}};
652objfile_test(File) ->
653    objfile_test({objfile_module(File), File}).
654
655objfile_module(File) ->
656    try
657	{value, {module, M}} = lists:keysearch(module, 1,
658					       beam_lib:info(File)),
659	M
660    catch
661	_:_ ->
662	    throw({file_read_error,
663		   {undefined, "extracting module name failed", File}})
664    end.
665
666
667%% ---------------------------------------------------------------------
668%% Getting a set of module tests from the object files in a directory
669
670%% @throws {file_read_error,
671%%          {Reason::atom(), Message::string(), fileName()}}
672
673get_directory_module_tests(D) ->
674    Ms = get_directory_modules(D),
675    %% for all 'm' in the set, remove 'm_tests' if present
676    F = fun ({M,_}, S) ->
677		Name = atom_to_list(M),
678		case lists:suffix(?DEFAULT_TESTMODULE_SUFFIX, Name) of
679		    false ->
680			Name1 = Name ++ ?DEFAULT_TESTMODULE_SUFFIX,
681			M1 = list_to_atom(Name1),
682			dict:erase(M1, S);
683		    true ->
684			S
685		end
686	end,
687    [objfile_test(Obj)
688     || Obj <- dict:to_list(lists:foldl(F, dict:from_list(Ms), Ms))].
689
690%% TODO: handle packages (recursive search for files)
691get_directory_modules(D) ->
692    [begin
693	 F1 = filename:join(D, F),
694	 {objfile_module(F1), F1}
695     end
696     || F <- eunit_lib:list_dir(D), is_module_filename(F)].
697
698
699
700%% ---------------------------------------------------------------------
701%% Entering a setup-context, with guaranteed cleanup.
702
703%% @spec (Tests::#context{}, Instantiate, Callback) -> any()
704%%    Instantiate = (any()) -> tests()
705%%    Callback = (tests()) -> any()
706%% @throws {context_error, Error, eunit_lib:exception()}
707%% Error = setup_failed | instantiation_failed | cleanup_failed
708
709enter_context(#context{setup = S, cleanup = C, process = P}, I, F) ->
710    F1 = case P of
711	     local -> F;
712	     spawn -> fun (X) -> F({spawn, X}) end;
713	     {spawn, N} -> fun (T) -> F({spawn, N, T}) end
714	 end,
715    eunit_test:enter_context(S, C, I, F1).
716
717
718-ifdef(TEST).
719generator_exported_() ->
720    generator().
721
722generator() ->
723    T = ?_test(ok),
724    [T, T, T].
725
726echo_proc() ->
727    receive {P,X} -> P ! X, echo_proc() end.
728
729ping(P) ->
730    P ! {self(),ping}, receive ping -> ok end.
731
732data_test_() ->
733    Setup = fun () -> spawn(fun echo_proc/0) end,
734    Cleanup = fun (Pid) -> exit(Pid, kill) end,
735    Fail = ?_test(throw(eunit)),
736    T = ?_test(ok),
737    Tests = [T,T,T],
738    [?_assertMatch(ok, eunit:test(T)),
739     ?_assertMatch(error, eunit:test(Fail)),
740     ?_assertMatch(ok, eunit:test({test, ?MODULE, trivial_test})),
741     ?_assertMatch(ok, eunit:test({generator, fun () -> Tests end})),
742     ?_assertMatch(ok, eunit:test({generator, fun generator/0})),
743     ?_assertMatch(ok, eunit:test({generator, ?MODULE, generator_exported_})),
744     ?_assertMatch(ok, eunit:test({inorder, Tests})),
745     ?_assertMatch(ok, eunit:test({inparallel, Tests})),
746     ?_assertMatch(ok, eunit:test({timeout, 10, Tests})),
747     ?_assertMatch(ok, eunit:test({spawn, Tests})),
748     ?_assertMatch(ok, eunit:test({setup, Setup, Cleanup,
749				   fun (P) -> ?_test(ok = ping(P)) end})),
750     %%?_assertMatch(ok, eunit:test({node, test@localhost, Tests})),
751     ?_assertMatch(ok, eunit:test({module, eunit_lib})),
752     ?_assertMatch(ok, eunit:test(eunit_lib)),
753     ?_assertMatch(ok, eunit:test("examples/tests.txt"))
754
755     %%?_test({foreach, Setup, [T, T, T]})
756    ].
757
758trivial_test() ->
759    ok.
760
761trivial_generator_test_() ->
762    [?_test(ok)].
763
764lazy_test_() ->
765    {spawn, [?_test(undefined = put(count, 0)),
766	     lazy_gen(7),
767	     ?_assertMatch(7, get(count))]}.
768
769-dialyzer({no_improper_lists, lazy_gen/1}).
770lazy_gen(N) ->
771    {generator,
772     fun () ->
773	     if N > 0 ->
774		     [?_test(put(count,1+get(count)))
775		      | lazy_gen(N-1)];
776		true ->
777		     []
778	     end
779     end}.
780-endif.
781