pytest-cov icon indicating copy to clipboard operation
pytest-cov copied to clipboard

Final case -> exit reported as uncovered branch on exhaustive match statement

Open sirrus233 opened this issue 3 years ago • 8 comments

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

sirrus233 avatar Apr 25 '22 11:04 sirrus233

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.

nedbat avatar Apr 25 '22 14:04 nedbat

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.

sirrus233 avatar Apr 25 '22 23:04 sirrus233

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.

nedbat avatar Apr 26 '22 00:04 nedbat

I think type checkers are a much better fit for this job.

zmievsa avatar Feb 16 '23 10:02 zmievsa

@nedbat I think, this issue can be closed as "not planned". What do you think?

zmievsa avatar Feb 26 '23 10:02 zmievsa

Is this issue resolved ? Or for the last case should i use # pragma: no cover

deserve-shubham avatar Apr 20 '23 12:04 deserve-shubham

@deserve-shubham What I do is add assert_never into coverage ignore list.

zmievsa avatar Apr 20 '23 12:04 zmievsa

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.

Kache avatar Dec 09 '23 06:12 Kache