Added running TODO tests.
[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
12 %% private interface
13 -export([call/4]).
14
15 -export_type([test/0, test_plan/0]).
16
17 %%%---------------------------------------------------------------------------
18 %%% types {{{
19
20 -type test() :: {Func :: {module(), atom()}, Description :: string(),
21                   Status :: run | {todo | skip, Why :: string()}}.
22
23 -type test_plan() :: {plan, pos_integer()} | no_plan.
24
25 %%% }}}
26 %%%---------------------------------------------------------------------------
27 %%% public interface
28 %%%---------------------------------------------------------------------------
29
30 %%----------------------------------------------------------
31
32 %% @doc Run tests according to plan.
33
34 -spec run(test_plan(), [test()]) ->
35   ok.
36
37 run(Plan, Tests) ->
38   TestRun = case Plan of
39     no_plan   -> estap_server:no_plan();
40     {plan, C} -> estap_server:plan(C)
41   end,
42   run_tests(TestRun, Tests),
43   estap_server:done(TestRun).
44
45 %% @doc Run tests, one by one, reporting their results to tracking process.
46 %%
47 %% @TODO Return something meaningful.
48
49 -spec run_tests(estap_server:test_run_id(), [test()]) ->
50   ok.
51
52 run_tests(_TestRun, [] = _Tests) ->
53   ok;
54 run_tests(TestRun, [{TestFunSpec, Description, Status} | Rest] = _Tests) ->
55   estap_server:running(TestRun, Description),
56   case Status of
57     run ->
58       case test(TestFunSpec) of
59         {success, _Result} ->
60           estap_server:test_passed(TestRun);
61         {failure, Result} ->
62           estap_server:test_failed(TestRun, Result);
63         {dubious, Result} ->
64           estap_server:dubious_result(TestRun, Result);
65         {died, Reason} ->
66           estap_server:test_died(TestRun, Reason)
67       end;
68     {todo, Why} ->
69       %estap_server:test_todo(TestRun, Why)
70       case test(TestFunSpec) of
71         {success, _Result} ->
72           estap_server:test_todo(TestRun, success, Why);
73         {failure, Result} ->
74           estap_server:test_todo(TestRun, {failure, Result}, Why);
75         {dubious, Result} ->
76           estap_server:test_todo(TestRun, {dubious, Result}, Why);
77         {died, Reason} ->
78           estap_server:test_todo(TestRun, {died, Reason}, Why)
79       end;
80     {skip, Why} ->
81       estap_server:test_skipped(TestRun, Why)
82   end,
83   run_tests(TestRun, Rest).
84
85 %% @doc Run a single test function, according to its specification.
86
87 -spec test({Module :: module(), Function :: atom()}) ->
88     {success, term()}
89   | {failure, term()}
90   | {dubious, term()}
91   | {died, term()}.
92
93 test({Mod, Func} = _TestFunSpec) ->
94   Args = [],
95   ResultRef = make_ref(),
96   ResultTo = {self(), ResultRef},
97   {Pid, MonRef} = spawn_monitor(?MODULE, call, [ResultTo, Mod, Func, Args]),
98   receive
99     {result, ResultRef, TestResult} ->
100       erlang:demonitor(MonRef, [flush]),
101       TestResult;
102     {'DOWN', MonRef, process, Pid, Reason} ->
103       {died, Reason}
104   end.
105
106 %% @private
107 %% @doc Run the specified function, collect its result (possibly thrown) and
108 %%   report it back to `ResultTo'.
109
110 -spec call({pid(), term()}, module(), atom(), [term()]) ->
111   ok.
112
113 call({Pid, Ref} = _ResultTo, Mod, Fun, Args) ->
114   TestResult = try apply(Mod, Fun, Args) of
115     ok          -> {success, ok};
116     {ok, Value} -> {success, {ok, Value}};
117     true        -> {success, true};
118     error           -> {failure, error};
119     {error, Reason} -> {failure, {error, Reason}};
120     false           -> {failure, false};
121     Result          -> {dubious, Result}
122   catch
123     throw:ok          -> {success, ok};
124     throw:{ok, Value} -> {success, {ok, Value}};
125     throw:true        -> {success, true};
126     throw:error           -> {failure, error};
127     throw:{error, Reason} -> {failure, {error, Reason}};
128     throw:false           -> {failure, false}
129   end,
130   Pid ! {result, Ref, TestResult},
131   ok.
132
133 %%----------------------------------------------------------
134
135 %%%---------------------------------------------------------------------------
136 %%% vim:ft=erlang:foldmethod=marker