1 %%%---------------------------------------------------------------------------
3 %%% Functions to use in test cases.
5 %%% Test passes if it returns or throws (`throw()'): `ok', `{ok, Value}', or
8 %%% Test fails if it returns or throws `error', `{error, Reason}', `false',
9 %%% or calls `exit(...)' or `erlang:error(...)' (or simply dies).
11 %%% Any other returned value is also considered a failure, but a dubious
12 %%% one. Stick to saying explicitly that the test failed.
14 %%% All test functions return the value they were passed (an exception to
15 %%% this rule are {@link pass/1} and {@link fail/1} functions, for obvious
16 %%% reason). This allows test to be added around preparation function call
17 %%% for more complex test cases.
19 %%%---------------------------------------------------------------------------
24 -export([ok/2, pass/1, fail/1, is/3, isnt/3, eq/3, ne/3, cmp/4]).
25 -export([like/3, unlike/3, matches/3]).
26 -export([bail_out/1, no_plan/0, plan/1, all_ok/0]).
27 -export([diag/1, diag/2, info/1, info/2, explain/1]).
28 -export([test_dir/0, test_dir/1]).
30 -export_type([value/0, cmp/0, regexp/0, match_fun/0]).
32 %%%---------------------------------------------------------------------------
35 -type value() :: term().
37 -type cmp() :: '<' | '>' | '=<' | '>=' | '/=' | '=/=' | '==' | '=:='.
39 -type regexp() :: iolist().
41 -type match_fun() :: fun((value()) -> any()).
43 -type message() :: iolist().
45 -type description() :: iolist().
47 -type info() :: atom() | iolist() |
48 {FieldName :: atom() | iolist(), Value :: atom() | iolist()}.
51 %%%---------------------------------------------------------------------------
53 %%%---------------------------------------------------------------------------
55 %% @doc Check if `Value' is any of the recognized truth values.
57 -spec ok(value(), description()) ->
60 ok(Value, Description) ->
61 report(estap_test:success_or_failure(Value), Description),
64 %% @doc Mark the test as a success unconditionally.
66 -spec pass(description()) ->
70 report({success, explicit}, Description),
73 %% @doc Mark the test as a failure unconditionally.
75 -spec fail(description()) ->
79 report({failure, explicit}, Description),
82 %% @doc Check if `Value' is the same as `Expected'.
84 -spec is(value(), value(), description()) ->
87 is(Value, Expected, Description) ->
89 Expected -> report({success, Value}, Description);
90 _ -> report({failure, Value}, Description)
94 %% @doc Check if `Value' is different than `Expected'.
96 -spec isnt(value(), value(), description()) ->
99 isnt(Value, Expected, Description) ->
101 Expected -> report({failure, Value}, Description);
102 _ -> report({success, Value}, Description)
106 %% @doc Check if `Value' is equal (`==') to `Expected'.
108 -spec eq(value(), value(), description()) ->
111 eq(Value, Expected, Description) ->
112 cmp(Value, '==', Expected, Description).
114 %% @doc Check if `Value' is not equal (`/=') to `Expected'.
116 -spec ne(value(), value(), description()) ->
119 ne(Value, Expected, Description) ->
120 cmp(Value, '/=', Expected, Description).
122 %% @doc Compare `Value' and `Expected' using comparison operator.
124 -spec cmp(value(), cmp(), value(), description()) ->
127 cmp(Value, Cmp, Expected, Description) ->
128 CmpResult = case Cmp of
129 '<' -> Value < Expected;
130 '>' -> Value > Expected;
131 '=<' -> Value =< Expected;
132 '>=' -> Value >= Expected;
133 '/=' -> Value /= Expected;
134 '=/=' -> Value =/= Expected;
135 '==' -> Value == Expected;
136 '=:=' -> Value =:= Expected
138 % FIXME: better reporting for failures
140 true -> report({success, Value}, Description);
141 false -> report({failure, Value}, Description)
145 %% @doc Check if `Value' matches a regexp.
147 -spec like(value(), regexp(), description()) ->
150 like(Value, Expected, Description) ->
151 % XXX: regular expression may be invalid, so prepare estap_server before
153 TestRun = get_test_run(),
154 estap_server:running(TestRun, Description),
155 case re:run(Value, Expected) of
156 {match, _Capture} -> estap_server:report_result(TestRun, {success, Value});
157 nomatch -> estap_server:report_result(TestRun, {failure, Value})
161 %% @doc Check if `Value' not matches a regexp.
163 -spec unlike(value(), regexp(), description()) ->
166 unlike(Value, Expected, Description) ->
167 % XXX: regular expression may be invalid, so prepare estap_server before
169 TestRun = get_test_run(),
170 estap_server:running(TestRun, Description),
171 case re:run(Value, Expected) of
172 {match, _Capture} -> estap_server:report_result(TestRun, {failure, Value});
173 nomatch -> estap_server:report_result(TestRun, {success, Value})
177 %% @doc Check if `Value' pattern-matches.
178 %% Pattern is specified as a fun that has clauses defined only for what
179 %% should match, i.e., calling the fun should fail with `function_clause'
180 %% error. Return value of the fun is ignored.
182 -spec matches(value(), match_fun(), description()) ->
185 matches(Value, MatchSpec, Description) ->
186 TestRun = get_test_run(),
187 estap_server:running(TestRun, Description),
190 estap_server:report_result(TestRun, {success, Value})
192 error:function_clause ->
193 estap_server:report_result(TestRun, {failure, Value})
197 %%%---------------------------------------------------------------------------
199 %% @doc Stop testing whatsoever because something terrible happened.
201 %% Note that bailing out is a very severe operation. It aborts all test
202 %% cases, including the ones in other scripts that were not executed yet.
203 %% It should be only used when an error that occurred renders whole test
204 %% suite unusable before it's fixed.
206 -spec bail_out(message()) ->
210 TestRun = get_test_run_or_parent(),
211 estap_server:bail_out(TestRun, Message),
214 %% @doc Set the "no plan" plan for sub-tests.
215 %% Calling this function may be safely skipped.
223 _TestRun = get_test_run(),
226 %% @doc Set expected number of sub-tests.
228 -spec plan(pos_integer()) ->
231 plan(TestCount) when is_integer(TestCount) ->
232 TestRun = estap_server:subplan(TestCount, 1),
233 set_test_run(TestRun),
236 %% @doc Check if all the current sub-tests were OK.
237 %% Function intended to be called at the end of a sequence of sub-tests, to
238 %% indicate that the test sequence passed or failed.
244 TestRun = get_test_run(),
245 {Planned, Total, Failed, _TODO} = estap_server:get_status(TestRun),
246 estap_server:done(TestRun), % this ends estap_server, so it goes last
247 (Failed == 0) and ((Planned == undefined) or (Planned == Total)).
249 %%%---------------------------------------------------------------------------
251 %% @doc Get a directory containing this test script.
253 %% <b>NOTE</b>: This function doesn't work in processes spawned from test
254 %% function. You need to get the directory in parent process and pass it as
258 case get(test_dir) of
259 undefined -> erlang:error({undefined, test_dir});
260 Directory -> Directory
263 %% @doc Get a subdirectory of the directory containing this test script.
265 %% <b>NOTE</b>: This function doesn't work in processes spawned from test
266 %% function. You need to get the directory in parent process and pass it as
270 filename:join(test_dir(), Subdir).
272 %%%---------------------------------------------------------------------------
274 %% @doc Print a diagnostic message.
275 %% Typically, diagnostic message is a warning, but may be notice important
276 %% enough to print it along with test progress by TAP consumer.
278 %% Before first call to {@link plan/1}, {@link no_plan/0} or test functions
279 %% ({@link ok/2}, {@link is/3} etc.) message is printed at the level of
280 %% parent test. After any of those, it's printed at sub-test level.
282 %% Normally diagnostic output goes to <i>STDERR</i>, but under TODO tests it
283 %% goes to <i>STDOUT</i>.
285 %% @TODO Make the diagnostic output go to <i>STDOUT</i> under TODO
287 -spec diag(message()) ->
291 TestRun = get_test_run_or_parent(),
292 estap_server:warning(TestRun, Message).
294 %% @doc Print a warning with some context.
295 %% Typically, diagnostic message is a warning, but may be notice important
296 %% enough to print it along with test progress by TAP consumer.
298 %% Before first call to {@link plan/1}, {@link no_plan/0} or test functions
299 %% ({@link ok/2}, {@link is/3} etc.) message is printed at the level of
300 %% parent test. After any of those, it's printed at sub-test level.
302 %% Normally diagnostic output goes to <i>STDERR</i>, but under TODO tests it
303 %% goes to <i>STDOUT</i>.
305 %% @TODO Make the diagnostic output go to <i>STDOUT</i> under TODO
307 -spec diag(message(), [info()]) ->
310 diag(Message, Info) ->
311 TestRun = get_test_run_or_parent(),
312 InfoLines = [[" ", format_info(I), "\n"] || I <- Info],
313 estap_server:warning(TestRun, [Message, "\n", InfoLines]).
315 %% @doc Print a message.
317 %% Before first call to {@link plan/1}, {@link no_plan/0} or test functions
318 %% ({@link ok/2}, {@link is/3} etc.) message is printed at the level of
319 %% parent test. After any of those, it's printed at sub-test level.
321 -spec info(message()) ->
325 TestRun = get_test_run_or_parent(),
326 estap_server:info(TestRun, Message).
328 %% @doc Print a message with some context.
330 %% Before first call to {@link plan/1}, {@link no_plan/0} or test functions
331 %% ({@link ok/2}, {@link is/3} etc.) message is printed at the level of
332 %% parent test. After any of those, it's printed at sub-test level.
334 -spec info(message(), [info()]) ->
337 info(Message, Info) ->
338 TestRun = get_test_run_or_parent(),
339 InfoLines = [[" ", format_info(I), "\n"] || I <- Info],
340 estap_server:info(TestRun, [Message, "\n", InfoLines]).
342 %% @doc Format a single info entry for printing it on screen.
344 -spec format_info(info()) ->
347 format_info(Info) when is_list(Info); is_binary(Info) ->
348 iolist_to_binary(Info);
349 format_info(Info) when is_atom(Info) ->
350 atom_to_binary(Info, unicode);
351 format_info({K, V} = _Info) ->
352 <<(format_info(K))/binary, ": ", (format_info(V))/binary>>.
354 %% @doc Format term so it can be printed to screen.
355 %% Convenience wrapper for {@link io_lib:format/2}.
360 -spec explain(term()) ->
364 % no term should weigh 1MB
365 io_lib:print(Term, 1, 1024 * 1024, -1).
367 %%%---------------------------------------------------------------------------
369 %% @doc Set previously started {@link estap_server}.
371 set_test_run(TestRun) ->
372 put(estap_server, TestRun).
374 %% @doc Get associated {@link estap_server}, starting it if necessary.
377 case get(estap_server) of
379 TestRun = estap_server:subplan(no_plan, 1),
380 put(estap_server, TestRun),
382 TestRun when is_pid(TestRun) ->
386 %% @doc Get associated {@link estap_server} or parent one if none is started
387 %% yet. Necessary for top-level {@link info/1} and {@link diag/1} to work.
389 get_test_run_or_parent() ->
390 case get(estap_server) of
392 % XXX: this must be set in `estap_test:run()'
393 get(estap_server_parent);
394 TestRun when is_pid(TestRun) ->
398 %% @doc Send a test report to {@link estap_server}.
400 -spec report({success | failure | dubious, Value :: value()}, description()) ->
403 report({T,_V} = Report, Description)
404 when T == success; T == failure; T == dubious ->
405 TestRun = get_test_run(),
406 estap_server:running(TestRun, Description),
407 estap_server:report_result(TestRun, Report),
410 %%%---------------------------------------------------------------------------
411 %%% vim:ft=erlang:foldmethod=marker