Added original value to error reports.
[erlang-estap.git] / src / estap.erl
1 %%%---------------------------------------------------------------------------
2 %%% @doc
3 %%%   Functions to use in test cases.
4 %%%
5 %%%   Test passes if it returns or throws (`throw()'): `ok', `{ok, Value}', or
6 %%%   `true'.
7 %%%
8 %%%   Test fails if it returns or throws `error', `{error, Reason}', `false',
9 %%%   or calls `exit(...)' or `erlang:error(...)' (or simply dies).
10 %%%
11 %%%   Any other returned value is also considered a failure, but a dubious
12 %%%   one. Stick to saying explicitly that the test failed.
13 %%%
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.
18 %%% @end
19 %%%---------------------------------------------------------------------------
20
21 -module(estap).
22
23 %% public interface
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]).
29
30 -export_type([value/0, cmp/0, regexp/0, match_fun/0]).
31
32 %%%---------------------------------------------------------------------------
33 %%% types {{{
34
35 -type value() :: term().
36
37 -type cmp() :: '<' | '>' | '=<' | '>=' | '/=' | '=/=' | '==' | '=:='.
38
39 -type regexp() :: iolist().
40
41 -type match_fun() :: fun((value()) -> any()).
42
43 -type message() :: iolist().
44
45 -type description() :: iolist().
46
47 -type info() :: atom() | iolist() |
48                 {FieldName :: atom() | iolist(), Value :: atom() | iolist()}.
49
50 %%% }}}
51 %%%---------------------------------------------------------------------------
52 %%% public interface
53 %%%---------------------------------------------------------------------------
54
55 %% @doc Check if `Value' is any of the recognized truth values.
56
57 -spec ok(value(), description()) ->
58   Value :: value().
59
60 ok(Value, Description) ->
61   report(estap_test:success_or_failure(Value), Description),
62   Value.
63
64 %% @doc Mark the test as a success unconditionally.
65
66 -spec pass(description()) ->
67   true.
68
69 pass(Description) ->
70   report({success, explicit}, Description),
71   true.
72
73 %% @doc Mark the test as a failure unconditionally.
74
75 -spec fail(description()) ->
76   false.
77
78 fail(Description) ->
79   report({failure, explicit}, Description),
80   false.
81
82 %% @doc Check if `Value' is the same as `Expected'.
83
84 -spec is(value(), value(), description()) ->
85   Value :: value().
86
87 is(Value, Expected, Description) ->
88   case Value of
89     Expected -> report({success, Value}, Description);
90     _        -> report({failure, Value}, Description)
91   end,
92   Value.
93
94 %% @doc Check if `Value' is different than `Expected'.
95
96 -spec isnt(value(), value(), description()) ->
97   Value :: value().
98
99 isnt(Value, Expected, Description) ->
100   case Value of
101     Expected -> report({failure, Value}, Description);
102     _        -> report({success, Value}, Description)
103   end,
104   Value.
105
106 %% @doc Check if `Value' is equal (`==') to `Expected'.
107
108 -spec eq(value(), value(), description()) ->
109   Value :: value().
110
111 eq(Value, Expected, Description) ->
112   cmp(Value, '==', Expected, Description).
113
114 %% @doc Check if `Value' is not equal (`/=') to `Expected'.
115
116 -spec ne(value(), value(), description()) ->
117   Value :: value().
118
119 ne(Value, Expected, Description) ->
120   cmp(Value, '/=', Expected, Description).
121
122 %% @doc Compare `Value' and `Expected' using comparison operator.
123
124 -spec cmp(value(), cmp(), value(), description()) ->
125   Value :: value().
126
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
137   end,
138   % FIXME: better reporting for failures
139   case CmpResult of
140     true  -> report({success, Value}, Description);
141     false -> report({failure, Value}, Description)
142   end,
143   Value.
144
145 %% @doc Check if `Value' matches a regexp.
146
147 -spec like(value(), regexp(), description()) ->
148   Value :: value().
149
150 like(Value, Expected, Description) ->
151   % XXX: regular expression may be invalid, so prepare estap_server before
152   % running the regexp
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})
158   end,
159   Value.
160
161 %% @doc Check if `Value' not matches a regexp.
162
163 -spec unlike(value(), regexp(), description()) ->
164   Value :: value().
165
166 unlike(Value, Expected, Description) ->
167   % XXX: regular expression may be invalid, so prepare estap_server before
168   % running the regexp
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})
174   end,
175   Value.
176
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.
181
182 -spec matches(value(), match_fun(), description()) ->
183   Value :: value().
184
185 matches(Value, MatchSpec, Description) ->
186   TestRun = get_test_run(),
187   estap_server:running(TestRun, Description),
188   try
189     MatchSpec(Value),
190     estap_server:report_result(TestRun, {success, Value})
191   catch
192     error:function_clause ->
193       estap_server:report_result(TestRun, {failure, Value})
194   end,
195   Value.
196
197 %%%---------------------------------------------------------------------------
198
199 %% @doc Stop testing whatsoever because something terrible happened.
200 %%
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.
205
206 -spec bail_out(message()) ->
207   no_return().
208
209 bail_out(Message) ->
210   TestRun = get_test_run_or_parent(),
211   estap_server:bail_out(TestRun, Message),
212   exit('BAIL_OUT').
213
214 %% @doc Set the "no plan" plan for sub-tests.
215 %%   Calling this function may be safely skipped.
216 %%
217 %% @see all_ok/0
218
219 -spec no_plan() ->
220   ok.
221
222 no_plan() ->
223   _TestRun = get_test_run(),
224   ok.
225
226 %% @doc Set expected number of sub-tests.
227
228 -spec plan(pos_integer()) ->
229   ok.
230
231 plan(TestCount) when is_integer(TestCount) ->
232   TestRun = estap_server:subplan(TestCount, 1),
233   set_test_run(TestRun),
234   ok.
235
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.
239
240 -spec all_ok() ->
241   true | false.
242
243 all_ok() ->
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)).
248
249 %%%---------------------------------------------------------------------------
250
251 %% @doc Get a directory containing this test script.
252 %%
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
255 %%   an argument.
256
257 test_dir() ->
258   case get(test_dir) of
259     undefined -> erlang:error({undefined, test_dir});
260     Directory -> Directory
261   end.
262
263 %% @doc Get a subdirectory of the directory containing this test script.
264 %%
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
267 %%   an argument.
268
269 test_dir(Subdir) ->
270   filename:join(test_dir(), Subdir).
271
272 %%%---------------------------------------------------------------------------
273
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.
277 %%
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.
281 %%
282 %%   Normally diagnostic output goes to <i>STDERR</i>, but under TODO tests it
283 %%   goes to <i>STDOUT</i>.
284 %%
285 %% @TODO Make the diagnostic output go to <i>STDOUT</i> under TODO
286
287 -spec diag(message()) ->
288   ok.
289
290 diag(Message) ->
291   TestRun = get_test_run_or_parent(),
292   estap_server:warning(TestRun, Message).
293
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.
297 %%
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.
301 %%
302 %%   Normally diagnostic output goes to <i>STDERR</i>, but under TODO tests it
303 %%   goes to <i>STDOUT</i>.
304 %%
305 %% @TODO Make the diagnostic output go to <i>STDOUT</i> under TODO
306
307 -spec diag(message(), [info()]) ->
308   ok.
309
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]).
314
315 %% @doc Print a message.
316 %%
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.
320
321 -spec info(message()) ->
322   ok.
323
324 info(Message) ->
325   TestRun = get_test_run_or_parent(),
326   estap_server:info(TestRun, Message).
327
328 %% @doc Print a message with some context.
329 %%
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.
333
334 -spec info(message(), [info()]) ->
335   ok.
336
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]).
341
342 %% @doc Format a single info entry for printing it on screen.
343
344 -spec format_info(info()) ->
345   binary().
346
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>>.
353
354 %% @doc Format term so it can be printed to screen.
355 %%   Convenience wrapper for {@link io_lib:format/2}.
356 %%
357 %% @see info/2
358 %% @see diag/2
359
360 -spec explain(term()) ->
361   iolist().
362
363 explain(Term) ->
364   % no term should weigh 1MB
365   io_lib:print(Term, 1, 1024 * 1024, -1).
366
367 %%%---------------------------------------------------------------------------
368
369 %% @doc Set previously started {@link estap_server}.
370
371 set_test_run(TestRun) ->
372   put(estap_server, TestRun).
373
374 %% @doc Get associated {@link estap_server}, starting it if necessary.
375
376 get_test_run() ->
377   case get(estap_server) of
378     undefined ->
379       TestRun = estap_server:subplan(no_plan, 1),
380       put(estap_server, TestRun),
381       TestRun;
382     TestRun when is_pid(TestRun) ->
383       TestRun
384   end.
385
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.
388
389 get_test_run_or_parent() ->
390   case get(estap_server) of
391     undefined ->
392       % XXX: this must be set in `estap_test:run()'
393       get(estap_server_parent);
394     TestRun when is_pid(TestRun) ->
395       TestRun
396   end.
397
398 %% @doc Send a test report to {@link estap_server}.
399
400 -spec report({success | failure | dubious, Value :: value()}, description()) ->
401   ok.
402
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),
408   ok.
409
410 %%%---------------------------------------------------------------------------
411 %%% vim:ft=erlang:foldmethod=marker