Skip to content

Code coverage tool (.cov)

Overview

.cov.run returns code coverage as a table of the number of times each logical line, line in a loop, and branch in a conditional was run. Functions rewritten to measure coverage run much slower than normal. This overhead is dependent only on the volume of code being run (i.e. the number of function calls, lines, and branches). Long-running operations which do not execute significant amounts of code, such as large disk operations, will not take longer. Try running coverage on a small dataset first to gauge performance, as runtimes can be significantly larger than normal.

Basic usage

.cov.run[.example.myFunc; 1 2 3; `functions`namespaces!(`.example.myFunc; `.example.i)]

Output

Coverage is output as a table showing how many times each logical line and 'block' ran for each function passed to .cov.run. A block is any line in a loop or conditional. The iterations column indicates how many times the function was run. lineIterations and blockIterations indicate how many times each line and block were run. The ranges of each line and block are given in the lines and blocks columns, as indices into the text in the text column. Only lambdas and projections (functions having types 100h or 104h) will be included in the output. Coverage will discard any compositions or functions bound to adverbs.

Examples

In this example, neither the OR function nor the else-branch in the conditional were ever executed.

.example.foo: {
    AND:{x and y};
    OR:{x or y};
    $[  AND[1b;1b];
        AND[0b;1b];
        OR[0b;0b]]
    };

.cov.run[.example.foo; enlist[::]; ``functions!``.example.foo]
name         iterations lineIterations blockIterations lines                         blocks            text                                                                                                          
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
.example.foo 1          2 0 1 1 1      1 1 0           11 18 29 35 6  19 25 36 42 96 46 56 66 76 86 95 "{\n    AND:{x and y};\n    OR:{x or y};\n    $[  AND[1b;1b];\n        AND[0b;1b];\n        OR[0b;0b]]\n    }"

In this example, each line in the do loop, and each clause in the conditionals is reported as a separate item in the blocks column.

foo: {
    do[10;
        quux: $[bar[]; 100; 200]];
    show quux * 5;
    : quux;
    };

bar: {
    num: rand 3;
    : $[num ~ 2; 1b; 0b];
    };

.cov.run[foo; enlist [::]; enlist[`functions]!enlist `foo`bar]
name iterations lineIterations blockIterations lines              blocks                         text                                                                                       
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
bar  10         10 10          10 1 9          (6 17;23 43)       (27 34;36 38;40 42)            "{\n    num: rand 3;\n    : $[num ~ 2; 1b; 0b];\n    }"                                    
foo  1          1 1 1          1 10 10 1 9     (6 46;52 65;71 77) (9 11;21 45;29 34;36 39;41 44) "{\n    do[10;\n        quux: $[bar[]; 100; 200]];\n    show quux * 5;\n    : quux;\n    }"

If .cov.run is invoked on a function with no name bound to it, which is the case for functions defined using set, or when using kdb+ versions \<3.6, then this initial function will show up in the output as .cov.initial.

`..foo set {$[x + y; 1b; 0b]};
.cov.run[foo; 1 2; ``functions!``foo];
name         iterations lineIterations blockIterations lines blocks            text                
---------------------------------------------------------------------------------------------------
.cov.initial 1          1              1 1 0           1 17  3  8  10 12 14 16 "{$[x + y; 1b; 0b]}"
foo          0          0              0 0 0           1 17  3  8  10 12 14 16 "{$[x + y; 1b; 0b]}"

Format

There are two functions for formatting the output of the coverage tool:

  • .cov.format.go returns the formatted output as a list of lines.
  • .cov.format.display will write the results to standard out, and as such, is not limited by the console size restrictions.

Both functions display the total % covered for all functions instrumented, the number of functions without complete coverage, and the body of those functions with unrun sections wrapped in "<<<" and ">>>". Any line containing unexecuted code will be prefixed with an X. The percentage next to the function name indicates how much of the function executed. It measures what percentage of characters covered by the lines and blocks ranges were run.

Examples

To format the results visually, pass the table to .cov.format.go, or .cov.format.display.
.cov.format.display uses STDOUT, so the output will not be truncated if it exceeds the console limits.

foo: {[x]
    x: not x;
    : $[ x;
        $[  0b;
            $[1b; 0b; 0b];
            $[1b; 0b; 0b]];
        $[  1b;
            $[1b; 0b; 0b];
            $[1b; 0b; 0b]]];
    : count
        1 2 3;
    };

.cov.format.display .cov.run[foo; enlist 0b; ``functions!``foo];
45% coverage
1/1 functions have incomplete coverage

foo 45%

  {[x]
      x: not x;
      : $[ x;
          $[  0b;
X             <<<$[1b; 0b; 0b]>>>;
X             $[1b; 0b; <<<0b>>>]];
X         <<<$[  1b;
X             $[1b; 0b; 0b];
X             $[1b; 0b; 0b]]>>>];
X     <<<: count
X         1 2 3>>>;
      }

.cov.format.display

Show the coverage results visually. This writes to -1 instead of returning strings, to avoid being limited by the console size

Parameter:

Name Type Description
results table A coverage results table

Returns:

Type Description
Null

Example: Format the coverage result, printing to STDOUT

 q) myFunc: {
     {{x*y}/[x]}
     };
 q) .cov.format.display .cov.run[myFunc; enlist 1 2 3; (enlist `functions)!(enlist `myFunc)]
 18% coverage
 1/1 functions have incomplete coverage

 myFunc 18%

   {
 X     {<<<{x*y}/[x]>>>}
       }
 ::

.cov.format.go

Format the coverage results visually

Parameter:

Name Type Description
results table A coverage results table

Returns:

Type Description
String[]

Example: Format the coverage result, returning the result as strings

 q) myFunc: {
     {{x*y}/[x]}
     };
 q) .cov.format.go .cov.run[myFunc; enlist 1 2 3; (enlist `functions)!(enlist `myFunc)]
 "18% coverage"
 "1/1 functions have incomplete coverage"
 ""
 "myFunc 18%"
 ""
 "  {"
 "X     {<<<{x*y}/[x]>>>}"
 "      }"

.cov.run

This evaluates a function while recording coverage

Parameters:

Name Type Description
function fn The function to run
parameters *[] The list of arguments for the function. If there is only one argument, it must be enlisted.
settings dict
settings.context symbol The context to run the function in. The root context (`.) by default.
settings.functions symbol | symbol[] The names of the functions to instrument. Unqualified functions are assumed to be in the root context
settings.ignoreFunctions symbol | symbol[] The names of the functions to ignore, even if their namespace is instrumented. Unqualified functions are assumed to be in the root context
settings.namespaces symbol | symbol[] The names of the namespaces to instrument
settings.ignoreNamespaces symbol | symbol[] The names of the namespaces to ignore, even if their parent namespace is instrumented

Returns:

Type Description
table The coverage data