On 7/9/2004 4:57 PM, Paul Johnson wrote:
> On Fri, Jul 09, 2004 at 12:10:52PM -0500, Pete Krawczyk wrote:
> 
>> Consider [code with unreachable path] Devel::Cover will always see that as
>> a partial test, and never a full test: Is that a bug, then?

That's for you to decide. The lack of coverage serves to bring it to your
attention so you can ask (and answer) that very question. As others have said,
coverage is just a tool. The amount of coverage you get does not (by itself) say
anything at all about the correctness of your code.

> There is some initial code in place in Devel::Cover to handle this situation,
>  [...] Michael Carman is looking at making this more usable.

I am, but I hit a roadblock with compound conditionals.

A little background for those unfamiliar with the guts of Devel::Cover: The
.uncoverable file captures analysis data at the same level of granularity as
Devel::Cover uses internally -- each individual operation.

e.g. the expression '$a && $b || $c' is actually seen as two completely separate
conditions:

           L       op     R
    -------------------------
    1)     $a      &&     $b
    2)  $a && $b   ||     $c

The truth table you see when you look at the condition coverage report (or
generate a "unified" report) is rather crudely and painfully calculated from
these primitives.

The problem is that for compound expressions, the unreachable paths are really
part of the composite expression and are best analyzed there. e.g. you can't
drive the "1 0 0" combination.

The "simple" solution is probably to add a new type of entry to .uncoverable for
 composite expressions that tells us which row of a truth table can't be reached.

It occurred to me that if I'm missing a path in a composite expression I should
be missing coverage for one (or more) of the simple expressions that feed into
it. If I were really clever I could use that to propagate coverage analysis data
when building a truth table. (The reverse mapping -- from a truth table to the
primitive ops that build it -- is much harder.)

This got me thinking about something that had tickled my thoughts before. I'm
afraid that I have uncovered a serious flaw/limitation in Devel::Cover. Take the
expression '$a && $b || $c'
Its truth table is:

     a b c | Z
     ---------
 0)  0 X 0 | 0
 1)  0 X 1 | 1
 2)  1 0 0 | 0
 3)  1 0 1 | 1
 4)  1 1 X | 1

Suppose for the moment that all rows except row 2 (1 0 0) can be driven. The
following (contrived) example does this:

#!/usr/bin/perl
use strict;
use warnings;

for my $a (0 .. 1) {
    for my $b (0 .. 1) {
        for my $c (0 .. 1) {
            foo($a, $b, $c);
        }
    }
}

sub foo {
    my ($a, $b, $c) = @_;
    $a = 0 unless ($b || $c); # Make '1 0 0' unreachable

    print "$a $b $c ";
    if ($a && $b || $c) {
        print "T\n";
    }
    else {
        print "F\n";
    }
}

__END__

Now run that with -MDevel::Cover and generate a report. Shows full coverage,
doesn't it? The reason is that to Devel::Cover, the "if ($a && $b || $c)" line
has two *independent* conditions:

    a b | Y       Y c | Z
    -------       -------
 0) 0 X | 0    0) 0 0 | 0
 1) 1 0 | 0    1) 0 1 | 1
 2) 1 1 | 1    2) 1 X | 1

Independently, all rows of both tables can be covered. What can't happen is
driving row 1 of table 1 at the same time as row 0 of table 2 (these are the
states that correspond to the "1 0 0" state in the composite table).

The truth table showing "1 0 0" as covered is my fault. I wrote the portion of
the reporting back end that builds the table from the simple operations. I
thought that I could calculate the coverage for a composite row from the
coverage of the simple rows that feed into it. I can see now that that
assumption was wrong.

Unfortunately, the information necessary to correct this isn't available.
Devel::Cover would have to either track condition coverage at an expression
level (instead of an operation level) or keep track of which combinations of
simple coverage were witnessed.

-mjc

Reply via email to