| 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). |