diff options
| author | antonyzhilin <[email protected]> | 2026-03-16 16:02:58 +0300 |
|---|---|---|
| committer | antonyzhilin <[email protected]> | 2026-03-17 06:05:46 +0300 |
| commit | 8b3f2accec2c327842e23d6c7e3674acb11419ed (patch) | |
| tree | 6c506cbb27aef24df44ce865560786691263341a /library/python/pytest/plugins | |
| parent | 456efb878536940cf1442d584ea367ec4bcbfa0e (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.py | 54 | ||||
| -rw-r--r-- | library/python/pytest/plugins/conftests.py | 67 |
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 |
