Open vsajip opened 2 years ago
Short reproducing program:
import os
import subprocess
import tempfile
class CustomException(Exception): pass
def run_command(cmd, wd):
p = subprocess.Popen(cmd, cwd=wd,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode == 0:
return stdout, stderr
else: # pragma: no cover
raise subprocess.CalledProcessError(p.returncode,
p.args,
output=stdout,
stderr=stderr)
def main():
fd, fn = tempfile.mkstemp(prefix='test-')
os.write(fd, b'Hello, world!')
os.close(fd)
try:
run_command(['ls', '-l', fn], os.getcwd())
except subprocess.CalledProcessError as e: # pragma: no cover
raise CustomException('Child process failed') from e
finally:
os.remove(fn)
return 0
if __name__ == '__main__': # pragma: no branch
main()
I think I understand what is happening here. In your first code example:
try:
# some code
finally:
# some finally code
the "finally" code can only execute one way: because "some code" has finished. There's only one path through the finally code, and it is executed, giving you 100% branch coverage.
In the second code:
try:
# some code
except SomeException as e: # pragma: no cover
raise SomeOtherException() from e
finally:
# some finally code
there are two ways to run "some finally code": "some code" can finish normally (which it does), and "raise SomeOtherException" can happen (which it doesn't). There are two paths through the finally code, and only one of them happens. This gives you 50% branch coverage.
The complicating factor here is the pragma noting that the raise can never happen. Coverage.py isn't taking that into account in deciding on the branch coverage in the finally clause. I'm not sure what it would take for it to understand that, but it's an interesting case.
In the meantime, you can add # pragma: no branch
to the line with 50% branch coverage to indicate that you know it is only partial.
OK, thanks for the explanation. Shall I close the issue? I ask in case you want to look into it further. I added some test cases to make sure the exception was raised, and now I have 100% coverage again.
Thank you so much for all your work on coverage
.
Describe the bug If a block such as
has 100% branch coverage, then adding an exception block can change things even when the exception isn't triggered:
The
finally
code branch coverage goes down.To Reproduce How can we reproduce the problem? Please be specific. Don't link to a failing CI job. Answer the questions below:
coverage debug sys
output below.coverage debug sys
is helpful. Seecoverage debug sys
output below.pip freeze
is helpful. Nothing relevant, I run coverage from a venv and my code under test has no dependencies.Expected behavior I didn't expect to see partial branch coverage for the
finally
block when adding anexcept
block that isn't triggered.Additional context See source lines 262 and 520 in https://app.codecov.io/gh/vsajip/pagesign/commit/ac5c6c1096763b1f858efbdf7385564f394246b9/pagesign.py - though sometimes codecov.io can't display the page. When testing locally, the annotation for a block such as
says
L06 ↛ exit
and with a tooltip that saysline L06 didn't except from function '<funcname>', because the raise on line L04 wasn't executed
. This is confusing because I wouldn't have expected L06 to "except from function" - not sure what's meant there.Can I add or change pragmas to avoid this decrease in coverage?