nedbat / coveragepy

The code coverage tool for Python
https://coverage.readthedocs.io
Apache License 2.0
3.01k stars 432 forks source link

Problem with statements under async with in Python 3.11 #1595

Open stinovlas opened 1 year ago

stinovlas commented 1 year ago

Describe the bug I have a problem with coverage of statements under async with while using fakeredis library. This problem manifests only under Python 3.11. Python 3.10 correctly reports 100% coverage. See minimal example below.

To Reproduce

  1. What version of Python are you using?

Python 3.11.2

  1. What version of coverage.py shows the problem? The output of coverage debug sys is helpful.
# coverage debug sys
-- sys -------------------------------------------------------
               coverage_version: 7.2.2
                coverage_module: /home/jmusilek/test/venv/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.2 (main, Feb 12 2023, 00:48:52) [GCC 12.2.0]
                       platform: Linux-6.1.0-5-amd64-x86_64-with-glibc2.36
                 implementation: CPython
                     executable: /home/jmusilek/test/venv/bin/python3.11
                   def_encoding: utf-8
                    fs_encoding: utf-8
                            pid: 673263
                            cwd: /home/jmusilek/test
                           path: /home/jmusilek/test/venv/bin
                                 /usr/lib/python311.zip
                                 /usr/lib/python3.11
                                 /usr/lib/python3.11/lib-dynload
                                 /home/jmusilek/test/venv/lib/python3.11/site-packages
                    environment: HOME = /home/jmusilek
                                 PYENV_ROOT = /home/jmusilek/.pyenv
                                 PYENV_SHELL = fish
                   command_line: /home/jmusilek/test/venv/bin/coverage debug sys
         sqlite3_sqlite_version: 3.40.1
             sqlite3_temp_store: 0
        sqlite3_compile_options: ATOMIC_INTRINSICS=1, COMPILER=gcc-12.2.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_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
  1. What versions of what packages do you have installed? The output of pip freeze is helpful.
# pip freeze
coverage==7.2.2
fakeredis==2.10.2
redis==4.5.2
sortedcontainers==2.4.0
  1. What code shows the problem? Give us a specific commit of a specific repo that we can check out. If you've already worked around the problem, please provide a commit before that fix.

Minimal example:

import asyncio
from contextlib import asynccontextmanager
from unittest import IsolatedAsyncioTestCase

from fakeredis.aioredis import FakeRedis

cache = FakeRedis(decode_responses=True)

async def manager():
    yield cache

class ManagerTest(IsolatedAsyncioTestCase):
    async def test_manager(self):
        async with asynccontextmanager(manager)() as cm:
            await cm.set("a", 1)
            self.assertEqual(await cm.get("a"), "1")
            await cm.set("a", 2)
  1. What commands did you run?

coverage run --branch -m unittest discover; coverage report --show-missing

Expected behavior I expect 100% coverage, which is the case under Python 3.10.10. However, under Python 3.11.2, I encounter this report:

Name              Stmts   Miss Branch BrPart  Cover   Missing
-------------------------------------------------------------
test_example.py      13      2      2      1    80%   16->exit, 18-19
-------------------------------------------------------------

Additional context It seems that fakeredis (or rather something behind it) is somehow connected to this. When I replace cache.set and cache.get with asyncio.sleep, coverage shows 100% as expected.

marcgibbons commented 1 year ago

@stinovlas Was able to reproduce this bug with the versions you pinned above. I was able to narrow it down to redis==4.5.2. Tried with redis==4.5.4 and I get the expected result:

OK
Name          Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------
test_bug.py      12      0      2      0   100%
---------------------------------------------------------
TOTAL            12      0      2      0   100%
glyph commented 8 months ago

I think I've run across the same bug in this chunk of code: https://github.com/glyph/Fritter/blob/6a5337db73c0b4ff2d055d415fcf2d3a38a4a5f3/src/fritter/test/test_repeat.py#L123-L142

I am able to work around it if I replace the with self.assertRaises with a different Twisted idiom for suppressing the cancellation error.

I assume similar to fakeredis, this code is performing deterministic no-I/O execution of a coroutine.

guywilsonjr commented 2 months ago

I have the same issue. It looks like async with lines get wrongly reported as uncovered