252 lines
7.4 KiB
Perl
252 lines
7.4 KiB
Perl
|
/* $Id$
|
||
|
|
||
|
Part of SWI-Prolog
|
||
|
|
||
|
Author: Jan Wielemaker
|
||
|
E-mail: wielemak@science.uva.nl
|
||
|
WWW: http://www.swi-prolog.org
|
||
|
Copyright (C): 1985-2006, University of 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
|
||
|
*/
|
||
|
|
||
|
:- module(prolog_cover,
|
||
|
[ show_coverage/1, % :Goal
|
||
|
covered_clauses/4 % +Goal, -Result, -Succeeded, -Failed
|
||
|
]).
|
||
|
:- use_module(library(ordsets)).
|
||
|
|
||
|
:- set_prolog_flag(generate_debug_info, false).
|
||
|
|
||
|
/** <module> Clause cover analysis
|
||
|
|
||
|
The purpose of this module is to find which part of the program has been
|
||
|
use by a certain goal. Usage is defined in terms of clauses that have
|
||
|
fired, seperated in clauses that succeeded at least once and clauses
|
||
|
that failed on each occasion.
|
||
|
|
||
|
This module relies on the SWI-Prolog tracer hooks. It modifies these
|
||
|
hooks and collects the results, after which it restores the debugging
|
||
|
environment. This has some limitations:
|
||
|
|
||
|
* The performance degrades significantly (about 10 times)
|
||
|
* It is not possible to use the debugger using coverage analysis
|
||
|
* The cover analysis tool is currently not thread-safe.
|
||
|
|
||
|
The result is represented as a list of clause-references. As the
|
||
|
references to clauses of dynamic predicates cannot be guaranteed, these
|
||
|
are omitted from the result.
|
||
|
|
||
|
@bug Relies heavily on SWI-Prolog internals. We have considered using
|
||
|
a meta-interpreter for this purpose, but it is nearly impossible
|
||
|
to do 100% complete meta-interpretation of Prolog. Example
|
||
|
problem areas include handling cuts in control-structures
|
||
|
and calls from non-interpreted meta-predicates.
|
||
|
*/
|
||
|
|
||
|
|
||
|
:- dynamic
|
||
|
entered/1, % clauses entered
|
||
|
exited/1. % clauses completed
|
||
|
|
||
|
:- module_transparent
|
||
|
covering/1,
|
||
|
covering/4.
|
||
|
|
||
|
%% show_coverage(Goal)
|
||
|
%
|
||
|
% Report on coverage by Goal
|
||
|
|
||
|
show_coverage(Goal) :-
|
||
|
covered_clauses(Goal, Result, Succeeded, Failed),
|
||
|
file_coverage(Succeeded, Failed),
|
||
|
return(Result).
|
||
|
|
||
|
return(true).
|
||
|
return(fail) :- !, fail.
|
||
|
return(error(E)) :-
|
||
|
throw(E).
|
||
|
|
||
|
%% covered_clauses(:Goal, -Result, -Succeeded, -Failed) is det.
|
||
|
%
|
||
|
% Run Goal as once/1. Unify Result with one of =true=, =fail= or
|
||
|
% error(Error).
|
||
|
%
|
||
|
% @param Succeeded Ordered set of succeeded clauses
|
||
|
% @param Failed Ordered set of clauses that are entered but
|
||
|
% never succeeded.
|
||
|
|
||
|
covered_clauses(Goal, Result, Succeeded, Failed) :-
|
||
|
asserta(user:prolog_trace_interception(Port, Frame, _, continue) :-
|
||
|
prolog_cover:assert_cover(Port, Frame), Ref),
|
||
|
port_mask([unify,exit], Mask),
|
||
|
'$visible'(V, Mask),
|
||
|
'$leash'(L, Mask),
|
||
|
trace,
|
||
|
call_with_result(Goal, Result),
|
||
|
set_prolog_flag(debug, false),
|
||
|
covered(Ref, V, L, Succeeded, Failed).
|
||
|
|
||
|
%% call_with_result(:Goal, -Result) is det.
|
||
|
%
|
||
|
% Run Goal as once/1. Unify Result with one of =true=, =fail= or
|
||
|
% error(Error).
|
||
|
|
||
|
call_with_result(Goal, Result) :-
|
||
|
( catch(Goal, E, true)
|
||
|
-> ( var(E)
|
||
|
-> Result = true
|
||
|
; Result = error(E)
|
||
|
)
|
||
|
; Result = false
|
||
|
).
|
||
|
|
||
|
port_mask([], 0).
|
||
|
port_mask([H|T], Mask) :-
|
||
|
port_mask(T, M0),
|
||
|
'$syspreds':'$port_bit'(H, Bit), % Private stuff
|
||
|
Mask is M0 \/ Bit.
|
||
|
|
||
|
%% assert_cover(+Port, +Frame) is det.
|
||
|
%
|
||
|
% Assert coverage of the current clause. We monitor two ports: the
|
||
|
% _unify_ port to see which clauses we entered, and the _exit_
|
||
|
% port to see which completed successfully.
|
||
|
|
||
|
assert_cover(unify, Frame) :-
|
||
|
running_static_pred(Frame),
|
||
|
prolog_frame_attribute(Frame, clause, Cl), !,
|
||
|
assert_entered(Cl).
|
||
|
assert_cover(exit, Frame) :-
|
||
|
running_static_pred(Frame),
|
||
|
prolog_frame_attribute(Frame, clause, Cl), !,
|
||
|
assert_exited(Cl).
|
||
|
assert_cover(_, _).
|
||
|
|
||
|
%% running_static_pred(+Frame) is semidet.
|
||
|
%
|
||
|
% True if Frame is not running a dynamic predicate.
|
||
|
|
||
|
running_static_pred(Frame) :-
|
||
|
prolog_frame_attribute(Frame, goal, Goal),
|
||
|
\+ predicate_property(Goal, dynamic).
|
||
|
|
||
|
%% assert_entered(+Ref) is det.
|
||
|
%% assert_exited(+Ref) is det.
|
||
|
%
|
||
|
% Add Ref to the set of entered or exited clauses.
|
||
|
|
||
|
assert_entered(Cl) :-
|
||
|
entered(Cl), !.
|
||
|
assert_entered(Cl) :-
|
||
|
assert(entered(Cl)).
|
||
|
|
||
|
assert_exited(Cl) :-
|
||
|
exited(Cl), !.
|
||
|
assert_exited(Cl) :-
|
||
|
assert(exited(Cl)).
|
||
|
|
||
|
%% covered(+Ref, +VisibleMask, +LeashMask, -Succeeded, -Failed) is det.
|
||
|
%
|
||
|
% Restore state and collect failed and succeeded clauses.
|
||
|
|
||
|
covered(Ref, V, L, Succeeded, Failed) :-
|
||
|
'$visible'(_, V),
|
||
|
'$leash'(_, L),
|
||
|
erase(Ref),
|
||
|
findall(Cl, (entered(Cl), \+exited(Cl)), Failed0),
|
||
|
findall(Cl, retract(exited(Cl)), Succeeded0),
|
||
|
retractall(entered(Cl)),
|
||
|
sort(Failed0, Failed),
|
||
|
sort(Succeeded0, Succeeded).
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* REPORTING *
|
||
|
*******************************/
|
||
|
|
||
|
%% file_coverage(+Succeeded, +Failed) is det.
|
||
|
%
|
||
|
% Write a report on the clauses covered organised by file to
|
||
|
% current output.
|
||
|
|
||
|
file_coverage(Succeeded, Failed) :-
|
||
|
format('~N~n~`=t~78|~n'),
|
||
|
format('~tCoverage by File~t~78|~n'),
|
||
|
format('~`=t~78|~n'),
|
||
|
format('~w~t~w~64|~t~w~72|~t~w~78|~n',
|
||
|
['File', 'Clauses', '%Cov', '%Fail']),
|
||
|
format('~`=t~78|~n'),
|
||
|
forall(source_file(File),
|
||
|
file_coverage(File, Succeeded, Failed)),
|
||
|
format('~`=t~78|~n').
|
||
|
|
||
|
file_coverage(File, Succeeded, Failed) :-
|
||
|
findall(Cl, clause_source(Cl, File, _), Clauses),
|
||
|
sort(Clauses, All),
|
||
|
( ord_intersect(All, Succeeded)
|
||
|
-> true
|
||
|
; ord_intersect(All, Failed)
|
||
|
), !,
|
||
|
ord_intersection(All, Failed, FailedInFile),
|
||
|
ord_intersection(All, Succeeded, SucceededInFile),
|
||
|
ord_subtract(All, SucceededInFile, UnCov1),
|
||
|
ord_subtract(UnCov1, FailedInFile, Uncovered),
|
||
|
length(All, AC),
|
||
|
length(Uncovered, UC),
|
||
|
length(FailedInFile, FC),
|
||
|
CP is 100-100*UC/AC,
|
||
|
FCP is 100*FC/AC,
|
||
|
summary(File, 56, SFile),
|
||
|
format('~w~t ~D~64| ~t~1f~72| ~t~1f~78|~n', [SFile, AC, CP, FCP]).
|
||
|
file_coverage(_,_,_).
|
||
|
|
||
|
|
||
|
summary(Atom, MaxLen, Summary) :-
|
||
|
atom_length(Atom, Len),
|
||
|
( Len < MaxLen
|
||
|
-> Summary = Atom
|
||
|
; SLen is MaxLen - 5,
|
||
|
sub_atom(Atom, _, SLen, 0, End),
|
||
|
atom_concat('...', End, Summary)
|
||
|
).
|
||
|
|
||
|
|
||
|
%% clause_source(+Clause, -File, -Line) is det.
|
||
|
%% clause_source(-Clause, +File, -Line) is det.
|
||
|
|
||
|
clause_source(Clause, File, Line) :-
|
||
|
nonvar(Clause), !,
|
||
|
clause_property(Clause, file(File)),
|
||
|
clause_property(Clause, line_count(Line)).
|
||
|
clause_source(Clause, File, Line) :-
|
||
|
source_file(Pred, File),
|
||
|
\+ predicate_property(Pred, multifile),
|
||
|
nth_clause(Pred, _Index, Clause),
|
||
|
clause_property(Clause, line_count(Line)).
|
||
|
clause_source(Clause, File, Line) :-
|
||
|
Pred = _:_,
|
||
|
predicate_property(Pred, multifile),
|
||
|
nth_clause(Pred, _Index, Clause),
|
||
|
clause_property(Clause, file(File)),
|
||
|
clause_property(Clause, line_count(Line)).
|