Python match (PEP634) "or wildcard" pattern marked as missing branch · Issue #1421 · nedbat/coveragepy (original) (raw)

Describe the bug
Two equivalent function using the match/case construct with two different ways to use wildcard pattern are reported differently, with the "or wildcard" case marked as missing branch.

To Reproduce
Here is a minimal reproducible example, note testing is done pairwise to verify the functions as equivalent.

import os

import pytest

def wildcard_alone() -> str:

match os.getenv("SOME_VAR", "default"):
    case "dev":
        return "development value"
    case "test" | "prod":
        return "production value"
    case "default":
        return "default value"
    case _:
        return "default value"

def wildcard_or() -> str:

match os.getenv("SOME_VAR", "default"):
    case "dev":
        return "development value"
    case "test" | "prod":
        return "production value"
    case "default" | _:  # marked as missing
        return "default value"

@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone]) def test_match_env_dev(f, monkeypatch): monkeypatch.setenv("SOME_VAR", "dev") assert f() == "development value"

@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone]) def test_match_env_test(f, monkeypatch): monkeypatch.setenv("SOME_VAR", "test") assert f() == "production value"

@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone]) def test_match_env_prod(f, monkeypatch): monkeypatch.setenv("SOME_VAR", "prod") assert f() == "production value"

@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone]) def test_match_env_unset(f, monkeypatch): monkeypatch.delenv("SOME_VAR", raising=False) assert f() == "default value"

@pytest.mark.parametrize("f", [wildcard_or, wildcard_alone]) def test_match_env_other(f, monkeypatch): monkeypatch.setenv("SOME_VAR", "unmatched") assert f() == "default value"

Here is the coverage.

% coverage run --branch -m pytest && coverage report -m
========================================================================================= test session starts =========================================================================================
platform darwin -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0
rootdir: /private/tmp/mre-coverage-case
collected 10 items

test_mre.py ..........                                                                                                                                                                          [100%]

========================================================================================= 10 passed in 0.05s ==========================================================================================
Name          Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------
test_mre.py      40      0     12      1    98%   26->exit
---------------------------------------------------------
TOTAL            40      0     12      1    98%

And finally, the output of coverage debug sys.

-- sys -------------------------------------------------------
               coverage_version: 6.4.2
                coverage_module: /private/tmp/mre-coverage-case/.venv/lib/python3.10/site-packages/coverage/__init__.py
                         tracer: -none-
                        CTracer: available
           plugins.file_tracers: -none-
            plugins.configurers: -none-
      plugins.context_switchers: -none-
              configs_attempted: .coveragerc
                                 setup.cfg
                                 tox.ini
                                 pyproject.toml
                   configs_read: -none-
                    config_file: None
                config_contents: -none-
                      data_file: -none-
                         python: 3.10.5 (main, Jul  6 2022, 15🔞41) [Clang 13.1.6 (clang-1316.0.21.2.3)]
                       platform: macOS-12.2.1-x86_64-i386-64bit
                 implementation: CPython
                     executable: /private/tmp/mre-coverage-case/.venv/bin/python
                   def_encoding: utf-8
                    fs_encoding: utf-8
                            pid: 44594
                            cwd: /private/tmp/mre-coverage-case
                           path: /private/tmp/mre-coverage-case/.venv/bin
                                 /Users/redacted/.pyenv/versions/3.10.5/lib/python310.zip
                                 /Users/redacted/.pyenv/versions/3.10.5/lib/python3.10
                                 /Users/redacted/.pyenv/versions/3.10.5/lib/python3.10/lib-dynload
                                 /private/tmp/mre-coverage-case/.venv/lib/python3.10/site-packages
                    environment: HOME = /Users/redacted
                                 PYENV_DIR = /tmp/mre-coverage-case
                                 PYENV_HOOK_PATH = /Users/redacted/.pyenv/pyenv.d:/usr/local/Cellar/pyenv/2.3.2/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks
                                 PYENV_ROOT = /Users/redacted/.pyenv
                                 PYENV_SHELL = zsh
                                 PYENV_VERSION = 3.10.5
                   command_line: /private/tmp/mre-coverage-case/.venv/bin/coverage debug sys
                sqlite3_version: 2.6.0
         sqlite3_sqlite_version: 3.36.0
             sqlite3_temp_store: 0
        sqlite3_compile_options: BUG_COMPATIBLE_20160819, COMPILER=clang-13.0.0, DEFAULT_CACHE_SIZE=2000,
                                 DEFAULT_CKPTFULLFSYNC, DEFAULT_JOURNAL_SIZE_LIMIT=32768,
                                 DEFAULT_MEMSTATUS=0, DEFAULT_PAGE_SIZE=4096, DEFAULT_SYNCHRONOUS=2,
                                 DEFAULT_WAL_SYNCHRONOUS=1, ENABLE_API_ARMOR, ENABLE_BYTECODE_VTAB,
                                 ENABLE_COLUMN_METADATA, ENABLE_DBSTAT_VTAB, ENABLE_FTS3,
                                 ENABLE_FTS3_PARENTHESIS, ENABLE_FTS3_TOKENIZER, ENABLE_FTS4, ENABLE_FTS5,
                                 ENABLE_JSON1, ENABLE_LOCKING_STYLE=1, ENABLE_NORMALIZE,
                                 ENABLE_PREUPDATE_HOOK, ENABLE_RTREE, ENABLE_SESSION, ENABLE_SNAPSHOT,
                                 ENABLE_SQLLOG, ENABLE_STMT_SCANSTATUS, ENABLE_UNKNOWN_SQL_FUNCTION,
                                 ENABLE_UPDATE_DELETE_LIMIT, HAS_CODEC_RESTRICTED, HAVE_ISNAN,
                                 MAX_LENGTH=2147483645, MAX_MMAP_SIZE=1073741824,
                                 MAX_VARIABLE_NUMBER=500000, OMIT_AUTORESET, OMIT_LOAD_EXTENSION,
                                 STMTJRNL_SPILL=131072, SYSTEM_MALLOC, THREADSAFE=2, USE_URI

Expected behavior
All branches are covered and both function should be at 100% coverage.

Additional context
NA