[internal] Generator-expression exit arcs aren't always filtered out of executed_branch_arcs · Issue #1852 · nedbat/coveragepy (original) (raw)

Consider this code snippet:

import sys

def foo(args): if any(arg.startswith("-") for arg in args): print("option found")

foo(sys.argv)

(Any use of a generator expression in the controlling condition of an if should provoke the problem, albeit I have not tried it with a multi-line controlling condition.)

If I run this snippet as coverage run --branch test.py or coverage run --branch test.py nonoption it produces this arc table:

sqlite3 -table .coverage 'select fromno, tono from arc order by fromno, tono'
fromno tono
-4 4
-3 4
-1 1
1 3
3 7
4 -4
4 -3
4 4
7 -1

If I run it as coverage run -branch test.py -flag I get this arc table instead:

fromno tono
-4 4
-3 4
-1 1
1 3
3 7
4 4
4 5
5 -3
7 -1

The problem is with the arc (4, -4), which is (as far as I can tell) the exit arc for the generator expression. The analysis code is supposed to be filtering this arc out as uninteresting, but either it only does this for missing_branch_arcs() and not for executed_branch_arcs(), or there's a bug in its handling of this construct. When the input is the first of the above two arc tables, executed_branch_arcs()[4] is {-4, -3, 4}.

The HTML report generator doesn't care about this because it only looks at missing arcs, but the LCOV report generator looks at both missing and executed arcs and trips over the (4, -4) edge. With the code in #1851, the difference in coverage lcov output for the above two arc tables is

--- coverage.1.lcov 2024-09-12 14:19:43.422893468 -0400 +++ coverage.2.lcov 2024-09-12 14:20:11.648031969 -0400 @@ -2,17 +2,16 @@ DA:1,1 DA:3,1 DA:4,1 -DA:5,0 +DA:5,1 DA:7,1 LF:5 -LH:4 +LH:5 FN:3,5,foo FNDA:1,foo FNF:1 FNH:1 -BRDA:4,0,to line 5,0 -BRDA:4,0,to exit 3,1 -BRDA:4,0,to exit 4,1 +BRDA:4,0,to line 5,1 +BRDA:4,0,to exit,0 BRF:2 BRH:1 end_of_record

The BRDA: lines we should be generating are

-BRDA:4,0,to line 5,0 -BRDA:4,0,to exit,1 +BRDA:4,0,to line 5,1 +BRDA:4,0,to exit,0

but I do not see any way to make that happen from inside lcovreport.py.