Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python unittest branch coverage seems to miss executed generator in zip

I don't quite understand what Python's branch coverage stats are trying to tell me. Given code of the form

def f(a, b):
    c = (i for i in a)
    d = (j for j in b)  # Line of interest
    return dict(zip(c, d))

print(f(['a', 'b'], [1, 2]))

which is imported during unit testing, Python's standard branch coverage tells me that the # Line of interest line is only partially covered (n->-n on the CLI output, "n ↛ exit [?]" in the pretty html report).

The returned dict is clearly printed out, and executing with empty lists still yields the uncovered line.

Am I misinterpreting the coverage output? Does this smell like a bug?

Python 3.5.1, Coverage 4.0.3

like image 999
Paul Weaver Avatar asked Feb 10 '16 14:02

Paul Weaver


1 Answers

I've investigated this further, and I don't think it is a bug in coverage. When the first generator (c) terminates, zip() efficiently doesn't bother collecting any more values from the second generator (d), so branch coverage doesn't track d as run to completion, even though every element is actually extracted.

If you instead write:

def f(a, b):
    c = (i for i in a)
    d = tuple(j for j in b)  # Line of interest
    return dict(zip(c, d))

print(f(['a', 'b'], [1, 2]))

as one would expect the second generator is run to completion and coverage is happy, even though the output is identical.

I don't think there's a simple way around this, even if you write the generator expression out as a generator function containing the same for loop, you get a (slightly clearer) error that execution never jumped to function exit.

I think this is just a limitation of coverage and the exit conditions of generators, as it can't know whether a generator is supposed to exit, so it flags the uncovered case.

like image 100
Paul Weaver Avatar answered Nov 15 '22 00:11

Paul Weaver