Issue #198 wontfix

continue marked as not covered

Anonymous created an issue

It seems that if: - there is an if statement with two predicates separated by an or operator - the second (last?) predicate is not always "reached" (due to the fact that the first was always evaluated to True) - inside the if, there is only a continue statement (or a pass followed by continue)

the continue statement is marked as missing which is not true. Especially in a "if True or True:" case.

continue.py: {{{

!python

for i in (1, 2, 3, 4): if True or False: continue # Missing

for i in (1, 2, 3, 4): if False or True: continue # Run

for i in (1, 2, 3, 4): if True or True: continue # Missing

for i in (1, 2, 3, 4): if True or True: print "test" # Run continue # Run

print "End" }}}

$ python --version Python 2.7.2+

Run as: $ coverage run continue.py && coverage html

Comments (13)

  1. Jean-Tiare Le Bigot

    I ran into a similar use case with this snippet from ddbmock project

    for fieldname, condition in expected.iteritems():
        if u'Exists' in condition and not condition[u'Exists']:
            if fieldname in self:
                raise ConditionalCheckFailedException(
                    "Field '{}' should not exist".format(fieldname))
            # *IS* executed but coverage bug
            continue  # pragma: no cover
        if fieldname not in self:
    

    I used "# pragma: no cover" as workaround for the stats after checking it is actually reached.

    This is annoying but really not critical to me.

    Thanks for this lib btw.

  2. Daniel Black

    Similar to Jean-Tiare's example here's an isolated case:

    continue is reached with example(4)

    def example(a):
        for x in [1,2,3,4]:
            if x >= a:
                if x == 3:
                    return
                continue
            b = a
    
    example(1)
    example(3)
    example(4)
    
  3. Ronald Oussoren

    And likewise for this code:

    dct = {
        'a': {
            'src': 'B',
            'val': 'A',
        },
        'b': {
            'src': 'B',
            'val': 'B',
        },
        'c': {
            'src': 'C',
            'val': 'C',
        }
    }
    
    for k in ('a', 'c', 'd'):
        s = dct.get(k, {}).get('src')
        v = dct.get(k, {}).get('val')
    
        if s == 'A' or v == 'A':
            pass
    
        else:
            continue
    
        print "got",k
    

    The continue is reached for 'c' and 'd'.

  4. Ned Batchelder repo owner

    Unfortunately, this is not a bug in coverage.py. All of these examples are cases where CPython's peephole optimizer replaces a jump to a continue with a jump to the top of the loop, so the continue line is never actually executed, even though its effects are seen.

    If you believe as I do that it would be useful to have a way to disable the peephole optimizer so that these sorts of analyses would give useful results, comment on this CPython ticket: http://bugs.python.org/issue2506 "Add mechanism to disable optimizations".

  5. Marc Schlaich

    Ned Batchelder Actually I find the behavior of Python in such a case pretty good regarding code coverage.

    Consider

    def test(a, b):
        for _ in xrange(5):
            if a or b:
               continue
    

    If you run this with True, False it shows the continue as not covered. I think this is a good thing because you have not covered the case False, True so it helps you to (dis)cover all possible code paths.

    IMO coverage should apply this even to all if conditions. If a if condition is only partly evaluated it should count as not covered (not sure if this is possible, though).

  6. Ned Batchelder repo owner

    Marc Schlaich I appreciate your support for the current behavior of coverage.py in this case, but it's pretty hard to argue that it is correct. The fact that it works out for your case doesn't change the fact that the continue line is executed, but is marked as not covered.

  7. Sei Lisa

    The attached is a proof of concept of what I meant. It successfully detects my test case and my real use case where I first ran into this issue, but not the OP's because the OP's jumps are conditional, so it's not known to be dead code unless the constants are analyzed.

  8. Ned Batchelder repo owner

    Sei Lisa I see what you mean about the unreachable blocks. I'll have to think about whether that is a good way to approach this or not.

    For example, this code will also have an unreachable block:

    for i in range(3):
        continue
        print("hello")  # unreachable
    

    but I'm not sure I want to just write off that print statement. I think I'd like to flag it as uncovered.

  9. Sei Lisa

    Ned Batchelder Yes, while I initially developed it trying to come up with some kind of solution for this issue, later I thought that it had some value by itself. It can be just a different category of line: no code, covered, uncovered, unreachable. Maybe I should instead open it as a feature request in a separate issue, as this one is related but not really the same thing?

  10. Log in to comment