Previous Section Next Section Table of Contents Glossary Index

Chapter 4. Using Clozure CL

4.13. Code Coverage

4.13.1. Overview

In Clozure CL 1.4 and later, code coverage provides information about which paths through generated code have been executed and which haven't. For each source form, it can report one of three possible outcomes:

  • Not covered: this form was never entered.

  • Partly covered: This form was entered, and some parts were executed and some weren't.

  • Fully covered: Every bit of code generated from this form was executed.

4.13.2. Limitations

While the information gathered for coverage of generated code is complete and precise, the mapping back to source forms is of necessity heuristic, and depends a great deal on the behavior of macros and the path of the source forms through compiler transforms. Source information is not recorded for variables, which further limits the source mapping. In practice, there is often enough information scattered about a partially covered function to figure out which logical path through the code was taken and which wasn't. If that doesn't work, you can try disassembling to see which parts of the compiled code were not executed: in the disassembled code there will be references to #<CODE-NOTE [xxx] ...> where xxx is NIL if the code that follows was never executed and non-NIL if it was.

Sometimes the situation can be improved by modifying macros to try to preserve more of the input forms, rather than destructuring and rebuilding them.

Because the code coverage information is associated with compiled functions, code coverage information is not available for load-time toplevel expressions. You can work around this by creating a function and calling it. I.e. instead of

(progn
  (do-this)
  (setq that ...) ...))

do:

(defun init-this-and-that ()
  (do-this)
  (setq that ...)  ...)
(init-this-and-that)

Then you can see the coverage information in the definition of init-this-and-that.

4.13.3. Usage

In order to gather code coverage information, you first have to recompile all your code to include code coverage instrumentation. Compiling files will generate code coverage instrumentation if CCL:*COMPILE-CODE-COVERAGE* is true:

(setq ccl:*compile-code-coverage* t) 
(recompile-all-your-files)

The compilation process will be many times slower than normal, and the fasl files will be many times bigger.

When you execute functions loaded from instrumented fasl files, they will record coverage information every time they are executed. You can examine that information by calling ccl:report-coverage or ccl:coverage-statistics.

While recording coverage, you can collect incremental coverage deltas between any two points in time. You might do this while running a test suite, to record the coverage for each test, for example:

(ccl:reset-incremental-coverage)
(loop with coverage = (make-hash-table)
      for test in (tests-to-run)
      do (run-test test)
      do (setf (gethash test coverage) (ccl:get-incremental-coverage))
      finally (return coverage))

creates a hash table mapping a test to a representation of all coverage recorded while running the test. This hash table can then be passed to ccl:report-coverage, ccl:incremental-coverage-svn-matches or ccl:incremental-coverage-source-matches.

4.13.4. Functions and Variables

The following functions can be used to manage the coverage data:

[Function]

report-coverage output-file &key (tags nil) (external-format :default) (statistics t) (html t)
Generate a code coverage report

Arguments and Values:

output-file--- Pathname for the output index file.

html--- If non-nil (the default), this will generate an HTML report, consisting of an index file in output-file and, in the same directory, one html file for each instrumented source file that has been loaded in the current session.

tags--- If non-nil, this should be a hash table mapping arbitrary keys (tags) to incremental coverage deltas. The HTML report will show a list of tags, and allow selection of an arbitrary subset of them to show the coloring and statistics for coverage by that subset.

external-format--- Controls the external format of the html files.

statistics--- If non-nil (the default), a comma-separated file is generated with the summary of statistics. You can specify a filename for the statistics argument, otherwise "statistics.csv" is created in the directory of output-file. See documentation of coverage-statistics below for a description of the values in the statistics file.

Example:

If you've loaded foo.lx64fsl and bar.lx64fsl, and have run some tests, you could do

(REPORT-COVERAGE "/my/dir/coverage/report.html")
    

and this would generate report.html, foo_lisp.html and bar_lisp.html, and statistics.csv all in /my/dir/coverage/.

[Function]

RESET-COVERAGE
Resets all coverage data back to the "Not Executed" state

Description:

Resets all coverage data back to the "Not Executed" state

[Function]

CLEAR-COVERAGE
Forget about all instrumented files that have been loaded.

Description:

Gets rid of the information about which instrumented files have been loaded, so ccl:report-coverage will not report any files, and ccl:save-coverage-in-file will not save any info, until more instrumented files are loaded.

[Function]

save-coverage-in-file pathname
Save all coverage into to a file so you can restore it later.

Description:

Saves all coverage info in a file, so you can restore the coverage state later. This allows you to combine multiple runs or continue in a later session. Equivalent to (ccl:write-coverage-to-file (ccl:get-coverage) pathname).

[Function]

restore-coverage-from-file pathname
Load coverage state from a file.

Description:

Restores the coverage data previously saved with ccl:save-coverage-in-file, for the set of instrumented fasls that were loaded both at save and restore time. I.e. coverage info is only restored for files that have been loaded in this session. For example if in a previous session you had loaded "foo.lx86fsl" and then saved the coverage info, in this session you must load the same "foo.lx86fsl" before calling restore-coverage-from-file in order to retrieve the stored coverage info for "foo". Equivalent to (ccl:restore-coverage (ccl:read-coverage-from-file pathname)).

[Function]

GET-COVERAGE
Returns a snapshot of the current coverage data.

Description:

Returns a snapshot of the current coverage data. A snapshot is a copy of the current coverage state. It can be saved in a file with ccl:write-coverage-to-file, reinstated back as the current state with ccl:restore-coverage, or combined with other snapshots with ccl:combine-coverage.

[Function]

restore-coverage snapshot
Reinstalls a coverage snapshot as the current coverage state.

Description:

Reinstalls a coverage snapshot as the current coverage state.

[Function]

combine-coverage snapshots
Combines multiple coverage snapshots into one.

Description:

Takes a list of coverage snapshots and returns a new coverage snapshot representing a union of all the coverage data.

[Function]

write-coverage-to-file snapshot pathname
Save a coverage snapshot in a file.

Description:

Saves the coverage snapshot in a file. The snapshot can be loaded back with ccl:read-coverage-from-file or loaded and restored with ccl:restore-coverage-from-file. Note that the file created is actually a lisp source file and can be compiled for faster loading.

[Function]

read-coverage-from-file pathname
Return the coverage snapshot saved in a file.

Description:

Returns the snapshot saved in pathname. Doesn't affect the current coverage state. pathname can be the file previously created with ccl:write-coverage-to-file or ccl:save-coverage-in-file, or it can be the name of the fasl created from compiling such a file.

[Function]

coverage-statistics
Returns a sequence of ccl:coverage-statistics objects, one per source file.

Description:

Returns a sequence of ccl:coverage-statistics objects, one for each source file, containing the same information as that written to the statistics file by ccl:report-coverage. The following accessors are defined for ccl:coverage-statistics objects:

coverage-source-file

the name of the source file corresponding to this information

coverage-expressions-total

the total number of expressions

coverage-expressions-entered

the number of source expressions that have been entered (i.e. at least partially covered)

coverage-expressions-covered

the number of source expressions that were fully covered

coverage-unreached-branches

the number of conditionals with one branch taken and one not taken

coverage-code-forms-total

the total number of code forms. A code form is an expression in the final stage of compilation, after all macroexpansion and compiler transforms and simplification

coverage-code-forms-covered

the number of code forms that have been entered

coverage-functions-total

the total number of functions

coverage-functions-fully-covered

the number of functions that were fully covered

coverage-functions-partly-covered

the number of functions that were partly covered

coverage-functions-not-entered

the number of functions never entered

[Function]

reset-incremental-coverage
Reset incremental coverage.

Description:

Marks a starting point for recording incremental coverage. Note that calling this function does not affect regular coverage data (whereas calling ccl:reset-coverage resets incremental coverage as well).

[Function]

get-incremental-coverage &key (reset t)
Returns the delta of coverage since the last incremental reset.

Description:

Returns the delta of coverage since the last reset of incremental coverage. If reset is true (the default), it also resets incremental coverage now, so that the next call to get-incremental-coverage will return the delta from this point.

Incremental coverage deltas are represented differently than the full coverage snapshots returned by functions such as ccl:get-coverage. Incremental coverage uses an abbreviated format and is missing some of the information in a full snapshot, and therefore cannot be passed to functions documented to accept a snapshot, only to functions specifically documented to accept incremental coverage deltas.

[Function]

incremental-coverage-source-matches collection sources
Find incremental coverage deltas intersecting source regions.

Arguments and Values:

collection--- A hash table mapping arbitrary keys to incremental coverage deltas, or a sequence of incremental coverage deltas.

sources--- A list of pathnames and/or source-notes, the latter representing a range within a file.

Description:

Given a hash table collection whose values are incremental coverage deltas, return a list of all keys corresponding to those deltas that intersect any region in sources.

For example if the deltas represent tests, then the returned value is a list of all tests that cover some part of the source regions.

collection can also be a sequence of deltas, in which case a subsequence of matching deltas is returned. In particular you can test whether any particular delta intersects the sources by passing it in as a single-element list.

[Function]

incremental-coverage-svn-matches collection &key (directory (current-directory)) (revision :base)
Find incremental coverage deltas matching changes from a particular subversion revision.

Arguments and Values:

collection--- A hash table mapping arbitrary keys to incremental coverage deltas, or a sequence of incremental coverage deltas.

directory--- The pathname of a subversion working directory.

revision--- The revision to compare to the working directory, an integer or another value whose printed representation is suitable for passing as the --revision argument to svn.

Description:

Given a hash table collection whose values are incremental coverage deltas, return a list of all keys corresponding to those deltas that intersect any changed source in directory since revision revision in subversion.

For example if the deltas represent tests, then the returned value is a list of all tests that might be affected by the changes.

collection can also be a sequence of deltas, in which case a subsequence of matching deltas is returned. In particular you can test whether any particular delta is affected by the changes by passing it in as a single-element list.

[Variable]

*compile-code-coverage*
When true, instrument functions being compiled to collect code coverage information.

Description:

This variable controls whether functions are instrumented for code coverage. Files compiled while this variable is true will contain code coverage instrumentation.

[Macro]

without-compiling-code-coverage
Don't record code coverage for forms within the body.

Description:

This macro arranges so that body doesn't record internal details of code coverage. It will be considered totally covered if it's entered at all. The Common Lisp macros ASSERT and CHECK-TYPE use this macro.

4.13.5. Interpreting Code Coloring

The output of ccl:report-coverage consists of formatted source code, with coverage indicated by coloring. Four colors are used: dark green for forms that compiled to code in which every single instruction was executed, light green for forms that have been entered but weren't totally covered, red for forms that were never entered, and the page background color for toplevel forms that weren't instrumented.

The source coloring is applied from outside in. So for example if you have

(outer-form ... (inner-form ...) ...)
  

first the whole outer form is painted with whatever color expresses the outer form coverage, and then the inner form color is replaced with whatever color expresses the inner form coverage. One consequence of this approach is that every part of the outer form that is not specifically inside some executable inner form will have the outer form's coverage color. If the syntax of outer form involves some non-executable forms, or forms that do not have coverage info of their own for whatever reason, then they will just inherit the color of the outer form, because they don't get repainted with a color of their own.

One case in which this approach can be confusing is in the case of symbols. As noted in the Limitations section, coverage information is not recorded for variables; hence the coloring of a variable does not convey information about whether the variable was evaluated or not -- that information is not available, and the variable just inherits the color of the form that contains it.


Previous Section Next Section Table of Contents Glossary Index