pytest-dev / pytest-cov

Coverage plugin for pytest.
MIT License
1.72k stars 211 forks source link

__del__ method incorrectly reported as missing in a problem with circular references #603

Open andymwood opened 1 year ago

andymwood commented 1 year ago

Summary

The following problem has two objects that reference each other and a __del__ method in one of the classes. pytest-cov incorrectly reports that the __del__ method hasn't been called.

Expected vs actual result

Expected 100% coverage. Get 91% coverage.

Reproducer

$ python3.10 -m venv venv
$ . venv/bin/activate
$ pip install pytest-cov
$ pytest --cov=. --cov-report=term-missing test_module.py 
================================================================== test session starts ===================================================================
platform linux -- Python 3.10.11, pytest-7.4.0, pluggy-1.2.0
rootdir: ...
plugins: cov-4.1.0
collected 1 item                                                                                                                                         

test_module.py .                                                                                                                                   [100%]
A.__del__

---------- coverage: platform linux, python 3.10.11-final-0 ----------
Name             Stmts   Miss  Cover   Missing
----------------------------------------------
test_module.py      10      1    90%   3
----------------------------------------------
TOTAL               10      1    90%

=================================================================== 1 passed in 0.06s ====================================================================

Versions

$ pip list
Package        Version
-------------- -------
coverage       7.2.7
exceptiongroup 1.1.2
iniconfig      2.0.0
packaging      23.1
pip            23.0.1
pluggy         1.2.0
pytest         7.4.0
pytest-cov     4.1.0
setuptools     65.5.0
tomli          2.0.1
$ python --version
Python 3.10.11

Code

test_module.py:

class A:
    def __del__(self):
        print('\nA.__del__')

class B:
    pass

def test_case():
    x = A()
    x.y = B()
    x.y.x = x
    assert x is not None
nedbat commented 1 year ago

__del__ methods are very difficult. In your case, the method is being called during interpreter shutdown, so possibly after coverage has stopped measurement. They can cause other troubles as well (see the pink warning box at https://docs.python.org/3/reference/datamodel.html#object.__del__). You might want to consider refactoring to avoid the need for __del__.

In the meantime, you can add a pragma comment to indicate that you understand the line will not be measured: https://coverage.readthedocs.io/en/7.2.7/excluding.html#excluding-code-from-coverage-py