This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
yap-6.3/swi/library/plunit.pl

1731 lines
46 KiB
Perl
Raw Normal View History

2016-07-31 17:35:10 +01:00
/* Part of SWI-Prolog
Author: Jan Wielemaker
E-mail: J.Wielemaker@cs.vu.nl
WWW: http://www.swi-prolog.org
Copyright (C): 2006-2013, University of Amsterdam
VU University Amsterdam
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
As a special exception, if you link this library with other files,
compiled with a Free Software compiler, to produce an executable, this
library does not by itself cause the resulting executable to be covered
by the GNU General Public License. This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
Alternatively, this program may be distributed under the Perl
Artistic License, version 2.0.
*/
:- module(plunit,
[ set_test_options/1, % +Options
begin_tests/1, % +Name
begin_tests/2, % +Name, +Options
end_tests/1, % +Name
run_tests/0, % Run all tests
run_tests/1, % Run named test-set
load_test_files/1, % +Options
running_tests/0, % Prints currently running test
test_report/1 % +What
]).
/** <module> Unit Testing
Unit testing environment for SWI-Prolog and SICStus Prolog. For usage,
please visit http://www.swi-prolog.org/pldoc/package/plunit.html.
@author Jan Wielemaker
@license GPL+SWI-exception or Artistic 2.0
*/
:- use_module(library(apply)).
:- use_module(library(ordsets), [ord_intersection/3]).
:- meta_predicate valid_options(+, 1).
/*******************************
* CONDITIONAL COMPILATION *
*******************************/
:- discontiguous
user:term_expansion/2.
:- dynamic
include_code/1.
including :-
include_code(X), !,
X == true.
including.
if_expansion((:- if(G)), []) :-
( including
-> ( catch(G, E, (print_message(error, E), fail))
-> asserta(include_code(true))
; asserta(include_code(false))
)
; asserta(include_code(else_false))
).
if_expansion((:- else), []) :-
( retract(include_code(X))
-> ( X == true
-> X2 = false
; X == false
-> X2 = true
; X2 = X
),
asserta(include_code(X2))
; throw_error(context_error(no_if),_)
).
if_expansion((:- endif), []) :-
retract(include_code(_)), !.
if_expansion(_, []) :-
\+ including.
user:term_expansion(In, Out) :-
prolog_load_context(module, plunit),
if_expansion(In, Out).
swi :- catch(current_prolog_flag(dialect, swi), _, fail), !.
swi :- catch(current_prolog_flag(dialect, yap), _, fail).
sicstus :- catch(current_prolog_flag(system_type, _), _, fail).
:- if(swi).
throw_error(Error_term,Impldef) :-
throw(error(Error_term,context(Impldef,_))).
:- set_prolog_flag(generate_debug_info, false).
:- use_module(library(option)).
:- use_module(library(pairs)).
current_test_flag(Name, Value) :-
current_prolog_flag(Name, Value).
set_test_flag(Name, Value) :-
create_prolog_flag(Name, Value, []).
:- endif.
:- if(sicstus).
throw_error(Error_term,Impldef) :-
throw(error(Error_term,i(Impldef))). % SICStus 3 work around
:- use_module(swi). % SWI-Compatibility
:- use_module(library(terms)).
:- op(700, xfx, =@=).
'$set_source_module'( In, Out) :-
current_source_module(In, Out).
%% current_test_flag(?Name, ?Value) is nondet.
%
% Query flags that control the testing process. Emulates
% SWI-Prologs flags.
:- dynamic test_flag/2. % Name, Val
current_test_flag(optimise, Val) :-
current_prolog_flag(compiling, Compiling),
( Compiling == debugcode ; true % TBD: Proper test
-> Val = false
; Val = true
).
current_test_flag(Name, Val) :-
test_flag(Name, Val).
%% set_test_flag(+Name, +Value) is det.
set_test_flag(Name, Val) :-
var(Name), !,
throw_error(instantiation_error, set_test_flag(Name,Val)).
set_test_flag( Name, Val ) :-
retractall(test_flag(Name,_)),
asserta(test_flag(Name, Val)).
:- op(1150, fx, thread_local).
user:term_expansion((:- thread_local(PI)), (:- dynamic(PI))) :-
prolog_load_context(module, plunit).
:- endif.
/*******************************
* IMPORTS *
*******************************/
:- use_module(library(lists)).
:- initialization
( current_test_flag(test_options, _)
-> true
; set_test_flag(test_options,
[ run(make), % run tests on make/0
sto(false)
])
).
%% 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).
%
% * silent(+Bool)
% If =true= (default =false=), report successful tests
% using message level =silent=, only printing errors and
% warnings.
%
% * sto(+Bool)
% How to test whether code is subject to occurs check
% (STO). If =false= (default), STO is not considered.
% If =true= and supported by the hosting Prolog, code
% is run in all supported unification mode and reported
% if the results are inconsistent.
%
% * cleanup(+Bool)
% If =true= (default =false), cleanup report at the end
% of run_tests/1. Used to improve cooperation with
% memory debuggers such as dmwfc.
set_test_options(Options) :-
valid_options(Options, global_test_option),
set_test_flag(test_options, Options).
global_test_option(load(Load)) :-
must_be(oneof([never,always,normal]), Load).
global_test_option(run(When)) :-
must_be(oneof([manual,make,make(all)]), When).
global_test_option(silent(Bool)) :-
must_be(boolean, Bool).
global_test_option(sto(Bool)) :-
must_be(boolean, Bool).
global_test_option(cleanup(Bool)) :-
must_be(boolean, Bool).
%% loading_tests
%
% True if tests must be loaded.
loading_tests :-
current_test_flag(test_options, Options),
option(load(Load), Options, normal),
( Load == always
-> true
; Load == normal,
\+ current_test_flag(optimise, true)
).
/*******************************
* MODULE *
*******************************/
:- dynamic
loading_unit/4, % Unit, Module, File, OldSource
current_unit/4, % Unit, Module, Context, Options
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).
begin_tests(Unit) :-
trace,
begin_tests(Unit, []).
begin_tests(Unit, Options) :-
% writeln('*************'+ Unit), trace,
valid_options(Options, test_set_option),
make_unit_module(Unit, Name),
source_location(File, Line),
begin_tests(Unit, Name, File:Line, Options).
:- if(swi).
begin_tests(Unit, Name, File:Line, Options) :-
loading_tests, !,
'$set_source_module'(Context, Context),
( current_unit(Unit, Name, Context, Options)
-> true
; retractall(current_unit(Unit, Name, _, _)),
assert(current_unit(Unit, Name, Context, Options))
),
'$set_source_module'(Old, Name),
'$declare_module'(Name, test, Context, File, Line, false),
discontiguous(Name:'unit test'/4),
% '$set_predicate_attribute'(Name:'unit test'/4, trace, 0),
discontiguous(Name:'unit body'/2),
asserta(loading_unit(Unit, Name, File, Old)).
begin_tests(Unit, Name, File:_Line, _Options) :-
'$set_source_module'(Old, Old),
asserta(loading_unit(Unit, Name, File, Old)).
'$declare_module'( Name, Class, Context, File, Line, _AllowFile ) :-
Name \= Context,
!,
set_module_property( Name, base(Context) ),
set_module_property( Name, class(Class) ),
set_module_property( Name, exports([], File, Line) ).
'$declare_module'( Name, _Class, Name, _File, _Line, _AllowFile ) .
:- else.
% we cannot use discontiguous as a goal in SICStus Prolog.
user:term_expansion((:- begin_tests(Set)),
[ (:- begin_tests(Set)),
(:- discontiguous(test/2)),
(:- discontiguous('unit body'/2)),
(:- discontiguous('unit test'/4))
]).
begin_tests(Unit, Name, File:_Line, Options) :-
loading_tests, !,
( current_unit(Unit, Name, _, Options)
-> true
; retractall(current_unit(Unit, Name, _, _)),
assert(current_unit(Unit, Name, -, Options))
),
asserta(loading_unit(Unit, Name, File, -)).
begin_tests(Unit, Name, File:_Line, _Options) :-
asserta(loading_unit(Unit, Name, File, -)).
:- endif.
%% end_tests(+Name) is det.
%
% Close a unit-test module.
%
% @tbd Run tests/clean module?
% @tbd End of file?
end_tests(Unit) :-
loading_unit(StartUnit, _, _, _), !,
( Unit == StartUnit
-> once(retract(loading_unit(StartUnit, _, _, Old))),
'$set_source_module'(_, Old)
; throw_error(context_error(plunit_close(Unit, StartUnit)), _)
).
end_tests(Unit) :-
throw_error(context_error(plunit_close(Unit, -)), _).
%% make_unit_module(+Name, -ModuleName) is det.
%% unit_module(+Name, -ModuleName) is det.
:- if(swi).
unit_module(Unit, Module) :-
atom_concat('plunit_', Unit, Module).
make_unit_module(Unit, Module) :-
unit_module(Unit, Module),
( current_module(Module),
\+ current_unit(_, Module, _, _),
predicate_property(Module:H, _P),
\+ predicate_property(Module:H, imported_from(_M))
-> throw_error(permission_error(create, plunit, Unit),
'Existing module')
; true
).
:- else.
:- dynamic
unit_module_store/2.
unit_module(Unit, Module) :-
unit_module_store(Unit, Module), !.
make_unit_module(Unit, Module) :-
prolog_load_context(module, Module),
assert(unit_module_store(Unit, Module)).
:- endif.
/*******************************
* EXPANSION *
*******************************/
%% expand_test(+Name, +Options, +Body, -Clause) is det.
%
% Expand test(Name, Options) :- Body into a clause for
% 'unit test'/4 and 'unit body'/2.
expand_test(Name, Options0, Body,
[ 'unit test'(Name, Line, Options, Module:'unit body'(Id, Vars)),
('unit body'(Id, Vars) :- !, Body)
]) :-
source_location(_File, Line),
prolog_load_context(module, Module),
atomic_list_concat([Name, '@line ', Line], Id),
term_variables(Options0, OptionVars0), sort(OptionVars0, OptionVars),
term_variables(Body, BodyVars0), sort(BodyVars0, BodyVars),
ord_intersection(OptionVars, BodyVars, VarList),
Vars =.. [vars|VarList],
( is_list(Options0) % allow for single option without list
-> Options1 = Options0
; Options1 = [Options0]
),
maplist(expand_option, Options1, Options2),
valid_options(Options2, test_option),
valid_test_mode(Options2, Options).
% writeln([ 'unit test'(Name, Line, Options, Module:'unit body'(Id, Vars)),
% ('unit body'(Id, Vars) :- !, Body)
% ]).
expand_option(Var, _) :-
var(Var), !,
throw_error(instantiation_error,_).
expand_option(A == B, true(A==B)) :- !.
expand_option(A = B, true(A=B)) :- !.
expand_option(A =@= B, true(A=@=B)) :- !.
expand_option(A =:= B, true(A=:=B)) :- !.
expand_option(error(X), throws(error(X, _))) :- !.
expand_option(exception(X), throws(X)) :- !. % SICStus 4 compatibility
expand_option(error(F,C), throws(error(F,C))) :- !. % SICStus 4 compatibility
expand_option(true, true(true)) :- !.
expand_option(O, O).
valid_test_mode(Options0, Options) :-
include(test_mode, Options0, Tests),
( Tests == []
-> Options = [true(true)|Options0]
; Tests = [_]
-> Options = Options0
; throw_error(plunit(incompatible_options, Tests), _)
).
test_mode(true(_)).
test_mode(all(_)).
test_mode(set(_)).
test_mode(fail).
test_mode(throws(_)).
%% expand(+Term, -Clauses) is semidet.
expand(end_of_file, _) :-
loading_unit(Unit, _, _, _), !,
end_tests(Unit), % warn?
fail.
expand((:-end_tests(_)), _) :- !,
fail.
expand(_Term, []) :-
\+ loading_tests.
expand((test(Name) :- Body), Clauses) :- !,
expand_test(Name, [], Body, Clauses).
expand((test(Name, Options) :- Body), Clauses) :- !,
expand_test(Name, Options, Body, Clauses).
expand(test(Name), _) :- !,
throw_error(existence_error(body, test(Name)), _).
expand(test(Name, _Options), _) :- !,
throw_error(existence_error(body, test(Name)), _).
:- if(swi).
:- multifile
user:term_expansion/2.
:- endif.
user:term_expansion(Term, Expanded) :-
%writeln(Term),
( loading_unit(_, _, File, _)
-> source_location(File, _),
expand(Term, Expanded)
).
/*******************************
* OPTIONS *
*******************************/
:- if(swi).
%:- use_module(library(error)).
:- else.
must_be(list, X) :- !,
( is_list(X)
-> true
; is_not(list, X)
).
must_be(Type, X) :-
( call(Type, X)
-> true
; is_not(Type, X)
).
is_not(Type, X) :-
( ground(X)
-> throw_error(type_error(Type, X), _)
; throw_error(instantiation_error, _)
).
:- endif.
%% valid_options(+Options, :Pred) is det.
%
% Verify Options to be a list of valid options according to
% Pred.
%
% @throws =type_error= or =instantiation_error=.
valid_options(Options, Pred) :-
must_be(list, Options),
verify_options(Options, Pred).
verify_options([], _).
verify_options([H|T], Pred) :-
( call(Pred, H)
-> verify_options(T, Pred)
; throw_error(domain_error(Pred, H), _)
).
%% test_option(+Option) is semidet.
%
% True if Option is a valid option for test(Name, Options).
test_option(Option) :-
test_set_option(Option), !.
test_option(true(_)).
test_option(fail).
test_option(throws(_)).
test_option(all(_)).
test_option(set(_)).
test_option(nondet).
test_option(fixme(_)).
test_option(forall(X)) :-
must_be(callable, X).
%% test_option(+Option) is semidet.
%
% True if Option is a valid option for :- begin_tests(Name,
% Options).
test_set_option(blocked(X)) :-
must_be(ground, X).
test_set_option(condition(X)) :-
must_be(callable, X).
test_set_option(setup(X)) :-
must_be(callable, X).
test_set_option(cleanup(X)) :-
must_be(callable, X).
test_set_option(sto(V)) :-
nonvar(V), member(V, [finite_trees, rational_trees]).
/*******************************
* RUNNING TOPLEVEL *
*******************************/
:- thread_local
passed/5, % Unit, Test, Line, Det, Time
failed/4, % Unit, Test, Line, Reason
failed_assertion/7, % Unit, Test, Line, ALoc, STO, Reason, Goal
blocked/4, % Unit, Test, Line, Reason
sto/4, % Unit, Test, Line, Results
fixme/5. % Unit, Test, Line, Reason, Status
:- dynamic
running/5. % Unit, Test, Line, STO, Thread
%% run_tests is semidet.
%% run_tests(+TestSet) 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.
% This 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.
run_tests :-
cleanup,
setup_call_cleanup(
setup_trap_assertions(Ref),
( forall(current_test_set(Set),
run_unit(Set)),
check_for_test_errors
),
( cleanup_trap_assertions(Ref),
report,
cleanup_after_test
)).
run_tests(Set) :-
cleanup,
setup_call_cleanup(
setup_trap_assertions(Ref),
( run_unit(Set),
% writeln(done),
check_for_test_errors
),
( cleanup_trap_assertions(Ref),
report,
cleanup_after_test
)).
run_unit([]) :- !.
run_unit([H|T]) :- !,
run_unit(H),
run_unit(T).
run_unit(Spec) :-
unit_from_spec(Spec, Unit, Tests, Module, UnitOptions),
( option(blocked(Reason), UnitOptions)
-> info(plunit(blocked(unit(Unit, Reason))))
; setup(Module, unit(Unit), UnitOptions)
-> info(plunit(begin(Spec))),
forall((Module:'unit test'(Name, Line, Options, Body),
matching_test(Name, Tests)),
run_test(Unit, Name, Line, Options, Body)),
info(plunit(end(Spec))),
( message_level(silent)
-> true
; format(user_error, '~N', [])
),
cleanup(Module, UnitOptions)
; true
).
unit_from_spec(Unit, Unit, _, Module, Options) :-
atom(Unit), !,
( current_unit(Unit, Module, _Supers, Options)
-> true
; throw_error(existence_error(unit_test, Unit), _)
).
unit_from_spec(Unit:Tests, Unit, Tests, Module, Options) :-
atom(Unit), !,
( current_unit(Unit, Module, _Supers, Options)
-> true
; throw_error(existence_error(unit_test, Unit), _)
).
matching_test(X, X) :- !.
matching_test(Name, Set) :-
is_list(Set),
memberchk(Name, Set).
cleanup :-
thread_self(Me),
retractall(passed(_, _, _, _, _)),
retractall(failed(_, _, _, _)),
retractall(failed_assertion(_, _, _, _, _, _, _)),
retractall(blocked(_, _, _, _)),
retractall(sto(_, _, _, _)),
retractall(fixme(_, _, _, _, _)),
retractall(running(_,_,_,_,Me)).
cleanup_after_test :-
current_test_flag(test_options, Options),
option(cleanup(Cleanup), Options, false),
( Cleanup == true
-> cleanup
; true
).
%% run_tests_in_files(+Files:list) is det.
%
% Run all test-units that appear in the given Files.
run_tests_in_files(Files) :-
findall(Unit, unit_in_files(Files, Unit), Units),
( Units == []
-> true
; run_tests(Units)
).
unit_in_files(Files, Unit) :-
is_list(Files), !,
member(F, Files),
absolute_file_name(F, Source,
[ file_type(prolog),
access(read),
file_errors(fail)
]),
unit_file(Unit, Source).
/*******************************
* HOOKING MAKE/0 *
*******************************/
%% make_run_tests(+Files)
%
% Called indirectly from make/0 after Files have been reloaded.
make_run_tests(Files) :-
current_test_flag(test_options, Options),
option(run(When), Options, manual),
( When == make
-> run_tests_in_files(Files)
; When == make(all)
-> run_tests
; true
).
:- if(swi).
unification_capability(sto_error_incomplete).
% can detect some (almost all) STO runs
unification_capability(rational_trees).
unification_capability(finite_trees).
set_unification_capability(Cap) :-
cap_to_flag(Cap, Flag),
set_prolog_flag(occurs_check, Flag).
current_unification_capability(Cap) :-
current_prolog_flag(occurs_check, Flag),
cap_to_flag(Cap, Flag), !.
cap_to_flag(sto_error_incomplete, error).
cap_to_flag(rational_trees, false).
cap_to_flag(finite_trees, true).
:- else.
:- if(sicstus).
unification_capability(rational_trees).
set_unification_capability(rational_trees).
current_unification_capability(rational_trees).
:- else.
unification_capability(_) :-
fail.
:- endif.
:- endif.
/*******************************
* ASSERTION HANDLING *
*******************************/
:- if(swi).
:- dynamic user:assertion_failed/2.
setup_trap_assertions(Ref) :-
asserta((user:assertion_failed(Reason, Goal) :-
test_assertion_failed(Reason, Goal)),
Ref).
cleanup_trap_assertions(Ref) :-
erase(Ref).
test_assertion_failed(Reason, Goal) :-
thread_self(Me),
running(Unit, Test, Line, STO, Me),
( catch(get_prolog_backtrace(10, Stack), _, fail),
assertion_location(Stack, AssertLoc)
-> true
; AssertLoc = unknown
),
current_test_flag(test_options, Options),
report_failed_assertion(Unit, Test, Line, AssertLoc,
STO, Reason, Goal, Options),
assert_cyclic(failed_assertion(Unit, Test, Line, AssertLoc,
STO, Reason, Goal)).
assertion_location(Stack, File:Line) :-
append(_, [AssertFrame,CallerFrame|_], Stack),
prolog_stack_frame_property(AssertFrame,
predicate(prolog_debug:assertion/1)), !,
prolog_stack_frame_property(CallerFrame, location(File:Line)).
report_failed_assertion(Unit, Test, Line, AssertLoc,
STO, Reason, Goal, _Options) :-
print_message(
error,
plunit(failed_assertion(Unit, Test, Line, AssertLoc,
STO, Reason, Goal))).
:- else.
setup_trap_assertions(_).
cleanup_trap_assertions(_).
:- endif.
/*******************************
* RUNNING A TEST *
*******************************/
%% run_test(+Unit, +Name, +Line, +Options, +Body) is det.
%
% Run a single test.
run_test(Unit, Name, Line, Options, Body) :-
option(forall(Generator), Options), !,
unit_module(Unit, Module),
term_variables(Generator, Vars),
forall(Module:Generator,
run_test_once(Unit, @(Name,Vars), Line, Options, Body)).
run_test(Unit, Name, Line, Options, Body) :-
run_test_once(Unit, Name, Line, Options, Body).
run_test_once(Unit, Name, Line, Options, Body) :-
current_test_flag(test_options, GlobalOptions),
option(sto(false), GlobalOptions, false), !,
current_unification_capability(Type),
begin_test(Unit, Name, Line, Type),
run_test_6(Unit, Name, Line, Options, Body, Result),
end_test(Unit, Name, Line, Type),
report_result(Result, Options).
run_test_once(Unit, Name, Line, Options, Body) :-
current_unit(Unit, _Module, _Supers, UnitOptions),
option(sto(Type), UnitOptions),
\+ option(sto(_), Options), !,
current_unification_capability(Cap0),
call_cleanup(run_test_cap(Unit, Name, Line, [sto(Type)|Options], Body),
set_unification_capability(Cap0)).
run_test_once(Unit, Name, Line, Options, Body) :-
current_unification_capability(Cap0),
call_cleanup(run_test_cap(Unit, Name, Line, Options, Body),
set_unification_capability(Cap0)).
run_test_cap(Unit, Name, Line, Options, Body) :-
( option(sto(Type), Options)
-> unification_capability(Type),
set_unification_capability(Type),
begin_test(Unit, Name, Line, Type),
run_test_6(Unit, Name, Line, Options, Body, Result),
end_test(Unit, Name, Line, Type),
report_result(Result, Options)
; findall(Key-(Type+Result),
test_caps(Type, Unit, Name, Line, Options, Body, Result, Key),
Pairs),
group_pairs_by_key(Pairs, Keyed),
( Keyed == []
-> true
; Keyed = [_-Results]
-> Results = [_Type+Result|_],
report_result(Result, Options) % consistent results
; pairs_values(Pairs, ResultByType),
report_result(sto(Unit, Name, Line, ResultByType), Options)
)
).
%% test_caps(-Type, +Unit, +Name, +Line, +Options, +Body, -Result, -Key) is nondet.
test_caps(Type, Unit, Name, Line, Options, Body, Result, Key) :-
unification_capability(Type),
set_unification_capability(Type),
begin_test(Unit, Name, Line, Type),
run_test_6(Unit, Name, Line, Options, Body, Result),
end_test(Unit, Name, Line, Type),
result_to_key(Result, Key),
Key \== setup_failed.
result_to_key(blocked(_, _, _, _), blocked).
result_to_key(failure(_, _, _, How0), failure(How1)) :-
( How0 = succeeded(_T) -> How1 = succeeded ; How0 = How1 ).
result_to_key(success(_, _, _, Determinism, _), success(Determinism)).
result_to_key(setup_failed(_,_,_), setup_failed).
report_result(blocked(Unit, Name, Line, Reason), _) :- !,
assert(blocked(Unit, Name, Line, Reason)).
report_result(failure(Unit, Name, Line, How), Options) :- !,
failure(Unit, Name, Line, How, Options).
report_result(success(Unit, Name, Line, Determinism, Time), Options) :- !,
success(Unit, Name, Line, Determinism, Time, Options).
report_result(setup_failed(_Unit, _Name, _Line), _Options).
report_result(sto(Unit, Name, Line, ResultByType), Options) :-
assert(sto(Unit, Name, Line, ResultByType)),
print_message(error, plunit(sto(Unit, Name, Line))),
report_sto_results(ResultByType, Options).
report_sto_results([], _).
report_sto_results([Type+Result|T], Options) :-
print_message(error, plunit(sto(Type, Result))),
report_sto_results(T, Options).
%% run_test_6(+Unit, +Name, +Line, +Options, :Body, -Result) is det.
%
% Result is one of:
%
% * blocked(Unit, Name, Line, Reason)
% * failure(Unit, Name, Line, How)
% * success(Unit, Name, Line, Determinism, Time)
% * setup_failed(Unit, Name, Line)
run_test_6(Unit, Name, Line, Options, _Body,
blocked(Unit, Name, Line, Reason)) :-
option(blocked(Reason), Options), !.
run_test_6(Unit, Name, Line, Options, Body, Result) :-
option(all(Answer), Options), !, % all(Bindings)
nondet_test(all(Answer), Unit, Name, Line, Options, Body, Result).
run_test_6(Unit, Name, Line, Options, Body, Result) :-
option(set(Answer), Options), !, % set(Bindings)
nondet_test(set(Answer), Unit, Name, Line, Options, Body, Result).
run_test_6(Unit, Name, Line, Options, Body, Result) :-
option(fail, Options), !, % fail
unit_module(Unit, Module),
( setup(Module, test(Unit,Name,Line), Options)
-> statistics(runtime, [T0,_]),
( catch(Module:Body, E, true)
-> ( var(E)
-> statistics(runtime, [T1,_]),
Time is (T1 - T0)/1000.0,
Result = failure(Unit, Name, Line, succeeded(Time)),
cleanup(Module, Options)
; Result = failure(Unit, Name, Line, E),
cleanup(Module, Options)
)
; statistics(runtime, [T1,_]),
Time is (T1 - T0)/1000.0,
Result = success(Unit, Name, Line, true, Time),
cleanup(Module, Options)
)
; Result = setup_failed(Unit, Name, Line)
).
run_test_6(Unit, Name, Line, Options, Body, Result) :-
option(true(Cmp), Options), !,
unit_module(Unit, Module),
( setup(Module, test(Unit,Name,Line), Options) % true(Binding)
-> statistics(runtime, [T0,_]),
( catch(call_det(Module:Body, Det), E, true)
-> ( var(E)
-> statistics(runtime, [T1,_]),
Time is (T1 - T0)/1000.0,
( catch(Module:Cmp, E, true)
-> ( var(E)
-> Result = success(Unit, Name, Line, Det, Time)
; Result = failure(Unit, Name, Line, cmp_error(Cmp, E))
)
; Result = failure(Unit, Name, Line, wrong_answer(Cmp))
),
cleanup(Module, Options)
; Result = failure(Unit, Name, Line, E),
cleanup(Module, Options)
)
; Result = failure(Unit, Name, Line, failed),
cleanup(Module, Options)
)
; Result = setup_failed(Unit, Name, Line)
).
run_test_6(Unit, Name, Line, Options, Body, Result) :-
option(throws(Expect), Options), !,
unit_module(Unit, Module),
( setup(Module, test(Unit,Name,Line), Options)
-> statistics(runtime, [T0,_]),
( catch(Module:Body, E, true)
-> ( var(E)
-> Result = failure(Unit, Name, Line, no_exception),
cleanup(Module, Options)
; statistics(runtime, [T1,_]),
Time is (T1 - T0)/1000.0,
( match_error(Expect, E)
-> Result = success(Unit, Name, Line, true, Time)
; Result = failure(Unit, Name, Line, wrong_error(Expect, E))
),
cleanup(Module, Options)
)
; Result = failure(Unit, Name, Line, failed),
cleanup(Module, Options)
)
; Result = setup_failed(Unit, Name, Line)
).
%% non_det_test(+Expected, +Unit, +Name, +Line, +Options, +Body, -Result)
%
% Run tests on non-deterministic predicates.
nondet_test(Expected, Unit, Name, Line, Options, Body, Result) :-
unit_module(Unit, Module),
result_vars(Expected, Vars),
statistics(runtime, [T0,_]),
( setup(Module, test(Unit,Name,Line), Options)
-> ( catch(findall(Vars, Module:Body, Bindings), E, true)
-> ( var(E)
-> statistics(runtime, [T1,_]),
Time is (T1 - T0)/1000.0,
( nondet_compare(Expected, Bindings, Unit, Name, Line)
-> Result = success(Unit, Name, Line, true, Time)
; Result = failure(Unit, Name, Line, wrong_answer(Expected, Bindings))
),
cleanup(Module, Options)
; Result = failure(Unit, Name, Line, E),
cleanup(Module, Options)
)
)
; Result = setup_failed(Unit, Name, Line)
).
%% result_vars(+Expected, -Vars) is det.
%
% Create a term v(V1, ...) containing all variables at the left
% side of the comparison operator on Expected.
result_vars(Expected, Vars) :-
arg(1, Expected, CmpOp),
arg(1, CmpOp, Vars).
%% nondet_compare(+Expected, +Bindings, +Unit, +Name, +Line) is semidet.
%
% Compare list/set results for non-deterministic predicates.
%
% @tbd Properly report errors
% @bug Sort should deal with equivalence on the comparison
% operator.
nondet_compare(all(Cmp), Bindings, _Unit, _Name, _Line) :-
cmp(Cmp, _Vars, Op, Values),
cmp_list(Values, Bindings, Op).
nondet_compare(set(Cmp), Bindings0, _Unit, _Name, _Line) :-
cmp(Cmp, _Vars, Op, Values0),
sort(Bindings0, Bindings),
sort(Values0, Values),
cmp_list(Values, Bindings, Op).
cmp_list([], [], _Op).
cmp_list([E0|ET], [V0|VT], Op) :-
call(Op, E0, V0),
cmp_list(ET, VT, Op).
%% cmp(+CmpTerm, -Left, -Op, -Right) is det.
cmp(Var == Value, Var, ==, Value).
cmp(Var =:= Value, Var, =:=, Value).
cmp(Var = Value, Var, =, Value).
:- if(swi).
cmp(Var =@= Value, Var, =@=, Value).
:- else.
:- if(sicstus).
cmp(Var =@= Value, Var, variant, Value). % variant/2 is the same =@=
:- endif.
:- endif.
%% call_det(:Goal, -Det) is nondet.
%
% True if Goal succeeded. Det is unified to =true= if Goal left
% no choicepoints and =false= otherwise.
:- if((swi|sicstus)).
call_det(Goal, Det) :-
call_cleanup(Goal,Det0=true),
( var(Det0) -> Det = false ; Det = true ).
:- else.
call_det(Goal, true) :-
call(Goal).
:- endif.
%% match_error(+Expected, +Received) is semidet.
%
% True if the Received errors matches the expected error. Matching
% is based on subsumes_term/2.
match_error(Expect, Rec) :-
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
setup(Module, Context, Options) :-
option(condition(Condition), Options),
option(setup(Setup), Options), !,
setup(Module, Context, [condition(Condition)]),
setup(Module, Context, [setup(Setup)]).
setup(Module, Context, Options) :-
option(setup(Setup), Options), !,
( catch(call_ex(Module, Setup), E, true)
-> ( var(E)
-> true
; print_message(error, plunit(error(setup, Context, E))),
fail
)
; print_message(error, error(goal_failed(Setup), _)),
fail
).
setup(Module, Context, Options) :-
option(condition(Setup), Options), !,
( catch(call_ex(Module, Setup), E, true)
-> ( var(E)
-> true
; print_message(error, plunit(error(condition, Context, E))),
fail
)
; fail
).
setup(_,_,_).
%% call_ex(+Module, +Goal)
%
% Call Goal in Module after applying goal expansion.
call_ex(Module, Goal) :-
Module:(expand_goal(Goal, GoalEx),
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.
cleanup(Module, Options) :-
option(cleanup(Cleanup), Options, true),
( catch(call_ex(Module, Cleanup), E, true)
-> ( var(E)
-> true
; print_message(warning, E)
)
; print_message(warning, goal_failed(Cleanup, '(cleanup handler)'))
).
success(Unit, Name, Line, Det, _Time, Options) :-
memberchk(fixme(Reason), Options), !,
( ( Det == true
; memberchk(nondet, Options)
)
-> put_char(user_error, +),
Ok = passed
; put_char(user_error, !),
Ok = nondet
),
flush_output(user_error),
assert(fixme(Unit, Name, Line, Reason, Ok)).
success(Unit, Name, Line, _, _, Options) :-
failed_assertion(Unit, Name, Line, _,_,_,_), !,
failure(Unit, Name, Line, assertion, Options).
success(Unit, Name, Line, Det, Time, Options) :-
assert(passed(Unit, Name, Line, Det, Time)),
( ( Det == true
; memberchk(nondet, Options)
)
-> put_char(user_error, .)
; unit_file(Unit, File),
print_message(warning, plunit(nondet(File, Line, Name)))
),
flush_output(user_error).
failure(Unit, Name, Line, _, Options) :-
memberchk(fixme(Reason), Options), !,
put_char(user_error, -),
flush_output(user_error),
assert(fixme(Unit, Name, Line, Reason, failed)).
failure(Unit, Name, Line, E, Options) :-
report_failure(Unit, Name, Line, E, Options),
assert_cyclic(failed(Unit, Name, Line, E)).
%% 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.
%
% @tbd Implement cycle-safe assert and remove this.
:- if(swi).
assert_cyclic(Term) :-
acyclic_term(Term), !,
assert(Term).
assert_cyclic(Term) :-
Term =.. [Functor|Args],
recorda(cyclic, Args, Id),
functor(Term, _, Arity),
length(NewArgs, Arity),
Head =.. [Functor|NewArgs],
assert((Head :- recorded(_, Var, Id), Var = NewArgs)).
:- else.
:- if(sicstus).
:- endif.
assert_cyclic(Term) :-
assert(Term).
:- endif.
/*******************************
* REPORTING *
*******************************/
%% begin_test(Unit, Test, Line, STO) is det.
%% end_test(Unit, Test, Line, STO) is det.
%
% Maintain running/5 and report a test has started/is ended using
% a =silent= message:
%
% * plunit(begin(Unit:Test, File:Line, STO))
% * plunit(end(Unit:Test, File:Line, STO))
%
% @see message_hook/3 for intercepting these messages
begin_test(Unit, Test, Line, STO) :-
thread_self(Me),
assert(running(Unit, Test, Line, STO, Me)),
unit_file(Unit, File),
print_message(silent, plunit(begin(Unit:Test, File:Line, STO))).
end_test(Unit, Test, Line, STO) :-
thread_self(Me),
retractall(running(_,_,_,_,Me)),
unit_file(Unit, File),
print_message(silent, plunit(end(Unit:Test, File:Line, STO))).
%% running_tests is det.
%
% Print the currently running test.
running_tests :-
running_tests(Running),
print_message(informational, plunit(running(Running))).
running_tests(Running) :-
findall(running(Unit:Test, File:Line, STO, Thread),
( running(Unit, Test, Line, STO, Thread),
unit_file(Unit, File)
), Running).
%% check_for_test_errors is semidet.
%
% True if there are no errors, otherwise false.
check_for_test_errors :-
number_of_clauses(failed/4, Failed),
number_of_clauses(failed_assertion/7, FailedAssertion),
number_of_clauses(sto/4, STO),
Failed+FailedAssertion+STO =:= 0. % fail on errors
%% report is det.
%
% Print a summary of the tests that ran.
report :-
number_of_clauses(passed/5, Passed),
number_of_clauses(failed/4, Failed),
number_of_clauses(failed_assertion/7, FailedAssertion),
number_of_clauses(blocked/4, Blocked),
number_of_clauses(sto/4, STO),
( Passed+Failed+FailedAssertion+Blocked+STO =:= 0
-> info(plunit(no_tests))
; Failed+FailedAssertion+Blocked+STO =:= 0
-> report_fixme,
info(plunit(all_passed(Passed)))
; report_blocked,
report_fixme,
report_failed_assertions,
report_failed,
report_sto,
info(plunit(passed(Passed)))
).
number_of_clauses(F/A,N) :-
( current_predicate(F/A) %, writeln(F/A)
-> functor(G,F,A),
% writeln( G:F/A),
findall(t, G, Ts),
length(Ts, N)
; N = 0
).
report_blocked :-
number_of_clauses(blocked/4,N),
N > 0, !,
info(plunit(blocked(N))),
( blocked(Unit, Name, Line, Reason),
unit_file(Unit, File),
print_message(informational,
plunit(blocked(File:Line, Name, Reason))),
fail ; true
).
report_blocked.
report_failed :-
number_of_clauses(failed/4, N),
info(plunit(failed(N))).
report_failed_assertions :-
number_of_clauses(failed_assertion/7, N),
info(plunit(failed_assertions(N))).
report_sto :-
number_of_clauses(sto/4, N),
info(plunit(sto(N))).
report_fixme :-
report_fixme(_,_,_).
report_fixme(TuplesF, TuplesP, TuplesN) :-
fixme(failed, TuplesF, Failed),
fixme(passed, TuplesP, Passed),
fixme(nondet, TuplesN, Nondet),
print_message(informational, plunit(fixme(Failed, Passed, Nondet))).
fixme(How, Tuples, Count) :-
findall(fixme(Unit, Name, Line, Reason, How),
fixme(Unit, Name, Line, Reason, How), Tuples),
length(Tuples, Count).
report_failure(_, _, _, assertion, _) :- !,
put_char(user_error, 'A').
report_failure(Unit, Name, Line, Error, _Options) :-
print_message(error, plunit(failed(Unit, Name, Line, Error))).
%% test_report(What) is det.
%
% Produce reports on test results after the run.
test_report(fixme) :- !,
report_fixme(TuplesF, TuplesP, TuplesN),
append([TuplesF, TuplesP, TuplesN], Tuples),
print_message(informational, plunit(fixme(Tuples))).
test_report(What) :-
throw_error(domain_error(report_class, What), _).
/*******************************
* INFO *
*******************************/
%% current_test_set(?Unit) is nondet.
%
% True if Unit is a currently loaded test-set.
current_test_set(Unit) :-
current_unit(Unit, _Module, _Context, _Options).
%% unit_file(+Unit, -File) is det.
%% unit_file(-Unit, +File) is nondet.
unit_file(Unit, File) :-
current_unit(Unit, Module, _Context, _Options),
current_module(Module, File).
unit_file(Unit, PlFile) :-
nonvar(PlFile),
test_file_for(TestFile, PlFile),
current_module(Module, TestFile),
current_unit(Unit, Module, _Context, _Options).
/*******************************
* FILES *
*******************************/
%% load_test_files(+Options) is det.
%
% Load .plt test-files related to loaded source-files.
load_test_files(_Options) :-
( source_file(File),
file_name_extension(Base, Old, File),
Old \== plt,
file_name_extension(Base, plt, TestFile),
exists_file(TestFile),
( test_file_for(TestFile, File)
-> true
; load_files(TestFile,
[ if(changed),
imports([])
]),
asserta(test_file_for(TestFile, File))
),
fail ; true
).
/*******************************
* MESSAGES *
*******************************/
%% info(+Term)
%
% Runs print_message(Level, Term), where Level is one of =silent=
% or =informational= (default).
info(Term) :-
message_level(Level),
print_message(Level, Term).
message_level(Level) :-
current_test_flag(test_options, Options),
option(silent(Silent), Options, false),
( Silent == false
-> Level = informational
; Level = silent
).
locationprefix(File:Line) --> !,
[ '~w:~d:\n\t'-[File,Line]].
locationprefix(test(Unit,_Test,Line)) --> !,
{ unit_file(Unit, File) },
locationprefix(File:Line).
locationprefix(unit(Unit)) --> !,
[ 'PL-Unit: unit ~w: '-[Unit] ].
locationprefix(FileLine) -->
{ throw_error(type_error(locationprefix,FileLine), _) }.
:- discontiguous message//1.
message(error(context_error(plunit_close(Name, -)), _)) -->
[ 'PL-Unit: cannot close unit ~w: no open unit'-[Name] ].
message(error(context_error(plunit_close(Name, Start)), _)) -->
[ 'PL-Unit: cannot close unit ~w: current unit is ~w'-[Name, Start] ].
message(plunit(nondet(File, Line, Name))) -->
locationprefix(File:Line),
[ 'PL-Unit: Test ~w: Test succeeded with choicepoint'- [Name] ].
message(error(plunit(incompatible_options, Tests), _)) -->
[ 'PL-Unit: incompatible test-options: ~p'-[Tests] ].
% Unit start/end
:- if(swi).
message(plunit(begin(Unit))) -->
[ 'PL-Unit: ~w '-[Unit], flush ].
message(plunit(end(_Unit))) -->
[ at_same_line, ' done' ].
:- else.
message(plunit(begin(Unit))) -->
[ 'PL-Unit: ~w '-[Unit]/*, flush-[]*/ ].
message(plunit(end(_Unit))) -->
[ ' done'-[] ].
:- endif.
message(plunit(blocked(unit(Unit, Reason)))) -->
[ 'PL-Unit: ~w blocked: ~w'-[Unit, Reason] ].
message(plunit(running([]))) --> !,
[ 'PL-Unit: no tests running' ].
message(plunit(running([One]))) --> !,
[ 'PL-Unit: running ' ],
running(One).
message(plunit(running(More))) --> !,
[ 'PL-Unit: running tests:', nl ],
running(More).
message(plunit(fixme([]))) --> !.
message(plunit(fixme(Tuples))) --> !,
fixme_message(Tuples).
% Blocked tests
message(plunit(blocked(1))) --> !,
[ 'one test is blocked:'-[] ].
message(plunit(blocked(N))) -->
[ '~D tests are blocked:'-[N] ].
message(plunit(blocked(Pos, Name, Reason))) -->
locationprefix(Pos),
test_name(Name),
[ ': ~w'-[Reason] ].
% fail/success
message(plunit(no_tests)) --> !,
[ 'No tests to run' ].
message(plunit(all_passed(1))) --> !,
[ 'test passed' ].
message(plunit(all_passed(Count))) --> !,
[ 'All ~D tests passed'-[Count] ].
message(plunit(passed(Count))) --> !,
[ '~D tests passed'-[Count] ].
message(plunit(failed(0))) --> !,
[].
message(plunit(failed(1))) --> !,
[ '1 test failed'-[] ].
message(plunit(failed(N))) -->
[ '~D tests failed'-[N] ].
message(plunit(failed_assertions(0))) --> !,
[].
message(plunit(failed_assertions(1))) --> !,
[ '1 assertion failed'-[] ].
message(plunit(failed_assertions(N))) -->
[ '~D assertions failed'-[N] ].
message(plunit(sto(0))) --> !,
[].
message(plunit(sto(N))) -->
[ '~D test results depend on unification mode'-[N] ].
message(plunit(fixme(0,0,0))) -->
[].
message(plunit(fixme(Failed,0,0))) --> !,
[ 'all ~D tests flagged FIXME failed'-[Failed] ].
message(plunit(fixme(Failed,Passed,0))) -->
[ 'FIXME: ~D failed; ~D passed'-[Failed, Passed] ].
message(plunit(fixme(Failed,Passed,Nondet))) -->
{ TotalPassed is Passed+Nondet },
[ 'FIXME: ~D failed; ~D passed; (~D nondet)'-
[Failed, TotalPassed, Nondet] ].
message(plunit(failed(Unit, Name, Line, Failure))) -->
{ unit_file(Unit, File) },
locationprefix(File:Line),
test_name(Name),
[': '-[] ],
failure(Failure).
:- if(swi).
message(plunit(failed_assertion(Unit, Name, Line, AssertLoc,
_STO, Reason, Goal))) -->
{ unit_file(Unit, File) },
locationprefix(File:Line),
test_name(Name),
[ ': assertion'-[] ],
assertion_location(AssertLoc, File),
assertion_reason(Reason), ['\n\t'],
assertion_goal(Unit, Goal).
assertion_location(File:Line, File) -->
[ ' at line ~w'-[Line] ].
assertion_location(File:Line, _) -->
[ ' at ~w:~w'-[File, Line] ].
assertion_location(unknown, _) -->
[].
assertion_reason(fail) --> !,
[ ' failed'-[] ].
assertion_reason(Error) -->
{ message_to_string(Error, String) },
[ ' raised "~w"'-[String] ].
assertion_goal(Unit, Goal) -->
{ unit_module(Unit, Module),
unqualify(Goal, Module, Plain)
},
[ 'Assertion: ~p'-[Plain] ].
unqualify(Var, _, Var) :-
var(Var), !.
unqualify(M:Goal, Unit, Goal) :-
nonvar(M),
unit_module(Unit, M), !.
unqualify(M:Goal, _, Goal) :-
callable(Goal),
predicate_property(M:Goal, imported_from(system)), !.
unqualify(Goal, _, Goal).
:- endif.
% Setup/condition errors
message(plunit(error(Where, Context, Exception))) -->
locationprefix(Context),
{ message_to_string(Exception, String) },
[ 'error in ~w: ~w'-[Where, String] ].
% STO messages
message(plunit(sto(Unit, Name, Line))) -->
{ unit_file(Unit, File) },
locationprefix(File:Line),
test_name(Name),
[' is subject to occurs check (STO): '-[] ].
message(plunit(sto(Type, Result))) -->
sto_type(Type),
sto_result(Result).
% Interrupts (SWI)
:- if(swi).
message(interrupt(begin)) -->
{ thread_self(Me),
running(Unit, Test, Line, STO, Me), !,
unit_file(Unit, File)
},
[ 'Interrupted test '-[] ],
running(running(Unit:Test, File:Line, STO, Me)),
[nl],
'$messages':prolog_message(interrupt(begin)).
message(interrupt(begin)) -->
'$messages':prolog_message(interrupt(begin)).
:- endif.
test_name(@(Name,Bindings)) --> !,
[ 'test ~w (forall bindings = ~p)'-[Name, Bindings] ].
test_name(Name) --> !,
[ 'test ~w'-[Name] ].
sto_type(sto_error_incomplete) -->
[ 'Finite trees (error checking): ' ].
sto_type(rational_trees) -->
[ 'Rational trees: ' ].
sto_type(finite_trees) -->
[ 'Finite trees: ' ].
sto_result(success(_Unit, _Name, _Line, Det, Time)) -->
det(Det),
[ ' success in ~2f seconds'-[Time] ].
sto_result(failure(_Unit, _Name, _Line, How)) -->
failure(How).
det(true) -->
[ 'deterministic' ].
det(false) -->
[ 'non-deterministic' ].
running(running(Unit:Test, File:Line, STO, Thread)) -->
thread(Thread),
[ '~q:~q at ~w:~d'-[Unit, Test, File, Line] ],
current_sto(STO).
running([H|T]) -->
['\t'], running(H),
( {T == []}
-> []
; [nl], running(T)
).
thread(main) --> !.
thread(Other) -->
[' [~w] '-[Other] ].
current_sto(sto_error_incomplete) -->
[ ' (STO: error checking)' ].
current_sto(rational_trees) -->
[].
current_sto(finite_trees) -->
[ ' (STO: occurs check enabled)' ].
:- if(swi).
write_term(T, OPS) -->
['~@'-[write_term(T,OPS)]].
:- else.
write_term(T, _OPS) -->
['~q'-[T]].
:- endif.
expected_got_ops_(Ex, E, OPS, Goals) -->
[' Expected: '-[]], write_term(Ex, OPS), [nl],
[' Got: '-[]], write_term(E, OPS), [nl],
( { Goals = [] } -> []
; [' with: '-[]], write_term(Goals, OPS), [nl]
).
failure(Var) -->
{ var(Var) }, !,
[ 'Unknown failure?' ].
failure(succeeded(Time)) --> !,
[ 'must fail but succeeded in ~2f seconds~n'-[Time] ].
failure(wrong_error(Expected, Error)) --> !,
{ copy_term(Expected-Error, Ex-E, Goals),
numbervars(Ex-E-Goals, 0, _),
write_options(OPS)
},
[ 'wrong error'-[], nl ],
expected_got_ops_(Ex, E, OPS, Goals).
failure(wrong_answer(Cmp)) -->
{ Cmp =.. [Op,Answer,Expected], !,
copy_term(Expected-Answer, Ex-A, Goals),
numbervars(Ex-A-Goals, 0, _),
write_options(OPS)
},
[ 'wrong answer (compared using ~w)'-[Op], nl ],
expected_got_ops_(Ex, A, OPS, Goals).
failure(wrong_answer(CmpExpected, Bindings)) -->
{ ( CmpExpected = all(Cmp)
-> Cmp =.. [_Op1,_,Expected],
Got = Bindings,
Type = all
; CmpExpected = set(Cmp),
Cmp =.. [_Op2,_,Expected0],
sort(Expected0, Expected),
sort(Bindings, Got),
Type = set
)
},
[ 'wrong "~w" answer:'-[Type] ],
[ nl, ' Expected: ~q'-[Expected] ],
[ nl, ' Found: ~q'-[Got] ].
:- if(swi).
failure(cmp_error(_Cmp, Error)) -->
{ message_to_string(Error, Message) },
[ 'Comparison error: ~w'-[Message] ].
failure(Error) -->
{ Error = error(_,_), !,
message_to_string(Error, Message)
},
[ 'received error: ~w'-[Message] ].
:- endif.
failure(Why) -->
[ '~p~n'-[Why] ].
fixme_message([]) --> [].
fixme_message([fixme(Unit, _Name, Line, Reason, How)|T]) -->
{ unit_file(Unit, File) },
fixme_message(File:Line, Reason, How),
( {T == []}
-> []
; [nl],
fixme_message(T)
).
fixme_message(Location, Reason, failed) -->
[ 'FIXME: ~w: ~w'-[Location, Reason] ].
fixme_message(Location, Reason, passed) -->
[ 'FIXME: ~w: passed ~w'-[Location, Reason] ].
fixme_message(Location, Reason, nondet) -->
[ 'FIXME: ~w: passed (nondet) ~w'-[Location, Reason] ].
write_options([ numbervars(true),
quoted(true),
portray(true),
max_depth(10),
attributes(portray)
]).
:- if(swi).
:- multifile
message/3,
user:message_hook/3.
% user:message_hook(+Term, +Kind, +Lines)
user:message_hook(make(done(Files)), _, _) :-
make_run_tests(Files),
fail. % give other hooks a chance
:- endif.
:- if(sicstus).
user:generate_message_hook(Message) -->
message(Message),
[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 ...
user:message_hook(informational, plunit(begin(Unit)), _Lines) :-
format(user_error, '% PL-Unit: ~w ', [Unit]),
flush_output(user_error).
user:message_hook(informational, plunit(end(_Unit)), _Lines) :-
format(user, ' done~n', []).
:- endif.