Added support for top-level info and diagnostic messages.
[erlang-estap.git] / src / estap_test.erl
1 %%%---------------------------------------------------------------------------
2 %%% @doc
3 %%%   Functions to use when running test cases.
4 %%% @end
5 %%%---------------------------------------------------------------------------
6
7 -module(estap_test).
8
9 %% public interface
10 -export([run/2]).
11 -export([success_or_failure/1]).
12
13 %% private interface
14 -export([call/5]).
15
16 -export_type([test/0, test_plan/0]).
17
18 %%%---------------------------------------------------------------------------
19 %%% types {{{
20
21 -type test() :: {Func :: {module(), atom()}, Description :: string(),
22                   Status :: run | {todo | skip, Why :: string()}}.
23
24 -type test_plan() :: {plan, pos_integer()} | no_plan.
25
26 %%% }}}
27 %%%---------------------------------------------------------------------------
28 %%% public interface
29 %%%---------------------------------------------------------------------------
30
31 %%----------------------------------------------------------
32
33 %% @doc Run tests according to plan.
34
35 -spec run(test_plan(), [test()]) ->
36   ok.
37
38 run(Plan, Tests) ->
39   TestRun = case Plan of
40     no_plan   -> estap_server:no_plan();
41     {plan, C} -> estap_server:plan(C)
42   end,
43   run_tests(TestRun, Tests),
44   estap_server:done(TestRun).
45
46 %% @doc Run tests, one by one, reporting their results to tracking process.
47 %%
48 %% @TODO Return something meaningful.
49
50 -spec run_tests(estap_server:test_run_id(), [test()]) ->
51   ok.
52
53 run_tests(_TestRun, [] = _Tests) ->
54   ok;
55 run_tests(TestRun, [{TestFunSpec, Description, Status} | Rest] = _Tests) ->
56   estap_server:running(TestRun, Description),
57   case Status of
58     run ->
59       Result = test(TestRun, TestFunSpec),
60       estap_server:report_result(TestRun, Result);
61     {todo, Why} ->
62       Result = test(TestRun, TestFunSpec),
63       estap_server:report_result_todo(TestRun, Why, Result);
64     {skip, Why} ->
65       estap_server:report_skipped(TestRun, Why)
66   end,
67   run_tests(TestRun, Rest).
68
69 %% @doc Run a single test function, according to its specification.
70
71 -spec test(estap_server:test_run_id(),
72            {Module :: module(), Function :: atom()}) ->
73     {success, term()}
74   | {failure, term()}
75   | {dubious, term()}
76   | {died, term()}.
77
78 test(TestRun, {Mod, Func} = _TestFunSpec) ->
79   Args = [],
80   ResultRef = make_ref(),
81   ResultTo = {self(), ResultRef},
82   {Pid, MonRef} = spawn_monitor(?MODULE, call, [ResultTo, TestRun, Mod, Func, Args]),
83   receive
84     {result, ResultRef, TestResult} ->
85       erlang:demonitor(MonRef, [flush]),
86       TestResult;
87     {'DOWN', MonRef, process, Pid, Reason} ->
88       {died, Reason}
89   end.
90
91 %% @private
92 %% @doc Run the specified function, collect its result (possibly thrown) and
93 %%   report it back to `ResultTo'.
94
95 -spec call({pid(), term()}, estap_server:test_run_id(),
96            module(), atom(), [term()]) ->
97   ok.
98
99 call({Pid, Ref} = _ResultTo, TestRun, Mod, Fun, Args) ->
100   % XXX: for `estap:info()' and `estap:diag()' to work with no sub-tests
101   put(estap_server_parent, TestRun),
102   % XXX: putting `test_dir' to proc dict is an important thing for
103   % `estap:test_dir()' function
104   ModuleAttrs = Mod:module_info(attributes),
105   case proplists:get_value(test_dir, ModuleAttrs) of
106     [DirName] -> put(test_dir, DirName);
107     DirName when is_list(DirName) -> put(test_dir, DirName);
108     _ -> ok
109   end,
110   TestResult = try
111     success_or_failure(apply(Mod, Fun, Args))
112   catch
113     throw:ok          -> {success, ok};
114     throw:{ok, Value} -> {success, {ok, Value}};
115     throw:true        -> {success, true};
116     throw:error           -> {failure, error};
117     throw:{error, Reason} -> {failure, {error, Reason}};
118     throw:false           -> {failure, false}
119   end,
120   Pid ! {result, Ref, TestResult},
121   ok.
122
123 %%----------------------------------------------------------
124
125 %% @doc Assess whether the value (returned by some function) is a success or
126 %%   failure.
127
128 -spec success_or_failure(Value) ->
129     {success, Value}
130   | {failure, Value}
131   | {dubious, Value}.
132
133 success_or_failure(ok = Value)      -> {success, Value};
134 success_or_failure({ok, _} = Value) -> {success, Value};
135 success_or_failure(true = Value)    -> {success, Value};
136 success_or_failure(error = Value)      -> {failure, Value};
137 success_or_failure({error, _} = Value) -> {failure, Value};
138 success_or_failure(false = Value)      -> {failure, Value};
139 success_or_failure(Value) -> {dubious, Value}.
140
141 %%----------------------------------------------------------
142
143 %%%---------------------------------------------------------------------------
144 %%% vim:ft=erlang:foldmethod=marker