pytest-dev / pytest

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing
https://pytest.org
MIT License
12.1k stars 2.68k forks source link

`monkeypatch.setattr` started failing in Pytest 8.1.2 (setuptools CI) #12254

Open abravalheri opened 6 months ago

abravalheri commented 6 months ago

Prior to 8.1.2 the following statement executed without raising an exception (8.0.2) in the setuptools CI, but now it started failing (8.1.2):

    def test_get_output_mapping_with_stub(self, tmpdir_cwd, monkeypatch):
        monkeypatch.setenv('SETUPTOOLS_EXT_SUFFIX', '.mp3')  # make test OS-independent
>       monkeypatch.setattr('setuptools.command.build_ext.use_stubs', True)

/home/runner/work/setuptools/setuptools/setuptools/tests/test_build_ext.py:141

The error message does not make much sense to me:

obj = <module 'setuptools.command' from '/home/runner/work/setuptools/setuptools/setuptools/command/__init__.py'>
name = 'build_ext', ann = 'setuptools.command.build_ext'

    def annotated_getattr(obj: object, name: str, ann: str) -> object:
        try:
>           obj = getattr(obj, name)
E           AttributeError: module 'setuptools.command' has no attribute 'build_ext'

/home/runner/work/setuptools/setuptools/.tox/py/lib/python3.11/site-packages/_pytest/monkeypatch.py:91: AttributeError

The above exception was the direct cause of the following exception:

self = <setuptools.tests.test_build_ext.TestBuildExt object at 0x7f41b3f86a50>
tmpdir_cwd = local('/home/runner/work/setuptools/setuptools')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f41b1dfe5d0>

    def test_get_outputs(self, tmpdir_cwd, monkeypatch):
        monkeypatch.setenv('SETUPTOOLS_EXT_SUFFIX', '.mp3')  # make test OS-independent
>       monkeypatch.setattr('setuptools.command.build_ext.use_stubs', False)

/home/runner/work/setuptools/setuptools/setuptools/tests/test_build_ext.py:109: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/home/runner/work/setuptools/setuptools/.tox/py/lib/python3.11/site-packages/_pytest/monkeypatch.py:103: in derive_importpath
    target = resolve(module)
/home/runner/work/setuptools/setuptools/.tox/py/lib/python3.11/site-packages/_pytest/monkeypatch.py:[85](https://github.com/pypa/setuptools/actions/runs/8853717171/job/24315188999#step:9:86): in resolve
    found = annotated_getattr(found, part, used)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

obj = <module 'setuptools.command' from '/home/runner/work/setuptools/setuptools/setuptools/command/__init__.py'>
name = 'build_ext', ann = 'setuptools.command.build_ext'

    def annotated_getattr(obj: object, name: str, ann: str) -> object:
        try:
            obj = getattr(obj, name)
        except AttributeError as e:
>           raise AttributeError(
                f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}"
            ) from e
E           AttributeError: 'module' object at setuptools.command.build_ext has no attribute 'build_ext'

/home/runner/work/setuptools/setuptools/.tox/py/lib/python3.11/site-packages/_pytest/monkeypatch.py:[93](https://github.com/pypa/setuptools/actions/runs/8853717171/job/24315188999#step:9:94): AttributeError

Because:

  1. setuptools.command.build_ext is defined as a module, not an attribute of setuptools.command
  2. setuptools.command.build_ext module does define a build_ext class. But also setuptools.command.build_ext.build_ext should have nothing to do with monkeypatching setuptools.command.build_ext.use_stubs...

In the same workflow run, we also have other similar errors (e.g. AttributeError: module 'setuptools.command' has no attribute 'build_clib')


- [x] output of `pip list` from the virtual environment you are using [For the tox output:](https://github.com/pypa/setuptools/actions/runs/8853717171/job/24315188999#step:9:1) ```console alabaster==0.7.16 attrs==23.2.0 autocommand==2.2.2 Babel==2.14.0 backports.tarfile==1.1.1 build==1.2.1 cachetools==5.3.3 certifi==2024.2.2 cffi==1.16.0 chardet==5.2.0 charset-normalizer==3.3.2 colorama==0.4.6 ConfigUpdater==3.2 coverage==7.5.0 cryptography==42.0.5 distlib==0.3.8 docutils==0.21.2 domdf-python-tools==3.8.0.post2 execnet==2.1.1 filelock==3.13.4 idna==3.7 imagesize==1.4.1 importlib_metadata==7.1.0 importlib_resources==6.4.0 inflect==7.2.1 ini2toml==0.14 iniconfig==2.0.0 jaraco.classes==3.4.0 jaraco.context==5.3.0 jaraco.develop==8.13.0 jaraco.env==1.0.0 jaraco.envs==2.6.0 jaraco.functools==4.0.1 jaraco.packaging==10.1.0 jaraco.path==3.7.0 jaraco.text==3.12.0 jaraco.ui==2.3.0 jaraco.vcs==2.2.0 jaraco.versioning==1.1.0 jeepney==0.8.0 Jinja2==3.1.3 keyring==25.2.0 MarkupSafe==2.1.5 more-itertools==10.2.0 mypy==1.9.0 mypy-extensions==1.0.0 natsort==8.4.0 packaging==24.0 path==16.14.0 pip==24.0 pip-run==12.6.1 platformdirs==4.2.1 pluggy==1.5.0 pycparser==2.22 Pygments==2.17.2 PyNaCl==1.5.0 pyproject-api==1.6.1 pyproject_hooks==1.0.0 pytest==8.1.2 pytest-checkdocs==2.12.0 pytest-cov==5.0.0 pytest-enabler==3.1.1 pytest-home==0.5.1 pytest-mypy==0.10.3 pytest-perf==0.14.0 pytest-ruff==0.3.1 pytest-timeout==2.3.1 pytest-xdist==3.5.0 python-dateutil==2.9.0.post0 pytz==2024.1 requests==2.31.0 requests-file==2.0.0 requests-toolbelt==1.0.0 ruff==0.4.2 SecretStorage==3.3.3 setuptools @ file:///home/runner/work/setuptools/setuptools/.tox/.tmp/package/1/setuptools-69.5.1.post20240426-0.editable-py3-none-any.whl#sha256=c45586b1cbdcef78ac5a07ff798e095fc4a634e8f28286d1144ed343e1f94bd2 six==1.16.0 snowballstemmer==2.2.0 Sphinx==7.3.7 sphinxcontrib-applehelp==1.0.8 sphinxcontrib-devhelp==1.0.6 sphinxcontrib-htmlhelp==2.0.5 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 subprocess-tee==0.4.1 tempora==5.5.1 toml==0.10.2 tomli==2.0.1 tomli_w==1.0.0 tomlkit==0.12.4 tox==4.15.0 typeguard==4.2.1 typing_extensions==4.11.0 urllib3==2.2.1 virtualenv==20.26.0 wheel==0.43.0 zipp==3.18.1 ```

ubuntu-latest (Github Actions) - Ubuntu 22.04 LTS
CPython 3.11.9

pytest==8.1.2
pytest-checkdocs==2.12.0
pytest-cov==5.0.0
pytest-enabler==3.1.1
pytest-home==0.5.1
pytest-mypy==0.10.3
pytest-perf==0.14.0
pytest-ruff==0.3.1
pytest-timeout==2.3.1
pytest-xdist==3.5.0

Unfortunately I was not able to simplify the reproducer. I tried to create a minimal example with monkeypatch and nested modules but all works fine:

docker run --rm -it python:3.11-bookworm /bin/bash

cd /tmp
python3 -m venv .venv
.venv/bin/python -m pip install 'pytest==8.1.2'

mkdir -p pkg/a/b
cat <<EOF > pkg/a/b/__init__.py
def f():
    from . import c
    return c.y()
EOF
cat <<EOF > pkg/a/b/c.py
x = 37

def y(): return x
EOF

cat <<EOF > test_monkeypatch.py
from pkg.a import b

def test_monkeypatch(monkeypatch):
    monkeypatch.setattr('pkg.a.b.c.x', 42)

    assert b.f() == 42
EOF

.venv/bin/pytest test_monkeypatch.py  # ==> this works fine...
RonnyPfannschmidt commented 6 months ago

It seems like a import error was hit and hidden by the loop