View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2006-2017, University of Amsterdam
    7                              VU University Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(prolog_cover,
   37          [ show_coverage/1,            % :Goal
   38            show_coverage/2
   39          ]).   40:- use_module(library(ordsets)).   41:- use_module(library(apply)).   42
   43:- set_prolog_flag(generate_debug_info, false).

Clause cover analysis

The purpose of this module is to find which part of the program has been used 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 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.
To be done
-
Provide detailed information organised by predicate. Possibly annotate the source with coverage information. */
   74:- dynamic
   75    entered/1,                      % clauses entered
   76    exited/1.                       % clauses completed
   77
   78:- meta_predicate
   79    show_coverage(0),
   80    show_coverage(0,+).
 show_coverage(:Goal) is semidet
 show_coverage(:Goal, +Modules:list(atom)) is semidet
Report on coverage by Goal. Goal is executed as in once/1. Report the details of the uncovered clauses for each module in the list Modules
   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).
 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.
  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(_, _).
 running_static_pred(+Frame) is semidet
True if Frame is not running a dynamic predicate.
  144running_static_pred(Frame) :-
  145    prolog_frame_attribute(Frame, goal, Goal),
  146    \+ predicate_property(Goal, dynamic).
 assert_entered(+Ref) is det
 assert_exited(+Ref) is det
Add Ref to the set of entered or exited clauses.
  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)).
 covered(+Ref, +VisibleMask, +LeashMask, -Succeeded, -Failed) is det
Restore state and collect failed and succeeded clauses.
  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                 /*******************************
  178                 *           REPORTING          *
  179                 *******************************/
 file_coverage(+Succeeded, +Failed, +Modules) is det
Write a report on the clauses covered organised by file to current output. Show detailed information about the non-coverered clauses defined in the modules Modules.
  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    %if doc_collect (from pldoc) is active, pldoc comments are recorded as
  212    % clauses but we do not want to count them in the statistics
  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    %We do not want to count clauses such as :-use_module(_) in the statistics
  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    ).
 clause_source(+Clause, -File, -Line) is det
clause_source(-Clause, +File, -Line) is det
  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)).
 detailed_report(+Uncovered:list(clause), +Modules:list(atom)) is det
  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])