6
7:- module(test_util_iso,
8 [ test/1, 9 test/2 10 ]). 11:- use_module(library(apply)). 12:- use_module(library(ansi_term)). 13
91
92report(brief).
93
94:- op(1200, fy, fixme). 95:- op(1110, xf, should_fail). 96:- op(1110, xfx, should_give). 97:- op(1110, xfx, should_output). 98:- op(1110, xfx, should_throw). 99:- op(1110, xfx, should_raise). 100:- op(1110, xfx, output). 101
105
106test(FileIn) :-
107 setup_call_cleanup(
108 open(FileIn, read, In),
109 test_stream(In, user_error),
110 close(In)).
111
112
117
118test(FileIn, FileOut) :-
119 setup_call_cleanup(
120 open(FileIn, read, In),
121 setup_call_cleanup(
122 open(FileOut, write, Out),
123 test_stream(In, Out),
124 close(Out)),
125 close(In)).
126
127 test_stream(In, Out) :-
128 stream_property(In, file_name(File)),
129 format(Out, '~N% Running ECLiPSe tests from file ~w~n', [File]),
130 counter_set(test_count, 0),
131 counter_set(non_test_count, 0),
132 counter_set(succeeded_test_count, 0),
133 counter_set(failed_test_count, 0),
134 counter_set(skipped_test_count, 0),
135 repeat,
136 catch(catch(read_term(In, Test,
137 [ module(test_util_iso)
138 ]), SyntaxError,
139 unexpected(Out, 0, valid_syntax, throw(SyntaxError))),
140 continue, fail),
141 source_location(_File, Line),
142 ( Test \== end_of_file ->
143 counter_inc(test_count),
144 counter_get(test_count, N),
145 catch(interpret_test(Test, t(File,Line,N), Out), continue, true),
146 fail
147 ;
148 counter_get(test_count, N),
149 counter_get(succeeded_test_count, TN),
150 counter_get(failed_test_count, FN),
151 counter_get(skipped_test_count, SN),
152 counter_get(non_test_count, NN),
153 format(Out, '~N% Finished tests from file ~w~n', [File]),
154 format(Out, '% ~D tests found.~n', [N]),
155 ( NN==0 -> true ; format(Out, '% ~D ignored as malformed.~n', [NN]) ),
156 format(Out, '% ~D tests succeeded.~n', [TN]),
157 ( FN==0 -> true ; format(Out, '% ~D tests failed.~n', [FN]) ),
158 ( SN==0 -> true ; format(Out, '% ~D tests skipped.~n', [SN]) )
159 ),
160 !,
161 FN =:= 0.
162
163
164interpret_test((fixme Test), Name, Stream) :- !,
165 fixme(Test, Name, Stream).
166interpret_test((Goal should_fail), Name, Stream) :- !,
167 should_fail(Goal, Name, Stream).
168interpret_test((Goal should_give Check), Name, Stream) :- !,
169 should_give(Goal, Check, Name, Stream).
170interpret_test((Goal should_output Check), Name, Stream) :- !,
171 should_output(Goal, Check, Name, Stream).
172interpret_test((Goal should_throw Ball), Name, Stream) :- !,
173 should_throw(Goal, Ball, Name, Stream).
174interpret_test((Goal should_raise Exception), Name, Stream) :- !,
175 ( Exception==4 -> Ball = error(instantiation_error,_)
176 ; Exception==5 -> Ball = error(type_error(_,_),_)
177 ; Exception==24 -> Ball = error(type_error(_,_),_)
178 ; Exception==6 -> Ball = error(domain_error(_,_),_)
179 ; Ball = error(_,_)
180 ),
181 should_throw(Goal, Ball, Name, Stream).
182interpret_test(_Goal, Name, Stream) :-
183 write(Stream, 'Non-test goal '), write(Stream, Name),
184 write(Stream, ': ignored'), nl(Stream),
185 counter_inc(non_test_count).
186
187
188
189fixme(Test) :-
190 current_output(Stream),
191 catch(fixme(Test, Test, Stream), continue, true).
192
193 fixme(_Test, Name, Stream) :-
194 write(Stream, 'Test '), write(Stream, Name),
195 write(Stream, ': skipped'), nl(Stream),
196 counter_inc(skipped_test_count),
197 throw(continue).
198
199
200
201Goal should_fail :-
202 current_output(Stream),
203 catch(should_fail(Goal, Goal, Stream), continue, true).
204
205 should_fail(Goal, Name, Stream) :-
206 ( catch(Goal, Ball, unexpected(Stream,Name,failure,throw(Ball))) ->
207 unexpected(Stream, Name, failure, success)
208 ;
209 expected_outcome(Stream, Name)
210 ).
211
212
213
214Goal should_give Check :-
215 current_output(Stream),
216 catch(should_give(Goal, Check, Goal, Stream), continue, true).
217
218
219 should_give(_Goal, Check, Name, Stream) :- \+ callable(Check), !,
220 unexpected(Stream, Name, success, illegal_check(Check)).
221
222 should_give(Goal, multiple_solutions(K,TotalCheck,SolutionCheck), Name, Stream) :- !,
223 counter_set(solutions, 0),
224 (
225 catch(Goal, Ball, unexpected(Stream,Name,'success or failure',throw(Ball))),
226 counter_inc(solutions),
227 ( counter_get(solutions, K), catch(SolutionCheck, _, fail) ->
228 fail 229 ;
230 unexpected(Stream, Name, success, failed_check(SolutionCheck))
231 )
232 ;
233 ( counter_get(solutions, K), catch(TotalCheck, _, fail) ->
234 expected_outcome(Stream, Name)
235 ;
236 unexpected(Stream,Name,success,failed_check(TotalCheck))
237 )
238 ).
239
240 should_give(Goal, Check, Name, Stream) :-
241 ( catch(Goal, Ball, unexpected(Stream,Name,success,throw(Ball))) ->
242 ( catch(Check, _, fail) ->
243 expected_outcome(Stream, Name)
244 ;
245 unexpected(Stream, Name, success, failed_check(Check))
246 )
247 ;
248 unexpected(Stream, Name, success, failure)
249 ).
250
251Goal should_output ExpectedText :-
252 current_output(Stream),
253 catch(should_output(Goal, ExpectedText, Goal, Stream), continue, true).
254
255 should_output(Goal, ExpectedText, Name, Stream) :-
256 ( catch(with_output_to(atom(OutputAtom), Goal), Ball,
257 unexpected(Stream,Name,Goal,success,throw(Ball))) ->
258 atom_chars(OutputAtom, OutputChars),
259 ( output_matches_expected(ExpectedText, OutputChars, OutputAtom) ->
260 expected_outcome(Stream, Name)
261 ;
262 most_readable_text(ExpectedText, MessageText),
263 unexpected(Stream, Name, Goal, output(MessageText), actual_output(OutputAtom))
264 )
265 ; unexpected(Stream, Name, Goal, success, failure)
266 ).
267
268 output_matches_expected(ExpectedText, OutputChars, OutputAtom) :-
269 ( var(ExpectedText)
270 ; ExpectedText == [], OutputChars == []
271 ; atom(ExpectedText), OutputAtom == ExpectedText
272 ; catch(atom_string(OutputAtom, ExpectedText), _, fail)
273 ; ExpectedText = [_|_],
274 ( subsumes_term(ExpectedText, OutputChars)
275 ; chars_codes(OutputChars, OutputCodes),
276 subsumes_term(ExpectedText, OutputCodes)
277 )
278 ), !.
279
280 most_readable_text(T, R) :- atom(T), !, R = T.
281 most_readable_text(T, R) :- catch(string(T),_,fail), !, R = T.
282 most_readable_text(T, R) :-
283 catch(atom_codes(A, T), _, fail),
284 !, R = A.
285 most_readable_text(T, R) :-
286 catch(atom_chars(A, T), _, fail),
287 !, R = A.
288 most_readable_text(T, T).
289
290 chars_codes([], []).
291 chars_codes([Char|Chars], [Code|Codes]) :-
292 char_code(Char, Code),
293 chars_codes(Chars, Codes).
294
295Goal should_throw Ball :-
296 current_output(Stream),
297 catch(should_throw(Goal, Ball, Goal, Stream), continue, true).
298
299 should_throw(Goal, Expected, Name, Stream) :-
300 ( catch(with_output_to(string(_), Goal), Ball,
301 ( subsumes_term(Expected,Ball) ->
302 expected_outcome(Stream, Name)
303 ;
304 unexpected(Stream, Name, throw(Expected), throw(Ball))
305 )
306 )
307 ->
308 unexpected(Stream, Name, throw(Expected), success)
309 ;
310 unexpected(Stream, Name, throw(Expected), failure)
311 ).
312
313
314
315expected_outcome(Stream, t(_File,Line,TestNo)) =>
316 ( report(brief)
317 -> put_char(Stream, '.'),
318 flush_output(Stream)
319 ; format(Stream, '~NTest ~w at line ~d: OK~n', [TestNo, Line])
320 ),
321 counter_inc(succeeded_test_count),
322 throw(continue).
323
324unexpected(Stream, t(File,Line,TestNo), Expected, Outcome) =>
325 ansi_format(Stream, error,
326 '~NTest ~w at ~w:~w: ~n~texpected ~12|~q,~n~tgot ~12|~q~n',
327 [TestNo, File, Line, Expected, Outcome]),
328 counter_inc(failed_test_count),
329 throw(continue).
330
331unexpected(Stream, t(File,Line,TestNo), Goal, Expected, Outcome) =>
332 ansi_format(Stream, error, 'Test ~w at ~w:~w:~n', [TestNo,File,Line]),
333 ansi_format(Stream, error, ' Expected ~p~n', [Expected]),
334 ansi_format(Stream, error, ' Got ~p~n', [Outcome]),
335 ansi_format(Stream, error, ' Goal ~p~n', [Goal]),
336 counter_inc(failed_test_count),
337 throw(continue).
338
342
343:- dynamic(counter/2). 344
345counter_set(Name, Value) :-
346 retractall(counter(Name,_)),
347 asserta(counter(Name,Value)).
348
349counter_inc(Name) :-
350 ( retract(counter(Name,N0)) -> N1 is N0+1 ; N1 = 1 ),
351 asserta(counter(Name,N1)).
352
353counter_get(Name, Value) :-
354 counter(Name, Value)