View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2006-2023, University of Amsterdam
    7			      VU University Amsterdam
    8			      CWI, Amsterdam
    9			      SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(plunit,
   39	  [ set_test_options/1,         % +Options
   40	    begin_tests/1,              % +Name
   41	    begin_tests/2,              % +Name, +Options
   42	    end_tests/1,                % +Name
   43	    run_tests/0,                % Run all tests
   44	    run_tests/1,                % +Tests
   45	    run_tests/2,                % +Tests, +Options
   46	    load_test_files/1,          % +Options
   47	    running_tests/0,            % Prints currently running test
   48	    current_test/5,             % ?Unit,?Test,?Line,?Body,?Options
   49	    current_test_unit/2,        % ?Unit,?Options
   50	    test_report/1               % +What
   51	  ]).

Unit Testing

Unit testing environment for SWI-Prolog and SICStus Prolog. For usage, please visit https://www.swi-prolog.org/pldoc/package/plunit. */

   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(time), [call_with_time_limit/2]).   69:- autoload(library(ansi_term), [ansi_format/3]).   70
   71:- meta_predicate
   72    valid_options(1, +),
   73    count(0, -).   74
   75		 /*******************************
   76		 *    CONDITIONAL COMPILATION   *
   77		 *******************************/
   78
   79swi     :- catch(current_prolog_flag(dialect, swi), _, fail), !.
   80swi     :- catch(current_prolog_flag(dialect, yap), _, fail).
   81sicstus :- catch(current_prolog_flag(system_type, _), _, fail).
   82
   83throw_error(Error_term,Impldef) :-
   84    throw(error(Error_term,context(Impldef,_))).
   85
   86:- set_prolog_flag(generate_debug_info, false).   87current_test_flag(optimise, Value) =>
   88    current_prolog_flag(optimise, Value).
   89current_test_flag(occurs_check, Value) =>
   90    (   current_prolog_flag(plunit_occurs_check, Value0)
   91    ->  Value = Value0
   92    ;   current_prolog_flag(occurs_check, Value)
   93    ).
   94current_test_flag(Name, Value), atom(Name) =>
   95    atom_concat(plunit_, Name, Flag),
   96    current_prolog_flag(Flag, Value).
   97current_test_flag(Name, Value), var(Name) =>
   98    global_test_option(Opt, _, _Type, _Default),
   99    functor(Opt, Name, 1),
  100    current_test_flag(Name, Value).
  101
  102set_test_flag(Name, Value) :-
  103    Opt =.. [Name, Value],
  104    global_test_option(Opt),
  105    !,
  106    atom_concat(plunit_, Name, Flag),
  107    set_prolog_flag(Flag, Value).
  108set_test_flag(Name, _) :-
  109    domain_error(test_flag, Name).
  110
  111current_test_flags(Flags) :-
  112    findall(Flag, current_test_flag(Flag), Flags).
  113
  114current_test_flag(Opt) :-
  115    current_test_flag(Name, Value),
  116    Opt =.. [Name, Value].
  117
  118% ensure expansion to avoid tracing
  119goal_expansion(forall(C,A),
  120	       \+ (C, \+ A)).
  121goal_expansion(current_module(Module,File),
  122	       module_property(Module, file(File))).
  123
  124
  125		 /*******************************
  126		 *            IMPORTS           *
  127		 *******************************/
  128
  129:- initialization init_flags.  130
  131init_flags :-
  132    (   global_test_option(Option, _Value, _Type, Default),
  133	Default \== (-),
  134	Option =.. [Name,_],
  135	atom_concat(plunit_, Name, Flag),
  136	create_prolog_flag(Flag, Default, [keep(true)]),
  137	fail
  138    ;   true
  139    ).
 set_test_options(+Options)
Specifies how to deal with test suites. Defined options are:
load +Load
Whether or not the tests must be loaded. Values are never, always, normal (only if not optimised)
run(+When)
When the tests are run. Values are manual, make or make(all).
format(+Mode)
Currently one of tty or log. tty uses terminal control to overwrite successful tests, allowing the user to see the currently running tests and output from failed tests. This is the default of the output is a tty. log prints a full log of the executed tests and their result and is intended for non-interactive usage.
output(+When)
If always, emit all output as it is produced, if never, suppress all output and if on_failure, emit the output if the test fails.
show_blocked(+Bool)
Show individual blocked tests during the report.
occurs_check(+Mode)
Defines the default for the occurs_check flag during testing.
cleanup(+Bool)
If true (default =false), cleanup report at the end of run_tests/1. Used to improve cooperation with memory debuggers such as dmalloc.
jobs(Num)
Number of jobs to use for concurrent testing. Default is one, implying sequential testing.
timeout(+Seconds)
Set timeout for each individual test. This acts as a default that may be overuled at the level of units or individual tests. A timeout of 0 or negative is handled as inifinite.
  189set_test_options(Options) :-
  190    flatten([Options], List),
  191    maplist(set_test_option, List).
  192
  193set_test_option(sto(true)) =>
  194    print_message(warning, plunit(sto(true))).
  195set_test_option(jobs(Jobs)) =>
  196    must_be(positive_integer, Jobs),
  197    set_test_option_flag(jobs(Jobs)).
  198set_test_option(Option),
  199  compound(Option), global_test_option(Option) =>
  200    set_test_option_flag(Option).
  201set_test_option(Option) =>
  202    domain_error(option, Option).
  203
  204global_test_option(Opt) :-
  205    global_test_option(Opt, Value, Type, _Default),
  206    must_be(Type, Value).
  207
  208global_test_option(load(Load), Load, oneof([never,always,normal]), normal).
  209global_test_option(output(Cond), Cond, oneof([always,on_failure]), on_failure).
  210global_test_option(format(Feedback), Feedback, oneof([tty,log]), tty).
  211global_test_option(silent(Silent), Silent, boolean, false).
  212global_test_option(show_blocked(Blocked), Blocked, boolean, false).
  213global_test_option(run(When), When, oneof([manual,make,make(all)]), make).
  214global_test_option(occurs_check(Mode), Mode, oneof([false,true,error]), -).
  215global_test_option(cleanup(Bool), Bool, boolean, true).
  216global_test_option(jobs(Count), Count, positive_integer, 1).
  217global_test_option(timeout(Number), Number, number, 3600).
  218
  219set_test_option_flag(Option) :-
  220    Option =.. [Name, Value],
  221    set_test_flag(Name, Value).
 loading_tests
True if tests must be loaded.
  227loading_tests :-
  228    current_test_flag(load, Load),
  229    (   Load == always
  230    ->  true
  231    ;   Load == normal,
  232	\+ current_test_flag(optimise, true)
  233    ).
  234
  235		 /*******************************
  236		 *            MODULE            *
  237		 *******************************/
  238
  239:- dynamic
  240    loading_unit/4,                 % Unit, Module, File, OldSource
  241    current_unit/4,                 % Unit, Module, Context, Options
  242    test_file_for/2.                % ?TestFile, ?PrologFile
 begin_tests(+UnitName:atom) is det
 begin_tests(+UnitName:atom, Options) is det
Start a test-unit. UnitName is the name of the test set. the unit is ended by :- end_tests(UnitName).
  250begin_tests(Unit) :-
  251    begin_tests(Unit, []).
  252
  253begin_tests(Unit, Options) :-
  254    must_be(atom, Unit),
  255    map_sto_option(Options, Options1),
  256    valid_options(test_set_option, Options1),
  257    make_unit_module(Unit, Name),
  258    source_location(File, Line),
  259    begin_tests(Unit, Name, File:Line, Options1).
  260
  261map_sto_option(Options0, Options) :-
  262    select_option(sto(Mode), Options0, Options1),
  263    !,
  264    map_sto(Mode, Flag),
  265    Options = [occurs_check(Flag)|Options1].
  266map_sto_option(Options, Options).
  267
  268map_sto(rational_trees, Flag) => Flag = false.
  269map_sto(finite_trees, Flag)   => Flag = true.
  270map_sto(Mode, _) => domain_error(sto, Mode).
  271
  272
  273:- if(swi).  274begin_tests(Unit, Name, File:Line, Options) :-
  275    loading_tests,
  276    !,
  277    '$set_source_module'(Context, Context),
  278    (   current_unit(Unit, Name, Context, Options)
  279    ->  true
  280    ;   retractall(current_unit(Unit, Name, _, _)),
  281	assert(current_unit(Unit, Name, Context, Options))
  282    ),
  283    '$set_source_module'(Old, Name),
  284    '$declare_module'(Name, test, Context, File, Line, false),
  285    discontiguous(Name:'unit test'/4),
  286    '$set_predicate_attribute'(Name:'unit test'/4, trace, false),
  287    discontiguous(Name:'unit body'/2),
  288    asserta(loading_unit(Unit, Name, File, Old)).
  289begin_tests(Unit, Name, File:_Line, _Options) :-
  290    '$set_source_module'(Old, Old),
  291    asserta(loading_unit(Unit, Name, File, Old)).
  292
  293:- else.  294
  295% we cannot use discontiguous as a goal in SICStus Prolog.
  296
  297user:term_expansion((:- begin_tests(Set)),
  298		    [ (:- begin_tests(Set)),
  299		      (:- discontiguous(test/2)),
  300		      (:- discontiguous('unit body'/2)),
  301		      (:- discontiguous('unit test'/4))
  302		    ]).
  303
  304begin_tests(Unit, Name, File:_Line, Options) :-
  305    loading_tests,
  306    !,
  307    (   current_unit(Unit, Name, _, Options)
  308    ->  true
  309    ;   retractall(current_unit(Unit, Name, _, _)),
  310	assert(current_unit(Unit, Name, -, Options))
  311    ),
  312    asserta(loading_unit(Unit, Name, File, -)).
  313begin_tests(Unit, Name, File:_Line, _Options) :-
  314    asserta(loading_unit(Unit, Name, File, -)).
  315
  316:- endif.
 end_tests(+Name) is det
Close a unit-test module.
To be done
- Run tests/clean module?
- End of file?
  325end_tests(Unit) :-
  326    loading_unit(StartUnit, _, _, _),
  327    !,
  328    (   Unit == StartUnit
  329    ->  once(retract(loading_unit(StartUnit, _, _, Old))),
  330	'$set_source_module'(_, Old)
  331    ;   throw_error(context_error(plunit_close(Unit, StartUnit)), _)
  332    ).
  333end_tests(Unit) :-
  334    throw_error(context_error(plunit_close(Unit, -)), _).
 make_unit_module(+Name, -ModuleName) is det
 unit_module(+Name, -ModuleName) is det
  339:- if(swi).  340
  341unit_module(Unit, Module) :-
  342    atom_concat('plunit_', Unit, Module).
  343
  344make_unit_module(Unit, Module) :-
  345    unit_module(Unit, Module),
  346    (   current_module(Module),
  347	\+ current_unit(_, Module, _, _),
  348	predicate_property(Module:H, _P),
  349	\+ predicate_property(Module:H, imported_from(_M))
  350    ->  throw_error(permission_error(create, plunit, Unit),
  351		    'Existing module')
  352    ;  true
  353    ).
  354
  355:- else.  356
  357:- dynamic
  358    unit_module_store/2.  359
  360unit_module(Unit, Module) :-
  361    unit_module_store(Unit, Module),
  362    !.
  363
  364make_unit_module(Unit, Module) :-
  365    prolog_load_context(module, Module),
  366    assert(unit_module_store(Unit, Module)).
  367
  368:- endif.  369
  370		 /*******************************
  371		 *           EXPANSION          *
  372		 *******************************/
 expand_test(+Name, +Options, +Body, -Clause) is det
Expand test(Name, Options) :- Body into a clause for 'unit test'/4 and 'unit body'/2.
  379expand_test(Name, Options0, Body,
  380	    [ 'unit test'(Name, Line, Options, Module:'unit body'(Id, Vars)),
  381	      ('unit body'(Id, Vars) :- !, Body)
  382	    ]) :-
  383    source_location(_File, Line),
  384    prolog_load_context(module, Module),
  385    (   prolog_load_context(variable_names, Bindings)
  386    ->  true
  387    ;   Bindings = []
  388    ),
  389    atomic_list_concat([Name, '@line ', Line], Id),
  390    term_variables(Options0, OptionVars0), sort(OptionVars0, OptionVars),
  391    term_variables(Body, BodyVars0), sort(BodyVars0, BodyVars),
  392    ord_intersection(OptionVars, BodyVars, VarList),
  393    Vars =.. [vars|VarList],
  394    (   is_list(Options0)           % allow for single option without list
  395    ->  Options1 = Options0
  396    ;   Options1 = [Options0]
  397    ),
  398    maplist(expand_option(Bindings), Options1, Options2),
  399    join_true_options(Options2, Options3),
  400    map_sto_option(Options3, Options4),
  401    valid_options(test_option, Options4),
  402    valid_test_mode(Options4, Options).
  403
  404expand_option(_, Var, _) :-
  405    var(Var),
  406    !,
  407    throw_error(instantiation_error,_).
  408expand_option(Bindings, Cmp, true(Cond)) :-
  409    cmp(Cmp),
  410    !,
  411    var_cmp(Bindings, Cmp, Cond).
  412expand_option(_, error(X), throws(error(X, _))) :- !.
  413expand_option(_, exception(X), throws(X)) :- !. % SICStus 4 compatibility
  414expand_option(_, error(F,C), throws(error(F,C))) :- !. % SICStus 4 compatibility
  415expand_option(_, true, true(true)) :- !.
  416expand_option(_, O, O).
  417
  418cmp(_ == _).
  419cmp(_ = _).
  420cmp(_ =@= _).
  421cmp(_ =:= _).
  422
  423var_cmp(Bindings, Expr, cmp(Name, Expr)) :-
  424    arg(_, Expr, Var),
  425    var(Var),
  426    member(Name=V, Bindings),
  427    V == Var,
  428    !.
  429var_cmp(_, Expr, Expr).
  430
  431join_true_options(Options0, Options) :-
  432    partition(true_option, Options0, True, Rest),
  433    True \== [],
  434    !,
  435    maplist(arg(1), True, Conds0),
  436    flatten(Conds0, Conds),
  437    Options = [true(Conds)|Rest].
  438join_true_options(Options, Options).
  439
  440true_option(true(_)).
  441
  442valid_test_mode(Options0, Options) :-
  443    include(test_mode, Options0, Tests),
  444    (   Tests == []
  445    ->  Options = [true([true])|Options0]
  446    ;   Tests = [_]
  447    ->  Options = Options0
  448    ;   throw_error(plunit(incompatible_options, Tests), _)
  449    ).
  450
  451test_mode(true(_)).
  452test_mode(all(_)).
  453test_mode(set(_)).
  454test_mode(fail).
  455test_mode(throws(_)).
 expand(+Term, -Clauses) is semidet
  460expand(end_of_file, _) :-
  461    loading_unit(Unit, _, _, _),
  462    !,
  463    end_tests(Unit),                % warn?
  464    fail.
  465expand((:-end_tests(_)), _) :-
  466    !,
  467    fail.
  468expand(_Term, []) :-
  469    \+ loading_tests.
  470expand((test(Name) :- Body), Clauses) :-
  471    !,
  472    expand_test(Name, [], Body, Clauses).
  473expand((test(Name, Options) :- Body), Clauses) :-
  474    !,
  475    expand_test(Name, Options, Body, Clauses).
  476expand(test(Name), _) :-
  477    !,
  478    throw_error(existence_error(body, test(Name)), _).
  479expand(test(Name, _Options), _) :-
  480    !,
  481    throw_error(existence_error(body, test(Name)), _).
  482
  483:- multifile
  484    system:term_expansion/2.  485
  486system:term_expansion(Term, Expanded) :-
  487    (   loading_unit(_, _, File, _)
  488    ->  source_location(ThisFile, _),
  489	(   File == ThisFile
  490	->  true
  491	;   source_file_property(ThisFile, included_in(File, _))
  492	),
  493	expand(Term, Expanded)
  494    ).
  495
  496
  497		 /*******************************
  498		 *             OPTIONS          *
  499		 *******************************/
 valid_options(:Pred, +Options) is det
Verify Options to be a list of valid options according to Pred.
Errors
- type_error or instantiation_error.
  508valid_options(Pred, Options) :-
  509    must_be(list, Options),
  510    verify_options(Options, Pred).
  511
  512verify_options([], _).
  513verify_options([H|T], Pred) :-
  514    (   call(Pred, H)
  515    ->  verify_options(T, Pred)
  516    ;   throw_error(domain_error(Pred, H), _)
  517    ).
  518
  519valid_options(Pred, Options0, Options, Rest) :-
  520    must_be(list, Options0),
  521    partition(Pred, Options0, Options, Rest).
 test_option(+Option) is semidet
True if Option is a valid option for test(Name, Options).
  527test_option(Option) :-
  528    test_set_option(Option),
  529    !.
  530test_option(true(_)).
  531test_option(fail).
  532test_option(throws(_)).
  533test_option(all(_)).
  534test_option(set(_)).
  535test_option(nondet).
  536test_option(fixme(_)).
  537test_option(forall(X)) :-
  538    must_be(callable, X).
  539test_option(timeout(Seconds)) :-
  540    must_be(number, Seconds).
 test_option(+Option) is semidet
True if Option is a valid option for :- begin_tests(Name, Options).
  547test_set_option(blocked(X)) :-
  548    must_be(ground, X).
  549test_set_option(condition(X)) :-
  550    must_be(callable, X).
  551test_set_option(setup(X)) :-
  552    must_be(callable, X).
  553test_set_option(cleanup(X)) :-
  554    must_be(callable, X).
  555test_set_option(occurs_check(V)) :-
  556    must_be(oneof([false,true,error]), V).
  557test_set_option(concurrent(V)) :-
  558    must_be(boolean, V),
  559    print_message(informational, plunit(concurrent)).
  560test_set_option(timeout(Seconds)) :-
  561    must_be(number, Seconds).
  562
  563		 /*******************************
  564		 *             UTIL		*
  565		 *******************************/
  566
  567:- meta_predicate
  568       reify_tmo(0, -, +),
  569       reify(0, -),
  570       capture_output(0,-),
  571       capture_output(0,-,+).
 reify_tmo(:Goal, -Result, +Options) is det
  575reify_tmo(Goal, Result, Options) :-
  576    option(timeout(Time), Options),
  577    Time > 0,
  578    !,
  579    reify(call_with_time_limit(Time, Goal), Result0),
  580    (   Result0 = throw(time_limit_exceeded)
  581    ->  Result = throw(time_limit_exceeded(Time))
  582    ;   Result = Result0
  583    ).
  584reify_tmo(Goal, Result, _Options) :-
  585    reify(Goal, Result).
 reify(:Goal, -Result) is det
Call Goal and unify Result with one of true, false or throw(E).
  592reify(Goal, Result) :-
  593    (   catch(Goal, E, true)
  594    ->  (   var(E)
  595	->  Result = true
  596	;   Result = throw(E)
  597	)
  598    ;   Result = false
  599    ).
  600
  601capture_output(Goal, Output) :-
  602    current_test_flag(output, OutputMode),
  603    capture_output(Goal, Output, [output(OutputMode)]).
  604
  605capture_output(Goal, Output, Options) :-
  606    option(output(How), Options, always),
  607    (   How == always
  608    ->  call(Goal)
  609    ;   with_output_to(string(Output), Goal,
  610                       [ capture([user_output, user_error]),
  611                         color(true)
  612                       ])
  613    ).
  614
  615
  616		 /*******************************
  617		 *        RUNNING TOPLEVEL      *
  618		 *******************************/
  619
  620:- dynamic
  621    output_streams/2,               % Output, Error
  622    test_count/1,                   % Count
  623    passed/5,                       % Unit, Test, Line, Det, Time
  624    failed/5,                       % Unit, Test, Line, Reason, Time
  625    timeout/5,                      % Unit, Test, Line, Limit, Time
  626    failed_assertion/7,             % Unit, Test, Line, ALoc, STO, Reason, Goal
  627    blocked/4,                      % Unit, Test, Line, Reason
  628    fixme/5,                        % Unit, Test, Line, Reason, Status
  629    running/5,                      % Unit, Test, Line, STO, Thread
  630    forall_failures/2.              % Nth, Failures
 run_tests is semidet
 run_tests(+TestSet) is semidet
 run_tests(+TestSet, +Options) is semidet
Run tests and report about the results. The predicate run_tests/0 runs all known tests that are not blocked. The predicate run_tests/1 takes a specification of tests to run.

The predicate run_tests/2 is synchronized. Concurrent testing may be achieved using the relevant options. See set_test_options/1. Options are passed to set_test_options/1. In addition the following options are processed:

summary(-Summary)
Unify Summary do a dict holding the keys below. The value of these keys is an integer describing the number of tests. If this option is given, run_tests/2 does not fail if some tests failed.
  • total
  • passed
  • failed
  • timeout
  • blocked
Arguments:
TestSet- is either a single specification or a list of specifications. Each single specification is either the name of a test-unit or a term <test-unit>:<test>, denoting a single test within a unit. If TestSet is all, all known tests are executed.
  662run_tests :-
  663    run_tests(all).
  664
  665run_tests(Set) :-
  666    run_tests(Set, []).
  667
  668run_tests(all, Options) :-
  669    !,
  670    findall(Unit, current_test_unit(Unit,_), Units),
  671    run_tests(Units, Options).
  672run_tests(Set, Options) :-
  673    valid_options(global_test_option, Options, Global, Rest),
  674    current_test_flags(Old),
  675    setup_call_cleanup(
  676	set_test_options(Global),
  677	( flatten([Set], List),
  678	  maplist(runnable_tests, List, Units),
  679	  with_mutex(plunit, run_tests_sync(Units, Rest))
  680	),
  681	set_test_options(Old)).
  682
  683run_tests_sync(Units0, Options) :-
  684    cleanup,
  685    count_tests(Units0, Units, Count),
  686    asserta(test_count(Count)),
  687    save_output_state,
  688    setup_call_cleanup(
  689	setup_jobs(Count),
  690	setup_call_cleanup(
  691	    setup_trap_assertions(Ref),
  692	    ( call_time(run_units(Units, Options), Time),
  693              test_summary(_All, Summary)
  694            ),
  695	    report_and_cleanup(Ref, Time, Options)),
  696	cleanup_jobs),
  697    (   option(summary(Summary), Options)
  698    ->  true
  699    ;   test_summary_passed(Summary) % fail if some test failed
  700    ).
 report_and_cleanup(+Ref, +Time, +Options)
Undo changes to the environment (trapping assertions), report the results and cleanup.
  707report_and_cleanup(Ref, Time, Options) :-
  708    cleanup_trap_assertions(Ref),
  709    report(Time, Options),
  710    cleanup_after_test.
 run_units_and_check_errors(+Units, +Options) is semidet
Run all test units and succeed if all tests passed.
  717run_units(Units, _Options) :-
  718    maplist(schedule_unit, Units),
  719    job_wait(_).
 runnable_tests(+Spec, -Plan) is det
Change a Unit+Test spec into a plain Unit:Tests lists, where blocked tests or tests whose condition fails are already removed. Each test in Tests is a term @(Test,Line), which serves as a unique identifier of the test.
  728:- det(runnable_tests/2).  729runnable_tests(Spec, Unit:RunnableTests) :-
  730    unit_from_spec(Spec, Unit, Tests, Module, UnitOptions),
  731    (   option(blocked(Reason), UnitOptions)
  732    ->  info(plunit(blocked(unit(Unit, Reason)))),
  733        RunnableTests = []
  734    ;   \+ condition(Module, unit(Unit), UnitOptions)
  735    ->  RunnableTests = []
  736    ;   var(Tests)
  737    ->  findall(TestID,
  738                runnable_test(Unit, _Test, Module, TestID),
  739                RunnableTests)
  740    ;   flatten([Tests], TestList),
  741        findall(TestID,
  742                ( member(Test, TestList),
  743                  runnable_test(Unit,Test,Module, TestID)
  744                ),
  745                RunnableTests)
  746    ).
  747
  748runnable_test(Unit, Name, Module, @(Test,Line)) :-
  749    current_test(Unit, Name, Line, _Body, TestOptions),
  750    (   option(blocked(Reason), TestOptions)
  751    ->  Test = blocked(Name, Reason)
  752    ;   condition(Module, test(Unit,Name,Line), TestOptions),
  753        Test = Name
  754    ).
  755
  756unit_from_spec(Unit0:Tests0, Unit, Tests, Module, Options), atom(Unit0) =>
  757    Unit = Unit0,
  758    Tests = Tests0,
  759    (   current_unit(Unit, Module, _Supers, Options)
  760    ->  true
  761    ;   throw_error(existence_error(unit_test, Unit), _)
  762    ).
  763unit_from_spec(Unit0, Unit, _, Module, Options), atom(Unit0) =>
  764    Unit = Unit0,
  765    (   current_unit(Unit, Module, _Supers, Options)
  766    ->  true
  767    ;   throw_error(existence_error(unit_test, Unit), _)
  768    ).
 count_tests(+Units0, -Units, -Count) is det
Count the number of tests to run. A forall(Generator, Test) counts as a single test. During the execution, the concrete tests of the forall are considered "sub tests".
  776count_tests(Units0, Units, Count) :-
  777    count_tests(Units0, Units, 0, Count).
  778
  779count_tests([], T, C0, C) =>
  780    T = [],
  781    C = C0.
  782count_tests([_:[]|T0], T, C0, C) =>
  783    count_tests(T0, T, C0, C).
  784count_tests([Unit:Tests|T0], T, C0, C) =>
  785    partition(is_blocked, Tests, Blocked, Use),
  786    maplist(assert_blocked(Unit), Blocked),
  787    (   Use == []
  788    ->  count_tests(T0, T, C0, C)
  789    ;   length(Use, N),
  790        C1 is C0+N,
  791        T = [Unit:Use|T1],
  792        count_tests(T0, T1, C1, C)
  793    ).
  794
  795is_blocked(@(blocked(_,_),_)) => true.
  796is_blocked(_) => fail.
  797
  798assert_blocked(Unit, @(blocked(Test, Reason), Line)) =>
  799    assert(blocked(Unit, Test, Line, Reason)).
 run_unit(+Unit) is det
Run a single test unit. Unit is a term Unit:Tests, where Tests is a list of tests to run.
  806run_unit(_Unit:[]) =>
  807    true.
  808run_unit(Unit:Tests) =>
  809    unit_module(Unit, Module),
  810    unit_options(Unit, UnitOptions),
  811    (   setup(Module, unit(Unit), UnitOptions)
  812    ->  begin_unit(Unit),
  813        call_time(run_unit_2(Unit, Tests), Time),
  814        test_summary(Unit, Summary),
  815	end_unit(Unit, Summary.put(time, Time)),
  816        cleanup(Module, UnitOptions)
  817    ;   job_info(end(unit(Unit, _{error:setup_failed})))
  818    ).
  819
  820begin_unit(Unit) :-
  821    job_info(begin(unit(Unit))),
  822    job_feedback(informational, begin(Unit)).
  823
  824end_unit(Unit, Summary) :-
  825    job_info(end(unit(Unit, Summary))),
  826    job_feedback(informational, end(Unit, Summary)).
  827
  828run_unit_2(Unit, Tests) :-
  829    forall(member(Test, Tests),
  830	   run_test(Unit, Test)).
  831
  832
  833unit_options(Unit, Options) :-
  834    current_unit(Unit, _Module, _Supers, Options).
  835
  836
  837cleanup :-
  838    set_flag(plunit_test, 1),
  839    retractall(output_streams(_,_)),
  840    retractall(test_count(_)),
  841    retractall(passed(_, _, _, _, _)),
  842    retractall(failed(_, _, _, _, _)),
  843    retractall(timeout(_, _, _, _, _)),
  844    retractall(failed_assertion(_, _, _, _, _, _, _)),
  845    retractall(blocked(_, _, _, _)),
  846    retractall(fixme(_, _, _, _, _)),
  847    retractall(running(_,_,_,_,_)),
  848    retractall(forall_failures(_,_)).
  849
  850cleanup_after_test :-
  851    (   current_test_flag(cleanup, true)
  852    ->  cleanup
  853    ;   true
  854    ).
 run_tests_in_files(+Files:list) is det
Run all test-units that appear in the given Files.
  861run_tests_in_files(Files) :-
  862    findall(Unit, unit_in_files(Files, Unit), Units),
  863    (   Units == []
  864    ->  true
  865    ;   run_tests(Units)
  866    ).
  867
  868unit_in_files(Files, Unit) :-
  869    is_list(Files),
  870    !,
  871    member(F, Files),
  872    absolute_file_name(F, Source,
  873		       [ file_type(prolog),
  874			 access(read),
  875			 file_errors(fail)
  876		       ]),
  877    unit_file(Unit, Source).
  878
  879
  880		 /*******************************
  881		 *         HOOKING MAKE/0       *
  882		 *******************************/
 make_run_tests(+Files)
Called indirectly from make/0 after Files have been reloaded.
  888make_run_tests(Files) :-
  889    current_test_flag(run, When),
  890    (   When == make
  891    ->  run_tests_in_files(Files)
  892    ;   When == make(all)
  893    ->  run_tests
  894    ;   true
  895    ).
  896
  897		 /*******************************
  898		 *      ASSERTION HANDLING      *
  899		 *******************************/
  900
  901:- if(swi).  902
  903:- dynamic prolog:assertion_failed/2.  904
  905setup_trap_assertions(Ref) :-
  906    asserta((prolog:assertion_failed(Reason, Goal) :-
  907		    test_assertion_failed(Reason, Goal)),
  908	    Ref).
  909
  910cleanup_trap_assertions(Ref) :-
  911    erase(Ref).
  912
  913test_assertion_failed(Reason, Goal) :-
  914    thread_self(Me),
  915    running(Unit, Test, Line, Progress, Me),
  916    (   catch(get_prolog_backtrace(10, Stack), _, fail),
  917	assertion_location(Stack, AssertLoc)
  918    ->  true
  919    ;   AssertLoc = unknown
  920    ),
  921    report_failed_assertion(Unit:Test, Line, AssertLoc,
  922			    Progress, Reason, Goal),
  923    assert_cyclic(failed_assertion(Unit, Test, Line, AssertLoc,
  924				   Progress, Reason, Goal)).
  925
  926assertion_location(Stack, File:Line) :-
  927    append(_, [AssertFrame,CallerFrame|_], Stack),
  928    prolog_stack_frame_property(AssertFrame,
  929				predicate(prolog_debug:assertion/1)),
  930    !,
  931    prolog_stack_frame_property(CallerFrame, location(File:Line)).
  932
  933report_failed_assertion(UnitTest, Line, AssertLoc,
  934			Progress, Reason, Goal) :-
  935    print_message(
  936	error,
  937	plunit(failed_assertion(UnitTest, Line, AssertLoc,
  938				Progress, Reason, Goal))).
  939
  940:- else.  941
  942setup_trap_assertions(_).
  943cleanup_trap_assertions(_).
  944
  945:- endif.  946
  947
  948		 /*******************************
  949		 *         RUNNING A TEST       *
  950		 *******************************/
 run_test(+Unit, +Test) is det
Run a single test.
  956run_test(Unit, @(Test,Line)) :-
  957    unit_module(Unit, Module),
  958    Module:'unit test'(Test, Line, TestOptions, Body),
  959    unit_options(Unit, UnitOptions),
  960    run_test(Unit, Test, Line, UnitOptions, TestOptions, Body).
 run_test(+Unit, +Name, +Line, +UnitOptions, +Options, +Body)
Deals with forall(Generator, Test)
  966run_test(Unit, Name, Line, UnitOptions, Options, Body) :-
  967    option(forall(Generator), Options),
  968    !,
  969    unit_module(Unit, Module),
  970    term_variables(Generator, Vars),
  971    start_test(Unit, @(Name,Line), Nth),
  972    State = state(0),
  973    call_time(forall(Module:Generator,            % may become concurrent
  974                     (   incr_forall(State, I),
  975                         run_test_once6(Unit, Name, forall(Vars, Nth-I), Line,
  976                                        UnitOptions, Options, Body)
  977                     )),
  978                     Time),
  979    arg(1, State, Generated),
  980    progress(Unit:Name, Nth, forall(end, Nth, Generated), Time).
  981run_test(Unit, Name, Line, UnitOptions, Options, Body) :-
  982    start_test(Unit, @(Name,Line), Nth),
  983    run_test_once6(Unit, Name, Nth, Line, UnitOptions, Options, Body).
  984
  985start_test(_Unit, _TestID, Nth) :-
  986    flag(plunit_test, Nth, Nth+1).
  987
  988incr_forall(State, I) :-
  989    arg(1, State, I0),
  990    I is I0+1,
  991    nb_setarg(1, State, I).
 run_test_once6(+Unit, +Name, +Progress, +Line, +UnitOptions, +Options, +Body)
Inherit the timeout and occurs_check option (Global -> Unit -> Test).
  998run_test_once6(Unit, Name, Progress, Line, UnitOptions, Options, Body) :-
  999    current_test_flag(timeout, DefTimeOut),
 1000    current_test_flag(occurs_check, DefOccurs),
 1001    inherit_option(timeout,      Options,  [UnitOptions], DefTimeOut, Options1),
 1002    inherit_option(occurs_check, Options1, [UnitOptions], DefOccurs, Options2),
 1003    run_test_once(Unit, Name, Progress, Line, Options2, Body).
 1004
 1005inherit_option(Name, Options0, Chain, Default, Options) :-
 1006    Term =.. [Name,_Value],
 1007    (   option(Term, Options0)
 1008    ->  Options = Options0
 1009    ;   member(Opts, Chain),
 1010        option(Term, Opts)
 1011    ->  Options = [Term|Options0]
 1012    ;   Default == (-)
 1013    ->  Options = Options0
 1014    ;   Opt =.. [Name,Default],
 1015	Options = [Opt|Options0]
 1016    ).
 run_test_once(+Unit, +Name, Progress, +Line, +Options, +Body)
Deal with occurs_check, i.e., running the test multiple times with different unification settings wrt. the occurs check.
 1023run_test_once(Unit, Name, Progress, Line, Options, Body) :-
 1024    option(occurs_check(Occurs), Options),
 1025    !,
 1026    begin_test(Unit, Name, Line, Progress),
 1027    current_prolog_flag(occurs_check, Old),
 1028    setup_call_cleanup(
 1029	set_prolog_flag(occurs_check, Occurs),
 1030	capture_output(run_test_6(Unit, Name, Line, Options, Body, Result),
 1031		       Output),
 1032	set_prolog_flag(occurs_check, Old)),
 1033    end_test(Unit, Name, Line, Progress),
 1034    report_result(Result, Progress, Output, Options).
 1035run_test_once(Unit, Name, Progress, Line, Options, Body) :-
 1036    begin_test(Unit, Name, Line, Progress),
 1037    capture_output(run_test_6(Unit, Name, Line, Options, Body, Result),
 1038		   Output),
 1039    end_test(Unit, Name, Line, Progress),
 1040    report_result(Result, Progress, Output, Options).
 report_result(+Result, +Progress, +Output, +Options) is det
 1044:- det(report_result/4). 1045report_result(failure(Unit, Name, Line, How, Time),
 1046	      Progress, Output, Options) :-
 1047    !,
 1048    failure(Unit, Name, Progress, Line, How, Time, Output, Options).
 1049report_result(success(Unit, Name, Line, Determinism, Time),
 1050	      Progress, Output, Options) :-
 1051    !,
 1052    success(Unit, Name, Progress, Line, Determinism, Time, Output, Options).
 1053report_result(setup_failed(_Unit, _Name, _Line),
 1054	      _Progress, _Output, _Options).
 run_test_6(+Unit, +Name, +Line, +Options, :Body, -Result) is det
6th step of the tests. Deals with tests that must be ignored (blocked, conditions fails), setup and cleanup at the test level. Result is one of:
failure(Unit, Name, Line, How, Time)
How is one of:
  • succeeded
  • Exception
  • time_limit_exceeded(Limit)
  • cmp_error(Cmp, E)
  • wrong_answer(Cmp)
  • failed
  • no_exception
  • wrong_error(Expect, E)
  • wrong_answer(Expected, Bindings)
success(Unit, Name, Line, Determinism, Time)
setup_failed(Unit, Name, Line)
 1076run_test_6(Unit, Name, Line, Options, Body, Result) :-
 1077    option(setup(_Setup), Options),
 1078    !,
 1079    (   unit_module(Unit, Module),
 1080        setup(Module, test(Unit,Name,Line), Options)
 1081    ->  run_test_7(Unit, Name, Line, Options, Body, Result),
 1082        cleanup(Module, Options)
 1083    ;   Result = setup_failed(Unit, Name, Line)
 1084    ).
 1085run_test_6(Unit, Name, Line, Options, Body, Result) :-
 1086    unit_module(Unit, Module),
 1087    run_test_7(Unit, Name, Line, Options, Body, Result),
 1088    cleanup(Module, Options).
 run_test_7(+Unit, +Name, +Line, +Options, :Body, -Result) is det
This step deals with the expected outcome of the test. It runs the actual test and then compares the result to the outcome. There are two main categories: dealing with a single result and all results.
 1097run_test_7(Unit, Name, Line, Options, Body, Result) :-
 1098    option(true(Cmp), Options),			   % expected success
 1099    !,
 1100    unit_module(Unit, Module),
 1101    call_time(reify_tmo(call_det(Module:Body, Det), Result0, Options), Time),
 1102    (   Result0 == true
 1103    ->  cmp_true(Cmp, Module, CmpResult),
 1104	(   CmpResult == []
 1105	->  Result = success(Unit, Name, Line, Det, Time)
 1106	;   Result = failure(Unit, Name, Line, CmpResult, Time)
 1107	)
 1108    ;   Result0 == false
 1109    ->  Result = failure(Unit, Name, Line, failed, Time)
 1110    ;   Result0 = throw(E2)
 1111    ->  Result = failure(Unit, Name, Line, throw(E2), Time)
 1112    ).
 1113run_test_7(Unit, Name, Line, Options, Body, Result) :-
 1114    option(fail, Options),                         % expected failure
 1115    !,
 1116    unit_module(Unit, Module),
 1117    call_time(reify_tmo(Module:Body, Result0, Options), Time),
 1118    (   Result0 == true
 1119    ->  Result = failure(Unit, Name, Line, succeeded, Time)
 1120    ;   Result0 == false
 1121    ->  Result = success(Unit, Name, Line, true, Time)
 1122    ;   Result0 = throw(E)
 1123    ->  Result = failure(Unit, Name, Line, throw(E), Time)
 1124    ).
 1125run_test_7(Unit, Name, Line, Options, Body, Result) :-
 1126    option(throws(Expect), Options),		   % Expected error
 1127    !,
 1128    unit_module(Unit, Module),
 1129    call_time(reify_tmo(Module:Body, Result0, Options), Time),
 1130    (   Result0 == true
 1131    ->  Result = failure(Unit, Name, Line, no_exception, Time)
 1132    ;   Result0 == false
 1133    ->  Result = failure(Unit, Name, Line, failed, Time)
 1134    ;   Result0 = throw(E)
 1135    ->  (   match_error(Expect, E)
 1136        ->  Result = success(Unit, Name, Line, true, Time)
 1137        ;   Result = failure(Unit, Name, Line, wrong_error(Expect, E), Time)
 1138        )
 1139    ).
 1140run_test_7(Unit, Name, Line, Options, Body, Result) :-
 1141    option(all(Answer), Options),                  % all(Bindings)
 1142    !,
 1143    nondet_test(all(Answer), Unit, Name, Line, Options, Body, Result).
 1144run_test_7(Unit, Name, Line, Options, Body, Result) :-
 1145    option(set(Answer), Options),                  % set(Bindings)
 1146    !,
 1147    nondet_test(set(Answer), Unit, Name, Line, Options, Body, Result).
 non_det_test(+Expected, +Unit, +Name, +Line, +Options, +Body, -Result)
Run tests on non-deterministic predicates.
 1153nondet_test(Expected, Unit, Name, Line, Options, Body, Result) :-
 1154    unit_module(Unit, Module),
 1155    result_vars(Expected, Vars),
 1156    (   call_time(reify_tmo(findall(Vars, Module:Body, Bindings),
 1157                            Result0, Options), Time)
 1158    ->  (   Result0 == true
 1159        ->  (   nondet_compare(Expected, Bindings, Unit, Name, Line)
 1160            ->  Result = success(Unit, Name, Line, true, Time)
 1161            ;   Result = failure(Unit, Name, Line,
 1162				 [wrong_answer(Expected, Bindings)], Time)
 1163            )
 1164        ;   Result0 = throw(E)
 1165        ->  Result = failure(Unit, Name, Line, throw(E), Time)
 1166        )
 1167    ).
 1168
 1169cmp_true([], _, L) =>
 1170    L = [].
 1171cmp_true([Cmp|T], Module, L) =>
 1172    E = error(Formal,_),
 1173    cmp_goal(Cmp, Goal),
 1174    (   catch(Module:Goal, E, true)
 1175    ->  (   var(Formal)
 1176	->  cmp_true(T, Module, L)
 1177	;   L = [cmp_error(Cmp,E)|L1],
 1178	    cmp_true(T, Module, L1)
 1179	)
 1180    ;   L = [wrong_answer(Cmp)|L1],
 1181	cmp_true(T, Module, L1)
 1182    ).
 1183
 1184cmp_goal(cmp(_Var, Expr), Goal) => Goal = Expr.
 1185cmp_goal(Expr, Goal) => Goal = Expr.
 result_vars(+Expected, -Vars) is det
Create a term v(V1, ...) containing all variables at the left side of the comparison operator on Expected.
 1193result_vars(Expected, Vars) :-
 1194    arg(1, Expected, CmpOp),
 1195    arg(1, CmpOp, Vars).
 nondet_compare(+Expected, +Bindings, +Unit, +Name, +Line) is semidet
Compare list/set results for non-deterministic predicates.
bug
- Sort should deal with equivalence on the comparison operator.
To be done
- Properly report errors
 1205nondet_compare(all(Cmp), Bindings, _Unit, _Name, _Line) :-
 1206    cmp(Cmp, _Vars, Op, Values),
 1207    cmp_list(Values, Bindings, Op).
 1208nondet_compare(set(Cmp), Bindings0, _Unit, _Name, _Line) :-
 1209    cmp(Cmp, _Vars, Op, Values0),
 1210    sort(Bindings0, Bindings),
 1211    sort(Values0, Values),
 1212    cmp_list(Values, Bindings, Op).
 1213
 1214cmp_list([], [], _Op).
 1215cmp_list([E0|ET], [V0|VT], Op) :-
 1216    call(Op, E0, V0),
 1217    cmp_list(ET, VT, Op).
 cmp(+CmpTerm, -Left, -Op, -Right) is det
 1221cmp(Var  == Value, Var,  ==, Value).
 1222cmp(Var =:= Value, Var, =:=, Value).
 1223cmp(Var  =  Value, Var,  =,  Value).
 1224:- if(swi). 1225cmp(Var =@= Value, Var, =@=, Value).
 1226:- else. 1227:- if(sicstus). 1228cmp(Var =@= Value, Var, variant, Value). % variant/2 is the same =@=
 1229:- endif. 1230:- endif.
 call_det(:Goal, -Det) is nondet
True if Goal succeeded. Det is unified to true if Goal left no choicepoints and false otherwise.
 1238:- if((swi;sicstus)). 1239call_det(Goal, Det) :-
 1240    call_cleanup(Goal,Det0=true),
 1241    ( var(Det0) -> Det = false ; Det = true ).
 1242:- else. 1243call_det(Goal, true) :-
 1244    call(Goal).
 1245:- endif.
 match_error(+Expected, +Received) is semidet
True if the Received errors matches the expected error. Matching is based on subsumes_term/2.
 1252match_error(Expect, Rec) :-
 1253    subsumes_term(Expect, Rec).
 setup(+Module, +Context, +Options) is semidet
Call the setup handler and fail if it cannot run for some reason. The condition handler is similar, but failing is not considered an error. Context is one of
unit(Unit)
If it is the setup handler for a unit
test(Unit, Name, Line)
If it is the setup handler for a test
 1266setup(Module, Context, Options) :-
 1267    option(setup(Setup), Options),
 1268    !,
 1269    capture_output(reify(call_ex(Module, Setup), Result), Output),
 1270    (   Result == true
 1271    ->  true
 1272    ;   print_message(error,
 1273		      plunit(error(setup, Context, Output, Result))),
 1274	fail
 1275    ).
 1276setup(_,_,_).
 condition(+Module, +Context, +Options) is semidet
Evaluate the test or test unit condition.
 1282condition(Module, Context, Options) :-
 1283    option(condition(Cond), Options),
 1284    !,
 1285    capture_output(reify(call_ex(Module, Cond), Result), Output),
 1286    (   Result == true
 1287    ->  true
 1288    ;   Result == false
 1289    ->  fail
 1290    ;   print_message(error,
 1291		      plunit(error(condition, Context, Output, Result))),
 1292	fail
 1293    ).
 1294condition(_, _, _).
 call_ex(+Module, +Goal)
Call Goal in Module after applying goal expansion.
 1301call_ex(Module, Goal) :-
 1302    Module:(expand_goal(Goal, GoalEx),
 1303	    GoalEx).
 cleanup(+Module, +Options) is det
Call the cleanup handler and succeed. Failure or error of the cleanup handler is reported, but tests continue normally.
 1310cleanup(Module, Options) :-
 1311    option(cleanup(Cleanup), Options, true),
 1312    (   catch(call_ex(Module, Cleanup), E, true)
 1313    ->  (   var(E)
 1314	->  true
 1315	;   print_message(warning, E)
 1316	)
 1317    ;   print_message(warning, goal_failed(Cleanup, '(cleanup handler)'))
 1318    ).
 1319
 1320success(Unit, Name, Progress, Line, Det, Time, _Output, Options) :-
 1321    memberchk(fixme(Reason), Options),
 1322    !,
 1323    (   (   Det == true
 1324	;   memberchk(nondet, Options)
 1325	)
 1326    ->  progress(Unit:Name, Progress, fixme(passed), Time),
 1327	Ok = passed
 1328    ;   progress(Unit:Name, Progress, fixme(nondet), Time),
 1329	Ok = nondet
 1330    ),
 1331    flush_output(user_error),
 1332    assert(fixme(Unit, Name, Line, Reason, Ok)).
 1333success(Unit, Name, Progress, Line, _, Time, Output, Options) :-
 1334    failed_assertion(Unit, Name, Line, _,Progress,_,_),
 1335    !,
 1336    failure(Unit, Name, Progress, Line, assertion, Time, Output, Options).
 1337success(Unit, Name, Progress, Line, Det, Time, _Output, Options) :-
 1338    assert(passed(Unit, Name, Line, Det, Time)),
 1339    (   (   Det == true
 1340	;   memberchk(nondet, Options)
 1341	)
 1342    ->  progress(Unit:Name, Progress, passed, Time)
 1343    ;   unit_file(Unit, File),
 1344	print_message(warning, plunit(nondet(File, Line, Name)))
 1345    ).
 failure(+Unit, +Name, +Progress, +Line, +How, +Time, +Output, +Options) is det
Test failed. Report the error.
 1352failure(Unit, Name, Progress, Line, _, Time, _Output, Options),
 1353  memberchk(fixme(Reason), Options) =>
 1354    assert(fixme(Unit, Name, Line, Reason, failed)),
 1355    progress(Unit:Name, Progress, fixme(failed), Time).
 1356failure(Unit, Name, Progress, Line, time_limit_exceeded(Limit), Time,
 1357	Output, Options) =>
 1358    assert_cyclic(timeout(Unit, Name, Line, Limit, Time)),
 1359    progress(Unit:Name, Progress, timeout(Limit), Time),
 1360    report_failure(Unit, Name, Progress, Line, timeout(Limit), Time, Output, Options).
 1361failure(Unit, Name, Progress, Line, E, Time, Output, Options) =>
 1362    assert_cyclic(failed(Unit, Name, Line, E, Time)),
 1363    progress(Unit:Name, Progress, failed, Time),
 1364    report_failure(Unit, Name, Progress, Line, E, Time, Output, Options).
 assert_cyclic(+Term) is det
Assert a possibly cyclic unit clause. Current SWI-Prolog assert/1 does not handle cyclic terms, so we emulate this using the recorded database.
To be done
- Implement cycle-safe assert and remove this.
 1374:- if(swi). 1375assert_cyclic(Term) :-
 1376    acyclic_term(Term),
 1377    !,
 1378    assert(Term).
 1379assert_cyclic(Term) :-
 1380    Term =.. [Functor|Args],
 1381    recorda(cyclic, Args, Id),
 1382    functor(Term, _, Arity),
 1383    length(NewArgs, Arity),
 1384    Head =.. [Functor|NewArgs],
 1385    assert((Head :- recorded(_, Var, Id), Var = NewArgs)).
 1386:- else. 1387:- if(sicstus). 1388:- endif. 1389assert_cyclic(Term) :-
 1390    assert(Term).
 1391:- endif. 1392
 1393
 1394		 /*******************************
 1395		 *             JOBS             *
 1396		 *******************************/
 1397
 1398:- if(current_prolog_flag(threads, true)). 1399
 1400:- dynamic
 1401       job_data/2,		% Queue, Threads
 1402       scheduled_unit/1. 1403
 1404schedule_unit(_:[]) :-
 1405    !.
 1406schedule_unit(UnitAndTests) :-
 1407    UnitAndTests = Unit:_Tests,
 1408    job_data(Queue, _),
 1409    !,
 1410    assertz(scheduled_unit(Unit)),
 1411    thread_send_message(Queue, unit(UnitAndTests)).
 1412schedule_unit(Unit) :-
 1413    run_unit(Unit).
 setup_jobs(+Count) is det
Setup threads for concurrent testing.
 1419setup_jobs(Count) :-
 1420    (   current_test_flag(jobs, Jobs0),
 1421	integer(Jobs0)
 1422    ->  true
 1423    ;   current_prolog_flag(cpu_count, Jobs0)
 1424    ),
 1425    Jobs is min(Count, Jobs0),
 1426    Jobs > 1,
 1427    !,
 1428    message_queue_create(Q, [alias(plunit_jobs)]),
 1429    length(TIDs, Jobs),
 1430    foldl(create_plunit_job(Q), TIDs, 1, _),
 1431    asserta(job_data(Q, TIDs)),
 1432    job_feedback(informational, jobs(Jobs)).
 1433setup_jobs(_) :-
 1434    job_feedback(informational, jobs(1)).
 1435
 1436create_plunit_job(Q, TID, N, N1) :-
 1437    N1 is N + 1,
 1438    atom_concat(plunit_job_, N, Alias),
 1439    thread_create(plunit_job(Q), TID, [alias(Alias)]).
 1440
 1441plunit_job(Queue) :-
 1442    repeat,
 1443    (   catch(thread_get_message(Queue, Job,
 1444				 [ timeout(10)
 1445				 ]),
 1446	      error(_,_), fail)
 1447    ->  job(Job),
 1448	fail
 1449    ;   !
 1450    ).
 1451
 1452job(unit(Unit:Tests)) =>
 1453    run_unit(Unit:Tests).
 1454job(test(Unit, Test)) =>
 1455    run_test(Unit, Test).
 1456
 1457cleanup_jobs :-
 1458    retract(job_data(Queue, TIDSs)),
 1459    !,
 1460    message_queue_destroy(Queue),
 1461    maplist(thread_join, TIDSs).
 1462cleanup_jobs.
 job_wait(?Unit) is det
Wait for all test jobs to finish.
 1468job_wait(Unit) :-
 1469    thread_wait(\+ scheduled_unit(Unit),
 1470		[ wait_preds([scheduled_unit/1]),
 1471		  timeout(1)
 1472		]),
 1473    !.
 1474job_wait(Unit) :-
 1475    job_data(_Queue, TIDs),
 1476    member(TID, TIDs),
 1477    thread_property(TID, status(running)),
 1478    !,
 1479    job_wait(Unit).
 1480job_wait(_).
 1481
 1482
 1483job_info(begin(unit(_Unit))) =>
 1484    true.
 1485job_info(end(unit(Unit, _Summary))) =>
 1486    retractall(scheduled_unit(Unit)).
 1487
 1488:- else.			% No jobs
 1489
 1490schedule_unit(Unit) :-
 1491    run_unit(Unit).
 1492
 1493setup_jobs(_) :-
 1494    print_message(silent, plunit(jobs(1))).
 1495cleanup_jobs.
 1496job_wait(_).
 1497job_info(_).
 1498
 1499:- endif. 1500
 1501
 1502
 1503		 /*******************************
 1504		 *            REPORTING         *
 1505		 *******************************/
 begin_test(+Unit, +Test, +Line, +Progress) is det
 end_test(+Unit, +Test, +Line, +Progress) is det
Maintain running/5 and report a test has started/is ended using a silent message:
See also
- message_hook/3 for intercepting these messages
 1518begin_test(Unit, Test, Line, Progress) :-
 1519    thread_self(Me),
 1520    assert(running(Unit, Test, Line, Progress, Me)),
 1521    unit_file(Unit, File),
 1522    test_count(Total),
 1523    job_feedback(information, begin(Unit:Test, File:Line, Progress/Total)).
 1524
 1525end_test(Unit, Test, Line, Progress) :-
 1526    thread_self(Me),
 1527    retractall(running(_,_,_,_,Me)),
 1528    unit_file(Unit, File),
 1529    test_count(Total),
 1530    job_feedback(information, end(Unit:Test, File:Line, Progress/Total)).
 running_tests is det
Print the currently running test.
 1536running_tests :-
 1537    running_tests(Running),
 1538    print_message(informational, plunit(running(Running))).
 1539
 1540running_tests(Running) :-
 1541    test_count(Total),
 1542    findall(running(Unit:Test, File:Line, Progress/Total, Thread),
 1543	    (   running(Unit, Test, Line, Progress, Thread),
 1544		unit_file(Unit, File)
 1545	    ), Running).
 current_test(?Unit, ?Test, ?Line, ?Body, ?Options) is nondet
True when a test with the specified properties is loaded.
 1552current_test(Unit, Test, Line, Body, Options) :-
 1553    current_unit(Unit, Module, _Supers, _UnitOptions),
 1554    Module:'unit test'(Test, Line, Options, Body).
 current_test_unit(?Unit, ?Options) is nondet
True when a Unit is a current unit test declared with Options.
 1560current_test_unit(Unit, UnitOptions) :-
 1561    current_unit(Unit, _Module, _Supers, UnitOptions).
 1562
 1563
 1564count(Goal, Count) :-
 1565    aggregate_all(count, Goal, Count).
 test_summary(?Unit, -Summary) is det
True when Summary is a dict that reports the main statistics about the executed tests.
 1572test_summary(Unit, Summary) :-
 1573    count(failed(Unit, _0Test, _0Line, _Reason, _0Time), Failed),
 1574    count(timeout(Unit, _0Test, _0Line, _Limit, _0Time), Timeout),
 1575    count(passed(Unit, _0Test, _0Line, _Det, _0Time), Passed),
 1576    count(blocked(Unit, _0Test, _0Line, _0Reason), Blocked),
 1577    count(fixme(Unit, _0Test, _0Line, _0Reason, _0How), Fixme),
 1578    test_count(Total),
 1579    Summary = plunit{total:Total,
 1580		     passed:Passed,
 1581		     failed:Failed,
 1582		     timeout:Timeout,
 1583		     blocked:Blocked,
 1584		     fixme:Fixme}.
 1585
 1586test_summary_passed(Summary) :-
 1587    _{failed: 0} :< Summary.
 report(+Time, +Options) is det
Print a summary of the tests that ran.
 1593report(Time, _Options) :-
 1594    test_summary(_, Summary),
 1595    print_message(silent, plunit(Summary)),
 1596    _{ passed:Passed,
 1597       failed:Failed,
 1598       timeout:Timeout,
 1599       blocked:Blocked,
 1600       fixme:Fixme
 1601     } :< Summary,
 1602    (   Passed+Failed+Timeout+Blocked+Fixme =:= 0
 1603    ->  info(plunit(no_tests))
 1604    ;   Failed+Timeout =:= 0
 1605    ->  report_blocked(Blocked),
 1606	report_fixme,
 1607        test_count(Total),
 1608	info(plunit(all_passed(Total, Passed, Time)))
 1609    ;   report_blocked(Blocked),
 1610	report_fixme,
 1611	report_failed(Failed),
 1612	report_timeout(Timeout),
 1613	info(plunit(passed(Passed))),
 1614        info(plunit(total_time(Time)))
 1615    ).
 1616
 1617report_blocked(0) =>
 1618    true.
 1619report_blocked(Blocked) =>
 1620    findall(blocked(Unit:Name, File:Line, Reason),
 1621	    ( blocked(Unit, Name, Line, Reason),
 1622	      unit_file(Unit, File)
 1623	    ),
 1624	    BlockedTests),
 1625    info(plunit(blocked(Blocked, BlockedTests))).
 1626
 1627report_failed(Failed) :-
 1628    print_message(error, plunit(failed(Failed))).
 1629
 1630report_timeout(Count) :-
 1631    print_message(warning, plunit(timeout(Count))).
 1632
 1633report_fixme :-
 1634    report_fixme(_,_,_).
 1635
 1636report_fixme(TuplesF, TuplesP, TuplesN) :-
 1637    fixme(failed, TuplesF, Failed),
 1638    fixme(passed, TuplesP, Passed),
 1639    fixme(nondet, TuplesN, Nondet),
 1640    print_message(informational, plunit(fixme(Failed, Passed, Nondet))).
 1641
 1642
 1643fixme(How, Tuples, Count) :-
 1644    findall(fixme(Unit, Name, Line, Reason, How),
 1645	    fixme(Unit, Name, Line, Reason, How), Tuples),
 1646    length(Tuples, Count).
 1647
 1648report_failure(Unit, Name, Progress, Line, Error,
 1649	       Time, Output, _Options) =>
 1650    test_count(Total),
 1651    job_feedback(error, failed(Unit:Name, Progress/Total, Line,
 1652			       Error, Time, Output)).
 test_report(+What) is det
Produce reports on test results after the run. Currently only supports fixme for What.
 1660test_report(fixme) :-
 1661    !,
 1662    report_fixme(TuplesF, TuplesP, TuplesN),
 1663    append([TuplesF, TuplesP, TuplesN], Tuples),
 1664    print_message(informational, plunit(fixme(Tuples))).
 1665test_report(What) :-
 1666    throw_error(domain_error(report_class, What), _).
 1667
 1668
 1669		 /*******************************
 1670		 *             INFO             *
 1671		 *******************************/
 unit_file(+Unit, -File) is det
unit_file(?Unit, ?File) is nondet
True when the test unit Unit is defined in File.
 1678unit_file(Unit, File), nonvar(Unit) =>
 1679    unit_file_(Unit, File),
 1680    !.
 1681unit_file(Unit, File) =>
 1682    unit_file_(Unit, File).
 1683
 1684unit_file_(Unit, File) :-
 1685    current_unit(Unit, Module, _Context, _Options),
 1686    module_property(Module, file(File)).
 1687unit_file_(Unit, PlFile) :-
 1688    test_file_for(TestFile, PlFile),
 1689    module_property(Module, file(TestFile)),
 1690    current_unit(Unit, Module, _Context, _Options).
 1691
 1692
 1693		 /*******************************
 1694		 *             FILES            *
 1695		 *******************************/
 load_test_files(+Options) is det
Load .plt test-files related to loaded source-files. Options is currently ignored.
 1702load_test_files(_Options) :-
 1703    State = state(0,0),
 1704    (   source_file(File),
 1705	file_name_extension(Base, Old, File),
 1706	Old \== plt,
 1707	file_name_extension(Base, plt, TestFile),
 1708	exists_file(TestFile),
 1709        inc_arg(1, State),
 1710	(   test_file_for(TestFile, File)
 1711	->  true
 1712	;   load_files(TestFile,
 1713		       [ if(changed),
 1714			 imports([])
 1715		       ]),
 1716            inc_arg(2, State),
 1717	    asserta(test_file_for(TestFile, File))
 1718	),
 1719        fail
 1720    ;   State = state(Total, Loaded),
 1721        print_message(informational, plunit(test_files(Total, Loaded)))
 1722    ).
 1723
 1724inc_arg(Arg, State) :-
 1725    arg(Arg, State, N0),
 1726    N is N0+1,
 1727    nb_setarg(Arg, State, N).
 1728
 1729
 1730		 /*******************************
 1731		 *           MESSAGES           *
 1732		 *******************************/
 info(+Term)
Runs print_message(Level, Term), where Level is one of silent or informational (default).
 1739info(Term) :-
 1740    message_level(Level),
 1741    print_message(Level, Term).
 progress(+UnitTest, +Progress, +Result, +Time) is det
Test Unit:Name completed in Time. Result is the result and is one of
passed
failed
assertion
nondet
fixme(passed)
fixme(nondet)
fixme(failed)
forall(end,Nth,FTotal)
Pseudo result for completion of a forall(Gen,Test) set. Mapped to forall(FTotal, FFailed)
 1758progress(UnitTest, _Progress, forall(end, Nth, FTotal), Time) =>
 1759    (   retract(forall_failures(Nth, FFailed))
 1760    ->  true
 1761    ;   FFailed = 0
 1762    ),
 1763    test_count(Total),
 1764    job_feedback(information, progress(UnitTest, forall(FTotal,FFailed), Nth/Total, Time)).
 1765progress(UnitTest, Progress, Result, Time), Progress = forall(_Vars, Nth-_I) =>
 1766    with_mutex(plunit_forall_counter,
 1767               update_forall_failures(Nth, Result)),
 1768    test_count(Total),
 1769    job_feedback(information, progress(UnitTest, Result, Progress/Total, Time)).
 1770progress(UnitTest, Progress, Result, Time) =>
 1771    test_count(Total),
 1772    job_feedback(information, progress(UnitTest, Result, Progress/Total, Time)).
 1773
 1774update_forall_failures(_Nth, passed) =>
 1775    true.
 1776update_forall_failures(Nth, _) =>
 1777    (   retract(forall_failures(Nth, Failed0))
 1778    ->  true
 1779    ;   Failed0 = 0
 1780    ),
 1781    Failed is Failed0+1,
 1782    asserta(forall_failures(Nth, Failed)).
 1783
 1784message_level(Level) :-
 1785    (   current_test_flag(silent, true)
 1786    ->  Level = silent
 1787    ;   Level = informational
 1788    ).
 1789
 1790locationprefix(File:Line) -->
 1791    !,
 1792    [ url(File:Line), ':'-[], nl, '    ' ].
 1793locationprefix(test(Unit,_Test,Line)) -->
 1794    !,
 1795    { unit_file(Unit, File) },
 1796    locationprefix(File:Line).
 1797locationprefix(unit(Unit)) -->
 1798    !,
 1799    [ 'PL-Unit: unit ~w: '-[Unit] ].
 1800locationprefix(FileLine) -->
 1801    { throw_error(type_error(locationprefix,FileLine), _) }.
 1802
 1803:- discontiguous
 1804    message//1. 1805:- '$hide'(message//1). 1806
 1807message(error(context_error(plunit_close(Name, -)), _)) -->
 1808    [ 'PL-Unit: cannot close unit ~w: no open unit'-[Name] ].
 1809message(error(context_error(plunit_close(Name, Start)), _)) -->
 1810    [ 'PL-Unit: cannot close unit ~w: current unit is ~w'-[Name, Start] ].
 1811message(plunit(nondet(File, Line, Name))) -->
 1812    locationprefix(File:Line),
 1813    [ 'PL-Unit: Test ~w: Test succeeded with choicepoint'- [Name] ].
 1814message(error(plunit(incompatible_options, Tests), _)) -->
 1815    [ 'PL-Unit: incompatible test-options: ~p'-[Tests] ].
 1816message(plunit(sto(true))) -->
 1817    [ 'Option sto(true) is ignored.  See `occurs_check` option.'-[] ].
 1818message(plunit(test_files(Total, Loaded))) -->
 1819    [ 'Found ~D .plt test files, loaded ~D'-[Total, Loaded] ].
 1820
 1821					% Unit start/end
 1822message(plunit(jobs(1))) -->
 1823    !.
 1824message(plunit(jobs(N))) -->
 1825    [ 'Testing with ~D parallel jobs'-[N] ].
 1826message(plunit(begin(_Unit))) -->
 1827    { tty_feedback },
 1828    !.
 1829message(plunit(begin(Unit))) -->
 1830    [ 'Start unit: ~w~n'-[Unit], flush ].
 1831message(plunit(end(_Unit, _Summary))) -->
 1832    { tty_feedback },
 1833    !.
 1834message(plunit(end(Unit, Summary))) -->
 1835    (   {test_summary_passed(Summary)}
 1836    ->  [ 'End unit ~w: passed (~3f sec CPU)'-[Unit, Summary.time.cpu] ]
 1837    ;   [ ansi(error, 'End unit ~w: **FAILED (~3f sec CPU)', [Unit, Summary.time.cpu]) ]
 1838    ).
 1839message(plunit(blocked(unit(Unit, Reason)))) -->
 1840    [ 'PL-Unit: ~w blocked: ~w'-[Unit, Reason] ].
 1841message(plunit(running([]))) -->
 1842    !,
 1843    [ 'PL-Unit: no tests running' ].
 1844message(plunit(running([One]))) -->
 1845    !,
 1846    [ 'PL-Unit: running ' ],
 1847    running(One).
 1848message(plunit(running(More))) -->
 1849    !,
 1850    [ 'PL-Unit: running tests:', nl ],
 1851    running(More).
 1852message(plunit(fixme([]))) --> !.
 1853message(plunit(fixme(Tuples))) -->
 1854    !,
 1855    fixme_message(Tuples).
 1856message(plunit(total_time(Time))) -->
 1857    [ 'Test run completed'-[] ],
 1858    test_time(Time).
 1859
 1860					% Blocked tests
 1861message(plunit(blocked(1, Tests))) -->
 1862    !,
 1863    [ 'one test is blocked'-[] ],
 1864    blocked_tests(Tests).
 1865message(plunit(blocked(N, Tests))) -->
 1866    [ '~D tests are blocked'-[N] ],
 1867    blocked_tests(Tests).
 1868
 1869blocked_tests(Tests) -->
 1870    { current_test_flag(show_blocked, true) },
 1871    !,
 1872    [':'-[]],
 1873    list_blocked(Tests).
 1874blocked_tests(_) -->
 1875    [ ' (use run_tests/2 with ', ansi(code, 'show_blocked(true)', []),
 1876      ' for details)'-[]
 1877    ].
 1878
 1879list_blocked([]) --> !.
 1880list_blocked([blocked(_Unit:Test, Pos, Reason)|T]) -->
 1881    [nl],
 1882    locationprefix(Pos),
 1883    test_name(Test, -),
 1884    [ ': ~w'-[Reason] ],
 1885    list_blocked(T).
 1886
 1887					% fail/success
 1888message(plunit(no_tests)) -->
 1889    !,
 1890    [ 'No tests to run' ].
 1891message(plunit(all_passed(1, 1, Time))) -->
 1892    !,
 1893    [ 'test passed' ],
 1894    test_time(Time).
 1895message(plunit(all_passed(Total, Total, Time))) -->
 1896    !,
 1897    [ 'All ~D tests passed'-[Total] ],
 1898    test_time(Time).
 1899message(plunit(all_passed(Total, Count, Time))) -->
 1900    !,
 1901    { SubTests is Count-Total },
 1902    [ 'All ~D (+~D sub-tests) tests passed'- [Total, SubTests] ],
 1903    test_time(Time).
 1904
 1905test_time(Time) -->
 1906    [ ' in ~3f seconds (~3f cpu)'-[Time.wall, Time.cpu] ].
 1907
 1908message(plunit(passed(Count))) -->
 1909    !,
 1910    [ '~D tests passed'-[Count] ].
 1911message(plunit(failed(0))) -->
 1912    !,
 1913    [].
 1914message(plunit(failed(1))) -->
 1915    !,
 1916    [ '1 test failed'-[] ].
 1917message(plunit(failed(N))) -->
 1918    [ '~D tests failed'-[N] ].
 1919message(plunit(timeout(0))) -->
 1920    !,
 1921    [].
 1922message(plunit(timeout(N))) -->
 1923    [ '~D tests timed out'-[N] ].
 1924message(plunit(fixme(0,0,0))) -->
 1925    [].
 1926message(plunit(fixme(Failed,0,0))) -->
 1927    !,
 1928    [ 'all ~D tests flagged FIXME failed'-[Failed] ].
 1929message(plunit(fixme(Failed,Passed,0))) -->
 1930    [ 'FIXME: ~D failed; ~D passed'-[Failed, Passed] ].
 1931message(plunit(fixme(Failed,Passed,Nondet))) -->
 1932    { TotalPassed is Passed+Nondet },
 1933    [ 'FIXME: ~D failed; ~D passed; (~D nondet)'-
 1934      [Failed, TotalPassed, Nondet] ].
 1935
 1936message(plunit(begin(Unit:Test, _Location, Progress))) -->
 1937    { tty_columns(SummaryWidth, _Margin),
 1938      test_name_summary(Unit:Test, SummaryWidth, NameS),
 1939      progress_string(Progress, ProgressS)
 1940    },
 1941    (   { tty_feedback,
 1942	  tty_clear_to_eol(CE)
 1943	}
 1944    ->  [ at_same_line, '\r[~w] ~w ..~w'-[ProgressS, NameS,
 1945					     CE], flush ]
 1946    ;   { jobs(_) }
 1947    ->  [ '[~w] ~w ..'-[ProgressS, NameS] ]
 1948    ;   [ '[~w] ~w ..'-[ProgressS, NameS], flush ]
 1949    ).
 1950message(plunit(end(_UnitTest, _Location, _Progress))) -->
 1951    [].
 1952message(plunit(progress(_UnitTest, Status, _Progress, _Time))) -->
 1953    { Status = forall(_,_)
 1954    ; Status == assertion
 1955    },
 1956    !.
 1957message(plunit(progress(Unit:Test, Status, Progress, Time))) -->
 1958    { jobs(_),
 1959      !,
 1960      tty_columns(SummaryWidth, Margin),
 1961      test_name_summary(Unit:Test, SummaryWidth, NameS),
 1962      progress_string(Progress, ProgressS),
 1963      progress_tag(Status, Tag, _Keep, Style)
 1964    },
 1965    [ ansi(Style, '[~w] ~w ~`.t ~w (~3f sec)~*|',
 1966	   [ProgressS, NameS, Tag, Time.wall, Margin]) ].
 1967message(plunit(progress(_UnitTest, Status, _Progress, Time))) -->
 1968    { tty_columns(_SummaryWidth, Margin),
 1969      progress_tag(Status, Tag, _Keep, Style)
 1970    },
 1971    [ at_same_line, ansi(Style, '~`.t ~w (~3f sec)~*|',
 1972			 [Tag, Time.wall, Margin]) ],
 1973    (   { tty_feedback }
 1974    ->  [flush]
 1975    ;   []
 1976    ).
 1977message(plunit(failed(Unit:Test, Progress, Line, Failure, _Time, Output))) -->
 1978    { unit_file(Unit, File) },
 1979    locationprefix(File:Line),
 1980    test_name(Test, Progress),
 1981    [': '-[] ],
 1982    failure(Failure),
 1983    test_output(Output).
 1984message(plunit(timeout(Unit:Test, Progress, Line, Limit, Output))) -->
 1985    { unit_file(Unit, File) },
 1986    locationprefix(File:Line),
 1987    test_name(Test, Progress),
 1988    [': '-[] ],
 1989    timeout(Limit),
 1990    test_output(Output).
 1991:- if(swi). 1992message(plunit(failed_assertion(Unit:Test, Line, AssertLoc,
 1993				Progress, Reason, Goal))) -->
 1994    { unit_file(Unit, File) },
 1995    locationprefix(File:Line),
 1996    test_name(Test, Progress),
 1997    [ ': assertion'-[] ],
 1998    assertion_location(AssertLoc, File),
 1999    assertion_reason(Reason), ['\n\t'],
 2000    assertion_goal(Unit, Goal).
 2001
 2002assertion_location(File:Line, File) -->
 2003    [ ' at line ~w'-[Line] ].
 2004assertion_location(File:Line, _) -->
 2005    [ ' at ', url(File:Line) ].
 2006assertion_location(unknown, _) -->
 2007    [].
 2008
 2009assertion_reason(fail) -->
 2010    !,
 2011    [ ' failed'-[] ].
 2012assertion_reason(Error) -->
 2013    { message_to_string(Error, String) },
 2014    [ ' raised "~w"'-[String] ].
 2015
 2016assertion_goal(Unit, Goal) -->
 2017    { unit_module(Unit, Module),
 2018      unqualify(Goal, Module, Plain)
 2019    },
 2020    [ 'Assertion: ~p'-[Plain] ].
 2021
 2022unqualify(Var, _, Var) :-
 2023    var(Var),
 2024    !.
 2025unqualify(M:Goal, Unit, Goal) :-
 2026    nonvar(M),
 2027    unit_module(Unit, M),
 2028    !.
 2029unqualify(M:Goal, _, Goal) :-
 2030    callable(Goal),
 2031    predicate_property(M:Goal, imported_from(system)),
 2032    !.
 2033unqualify(Goal, _, Goal).
 2034
 2035test_output("") --> [].
 2036test_output(Output) -->
 2037    [ ansi(code, '~s', [Output]) ].
 2038
 2039:- endif. 2040					% Setup/condition errors
 2041message(plunit(error(Where, Context, _Output, throw(Exception)))) -->
 2042    locationprefix(Context),
 2043    { message_to_string(Exception, String) },
 2044    [ 'error in ~w: ~w'-[Where, String] ].
 2045message(plunit(error(Where, Context, _Output, false))) -->
 2046    locationprefix(Context),
 2047    [ 'setup failed in ~w'-[Where] ].
 2048
 2049                                        % delayed output
 2050message(plunit(test_output(_, Output))) -->
 2051    [ '~s'-[Output] ].
 2052					% Interrupts (SWI)
 2053:- if(swi). 2054message(interrupt(begin)) -->
 2055    { thread_self(Me),
 2056      running(Unit, Test, Line, Progress, Me),
 2057      !,
 2058      unit_file(Unit, File),
 2059      restore_output_state
 2060    },
 2061    [ 'Interrupted test '-[] ],
 2062    running(running(Unit:Test, File:Line, Progress, Me)),
 2063    [nl],
 2064    '$messages':prolog_message(interrupt(begin)).
 2065message(interrupt(begin)) -->
 2066    '$messages':prolog_message(interrupt(begin)).
 2067:- endif. 2068
 2069message(concurrent) -->
 2070    [ 'concurrent(true) at the level of units is currently ignored.', nl,
 2071      'See set_test_options/1 with jobs(Count) for concurrent testing.'
 2072    ].
 2073
 2074test_name(Name, forall(Bindings, _Nth-I)) -->
 2075    !,
 2076    test_name(Name, -),
 2077    [ ' (~d-th forall bindings = '-[I],
 2078      ansi(code, '~p', [Bindings]), ')'-[]
 2079    ].
 2080test_name(Name, _) -->
 2081    !,
 2082    [ 'test ', ansi(code, '~w', [Name]) ].
 2083
 2084running(running(Unit:Test, File:Line, _Progress, Thread)) -->
 2085    thread(Thread),
 2086    [ '~q:~q at '-[Unit, Test], url(File:Line) ].
 2087running([H|T]) -->
 2088    ['\t'], running(H),
 2089    (   {T == []}
 2090    ->  []
 2091    ;   [nl], running(T)
 2092    ).
 2093
 2094thread(main) --> !.
 2095thread(Other) -->
 2096    [' [~w] '-[Other] ].
 2097
 2098:- if(swi). 2099write_term(T, OPS) -->
 2100    ['~W'-[T,OPS] ].
 2101:- else. 2102write_term(T, _OPS) -->
 2103    ['~q'-[T]].
 2104:- endif. 2105
 2106expected_got_ops_(Ex, E, OPS, Goals) -->
 2107    ['    Expected: '-[]], write_term(Ex, OPS), [nl],
 2108    ['    Got:      '-[]], write_term(E,  OPS), [],
 2109    ( { Goals = [] } -> []
 2110    ; [nl, '       with: '-[]], write_term(Goals, OPS), []
 2111    ).
 2112
 2113
 2114failure(List) -->
 2115    { is_list(List) },
 2116    !,
 2117    [ nl ],
 2118    failures(List).
 2119failure(Var) -->
 2120    { var(Var) },
 2121    !,
 2122    [ 'Unknown failure?' ].
 2123failure(succeeded(Time)) -->
 2124    !,
 2125    [ 'must fail but succeeded in ~2f seconds~n'-[Time] ].
 2126failure(wrong_error(Expected, Error)) -->
 2127    !,
 2128    { copy_term(Expected-Error, Ex-E, Goals),
 2129      numbervars(Ex-E-Goals, 0, _),
 2130      write_options(OPS)
 2131    },
 2132    [ 'wrong error'-[], nl ],
 2133    expected_got_ops_(Ex, E, OPS, Goals).
 2134failure(wrong_answer(cmp(Var, Cmp))) -->
 2135    { Cmp =.. [Op,Answer,Expected],
 2136      !,
 2137      copy_term(Expected-Answer, Ex-A, Goals),
 2138      numbervars(Ex-A-Goals, 0, _),
 2139      write_options(OPS)
 2140    },
 2141    [ 'wrong answer for ', ansi(code, '~w', [Var]),
 2142      ' (compared using ~w)'-[Op], nl ],
 2143    expected_got_ops_(Ex, A, OPS, Goals).
 2144failure(wrong_answer(Cmp)) -->
 2145    { Cmp =.. [Op,Answer,Expected],
 2146      !,
 2147      copy_term(Expected-Answer, Ex-A, Goals),
 2148      numbervars(Ex-A-Goals, 0, _),
 2149      write_options(OPS)
 2150    },
 2151    [ 'wrong answer (compared using ~w)'-[Op], nl ],
 2152    expected_got_ops_(Ex, A, OPS, Goals).
 2153failure(wrong_answer(CmpExpected, Bindings)) -->
 2154    { (   CmpExpected = all(Cmp)
 2155      ->  Cmp =.. [_Op1,_,Expected],
 2156	  Got = Bindings,
 2157	  Type = all
 2158      ;   CmpExpected = set(Cmp),
 2159	  Cmp =.. [_Op2,_,Expected0],
 2160	  sort(Expected0, Expected),
 2161	  sort(Bindings, Got),
 2162	  Type = set
 2163      )
 2164    },
 2165    [ 'wrong "~w" answer:'-[Type] ],
 2166    [ nl, '    Expected: ~q'-[Expected] ],
 2167    [ nl, '       Found: ~q'-[Got] ].
 2168:- if(swi). 2169failure(cmp_error(_Cmp, Error)) -->
 2170    { message_to_string(Error, Message) },
 2171    [ 'Comparison error: ~w'-[Message] ].
 2172failure(throw(Error)) -->
 2173    { Error = error(_,_),
 2174      !,
 2175      message_to_string(Error, Message)
 2176    },
 2177    [ 'received error: ~w'-[Message] ].
 2178:- endif. 2179failure(Why) -->
 2180    [ '~p'-[Why] ].
 2181
 2182failures([]) -->
 2183    !.
 2184failures([H|T]) -->
 2185    !,
 2186    failure(H), [nl],
 2187    failures(T).
 2188
 2189timeout(Limit) -->
 2190    [ 'Timeout exceeeded (~2f sec)'-[Limit] ].
 2191
 2192fixme_message([]) --> [].
 2193fixme_message([fixme(Unit, _Name, Line, Reason, How)|T]) -->
 2194    { unit_file(Unit, File) },
 2195    fixme_message(File:Line, Reason, How),
 2196    (   {T == []}
 2197    ->  []
 2198    ;   [nl],
 2199	fixme_message(T)
 2200    ).
 2201
 2202fixme_message(Location, Reason, failed) -->
 2203    [ 'FIXME: ~w: ~w'-[Location, Reason] ].
 2204fixme_message(Location, Reason, passed) -->
 2205    [ 'FIXME: ~w: passed ~w'-[Location, Reason] ].
 2206fixme_message(Location, Reason, nondet) -->
 2207    [ 'FIXME: ~w: passed (nondet) ~w'-[Location, Reason] ].
 2208
 2209
 2210write_options([ numbervars(true),
 2211		quoted(true),
 2212		portray(true),
 2213		max_depth(100),
 2214		attributes(portray)
 2215	      ]).
 test_name_summary(+Term, +MaxLen, -Summary) is det
Given the test id, generate string that summarizes this in MaxLen characters.
 2222test_name_summary(Term, MaxLen, Summary) :-
 2223    summary_string(Term, Text),
 2224    atom_length(Text, Len),
 2225    (   Len =< MaxLen
 2226    ->  Summary = Text
 2227    ;   End is MaxLen//2,
 2228        Pre is MaxLen - End - 2,
 2229        sub_string(Text, 0, Pre, _, PreText),
 2230        sub_string(Text, _, End, 0, PostText),
 2231        format(string(Summary), '~w..~w', [PreText,PostText])
 2232    ).
 2233
 2234summary_string(Unit:Test, String) =>
 2235    summary_string(Test, String1),
 2236    atomics_to_string([Unit, String1], :, String).
 2237summary_string(@(Name,Vars), String) =>
 2238    format(string(String), '~W (using ~W)',
 2239           [ Name, [numbervars(true), quoted(false)],
 2240             Vars, [numbervars(true), portray(true), quoted(true)]
 2241           ]).
 2242summary_string(Name, String) =>
 2243    term_string(Name, String, [numbervars(true), quoted(false)]).
 progress_string(+Progress, -S) is det
True when S is a string representation for the test progress.
 2249progress_string(forall(_Vars, N-I)/Total, S) =>
 2250    format(string(S), '~w-~w/~w', [N,I,Total]).
 2251progress_string(Progress, S) =>
 2252    term_string(Progress, S).
 progress_tag(+Status, -Tag, -Keep, -Style) is det
Given a progress status, determine the status tag, whether we must preserve the line and the Style we must use to print the status line.
 2260progress_tag(passed,        Tag, Keep, Style) =>
 2261    Tag = passed, Keep = false, Style = comment.
 2262progress_tag(fixme(passed), Tag, Keep, Style) =>
 2263    Tag = passed, Keep = false, Style = comment.
 2264progress_tag(fixme(_),      Tag, Keep, Style) =>
 2265    Tag = fixme, Keep = true, Style = warning.
 2266progress_tag(nondet,        Tag, Keep, Style) =>
 2267    Tag = '**NONDET', Keep = true, Style = warning.
 2268progress_tag(timeout(_Limit), Tag, Keep, Style) =>
 2269    Tag = '**TIMEOUT', Keep = true, Style = warning.
 2270progress_tag(assertion,     Tag, Keep, Style) =>
 2271    Tag = '**FAILED', Keep = true, Style = error.
 2272progress_tag(failed,        Tag, Keep, Style) =>
 2273    Tag = '**FAILED', Keep = true, Style = error.
 2274progress_tag(forall(_,0),   Tag, Keep, Style) =>
 2275    Tag = passed, Keep = false, Style = comment.
 2276progress_tag(forall(_,_),   Tag, Keep, Style) =>
 2277    Tag = '**FAILED', Keep = true, Style = error.
 2278
 2279
 2280		 /*******************************
 2281		 *           OUTPUT		*
 2282		 *******************************/
 2283
 2284save_output_state :-
 2285    stream_property(Output, alias(user_output)),
 2286    stream_property(Error, alias(user_error)),
 2287    asserta(output_streams(Output, Error)).
 2288
 2289restore_output_state :-
 2290    output_streams(Output, Error),
 2291    !,
 2292    set_stream(Output, alias(user_output)),
 2293    set_stream(Error, alias(user_error)).
 2294restore_output_state.
 2295
 2296
 2297
 2298		 /*******************************
 2299		 *      CONCURRENT STATUS       *
 2300		 *******************************/
 2301
 2302/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 2303This part deals with interactive feedback   when we are running multiple
 2304threads. The terminal window cannot work on   top  of the Prolog message
 2305infrastructure and (thus) we have to use more low-level means.
 2306- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 2307
 2308:- dynamic
 2309       jobs/1,			% Count
 2310       job_window/1,		% Count
 2311       job_status_line/3.	% Job, Format, Args
 2312
 2313job_feedback(_, jobs(Jobs)) :-
 2314    retractall(jobs(_)),
 2315    Jobs > 1,
 2316    asserta(jobs(Jobs)),
 2317    tty_feedback,
 2318    !,
 2319    retractall(job_window(_)),
 2320    asserta(job_window(Jobs)),
 2321    retractall(job_status_line(_,_,_)),
 2322    jobs_redraw.
 2323job_feedback(_, jobs(Jobs)) :-
 2324    !,
 2325    retractall(job_window(_)),
 2326    info(plunit(jobs(Jobs))).
 2327job_feedback(_, Msg) :-
 2328    job_window(_),
 2329    !,
 2330    with_mutex(plunit_feedback, job_feedback(Msg)).
 2331job_feedback(Level, Msg) :-
 2332    print_message(Level, plunit(Msg)).
 2333
 2334job_feedback(begin(Unit:Test, _Location, Progress)) =>
 2335    tty_columns(SummaryWidth, _Margin),
 2336    test_name_summary(Unit:Test, SummaryWidth, NameS),
 2337    progress_string(Progress, ProgressS),
 2338    tty_clear_to_eol(CE),
 2339    job_format(comment, '\r[~w] ~w ..~w',
 2340	       [ProgressS, NameS, CE]),
 2341    flush_output.
 2342job_feedback(end(_UnitTest, _Location, _Progress)) =>
 2343    true.
 2344job_feedback(progress(_UnitTest, Status, _Progress, Time)) =>
 2345    (   hide_progress(Status)
 2346    ->  true
 2347    ;   tty_columns(_SummaryWidth, Margin),
 2348	progress_tag(Status, Tag, _Keep, Style),
 2349	job_finish(Style, '~`.t ~w (~3f sec)~*|',
 2350		   [Tag, Time.wall, Margin])
 2351    ).
 2352job_feedback(failed(UnitTest, Progress, Line, Error, Time, Output)) =>
 2353    tty_columns(_SummaryWidth, Margin),
 2354    progress_tag(failed, Tag, _Keep, Style),
 2355    job_finish(Style, '~`.t ~w (~3f sec)~*|',
 2356	       [Tag, Time.wall, Margin]),
 2357    print_test_output(Error, Output),
 2358    (   (   Error = timeout(_)	% Status line suffices
 2359	;   Error == assertion	% We will get an failed test later
 2360	)
 2361    ->  true
 2362    ;   print_message(Style, plunit(failed(UnitTest, Progress, Line,
 2363					   Error, Time, "")))
 2364    ),
 2365    jobs_redraw.
 2366job_feedback(begin(_Unit)) => true.
 2367job_feedback(end(_Unit, _Summary)) => true.
 2368
 2369hide_progress(assertion).
 2370hide_progress(forall(_,_)).
 2371hide_progress(failed).
 2372hide_progress(timeout(_)).
 2373
 2374print_test_output(_, "") => true.
 2375print_test_output(assertion, Output) =>
 2376    print_message(debug, plunit(test_output(error, Output))).
 2377print_test_output(_, Output) =>
 2378    print_message(debug, plunit(test_output(informational, Output))).
 jobs_redraw is det
Redraw the job window.
 2384jobs_redraw :-
 2385    job_window(N),
 2386    !,
 2387    tty_columns(_, Width),
 2388    tty_header_line(Width),
 2389    forall(between(1,N,Line), job_redraw_worker(Line)),
 2390    tty_header_line(Width).
 2391jobs_redraw.
 2392
 2393job_redraw_worker(Line) :-
 2394    (   job_status_line(Line, Fmt, Args)
 2395    ->  ansi_format(comment, Fmt, Args)
 2396    ;   true
 2397    ),
 2398    nl.
 job_format(+Style, +Fmt, +Args) is det
 job_format(+Job, +Style, +Fmt, +Args, +Save) is det
Point should be below the status window. Format Fmt+Args in the line Job using Style and return to the position below the window.
 2406job_format(Style, Fmt, Args) :-
 2407    job_self(Job),
 2408    job_format(Job, Style, Fmt, Args, true).
 job_finish(+Style, +Fmt, +Args) is det
 job_finish(+Job, +Style, +Fmt, +Args) is det
Complete the status line for Job. This redraws the original status line when we are using a job window.
 2416job_finish(Style, Fmt, Args) :-
 2417    job_self(Job),
 2418    job_finish(Job, Style, Fmt, Args).
 2419
 2420:- det(job_finish/4). 2421job_finish(Job, Style, Fmt, Args) :-
 2422    retract(job_status_line(Job, Fmt0, Args0)),
 2423    !,
 2424    string_concat(Fmt0, Fmt, Fmt1),
 2425    append(Args0, Args, Args1),
 2426    job_format(Job, Style, Fmt1, Args1, false).
 2427
 2428:- det(job_format/5). 2429job_format(Job, Style, Fmt, Args, Save) :-
 2430    job_window(Jobs),
 2431    Up is Jobs+2-Job,
 2432    flush_output(user_output),
 2433    tty_up_and_clear(Up),
 2434    ansi_format(Style, Fmt, Args),
 2435    (   Save == true
 2436    ->  retractall(job_status_line(Job, _, _)),
 2437	asserta(job_status_line(Job, Fmt, Args))
 2438    ;   true
 2439    ),
 2440    tty_down_and_home(Up),
 2441    flush_output(user_output).
 2442
 2443:- det(job_self/1). 2444job_self(Job) :-
 2445    job_window(N),
 2446    N > 1,
 2447    thread_self(Me),
 2448    split_string(Me, '_', '', [_,_,S]),
 2449    number_string(Job, S).
 feedback is semidet
provide feedback using the tty format, which reuses the current output line if the test is successful.
 2456tty_feedback :-
 2457    has_tty,
 2458    current_test_flag(format, tty).
 2459
 2460has_tty :-
 2461    stream_property(user_output, tty(true)).
 2462
 2463tty_columns(SummaryWidth, Margin) :-
 2464    tty_width(W),
 2465    Margin is W-8,
 2466    SummaryWidth is max(20,Margin-34).
 2467
 2468tty_width(W) :-
 2469    current_predicate(tty_size/2),
 2470    catch(tty_size(_Rows, Cols), error(_,_), fail),
 2471    Cols > 25,
 2472    !,
 2473    W = Cols.
 2474tty_width(80).
 2475
 2476tty_header_line(Width) :-
 2477    ansi_format(comment, '~N~`\u2015t~*|~n', [Width]).
 2478
 2479:- if(current_predicate(tty_get_capability/3)). 2480tty_clear_to_eol(S) :-
 2481    tty_get_capability(ce, string, S),
 2482    !.
 2483:- endif. 2484tty_clear_to_eol('\e[K').
 2485
 2486tty_up_and_clear(Lines) :-
 2487    format(user_output, '\e[~dA\r\e[K', [Lines]).
 2488
 2489tty_down_and_home(Lines) :-
 2490    format(user_output, '\e[~dB\r', [Lines]).
 2491
 2492:- if(swi). 2493
 2494:- multifile
 2495    prolog:message/3,
 2496    user:message_hook/3. 2497
 2498prolog:message(Term) -->
 2499    message(Term).
 2500
 2501%       user:message_hook(+Term, +Kind, +Lines)
 2502
 2503user:message_hook(make(done(Files)), _, _) :-
 2504    make_run_tests(Files),
 2505    fail.                           % give other hooks a chance
 2506
 2507:- endif. 2508
 2509:- if(sicstus). 2510
 2511user:generate_message_hook(Message) -->
 2512    message(Message),
 2513    [nl].                           % SICStus requires nl at the end
 user:message_hook(+Severity, +Message, +Lines) is semidet
Redefine printing some messages. It appears SICStus has no way to get multiple messages at the same line, so we roll our own. As there is a lot pre-wired and checked in the SICStus message handling we cannot reuse the lines. Unless I miss something ...
 2522user:message_hook(informational, plunit(begin(Unit)), _Lines) :-
 2523    format(user_error, '% PL-Unit: ~w ', [Unit]),
 2524    flush_output(user_error).
 2525user:message_hook(informational, plunit(end(_Unit)), _Lines) :-
 2526    format(user, ' done~n', []).
 2527
 2528:- endif.