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