Skip to content

Pytest does not follow symlinks correctly when passed full path and tries to import conftest.py twice #4174

@digitalresistor

Description

@digitalresistor

This is an issue with the latest version of pytest, that didn't happen with pytest 3.8.2, likely related to the change at #4108

Here's the setup:

We set up a symlink for Jenkins that points the workspace to a new folder on different mount:

/jenkins/workspace -> /scratch0/workspace

According to Jenkins for a job, /jenkins/workspace/job-name/ is the $WORKSPACE.

We clone a project using pytest into /jenkins/workspace/job-name/pytest_example/, it looks like this:

.
./conftest.py  # contains: collect_ignore = ['ignore']
./ignore
./ignore/test_stuff.py
./tests
./tests/test_something.py

We then run (while in /jenkins/workspace/job-name/):

pytest $WORKSPACE/pytest_example/ignore/

This will blow up stating it is attempting to load a plugin again, here's an example from my local laptop, temp2 is a symlink to temp:

(testing) Riley:temp2 xistence$ pytest /Users/xistence/Projects/temp2/pytest_example/ignore/
Test session starts (platform: darwin, Python 3.7.0, pytest 3.9.1, pytest-sugar 0.9.1)
rootdir: /Users/xistence/Projects, inifile:
plugins: sugar-0.9.1, flake8-1.0.2, cov-2.6.0, backports.unittest-mock-1.4

―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ERROR collecting  ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
../../.ve/testing/lib/python3.7/site-packages/_pytest/config/__init__.py:388: in _getconftestmodules
    return self._path2confmods[directory]
E   KeyError: local('/Users/xistence/Projects/temp/pytest_example/ignore')

During handling of the above exception, another exception occurred:
../../.ve/testing/lib/python3.7/site-packages/_pytest/config/__init__.py:416: in _importconftest
    return self._conftestpath2mod[conftestpath]
E   KeyError: local('/Users/xistence/Projects/temp/pytest_example/conftest.py')

During handling of the above exception, another exception occurred:
../../.ve/testing/lib/python3.7/site-packages/py/_path/common.py:377: in visit
    for x in Visitor(fil, rec, ignore, bf, sort).gen(self):
../../.ve/testing/lib/python3.7/site-packages/py/_path/common.py:418: in gen
    dirs = self.optsort([p for p in entries
../../.ve/testing/lib/python3.7/site-packages/py/_path/common.py:419: in <listcomp>
    if p.check(dir=1) and (rec is None or rec(p))])
../../.ve/testing/lib/python3.7/site-packages/_pytest/main.py:562: in _recurse
    ihook = self.gethookproxy(path.dirpath())
../../.ve/testing/lib/python3.7/site-packages/_pytest/main.py:422: in gethookproxy
    my_conftestmodules = pm._getconftestmodules(fspath)
../../.ve/testing/lib/python3.7/site-packages/_pytest/config/__init__.py:399: in _getconftestmodules
    mod = self._importconftest(conftestpath)
../../.ve/testing/lib/python3.7/site-packages/_pytest/config/__init__.py:450: in _importconftest
    self.consider_conftest(mod)
../../.ve/testing/lib/python3.7/site-packages/_pytest/config/__init__.py:473: in consider_conftest
    self.register(conftestmodule, name=conftestmodule.__file__)
../../.ve/testing/lib/python3.7/site-packages/_pytest/config/__init__.py:296: in register
    ret = super(PytestPluginManager, self).register(plugin, name)
../../.ve/testing/lib/python3.7/site-packages/pluggy/manager.py:80: in register
    % (plugin_name, plugin, self._name2plugin)
E   ValueError: Plugin already registered: /Users/xistence/Projects/temp/pytest_example/conftest.py=<module 'conftest' from '/Users/xistence/Projects/temp/pytest_example/conftest.py'>
E   {'4387271288': <_pytest.config.PytestPluginManager object at 0x105807278>, 'pytestconfig': <_pytest.config.Config object at 0x1061d7e80>, 'mark': <module '_pytest.mark' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/mark/__init__.py'>, 'main': <module '_pytest.main' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/main.py'>, 'terminal': <module '_pytest.terminal' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/terminal.py'>, 'runner': <module '_pytest.runner' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/runner.py'>, 'python': <module '_pytest.python' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/python.py'>, 'fixtures': <module '_pytest.fixtures' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/fixtures.py'>, 'debugging': <module '_pytest.debugging' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/debugging.py'>, 'unittest': <module '_pytest.unittest' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/unittest.py'>, 'capture': <module '_pytest.capture' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/capture.py'>, 'skipping': <module '_pytest.skipping' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/skipping.py'>, 'tmpdir': <module '_pytest.tmpdir' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/tmpdir.py'>, 'monkeypatch': <module '_pytest.monkeypatch' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/monkeypatch.py'>, 'recwarn': <module '_pytest.recwarn' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/recwarn.py'>, 'pastebin': <module '_pytest.pastebin' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/pastebin.py'>, 'helpconfig': <module '_pytest.helpconfig' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/helpconfig.py'>, 'nose': <module '_pytest.nose' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/nose.py'>, 'assertion': <module '_pytest.assertion' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/assertion/__init__.py'>, 'junitxml': <module '_pytest.junitxml' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/junitxml.py'>, 'resultlog': <module '_pytest.resultlog' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/resultlog.py'>, 'doctest': <module '_pytest.doctest' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/doctest.py'>, 'cacheprovider': <module '_pytest.cacheprovider' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/cacheprovider.py'>, 'freeze_support': <module '_pytest.freeze_support' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/freeze_support.py'>, 'setuponly': <module '_pytest.setuponly' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/setuponly.py'>, 'setupplan': <module '_pytest.setupplan' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/setupplan.py'>, 'warnings': <module '_pytest.warnings' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/warnings.py'>, 'logging': <module '_pytest.logging' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/_pytest/logging.py'>, 'sugar': <module 'pytest_sugar' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/pytest_sugar.py'>, 'flake8': <module 'pytest_flake8' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/pytest_flake8.py'>, 'pytest_cov': <module 'pytest_cov.plugin' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/pytest_cov/plugin.py'>, 'unittest_mock': <module 'backports.unittest_mock' from '/Users/xistence/.ve/testing/lib/python3.7/site-packages/backports/unittest_mock/__init__.py'>, 'capturemanager': <_pytest.capture.CaptureManager object at 0x106b46588>, '/Users/xistence/Projects/temp/pytest_example/conftest.py': <module 'conftest' from '/Users/xistence/Projects/temp/pytest_example/conftest.py'>, 'session': <Session 'Projects'>, 'lfplugin': <_pytest.cacheprovider.LFPlugin object at 0x106b6a4a8>, 'nfplugin': <_pytest.cacheprovider.NFPlugin object at 0x106b6a518>, 'logging-plugin': <_pytest.logging.LoggingPlugin object at 0x106b6a710>, 'terminalreporter': <pytest_sugar.SugarTerminalReporter object at 0x106b6ab38>, 'funcmanage': <_pytest.fixtures.FixtureManager object at 0x1063ca048>}

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Results (0.24s):

If we use a relative path instead:

(testing) Riley:temp2 xistence$ pytest pytest_example/ignore/
Test session starts (platform: darwin, Python 3.7.0, pytest 3.9.1, pytest-sugar 0.9.1)
rootdir: /Users/xistence/Projects/temp, inifile:
plugins: sugar-0.9.1, flake8-1.0.2, cov-2.6.0, backports.unittest-mock-1.4

 pytest_example/ignore/test_stuff.py ✓                                                                                                                                                                                                                                                                       50% █████     

――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― test_false ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

    def test_false():
>       assert False
E       assert False

pytest_example/ignore/test_stuff.py:5: AssertionError

 pytest_example/ignore/test_stuff.py ⨯                                                                                                                                                                                                                                                                      100% ██████████

Results (0.06s):
       1 passed
       1 failed
         - pytest_example/ignore/test_stuff.py:4 test_false

Or if we use the real path (without the symlink in the middle):

testing) Riley:temp2 xistence$ pytest /Users/xistence/Projects/temp/pytest_example/ignore/
Test session starts (platform: darwin, Python 3.7.0, pytest 3.9.1, pytest-sugar 0.9.1)
rootdir: /Users/xistence/Projects/temp, inifile:
plugins: sugar-0.9.1, flake8-1.0.2, cov-2.6.0, backports.unittest-mock-1.4

 pytest_example/ignore/test_stuff.py ✓                                                                                                                                                                                                                                                                       50% █████     

――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― test_false ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

    def test_false():
>       assert False
E       assert False

pytest_example/ignore/test_stuff.py:5: AssertionError

 pytest_example/ignore/test_stuff.py ⨯                                                                                                                                                                                                                                                                      100% ██████████

Results (0.06s):
       1 passed
       1 failed
         - pytest_example/ignore/test_stuff.py:4 test_false

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: collectionrelated to the collection phasetopic: configrelated to config handling, argument parsing and config filetype: regressionindicates a problem that was introduced in a release which was working previously

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions