Final case -> exit reported as uncovered branch on exhaustive match statement
Consider the following code:
from enum import Enum
class MyEnum(Enum):
A = 1
B = 2
C = 3
def print_value(x: MyEnum) -> None:
match x:
case MyEnum.A:
print("A")
case MyEnum.B:
print("B")
case MyEnum.C:
print("C")
And the following unit test:
def test() -> None:
print_value(MyEnum.A)
print_value(MyEnum.B)
print_value(MyEnum.C)
This should have 100% line coverage and 100% branch coverage, since the match statement is exhaustive. However pytest-cov reports missing branch coverage from case MyEnum.C to the exit of the function. Such a branch is impossible, so should not be counted as uncovered.
pytest version = 7.1.1 pytest-cov version = 3.0.0 python version = 3.10.4
This is really about coverage.py's behavior. But I'm not sure what can be done about it. Coverage isn't going to be able to analyze the universe of possible values and decide that they are fully covered.
Quite right about the repo, my apologies! Should I move this ticket there, or do you believe it should be closed?
Of course Coverage shouldn't be expected to determine exhaustiveness in every case. But depending on how much semantic analysis it is willing to do (and I assume it must do some?) I think the common case of matching on an enum could be addressed as these are finite iterables whose contents are statically known.
This kind of analysis is well outside what coverage.py does. It currently does nothing about data values or types. I won't say it can't be done, but it would be a massive change.
I think type checkers are a much better fit for this job.
@nedbat I think, this issue can be closed as "not planned". What do you think?
Is this issue resolved ? Or for the last case should i use # pragma: no cover
@deserve-shubham What I do is add assert_never into coverage ignore list.
It makes sense for coverage not to do typechecker things like exhaustiveness checking
I also think using assert_never is the right call, and users can configure coverage to ignore them.
So I think this request is valid in such a case, i.e. the case statement above an ignored assert_never() should also be ignored for coverage, because it'd be wrong to ignore case _:.
match x:
case MyEnum.REACHED:
pass
case _: # should also be ignored for coverage b/c there is an `assert_never()` inside
assert_never(x)
Pretty minor issue though, and note this particular example could be avoided by using an if/else.