Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

continue marked as not covered #198

Closed
nedbat opened this issue Sep 28, 2012 · 28 comments
Closed

continue marked as not covered #198

nedbat opened this issue Sep 28, 2012 · 28 comments
Labels
bug Something isn't working not our bug The problem was elsewhere run

Comments

@nedbat
Copy link
Owner

nedbat commented Sep 28, 2012

Originally reported by Anonymous


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


@nedbat
Copy link
Owner Author

nedbat commented Oct 9, 2012

Original comment by Jean-Tiare Le Bigot (Bitbucket: jtlebigot, GitHub: Unknown)


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

#!python
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.

@nedbat
Copy link
Owner Author

nedbat commented Jan 5, 2013

Original comment by Daniel Black (Bitbucket: dan_black, GitHub: Unknown)


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

continue is reached with example(4)

#!python

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)

@nedbat
Copy link
Owner Author

nedbat commented Apr 5, 2013

Original comment by Ronald Oussoren (Bitbucket: ronaldoussoren, GitHub: ronaldoussoren)


And likewise for this code:

#!python



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'.

@nedbat
Copy link
Owner Author

nedbat commented May 12, 2013

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".

@nedbat
Copy link
Owner Author

nedbat commented Aug 13, 2013

Issue #254 was marked as a duplicate of this issue.

@nedbat
Copy link
Owner Author

nedbat commented Sep 6, 2013

Original comment by Peter Portante (Bitbucket: portante, GitHub: portante)


Could coverage detect the peephole optimization and mark it covered anyways?

@nedbat
Copy link
Owner Author

nedbat commented Sep 6, 2013

If you have an idea of how to detect that, I'm all ears.

@nedbat
Copy link
Owner Author

nedbat commented Mar 17, 2014

Original comment by Marc Schlaich (Bitbucket: schlamar, GitHub: schlamar)


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

Consider

#!python

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).

@nedbat
Copy link
Owner Author

nedbat commented Mar 17, 2014

@schlamar 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.

@nedbat
Copy link
Owner Author

nedbat commented Jun 23, 2014

Original comment by Sei Lisa (Bitbucket: Sei_Lisa, GitHub: Unknown)


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.

@nedbat
Copy link
Owner Author

nedbat commented Jul 5, 2014

BTW: A recent thread in Python-Ideas (starting here: https://mail.python.org/pipermail/python-ideas/2014-May/027893.html) approved a possible change to Python to make disabling the optimizer possible.

@nedbat
Copy link
Owner Author

nedbat commented Jul 6, 2014

@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.

@nedbat
Copy link
Owner Author

nedbat commented Jul 7, 2014

Original comment by Sei Lisa (Bitbucket: Sei_Lisa, GitHub: Unknown)


@nedbat 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?

@nedbat
Copy link
Owner Author

nedbat commented Jun 7, 2016

Issue #497 was marked as a duplicate of this issue.

@nedbat
Copy link
Owner Author

nedbat commented Jun 7, 2016

Original comment by Peter Inglesby (Bitbucket: inglesp, GitHub: inglesp)


Is this limitation documented anywhere?

@nedbat nedbat closed this as completed Jun 7, 2016
@nedbat
Copy link
Owner Author

nedbat commented Jul 27, 2017

Issue #593 was marked as a duplicate of this issue.

@nedbat
Copy link
Owner Author

nedbat commented Aug 2, 2017

Issue #594 was marked as a duplicate of this issue.

@nedbat
Copy link
Owner Author

nedbat commented Oct 21, 2017

Issue #598 was marked as a duplicate of this issue.

@skirpichev
Copy link

The CPython developers believe they have fully implemented PEP626.

I'm not sure. Release notes still doesn't mention it.

If you have specific examples of problems, please let us know.

Sure, my biggest project has ~20 workarounds for this issue - I'll try run coverage tests on the 3.10 and report if problem persist.

@nedbat
Copy link
Owner Author

nedbat commented Mar 16, 2021

The What's New is being updated: python/cpython#24892

kosayoda added a commit to python-discord/site that referenced this issue May 14, 2021
Due to an optimization in CPython that is amended in 3.10, coverage.py
is sometimes unable to determine the coverage of continue statements in
branches. See: nedbat/coveragepy#198

Adding a no-op like a print or an empty statement would solve the
coverage issue, but I've opted to just ignore the line. This should be
tested and the line removed when the site is updated to Python 3.10.
atreyasha added a commit to infsys-lab/privacy-glue that referenced this issue Oct 11, 2022
Synthesize into single if-elif-else block and add pragmas for no
coverage where continue is called, see:

nedbat/coveragepy#198
evansd added a commit to opensafely-core/ehrql that referenced this issue Oct 19, 2022
I've confirmed that the lines are in fact covered by raising errors from
them. This relates to a long standing bug in CPython that Coverage is
unable to work around, but which is apparently fixed in Python 3.10:
nedbat/coveragepy#198 (comment)
tyralla added a commit to hydpy-dev/hydpy that referenced this issue Sep 3, 2023
…`Model` of module `modeltools` slightly to avoid `coveragepy` wrongly reporting an uncovered `continue` line.

Due to CPython's peephole optimizer, see nedbat/coveragepy#198).
tyralla added a commit to hydpy-dev/hydpy that referenced this issue Sep 5, 2023
…onnect_subgroup` of class `Model` of module `modeltools` for which `coveragepy` otherwise reports missing coverage.

Due to CPython's peephole optimizer, see nedbat/coveragepy#198.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working not our bug The problem was elsewhere run
Projects
None yet
Development

No branches or pull requests

2 participants