Skip to content

Coverage is wrong after asyncio cancel #1648

Description

@LostInDarkMath

Describe the bug
I have some line of code where the calculated coverage is wrong.

import asyncio
from datetime import timedelta


async def cancel_task(task: asyncio.Task) -> None:
    task.cancel()

    try:
        await task
    except asyncio.CancelledError:
        pass


class TTLDict:
    def __init__(self, ttl: timedelta, expiration_cb) -> None:
        self._ttl = ttl
        self._data = {}
        self._expiration_tasks = {}
        self._expiration_cb = expiration_cb
        asyncio.get_running_loop()

    def __setitem__(self, key, value) -> None:
        async def _waiter() -> None:
            await asyncio.sleep(self._ttl.total_seconds())

        self._data[key] = value
        self._expiration_tasks[key] = asyncio.create_task(_waiter())

    def __contains__(self, item) -> bool:
        return item in self._data

    async def delete(self, key):
        if key not in self:
            return

        await cancel_task(self._expiration_tasks.pop(key))
        return self._data.pop(key)

    def __getitem__(self, item):
        return self._data[item]


async def _main():
    ttl_dict = TTLDict(ttl=timedelta(days=1), expiration_cb=lambda x: None)

    assert (await ttl_dict.delete('foo')) is None

    ttl_dict['foo'] = 42

    actual = await ttl_dict.delete('foo')
    assert actual == 42

    assert ttl_dict._expiration_tasks == {}
    assert ttl_dict._data == {}

    new_value = 234567
    ttl_dict['foo'] = new_value

    assert ttl_dict['foo'] == new_value
    print('FINISH')


asyncio.run(_main())

Coverage determinded with PyCharm:
image

Coverage determined with the coverage CLI:
image

To Reproduce
I use Python 3.11.4 with coverage 7.2.7.

Run python foo.py which will print FINISH. Even coverage run foo.py will print this, but the line is always treated as not covered.

coverage debug sys

-- sys -------------------------------------------------------
               coverage_version: 7.2.7
                coverage_module: /home/willi/.cache/pypoetry/virtualenvs/maddox-wk5nYrSJ-py3.11/lib/python3.11/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.11.4 (main, Jun  7 2023, 12:45:48) [GCC 11.3.0]
                       platform: Linux-5.19.0-46-generic-x86_64-with-glibc2.35
                 implementation: CPython
                     executable: /home/willi/.cache/pypoetry/virtualenvs/maddox-wk5nYrSJ-py3.11/bin/python
                   def_encoding: utf-8
                    fs_encoding: utf-8
                            pid: 50756
                            cwd: /home/willi/.config/JetBrains/PyCharm2023.1/scratches
                           path: /home/willi/.cache/pypoetry/virtualenvs/maddox-wk5nYrSJ-py3.11/bin
                                 /usr/lib/python311.zip
                                 /usr/lib/python3.11
                                 /usr/lib/python3.11/lib-dynload
                                 /home/willi/.cache/pypoetry/virtualenvs/maddox-wk5nYrSJ-py3.11/lib/python3.11/site-packages
                    environment: HOME = /home/willi
                   command_line: /home/willi/.cache/pypoetry/virtualenvs/maddox-wk5nYrSJ-py3.11/bin/coverage debug sys
         sqlite3_sqlite_version: 3.37.2
             sqlite3_temp_store: 0
        sqlite3_compile_options: ATOMIC_INTRINSICS=1, COMPILER=gcc-11.3.0, DEFAULT_AUTOVACUUM,
                                 DEFAULT_CACHE_SIZE=-2000, DEFAULT_FILE_FORMAT=4,
                                 DEFAULT_JOURNAL_SIZE_LIMIT=-1, DEFAULT_MMAP_SIZE=0, DEFAULT_PAGE_SIZE=4096,
                                 DEFAULT_PCACHE_INITSZ=20, DEFAULT_RECURSIVE_TRIGGERS,
                                 DEFAULT_SECTOR_SIZE=4096, DEFAULT_SYNCHRONOUS=2,
                                 DEFAULT_WAL_AUTOCHECKPOINT=1000, DEFAULT_WAL_SYNCHRONOUS=2,
                                 DEFAULT_WORKER_THREADS=0, ENABLE_COLUMN_METADATA, ENABLE_DBSTAT_VTAB,
                                 ENABLE_FTS3, ENABLE_FTS3_PARENTHESIS, ENABLE_FTS3_TOKENIZER, ENABLE_FTS4,
                                 ENABLE_FTS5, ENABLE_JSON1, ENABLE_LOAD_EXTENSION, ENABLE_MATH_FUNCTIONS,
                                 ENABLE_PREUPDATE_HOOK, ENABLE_RTREE, ENABLE_SESSION, ENABLE_STMTVTAB,
                                 ENABLE_UNLOCK_NOTIFY, ENABLE_UPDATE_DELETE_LIMIT, HAVE_ISNAN,
                                 LIKE_DOESNT_MATCH_BLOBS, MALLOC_SOFT_LIMIT=1024, MAX_ATTACHED=10,
                                 MAX_COLUMN=2000, MAX_COMPOUND_SELECT=500, MAX_DEFAULT_PAGE_SIZE=32768,
                                 MAX_EXPR_DEPTH=1000, MAX_FUNCTION_ARG=127, MAX_LENGTH=1000000000,
                                 MAX_LIKE_PATTERN_LENGTH=50000, MAX_MMAP_SIZE=0x7fff0000,
                                 MAX_PAGE_COUNT=1073741823, MAX_PAGE_SIZE=65536, MAX_SCHEMA_RETRY=25,
                                 MAX_SQL_LENGTH=1000000000, MAX_TRIGGER_DEPTH=1000,
                                 MAX_VARIABLE_NUMBER=250000, MAX_VDBE_OP=250000000, MAX_WORKER_THREADS=8,
                                 MUTEX_PTHREADS, OMIT_LOOKASIDE, SECURE_DELETE, SOUNDEX, SYSTEM_MALLOC,
                                 TEMP_STORE=1, THREADSAFE=1, USE_URI

pip freeze

aenum==3.1.12
aiofiles==23.1.0
aiosqlite==0.19.0
allure-pytest==2.13.2
allure-python-commons==2.13.2
anyio==3.7.0
asgiref==3.7.2
astroid==2.15.5
async-generator==1.10
asyncua==1.0.2
attrs==23.1.0
Authlib==1.2.1
axe-selenium-python==2.1.6
bidict==0.22.1
black==23.3.0
blinker==1.6.2
Brotli==1.0.9
cachetools==5.3.1
certifi==2023.5.7
cffi==1.15.1
charset-normalizer==3.1.0
click==8.1.3
colorama==0.4.6
coverage==7.2.7
cryptography==41.0.1
decorator==4.4.2
delayed-assert==0.3.6
deprecation==2.1.0
dill==0.3.6
docker==6.1.3
ecdsa==0.18.0
et-xmlfile==1.1.0
exceptiongroup==1.1.1
execnet==1.9.0
Faker==18.11.2
fastapi==0.98.0
flake8==6.0.0
Flask==2.3.2
Flask-SocketIO==5.3.4
future==0.18.3
gevent==22.10.2
gevent-websocket==0.10.1
greenlet==2.0.2
h11==0.14.0
h2==4.1.0
hdbcli==2.17.14
hiredis==2.2.3
hpack==4.0.0
httpcore==0.17.2
httpx==0.24.1
hyperframe==6.0.1
idna==3.4
imageio==2.31.1
imageio-ffmpeg==0.4.8
influxdb==5.3.1
influxdb-client==1.36.1
iniconfig==2.0.0
isodate==0.6.1
isort==5.12.0
itsdangerous==2.1.2
Jinja2==3.1.2
joblib==1.3.0
junitparser==3.1.0
kaitaistruct==0.10
lazy-object-proxy==1.9.0
lxml==4.9.2
markdown-it-py==3.0.0
MarkupSafe==2.1.3
marshmallow==3.19.0
marshmallow-dataclass==8.5.14
mccabe==0.7.0
mdurl==0.1.2
minio==7.1.15
moviepy==1.0.3
msgpack==1.0.5
multiprocess==0.70.14
mypy==1.3.0
mypy-extensions==1.0.0
nats-py==2.3.1
numpy==1.25.0
openpyxl==3.1.2
outcome==1.2.0
packaging==23.1
paho-mqtt==1.6.1
pandas==2.0.3
pathspec==0.11.1
pedantic==1.14.5
Pillow==9.5.0
platformdirs==3.8.0
pluggy==1.2.0
proglog==0.1.10
psycopg==3.1.9
pyasn1==0.5.0
pycodestyle==2.10.0
pycparser==2.21
pydantic==1.10.9
pyfiglet==0.8.post1
pyflakes==3.0.1
Pygments==2.15.1
pyleniumio==1.19.1
pylint==2.17.4
PyMySQL==1.1.0
pyOpenSSL==23.2.0
pyparsing==3.1.0
PySocks==1.7.1
pytest==7.4.0
pytest-asyncio==0.21.0
pytest-cov==4.1.0
pytest-docker-tools==3.1.3
pytest-mock==3.11.1
pytest-order==1.1.0
pytest-parallel==0.1.1
pytest-reportportal==5.1.9
pytest-split==0.8.1
pytest-xdist==3.3.1
python-dateutil==2.8.2
python-dotenv==1.0.0
python-engineio==4.4.1
python-jose==3.3.0
python-keycloak==3.3.0
python-multipart==0.0.6
python-socketio==5.8.0
pytz==2023.3
pyupgrade==3.7.0
PyYAML==6.0
reactivex==4.0.4
redis==4.6.0
reportportal-client==5.3.5
requests==2.31.0
requests-file==1.5.1
requests-toolbelt==1.0.0
rfc3339==6.2
rfc3986==1.5.0
rich==13.4.2
rich-click==1.6.1
rsa==4.9
scikit-learn==1.2.2
scipy==1.11.1
selenium==4.10.0
selenium-wire==5.1.0
shellingham==1.5.0.post1
six==1.16.0
sniffio==1.3.0
sortedcontainers==2.4.0
starlette==0.27.0
tblib==2.0.0
threadpoolctl==3.1.0
tokenize-rt==5.1.0
tomlkit==0.11.8
tqdm==4.65.0
trio==0.22.0
trio-websocket==0.10.3
typeguard==4.0.0
typer==0.9.0
typing-inspect==0.9.0
typing_extensions==4.7.0
tzdata==2023.3
urllib3==2.0.3
uvicorn==0.22.0
uWSGI==2.0.21
webdriver-manager==3.8.6
websocket-client==1.6.1
Werkzeug==2.3.6
wrapt==1.15.0
wsproto==1.2.0
xmltodict==0.13.0
zeep==4.2.1
zope.event==5.0
zope.interface==6.0
zstandard==0.21.0

Expected behavior
100 % Coverage

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcpythonReported as a bug in CPython

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions