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