summaryrefslogtreecommitdiffstats
path: root/library/python/pytest/plugins
diff options
context:
space:
mode:
authorantonyzhilin <[email protected]>2026-03-16 16:02:58 +0300
committerantonyzhilin <[email protected]>2026-03-17 06:05:46 +0300
commit8b3f2accec2c327842e23d6c7e3674acb11419ed (patch)
tree6c506cbb27aef24df44ce865560786691263341a /library/python/pytest/plugins
parent456efb878536940cf1442d584ea367ec4bcbfa0e (diff)
feat pytest: partially enable behavior of CONFTEST_LOAD_POLICY_LOCAL for all tests
1. `module_node.path` (py3) or `fspath` (py2) are now always in the form `yatest.common.source_path("path/from/repo/root/to/my_test.py")` instead of the previous `partial.path.to.my_test.py` 2. `module_node.nodeid` is now always in the form `path/from/repo/root/to/my_test.py` instead of the previous `partial.path.to.my_test.py` 3. `module_node.name` was also changed to `path/from/repo/root/to/my_test.py`. But it is not recommended to specialize on that, it may change again shortly in the future 4. Refactorings, simplifications and unifications in `library/python/pytest` commit_hash:8a5b4951208a034f94231b2f799eb0c32a6d787d
Diffstat (limited to 'library/python/pytest/plugins')
-rw-r--r--library/python/pytest/plugins/collection.py54
-rw-r--r--library/python/pytest/plugins/conftests.py67
2 files changed, 61 insertions, 60 deletions
diff --git a/library/python/pytest/plugins/collection.py b/library/python/pytest/plugins/collection.py
index 039031635c6..e00d9292921 100644
--- a/library/python/pytest/plugins/collection.py
+++ b/library/python/pytest/plugins/collection.py
@@ -15,6 +15,7 @@ if six.PY3:
from library.python.pytest import module_utils
import library.python.testing.filter.filter as test_filter
+import yatest.common
def _make_module_with_single_skipped_test(module_name, file_name, exc):
@@ -40,36 +41,30 @@ class LoadedModule(pytest.Module):
self.module_name = name
if namespace:
assert name.startswith('__tests__.')
- self.display_module_name = name[len('__tests__.'):]
+ self.display_module_name = name[len('__tests__.') :]
else:
self.display_module_name = name
self.is_fake_module = False
# Always passed to `super().__init__` explicitly.
kwargs.pop('nodeid', None)
- if os.getenv('CONFTEST_LOAD_POLICY') == 'LOCAL':
- nodeid = module_utils.get_proper_module_path(self.module_name)
- if nodeid is None:
- self.is_fake_module = True
- return
+ nodeid = module_utils.get_proper_module_path(self.module_name)
+ if nodeid is None:
+ self.is_fake_module = True
+ return
- name = nodeid
- if six.PY3:
- kwargs['path'] = kwargs.get('path') or pathlib.Path(nodeid)
- else:
- raw_module_path = module_utils.get_module_file_path(self.module_name)
- kwargs['fspath'] = kwargs.get('fspath') or py.path.local(raw_module_path)
- else:
- nodeid = self.display_module_name + '.py'
- name = nodeid
- if six.PY3:
- kwargs['path'] = kwargs.get('path') or pathlib.Path(py.path.local())
- else:
- kwargs['fspath'] = kwargs.get('fspath') or py.path.local()
+ # Avoid specializing on `name`. It may change in the future if we add virtual collection nodes for directories.
+ name = nodeid
+ # `path` and `fspath` are required to be absolute paths.
+ # The original source or build directory may be unavailable (at least in CI) and is irrelevant at this point.
+ # Meanwhile, `source_path` is useful, because it allows to find nearby test data, like with vanilla pytest.
+ path = yatest.common.source_path(nodeid)
if six.PY3:
+ kwargs['path'] = kwargs.get('path') or pathlib.Path(path)
super().__init__(parent=parent, name=name, nodeid=nodeid, **kwargs)
else:
+ kwargs['fspath'] = kwargs.get('fspath') or py.path.local(path)
super(LoadedModule, self).__init__(parent=parent, nodeid=nodeid, **kwargs)
self.name = name
@@ -83,7 +78,9 @@ class LoadedModule(pytest.Module):
__import__(self.module_name)
except pytest.skip.Exception as e:
if not e.allow_module_level:
- raise RuntimeError("Using pytest.skip outside of a test will skip the entire module. If that's your intention, pass `allow_module_level=True`.")
+ raise RuntimeError(
+ "Using pytest.skip outside of a test will skip the entire module. If that's your intention, pass `allow_module_level=True`."
+ )
# DEVTOOLSSUPPORT-79470: reraising pytest.skip.Exception from here seems to break reporting in ya plugin.
# Instead, pretend to be a module with a single skipped test.
return _make_module_with_single_skipped_test(self.module_name, self.nodeid, e)
@@ -114,10 +111,8 @@ class DoctestModule(LoadedModule):
for test in finder.find(module, self.display_module_name):
if test.examples: # skip empty doctests
yield getattr(_pytest.doctest.DoctestItem, 'from_parent', _pytest.doctest.DoctestItem)(
- name=test.name,
- parent=self,
- runner=runner,
- dtest=test)
+ name=test.name, parent=self, runner=runner, dtest=test
+ )
except Exception:
logging.exception('DoctestModule failed, probably you can add NO_DOCTESTS() macro to ya.make')
etype, exc, tb = sys.exc_info()
@@ -153,12 +148,14 @@ def _patch_set_initial_conftests(pluginmanager):
Avoids attempts to access filesystem directly in built-in `pytest_load_initial_conftests` hook impl.
"""
if six.PY3:
+
def _set_initial_conftests_py3(pyargs, noconftest, *args, **kwargs):
pluginmanager._noconftest = noconftest
pluginmanager._using_pyargs = pyargs
pluginmanager._set_initial_conftests = _set_initial_conftests_py3
else:
+
def _set_initial_conftests_py2(namespace):
pluginmanager._noconftest = namespace.noconftest
pluginmanager._using_pyargs = namespace.pyargs
@@ -180,9 +177,8 @@ class CollectionPlugin(object):
def collect(*args, **kwargs):
config = session.config
- if os.getenv('CONFTEST_LOAD_POLICY') == 'LOCAL':
- # A custom function that is set in conftests.py.
- config._ya_register_non_initial_conftests()
+ # A custom function that is set in conftests.py.
+ config._ya_register_non_initial_conftests()
accept_filename_predicate = test_filter.make_py_file_filter(config.option.test_filter)
full_test_names_file_path = config.option.test_list_path
@@ -206,6 +202,8 @@ class CollectionPlugin(object):
if os.environ.get('YA_PYTEST_DISABLE_DOCTEST', 'no') == 'no':
for doctest_module in self._doctest_modules:
- yield DoctestModule.from_parent(name=doctest_module, parent=session, namespace=False)
+ module = DoctestModule.from_parent(name=doctest_module, parent=session, namespace=False)
+ if not module.is_fake_module:
+ yield module
session.collect = collect
diff --git a/library/python/pytest/plugins/conftests.py b/library/python/pytest/plugins/conftests.py
index 33701fd7452..667874bb3d8 100644
--- a/library/python/pytest/plugins/conftests.py
+++ b/library/python/pytest/plugins/conftests.py
@@ -8,7 +8,6 @@ import sys
import pytest
-from library.python.pytest.plugins.fixtures import metrics, links # noqa
from library.python.pytest import module_utils
import yatest.common
@@ -48,7 +47,20 @@ def _import_conftest(pluginmanager, module_name, proper_path):
pluginmanager._ya_loaded_conftest_infos.append(conftest_info)
-def _getconftestmodules_local(self, path):
+def _import_conftest_force_initial(pluginmanager, module_name):
+ """Sets conftest filename to '', which makes pytest consider it an initial conftest."""
+ module = importlib.import_module(module_name)
+
+ if six.PY2:
+ setattr(module, '__orig_file__', module.__file__)
+ module.__file__ = ''
+
+ _consider_conftest(pluginmanager, module, '')
+ conftest_info = LoadedConftestInfo(conftest_dir='', module=module)
+ pluginmanager._ya_loaded_conftest_infos.append(conftest_info)
+
+
+def _getconftestmodules(self, path):
"""
_getconftestmodules returns a cached list of conftest modules that relate to the given test path.
This list is then used by pytest to temporarily mute other non-initial conftests when processing those tests.
@@ -69,17 +81,21 @@ def _getconftestmodules_local(self, path):
return cache[path]
-def _getconftestmodules_non_local(path):
- return _conftest_modules
-
-
-_conftest_modules = []
+def _non_local_conftest_key(name):
+ if not name.startswith('__tests__.'):
+ # Make __tests__ come last
+ return '_.' + name
+ return name
@pytest.hookimpl(trylast=True)
def pytest_load_initial_conftests(early_config):
pluginmanager = early_config.pluginmanager
+ pluginmanager._ya_loaded_conftest_infos = []
+ pluginmanager._ya_getconftestmodules_cache = {}
+ pluginmanager._getconftestmodules = functools.partial(_getconftestmodules, pluginmanager)
+
extra_modules = getattr(sys, 'extra_modules', [])
conftests = filter(lambda name: name == 'conftest' or name.endswith('.conftest'), extra_modules)
@@ -93,50 +109,37 @@ def pytest_load_initial_conftests(early_config):
proper_path = module_utils.get_proper_module_path(module_name)
assert proper_path is not None
conftest_dir = os.path.dirname(proper_path)
+ conftest_info = ConftestInfo(module_name=module_name, proper_path=proper_path)
# There are two kinds of relevant conftests:
# - Initial: conftest is in the directory of the test suite or in a parent directory (applies to all tests)
# - Non-initial: conftest is strictly within the test directory
if module_utils.is_relative_to(test_dir, conftest_dir):
- initial_conftests.append(ConftestInfo(module_name=module_name, proper_path=proper_path))
+ initial_conftests.append(conftest_info)
elif module_utils.is_relative_to(conftest_dir, test_dir):
- non_initial_conftests.append(ConftestInfo(module_name=module_name, proper_path=proper_path))
+ non_initial_conftests.append(conftest_info)
# Ensure parent conftests are loaded before child conftests
initial_conftests.sort(key=lambda x: len(x.proper_path))
non_initial_conftests.sort(key=lambda x: len(x.proper_path))
- pluginmanager._ya_loaded_conftest_infos = []
- pluginmanager._ya_getconftestmodules_cache = {}
-
for conftest_info in initial_conftests:
_import_conftest(pluginmanager, conftest_info.module_name, conftest_info.proper_path)
- pluginmanager._getconftestmodules = functools.partial(_getconftestmodules_local, pluginmanager)
-
def _ya_register_non_initial_conftests():
for conftest_info in non_initial_conftests:
_import_conftest(pluginmanager, conftest_info.module_name, conftest_info.proper_path)
- # See collection.py, non-initial conftests are registered at the start of collection phase, like in vanilla.
- # Alas, stash is not available in py2.
- early_config._ya_register_non_initial_conftests = _ya_register_non_initial_conftests
else:
- _patch_inspect_getfile()
+ if six.PY2:
+ _patch_inspect_getfile()
- def conftest_key(name):
- if not name.startswith('__tests__.'):
- # Make __tests__ come last
- return '_.' + name
- return name
+ for name in sorted(conftests, key=_non_local_conftest_key):
+ _import_conftest_force_initial(pluginmanager, name)
- for name in sorted(conftests, key=conftest_key):
- module = importlib.import_module(name)
-
- setattr(module, '__orig_file__', module.__file__)
- module.__file__ = ''
-
- _conftest_modules.append(module)
- _consider_conftest(pluginmanager, module, module.__file__)
+ def _ya_register_non_initial_conftests():
+ pass
- pluginmanager._getconftestmodules = _getconftestmodules_non_local
+ # See collection.py, non-initial conftests are registered at the start of collection phase, like in vanilla.
+ # Alas, stash is not available in py2.
+ early_config._ya_register_non_initial_conftests = _ya_register_non_initial_conftests