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