37
38:- module(plunit,
39 [ set_test_options/1, 40 begin_tests/1, 41 begin_tests/2, 42 end_tests/1, 43 run_tests/0, 44 run_tests/1, 45 run_tests/2, 46 load_test_files/1, 47 running_tests/0, 48 current_test/5, 49 current_test_unit/2, 50 test_report/1 51 ]). 52
58
59:- autoload(library(statistics), [call_time/2]). 60:- autoload(library(apply),
61 [maplist/3, include/3, maplist/2, foldl/4, partition/4]). 62:- autoload(library(lists), [member/2, append/2, flatten/2, append/3]). 63:- autoload(library(option), [ option/3, option/2, select_option/3 ]). 64:- autoload(library(ordsets), [ord_intersection/3]). 65:- autoload(library(error), [must_be/2, domain_error/2]). 66:- autoload(library(aggregate), [aggregate_all/3]). 67:- autoload(library(streams), [with_output_to/3]). 68:- autoload(library(ansi_term), [ansi_format/3]). 69:- if(exists_source(library(time))). 70:- autoload(library(time), [call_with_time_limit/2]). 71:- endif. 72
73:- public
74 unit_module/2. 75
76:- meta_predicate
77 valid_options(1, +),
78 count(0, -). 79
80 83
84swi :- catch(current_prolog_flag(dialect, swi), _, fail).
85sicstus :- catch(current_prolog_flag(dialect, sicstus), _, fail).
86
87throw_error(Error_term,Impldef) :-
88 throw(error(Error_term,context(Impldef,_))).
89
90:- set_prolog_flag(generate_debug_info, false). 91current_test_flag(optimise, Value) =>
92 current_prolog_flag(optimise, Value).
93current_test_flag(occurs_check, Value) =>
94 ( current_prolog_flag(plunit_occurs_check, Value0)
95 -> Value = Value0
96 ; current_prolog_flag(occurs_check, Value)
97 ).
98current_test_flag(Name, Value), atom(Name) =>
99 atom_concat(plunit_, Name, Flag),
100 current_prolog_flag(Flag, Value).
101current_test_flag(Name, Value), var(Name) =>
102 global_test_option(Opt, _, _Type, _Default),
103 functor(Opt, Name, 1),
104 current_test_flag(Name, Value).
105
106set_test_flag(Name, Value) :-
107 Opt =.. [Name, Value],
108 global_test_option(Opt),
109 !,
110 atom_concat(plunit_, Name, Flag),
111 set_prolog_flag(Flag, Value).
112set_test_flag(Name, _) :-
113 domain_error(test_flag, Name).
114
115current_test_flags(Flags) :-
116 findall(Flag, current_test_flag(Flag), Flags).
117
118current_test_flag(Opt) :-
119 current_test_flag(Name, Value),
120 Opt =.. [Name, Value].
121
123goal_expansion(forall(C,A),
124 \+ (C, \+ A)).
125goal_expansion(current_module(Module,File),
126 module_property(Module, file(File))).
127
128
129 132
133:- initialization init_flags. 134
135init_flags :-
136 ( global_test_option(Option, _Value, Type, Default),
137 Default \== (-),
138 Option =.. [Name,_],
139 atom_concat(plunit_, Name, Flag),
140 flag_type(Type, FlagType),
141 create_prolog_flag(Flag, Default, [type(FlagType), keep(true)]),
142 fail
143 ; true
144 ).
145
146flag_type(boolean, FlagType) => FlagType = boolean.
147flag_type(Type, FlagType), Type = oneof(Atoms), maplist(atom, Atoms) =>
148 FlagType = Type.
149flag_type(oneof(_), FlagType) => FlagType = term.
150flag_type(positive_integer, FlagType) => FlagType = integer.
151flag_type(number, FlagType) => FlagType = float.
152
153
154
202
203set_test_options(Options) :-
204 flatten([Options], List),
205 maplist(set_test_option, List).
206
207set_test_option(sto(true)) =>
208 print_message(warning, plunit(sto(true))).
209set_test_option(jobs(Jobs)) =>
210 must_be(positive_integer, Jobs),
211 set_test_option_flag(jobs(Jobs)).
212set_test_option(Option),
213 compound(Option), global_test_option(Option) =>
214 set_test_option_flag(Option).
215set_test_option(Option) =>
216 domain_error(option, Option).
217
218global_test_option(Opt) :-
219 global_test_option(Opt, Value, Type, _Default),
220 must_be(Type, Value).
221
222global_test_option(load(Load), Load, oneof([never,always,normal]), normal).
223global_test_option(output(Cond), Cond, oneof([always,on_failure]), on_failure).
224global_test_option(format(Feedback), Feedback, oneof([tty,log]), tty).
225global_test_option(silent(Silent), Silent, boolean, false).
226global_test_option(show_blocked(Blocked), Blocked, boolean, false).
227global_test_option(run(When), When, oneof([manual,make,make(all)]), make).
228global_test_option(occurs_check(Mode), Mode, oneof([false,true,error]), -).
229global_test_option(cleanup(Bool), Bool, boolean, true).
230global_test_option(jobs(Count), Count, positive_integer, 1).
231global_test_option(timeout(Number), Number, number, 3600).
232
233set_test_option_flag(Option) :-
234 Option =.. [Name, Value],
235 set_test_flag(Name, Value).
236
240
241loading_tests :-
242 current_test_flag(load, Load),
243 ( Load == always
244 -> true
245 ; Load == normal,
246 \+ current_test_flag(optimise, true)
247 ).
248
249 252
253:- dynamic
254 loading_unit/4, 255 current_unit/4, 256 test_file_for/2. 257
263
264begin_tests(Unit) :-
265 begin_tests(Unit, []).
266
267begin_tests(Unit, Options) :-
268 must_be(atom, Unit),
269 map_sto_option(Options, Options1),
270 valid_options(test_set_option, Options1),
271 make_unit_module(Unit, Name),
272 source_location(File, Line),
273 begin_tests(Unit, Name, File:Line, Options1).
274
275map_sto_option(Options0, Options) :-
276 select_option(sto(Mode), Options0, Options1),
277 !,
278 map_sto(Mode, Flag),
279 Options = [occurs_check(Flag)|Options1].
280map_sto_option(Options, Options).
281
282map_sto(rational_trees, Flag) => Flag = false.
283map_sto(finite_trees, Flag) => Flag = true.
284map_sto(Mode, _) => domain_error(sto, Mode).
285
286
287:- if(swi). 288begin_tests(Unit, Name, File:Line, Options) :-
289 loading_tests,
290 !,
291 '$set_source_module'(Context, Context),
292 ( current_unit(Unit, Name, Context, Options)
293 -> true
294 ; retractall(current_unit(Unit, Name, _, _)),
295 assert(current_unit(Unit, Name, Context, Options))
296 ),
297 '$set_source_module'(Old, Name),
298 '$declare_module'(Name, test, Context, File, Line, false),
299 discontiguous(Name:'unit test'/4),
300 '$set_predicate_attribute'(Name:'unit test'/4, trace, false),
301 discontiguous(Name:'unit body'/2),
302 asserta(loading_unit(Unit, Name, File, Old)).
303begin_tests(Unit, Name, File:_Line, _Options) :-
304 '$set_source_module'(Old, Old),
305 asserta(loading_unit(Unit, Name, File, Old)).
306
307:- else. 308
310
311user:term_expansion((:- begin_tests(Set)),
312 [ (:- begin_tests(Set)),
313 (:- discontiguous(test/2)),
314 (:- discontiguous('unit body'/2)),
315 (:- discontiguous('unit test'/4))
316 ]).
317
318begin_tests(Unit, Name, File:_Line, Options) :-
319 loading_tests,
320 !,
321 ( current_unit(Unit, Name, _, Options)
322 -> true
323 ; retractall(current_unit(Unit, Name, _, _)),
324 assert(current_unit(Unit, Name, -, Options))
325 ),
326 asserta(loading_unit(Unit, Name, File, -)).
327begin_tests(Unit, Name, File:_Line, _Options) :-
328 asserta(loading_unit(Unit, Name, File, -)).
329
330:- endif. 331
338
339end_tests(Unit) :-
340 loading_unit(StartUnit, _, _, _),
341 !,
342 ( Unit == StartUnit
343 -> once(retract(loading_unit(StartUnit, _, _, Old))),
344 '$set_source_module'(_, Old)
345 ; throw_error(context_error(plunit_close(Unit, StartUnit)), _)
346 ).
347end_tests(Unit) :-
348 throw_error(context_error(plunit_close(Unit, -)), _).
349
352
353:- if(swi). 354
355unit_module(Unit, Module) :-
356 atom_concat('plunit_', Unit, Module).
357
358make_unit_module(Unit, Module) :-
359 unit_module(Unit, Module),
360 ( current_module(Module),
361 \+ current_unit(_, Module, _, _),
362 predicate_property(Module:H, _P),
363 \+ predicate_property(Module:H, imported_from(_M))
364 -> throw_error(permission_error(create, plunit, Unit),
365 'Existing module')
366 ; true
367 ).
368
369:- else. 370
371:- dynamic
372 unit_module_store/2. 373
374unit_module(Unit, Module) :-
375 unit_module_store(Unit, Module),
376 !.
377
378make_unit_module(Unit, Module) :-
379 prolog_load_context(module, Module),
380 assert(unit_module_store(Unit, Module)).
381
382:- endif. 383
384 387
392
393expand_test(Name, Options0, Body,
394 [ 'unit test'(Name, Line, Options, Module:'unit body'(Id, Vars)),
395 ('unit body'(Id, Vars) :- !, Body)
396 ]) :-
397 source_location(_File, Line),
398 prolog_load_context(module, Module),
399 ( prolog_load_context(variable_names, Bindings)
400 -> true
401 ; Bindings = []
402 ),
403 atomic_list_concat([Name, '@line ', Line], Id),
404 term_variables(Options0, OptionVars0), sort(OptionVars0, OptionVars),
405 term_variables(Body, BodyVars0), sort(BodyVars0, BodyVars),
406 ord_intersection(OptionVars, BodyVars, VarList),
407 Vars =.. [vars|VarList],
408 ( is_list(Options0) 409 -> Options1 = Options0
410 ; Options1 = [Options0]
411 ),
412 maplist(expand_option(Bindings), Options1, Options2),
413 join_true_options(Options2, Options3),
414 map_sto_option(Options3, Options4),
415 valid_options(test_option, Options4),
416 valid_test_mode(Options4, Options).
417
418expand_option(_, Var, _) :-
419 var(Var),
420 !,
421 throw_error(instantiation_error,_).
422expand_option(Bindings, Cmp, true(Cond)) :-
423 cmp(Cmp),
424 !,
425 var_cmp(Bindings, Cmp, Cond).
426expand_option(_, error(X), throws(error(X, _))) :- !.
427expand_option(_, exception(X), throws(X)) :- !. 428expand_option(_, error(F,C), throws(error(F,C))) :- !. 429expand_option(_, true, true(true)) :- !.
430expand_option(_, O, O).
431
432cmp(_ == _).
433cmp(_ = _).
434cmp(_ =@= _).
435cmp(_ =:= _).
436
437var_cmp(Bindings, Expr, cmp(Name, Expr)) :-
438 arg(_, Expr, Var),
439 var(Var),
440 member(Name=V, Bindings),
441 V == Var,
442 !.
443var_cmp(_, Expr, Expr).
444
445join_true_options(Options0, Options) :-
446 partition(true_option, Options0, True, Rest),
447 True \== [],
448 !,
449 maplist(arg(1), True, Conds0),
450 flatten(Conds0, Conds),
451 Options = [true(Conds)|Rest].
452join_true_options(Options, Options).
453
454true_option(true(_)).
455
456valid_test_mode(Options0, Options) :-
457 include(test_mode, Options0, Tests),
458 ( Tests == []
459 -> Options = [true([true])|Options0]
460 ; Tests = [_]
461 -> Options = Options0
462 ; throw_error(plunit(incompatible_options, Tests), _)
463 ).
464
465test_mode(true(_)).
466test_mode(all(_)).
467test_mode(set(_)).
468test_mode(fail).
469test_mode(throws(_)).
470
471
473
474expand(end_of_file, _) :-
475 loading_unit(Unit, _, _, _),
476 !,
477 end_tests(Unit), 478 fail.
479expand((:-end_tests(_)), _) :-
480 !,
481 fail.
482expand(_Term, []) :-
483 \+ loading_tests.
484expand((test(Name) :- Body), Clauses) :-
485 !,
486 expand_test(Name, [], Body, Clauses).
487expand((test(Name, Options) :- Body), Clauses) :-
488 !,
489 expand_test(Name, Options, Body, Clauses).
490expand(test(Name), _) :-
491 !,
492 throw_error(existence_error(body, test(Name)), _).
493expand(test(Name, _Options), _) :-
494 !,
495 throw_error(existence_error(body, test(Name)), _).
496
497:- multifile
498 system:term_expansion/2. 499
500system:term_expansion(Term, Expanded) :-
501 ( loading_unit(_, _, File, _)
502 -> source_location(ThisFile, _),
503 ( File == ThisFile
504 -> true
505 ; source_file_property(ThisFile, included_in(File, _))
506 ),
507 expand(Term, Expanded)
508 ).
509
510
511 514
521
522valid_options(Pred, Options) :-
523 must_be(list, Options),
524 verify_options(Options, Pred).
525
526verify_options([], _).
527verify_options([H|T], Pred) :-
528 ( call(Pred, H)
529 -> verify_options(T, Pred)
530 ; throw_error(domain_error(Pred, H), _)
531 ).
532
533valid_options(Pred, Options0, Options, Rest) :-
534 must_be(list, Options0),
535 partition(Pred, Options0, Options, Rest).
536
540
541test_option(Option) :-
542 test_set_option(Option),
543 !.
544test_option(true(_)).
545test_option(fail).
546test_option(throws(_)).
547test_option(all(_)).
548test_option(set(_)).
549test_option(nondet).
550test_option(fixme(_)).
551test_option(forall(X)) :-
552 must_be(callable, X).
553test_option(timeout(Seconds)) :-
554 must_be(number, Seconds).
555
560
561test_set_option(blocked(X)) :-
562 must_be(ground, X).
563test_set_option(condition(X)) :-
564 must_be(callable, X).
565test_set_option(setup(X)) :-
566 must_be(callable, X).
567test_set_option(cleanup(X)) :-
568 must_be(callable, X).
569test_set_option(occurs_check(V)) :-
570 must_be(oneof([false,true,error]), V).
571test_set_option(concurrent(V)) :-
572 must_be(boolean, V),
573 print_message(informational, plunit(concurrent)).
574test_set_option(timeout(Seconds)) :-
575 must_be(number, Seconds).
576
577 580
581:- meta_predicate
582 reify_tmo(0, -, +),
583 reify(0, -),
584 capture_output(0,-),
585 capture_output(0,-,+),
586 got_messages(0,-). 587
589
590:- if(current_predicate(call_with_time_limit/2)). 591reify_tmo(Goal, Result, Options) :-
592 option(timeout(Time), Options),
593 Time > 0,
594 !,
595 reify(call_with_time_limit(Time, Goal), Result0),
596 ( Result0 = throw(time_limit_exceeded)
597 -> Result = throw(time_limit_exceeded(Time))
598 ; Result = Result0
599 ).
600:- endif. 601reify_tmo(Goal, Result, _Options) :-
602 reify(Goal, Result).
603
608
609reify(Goal, Result) :-
610 ( catch(Goal, E, true)
611 -> ( var(E)
612 -> Result = true
613 ; Result = throw(E)
614 )
615 ; Result = false
616 ).
617
624
625capture_output(Goal, Output) :-
626 current_test_flag(output, OutputMode),
627 capture_output(Goal, Output, [output(OutputMode)]).
628
629capture_output(Goal, Msgs-Output, Options) :-
630 option(output(How), Options, always),
631 ( How == always
632 -> call(Goal),
633 Msgs = false 634 ; with_output_to(string(Output), got_messages(Goal, Msgs),
635 [ capture([user_output, user_error]),
636 color(true)
637 ])
638 ).
639
641
642got_messages(Goal, Result) :-
643 ( current_prolog_flag(on_warning, status)
644 ; current_prolog_flag(on_error, status)
645 ), !,
646 nb_delete(plunit_got_message),
647 setup_call_cleanup(
648 asserta(( user:thread_message_hook(_Term, Kind, _Lines) :-
649 got_message(Kind), fail), Ref),
650 Goal,
651 erase(Ref)),
652 ( nb_current(plunit_got_message, true)
653 -> Result = true
654 ; Result = false
655 ).
656got_messages(Goal, false) :-
657 call(Goal).
658
659:- public got_message/1. 660got_message(warning) :-
661 current_prolog_flag(on_warning, status), !,
662 nb_setval(plunit_got_message, true).
663got_message(error) :-
664 current_prolog_flag(on_error, status), !,
665 nb_setval(plunit_got_message, true).
666
667
668 671
672:- dynamic
673 output_streams/2, 674 test_count/1, 675 passed/5, 676 failed/5, 677 timeout/5, 678 failed_assertion/7, 679 blocked/4, 680 fixme/5, 681 running/5, 682 forall_failures/2. 683
713
714run_tests :-
715 run_tests(all).
716
717run_tests(Set) :-
718 run_tests(Set, []).
719
720run_tests(all, Options) :-
721 !,
722 findall(Unit, current_test_unit(Unit,_), Units),
723 run_tests(Units, Options).
724run_tests(Set, Options) :-
725 valid_options(global_test_option, Options, Global, Rest),
726 current_test_flags(Old),
727 setup_call_cleanup(
728 set_test_options(Global),
729 ( flatten([Set], List),
730 maplist(runnable_tests, List, Units),
731 with_mutex(plunit, run_tests_sync(Units, Rest))
732 ),
733 set_test_options(Old)).
734
735run_tests_sync(Units0, Options) :-
736 cleanup,
737 count_tests(Units0, Units, Count),
738 asserta(test_count(Count)),
739 save_output_state,
740 setup_call_cleanup(
741 setup_jobs(Count),
742 setup_call_cleanup(
743 setup_trap_assertions(Ref),
744 ( call_time(run_units(Units, Options), Time),
745 test_summary(_All, Summary)
746 ),
747 report_and_cleanup(Ref, Time, Options)),
748 cleanup_jobs),
749 ( option(summary(Summary), Options)
750 -> true
751 ; test_summary_passed(Summary) 752 ).
753
758
759report_and_cleanup(Ref, Time, Options) :-
760 cleanup_trap_assertions(Ref),
761 report(Time, Options),
762 cleanup_after_test.
763
764
768
769run_units(Units, _Options) :-
770 maplist(schedule_unit, Units),
771 job_wait(_).
772
779
780:- det(runnable_tests/2). 781runnable_tests(Spec, Unit:RunnableTests) :-
782 unit_from_spec(Spec, Unit, Tests, Module, UnitOptions),
783 ( option(blocked(Reason), UnitOptions)
784 -> info(plunit(blocked(unit(Unit, Reason)))),
785 RunnableTests = []
786 ; \+ condition(Module, unit(Unit), UnitOptions)
787 -> RunnableTests = []
788 ; var(Tests)
789 -> findall(TestID,
790 runnable_test(Unit, _Test, Module, TestID),
791 RunnableTests)
792 ; flatten([Tests], TestList),
793 findall(TestID,
794 ( member(Test, TestList),
795 runnable_test(Unit,Test,Module, TestID)
796 ),
797 RunnableTests)
798 ).
799
800runnable_test(Unit, Name, Module, @(Test,Line)) :-
801 current_test(Unit, Name, Line, _Body, TestOptions),
802 ( option(blocked(Reason), TestOptions)
803 -> Test = blocked(Name, Reason)
804 ; condition(Module, test(Unit,Name,Line), TestOptions),
805 Test = Name
806 ).
807
808unit_from_spec(Unit0:Tests0, Unit, Tests, Module, Options), atom(Unit0) =>
809 Unit = Unit0,
810 Tests = Tests0,
811 ( current_unit(Unit, Module, _Supers, Options)
812 -> true
813 ; throw_error(existence_error(unit_test, Unit), _)
814 ).
815unit_from_spec(Unit0, Unit, _, Module, Options), atom(Unit0) =>
816 Unit = Unit0,
817 ( current_unit(Unit, Module, _Supers, Options)
818 -> true
819 ; throw_error(existence_error(unit_test, Unit), _)
820 ).
821
827
828count_tests(Units0, Units, Count) :-
829 count_tests(Units0, Units, 0, Count).
830
831count_tests([], T, C0, C) =>
832 T = [],
833 C = C0.
834count_tests([_:[]|T0], T, C0, C) =>
835 count_tests(T0, T, C0, C).
836count_tests([Unit:Tests|T0], T, C0, C) =>
837 partition(is_blocked, Tests, Blocked, Use),
838 maplist(assert_blocked(Unit), Blocked),
839 ( Use == []
840 -> count_tests(T0, T, C0, C)
841 ; length(Use, N),
842 C1 is C0+N,
843 T = [Unit:Use|T1],
844 count_tests(T0, T1, C1, C)
845 ).
846
847is_blocked(@(blocked(_,_),_)) => true.
848is_blocked(_) => fail.
849
850assert_blocked(Unit, @(blocked(Test, Reason), Line)) =>
851 assert(blocked(Unit, Test, Line, Reason)).
852
857
858run_unit(_Unit:[]) =>
859 true.
860run_unit(Unit:Tests) =>
861 unit_module(Unit, Module),
862 unit_options(Unit, UnitOptions),
863 ( setup(Module, unit(Unit), UnitOptions)
864 -> begin_unit(Unit),
865 call_time(run_unit_2(Unit, Tests), Time),
866 test_summary(Unit, Summary),
867 end_unit(Unit, Summary.put(time, Time)),
868 cleanup(Module, UnitOptions)
869 ; job_info(end(unit(Unit, _{error:setup_failed})))
870 ).
871
872begin_unit(Unit) :-
873 job_info(begin(unit(Unit))),
874 job_feedback(informational, begin(Unit)).
875
876end_unit(Unit, Summary) :-
877 job_info(end(unit(Unit, Summary))),
878 job_feedback(informational, end(Unit, Summary)).
879
880run_unit_2(Unit, Tests) :-
881 forall(member(Test, Tests),
882 run_test(Unit, Test)).
883
884
885unit_options(Unit, Options) :-
886 current_unit(Unit, _Module, _Supers, Options).
887
888
889cleanup :-
890 set_flag(plunit_test, 1),
891 retractall(output_streams(_,_)),
892 retractall(test_count(_)),
893 retractall(passed(_, _, _, _, _)),
894 retractall(failed(_, _, _, _, _)),
895 retractall(timeout(_, _, _, _, _)),
896 retractall(failed_assertion(_, _, _, _, _, _, _)),
897 retractall(blocked(_, _, _, _)),
898 retractall(fixme(_, _, _, _, _)),
899 retractall(running(_,_,_,_,_)),
900 retractall(forall_failures(_,_)).
901
902cleanup_after_test :-
903 ( current_test_flag(cleanup, true)
904 -> cleanup
905 ; true
906 ).
907
908
912
913run_tests_in_files(Files) :-
914 findall(Unit, unit_in_files(Files, Unit), Units),
915 ( Units == []
916 -> true
917 ; run_tests(Units)
918 ).
919
920unit_in_files(Files, Unit) :-
921 is_list(Files),
922 !,
923 member(F, Files),
924 absolute_file_name(F, Source,
925 [ file_type(prolog),
926 access(read),
927 file_errors(fail)
928 ]),
929 unit_file(Unit, Source).
930
931
932 935
939
940make_run_tests(Files) :-
941 current_test_flag(run, When),
942 ( When == make
943 -> run_tests_in_files(Files)
944 ; When == make(all)
945 -> run_tests
946 ; true
947 ).
948
949 952
953:- if(swi). 954
955:- dynamic prolog:assertion_failed/2. 956
957setup_trap_assertions(Ref) :-
958 asserta((prolog:assertion_failed(Reason, Goal) :-
959 test_assertion_failed(Reason, Goal)),
960 Ref).
961
962cleanup_trap_assertions(Ref) :-
963 erase(Ref).
964
965test_assertion_failed(Reason, Goal) :-
966 thread_self(Me),
967 running(Unit, Test, Line, Progress, Me),
968 ( catch(get_prolog_backtrace(10, Stack), _, fail),
969 assertion_location(Stack, AssertLoc)
970 -> true
971 ; AssertLoc = unknown
972 ),
973 report_failed_assertion(Unit:Test, Line, AssertLoc,
974 Progress, Reason, Goal),
975 assert_cyclic(failed_assertion(Unit, Test, Line, AssertLoc,
976 Progress, Reason, Goal)).
977
978assertion_location(Stack, File:Line) :-
979 append(_, [AssertFrame,CallerFrame|_], Stack),
980 prolog_stack_frame_property(AssertFrame,
981 predicate(prolog_debug:assertion/1)),
982 !,
983 prolog_stack_frame_property(CallerFrame, location(File:Line)).
984
985report_failed_assertion(UnitTest, Line, AssertLoc,
986 Progress, Reason, Goal) :-
987 print_message(
988 error,
989 plunit(failed_assertion(UnitTest, Line, AssertLoc,
990 Progress, Reason, Goal))).
991
992:- else. 993
994setup_trap_assertions(_).
995cleanup_trap_assertions(_).
996
997:- endif. 998
999
1000 1003
1007
1008run_test(Unit, @(Test,Line)) :-
1009 unit_module(Unit, Module),
1010 Module:'unit test'(Test, Line, TestOptions, Body),
1011 unit_options(Unit, UnitOptions),
1012 run_test(Unit, Test, Line, UnitOptions, TestOptions, Body).
1013
1017
1018run_test(Unit, Name, Line, UnitOptions, Options, Body) :-
1019 option(forall(Generator), Options),
1020 !,
1021 unit_module(Unit, Module),
1022 term_variables(Generator, Vars),
1023 start_test(Unit, @(Name,Line), Nth),
1024 State = state(0),
1025 call_time(forall(Module:Generator, 1026 ( incr_forall(State, I),
1027 run_test_once6(Unit, Name, forall(Vars, Nth-I), Line,
1028 UnitOptions, Options, Body)
1029 )),
1030 Time),
1031 arg(1, State, Generated),
1032 progress(Unit:Name, Nth, forall(end, Nth, Generated), Time).
1033run_test(Unit, Name, Line, UnitOptions, Options, Body) :-
1034 start_test(Unit, @(Name,Line), Nth),
1035 run_test_once6(Unit, Name, Nth, Line, UnitOptions, Options, Body).
1036
1037start_test(_Unit, _TestID, Nth) :-
1038 flag(plunit_test, Nth, Nth+1).
1039
1040incr_forall(State, I) :-
1041 arg(1, State, I0),
1042 I is I0+1,
1043 nb_setarg(1, State, I).
1044
1049
1050run_test_once6(Unit, Name, Progress, Line, UnitOptions, Options, Body) :-
1051 current_test_flag(timeout, DefTimeOut),
1052 current_test_flag(occurs_check, DefOccurs),
1053 inherit_option(timeout, Options, [UnitOptions], DefTimeOut, Options1),
1054 inherit_option(occurs_check, Options1, [UnitOptions], DefOccurs, Options2),
1055 run_test_once(Unit, Name, Progress, Line, Options2, Body).
1056
1057inherit_option(Name, Options0, Chain, Default, Options) :-
1058 Term =.. [Name,_Value],
1059 ( option(Term, Options0)
1060 -> Options = Options0
1061 ; member(Opts, Chain),
1062 option(Term, Opts)
1063 -> Options = [Term|Options0]
1064 ; Default == (-)
1065 -> Options = Options0
1066 ; Opt =.. [Name,Default],
1067 Options = [Opt|Options0]
1068 ).
1069
1074
1075run_test_once(Unit, Name, Progress, Line, Options, Body) :-
1076 option(occurs_check(Occurs), Options),
1077 !,
1078 begin_test(Unit, Name, Line, Progress),
1079 current_prolog_flag(occurs_check, Old),
1080 setup_call_cleanup(
1081 set_prolog_flag(occurs_check, Occurs),
1082 capture_output(run_test_6(Unit, Name, Line, Options, Body, Result),
1083 Output),
1084 set_prolog_flag(occurs_check, Old)),
1085 end_test(Unit, Name, Line, Progress),
1086 report_result(Result, Progress, Output, Options).
1087run_test_once(Unit, Name, Progress, Line, Options, Body) :-
1088 begin_test(Unit, Name, Line, Progress),
1089 capture_output(run_test_6(Unit, Name, Line, Options, Body, Result),
1090 Output),
1091 end_test(Unit, Name, Line, Progress),
1092 report_result(Result, Progress, Output, Options).
1093
1095
1096:- det(report_result/4). 1097report_result(failure(Unit, Name, Line, How, Time),
1098 Progress, Output, Options) :-
1099 !,
1100 failure(Unit, Name, Progress, Line, How, Time, Output, Options).
1101report_result(success(Unit, Name, Line, Determinism, Time),
1102 Progress, Output, Options) :-
1103 !,
1104 success(Unit, Name, Progress, Line, Determinism, Time, Output, Options).
1105report_result(setup_failed(_Unit, _Name, _Line),
1106 _Progress, _Output, _Options).
1107
1127
1128run_test_6(Unit, Name, Line, Options, Body, Result) :-
1129 option(setup(_Setup), Options),
1130 !,
1131 ( unit_module(Unit, Module),
1132 setup(Module, test(Unit,Name,Line), Options)
1133 -> run_test_7(Unit, Name, Line, Options, Body, Result),
1134 cleanup(Module, Options)
1135 ; Result = setup_failed(Unit, Name, Line)
1136 ).
1137run_test_6(Unit, Name, Line, Options, Body, Result) :-
1138 unit_module(Unit, Module),
1139 run_test_7(Unit, Name, Line, Options, Body, Result),
1140 cleanup(Module, Options).
1141
1148
1149run_test_7(Unit, Name, Line, Options, Body, Result) :-
1150 option(true(Cmp), Options), 1151 !,
1152 unit_module(Unit, Module),
1153 call_time(reify_tmo(call_det(Module:Body, Det), Result0, Options), Time),
1154 ( Result0 == true
1155 -> cmp_true(Cmp, Module, CmpResult),
1156 ( CmpResult == []
1157 -> Result = success(Unit, Name, Line, Det, Time)
1158 ; Result = failure(Unit, Name, Line, CmpResult, Time)
1159 )
1160 ; Result0 == false
1161 -> Result = failure(Unit, Name, Line, failed, Time)
1162 ; Result0 = throw(E2)
1163 -> Result = failure(Unit, Name, Line, throw(E2), Time)
1164 ).
1165run_test_7(Unit, Name, Line, Options, Body, Result) :-
1166 option(fail, Options), 1167 !,
1168 unit_module(Unit, Module),
1169 call_time(reify_tmo(Module:Body, Result0, Options), Time),
1170 ( Result0 == true
1171 -> Result = failure(Unit, Name, Line, succeeded, Time)
1172 ; Result0 == false
1173 -> Result = success(Unit, Name, Line, true, Time)
1174 ; Result0 = throw(E)
1175 -> Result = failure(Unit, Name, Line, throw(E), Time)
1176 ).
1177run_test_7(Unit, Name, Line, Options, Body, Result) :-
1178 option(throws(Expect), Options), 1179 !,
1180 unit_module(Unit, Module),
1181 call_time(reify_tmo(Module:Body, Result0, Options), Time),
1182 ( Result0 == true
1183 -> Result = failure(Unit, Name, Line, no_exception, Time)
1184 ; Result0 == false
1185 -> Result = failure(Unit, Name, Line, failed, Time)
1186 ; Result0 = throw(E)
1187 -> ( match_error(Expect, E)
1188 -> Result = success(Unit, Name, Line, true, Time)
1189 ; Result = failure(Unit, Name, Line, wrong_error(Expect, E), Time)
1190 )
1191 ).
1192run_test_7(Unit, Name, Line, Options, Body, Result) :-
1193 option(all(Answer), Options), 1194 !,
1195 nondet_test(all(Answer), Unit, Name, Line, Options, Body, Result).
1196run_test_7(Unit, Name, Line, Options, Body, Result) :-
1197 option(set(Answer), Options), 1198 !,
1199 nondet_test(set(Answer), Unit, Name, Line, Options, Body, Result).
1200
1204
1205nondet_test(Expected, Unit, Name, Line, Options, Body, Result) :-
1206 unit_module(Unit, Module),
1207 result_vars(Expected, Vars),
1208 ( call_time(reify_tmo(findall(Vars, Module:Body, Bindings),
1209 Result0, Options), Time)
1210 -> ( Result0 == true
1211 -> ( nondet_compare(Expected, Bindings, Unit, Name, Line)
1212 -> Result = success(Unit, Name, Line, true, Time)
1213 ; Result = failure(Unit, Name, Line,
1214 [wrong_answer(Expected, Bindings)], Time)
1215 )
1216 ; Result0 = throw(E)
1217 -> Result = failure(Unit, Name, Line, throw(E), Time)
1218 )
1219 ).
1220
1221cmp_true([], _, L) =>
1222 L = [].
1223cmp_true([Cmp|T], Module, L) =>
1224 E = error(Formal,_),
1225 cmp_goal(Cmp, Goal),
1226 ( catch(Module:Goal, E, true)
1227 -> ( var(Formal)
1228 -> cmp_true(T, Module, L)
1229 ; L = [cmp_error(Cmp,E)|L1],
1230 cmp_true(T, Module, L1)
1231 )
1232 ; L = [wrong_answer(Cmp)|L1],
1233 cmp_true(T, Module, L1)
1234 ).
1235
1236cmp_goal(cmp(_Var, Expr), Goal) => Goal = Expr.
1237cmp_goal(Expr, Goal) => Goal = Expr.
1238
1239
1244
1245result_vars(Expected, Vars) :-
1246 arg(1, Expected, CmpOp),
1247 arg(1, CmpOp, Vars).
1248
1256
1257nondet_compare(all(Cmp), Bindings, _Unit, _Name, _Line) :-
1258 cmp(Cmp, _Vars, Op, Values),
1259 cmp_list(Values, Bindings, Op).
1260nondet_compare(set(Cmp), Bindings0, _Unit, _Name, _Line) :-
1261 cmp(Cmp, _Vars, Op, Values0),
1262 sort(Bindings0, Bindings),
1263 sort(Values0, Values),
1264 cmp_list(Values, Bindings, Op).
1265
1266cmp_list([], [], _Op).
1267cmp_list([E0|ET], [V0|VT], Op) :-
1268 call(Op, E0, V0),
1269 cmp_list(ET, VT, Op).
1270
1272
1273cmp(Var == Value, Var, ==, Value).
1274cmp(Var =:= Value, Var, =:=, Value).
1275cmp(Var = Value, Var, =, Value).
1276:- if(swi). 1277cmp(Var =@= Value, Var, =@=, Value).
1278:- else. 1279:- if(sicstus). 1280cmp(Var =@= Value, Var, variant, Value). 1281:- endif. 1282:- endif. 1283
1284
1289
1290:- if((swi;sicstus)). 1291call_det(Goal, Det) :-
1292 call_cleanup(Goal,Det0=true),
1293 ( var(Det0) -> Det = false ; Det = true ).
1294:- else. 1295call_det(Goal, true) :-
1296 call(Goal).
1297:- endif. 1298
1303
1304match_error(Expect, Rec) :-
1305 subsumes_term(Expect, Rec).
1306
1317
1318setup(Module, Context, Options) :-
1319 option(setup(Setup), Options),
1320 !,
1321 capture_output(reify(call_ex(Module, Setup), Result), Output),
1322 ( Result == true
1323 -> true
1324 ; print_message(error,
1325 plunit(error(setup, Context, Output, Result))),
1326 fail
1327 ).
1328setup(_,_,_).
1329
1333
1334condition(Module, Context, Options) :-
1335 option(condition(Cond), Options),
1336 !,
1337 capture_output(reify(call_ex(Module, Cond), Result), Output),
1338 ( Result == true
1339 -> true
1340 ; Result == false
1341 -> fail
1342 ; print_message(error,
1343 plunit(error(condition, Context, Output, Result))),
1344 fail
1345 ).
1346condition(_, _, _).
1347
1348
1352
1353call_ex(Module, Goal) :-
1354 Module:(expand_goal(Goal, GoalEx),
1355 GoalEx).
1356
1361
1362cleanup(Module, Options) :-
1363 option(cleanup(Cleanup), Options, true),
1364 ( catch(call_ex(Module, Cleanup), E, true)
1365 -> ( var(E)
1366 -> true
1367 ; print_message(warning, E)
1368 )
1369 ; print_message(warning, goal_failed(Cleanup, '(cleanup handler)'))
1370 ).
1371
1372success(Unit, Name, Progress, Line, Det, Time, _Output, Options) :-
1373 memberchk(fixme(Reason), Options),
1374 !,
1375 ( ( Det == true
1376 ; memberchk(nondet, Options)
1377 )
1378 -> progress(Unit:Name, Progress, fixme(passed), Time),
1379 Ok = passed
1380 ; progress(Unit:Name, Progress, fixme(nondet), Time),
1381 Ok = nondet
1382 ),
1383 flush_output(user_error),
1384 assert(fixme(Unit, Name, Line, Reason, Ok)).
1385success(Unit, Name, Progress, Line, _, Time, Output, Options) :-
1386 failed_assertion(Unit, Name, Line, _,Progress,_,_),
1387 !,
1388 failure(Unit, Name, Progress, Line, assertion, Time, Output, Options).
1389success(Unit, Name, Progress, Line, _, Time, Output, Options) :-
1390 Output = true-_,
1391 !,
1392 failure(Unit, Name, Progress, Line, message, Time, Output, Options).
1393success(Unit, Name, Progress, Line, Det, Time, _Output, Options) :-
1394 assert(passed(Unit, Name, Line, Det, Time)),
1395 ( ( Det == true
1396 ; memberchk(nondet, Options)
1397 )
1398 -> progress(Unit:Name, Progress, passed, Time)
1399 ; unit_file(Unit, File),
1400 print_message(warning, plunit(nondet(File, Line, Name)))
1401 ).
1402
1407
1408failure(Unit, Name, Progress, Line, _, Time, _Output, Options),
1409 memberchk(fixme(Reason), Options) =>
1410 assert(fixme(Unit, Name, Line, Reason, failed)),
1411 progress(Unit:Name, Progress, fixme(failed), Time).
1412failure(Unit, Name, Progress, Line, time_limit_exceeded(Limit), Time,
1413 Output, Options) =>
1414 assert_cyclic(timeout(Unit, Name, Line, Limit, Time)),
1415 progress(Unit:Name, Progress, timeout(Limit), Time),
1416 report_failure(Unit, Name, Progress, Line, timeout(Limit), Time, Output, Options).
1417failure(Unit, Name, Progress, Line, E, Time, Output, Options) =>
1418 assert_cyclic(failed(Unit, Name, Line, E, Time)),
1419 progress(Unit:Name, Progress, failed, Time),
1420 report_failure(Unit, Name, Progress, Line, E, Time, Output, Options).
1421
1429
1430:- if(swi). 1431assert_cyclic(Term) :-
1432 acyclic_term(Term),
1433 !,
1434 assert(Term).
1435assert_cyclic(Term) :-
1436 Term =.. [Functor|Args],
1437 recorda(cyclic, Args, Id),
1438 functor(Term, _, Arity),
1439 length(NewArgs, Arity),
1440 Head =.. [Functor|NewArgs],
1441 assert((Head :- recorded(_, Var, Id), Var = NewArgs)).
1442:- else. 1443:- if(sicstus). 1444:- endif. 1445assert_cyclic(Term) :-
1446 assert(Term).
1447:- endif. 1448
1449
1450 1453
1454:- if(current_prolog_flag(threads, true)). 1455
1456:- dynamic
1457 job_data/2, 1458 scheduled_unit/1. 1459
1460schedule_unit(_:[]) :-
1461 !.
1462schedule_unit(UnitAndTests) :-
1463 UnitAndTests = Unit:_Tests,
1464 job_data(Queue, _),
1465 !,
1466 assertz(scheduled_unit(Unit)),
1467 thread_send_message(Queue, unit(UnitAndTests)).
1468schedule_unit(Unit) :-
1469 run_unit(Unit).
1470
1474
1475setup_jobs(Count) :-
1476 ( current_test_flag(jobs, Jobs0),
1477 integer(Jobs0)
1478 -> true
1479 ; current_prolog_flag(cpu_count, Jobs0)
1480 ),
1481 Jobs is min(Count, Jobs0),
1482 Jobs > 1,
1483 !,
1484 message_queue_create(Q, [alias(plunit_jobs)]),
1485 length(TIDs, Jobs),
1486 foldl(create_plunit_job(Q), TIDs, 1, _),
1487 asserta(job_data(Q, TIDs)),
1488 job_feedback(informational, jobs(Jobs)).
1489setup_jobs(_) :-
1490 job_feedback(informational, jobs(1)).
1491
1492create_plunit_job(Q, TID, N, N1) :-
1493 N1 is N + 1,
1494 atom_concat(plunit_job_, N, Alias),
1495 thread_create(plunit_job(Q), TID, [alias(Alias)]).
1496
1497plunit_job(Queue) :-
1498 repeat,
1499 ( catch(thread_get_message(Queue, Job,
1500 [ timeout(10)
1501 ]),
1502 error(_,_), fail)
1503 -> job(Job),
1504 fail
1505 ; !
1506 ).
1507
1508job(unit(Unit:Tests)) =>
1509 run_unit(Unit:Tests).
1510job(test(Unit, Test)) =>
1511 run_test(Unit, Test).
1512
1513cleanup_jobs :-
1514 retract(job_data(Queue, TIDSs)),
1515 !,
1516 message_queue_destroy(Queue),
1517 maplist(thread_join, TIDSs).
1518cleanup_jobs.
1519
1523
1524job_wait(Unit) :-
1525 thread_wait(\+ scheduled_unit(Unit),
1526 [ wait_preds([scheduled_unit/1]),
1527 timeout(1)
1528 ]),
1529 !.
1530job_wait(Unit) :-
1531 job_data(_Queue, TIDs),
1532 member(TID, TIDs),
1533 thread_property(TID, status(running)),
1534 !,
1535 job_wait(Unit).
1536job_wait(_).
1537
1538
1539job_info(begin(unit(Unit))) =>
1540 print_message(silent, plunit(begin(Unit))).
1541job_info(end(unit(Unit, Summary))) =>
1542 retractall(scheduled_unit(Unit)),
1543 print_message(silent, plunit(end(Unit, Summary))).
1544
1545:- else. 1546
1547schedule_unit(Unit) :-
1548 run_unit(Unit).
1549
1550setup_jobs(_) :-
1551 print_message(silent, plunit(jobs(1))).
1552cleanup_jobs.
1553job_wait(_).
1554job_info(_).
1555
1556:- endif. 1557
1558
1559
1560 1563
1574
1575begin_test(Unit, Test, Line, Progress) :-
1576 thread_self(Me),
1577 assert(running(Unit, Test, Line, Progress, Me)),
1578 unit_file(Unit, File),
1579 test_count(Total),
1580 job_feedback(information, begin(Unit:Test, File:Line, Progress/Total)).
1581
1582end_test(Unit, Test, Line, Progress) :-
1583 thread_self(Me),
1584 retractall(running(_,_,_,_,Me)),
1585 unit_file(Unit, File),
1586 test_count(Total),
1587 job_feedback(information, end(Unit:Test, File:Line, Progress/Total)).
1588
1592
1593running_tests :-
1594 running_tests(Running),
1595 print_message(informational, plunit(running(Running))).
1596
1597running_tests(Running) :-
1598 test_count(Total),
1599 findall(running(Unit:Test, File:Line, Progress/Total, Thread),
1600 ( running(Unit, Test, Line, Progress, Thread),
1601 unit_file(Unit, File)
1602 ), Running).
1603
1604
1608
1609current_test(Unit, Test, Line, Body, Options) :-
1610 current_unit(Unit, Module, _Supers, _UnitOptions),
1611 Module:'unit test'(Test, Line, Options, Body).
1612
1616
1617current_test_unit(Unit, UnitOptions) :-
1618 current_unit(Unit, _Module, _Supers, UnitOptions).
1619
1620
1621count(Goal, Count) :-
1622 aggregate_all(count, Goal, Count).
1623
1628
1629test_summary(Unit, Summary) :-
1630 count(failed(Unit, _0Test, _0Line, _Reason, _0Time), Failed),
1631 count(timeout(Unit, _0Test, _0Line, _Limit, _0Time), Timeout),
1632 count(passed(Unit, _0Test, _0Line, _Det, _0Time), Passed),
1633 count(blocked(Unit, _0Test, _0Line, _0Reason), Blocked),
1634 count(fixme(Unit, _0Test, _0Line, _0Reason, _0How), Fixme),
1635 test_count(Total),
1636 Summary = plunit{total:Total,
1637 passed:Passed,
1638 failed:Failed,
1639 timeout:Timeout,
1640 blocked:Blocked,
1641 fixme:Fixme}.
1642
1643test_summary_passed(Summary) :-
1644 _{failed: 0} :< Summary.
1645
1649
1650report(Time, _Options) :-
1651 test_summary(_, Summary),
1652 print_message(silent, plunit(Summary)),
1653 _{ passed:Passed,
1654 failed:Failed,
1655 timeout:Timeout,
1656 blocked:Blocked,
1657 fixme:Fixme
1658 } :< Summary,
1659 ( Passed+Failed+Timeout+Blocked+Fixme =:= 0
1660 -> info(plunit(no_tests))
1661 ; Failed+Timeout =:= 0
1662 -> report_blocked(Blocked),
1663 report_fixme,
1664 test_count(Total),
1665 info(plunit(all_passed(Total, Passed, Time)))
1666 ; report_blocked(Blocked),
1667 report_fixme,
1668 report_failed(Failed),
1669 report_timeout(Timeout),
1670 info(plunit(passed(Passed))),
1671 info(plunit(total_time(Time)))
1672 ).
1673
1674report_blocked(0) =>
1675 true.
1676report_blocked(Blocked) =>
1677 findall(blocked(Unit:Name, File:Line, Reason),
1678 ( blocked(Unit, Name, Line, Reason),
1679 unit_file(Unit, File)
1680 ),
1681 BlockedTests),
1682 info(plunit(blocked(Blocked, BlockedTests))).
1683
1684report_failed(Failed) :-
1685 print_message(error, plunit(failed(Failed))).
1686
1687report_timeout(Count) :-
1688 print_message(warning, plunit(timeout(Count))).
1689
1690report_fixme :-
1691 report_fixme(_,_,_).
1692
1693report_fixme(TuplesF, TuplesP, TuplesN) :-
1694 fixme(failed, TuplesF, Failed),
1695 fixme(passed, TuplesP, Passed),
1696 fixme(nondet, TuplesN, Nondet),
1697 print_message(informational, plunit(fixme(Failed, Passed, Nondet))).
1698
1699
1700fixme(How, Tuples, Count) :-
1701 findall(fixme(Unit, Name, Line, Reason, How),
1702 fixme(Unit, Name, Line, Reason, How), Tuples),
1703 length(Tuples, Count).
1704
1705report_failure(Unit, Name, Progress, Line, Error,
1706 Time, Output, _Options) =>
1707 test_count(Total),
1708 job_feedback(error, failed(Unit:Name, Progress/Total, Line,
1709 Error, Time, Output)).
1710
1711
1716
1717test_report(fixme) :-
1718 !,
1719 report_fixme(TuplesF, TuplesP, TuplesN),
1720 append([TuplesF, TuplesP, TuplesN], Tuples),
1721 print_message(informational, plunit(fixme(Tuples))).
1722test_report(What) :-
1723 throw_error(domain_error(report_class, What), _).
1724
1725
1726 1729
1734
1735unit_file(Unit, File), nonvar(Unit) =>
1736 unit_file_(Unit, File),
1737 !.
1738unit_file(Unit, File) =>
1739 unit_file_(Unit, File).
1740
1741unit_file_(Unit, File) :-
1742 current_unit(Unit, Module, _Context, _Options),
1743 module_property(Module, file(File)).
1744unit_file_(Unit, PlFile) :-
1745 test_file_for(TestFile, PlFile),
1746 module_property(Module, file(TestFile)),
1747 current_unit(Unit, Module, _Context, _Options).
1748
1749
1750 1753
1758
1759load_test_files(_Options) :-
1760 State = state(0,0),
1761 ( source_file(File),
1762 file_name_extension(Base, Old, File),
1763 Old \== plt,
1764 file_name_extension(Base, plt, TestFile),
1765 exists_file(TestFile),
1766 inc_arg(1, State),
1767 ( test_file_for(TestFile, File)
1768 -> true
1769 ; load_files(TestFile,
1770 [ if(changed),
1771 imports([])
1772 ]),
1773 inc_arg(2, State),
1774 asserta(test_file_for(TestFile, File))
1775 ),
1776 fail
1777 ; State = state(Total, Loaded),
1778 print_message(informational, plunit(test_files(Total, Loaded)))
1779 ).
1780
1781inc_arg(Arg, State) :-
1782 arg(Arg, State, N0),
1783 N is N0+1,
1784 nb_setarg(Arg, State, N).
1785
1786
1787 1790
1795
1796info(Term) :-
1797 message_level(Level),
1798 print_message(Level, Term).
1799
1814
1815progress(UnitTest, _Progress, forall(end, Nth, FTotal), Time) =>
1816 ( retract(forall_failures(Nth, FFailed))
1817 -> true
1818 ; FFailed = 0
1819 ),
1820 test_count(Total),
1821 job_feedback(information, progress(UnitTest, forall(FTotal,FFailed), Nth/Total, Time)).
1822progress(UnitTest, Progress, Result, Time), Progress = forall(_Vars, Nth-_I) =>
1823 with_mutex(plunit_forall_counter,
1824 update_forall_failures(Nth, Result)),
1825 test_count(Total),
1826 job_feedback(information, progress(UnitTest, Result, Progress/Total, Time)).
1827progress(UnitTest, Progress, Result, Time) =>
1828 test_count(Total),
1829 job_feedback(information, progress(UnitTest, Result, Progress/Total, Time)).
1830
1831update_forall_failures(_Nth, passed) =>
1832 true.
1833update_forall_failures(Nth, _) =>
1834 ( retract(forall_failures(Nth, Failed0))
1835 -> true
1836 ; Failed0 = 0
1837 ),
1838 Failed is Failed0+1,
1839 asserta(forall_failures(Nth, Failed)).
1840
1841message_level(Level) :-
1842 ( current_test_flag(silent, true)
1843 -> Level = silent
1844 ; Level = informational
1845 ).
1846
1847locationprefix(File:Line) -->
1848 !,
1849 [ url(File:Line), ':'-[], nl, ' ' ].
1850locationprefix(test(Unit,_Test,Line)) -->
1851 !,
1852 { unit_file(Unit, File) },
1853 locationprefix(File:Line).
1854locationprefix(unit(Unit)) -->
1855 !,
1856 [ 'PL-Unit: unit ~w: '-[Unit] ].
1857locationprefix(FileLine) -->
1858 { throw_error(type_error(locationprefix,FileLine), _) }.
1859
1860:- discontiguous
1861 message//1. 1862:- '$hide'(message//1). 1863
1864message(error(context_error(plunit_close(Name, -)), _)) -->
1865 [ 'PL-Unit: cannot close unit ~w: no open unit'-[Name] ].
1866message(error(context_error(plunit_close(Name, Start)), _)) -->
1867 [ 'PL-Unit: cannot close unit ~w: current unit is ~w'-[Name, Start] ].
1868message(plunit(nondet(File, Line, Name))) -->
1869 locationprefix(File:Line),
1870 [ 'PL-Unit: Test ~w: Test succeeded with choicepoint'- [Name] ].
1871message(error(plunit(incompatible_options, Tests), _)) -->
1872 [ 'PL-Unit: incompatible test-options: ~p'-[Tests] ].
1873message(plunit(sto(true))) -->
1874 [ 'Option sto(true) is ignored. See `occurs_check` option.'-[] ].
1875message(plunit(test_files(Total, Loaded))) -->
1876 [ 'Found ~D .plt test files, loaded ~D'-[Total, Loaded] ].
1877
1878 1879message(plunit(jobs(1))) -->
1880 !.
1881message(plunit(jobs(N))) -->
1882 [ 'Testing with ~D parallel jobs'-[N] ].
1883message(plunit(begin(_Unit))) -->
1884 { tty_feedback },
1885 !.
1886message(plunit(begin(Unit))) -->
1887 [ 'Start unit: ~w~n'-[Unit], flush ].
1888message(plunit(end(_Unit, _Summary))) -->
1889 { tty_feedback },
1890 !.
1891message(plunit(end(Unit, Summary))) -->
1892 ( {test_summary_passed(Summary)}
1893 -> [ 'End unit ~w: passed (~3f sec CPU)'-[Unit, Summary.time.cpu] ]
1894 ; [ ansi(error, 'End unit ~w: **FAILED (~3f sec CPU)', [Unit, Summary.time.cpu]) ]
1895 ).
1896message(plunit(blocked(unit(Unit, Reason)))) -->
1897 [ 'PL-Unit: ~w blocked: ~w'-[Unit, Reason] ].
1898message(plunit(running([]))) -->
1899 !,
1900 [ 'PL-Unit: no tests running' ].
1901message(plunit(running([One]))) -->
1902 !,
1903 [ 'PL-Unit: running ' ],
1904 running(One).
1905message(plunit(running(More))) -->
1906 !,
1907 [ 'PL-Unit: running tests:', nl ],
1908 running(More).
1909message(plunit(fixme([]))) --> !.
1910message(plunit(fixme(Tuples))) -->
1911 !,
1912 fixme_message(Tuples).
1913message(plunit(total_time(Time))) -->
1914 [ 'Test run completed'-[] ],
1915 test_time(Time).
1916
1917 1918message(plunit(blocked(1, Tests))) -->
1919 !,
1920 [ 'one test is blocked'-[] ],
1921 blocked_tests(Tests).
1922message(plunit(blocked(N, Tests))) -->
1923 [ '~D tests are blocked'-[N] ],
1924 blocked_tests(Tests).
1925
1926blocked_tests(Tests) -->
1927 { current_test_flag(show_blocked, true) },
1928 !,
1929 [':'-[]],
1930 list_blocked(Tests).
1931blocked_tests(_) -->
1932 [ ' (use run_tests/2 with ', ansi(code, 'show_blocked(true)', []),
1933 ' for details)'-[]
1934 ].
1935
1936list_blocked([]) --> !.
1937list_blocked([blocked(Unit:Test, Pos, Reason)|T]) -->
1938 [nl],
1939 locationprefix(Pos),
1940 test_name(Unit:Test, -),
1941 [ ': ~w'-[Reason] ],
1942 list_blocked(T).
1943
1944 1945message(plunit(no_tests)) -->
1946 !,
1947 [ 'No tests to run' ].
1948message(plunit(all_passed(1, 1, Time))) -->
1949 !,
1950 [ 'test passed' ],
1951 test_time(Time).
1952message(plunit(all_passed(Total, Total, Time))) -->
1953 !,
1954 [ 'All ~D tests passed'-[Total] ],
1955 test_time(Time).
1956message(plunit(all_passed(Total, Count, Time))) -->
1957 !,
1958 { SubTests is Count-Total },
1959 [ 'All ~D (+~D sub-tests) tests passed'- [Total, SubTests] ],
1960 test_time(Time).
1961
1962test_time(Time) -->
1963 { var(Time) }, !.
1964test_time(Time) -->
1965 [ ' in ~3f seconds (~3f cpu)'-[Time.wall, Time.cpu] ].
1966
1967message(plunit(passed(Count))) -->
1968 !,
1969 [ '~D tests passed'-[Count] ].
1970message(plunit(failed(0))) -->
1971 !,
1972 [].
1973message(plunit(failed(1))) -->
1974 !,
1975 [ '1 test failed'-[] ].
1976message(plunit(failed(N))) -->
1977 [ '~D tests failed'-[N] ].
1978message(plunit(timeout(0))) -->
1979 !,
1980 [].
1981message(plunit(timeout(N))) -->
1982 [ '~D tests timed out'-[N] ].
1983message(plunit(fixme(0,0,0))) -->
1984 [].
1985message(plunit(fixme(Failed,0,0))) -->
1986 !,
1987 [ 'all ~D tests flagged FIXME failed'-[Failed] ].
1988message(plunit(fixme(Failed,Passed,0))) -->
1989 [ 'FIXME: ~D failed; ~D passed'-[Failed, Passed] ].
1990message(plunit(fixme(Failed,Passed,Nondet))) -->
1991 { TotalPassed is Passed+Nondet },
1992 [ 'FIXME: ~D failed; ~D passed; (~D nondet)'-
1993 [Failed, TotalPassed, Nondet] ].
1994
1995message(plunit(begin(Unit:Test, _Location, Progress))) -->
1996 { tty_columns(SummaryWidth, _Margin),
1997 test_name_summary(Unit:Test, SummaryWidth, NameS),
1998 progress_string(Progress, ProgressS)
1999 },
2000 ( { tty_feedback,
2001 tty_clear_to_eol(CE)
2002 }
2003 -> [ at_same_line, '\r[~w] ~w ..~w'-[ProgressS, NameS,
2004 CE], flush ]
2005 ; { jobs(_) }
2006 -> [ '[~w] ~w ..'-[ProgressS, NameS] ]
2007 ; [ '[~w] ~w ..'-[ProgressS, NameS], flush ]
2008 ).
2009message(plunit(end(_UnitTest, _Location, _Progress))) -->
2010 [].
2011message(plunit(progress(_UnitTest, Status, _Progress, _Time))) -->
2012 { Status = forall(_,_)
2013 ; Status == assertion
2014 },
2015 !.
2016message(plunit(progress(Unit:Test, Status, Progress, Time))) -->
2017 { jobs(_),
2018 !,
2019 tty_columns(SummaryWidth, Margin),
2020 test_name_summary(Unit:Test, SummaryWidth, NameS),
2021 progress_string(Progress, ProgressS),
2022 progress_tag(Status, Tag, _Keep, Style)
2023 },
2024 [ ansi(Style, '[~w] ~w ~`.t ~w (~3f sec)~*|',
2025 [ProgressS, NameS, Tag, Time.wall, Margin]) ].
2026message(plunit(progress(_UnitTest, Status, _Progress, Time))) -->
2027 { tty_columns(_SummaryWidth, Margin),
2028 progress_tag(Status, Tag, _Keep, Style)
2029 },
2030 [ at_same_line, ansi(Style, '~`.t ~w (~3f sec)~*|',
2031 [Tag, Time.wall, Margin]) ],
2032 ( { tty_feedback }
2033 -> [flush]
2034 ; []
2035 ).
2036message(plunit(failed(Unit:Test, Progress, Line, Failure, _Time, Output))) -->
2037 { unit_file(Unit, File) },
2038 locationprefix(File:Line),
2039 test_name(Unit:Test, Progress),
2040 [': '-[] ],
2041 failure(Failure),
2042 test_output(Output).
2043message(plunit(timeout(Unit:Test, Progress, Line, Limit, Output))) -->
2044 { unit_file(Unit, File) },
2045 locationprefix(File:Line),
2046 test_name(Unit:Test, Progress),
2047 [': '-[] ],
2048 timeout(Limit),
2049 test_output(Output).
2050:- if(swi). 2051message(plunit(failed_assertion(Unit:Test, Line, AssertLoc,
2052 Progress, Reason, Goal))) -->
2053 { unit_file(Unit, File) },
2054 locationprefix(File:Line),
2055 test_name(Unit:Test, Progress),
2056 [ ': assertion'-[] ],
2057 assertion_location(AssertLoc, File),
2058 assertion_reason(Reason), ['\n\t'],
2059 assertion_goal(Unit, Goal).
2060
2061assertion_location(File:Line, File) -->
2062 [ ' at line ~w'-[Line] ].
2063assertion_location(File:Line, _) -->
2064 [ ' at ', url(File:Line) ].
2065assertion_location(unknown, _) -->
2066 [].
2067
2068assertion_reason(fail) -->
2069 !,
2070 [ ' failed'-[] ].
2071assertion_reason(Error) -->
2072 { message_to_string(Error, String) },
2073 [ ' raised "~w"'-[String] ].
2074
2075assertion_goal(Unit, Goal) -->
2076 { unit_module(Unit, Module),
2077 unqualify(Goal, Module, Plain)
2078 },
2079 [ 'Assertion: ~p'-[Plain] ].
2080
2081unqualify(Var, _, Var) :-
2082 var(Var),
2083 !.
2084unqualify(M:Goal, Unit, Goal) :-
2085 nonvar(M),
2086 unit_module(Unit, M),
2087 !.
2088unqualify(M:Goal, _, Goal) :-
2089 callable(Goal),
2090 predicate_property(M:Goal, imported_from(system)),
2091 !.
2092unqualify(Goal, _, Goal).
2093
2094test_output(Msgs-String) -->
2095 { nonvar(Msgs) },
2096 !,
2097 test_output(String).
2098test_output("") --> [].
2099test_output(Output) -->
2100 [ ansi(code, '~N~s', [Output]) ].
2101
2102:- endif. 2103 2104message(plunit(error(Where, Context, _Output, throw(Exception)))) -->
2105 locationprefix(Context),
2106 { message_to_string(Exception, String) },
2107 [ 'error in ~w: ~w'-[Where, String] ].
2108message(plunit(error(Where, Context, _Output, false))) -->
2109 locationprefix(Context),
2110 [ 'setup failed in ~w'-[Where] ].
2111
2112 2113message(plunit(test_output(_, Output))) -->
2114 [ '~s'-[Output] ].
2115 2116:- if(swi). 2117message(interrupt(begin)) -->
2118 { thread_self(Me),
2119 running(Unit, Test, Line, Progress, Me),
2120 !,
2121 unit_file(Unit, File),
2122 restore_output_state
2123 },
2124 [ 'Interrupted test '-[] ],
2125 running(running(Unit:Test, File:Line, Progress, Me)),
2126 [nl],
2127 '$messages':prolog_message(interrupt(begin)).
2128message(interrupt(begin)) -->
2129 '$messages':prolog_message(interrupt(begin)).
2130:- endif. 2131
2132message(concurrent) -->
2133 [ 'concurrent(true) at the level of units is currently ignored.', nl,
2134 'See set_test_options/1 with jobs(Count) for concurrent testing.'
2135 ].
2136
2137test_name(Name, forall(Bindings, _Nth-I)) -->
2138 !,
2139 test_name(Name, -),
2140 [ ' (~d-th forall bindings = '-[I],
2141 ansi(code, '~p', [Bindings]), ')'-[]
2142 ].
2143test_name(Name, _) -->
2144 !,
2145 [ 'test ', ansi(code, '~q', [Name]) ].
2146
2147running(running(Unit:Test, File:Line, _Progress, Thread)) -->
2148 thread(Thread),
2149 [ '~q:~q at '-[Unit, Test], url(File:Line) ].
2150running([H|T]) -->
2151 ['\t'], running(H),
2152 ( {T == []}
2153 -> []
2154 ; [nl], running(T)
2155 ).
2156
2157thread(main) --> !.
2158thread(Other) -->
2159 [' [~w] '-[Other] ].
2160
2161:- if(swi). 2162write_term(T, OPS) -->
2163 ['~W'-[T,OPS] ].
2164:- else. 2165write_term(T, _OPS) -->
2166 ['~q'-[T]].
2167:- endif. 2168
2169expected_got_ops_(Ex, E, OPS, Goals) -->
2170 [' Expected: '-[]], write_term(Ex, OPS), [nl],
2171 [' Got: '-[]], write_term(E, OPS), [],
2172 ( { Goals = [] } -> []
2173 ; [nl, ' with: '-[]], write_term(Goals, OPS), []
2174 ).
2175
2176
2177failure(List) -->
2178 { is_list(List) },
2179 !,
2180 [ nl ],
2181 failures(List).
2182failure(Var) -->
2183 { var(Var) },
2184 !,
2185 [ 'Unknown failure?' ].
2186failure(succeeded(Time)) -->
2187 !,
2188 [ 'must fail but succeeded in ~2f seconds~n'-[Time] ].
2189failure(wrong_error(Expected, Error)) -->
2190 !,
2191 { copy_term(Expected-Error, Ex-E, Goals),
2192 numbervars(Ex-E-Goals, 0, _),
2193 write_options(OPS)
2194 },
2195 [ 'wrong error'-[], nl ],
2196 expected_got_ops_(Ex, E, OPS, Goals).
2197failure(wrong_answer(cmp(Var, Cmp))) -->
2198 { Cmp =.. [Op,Answer,Expected],
2199 !,
2200 copy_term(Expected-Answer, Ex-A, Goals),
2201 numbervars(Ex-A-Goals, 0, _),
2202 write_options(OPS)
2203 },
2204 [ 'wrong answer for ', ansi(code, '~w', [Var]),
2205 ' (compared using ~w)'-[Op], nl ],
2206 expected_got_ops_(Ex, A, OPS, Goals).
2207failure(wrong_answer(Cmp)) -->
2208 { Cmp =.. [Op,Answer,Expected],
2209 !,
2210 copy_term(Expected-Answer, Ex-A, Goals),
2211 numbervars(Ex-A-Goals, 0, _),
2212 write_options(OPS)
2213 },
2214 [ 'wrong answer (compared using ~w)'-[Op], nl ],
2215 expected_got_ops_(Ex, A, OPS, Goals).
2216failure(wrong_answer(CmpExpected, Bindings)) -->
2217 { ( CmpExpected = all(Cmp)
2218 -> Cmp =.. [_Op1,_,Expected],
2219 Got = Bindings,
2220 Type = all
2221 ; CmpExpected = set(Cmp),
2222 Cmp =.. [_Op2,_,Expected0],
2223 sort(Expected0, Expected),
2224 sort(Bindings, Got),
2225 Type = set
2226 )
2227 },
2228 [ 'wrong "~w" answer:'-[Type] ],
2229 [ nl, ' Expected: ~q'-[Expected] ],
2230 [ nl, ' Found: ~q'-[Got] ].
2231:- if(swi). 2232failure(cmp_error(_Cmp, Error)) -->
2233 { message_to_string(Error, Message) },
2234 [ 'Comparison error: ~w'-[Message] ].
2235failure(throw(Error)) -->
2236 { Error = error(_,_),
2237 !,
2238 message_to_string(Error, Message)
2239 },
2240 [ 'received error: ~w'-[Message] ].
2241:- endif. 2242failure(message) -->
2243 !,
2244 [ 'Generated unexpected warning or error'-[] ].
2245failure(Why) -->
2246 [ '~p'-[Why] ].
2247
2248failures([]) -->
2249 !.
2250failures([H|T]) -->
2251 !,
2252 failure(H), [nl],
2253 failures(T).
2254
2255timeout(Limit) -->
2256 [ 'Timeout exceeeded (~2f sec)'-[Limit] ].
2257
2258fixme_message([]) --> [].
2259fixme_message([fixme(Unit, _Name, Line, Reason, How)|T]) -->
2260 { unit_file(Unit, File) },
2261 fixme_message(File:Line, Reason, How),
2262 ( {T == []}
2263 -> []
2264 ; [nl],
2265 fixme_message(T)
2266 ).
2267
2268fixme_message(Location, Reason, failed) -->
2269 [ 'FIXME: ~w: ~w'-[Location, Reason] ].
2270fixme_message(Location, Reason, passed) -->
2271 [ 'FIXME: ~w: passed ~w'-[Location, Reason] ].
2272fixme_message(Location, Reason, nondet) -->
2273 [ 'FIXME: ~w: passed (nondet) ~w'-[Location, Reason] ].
2274
2275
2276write_options([ numbervars(true),
2277 quoted(true),
2278 portray(true),
2279 max_depth(100),
2280 attributes(portray)
2281 ]).
2282
2287
2288test_name_summary(Term, MaxLen, Summary) :-
2289 summary_string(Term, Text),
2290 atom_length(Text, Len),
2291 ( Len =< MaxLen
2292 -> Summary = Text
2293 ; End is MaxLen//2,
2294 Pre is MaxLen - End - 2,
2295 sub_string(Text, 0, Pre, _, PreText),
2296 sub_string(Text, _, End, 0, PostText),
2297 format(string(Summary), '~w..~w', [PreText,PostText])
2298 ).
2299
2300summary_string(Unit:Test, String) =>
2301 summary_string(Test, String1),
2302 atomics_to_string([Unit, String1], :, String).
2303summary_string(@(Name,Vars), String) =>
2304 format(string(String), '~W (using ~W)',
2305 [ Name, [numbervars(true), quoted(false)],
2306 Vars, [numbervars(true), portray(true), quoted(true)]
2307 ]).
2308summary_string(Name, String) =>
2309 term_string(Name, String, [numbervars(true), quoted(false)]).
2310
2314
2315progress_string(forall(_Vars, N-I)/Total, S) =>
2316 format(string(S), '~w-~w/~w', [N,I,Total]).
2317progress_string(Progress, S) =>
2318 term_string(Progress, S).
2319
2325
2326progress_tag(passed, Tag, Keep, Style) =>
2327 Tag = passed, Keep = false, Style = comment.
2328progress_tag(fixme(passed), Tag, Keep, Style) =>
2329 Tag = passed, Keep = false, Style = comment.
2330progress_tag(fixme(_), Tag, Keep, Style) =>
2331 Tag = fixme, Keep = true, Style = warning.
2332progress_tag(nondet, Tag, Keep, Style) =>
2333 Tag = '**NONDET', Keep = true, Style = warning.
2334progress_tag(timeout(_Limit), Tag, Keep, Style) =>
2335 Tag = '**TIMEOUT', Keep = true, Style = warning.
2336progress_tag(assertion, Tag, Keep, Style) =>
2337 Tag = '**FAILED', Keep = true, Style = error.
2338progress_tag(failed, Tag, Keep, Style) =>
2339 Tag = '**FAILED', Keep = true, Style = error.
2340progress_tag(forall(_,0), Tag, Keep, Style) =>
2341 Tag = passed, Keep = false, Style = comment.
2342progress_tag(forall(_,_), Tag, Keep, Style) =>
2343 Tag = '**FAILED', Keep = true, Style = error.
2344
2345
2346 2349
2350save_output_state :-
2351 stream_property(Output, alias(user_output)),
2352 stream_property(Error, alias(user_error)),
2353 asserta(output_streams(Output, Error)).
2354
2355restore_output_state :-
2356 output_streams(Output, Error),
2357 !,
2358 set_stream(Output, alias(user_output)),
2359 set_stream(Error, alias(user_error)).
2360restore_output_state.
2361
2362
2363
2364 2367
2373
2374:- dynamic
2375 jobs/1, 2376 job_window/1, 2377 job_status_line/3. 2378
2379job_feedback(_, jobs(Jobs)) :-
2380 retractall(jobs(_)),
2381 Jobs > 1,
2382 asserta(jobs(Jobs)),
2383 tty_feedback,
2384 !,
2385 retractall(job_window(_)),
2386 asserta(job_window(Jobs)),
2387 retractall(job_status_line(_,_,_)),
2388 jobs_redraw.
2389job_feedback(_, jobs(Jobs)) :-
2390 !,
2391 retractall(job_window(_)),
2392 info(plunit(jobs(Jobs))).
2393job_feedback(_, Msg) :-
2394 job_window(_),
2395 !,
2396 with_mutex(plunit_feedback, job_feedback(Msg)).
2397job_feedback(Level, Msg) :-
2398 print_message(Level, plunit(Msg)).
2399
2400job_feedback(begin(Unit:Test, _Location, Progress)) =>
2401 tty_columns(SummaryWidth, _Margin),
2402 test_name_summary(Unit:Test, SummaryWidth, NameS),
2403 progress_string(Progress, ProgressS),
2404 tty_clear_to_eol(CE),
2405 job_format(comment, '\r[~w] ~w ..~w',
2406 [ProgressS, NameS, CE]),
2407 flush_output.
2408job_feedback(end(_UnitTest, _Location, _Progress)) =>
2409 true.
2410job_feedback(progress(_UnitTest, Status, _Progress, Time)) =>
2411 ( hide_progress(Status)
2412 -> true
2413 ; tty_columns(_SummaryWidth, Margin),
2414 progress_tag(Status, Tag, _Keep, Style),
2415 job_finish(Style, '~`.t ~w (~3f sec)~*|',
2416 [Tag, Time.wall, Margin])
2417 ).
2418job_feedback(failed(UnitTest, Progress, Line, Error, Time, Output)) =>
2419 tty_columns(_SummaryWidth, Margin),
2420 progress_tag(failed, Tag, _Keep, Style),
2421 job_finish(Style, '~`.t ~w (~3f sec)~*|',
2422 [Tag, Time.wall, Margin]),
2423 print_test_output(Error, Output),
2424 ( ( Error = timeout(_) 2425 ; Error == assertion 2426 )
2427 -> true
2428 ; print_message(Style, plunit(failed(UnitTest, Progress, Line,
2429 Error, Time, "")))
2430 ),
2431 jobs_redraw.
2432job_feedback(begin(_Unit)) => true.
2433job_feedback(end(_Unit, _Summary)) => true.
2434
2435hide_progress(assertion).
2436hide_progress(forall(_,_)).
2437hide_progress(failed).
2438hide_progress(timeout(_)).
2439
2440print_test_output(Error, _Msgs-Output) =>
2441 print_test_output(Error, Output).
2442print_test_output(_, "") => true.
2443print_test_output(assertion, Output) =>
2444 print_message(debug, plunit(test_output(error, Output))).
2445print_test_output(message, Output) =>
2446 print_message(debug, plunit(test_output(error, Output))).
2447print_test_output(_, Output) =>
2448 print_message(debug, plunit(test_output(informational, Output))).
2449
2453
2454jobs_redraw :-
2455 job_window(N),
2456 !,
2457 tty_columns(_, Width),
2458 tty_header_line(Width),
2459 forall(between(1,N,Line), job_redraw_worker(Line)),
2460 tty_header_line(Width).
2461jobs_redraw.
2462
2463job_redraw_worker(Line) :-
2464 ( job_status_line(Line, Fmt, Args)
2465 -> ansi_format(comment, Fmt, Args)
2466 ; true
2467 ),
2468 nl.
2469
2475
2476job_format(Style, Fmt, Args) :-
2477 job_self(Job),
2478 job_format(Job, Style, Fmt, Args, true).
2479
2485
2486job_finish(Style, Fmt, Args) :-
2487 job_self(Job),
2488 job_finish(Job, Style, Fmt, Args).
2489
2490:- det(job_finish/4). 2491job_finish(Job, Style, Fmt, Args) :-
2492 retract(job_status_line(Job, Fmt0, Args0)),
2493 !,
2494 string_concat(Fmt0, Fmt, Fmt1),
2495 append(Args0, Args, Args1),
2496 job_format(Job, Style, Fmt1, Args1, false).
2497
2498:- det(job_format/5). 2499job_format(Job, Style, Fmt, Args, Save) :-
2500 job_window(Jobs),
2501 Up is Jobs+2-Job,
2502 flush_output(user_output),
2503 tty_up_and_clear(Up),
2504 ansi_format(Style, Fmt, Args),
2505 ( Save == true
2506 -> retractall(job_status_line(Job, _, _)),
2507 asserta(job_status_line(Job, Fmt, Args))
2508 ; true
2509 ),
2510 tty_down_and_home(Up),
2511 flush_output(user_output).
2512
2513:- det(job_self/1). 2514job_self(Job) :-
2515 job_window(N),
2516 N > 1,
2517 thread_self(Me),
2518 split_string(Me, '_', '', [_,_,S]),
2519 number_string(Job, S).
2520
2525
2526tty_feedback :-
2527 has_tty,
2528 current_test_flag(format, tty).
2529
2530has_tty :-
2531 stream_property(user_output, tty(true)).
2532
2533tty_columns(SummaryWidth, Margin) :-
2534 tty_width(W),
2535 Margin is W-8,
2536 SummaryWidth is max(20,Margin-34).
2537
2538tty_width(W) :-
2539 current_predicate(tty_size/2),
2540 catch(tty_size(_Rows, Cols), error(_,_), fail),
2541 Cols > 25,
2542 !,
2543 W = Cols.
2544tty_width(80).
2545
(Width) :-
2547 ansi_format(comment, '~N~`\u2015t~*|~n', [Width]).
2548
2549:- if(current_predicate(tty_get_capability/3)). 2550tty_clear_to_eol(S) :-
2551 getenv('TERM', _),
2552 catch(tty_get_capability(ce, string, S),
2553 error(_,_),
2554 fail),
2555 !.
2556:- endif. 2557tty_clear_to_eol('\e[K').
2558
2559tty_up_and_clear(Lines) :-
2560 format(user_output, '\e[~dA\r\e[K', [Lines]).
2561
2562tty_down_and_home(Lines) :-
2563 format(user_output, '\e[~dB\r', [Lines]).
2564
2565:- if(swi). 2566
2567:- multifile
2568 prolog:message/3,
2569 user:message_hook/3. 2570
2571prolog:message(Term) -->
2572 message(Term).
2573
2575
2576user:message_hook(make(done(Files)), _, _) :-
2577 make_run_tests(Files),
2578 fail. 2579
2580:- endif. 2581
2582:- if(sicstus). 2583
2584user:generate_message_hook(Message) -->
2585 message(Message),
2586 [nl]. 2587
2594
2595user:message_hook(informational, plunit(begin(Unit)), _Lines) :-
2596 format(user_error, '% PL-Unit: ~w ', [Unit]),
2597 flush_output(user_error).
2598user:message_hook(informational, plunit(end(_Unit)), _Lines) :-
2599 format(user, ' done~n', []).
2600
2601:- endif.