%% public interface
-export([ok/2, is/3, isnt/3, eq/3, ne/3, cmp/4, like/3, unlike/3, matches/3]).
--export([bail_out/1, plan/0, plan/1, all_ok/0]).
+-export([bail_out/1, no_plan/0, plan/1, all_ok/0]).
-export([diag/1, diag/2, note/1, note/2, explain/1]).
-export_type([value/0, cmp/0, regexp/0, match_fun/0]).
%% @doc Check if `Value' is any of the recognized truth values.
-spec ok(value(), description()) ->
- 'TODO'.
+ ok.
-ok(_Value, _Description) ->
- 'TODO'.
+ok(Value, Description) ->
+ TestRun = get_test_run(),
+ estap_server:running(TestRun, Description),
+ estap_server:report_result(TestRun, estap_test:success_or_failure(Value)).
%% @doc Check if `Value' is the same as `Expected'.
-spec is(value(), value(), description()) ->
- 'TODO'.
-
-is(_Value, _Expected, _Description) ->
- 'TODO'.
+ ok.
+
+is(Value, Expected, Description) ->
+ TestRun = get_test_run(),
+ estap_server:running(TestRun, Description),
+ case Value of
+ Expected ->
+ estap_server:report_result(TestRun, {success, true});
+ _ ->
+ estap_server:report_result(TestRun, {failure, false})
+ end.
%% @doc Check if `Value' is different than `Expected'.
-spec isnt(value(), value(), description()) ->
- 'TODO'.
-
-isnt(_Value, _Expected, _Description) ->
- 'TODO'.
+ ok.
+
+isnt(Value, Expected, Description) ->
+ TestRun = get_test_run(),
+ estap_server:running(TestRun, Description),
+ case Value of
+ Expected ->
+ estap_server:report_result(TestRun, {failure, false});
+ _ ->
+ estap_server:report_result(TestRun, {success, true})
+ end.
%% @doc Check if `Value' is equal (`==') to `Expected'.
-spec eq(value(), value(), description()) ->
- 'TODO'.
+ ok.
eq(Value, Expected, Description) ->
+ % XXX: no `get_test_run()' call
cmp(Value, '==', Expected, Description).
%% @doc Check if `Value' is not equal (`/=') to `Expected'.
-spec ne(value(), value(), description()) ->
- 'TODO'.
+ ok.
ne(Value, Expected, Description) ->
+ % XXX: no `get_test_run()' call
cmp(Value, '/=', Expected, Description).
%% @doc Compare `Value' and `Expected' using comparison operator.
-spec cmp(value(), cmp(), value(), description()) ->
- 'TODO'.
-
-cmp(_Value, _Cmp, _Expected, _Description) ->
- 'TODO'.
+ ok.
+
+cmp(Value, Cmp, Expected, Description) ->
+ TestRun = get_test_run(),
+ estap_server:running(TestRun, Description),
+ CmpResult = case Cmp of
+ '<' -> Value < Expected;
+ '>' -> Value > Expected;
+ '=<' -> Value =< Expected;
+ '>=' -> Value >= Expected;
+ '/=' -> Value /= Expected;
+ '=/=' -> Value =/= Expected;
+ '==' -> Value == Expected;
+ '=:=' -> Value =:= Expected
+ end,
+ case CmpResult of
+ true -> estap_server:report_result(TestRun, {success, true});
+ false -> estap_server:report_result(TestRun, {failure, false})
+ end.
%% @doc Check if `Value' matches a regexp.
-spec like(value(), regexp(), description()) ->
- 'TODO'.
+ ok.
-like(_Value, _Expected, _Description) ->
- 'TODO'.
+like(Value, Expected, Description) ->
+ TestRun = get_test_run(),
+ estap_server:running(TestRun, Description),
+ case re:run(Value, Expected) of
+ {match, _Capture} -> estap_server:report_result(TestRun, {success, true});
+ nomatch -> estap_server:report_result(TestRun, {failure, false})
+ end.
%% @doc Check if `Value' not matches a regexp.
-spec unlike(value(), regexp(), description()) ->
- 'TODO'.
+ ok.
-unlike(_Value, _Expected, _Description) ->
- 'TODO'.
+unlike(Value, Expected, Description) ->
+ TestRun = get_test_run(),
+ estap_server:running(TestRun, Description),
+ case re:run(Value, Expected) of
+ {match, _Capture} -> estap_server:report_result(TestRun, {failure, false});
+ nomatch -> estap_server:report_result(TestRun, {success, true})
+ end.
%% @doc Check if `Value' pattern-matches.
%% Pattern is specified as a fun that has clauses defined only for what
%% error. Return value of the fun is ignored.
-spec matches(value(), match_fun(), description()) ->
- 'TODO'.
-
-matches(_Value, _MatchSpec, _Description) ->
- 'TODO'.
+ ok.
+
+matches(Value, MatchSpec, Description) ->
+ TestRun = get_test_run(),
+ estap_server:running(TestRun, Description),
+ try
+ MatchSpec(Value),
+ estap_server:report_result(TestRun, {success, true})
+ catch
+ error:function_clause ->
+ estap_server:report_result(TestRun, {failure, false})
+ end.
%%%---------------------------------------------------------------------------
%% @doc Stop testing current suite because something terrible happened.
+%%
+%% @TODO Implement this function.
-spec bail_out(message()) ->
no_return().
%%
%% @see all_ok/0
--spec plan() ->
- 'TODO'.
+-spec no_plan() ->
+ ok.
-plan() ->
- 'TODO'.
+no_plan() ->
+ _TestRun = get_test_run(),
+ ok.
%% @doc Set expected number of sub-tests.
-spec plan(pos_integer()) ->
- 'TODO'.
+ ok.
-plan(_TestCount) ->
- 'TODO'.
+plan(TestCount) when is_integer(TestCount) ->
+ TestRun = estap_server:subplan(TestCount, 1),
+ set_test_run(TestRun),
+ ok.
%% @doc Check if all the current sub-tests were OK.
%% Function intended to be called at the end of a sequence of sub-tests, to
%% indicate that the test sequence passed or failed.
-spec all_ok() ->
- 'TODO'.
+ true | false.
all_ok() ->
- 'TODO'.
+ TestRun = get_test_run(),
+ {Planned, Total, Failed, _TODO} = estap_server:get_status(TestRun),
+ estap_server:done(TestRun), % this ends estap_server, so it goes last
+ (Failed == 0) and ((Planned == undefined) or (Planned == Total)).
%%%---------------------------------------------------------------------------
%% @doc Print a warning.
+%%
+%% @TODO Implement this function.
-spec diag(message()) ->
'TODO'.
'TODO'.
%% @doc Print a warning with some context.
+%%
+%% @TODO Implement this function.
-spec diag(message(), [info()]) ->
'TODO'.
'TODO'.
%% @doc Print a message.
+%%
+%% @TODO Implement this function.
-spec note(message()) ->
'TODO'.
'TODO'.
%% @doc Print a message with some context.
+%%
+%% @TODO Implement this function.
-spec note(message(), [info()]) ->
'TODO'.
%% @doc Format term so it can be printed to screen.
%% Convenience wrapper for {@link io_lib:format/2}.
+%% @spec explain(term()) ->
+%% iolist()
+%%
+%% @TODO Implement this function.
-spec explain(term()) ->
- iolist().
+ 'TODO'.
explain(_Term) ->
'TODO'.
%%%---------------------------------------------------------------------------
+
+%% @doc Set previously started {@link estap_server}.
+
+set_test_run(TestRun) ->
+ put(estap_server, TestRun).
+
+%% @doc Get associated {@link estap_server}, starting it if necessary.
+
+get_test_run() ->
+ case get(estap_server) of
+ undefined ->
+ TestRun = estap_server:subplan(no_plan, 1),
+ put(estap_server, TestRun),
+ TestRun;
+ TestRun when is_pid(TestRun) ->
+ TestRun
+ end.
+
+%%%---------------------------------------------------------------------------
%%% vim:ft=erlang:foldmethod=marker
%% public interface
-export([no_plan/0, plan/1, subplan/2, done/1]).
+-export([get_status/1]).
-export([running/2, report_result/2, report_result_todo/3, report_skipped/2]).
%% supervision tree API
-type plan() :: no_plan | pos_integer().
+-record(counters, {
+ tests = 0,
+ successes = 0,
+ failures = 0,
+ todo_successes = 0,
+ todo_failures = 0,
+ skipped = 0
+}).
+
-record(state, {
plan :: plan(),
level = 0 :: non_neg_integer(),
- test :: {TestNo :: pos_integer(), Description :: string()}
+ test :: {TestNo :: pos_integer(), Description :: string()},
+ counters = #counters{}
}).
%%%---------------------------------------------------------------------------
done(TestRunId) ->
gen_server:call(TestRunId, done).
+%% @doc Return summary in form of numbers of tests.
+%% `TODO' tests are the ones marked as TODO that failed.
+
+-spec get_status(test_run_id()) ->
+ {Planned :: integer() | undefined,
+ Total :: integer(), Failed :: integer(), TODO :: integer()}.
+
+get_status(TestRunId) ->
+ gen_server:call(TestRunId, status).
+
%% @doc Mark the beginning of new test.
-spec running(test_run_id(), string()) ->
-spec start(plan(), non_neg_integer()) ->
{ok, pid()} | {error, term()}.
-start(Plan, _Level) ->
- gen_server:start(?MODULE, [Plan], []).
+start(Plan, Level) ->
+ gen_server:start(?MODULE, [Plan, Level], []).
%% @private
%% @doc Start the process.
-spec start_link(plan(), non_neg_integer()) ->
{ok, pid()} | {error, term()}.
-start_link(Plan, _Level) ->
- gen_server:start_link(?MODULE, [Plan], []).
+start_link(Plan, Level) ->
+ gen_server:start_link(?MODULE, [Plan, Level], []).
%%%---------------------------------------------------------------------------
%%% gen_server callbacks
%% @private
%% @doc Initialize event handler.
-init([Plan] = _Args) ->
+init([Plan, Level] = _Args) ->
+ State = #state{plan = Plan, level = Level},
case Plan of
no_plan -> skip;
- C when is_integer(C) -> io:fwrite("1..~B~n", [C])
+ C when is_integer(C) -> print("1..~B", [C], State)
end,
- State = #state{plan = Plan},
{ok, State}.
%% @private
handle_call(done = _Request, _From,
State = #state{plan = Plan, test = {LastTestNo, _}}) ->
case Plan of
- no_plan -> io:fwrite("1..~B~n", [LastTestNo]);
- C when is_integer(C) -> io:fwrite("# ran ~B of ~B tests~n", [LastTestNo, C])
+ no_plan ->
+ print("1..~B", [LastTestNo], State);
+ C when is_integer(C) ->
+ print("# ran ~B of ~B tests", [LastTestNo, C], State)
end,
{stop, normal, ok, State};
{reply, ok, NewState};
handle_call({result, TestResult} = _Request, _From,
- State = #state{test = {TestNo, TestDesc}}) ->
+ State = #state{test = {TestNo, TestDesc}, counters = Counters}) ->
case TestResult of
success ->
- io:fwrite("ok ~B - ~s~n", [TestNo, TestDesc]);
+ print("ok ~B - ~s", [TestNo, TestDesc], State);
{failure, Reason} ->
- io:fwrite("not ok ~B - ~s # result: ~s~n",
- [TestNo, TestDesc, format(Reason)]);
+ print("not ok ~B - ~s # result: ~s",
+ [TestNo, TestDesc, format(Reason)], State);
{dubious, Value} ->
- io:fwrite("not ok ~B - ~s # dubious result: ~s~n",
- [TestNo, TestDesc, format(Value)]);
+ print("not ok ~B - ~s # dubious result: ~s",
+ [TestNo, TestDesc, format(Value)], State);
{died, Reason} ->
- io:fwrite("not ok ~B - ~s # died: ~s~n",
- [TestNo, TestDesc, format(Reason)])
+ print("not ok ~B - ~s # died: ~s",
+ [TestNo, TestDesc, format(Reason)], State)
end,
- {reply, ok, State};
+ NewState = State#state{counters = add_result(TestResult, Counters)},
+ {reply, ok, NewState};
handle_call({todo, TestResult, Why} = _Request, _From,
- State = #state{test = {TestNo, TestDesc}}) ->
+ State = #state{test = {TestNo, TestDesc}, counters = Counters}) ->
case TestResult of
success ->
- io:fwrite("ok ~B - ~s # TODO ~s~n", [TestNo, TestDesc, Why]);
+ print("ok ~B - ~s # TODO ~s", [TestNo, TestDesc, Why], State);
{failure, _Reason} ->
- io:fwrite("not ok ~B - ~s # TODO ~s~n", [TestNo, TestDesc, Why]);
+ print("not ok ~B - ~s # TODO ~s", [TestNo, TestDesc, Why], State);
{dubious, _Value} ->
- io:fwrite("not ok ~B - ~s # TODO ~s~n", [TestNo, TestDesc, Why]);
+ print("not ok ~B - ~s # TODO ~s", [TestNo, TestDesc, Why], State);
{died, _Reason} ->
- io:fwrite("not ok ~B - ~s # TODO ~s~n", [TestNo, TestDesc, Why])
+ print("not ok ~B - ~s # TODO ~s", [TestNo, TestDesc, Why], State)
end,
- {reply, ok, State};
+ NewState = State#state{counters = add_todo(TestResult, Counters)},
+ {reply, ok, NewState};
handle_call({skipped, Reason} = _Request, _From,
- State = #state{test = {TestNo, TestDesc}}) ->
- io:fwrite("ok ~B - ~s # SKIP ~s~n", [TestNo, TestDesc, Reason]),
- {reply, ok, State};
+ State = #state{test = {TestNo, TestDesc}, counters = Counters}) ->
+ print("ok ~B - ~s # SKIP ~s", [TestNo, TestDesc, Reason], State),
+ NewState = State#state{counters = add_skipped(Counters)},
+ {reply, ok, NewState};
+
+handle_call(status = _Request, _From, State = #state{counters = Counters}) ->
+ Planned = case State of
+ #state{plan = no_plan} -> undefined;
+ #state{plan = C} -> C
+ end,
+ Total = Counters#counters.tests,
+ Failed = Counters#counters.failures,
+ TODO = Counters#counters.todo_failures,
+ {reply, {Planned, Total, Failed, TODO}, State};
%% unknown calls
handle_call(_Request, _From, State) ->
% no term should weigh 1MB
io_lib:print(Term, 1, 1024 * 1024, -1).
+%% @doc Print message to screen.
+%% The message doesn't need to end with NL character.
+
+-spec print(string(), [term()], #state{}) ->
+ ok.
+
+print(Format, Args, _State = #state{level = Level}) ->
+ Indent = [" " || _ <- lists:seq(1, Level)],
+ Text = iolist_to_binary(io_lib:format(Format, Args)),
+ Lines = binary:split(Text, <<"\n">>, [global, trim]),
+ [io:put_chars([Indent, L, "\n"]) || L <- Lines],
+ ok.
+
+%%----------------------------------------------------------
+
+%% @doc Add 1 to skipped tests counter.
+
+add_skipped(Counters = #counters{tests = T, skipped = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, skipped = N + 1}.
+
+%% @doc Add 1 to successes or failures counter, depending on the `Result'.
+
+add_result(success = _Result,
+ Counters = #counters{tests = T, successes = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, successes = N + 1};
+
+add_result({failure, _} = _Result,
+ Counters = #counters{tests = T, failures = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, failures = N + 1};
+
+add_result({dubious, _} = _Result,
+ Counters = #counters{tests = T, failures = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, failures = N + 1};
+
+add_result({died, _} = _Result,
+ Counters = #counters{tests = T, failures = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, failures = N + 1}.
+
+%% @doc Add 1 to TODO successes or failures counter, depending on the
+%% `Result'.
+
+add_todo(success = _Result,
+ Counters = #counters{tests = T, todo_successes = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, todo_successes = N + 1};
+
+add_todo({failure, _} = _Result,
+ Counters = #counters{tests = T, todo_failures = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, todo_failures = N + 1};
+
+add_todo({dubious, _} = _Result,
+ Counters = #counters{tests = T, todo_failures = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, todo_failures = N + 1};
+
+add_todo({died, _} = _Result,
+ Counters = #counters{tests = T, todo_failures = N}) ->
+ _NewCounters = Counters#counters{tests = T + 1, todo_failures = N + 1}.
+
%%----------------------------------------------------------
%%%---------------------------------------------------------------------------