main([File]) ->
{ok, {_Module, Forms}} = estap_file:read_file(File, []),
{ok, {Plan, Tests}} = estap_file:load_code(Forms),
- TestRunId = case Plan of
- no_plan -> estap_server:no_plan();
- {plan, C} -> estap_server:plan(C)
- end,
- run_tests(Tests, TestRunId).
-
-%%----------------------------------------------------------------------------
-
-run_tests([] = _Tests, TestRunId) ->
- estap_server:done(TestRunId),
- ok;
-run_tests([{{Mod, Func}, Description, Status} | Rest] = _Tests, TestRunId) ->
- % TODO: bail out support
- estap_server:running(TestRunId, Description),
- case Status of
- run ->
- case run_test(Mod, Func) of
- {success, _Result} ->
- estap_server:test_passed(TestRunId);
- {failure, Result} ->
- estap_server:test_failed(TestRunId, Result);
- {dubious, Result} ->
- estap_server:dubious_result(TestRunId, Result);
- {died, Reason} ->
- estap_server:test_died(TestRunId, Reason)
- end;
- {skip, Why} ->
- estap_server:test_skipped(TestRunId, Why);
- {todo, Why} ->
- estap_server:test_todo(TestRunId, Why)
- end,
- run_tests(Rest, TestRunId).
-
-%%----------------------------------------------------------------------------
-
-run_test(Mod, Func) ->
- Self = self(),
- ResultRef = make_ref(),
- {Pid, MonitorRef} = spawn_monitor(
- fun() ->
- TestResult = try Mod:Func() of
- ok -> {success, ok};
- {ok, Value} -> {success, {ok, Value}};
- true -> {success, true};
- error -> {failure, error};
- {error, Reason} -> {failure, {error, Reason}};
- false -> {failure, false};
- Result -> {dubious, Result}
- catch
- throw:ok -> {success, ok};
- throw:{ok, Value} -> {success, {ok, Value}};
- throw:true -> {success, true};
- throw:error -> {failure, error};
- throw:{error, Reason} -> {failure, {error, Reason}};
- throw:false -> {failure, false}
- end,
- Self ! {result, ResultRef, TestResult}
- end
- ),
- receive
- {result, ResultRef, TestResult} ->
- erlang:demonitor(MonitorRef, [flush]),
- TestResult;
- {'DOWN', MonitorRef, process, Pid, Reason} ->
- {died, Reason}
- end.
+ estap_test:run(Plan, Tests),
+ ok.
%%----------------------------------------------------------------------------
%% vim:ft=erlang
--- /dev/null
+%%%---------------------------------------------------------------------------
+%%% @doc
+%%% Functions to use when running test cases.
+%%% @end
+%%%---------------------------------------------------------------------------
+
+-module(estap_test).
+
+%% public interface
+-export([run/2]).
+
+%% private interface
+-export([call/4]).
+
+-export_type([test/0, test_plan/0]).
+
+%%%---------------------------------------------------------------------------
+%%% types {{{
+
+-type test() :: {Func :: {module(), atom()}, Description :: string(),
+ Status :: run | {todo | skip, Why :: string()}}.
+
+-type test_plan() :: {plan, pos_integer()} | no_plan.
+
+%%% }}}
+%%%---------------------------------------------------------------------------
+%%% public interface
+%%%---------------------------------------------------------------------------
+
+%%----------------------------------------------------------
+
+%% @doc Run tests according to plan.
+
+-spec run(test_plan(), [test()]) ->
+ ok.
+
+run(Plan, Tests) ->
+ TestRun = case Plan of
+ no_plan -> estap_server:no_plan();
+ {plan, C} -> estap_server:plan(C)
+ end,
+ run_tests(TestRun, Tests),
+ estap_server:done(TestRun).
+
+%% @doc Run tests, one by one, reporting their results to tracking process.
+%%
+%% @TODO Return something meaningful.
+
+-spec run_tests(estap_server:test_run_id(), [test()]) ->
+ ok.
+
+run_tests(_TestRun, [] = _Tests) ->
+ ok;
+run_tests(TestRun, [{TestFunSpec, Description, Status} | Rest] = _Tests) ->
+ estap_server:running(TestRun, Description),
+ case Status of
+ run ->
+ case test(TestFunSpec) of
+ {success, _Result} ->
+ estap_server:test_passed(TestRun);
+ {failure, Result} ->
+ estap_server:test_failed(TestRun, Result);
+ {dubious, Result} ->
+ estap_server:dubious_result(TestRun, Result);
+ {died, Reason} ->
+ estap_server:test_died(TestRun, Reason)
+ end;
+ {skip, Why} ->
+ estap_server:test_skipped(TestRun, Why);
+ {todo, Why} ->
+ estap_server:test_todo(TestRun, Why)
+ end,
+ run_tests(TestRun, Rest).
+
+%% @doc Run a single test function, according to its specification.
+
+-spec test({Module :: module(), Function :: atom()}) ->
+ {success, term()}
+ | {failure, term()}
+ | {dubious, term()}
+ | {died, term()}.
+
+test({Mod, Func} = _TestFunSpec) ->
+ Args = [],
+ ResultRef = make_ref(),
+ ResultTo = {self(), ResultRef},
+ {Pid, MonRef} = spawn_monitor(?MODULE, call, [ResultTo, Mod, Func, Args]),
+ receive
+ {result, ResultRef, TestResult} ->
+ erlang:demonitor(MonRef, [flush]),
+ TestResult;
+ {'DOWN', MonRef, process, Pid, Reason} ->
+ {died, Reason}
+ end.
+
+%% @private
+%% @doc Run the specified function, collect its result (possibly thrown) and
+%% report it back to `ResultTo'.
+
+-spec call({pid(), term()}, module(), atom(), [term()]) ->
+ ok.
+
+call({Pid, Ref} = _ResultTo, Mod, Fun, Args) ->
+ TestResult = try apply(Mod, Fun, Args) of
+ ok -> {success, ok};
+ {ok, Value} -> {success, {ok, Value}};
+ true -> {success, true};
+ error -> {failure, error};
+ {error, Reason} -> {failure, {error, Reason}};
+ false -> {failure, false};
+ Result -> {dubious, Result}
+ catch
+ throw:ok -> {success, ok};
+ throw:{ok, Value} -> {success, {ok, Value}};
+ throw:true -> {success, true};
+ throw:error -> {failure, error};
+ throw:{error, Reason} -> {failure, {error, Reason}};
+ throw:false -> {failure, false}
+ end,
+ Pid ! {result, Ref, TestResult},
+ ok.
+
+%%----------------------------------------------------------
+
+%%%---------------------------------------------------------------------------
+%%% vim:ft=erlang:foldmethod=marker