35
36:- module(prolog_cover,
37 [ show_coverage/1, 38 show_coverage/2
39 ]). 40:- autoload(library(apply),[exclude/3,maplist/3,include/3,maplist/2]). 41:- autoload(library(edinburgh),[nodebug/0]). 42:- autoload(library(ordsets),
43 [ord_intersect/2,ord_intersection/3,ord_subtract/3]). 44:- autoload(library(pairs),[group_pairs_by_key/2]). 45
46:- set_prolog_flag(generate_debug_info, false). 47
75
76
77:- dynamic
78 entered/1, 79 exited/1. 80
81:- meta_predicate
82 show_coverage(0),
83 show_coverage(0,+). 84
91
92show_coverage(Goal) :-
93 show_coverage(Goal, []).
94show_coverage(Goal, Modules):-
95 setup_call_cleanup(
96 setup_trace(State),
97 once(Goal),
98 cleanup_trace(State, Modules)).
99
100setup_trace(state(Visible, Leash, Ref)) :-
101 set_prolog_flag(coverage_analysis, true),
102 asserta((user:prolog_trace_interception(Port, Frame, _, continue) :-
103 prolog_cover:assert_cover(Port, Frame)), Ref),
104 port_mask([unify,exit], Mask),
105 '$visible'(Visible, Mask),
106 '$leash'(Leash, Mask),
107 trace.
108
109port_mask([], 0).
110port_mask([H|T], Mask) :-
111 port_mask(T, M0),
112 '$syspreds':port_name(H, Bit),
113 Mask is M0 \/ Bit.
114
115cleanup_trace(state(Visible, Leash, Ref), Modules) :-
116 nodebug,
117 '$visible'(_, Visible),
118 '$leash'(_, Leash),
119 erase(Ref),
120 set_prolog_flag(coverage_analysis, false),
121 covered(Succeeded, Failed),
122 file_coverage(Succeeded, Failed, Modules).
123
124
130
131assert_cover(unify, Frame) :-
132 running_static_pred(Frame),
133 prolog_frame_attribute(Frame, clause, Cl),
134 !,
135 assert_entered(Cl).
136assert_cover(exit, Frame) :-
137 running_static_pred(Frame),
138 prolog_frame_attribute(Frame, clause, Cl),
139 !,
140 assert_exited(Cl).
141assert_cover(_, _).
142
146
147running_static_pred(Frame) :-
148 prolog_frame_attribute(Frame, goal, Goal),
149 \+ predicate_property(Goal, dynamic).
150
155
156assert_entered(Cl) :-
157 entered(Cl),
158 !.
159assert_entered(Cl) :-
160 assert(entered(Cl)).
161
162assert_exited(Cl) :-
163 exited(Cl),
164 !.
165assert_exited(Cl) :-
166 assert(exited(Cl)).
167
171
172covered(Succeeded, Failed) :-
173 findall(Cl, (entered(Cl), \+exited(Cl)), Failed0),
174 findall(Cl, retract(exited(Cl)), Succeeded0),
175 retractall(entered(Cl)),
176 sort(Failed0, Failed),
177 sort(Succeeded0, Succeeded).
178
179
180 183
189
190file_coverage(Succeeded, Failed, Modules) :-
191 format('~N~n~`=t~78|~n'),
192 format('~tCoverage by File~t~78|~n'),
193 format('~`=t~78|~n'),
194 format('~w~t~w~64|~t~w~72|~t~w~78|~n',
195 ['File', 'Clauses', '%Cov', '%Fail']),
196 format('~`=t~78|~n'),
197 forall(source_file(File),
198 file_coverage(File, Succeeded, Failed, Modules)),
199 format('~`=t~78|~n').
200
201file_coverage(File, Succeeded, Failed, Modules) :-
202 findall(Cl, clause_source(Cl, File, _), Clauses),
203 sort(Clauses, All),
204 ( ord_intersect(All, Succeeded)
205 -> true
206 ; ord_intersect(All, Failed)
207 ),
208 !,
209 ord_intersection(All, Failed, FailedInFile),
210 ord_intersection(All, Succeeded, SucceededInFile),
211 ord_subtract(All, SucceededInFile, UnCov1),
212 ord_subtract(UnCov1, FailedInFile, Uncovered),
213
214 215 216 exclude(is_pldoc, All, All_wo_pldoc),
217 exclude(is_pldoc, Uncovered, Uncovered_wo_pldoc),
218 exclude(is_pldoc, FailedInFile, Failed_wo_pldoc),
219
220 221 exclude(is_system_clause, All_wo_pldoc, All_wo_system),
222 exclude(is_system_clause, Uncovered_wo_pldoc, Uncovered_wo_system),
223 exclude(is_system_clause, Failed_wo_pldoc, Failed_wo_system),
224
225 length(All_wo_system, AC),
226 length(Uncovered_wo_system, UC),
227 length(Failed_wo_system, FC),
228
229 CP is 100-100*UC/AC,
230 FCP is 100*FC/AC,
231 summary(File, 56, SFile),
232 format('~w~t ~D~64| ~t~1f~72| ~t~1f~78|~n', [SFile, AC, CP, FCP]),
233 detailed_report(Uncovered_wo_system, Modules).
234file_coverage(_,_,_,_).
235
236
237is_system_clause(Clause) :-
238 clause_name(Clause, Name),
239 Name = system:_.
240
241is_pldoc(Clause) :-
242 clause_name(Clause, _Module:Name2/_Arity),
243 pldoc_predicate(Name2).
244
245pldoc_predicate('$pldoc').
246pldoc_predicate('$mode').
247pldoc_predicate('$pred_option').
248
249summary(Atom, MaxLen, Summary) :-
250 atom_length(Atom, Len),
251 ( Len < MaxLen
252 -> Summary = Atom
253 ; SLen is MaxLen - 5,
254 sub_atom(Atom, _, SLen, 0, End),
255 atom_concat('...', End, Summary)
256 ).
257
258
261
262clause_source(Clause, File, Line) :-
263 nonvar(Clause),
264 !,
265 clause_property(Clause, file(File)),
266 clause_property(Clause, line_count(Line)).
267clause_source(Clause, File, Line) :-
268 Pred = _:_,
269 source_file(Pred, File),
270 \+ predicate_property(Pred, multifile),
271 nth_clause(Pred, _Index, Clause),
272 clause_property(Clause, line_count(Line)).
273clause_source(Clause, File, Line) :-
274 Pred = _:_,
275 predicate_property(Pred, multifile),
276 nth_clause(Pred, _Index, Clause),
277 clause_property(Clause, file(File)),
278 clause_property(Clause, line_count(Line)).
279
281
282detailed_report(Uncovered, Modules):-
283 maplist(clause_line_pair, Uncovered, Pairs),
284 include(pair_in_modules(Modules), Pairs, Pairs_in_modules),
285 ( Pairs_in_modules \== []
286 -> sort(Pairs_in_modules, Pairs_sorted),
287 group_pairs_by_key(Pairs_sorted, Compact_pairs),
288 nl,
289 format('~2|Clauses not covered from modules ~p~n', [Modules]),
290 format('~4|Predicate ~59|Clauses at lines ~n', []),
291 maplist(print_clause_line, Compact_pairs),
292 nl
293 ; true
294 ).
295
296pair_in_modules(Modules,(Module:_Name)-_Line):-
297 memberchk(Module, Modules).
298
299clause_line_pair(Clause, Name-Line):-
300 clause_property(Clause, line_count(Line)),
301 clause_name(Clause, Name).
302
303clause_name(Clause,Name):-
304 clause(Module:Head, _, Clause),
305 functor(Head,F,A),
306 Name=Module:F/A.
307
308print_clause_line((Module:Name/Arity)-Lines):-
309 term_to_atom(Module:Name, Complete_name),
310 summary(Complete_name, 54, SName),
311 format('~4|~w~t~59|~p~n', [SName/Arity, Lines])