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 %%%---------------------------------------------------------------------------
19 -export([ok/2, is/3, isnt/3, eq/3, ne/3, cmp/4, like/3, unlike/3, matches/3]).
20 -export([bail_out/1, no_plan/0, plan/1, all_ok/0]).
21 -export([diag/1, diag/2, info/1, info/2, explain/1]).
22 -export([test_dir/0, test_dir/1]).
24 -export_type([value/0, cmp/0, regexp/0, match_fun/0]).
26 %%%---------------------------------------------------------------------------
29 -type value() :: term().
31 -type cmp() :: '<' | '>' | '=<' | '>=' | '/=' | '=/=' | '==' | '=:='.
33 -type regexp() :: iolist().
35 -type match_fun() :: fun((value()) -> any()).
37 -type message() :: iolist().
39 -type description() :: iolist().
41 -type info() :: atom() | iolist() |
42 {FieldName :: atom() | iolist(), Value :: atom() | iolist()}.
45 %%%---------------------------------------------------------------------------
47 %%%---------------------------------------------------------------------------
49 %% @doc Check if `Value' is any of the recognized truth values.
51 -spec ok(value(), description()) ->
54 ok(Value, Description) ->
55 TestRun = get_test_run(),
56 estap_server:running(TestRun, Description),
57 estap_server:report_result(TestRun, estap_test:success_or_failure(Value)).
59 %% @doc Check if `Value' is the same as `Expected'.
61 -spec is(value(), value(), description()) ->
64 is(Value, Expected, Description) ->
65 TestRun = get_test_run(),
66 estap_server:running(TestRun, Description),
69 estap_server:report_result(TestRun, {success, true});
71 estap_server:report_result(TestRun, {failure, false})
74 %% @doc Check if `Value' is different than `Expected'.
76 -spec isnt(value(), value(), description()) ->
79 isnt(Value, Expected, Description) ->
80 TestRun = get_test_run(),
81 estap_server:running(TestRun, Description),
84 estap_server:report_result(TestRun, {failure, false});
86 estap_server:report_result(TestRun, {success, true})
89 %% @doc Check if `Value' is equal (`==') to `Expected'.
91 -spec eq(value(), value(), description()) ->
94 eq(Value, Expected, Description) ->
95 % XXX: no `get_test_run()' call
96 cmp(Value, '==', Expected, Description).
98 %% @doc Check if `Value' is not equal (`/=') to `Expected'.
100 -spec ne(value(), value(), description()) ->
103 ne(Value, Expected, Description) ->
104 % XXX: no `get_test_run()' call
105 cmp(Value, '/=', Expected, Description).
107 %% @doc Compare `Value' and `Expected' using comparison operator.
109 -spec cmp(value(), cmp(), value(), description()) ->
112 cmp(Value, Cmp, Expected, Description) ->
113 TestRun = get_test_run(),
114 estap_server:running(TestRun, Description),
115 CmpResult = case Cmp of
116 '<' -> Value < Expected;
117 '>' -> Value > Expected;
118 '=<' -> Value =< Expected;
119 '>=' -> Value >= Expected;
120 '/=' -> Value /= Expected;
121 '=/=' -> Value =/= Expected;
122 '==' -> Value == Expected;
123 '=:=' -> Value =:= Expected
126 true -> estap_server:report_result(TestRun, {success, true});
127 false -> estap_server:report_result(TestRun, {failure, false})
130 %% @doc Check if `Value' matches a regexp.
132 -spec like(value(), regexp(), description()) ->
135 like(Value, Expected, Description) ->
136 TestRun = get_test_run(),
137 estap_server:running(TestRun, Description),
138 case re:run(Value, Expected) of
139 {match, _Capture} -> estap_server:report_result(TestRun, {success, true});
140 nomatch -> estap_server:report_result(TestRun, {failure, false})
143 %% @doc Check if `Value' not matches a regexp.
145 -spec unlike(value(), regexp(), description()) ->
148 unlike(Value, Expected, Description) ->
149 TestRun = get_test_run(),
150 estap_server:running(TestRun, Description),
151 case re:run(Value, Expected) of
152 {match, _Capture} -> estap_server:report_result(TestRun, {failure, false});
153 nomatch -> estap_server:report_result(TestRun, {success, true})
156 %% @doc Check if `Value' pattern-matches.
157 %% Pattern is specified as a fun that has clauses defined only for what
158 %% should match, i.e., calling the fun should fail with `function_clause'
159 %% error. Return value of the fun is ignored.
161 -spec matches(value(), match_fun(), description()) ->
164 matches(Value, MatchSpec, Description) ->
165 TestRun = get_test_run(),
166 estap_server:running(TestRun, Description),
169 estap_server:report_result(TestRun, {success, true})
171 error:function_clause ->
172 estap_server:report_result(TestRun, {failure, false})
175 %%%---------------------------------------------------------------------------
177 %% @doc Stop testing current suite because something terrible happened.
179 %% @TODO Implement this function.
181 -spec bail_out(message()) ->
184 bail_out(_Message) ->
187 %% @doc Set the "no plan" plan for sub-tests.
188 %% Calling this function may be safely skipped.
196 _TestRun = get_test_run(),
199 %% @doc Set expected number of sub-tests.
201 -spec plan(pos_integer()) ->
204 plan(TestCount) when is_integer(TestCount) ->
205 TestRun = estap_server:subplan(TestCount, 1),
206 set_test_run(TestRun),
209 %% @doc Check if all the current sub-tests were OK.
210 %% Function intended to be called at the end of a sequence of sub-tests, to
211 %% indicate that the test sequence passed or failed.
217 TestRun = get_test_run(),
218 {Planned, Total, Failed, _TODO} = estap_server:get_status(TestRun),
219 estap_server:done(TestRun), % this ends estap_server, so it goes last
220 (Failed == 0) and ((Planned == undefined) or (Planned == Total)).
222 %%%---------------------------------------------------------------------------
224 %% @doc Get a directory containing this test script.
226 %% <b>NOTE</b>: This function doesn't work in processes spawned from test
227 %% function. You need to get the directory in parent process and pass it as
231 case get(test_dir) of
232 undefined -> erlang:error({undefined, test_dir});
233 Directory -> Directory
236 %% @doc Get a subdirectory of the directory containing this test script.
238 %% <b>NOTE</b>: This function doesn't work in processes spawned from test
239 %% function. You need to get the directory in parent process and pass it as
243 filename:join(test_dir(), Subdir).
245 %%%---------------------------------------------------------------------------
247 %% @doc Print a diagnostic message.
248 %% Typically, diagnostic message is a warning, but may be notice important
249 %% enough to print it along with test progress by TAP consumer.
251 %% Before first call to {@link plan/1}, {@link no_plan/0} or test functions
252 %% ({@link ok/2}, {@link is/3} etc.) message is printed at the level of
253 %% parent test. After any of those, it's printed at sub-test level.
255 %% Normally diagnostic output goes to <i>STDERR</i>, but under TODO tests it
256 %% goes to <i>STDOUT</i>.
258 %% @TODO Make the diagnostic output go to <i>STDOUT</i> under TODO
260 -spec diag(message()) ->
264 TestRun = get_test_run_or_parent(),
265 estap_server:warning(TestRun, Message).
267 %% @doc Print a warning with some context.
268 %% Typically, diagnostic message is a warning, but may be notice important
269 %% enough to print it along with test progress by TAP consumer.
271 %% Before first call to {@link plan/1}, {@link no_plan/0} or test functions
272 %% ({@link ok/2}, {@link is/3} etc.) message is printed at the level of
273 %% parent test. After any of those, it's printed at sub-test level.
275 %% Normally diagnostic output goes to <i>STDERR</i>, but under TODO tests it
276 %% goes to <i>STDOUT</i>.
278 %% @TODO Make the diagnostic output go to <i>STDOUT</i> under TODO
280 -spec diag(message(), [info()]) ->
283 diag(Message, Info) ->
284 TestRun = get_test_run_or_parent(),
285 InfoLines = [[" ", format_info(I), "\n"] || I <- Info],
286 estap_server:warning(TestRun, [Message, "\n", InfoLines]).
288 %% @doc Print a message.
290 %% Before first call to {@link plan/1}, {@link no_plan/0} or test functions
291 %% ({@link ok/2}, {@link is/3} etc.) message is printed at the level of
292 %% parent test. After any of those, it's printed at sub-test level.
294 -spec info(message()) ->
298 TestRun = get_test_run_or_parent(),
299 estap_server:info(TestRun, Message).
301 %% @doc Print a message with some context.
303 %% Before first call to {@link plan/1}, {@link no_plan/0} or test functions
304 %% ({@link ok/2}, {@link is/3} etc.) message is printed at the level of
305 %% parent test. After any of those, it's printed at sub-test level.
307 -spec info(message(), [info()]) ->
310 info(Message, Info) ->
311 TestRun = get_test_run_or_parent(),
312 InfoLines = [[" ", format_info(I), "\n"] || I <- Info],
313 estap_server:info(TestRun, [Message, "\n", InfoLines]).
315 %% @doc Format a single info entry for printing it on screen.
317 -spec format_info(info()) ->
320 format_info(Info) when is_list(Info); is_binary(Info) ->
321 iolist_to_binary(Info);
322 format_info(Info) when is_atom(Info) ->
323 atom_to_binary(Info, unicode);
324 format_info({K, V} = _Info) ->
325 [format_info(K), ": ", format_info(V)].
327 %% @doc Format term so it can be printed to screen.
328 %% Convenience wrapper for {@link io_lib:format/2}.
333 -spec explain(term()) ->
337 % no term should weigh 1MB
338 io_lib:print(Term, 1, 1024 * 1024, -1).
340 %%%---------------------------------------------------------------------------
342 %% @doc Set previously started {@link estap_server}.
344 set_test_run(TestRun) ->
345 put(estap_server, TestRun).
347 %% @doc Get associated {@link estap_server}, starting it if necessary.
350 case get(estap_server) of
352 TestRun = estap_server:subplan(no_plan, 1),
353 put(estap_server, TestRun),
355 TestRun when is_pid(TestRun) ->
359 %% @doc Get associated {@link estap_server} or parent one if none is started
360 %% yet. Necessary for top-level {@link info/1} and {@link diag/1} to work.
362 get_test_run_or_parent() ->
363 case get(estap_server) of
365 % XXX: this must be set in `estap_test:run()'
366 get(estap_server_parent);
367 TestRun when is_pid(TestRun) ->
371 %%%---------------------------------------------------------------------------
372 %%% vim:ft=erlang:foldmethod=marker