1 | | % (c) 2009-2024 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, |
2 | | % Heinrich Heine Universitaet Duesseldorf |
3 | | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) |
4 | | |
5 | | :- module(self_check, |
6 | | [assert_pre/2, assert_post/2,pp_mnf/1, pp_cll/1, mnf/1, mnf/2, |
7 | | mnf_det/1, det_call/1, force_det_call/1, |
8 | | assert_must_succeed/1,assert_must_fail/1, |
9 | | assert_must_succeed_multiple/1, assert_must_succeed_any/1, |
10 | | must_fail/1, must_succeed/1, must_succeed_without_residue/1, |
11 | | must_succeed_without_residue_and_time_out/1, |
12 | | |
13 | | run_time_type_check_mode/1, |
14 | | turn_on_run_time_type_checks/0,turn_off_run_time_type_checks/0, |
15 | | |
16 | | get_module_list/1, |
17 | | |
18 | | perform_self_check/0, perform_self_check/1, perform_self_check/2, |
19 | | perform_verbose_self_check/0, perform_verbose_self_check/1, |
20 | | portray_self_checks/0, |
21 | | |
22 | | disable_interaction_on_errors/0, |
23 | | |
24 | | % time_out_call/2, time_out_with_factor_call/3, time_out_call/1, % now in tools_timeout |
25 | | |
26 | | check_deterministic/1, check_det/1, |
27 | | |
28 | | list_skeleton/1, |
29 | | |
30 | | self_checks_exist/0, |
31 | | check_eq/2, |
32 | | check_eqeq/2 |
33 | | |
34 | | ]). |
35 | | |
36 | | :- meta_predicate nonvar_call(0). |
37 | | :- meta_predicate assert_post(0,0). |
38 | | :- meta_predicate(get_call_module(0, *)). |
39 | | :- meta_predicate assert_must_succeed_any(0). |
40 | | :- meta_predicate assert_must_succeed(0). |
41 | | :- meta_predicate assert_must_succeed_multiple(0). |
42 | | :- meta_predicate assert_must_fail(0). |
43 | | %:- meta_predicate kernel_waitflags:assert_must_abort_wf(0,*). |
44 | | :- meta_predicate pp_mnf(0), pp_cll(0), mnf(0), mnf(-,0), det_call(0). |
45 | | :- meta_predicate mnf_call(0), mnf_call_with_pp(-,0), prepost_mnf_call(0). |
46 | | :- meta_predicate prepost_call(0). |
47 | | :- meta_predicate check_exception_call(0). |
48 | | :- meta_predicate rt_timeout_call(0). |
49 | | :- meta_predicate mnf_det(0). |
50 | | :- meta_predicate force_det_call(0). |
51 | | :- meta_predicate residue_check_call(0). |
52 | | :- meta_predicate must_fail(0). |
53 | | :- meta_predicate must_succeed(0). |
54 | | :- meta_predicate must_succeed_without_residue_and_time_out(0). |
55 | | :- meta_predicate must_succeed_without_residue(0). |
56 | | :- meta_predicate must_succeed_multiple_without_residue(0). |
57 | | :- meta_predicate safe_call(0). |
58 | | :- meta_predicate check_deterministic(0). |
59 | | :- meta_predicate check_det(0). |
60 | | :- meta_predicate check_det2(0,-). |
61 | | |
62 | | :- use_module(debugging_calls). |
63 | | %:- disable_debugging_calls. |
64 | | |
65 | | :- use_module(module_information,[module_info/2]). |
66 | | :- module_info(group,testing). |
67 | | :- module_info(description,'This module provides predicates to define and run unit tests.'). |
68 | | |
69 | | :- use_module(tools_printing,[format_with_colour/4, print_goal/1]). |
70 | | |
71 | | % first code which asserts pre-, post-conditions and self-checks |
72 | | |
73 | | % first a copy of print_error; so that this bit of self-check does not rely on other modules |
74 | | my_print_error(Error) :- |
75 | | (var(Error) |
76 | | -> print_message(error,'_') |
77 | | ; write(user_error,'! '),write_term(user_error,Error,[max_depth(50),numbervars(true)]),nl(user_error) |
78 | | ). |
79 | | |
80 | | my_portray_error(Query) :- safe_numbervars(Query,0,_), |
81 | | write_term(user_error,Query,[portrayed(true),numbervars(true),max_depth(50),indented(true)]),fail. |
82 | | my_portray_error(_). |
83 | | %my_portray_error(Query) :- portray_clause(user_error,Query). % goes into loop for cyclic terms |
84 | | |
85 | | :- dynamic pre_condition/2, post_condition/2. |
86 | | :- meta_predicate assert_pre(0,0). |
87 | | assert_pre(X,Pre) :- % print(adding_pre(X)),nl, |
88 | | (nonvar_call(X),nonvar_call(Pre) |
89 | | -> retractall(pre_condition(X,_)),assertz(pre_condition(X,Pre)) |
90 | | ; my_print_error('### illegal variable(s) in: '), |
91 | | my_print_error(assert_pre(X,Pre)),fail |
92 | | ). |
93 | | assert_post(X,Post) :- |
94 | | (nonvar_call(X),nonvar_call(Post) |
95 | | -> retractall(post_condition(X,_)),assertz(post_condition(X,Post)) |
96 | | ; my_print_error('### illegal variable(s) in: '), |
97 | | my_print_error(assert_post(X,Post)),fail |
98 | | ). |
99 | | |
100 | | nonvar_call(Call) :- |
101 | | nonvar(Call), |
102 | | (Call = M:P -> nonvar(M), nonvar(P) ; true). |
103 | | |
104 | | :- dynamic self_check/4. |
105 | | :- dynamic self_check_module/2. |
106 | | :- volatile self_check/4, self_check_module/2. % this means that self-check cannot be run in the compiled versions ! |
107 | | |
108 | | self_check_module(Module) :- self_check_module(Module,_NrTests). |
109 | | |
110 | | % increase number of tests for a module and return new number as id |
111 | | inc_self_check_module(Module,Res) :- |
112 | | (retract(self_check_module(Module,N1)) -> true ; N1=0), |
113 | | Nr is N1+1, |
114 | | assertz(self_check_module(Module,Nr)), |
115 | | Res = Nr. |
116 | | |
117 | | % used to check if there are self checks. if not, tcl/tk interface does not show the button |
118 | | self_checks_exist :- self_check(_,_,_,_). |
119 | | |
120 | | % has a strange behaviour; investigate :- meta_predicate self_check(:,*). |
121 | | |
122 | ? | self_check(X,Module) :- self_check(X,Module,_,_). |
123 | | |
124 | | :- load_files(library(system), [when(compile_time), imports([environ/2])]). |
125 | | :- if(environ(prob_release,true)). |
126 | | add_self_check(_,_) :- !. % comment in to not include self-checks in distribution |
127 | | :- endif. |
128 | | add_self_check(Module,X) :- |
129 | | (nonvar(X) -> |
130 | ? | (self_check(X,Module) -> true |
131 | | ; inc_self_check_module(Module,Nr), |
132 | | get_current_term_position(Line,_Col), |
133 | | assertz(self_check(X,Module,Nr,Line)) |
134 | | ) |
135 | | ; |
136 | | my_print_error('### trying to assert variable as self_check: '),my_print_error(X),fail |
137 | | ). |
138 | | |
139 | | get_current_term_position(Line,Col) :- |
140 | | %prolog_load_context(file, File), |
141 | | prolog_load_context(term_position,TPos), |
142 | | !, |
143 | | (stream_position_data(line_count,TPos,LineM1) -> Line is LineM1+1 ; Line='?'), |
144 | | (stream_position_data(line_position,TPos,Col) -> true ; Col='?'). |
145 | | get_current_term_position('?','?'). |
146 | | |
147 | | |
148 | | :- if(predicate_property(current_logtalk_flag(_, _), _)). |
149 | | get_call_module(_, Module) :- |
150 | | sender(Module). |
151 | | :- else. |
152 | | get_call_module(Module:_, Module). |
153 | | :- endif. |
154 | | |
155 | | assert_must_succeed_any(Call) :- |
156 | | get_call_module(Call,M), |
157 | | add_self_check(M,must_succeed(Call)). |
158 | | assert_must_succeed(Call) :- |
159 | | get_call_module(Call,M), |
160 | | add_self_check(M,must_succeed_without_residue(Call)). |
161 | | assert_must_succeed_multiple(Call) :- |
162 | | get_call_module(Call,M), |
163 | | add_self_check(M,must_succeed_multiple_without_residue(Call)). |
164 | | assert_must_fail(Call) :- |
165 | | get_call_module(Call,M), |
166 | | add_self_check(M,must_fail(Call)). |
167 | | |
168 | | |
169 | | % ------------------------ |
170 | | |
171 | | :- use_module(debug). |
172 | | :- use_module(tools_printing). |
173 | | :- use_module(error_manager). |
174 | | :- use_module(junit_tests). |
175 | | :- use_module(library(lists)). |
176 | | :- use_module(tools_meta,[call_residue/2,safe_numbervars/3]). |
177 | | |
178 | | /* Example use: |
179 | | |
180 | | :- assert_pre(user:b_fd_type(_G,_L,_U),true). |
181 | | :- assert_post(user:b_fd_type(G,L,U),(atomic(G),(integer(L),integer(U)))). |
182 | | |
183 | | */ |
184 | | |
185 | | |
186 | | |
187 | | /* some auxilary predicates that can be used for pre,post conditions: */ |
188 | | |
189 | | % used in some pre- and post-conditions |
190 | | list_skeleton(X) :- |
191 | | (nonvar(X),list_skel2(X) -> true |
192 | | ; (my_print_error('### not list skeleton: '), my_print_error(X),fail)). |
193 | | list_skel2([]). |
194 | | list_skel2([_H|T]) :- nonvar(T), list_skel2(T). |
195 | | |
196 | | |
197 | | |
198 | | :- dynamic run_time_type_check_mode/1. |
199 | | run_time_type_check_mode(on). |
200 | | |
201 | | turn_on_run_time_type_checks :- |
202 | | retract(run_time_type_check_mode(_)), |
203 | | assertz(run_time_type_check_mode(on)). |
204 | | |
205 | | turn_off_run_time_type_checks :- |
206 | | retract(run_time_type_check_mode(_)), |
207 | | assertz(run_time_type_check_mode(off)). |
208 | | |
209 | | |
210 | | /* ===================================================== */ |
211 | | |
212 | | :- dynamic prepost_no_error_so_far/0. |
213 | | |
214 | | prepost_no_error_so_far. % :- fail. |
215 | | /* as errors are now displayed by error_manager, no need for interaction ?? */ |
216 | | |
217 | | disable_interaction_on_errors :- |
218 | | retractall(prepost_no_error_so_far). |
219 | | |
220 | | |
221 | | /* ===================================================== */ |
222 | | |
223 | | :- register_debugging_calls([pp_mnf(*), pp_cll(*), mnf(*), mnf(-,*), det_call(*)]). |
224 | | |
225 | | pp_mnf(X) :- prepost_mnf_call(X). |
226 | | pp_cll(X) :- prepost_call(X). |
227 | | mnf(X) :- mnf_call(X). |
228 | | mnf(ProgramPoint,X) :- mnf_call_with_pp(ProgramPoint,X). |
229 | | |
230 | | |
231 | | /* ===================================================== */ |
232 | | |
233 | | |
234 | | :- use_module(library(timeout)). |
235 | | |
236 | ? | rt_timeout_call(Call) :- run_time_type_check_mode(off),!,check_exception_call(Call). |
237 | | rt_timeout_call(Call) :- time_out(check_exception_call(Call),5000,TimeOutRes), |
238 | | (TimeOutRes = success -> true ; inc_error_count, add_error(self_check,'### TIMEOUT: ',Call),fail). |
239 | | |
240 | | % just catch exceptions and print them nicely |
241 | | check_exception_call(X) :- |
242 | ? | catch(call(X), Error, ( |
243 | | format_with_colour(user_error,[red,bold],'! *** EXCEPTION *** :~n',[]), |
244 | | write('! '),print_numbervars(X),nl, |
245 | | write_exception_error(Error),nl, |
246 | | throw(Error) |
247 | | )). |
248 | | |
249 | | write_exception_error(error(existence_error(procedure,CALL),_)) :- |
250 | | format_with_colour(user_error,[red,bold],'! EXISTENCE ERROR: ~w~n',[CALL]),fail. |
251 | | write_exception_error(Error) :- write('! '),write_term(Error,[max_depth(20),numbervars(true)]),nl. |
252 | | |
253 | | print_numbervars(X) :- safe_numbervars(X,0,_), print(X),fail. |
254 | | print_numbervars(_). |
255 | | |
256 | | prepost_call(X) :- |
257 | | /* print(pre(X)),nl, */ |
258 | | (verify_pre(X) -> true ; my_print_error(verify_pre_failed(X))), |
259 | | rt_timeout_call(X), |
260 | | /* print(post(X)),nl, */ |
261 | | (verify_post(X) -> true ; my_print_error(verify_post_failed(X))). |
262 | | |
263 | | verify_pre(Call) :- |
264 | | (run_time_type_check_mode(off) -> true |
265 | | ; pre_condition(Call,Pre) -> |
266 | | ( \+ rt_timeout_call(Pre) |
267 | | -> get_predicate_arity(Call,Pred,Arity), |
268 | | add_error(verify_pre,'### PRE-CONDITION ERROR OCCURRED: ',Pred/Arity), |
269 | | add_error(verify_pre,'### CALL: ',Call),nl, |
270 | | prepost_user_interaction |
271 | | ; true |
272 | | ) |
273 | | ; get_predicate_arity(Call,Pred,Arity), |
274 | | add_error(verify_pre,'### No PRE-CONDITION for ',Pred/Arity), |
275 | | print_term_summary(Call), |
276 | | prepost_user_interaction |
277 | | ). |
278 | | |
279 | | verify_post(Call) :- |
280 | | (run_time_type_check_mode(off) -> true |
281 | | ; post_condition(Call,Post) -> |
282 | | ( \+ rt_timeout_call(Post) |
283 | | -> get_predicate_arity(Call,Pred,Arity), |
284 | | add_error(verify_post,'### POST-CONDITION ERROR OCCURRED: ',Pred/Arity), |
285 | | add_error(verify_post,'### CALL: ',Call),nl, |
286 | | prepost_user_interaction |
287 | | ; true |
288 | | ) |
289 | | ; get_predicate_arity(Call,Pred,Arity), |
290 | | add_error(verify_post,'### No POST-CONDITION for ',Pred/Arity), |
291 | | prepost_user_interaction |
292 | | ). |
293 | | |
294 | | |
295 | | prepost_user_interaction :- prepost_no_error_so_far,!, |
296 | | my_print_error('### => Stop at next error (y/n/halt/trace) => '), |
297 | | read(Answer), |
298 | | (Answer='y' -> true |
299 | | ; Answer='halt' -> halt |
300 | | ; Answer='trace' -> trace |
301 | | ; retract(prepost_no_error_so_far)). |
302 | | prepost_user_interaction. |
303 | | |
304 | | |
305 | | get_predicate_arity(':'(Module,Call),Res,Arity) :- !,Res=Module:Pred, |
306 | | functor(Call,Pred,Arity). |
307 | | get_predicate_arity(Call,Pred,Arity) :- |
308 | | functor(Call,Pred,Arity). |
309 | | |
310 | | /* ===================================================== */ |
311 | | |
312 | | prepost_mnf_call(X) :- % print(prepost_mnf_call(X)),nl, |
313 | | (run_time_type_check_mode(off) |
314 | | -> call(X) |
315 | | ; if(prepost_call(X),true, |
316 | | (add_error(mnf,'### WARNING CALL HAS FAILED: ',X), |
317 | | print_call(X), |
318 | | prepost_user_interaction, |
319 | | fail) |
320 | | ) |
321 | | ). |
322 | | |
323 | | mnf_call(X) :- |
324 | | (run_time_type_check_mode(off) |
325 | | -> call(X) |
326 | | ; if(call(X),true, |
327 | | (add_error(mnf,'### WARNING CALL HAS FAILED: ',X), |
328 | | print_call(X), |
329 | | prepost_user_interaction, |
330 | | fail) |
331 | | ) |
332 | | ). |
333 | | |
334 | | print_call(X) :- |
335 | | print('### Call: '),print_quoted_with_max_depth(X,10),nl, |
336 | | print('### Summary: '),print_term_summary(X),nl, |
337 | | print('### Predicate: '), |
338 | | (X = ':'(M,C) -> (print(M),print(':')) ; C=X), |
339 | | functor(C,F,N), |
340 | | print(F), print('/'), print(N),nl. |
341 | | |
342 | | |
343 | | mnf_call_with_pp(ProgramPoint,X) :- |
344 | | (run_time_type_check_mode(off) |
345 | | -> call(X) |
346 | | ; if(rt_timeout_call(X),true, |
347 | | (add_error(mnf,'### WARNING CALL HAS FAILED: ',(X,ProgramPoint)), |
348 | | print_call(X), |
349 | | print('### at program point:'),print(ProgramPoint),nl, |
350 | | prepost_user_interaction, |
351 | | fail) |
352 | | ) |
353 | | ). |
354 | | |
355 | | :- volatile found_det_sol/4. |
356 | | :- dynamic found_det_sol/4. |
357 | | |
358 | | mnf_det(X) :- |
359 | | (run_time_type_check_mode(off) |
360 | | -> call(X) |
361 | | ; (get_functor_module(X,Module,F,Arity), |
362 | | retractall(found_det_sol(F,Arity,Module,_)), /* cannot be nested for same predicate !*/ |
363 | | copy_term(X,CopyX), |
364 | | mnf_call(X), |
365 | | (found_det_sol(F,Arity,Module,Previous) -> |
366 | | (add_error(mnf,'### WARNING CALL HAS MULTIPLE SOLUTIONS: ',CopyX),nl, |
367 | | print('### Solution 1: '),print_quoted_with_max_depth(Previous,60),nl,nl, |
368 | | print('### Solution 2: '),print_quoted_with_max_depth(X,60),nl,nl, |
369 | | prepost_user_interaction) |
370 | | ; assertz(found_det_sol(F,Arity,Module,X)) |
371 | | ) |
372 | | ) |
373 | | ). |
374 | | |
375 | | % det_call(';'(X=a,X=b)). |
376 | | get_functor_module(Module:Call,Module,F,Arity) :- !, functor(Call,F,Arity). |
377 | | get_functor_module(Call,unknown,F,Arity) :- functor(Call,F,Arity). |
378 | | |
379 | | det_call(X) :- |
380 | | (run_time_type_check_mode(off) |
381 | | -> call(X) |
382 | | ; force_det_call(X) |
383 | | ). |
384 | | |
385 | | residue_check_call(X) :- |
386 | | copy_term(X,CX), |
387 | ? | call_residue(X,CallResidue), |
388 | | (maplist(acceptable_residue,CallResidue) -> true |
389 | | ; add_error(must_succeed_without_residue,'### Call has residue: ',X), |
390 | | add_error(must_succeed_without_residue,'### Residue: ',CallResidue), |
391 | | inc_error_count, |
392 | | print_goal(CallResidue),nl, |
393 | | safe_numbervars(CX,0,_), |
394 | | format('### Original call: ~w~n',[CX]) |
395 | | ). |
396 | | |
397 | | % if we use ground_det_wait_flag: we can have these residues |
398 | | acceptable_residue(kernel_waitflags:copy_wfe_to_inner(_,_)). |
399 | | % Default representation returned by SWI for attributes from modules |
400 | | % that don't define attribute_goals//1. |
401 | | acceptable_residue(put_attr(_,_,_)). |
402 | | acceptable_residue((A,B)) :- acceptable_residue(A),acceptable_residue(B). |
403 | | |
404 | | force_det_call(X) :- |
405 | | get_functor_module(X,Module,F,Arity), |
406 | | retractall(found_det_sol(F,Arity,Module,_)), /* cannot be nested for same predicate !*/ |
407 | | copy_term(X,CopyX), |
408 | | %print(det_call(X)),nl, |
409 | | residue_check_call(X), % was rt_timeout_call(X) |
410 | | (found_det_sol(F,Arity,Module,Previous) -> |
411 | | (add_error(mnf,'### WARNING CALL HAS MULTIPLE SOLUTIONS: ',CopyX),nl, |
412 | | print('### Solution 1: '),print_quoted_with_max_depth(Previous,60),nl,nl, |
413 | | print('### Solution 2: '),print_quoted_with_max_depth(X,60),nl,nl, |
414 | | prepost_user_interaction) |
415 | | ; assertz(found_det_sol(F,Arity,Module,X)) |
416 | | ). |
417 | | |
418 | | /* ===================================================== */ |
419 | | |
420 | | must_fail(X) :- |
421 | | copy_term(X,Y), |
422 | | rt_timeout_call(X),!, |
423 | | %(safe_numbervars(Y,0,_) -> true ; true), % portray already does a numbervars |
424 | | inc_error_count(Module,_NrErr), |
425 | | add_error(must_fail,'Unit Test Failed, a call unexpectedly succeeded in module:',Module), |
426 | | my_print_error('! The call: '), |
427 | | my_portray_error(Y), |
428 | | my_print_error('! should have failed but succeeded with:'), |
429 | | my_portray_error(X). |
430 | | must_fail(_X). |
431 | | |
432 | | |
433 | | |
434 | | must_succeed(X) :- %print(must_suceed(X)),nl, |
435 | ? | \+ rt_timeout_call(X),!, |
436 | | %(safe_numbervars(X,0,_) -> true ; true), % portray already does a numbervars |
437 | | inc_error_count(Module,_NrErr), |
438 | | add_error(must_succeed,'Unit Test Failed, a call unexpectedly failed in module:',Module), |
439 | | my_print_error('! The call failed but should have succeeded: '), |
440 | | my_portray_error(X), |
441 | | %trace, X, % and comment out safe_numbervars above |
442 | | nl. |
443 | | must_succeed(_X). % :- print(ok_must_succeed(_X)),nl. |
444 | | |
445 | | :- volatile found_must_succeed_sol/1, found_id/1. |
446 | | :- dynamic found_must_succeed_sol/1, found_id/1. |
447 | | found_id(0). |
448 | | get_found_id(Nr) :- retract(found_id(Nr)), N1 is Nr+1, assertz(found_id(N1)). |
449 | | reset_found_id :- retractall(found_must_succeed_sol(_)), |
450 | | retractall(found_id(_)), assertz(found_id(0)). |
451 | | |
452 | | must_succeed_without_residue_and_time_out(X) :- |
453 | ? | time_out(check_exception_call(X),2000,TimeOutRes), |
454 | | (TimeOutRes = success -> true |
455 | | ; inc_error_count, |
456 | | add_error(self_check,'### TIMEOUT: ',X), portray_clause(X),nl, |
457 | | fail). |
458 | | |
459 | | must_succeed_without_residue(X) :- get_found_id(ID), |
460 | | must_succeed(residue_check_call(X)), |
461 | | retractall(found_must_succeed_sol(ID)), |
462 | | %residue_check_call(X), |
463 | | (found_must_succeed_sol(ID) |
464 | | -> add_error(must_succeed_without_residue,'### Self-Check has multiple solutions: ',X), |
465 | | inc_error_count, |
466 | | ! |
467 | | ; assertz(found_must_succeed_sol(ID)),fail |
468 | | ). |
469 | | must_succeed_without_residue(_). |
470 | | |
471 | | :- public must_succeed_multiple_without_residue/1. % used by assert_must_succeed_multiple |
472 | | must_succeed_multiple_without_residue(X) :- get_found_id(ID), |
473 | | must_succeed(X), |
474 | | retractall(found_must_succeed_sol(ID)), |
475 | ? | call_residue(X,CallResidue), |
476 | | (maplist(acceptable_residue,CallResidue) -> true -> true |
477 | | ; add_error(must_succeed_multiple_without_residue,'### Self-Check has residue: ',X), |
478 | | add_error(must_succeed_multiple_without_residue,'### Residue: ',CallResidue), |
479 | | inc_error_count, |
480 | | print_goal(CallResidue),nl |
481 | | ), |
482 | | (found_must_succeed_sol(ID) |
483 | | -> (true,!) |
484 | | ; assertz(found_must_succeed_sol(ID)),fail |
485 | | ). |
486 | | must_succeed_multiple_without_residue(X) :- |
487 | | inc_error_count, |
488 | | add_error(must_succeed_multiple_without_residue, |
489 | | '### Self-Check did not succeed multiple times: ',X). |
490 | | |
491 | | |
492 | | |
493 | | safe_call(X) :- |
494 | | catch(call(X), Exception, ( |
495 | | print(exception(X,Exception)),nl,nl, |
496 | | inc_error_count, |
497 | | add_error(safe_call,'### Exception occurred during self-check: ',X:Exception) |
498 | | )). |
499 | | |
500 | | :- dynamic starttime/1. |
501 | | |
502 | | get_module_list(ML) :- findall(Module,self_check_module(Module),Modules), sort(Modules,ML). |
503 | | |
504 | | |
505 | | |
506 | | |
507 | | :- dynamic errors_in_module/2, current_module_under_test/1, tests_in_module/2. |
508 | | set_current_module(M) :- var(M),!, my_print_error('Module is variable'). |
509 | | set_current_module(M) :- |
510 | | retractall(current_module_under_test(_)), |
511 | | assertz(current_module_under_test(M)). |
512 | | get_error_count(Module,Nr) :- errors_in_module(Module,Nr), !. |
513 | | get_error_count(_,0). |
514 | | inc_error_count :- inc_error_count(_,_). |
515 | | inc_error_count(Module,N1) :- current_module_under_test(Module),!, |
516 | | (retract(errors_in_module(Module,Nr)) -> true ; Nr=0), |
517 | | N1 is Nr+1, |
518 | | assertz(errors_in_module(Module,N1)). |
519 | | inc_error_count(unknown,1) :- print('No current module.'),nl. |
520 | | |
521 | | :- dynamic unit_test_failed_in_module/2. |
522 | | register_failure(Module,TestNr) :- assertz(unit_test_failed_in_module(Module,TestNr)). |
523 | | |
524 | | print_error_summary :- errors_in_module(Module,Nr), |
525 | | findall(TestNr,unit_test_failed_in_module(Module,TestNr),List), |
526 | | length(List,Len), |
527 | | format('Module ~w has ~w errors and ~w failed test(s): ~w.~n',[Module,Nr,Len,List]), |
528 | | fail. |
529 | | print_error_summary. |
530 | | |
531 | | print_summary :- findall(T,tests_in_module(_,T),NrTests), |
532 | | length(NrTests,NrModules), |
533 | | sumlist(NrTests,TotTests), |
534 | | format('Number of Unit Tests: ~w in ~w modules~n',[TotTests,NrModules]). |
535 | | |
536 | | reset_error_summary :- |
537 | | retractall(errors_in_module(_,_)), |
538 | | retractall(unit_test_failed_in_module(_,_)), |
539 | | retractall(tests_in_module(_,_)). |
540 | | |
541 | | run_module_tests(Module,Results,Options) :- |
542 | | findall(testcase(Module,TestNr,Line,X), included_self_check(X,Module,TestNr,Line,Options), Calls), |
543 | | length(Calls,Len), |
544 | | assertz(tests_in_module(Module,Len)), |
545 | | %maplist(check_test_case, Calls, Results). |
546 | | check_test_cases(Calls,Results,0,Module,Options). |
547 | | |
548 | | included_self_check(X,Module,TestNr,Line,Options) :- |
549 | ? | self_check(X,Module,TestNr,Line), |
550 | | (member(run_only_nr(Nr),Options) -> TestNr=Nr ; true). |
551 | | |
552 | | |
553 | | check_test_cases([],[],Nr,Module,_Options) :- |
554 | | format('Number of unit tests run in ~w: ~w~n',[Module,Nr]). |
555 | | check_test_cases([H|T],[V|VT],Nr,Module,Options) :- |
556 | | N1 is Nr+1, |
557 | | get_error_count(Module,NrBefore), |
558 | | check_test_case(H,N1,V,Options), |
559 | | get_error_count(Module,NrAfter), |
560 | | (NrBefore=NrAfter -> true |
561 | | % ; NrBefore > 0 -> true % we already have a previous unit test failure |
562 | | ; H=testcase(_,TestNr,LineNr,_Call), |
563 | | format_with_colour(user_error,[red,bold], |
564 | | '~n*** UNIT TEST ~w FAILED in module ~w (line ~w)~n',[TestNr,Module,LineNr]), |
565 | | format_with_colour(user_error,[red,bold], |
566 | | '*** Rerun this test with: probcli -selfcheck_module ~w:~w~n~n',[Module,TestNr]), |
567 | | register_failure(Module,TestNr) |
568 | | %,portray_testcase(H) |
569 | | %%% ,trace %%% |
570 | | ), |
571 | | check_test_cases(T,VT,N1,Module,Options). |
572 | | |
573 | | :- use_module(tools_printing,[start_terminal_colour/2, reset_terminal_colour/1]). |
574 | | portray_testcase(testcase(Module,TestNr,Line,Call)) :- !, |
575 | | start_terminal_colour([blue],user_error), |
576 | | format(user_error,'UNIT TEST ~w in module ~w at line ~w:~n :- ',[TestNr,Module,Line]), |
577 | | portray_clause(user_error,Call),nl, |
578 | | reset_terminal_colour(user_error). |
579 | | portray_testcase(X) :- print(unknown_testcase(X)),nl. |
580 | | |
581 | | |
582 | | check_test_case(testcase(Module,Nr,Line,Call), TotNr, Verdict,Options) :- |
583 | | (member(silent,Options) -> true ; format('Running unit test ~w in module ~w (line ~w)~n',[Nr,Module,Line])), |
584 | | set_error_context(unit_test_context(Module,TotNr,Line,Call)), |
585 | | statistics(runtime,[T1,_]), |
586 | | (member(verbose,Options) -> portray_clause(Call) ; true), |
587 | | safe_call(Call), |
588 | | flush_output, |
589 | | statistics(runtime,[T2,_]), |
590 | | Time is T2-T1, |
591 | | (member(verbose,Options) -> format('Runtime for unit test ~w: ~w ms~n',[TotNr,Time]) |
592 | | ; Time>20 -> format('*~w ms*~n',[Time]) ; true), |
593 | | (Time>2000 -> format_with_colour(user_output,[blue],'Warning: long unit test ~w: ~w ms in module ~w (line ~w)~n ~w~n',[TotNr,Time,Module,Line,Call]) ; true), |
594 | | (get_all_errors_and_reset(Errors) -> V=error(Errors) ; V=pass), |
595 | | create_junit_result(Call, Time, V, Verdict). |
596 | | |
597 | | |
598 | | perform_self_check(M) :- perform_self_check(M,[]). |
599 | | perform_self_check :- perform_self_check(_). |
600 | | perform_verbose_self_check :- perform_verbose_self_check(_). |
601 | | perform_verbose_self_check(M) :- perform_self_check(M,[verbose]). |
602 | | |
603 | | :- use_module(eventhandling,[announce_event/1]). |
604 | | perform_self_check(Module,Options) :- nl, reset_found_id, |
605 | | reset_error_summary, |
606 | | announce_event(start_unit_tests), |
607 | | flush_output, |
608 | | print('% '), |
609 | | retractall(starttime(_)), |
610 | | statistics(runtime,[Start,_]), assertz(starttime(Start)), |
611 | ? | if(self_check_module(Module,NrTests),true, |
612 | | (my_print_error(illegal_module(Module)),inc_error_count, |
613 | | fail) |
614 | | ), |
615 | | set_current_module(Module), |
616 | | (member(silent,Options) -> true ; format_with_colour(user_output,[blue],'~n~nPerforming ~w unit tests for module ~w~n',[NrTests,Module])), |
617 | | run_module_tests(Module,TestResults,Options), |
618 | | print_junit(['Selfcheck',Module], TestResults), |
619 | | fail. |
620 | | perform_self_check(_,_) :- |
621 | | statistics(runtime,[End,_]), |
622 | | starttime(Start), Tot is End-Start, |
623 | | nl, print('Runtime for Performing Unit Tests: '), print(Tot), print(' ms'), |
624 | | nl, |
625 | | announce_event(stop_unit_tests), |
626 | | clear_error_context, |
627 | | print_summary, |
628 | | (errors_in_module(_,_) |
629 | | -> format_with_colour(user_error,[red,bold],'~n! Unit Tests FAILED !!!~n',[]), |
630 | | print_error_summary, |
631 | | fail |
632 | | ; true), |
633 | | format_with_colour(user_output,[green],'~nUnit Tests Successful.~n',[]), |
634 | | flush_output. |
635 | | |
636 | | |
637 | | portray_self_checks :- portray_self_checks(_). |
638 | | |
639 | | /* self_check:portray_self_checks */ |
640 | | portray_self_checks(Module) :- |
641 | | self_check_module(Module,NrTests), |
642 | | format_with_colour(user_output,[blue],'~n~nUnit Tests (~w) for Module ~w~n',[NrTests,Module]), |
643 | | self_check(X,Module,Nr,Line), |
644 | | format_with_colour(user_output,[blue],' ~w (line ~w): ',[Nr, Line]), |
645 | | portray_clause(X), |
646 | | fail. |
647 | | portray_self_checks(_) :- nl. |
648 | | |
649 | | % ------------------------ |
650 | | |
651 | | :- dynamic det_counter/1. |
652 | | det_counter(0). |
653 | | check_deterministic(Call) :- |
654 | | retract(det_counter(X)), X1 is X+1, assertz(det_counter(X1)), |
655 | | check_det2(Call,X). |
656 | | check_det(Call) :- check_deterministic(Call). |
657 | | |
658 | | :- volatile calling/1. |
659 | | :- dynamic calling/1. |
660 | | check_det2(Call,X) :- assertz(calling(X)), |
661 | | call(Call), |
662 | | (retract(calling(X)) -> true |
663 | | ; nl, |
664 | | print('### Call has multiple solutions: '),nl, |
665 | | print('### '), print(Call),nl, |
666 | | nl |
667 | | ). |
668 | | check_det2(Call,X) :- (retract(calling(X)) -> print(fails(Call)),nl ; fail). |
669 | | |
670 | | |
671 | | % ------------------------ |
672 | | |
673 | | check_eq(A,B) :- (A=B -> true ; format(user_error,'! Not unifiable:~n ~w~n ~w~n',[A,B]),fail). |
674 | | check_eqeq(A,B) :- (A==B -> true ; format(user_error,'! Not identical:~n ~w~n ~w~n',[A,B]),fail). |