diff options
author | Dmitry Kopylov <kopylovd@gmail.com> | 2022-02-10 16:48:18 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:48:18 +0300 |
commit | 7230275728d34873cba1ba78bb68669b0c5faa31 (patch) | |
tree | b222e5ac2e2e98872661c51ccceee5da0d291e13 /library/python/pytest | |
parent | b2f5101486cc0de2e979c8ba9ada2109785bf5fd (diff) | |
download | ydb-7230275728d34873cba1ba78bb68669b0c5faa31.tar.gz |
Restoring authorship annotation for Dmitry Kopylov <kopylovd@gmail.com>. Commit 2 of 2.
Diffstat (limited to 'library/python/pytest')
-rw-r--r-- | library/python/pytest/allure/conftest.py | 16 | ||||
-rw-r--r-- | library/python/pytest/empty/main.c | 14 | ||||
-rw-r--r-- | library/python/pytest/empty/ya.make | 10 | ||||
-rw-r--r-- | library/python/pytest/main.py | 38 | ||||
-rw-r--r-- | library/python/pytest/plugins/collection.py | 70 | ||||
-rw-r--r-- | library/python/pytest/plugins/conftests.py | 38 | ||||
-rw-r--r-- | library/python/pytest/plugins/fixtures.py | 120 | ||||
-rw-r--r-- | library/python/pytest/plugins/ya.make | 28 | ||||
-rw-r--r-- | library/python/pytest/plugins/ya.py | 862 | ||||
-rw-r--r-- | library/python/pytest/pytest.yatest.ini | 4 | ||||
-rw-r--r-- | library/python/pytest/ya.make | 28 | ||||
-rw-r--r-- | library/python/pytest/yatest_tools.py | 262 |
12 files changed, 745 insertions, 745 deletions
diff --git a/library/python/pytest/allure/conftest.py b/library/python/pytest/allure/conftest.py index 451e377cfc..0d5cfda1e5 100644 --- a/library/python/pytest/allure/conftest.py +++ b/library/python/pytest/allure/conftest.py @@ -1,8 +1,8 @@ -import os -import pytest - - -@pytest.mark.tryfirst -def pytest_configure(config): - if "ALLURE_REPORT_DIR" in os.environ: - config.option.allurereportdir = os.environ["ALLURE_REPORT_DIR"] +import os +import pytest + + +@pytest.mark.tryfirst +def pytest_configure(config): + if "ALLURE_REPORT_DIR" in os.environ: + config.option.allurereportdir = os.environ["ALLURE_REPORT_DIR"] diff --git a/library/python/pytest/empty/main.c b/library/python/pytest/empty/main.c index d49fcc7031..9efa08162a 100644 --- a/library/python/pytest/empty/main.c +++ b/library/python/pytest/empty/main.c @@ -1,7 +1,7 @@ -/* -to be used for build python tests in a stub binary for the case of using system python -*/ - -int main(void) { - return 0; -} +/* +to be used for build python tests in a stub binary for the case of using system python +*/ + +int main(void) { + return 0; +} diff --git a/library/python/pytest/empty/ya.make b/library/python/pytest/empty/ya.make index 4394568f15..8f0fa37e2a 100644 --- a/library/python/pytest/empty/ya.make +++ b/library/python/pytest/empty/ya.make @@ -1,12 +1,12 @@ -LIBRARY() - +LIBRARY() + OWNER( g:yatool dmitko ) - + SRCS( main.c ) - -END() + +END() diff --git a/library/python/pytest/main.py b/library/python/pytest/main.py index df3513031f..6296bd6f0f 100644 --- a/library/python/pytest/main.py +++ b/library/python/pytest/main.py @@ -1,25 +1,25 @@ import os -import sys +import sys import time import __res - + FORCE_EXIT_TESTSFAILED_ENV = 'FORCE_EXIT_TESTSFAILED' -def main(): +def main(): import library.python.pytest.context as context context.Ctx["YA_PYTEST_START_TIMESTAMP"] = time.time() profile = None if '--profile-pytest' in sys.argv: sys.argv.remove('--profile-pytest') - + import pstats import cProfile profile = cProfile.Profile() profile.enable() - + # Reset influencing env. vars # For more info see library/python/testing/yatest_common/yatest/common/errors.py if FORCE_EXIT_TESTSFAILED_ENV in os.environ: @@ -46,12 +46,12 @@ def main(): m.setattr(_pytest.assertion.rewrite, "AssertionRewritingHook", rewrite.AssertionRewritingHook) prefix = '__tests__.' - + test_modules = [ name[len(prefix):] for name in sys.extra_modules if name.startswith(prefix) and not name.endswith('.conftest') ] - + doctest_packages = __res.find("PY_DOCTEST_PACKAGES") or "" if isinstance(doctest_packages, bytes): doctest_packages = doctest_packages.decode('utf-8') @@ -70,20 +70,20 @@ def main(): def remove_user_site(paths): site_paths = ('site-packages', 'site-python') - + def is_site_path(path): for p in site_paths: if path.find(p) != -1: return True return False - + new_paths = list(paths) for p in paths: if is_site_path(p): new_paths.remove(p) - + return new_paths - + sys.path = remove_user_site(sys.path) rc = pytest.main(plugins=[ collection.CollectionPlugin(test_modules, doctest_modules), @@ -91,10 +91,10 @@ def main(): conftests, ]) - if rc == 5: - # don't care about EXIT_NOTESTSCOLLECTED - rc = 0 - + if rc == 5: + # don't care about EXIT_NOTESTSCOLLECTED + rc = 0 + if rc == 1 and yatest_runner and not listing_mode and not os.environ.get(FORCE_EXIT_TESTSFAILED_ENV) == '1': # XXX it's place for future improvements # Test wrapper should terminate with 0 exit code if there are common test failures @@ -110,7 +110,7 @@ def main(): ps.print_stats() sys.exit(rc) - - -if __name__ == '__main__': - main() + + +if __name__ == '__main__': + main() diff --git a/library/python/pytest/plugins/collection.py b/library/python/pytest/plugins/collection.py index 1535da686c..e36f47a78f 100644 --- a/library/python/pytest/plugins/collection.py +++ b/library/python/pytest/plugins/collection.py @@ -1,26 +1,26 @@ import os -import sys +import sys from six import reraise - -import py + +import py import pytest # noqa -import _pytest.python -import _pytest.doctest +import _pytest.python +import _pytest.doctest import json import library.python.testing.filter.filter as test_filter - - -class LoadedModule(_pytest.python.Module): + + +class LoadedModule(_pytest.python.Module): def __init__(self, parent, name, **kwargs): self.name = name + '.py' self.session = parent self.parent = parent self.config = parent.config - self.keywords = {} + self.keywords = {} self.own_markers = [] self.fspath = py.path.local() - + @classmethod def from_parent(cls, **kwargs): namespace = kwargs.pop('namespace', True) @@ -31,7 +31,7 @@ class LoadedModule(_pytest.python.Module): return loaded_module - @property + @property def _nodeid(self): if os.getenv('CONFTEST_LOAD_POLICY') == 'LOCAL': return self._getobj().__file__ @@ -41,25 +41,25 @@ class LoadedModule(_pytest.python.Module): @property def nodeid(self): return self._nodeid - - def _getobj(self): + + def _getobj(self): module_name = self.name[:-len('.py')] if self.namespace: module_name = '__tests__.' + module_name - __import__(module_name) - return sys.modules[module_name] - - -class DoctestModule(LoadedModule): - - def collect(self): - import doctest + __import__(module_name) + return sys.modules[module_name] + + +class DoctestModule(LoadedModule): + + def collect(self): + import doctest module = self._getobj() # uses internal doctest module parsing mechanism - finder = doctest.DocTestFinder() - optionflags = _pytest.doctest.get_optionflags(self) - runner = doctest.DebugRunner(verbose=0, optionflags=optionflags) + finder = doctest.DocTestFinder() + optionflags = _pytest.doctest.get_optionflags(self) + runner = doctest.DebugRunner(verbose=0, optionflags=optionflags) try: for test in finder.find(module, self.name[:-len('.py')]): @@ -75,8 +75,8 @@ class DoctestModule(LoadedModule): etype, exc, tb = sys.exc_info() msg = 'DoctestModule failed, probably you can add NO_DOCTESTS() macro to ya.make' reraise(etype, type(exc)('{}\n{}'.format(exc, msg)), tb) - - + + # NOTE: Since we are overriding collect method of pytest session, pytest hooks are not invoked during collection. def pytest_ignore_collect(module, session, filenames_from_full_filters, accept_filename_predicate): if session.config.option.mode == 'list': @@ -93,14 +93,14 @@ def pytest_ignore_collect(module, session, filenames_from_full_filters, accept_f return False -class CollectionPlugin(object): +class CollectionPlugin(object): def __init__(self, test_modules, doctest_modules): - self._test_modules = test_modules + self._test_modules = test_modules self._doctest_modules = doctest_modules - - def pytest_sessionstart(self, session): - - def collect(*args, **kwargs): + + def pytest_sessionstart(self, session): + + def collect(*args, **kwargs): accept_filename_predicate = test_filter.make_py_file_filter(session.config.option.test_filter) full_test_names_file_path = session.config.option.test_list_path filenames_filter = None @@ -111,7 +111,7 @@ class CollectionPlugin(object): full_names_filter = set(json.load(afile)[int(session.config.option.modulo_index)]) filenames_filter = set(map(lambda x: x.split('::')[0], full_names_filter)) - for test_module in self._test_modules: + for test_module in self._test_modules: module = LoadedModule.from_parent(name=test_module, parent=session) if not pytest_ignore_collect(module, session, filenames_filter, accept_filename_predicate): yield module @@ -120,9 +120,9 @@ class CollectionPlugin(object): module = DoctestModule.from_parent(name=test_module, parent=session) if not pytest_ignore_collect(module, session, filenames_filter, accept_filename_predicate): yield module - + 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) - session.collect = collect + session.collect = collect diff --git a/library/python/pytest/plugins/conftests.py b/library/python/pytest/plugins/conftests.py index dfae771ef8..522041f5a7 100644 --- a/library/python/pytest/plugins/conftests.py +++ b/library/python/pytest/plugins/conftests.py @@ -1,26 +1,26 @@ import os -import importlib +import importlib import sys -import inspect +import inspect from pytest import hookimpl from .fixtures import metrics, links # noqa - -orig_getfile = inspect.getfile - - -def getfile(object): - res = orig_getfile(object) - if inspect.ismodule(object): - if not res and getattr(object, '__orig_file__'): - res = object.__orig_file__ - return res - -inspect.getfile = getfile + +orig_getfile = inspect.getfile + + +def getfile(object): + res = orig_getfile(object) + if inspect.ismodule(object): + if not res and getattr(object, '__orig_file__'): + res = object.__orig_file__ + return res + +inspect.getfile = getfile conftest_modules = [] - - + + @hookimpl(trylast=True) def pytest_load_initial_conftests(early_config, parser, args): conftests = filter(lambda name: name.endswith(".conftest"), sys.extra_modules) @@ -45,6 +45,6 @@ def getconftestmodules(*args, **kwargs): def pytest_sessionstart(session): - # Override filesystem based relevant conftest discovery on the call path - assert session.config.pluginmanager - session.config.pluginmanager._getconftestmodules = getconftestmodules + # Override filesystem based relevant conftest discovery on the call path + assert session.config.pluginmanager + session.config.pluginmanager._getconftestmodules = getconftestmodules diff --git a/library/python/pytest/plugins/fixtures.py b/library/python/pytest/plugins/fixtures.py index 9f5fd6ccf1..6f7e0a27e4 100644 --- a/library/python/pytest/plugins/fixtures.py +++ b/library/python/pytest/plugins/fixtures.py @@ -1,26 +1,26 @@ -import os -import pytest +import os +import pytest import six - - -MAX_ALLOWED_LINKS_COUNT = 10 - - -@pytest.fixture -def metrics(request): - - class Metrics(object): - @classmethod - def set(cls, name, value): - assert len(name) <= 128, "Length of the metric name must less than 128" - assert type(value) in [int, float], "Metric value must be of type int or float" - test_name = request.node.nodeid - if test_name not in request.config.test_metrics: - request.config.test_metrics[test_name] = {} - request.config.test_metrics[test_name][name] = value - - @classmethod - def set_benchmark(cls, benchmark_values): + + +MAX_ALLOWED_LINKS_COUNT = 10 + + +@pytest.fixture +def metrics(request): + + class Metrics(object): + @classmethod + def set(cls, name, value): + assert len(name) <= 128, "Length of the metric name must less than 128" + assert type(value) in [int, float], "Metric value must be of type int or float" + test_name = request.node.nodeid + if test_name not in request.config.test_metrics: + request.config.test_metrics[test_name] = {} + request.config.test_metrics[test_name][name] = value + + @classmethod + def set_benchmark(cls, benchmark_values): # report of google has key 'benchmarks' which is a list of benchmark results # yandex benchmark has key 'benchmark', which is a list of benchmark results # use this to differentiate which kind of result it is @@ -31,12 +31,12 @@ def metrics(request): @classmethod def set_ybenchmark(cls, benchmark_values): - for benchmark in benchmark_values["benchmark"]: - name = benchmark["name"] + for benchmark in benchmark_values["benchmark"]: + name = benchmark["name"] for key, value in six.iteritems(benchmark): - if key != "name": - cls.set("{}_{}".format(name, key), value) - + if key != "name": + cls.set("{}_{}".format(name, key), value) + @classmethod def set_gbenchmark(cls, benchmark_values): time_unit_multipliers = {"ns": 1, "us": 1000, "ms": 1000000} @@ -50,36 +50,36 @@ def metrics(request): cls.set("{}_{}".format(name, k), v * time_unit_mult) elif k not in ignore_keys and isinstance(v, (float, int)): cls.set("{}_{}".format(name, k), v) - return Metrics - - -@pytest.fixture -def links(request): - - class Links(object): - @classmethod - def set(cls, name, path): - - if len(request.config.test_logs[request.node.nodeid]) >= MAX_ALLOWED_LINKS_COUNT: - raise Exception("Cannot add more than {} links to test".format(MAX_ALLOWED_LINKS_COUNT)) - - reserved_names = ["log", "logsdir", "stdout", "stderr"] - if name in reserved_names: - raise Exception("Attachment name should not belong to the reserved list: {}".format(", ".join(reserved_names))) - output_dir = request.config.ya.output_dir - - if not os.path.exists(path): - raise Exception("Path to be attached does not exist: {}".format(path)) - - if os.path.isabs(path) and ".." in os.path.relpath(path, output_dir): - raise Exception("Test attachment must be inside yatest.common.output_path()") - - request.config.test_logs[request.node.nodeid][name] = path - - @classmethod - def get(cls, name): - if name not in request.config.test_logs[request.node.nodeid]: - raise KeyError("Attachment with name '{}' does not exist".format(name)) - return request.config.test_logs[request.node.nodeid][name] - - return Links + return Metrics + + +@pytest.fixture +def links(request): + + class Links(object): + @classmethod + def set(cls, name, path): + + if len(request.config.test_logs[request.node.nodeid]) >= MAX_ALLOWED_LINKS_COUNT: + raise Exception("Cannot add more than {} links to test".format(MAX_ALLOWED_LINKS_COUNT)) + + reserved_names = ["log", "logsdir", "stdout", "stderr"] + if name in reserved_names: + raise Exception("Attachment name should not belong to the reserved list: {}".format(", ".join(reserved_names))) + output_dir = request.config.ya.output_dir + + if not os.path.exists(path): + raise Exception("Path to be attached does not exist: {}".format(path)) + + if os.path.isabs(path) and ".." in os.path.relpath(path, output_dir): + raise Exception("Test attachment must be inside yatest.common.output_path()") + + request.config.test_logs[request.node.nodeid][name] = path + + @classmethod + def get(cls, name): + if name not in request.config.test_logs[request.node.nodeid]: + raise KeyError("Attachment with name '{}' does not exist".format(name)) + return request.config.test_logs[request.node.nodeid][name] + + return Links diff --git a/library/python/pytest/plugins/ya.make b/library/python/pytest/plugins/ya.make index 07914cf4d3..c15d6f759d 100644 --- a/library/python/pytest/plugins/ya.make +++ b/library/python/pytest/plugins/ya.make @@ -1,20 +1,20 @@ OWNER(g:yatest) - + PY23_LIBRARY() - -PY_SRCS( - ya.py - collection.py - conftests.py - fixtures.py -) - -PEERDIR( + +PY_SRCS( + ya.py + collection.py + conftests.py + fixtures.py +) + +PEERDIR( library/python/filelock - library/python/find_root + library/python/find_root library/python/testing/filter -) - +) + IF (PYTHON2) PY_SRCS( fakeid_py2.py @@ -29,4 +29,4 @@ ELSE() ) ENDIF() -END() +END() diff --git a/library/python/pytest/plugins/ya.py b/library/python/pytest/plugins/ya.py index c6b06478d9..1bde03042d 100644 --- a/library/python/pytest/plugins/ya.py +++ b/library/python/pytest/plugins/ya.py @@ -3,101 +3,101 @@ import base64 import errno import re -import sys -import os -import logging -import fnmatch -import json -import time +import sys +import os +import logging +import fnmatch +import json +import time import traceback -import collections +import collections import signal import inspect import warnings import attr import faulthandler -import py -import pytest +import py +import pytest import six -import _pytest +import _pytest import _pytest._io import _pytest.mark import _pytest.outcomes import _pytest.skipping - + from _pytest.warning_types import PytestUnhandledCoroutineWarning from yatest_lib import test_splitter -try: - import resource -except ImportError: - resource = None - -try: - import library.python.pytest.yatest_tools as tools -except ImportError: - # fallback for pytest script mode - import yatest_tools as tools - +try: + import resource +except ImportError: + resource = None + +try: + import library.python.pytest.yatest_tools as tools +except ImportError: + # fallback for pytest script mode + import yatest_tools as tools + try: from library.python import filelock except ImportError: filelock = None -import yatest_lib.tools - -import yatest_lib.external as canon - +import yatest_lib.tools + +import yatest_lib.external as canon + import yatest_lib.ya from library.python.pytest import context -console_logger = logging.getLogger("console") -yatest_logger = logging.getLogger("ya.test") - - -_pytest.main.EXIT_NOTESTSCOLLECTED = 0 +console_logger = logging.getLogger("console") +yatest_logger = logging.getLogger("ya.test") + + +_pytest.main.EXIT_NOTESTSCOLLECTED = 0 SHUTDOWN_REQUESTED = False - + pytest_config = None - -def configure_pdb_on_demand(): - import signal - - if hasattr(signal, "SIGUSR1"): - def on_signal(*args): + +def configure_pdb_on_demand(): + import signal + + if hasattr(signal, "SIGUSR1"): + def on_signal(*args): import ipdb ipdb.set_trace() - - signal.signal(signal.SIGUSR1, on_signal) - - -class CustomImporter(object): - def __init__(self, roots): - self._roots = roots - - def find_module(self, fullname, package_path=None): - for path in self._roots: - full_path = self._get_module_path(path, fullname) - - if os.path.exists(full_path) and os.path.isdir(full_path) and not os.path.exists(os.path.join(full_path, "__init__.py")): - open(os.path.join(full_path, "__init__.py"), "w").close() - - return None - - def _get_module_path(self, path, fullname): - return os.path.join(path, *fullname.split('.')) - - -class YaTestLoggingFileHandler(logging.FileHandler): - pass - - + + signal.signal(signal.SIGUSR1, on_signal) + + +class CustomImporter(object): + def __init__(self, roots): + self._roots = roots + + def find_module(self, fullname, package_path=None): + for path in self._roots: + full_path = self._get_module_path(path, fullname) + + if os.path.exists(full_path) and os.path.isdir(full_path) and not os.path.exists(os.path.join(full_path, "__init__.py")): + open(os.path.join(full_path, "__init__.py"), "w").close() + + return None + + def _get_module_path(self, path, fullname): + return os.path.join(path, *fullname.split('.')) + + +class YaTestLoggingFileHandler(logging.FileHandler): + pass + + class _TokenFilterFormatter(logging.Formatter): def __init__(self, fmt): super(_TokenFilterFormatter, self).__init__(fmt) @@ -123,141 +123,141 @@ class _TokenFilterFormatter(logging.Formatter): return self._filter(super(_TokenFilterFormatter, self).format(record)) -def setup_logging(log_path, level=logging.DEBUG, *other_logs): - logs = [log_path] + list(other_logs) - root_logger = logging.getLogger() - for i in range(len(root_logger.handlers) - 1, -1, -1): - if isinstance(root_logger.handlers[i], YaTestLoggingFileHandler): +def setup_logging(log_path, level=logging.DEBUG, *other_logs): + logs = [log_path] + list(other_logs) + root_logger = logging.getLogger() + for i in range(len(root_logger.handlers) - 1, -1, -1): + if isinstance(root_logger.handlers[i], YaTestLoggingFileHandler): root_logger.handlers.pop(i).close() - root_logger.setLevel(level) - for log_file in logs: - file_handler = YaTestLoggingFileHandler(log_file) - log_format = '%(asctime)s - %(levelname)s - %(name)s - %(funcName)s: %(message)s' + root_logger.setLevel(level) + for log_file in logs: + file_handler = YaTestLoggingFileHandler(log_file) + log_format = '%(asctime)s - %(levelname)s - %(name)s - %(funcName)s: %(message)s' file_handler.setFormatter(_TokenFilterFormatter(log_format)) - file_handler.setLevel(level) - root_logger.addHandler(file_handler) - - -def pytest_addoption(parser): - parser.addoption("--build-root", action="store", dest="build_root", default="", help="path to the build root") - parser.addoption("--dep-root", action="append", dest="dep_roots", default=[], help="path to the dep build roots") - parser.addoption("--source-root", action="store", dest="source_root", default="", help="path to the source root") - parser.addoption("--data-root", action="store", dest="data_root", default="", help="path to the arcadia_tests_data root") - parser.addoption("--output-dir", action="store", dest="output_dir", default="", help="path to the test output dir") - parser.addoption("--python-path", action="store", dest="python_path", default="", help="path the canonical python binary") - parser.addoption("--valgrind-path", action="store", dest="valgrind_path", default="", help="path the canonical valgring binary") - parser.addoption("--test-filter", action="append", dest="test_filter", default=None, help="test filter") + file_handler.setLevel(level) + root_logger.addHandler(file_handler) + + +def pytest_addoption(parser): + parser.addoption("--build-root", action="store", dest="build_root", default="", help="path to the build root") + parser.addoption("--dep-root", action="append", dest="dep_roots", default=[], help="path to the dep build roots") + parser.addoption("--source-root", action="store", dest="source_root", default="", help="path to the source root") + parser.addoption("--data-root", action="store", dest="data_root", default="", help="path to the arcadia_tests_data root") + parser.addoption("--output-dir", action="store", dest="output_dir", default="", help="path to the test output dir") + parser.addoption("--python-path", action="store", dest="python_path", default="", help="path the canonical python binary") + parser.addoption("--valgrind-path", action="store", dest="valgrind_path", default="", help="path the canonical valgring binary") + parser.addoption("--test-filter", action="append", dest="test_filter", default=None, help="test filter") parser.addoption("--test-file-filter", action="store", dest="test_file_filter", default=None, help="test file filter") - parser.addoption("--test-param", action="append", dest="test_params", default=None, help="test parameters") - parser.addoption("--test-log-level", action="store", dest="test_log_level", choices=["critical", "error", "warning", "info", "debug"], default="debug", help="test log level") + parser.addoption("--test-param", action="append", dest="test_params", default=None, help="test parameters") + parser.addoption("--test-log-level", action="store", dest="test_log_level", choices=["critical", "error", "warning", "info", "debug"], default="debug", help="test log level") parser.addoption("--mode", action="store", choices=[yatest_lib.ya.RunMode.List, yatest_lib.ya.RunMode.Run], dest="mode", default=yatest_lib.ya.RunMode.Run, help="testing mode") parser.addoption("--test-list-file", action="store", dest="test_list_file") - parser.addoption("--modulo", default=1, type=int) - parser.addoption("--modulo-index", default=0, type=int) + parser.addoption("--modulo", default=1, type=int) + parser.addoption("--modulo-index", default=0, type=int) parser.addoption("--partition-mode", default='SEQUENTIAL', help="Split tests according to partitoin mode") - parser.addoption("--split-by-tests", action='store_true', help="Split test execution by tests instead of suites", default=False) - parser.addoption("--project-path", action="store", default="", help="path to CMakeList where test is declared") - parser.addoption("--build-type", action="store", default="", help="build type") + parser.addoption("--split-by-tests", action='store_true', help="Split test execution by tests instead of suites", default=False) + parser.addoption("--project-path", action="store", default="", help="path to CMakeList where test is declared") + parser.addoption("--build-type", action="store", default="", help="build type") parser.addoption("--flags", action="append", dest="flags", default=[], help="build flags (-D)") parser.addoption("--sanitize", action="store", default="", help="sanitize mode") - parser.addoption("--test-stderr", action="store_true", default=False, help="test stderr") + parser.addoption("--test-stderr", action="store_true", default=False, help="test stderr") parser.addoption("--test-debug", action="store_true", default=False, help="test debug mode") - parser.addoption("--root-dir", action="store", default=None) - parser.addoption("--ya-trace", action="store", dest="ya_trace_path", default=None, help="path to ya trace report") + parser.addoption("--root-dir", action="store", default=None) + parser.addoption("--ya-trace", action="store", dest="ya_trace_path", default=None, help="path to ya trace report") parser.addoption("--ya-version", action="store", dest="ya_version", default=0, type=int, help="allows to be compatible with ya and the new changes in ya-dev") - parser.addoption( - "--test-suffix", action="store", dest="test_suffix", default=None, help="add suffix to every test name" - ) - parser.addoption("--gdb-path", action="store", dest="gdb_path", default="", help="path the canonical gdb binary") - parser.addoption("--collect-cores", action="store_true", dest="collect_cores", default=False, help="allows core dump file recovering during test") + parser.addoption( + "--test-suffix", action="store", dest="test_suffix", default=None, help="add suffix to every test name" + ) + parser.addoption("--gdb-path", action="store", dest="gdb_path", default="", help="path the canonical gdb binary") + parser.addoption("--collect-cores", action="store_true", dest="collect_cores", default=False, help="allows core dump file recovering during test") parser.addoption("--sanitizer-extra-checks", action="store_true", dest="sanitizer_extra_checks", default=False, help="enables extra checks for tests built with sanitizers") - parser.addoption("--report-deselected", action="store_true", dest="report_deselected", default=False, help="report deselected tests to the trace file") - parser.addoption("--pdb-on-sigusr1", action="store_true", default=False, help="setup pdb.set_trace on SIGUSR1") + parser.addoption("--report-deselected", action="store_true", dest="report_deselected", default=False, help="report deselected tests to the trace file") + parser.addoption("--pdb-on-sigusr1", action="store_true", default=False, help="setup pdb.set_trace on SIGUSR1") parser.addoption("--test-tool-bin", help="Path to test_tool") parser.addoption("--test-list-path", dest="test_list_path", action="store", help="path to test list", default="") - - + + def from_ya_test(): return "YA_TEST_RUNNER" in os.environ -def pytest_configure(config): +def pytest_configure(config): global pytest_config pytest_config = config config.option.continue_on_collection_errors = True - + config.addinivalue_line("markers", "ya:external") config.from_ya_test = from_ya_test() - config.test_logs = collections.defaultdict(dict) - config.test_metrics = {} + config.test_logs = collections.defaultdict(dict) + config.test_metrics = {} config.suite_metrics = {} config.configure_timestamp = time.time() - context = { - "project_path": config.option.project_path, - "test_stderr": config.option.test_stderr, + context = { + "project_path": config.option.project_path, + "test_stderr": config.option.test_stderr, "test_debug": config.option.test_debug, - "build_type": config.option.build_type, - "test_traceback": config.option.tbstyle, + "build_type": config.option.build_type, + "test_traceback": config.option.tbstyle, "flags": config.option.flags, "sanitize": config.option.sanitize, - } + } if config.option.collectonly: config.option.mode = yatest_lib.ya.RunMode.List config.ya = yatest_lib.ya.Ya( - config.option.mode, - config.option.source_root, - config.option.build_root, - config.option.dep_roots, - config.option.output_dir, - config.option.test_params, - context, - config.option.python_path, - config.option.valgrind_path, - config.option.gdb_path, - config.option.data_root, - ) - config.option.test_log_level = { - "critical": logging.CRITICAL, - "error": logging.ERROR, - "warning": logging.WARN, - "info": logging.INFO, - "debug": logging.DEBUG, - }[config.option.test_log_level] - - if not config.option.collectonly: - setup_logging(os.path.join(config.ya.output_dir, "run.log"), config.option.test_log_level) - config.current_item_nodeid = None - config.current_test_name = None - config.test_cores_count = 0 - config.collect_cores = config.option.collect_cores + config.option.mode, + config.option.source_root, + config.option.build_root, + config.option.dep_roots, + config.option.output_dir, + config.option.test_params, + context, + config.option.python_path, + config.option.valgrind_path, + config.option.gdb_path, + config.option.data_root, + ) + config.option.test_log_level = { + "critical": logging.CRITICAL, + "error": logging.ERROR, + "warning": logging.WARN, + "info": logging.INFO, + "debug": logging.DEBUG, + }[config.option.test_log_level] + + if not config.option.collectonly: + setup_logging(os.path.join(config.ya.output_dir, "run.log"), config.option.test_log_level) + config.current_item_nodeid = None + config.current_test_name = None + config.test_cores_count = 0 + config.collect_cores = config.option.collect_cores config.sanitizer_extra_checks = config.option.sanitizer_extra_checks try: config.test_tool_bin = config.option.test_tool_bin except AttributeError: logging.info("test_tool_bin not specified") - - if config.sanitizer_extra_checks: + + if config.sanitizer_extra_checks: for envvar in ['LSAN_OPTIONS', 'ASAN_OPTIONS']: if envvar in os.environ: os.environ.pop(envvar) if envvar + '_ORIGINAL' in os.environ: os.environ[envvar] = os.environ[envvar + '_ORIGINAL'] - if config.option.root_dir: + if config.option.root_dir: config.rootdir = py.path.local(config.option.root_dir) config.invocation_params = attr.evolve(config.invocation_params, dir=config.rootdir) - + extra_sys_path = [] # Arcadia paths from the test DEPENDS section of ya.make extra_sys_path.append(os.path.join(config.option.source_root, config.option.project_path)) - # Build root is required for correct import of protobufs, because imports are related to the root - # (like import devtools.dummy_arcadia.protos.lib.my_proto_pb2) + # Build root is required for correct import of protobufs, because imports are related to the root + # (like import devtools.dummy_arcadia.protos.lib.my_proto_pb2) extra_sys_path.append(config.option.build_root) - + for path in config.option.dep_roots: if os.path.isabs(path): extra_sys_path.append(path) @@ -272,17 +272,17 @@ def pytest_configure(config): os.environ["PYTHONPATH"] = os.pathsep.join(sys.path) - if not config.option.collectonly: - if config.option.ya_trace_path: - config.ya_trace_reporter = TraceReportGenerator(config.option.ya_trace_path) - else: - config.ya_trace_reporter = DryTraceReportGenerator(config.option.ya_trace_path) + if not config.option.collectonly: + if config.option.ya_trace_path: + config.ya_trace_reporter = TraceReportGenerator(config.option.ya_trace_path) + else: + config.ya_trace_reporter = DryTraceReportGenerator(config.option.ya_trace_path) config.ya_version = config.option.ya_version - - sys.meta_path.append(CustomImporter([config.option.build_root] + [os.path.join(config.option.build_root, dep) for dep in config.option.dep_roots])) - if config.option.pdb_on_sigusr1: - configure_pdb_on_demand() - + + sys.meta_path.append(CustomImporter([config.option.build_root] + [os.path.join(config.option.build_root, dep) for dep in config.option.dep_roots])) + if config.option.pdb_on_sigusr1: + configure_pdb_on_demand() + # Dump python backtrace in case of any errors faulthandler.enable() if hasattr(signal, "SIGQUIT"): @@ -291,7 +291,7 @@ def pytest_configure(config): if hasattr(signal, "SIGUSR2"): signal.signal(signal.SIGUSR2, _graceful_shutdown) - + session_should_exit = False @@ -327,122 +327,122 @@ def _graceful_shutdown(*args): _graceful_shutdown_on_log(not capman.is_globally_capturing()) -def _get_rusage(): - return resource and resource.getrusage(resource.RUSAGE_SELF) - - -def _collect_test_rusage(item): - if resource and hasattr(item, "rusage"): - finish_rusage = _get_rusage() +def _get_rusage(): + return resource and resource.getrusage(resource.RUSAGE_SELF) + + +def _collect_test_rusage(item): + if resource and hasattr(item, "rusage"): + finish_rusage = _get_rusage() ya_inst = pytest_config.ya - - def add_metric(attr_name, metric_name=None, modifier=None): - if not metric_name: - metric_name = attr_name - if not modifier: - modifier = lambda x: x - if hasattr(item.rusage, attr_name): + + def add_metric(attr_name, metric_name=None, modifier=None): + if not metric_name: + metric_name = attr_name + if not modifier: + modifier = lambda x: x + if hasattr(item.rusage, attr_name): ya_inst.set_metric_value(metric_name, modifier(getattr(finish_rusage, attr_name) - getattr(item.rusage, attr_name))) - - for args in [ - ("ru_maxrss", "ru_rss", lambda x: x*1024), # to be the same as in util/system/rusage.cpp - ("ru_utime",), - ("ru_stime",), - ("ru_ixrss", None, lambda x: x*1024), - ("ru_idrss", None, lambda x: x*1024), - ("ru_isrss", None, lambda x: x*1024), - ("ru_majflt", "ru_major_pagefaults"), - ("ru_minflt", "ru_minor_pagefaults"), - ("ru_nswap",), - ("ru_inblock",), - ("ru_oublock",), - ("ru_msgsnd",), - ("ru_msgrcv",), - ("ru_nsignals",), - ("ru_nvcsw",), - ("ru_nivcsw",), - ]: - add_metric(*args) - - -def _get_item_tags(item): - tags = [] - for key, value in item.keywords.items(): + + for args in [ + ("ru_maxrss", "ru_rss", lambda x: x*1024), # to be the same as in util/system/rusage.cpp + ("ru_utime",), + ("ru_stime",), + ("ru_ixrss", None, lambda x: x*1024), + ("ru_idrss", None, lambda x: x*1024), + ("ru_isrss", None, lambda x: x*1024), + ("ru_majflt", "ru_major_pagefaults"), + ("ru_minflt", "ru_minor_pagefaults"), + ("ru_nswap",), + ("ru_inblock",), + ("ru_oublock",), + ("ru_msgsnd",), + ("ru_msgrcv",), + ("ru_nsignals",), + ("ru_nvcsw",), + ("ru_nivcsw",), + ]: + add_metric(*args) + + +def _get_item_tags(item): + tags = [] + for key, value in item.keywords.items(): if key == 'pytestmark' and isinstance(value, list): for mark in value: tags.append(mark.name) elif isinstance(value, _pytest.mark.MarkDecorator): - tags.append(key) - return tags - - -def pytest_runtest_setup(item): - item.rusage = _get_rusage() + tags.append(key) + return tags + + +def pytest_runtest_setup(item): + item.rusage = _get_rusage() pytest_config.test_cores_count = 0 pytest_config.current_item_nodeid = item.nodeid - class_name, test_name = tools.split_node_id(item.nodeid) + class_name, test_name = tools.split_node_id(item.nodeid) test_log_path = tools.get_test_log_file_path(pytest_config.ya.output_dir, class_name, test_name) - setup_logging( + setup_logging( os.path.join(pytest_config.ya.output_dir, "run.log"), pytest_config.option.test_log_level, - test_log_path - ) + test_log_path + ) pytest_config.test_logs[item.nodeid]['log'] = test_log_path pytest_config.test_logs[item.nodeid]['logsdir'] = pytest_config.ya.output_dir pytest_config.current_test_log_path = test_log_path pytest_config.current_test_name = "{}::{}".format(class_name, test_name) - separator = "#" * 100 - yatest_logger.info(separator) - yatest_logger.info(test_name) - yatest_logger.info(separator) - yatest_logger.info("Test setup") - + separator = "#" * 100 + yatest_logger.info(separator) + yatest_logger.info(test_name) + yatest_logger.info(separator) + yatest_logger.info("Test setup") + test_item = CrashedTestItem(item.nodeid, pytest_config.option.test_suffix) pytest_config.ya_trace_reporter.on_start_test_class(test_item) pytest_config.ya_trace_reporter.on_start_test_case(test_item) - - -def pytest_runtest_teardown(item, nextitem): - yatest_logger.info("Test teardown") - - -def pytest_runtest_call(item): + + +def pytest_runtest_teardown(item, nextitem): + yatest_logger.info("Test teardown") + + +def pytest_runtest_call(item): class_name, test_name = tools.split_node_id(item.nodeid) yatest_logger.info("Test call (class_name: %s, test_name: %s)", class_name, test_name) - - -def pytest_deselected(items): + + +def pytest_deselected(items): config = pytest_config - if config.option.report_deselected: - for item in items: - deselected_item = DeselectedTestItem(item.nodeid, config.option.test_suffix) - config.ya_trace_reporter.on_start_test_class(deselected_item) - config.ya_trace_reporter.on_finish_test_case(deselected_item) - config.ya_trace_reporter.on_finish_test_class(deselected_item) - - -@pytest.mark.trylast -def pytest_collection_modifyitems(items, config): - - def filter_items(filters): - filtered_items = [] - deselected_items = [] - for item in items: + if config.option.report_deselected: + for item in items: + deselected_item = DeselectedTestItem(item.nodeid, config.option.test_suffix) + config.ya_trace_reporter.on_start_test_class(deselected_item) + config.ya_trace_reporter.on_finish_test_case(deselected_item) + config.ya_trace_reporter.on_finish_test_class(deselected_item) + + +@pytest.mark.trylast +def pytest_collection_modifyitems(items, config): + + def filter_items(filters): + filtered_items = [] + deselected_items = [] + for item in items: canonical_node_id = str(CustomTestItem(item.nodeid, pytest_config.option.test_suffix)) - matched = False - for flt in filters: + matched = False + for flt in filters: if "::" not in flt and "*" not in flt: - flt += "*" # add support for filtering by module name - if canonical_node_id.endswith(flt) or fnmatch.fnmatch(tools.escape_for_fnmatch(canonical_node_id), tools.escape_for_fnmatch(flt)): - matched = True - if matched: - filtered_items.append(item) - else: - deselected_items.append(item) - - config.hook.pytest_deselected(items=deselected_items) - items[:] = filtered_items - + flt += "*" # add support for filtering by module name + if canonical_node_id.endswith(flt) or fnmatch.fnmatch(tools.escape_for_fnmatch(canonical_node_id), tools.escape_for_fnmatch(flt)): + matched = True + if matched: + filtered_items.append(item) + else: + deselected_items.append(item) + + config.hook.pytest_deselected(items=deselected_items) + items[:] = filtered_items + def filter_by_full_name(filters): filter_set = {flt for flt in filters} filtered_items = [] @@ -456,10 +456,10 @@ def pytest_collection_modifyitems(items, config): config.hook.pytest_deselected(items=deselected_items) items[:] = filtered_items - # XXX - check to be removed when tests for peerdirs don't run - for item in items: - if not item.nodeid: - item._nodeid = os.path.basename(item.location[0]) + # XXX - check to be removed when tests for peerdirs don't run + for item in items: + if not item.nodeid: + item._nodeid = os.path.basename(item.location[0]) if os.path.exists(config.option.test_list_path): with open(config.option.test_list_path, 'r') as afile: chunks = json.load(afile) @@ -490,39 +490,39 @@ def pytest_collection_modifyitems(items, config): for item in chunk_items: items.extend(item) yatest_logger.info("Modulo %s tests are: %s", modulo_index, chunk_items) - + if config.option.mode == yatest_lib.ya.RunMode.Run: - for item in items: - test_item = NotLaunchedTestItem(item.nodeid, config.option.test_suffix) - config.ya_trace_reporter.on_start_test_class(test_item) - config.ya_trace_reporter.on_finish_test_case(test_item) - config.ya_trace_reporter.on_finish_test_class(test_item) + for item in items: + test_item = NotLaunchedTestItem(item.nodeid, config.option.test_suffix) + config.ya_trace_reporter.on_start_test_class(test_item) + config.ya_trace_reporter.on_finish_test_case(test_item) + config.ya_trace_reporter.on_finish_test_class(test_item) elif config.option.mode == yatest_lib.ya.RunMode.List: - tests = [] - for item in items: + tests = [] + for item in items: item = CustomTestItem(item.nodeid, pytest_config.option.test_suffix, item.keywords) - record = { - "class": item.class_name, - "test": item.test_name, - "tags": _get_item_tags(item), - } - tests.append(record) + record = { + "class": item.class_name, + "test": item.test_name, + "tags": _get_item_tags(item), + } + tests.append(record) if config.option.test_list_file: with open(config.option.test_list_file, 'w') as afile: json.dump(tests, afile) # TODO prettyboy remove after test_tool release - currently it's required for backward compatibility - sys.stderr.write(json.dumps(tests)) - - -def pytest_collectreport(report): - if not report.passed: + sys.stderr.write(json.dumps(tests)) + + +def pytest_collectreport(report): + if not report.passed: if hasattr(pytest_config, 'ya_trace_reporter'): test_item = TestItem(report, None, pytest_config.option.test_suffix) pytest_config.ya_trace_reporter.on_error(test_item) - else: - sys.stderr.write(yatest_lib.tools.to_utf8(report.longrepr)) - - + else: + sys.stderr.write(yatest_lib.tools.to_utf8(report.longrepr)) + + @pytest.mark.tryfirst def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj @@ -542,7 +542,7 @@ def pytest_pyfunc_call(pyfuncitem): @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item, call): +def pytest_runtest_makereport(item, call): def logreport(report, result, call): test_item = TestItem(report, result, pytest_config.option.test_suffix) if not pytest_config.suite_metrics and context.Ctx.get("YA_PYTEST_START_TIMESTAMP"): @@ -554,24 +554,24 @@ def pytest_runtest_makereport(item, call): if report.outcome == "failed": yatest_logger.error(report.longrepr) - if report.when == "call": - _collect_test_rusage(item) + if report.when == "call": + _collect_test_rusage(item) pytest_config.ya_trace_reporter.on_finish_test_case(test_item) - elif report.when == "setup": + elif report.when == "setup": pytest_config.ya_trace_reporter.on_start_test_class(test_item) - if report.outcome != "passed": + if report.outcome != "passed": pytest_config.ya_trace_reporter.on_start_test_case(test_item) pytest_config.ya_trace_reporter.on_finish_test_case(test_item) - else: + else: pytest_config.ya_trace_reporter.on_start_test_case(test_item) - elif report.when == "teardown": - if report.outcome == "failed": + elif report.when == "teardown": + if report.outcome == "failed": pytest_config.ya_trace_reporter.on_start_test_case(test_item) pytest_config.ya_trace_reporter.on_finish_test_case(test_item) else: pytest_config.ya_trace_reporter.on_finish_test_case(test_item, duration_only=True) pytest_config.ya_trace_reporter.on_finish_test_class(test_item) - + outcome = yield rep = outcome.get_result() result = None @@ -580,10 +580,10 @@ def pytest_runtest_makereport(item, call): if not pytest_config.from_ya_test: ti = TestItem(rep, result, pytest_config.option.test_suffix) tr = pytest_config.pluginmanager.getplugin('terminalreporter') - tr.write_line("{} - Validating canonical data is not supported when running standalone binary".format(ti), yellow=True, bold=True) + tr.write_line("{} - Validating canonical data is not supported when running standalone binary".format(ti), yellow=True, bold=True) logreport(rep, result, call) - - + + def pytest_make_parametrize_id(config, val, argname): # Avoid <, > symbols in canondata file names if inspect.isfunction(val) and val.__name__ == "<lambda>": @@ -598,7 +598,7 @@ def get_formatted_error(report): text += colorize(entry) else: text = colorize(report.longrepr) - text = yatest_lib.tools.to_utf8(text) + text = yatest_lib.tools.to_utf8(text) return text @@ -616,9 +616,9 @@ def colorize(longrepr): if hasattr(longrepr, 'reprtraceback') and hasattr(longrepr.reprtraceback, 'toterminal'): longrepr.reprtraceback.toterminal(writer) return io.getvalue().strip() - return yatest_lib.tools.to_utf8(longrepr) + return yatest_lib.tools.to_utf8(longrepr) - text = yatest_lib.tools.to_utf8(longrepr) + text = yatest_lib.tools.to_utf8(longrepr) pos = text.find("E ") if pos == -1: return text @@ -633,25 +633,25 @@ def colorize(longrepr): return "{}[[bad]]{}".format(bt, error) -class TestItem(object): - - def __init__(self, report, result, test_suffix): - self._result = result - self.nodeid = report.nodeid - self._class_name, self._test_name = tools.split_node_id(self.nodeid, test_suffix) - self._error = None - self._status = None - self._process_report(report) - self._duration = hasattr(report, 'duration') and report.duration or 0 - self._keywords = getattr(report, "keywords", {}) - - def _process_report(self, report): - if report.longrepr: - self.set_error(report) - if hasattr(report, 'when') and report.when != "call": - self.set_error(report.when + " failed:\n" + self._error) - else: - self.set_error("") +class TestItem(object): + + def __init__(self, report, result, test_suffix): + self._result = result + self.nodeid = report.nodeid + self._class_name, self._test_name = tools.split_node_id(self.nodeid, test_suffix) + self._error = None + self._status = None + self._process_report(report) + self._duration = hasattr(report, 'duration') and report.duration or 0 + self._keywords = getattr(report, "keywords", {}) + + def _process_report(self, report): + if report.longrepr: + self.set_error(report) + if hasattr(report, 'when') and report.when != "call": + self.set_error(report.when + " failed:\n" + self._error) + else: + self.set_error("") report_teststatus = _pytest.skipping.pytest_report_teststatus(report) if report_teststatus is not None: @@ -667,89 +667,89 @@ class TestItem(object): self._status = 'skipped' self.set_error(yatest_lib.tools.to_utf8(report.longrepr[-1])) elif report.passed: - self._status = 'good' - self.set_error("") + self._status = 'good' + self.set_error("") else: self._status = 'fail' - - @property - def status(self): - return self._status - - def set_status(self, status): - self._status = status - - @property - def test_name(self): - return tools.normalize_name(self._test_name) - - @property - def class_name(self): - return tools.normalize_name(self._class_name) - - @property - def error(self): - return self._error - + + @property + def status(self): + return self._status + + def set_status(self, status): + self._status = status + + @property + def test_name(self): + return tools.normalize_name(self._test_name) + + @property + def class_name(self): + return tools.normalize_name(self._class_name) + + @property + def error(self): + return self._error + def set_error(self, entry, marker='bad'): if isinstance(entry, _pytest.reports.BaseReport): - self._error = get_formatted_error(entry) - else: + self._error = get_formatted_error(entry) + else: self._error = "[[{}]]{}".format(yatest_lib.tools.to_str(marker), yatest_lib.tools.to_str(entry)) - - @property - def duration(self): - return self._duration - - @property - def result(self): - if 'not_canonize' in self._keywords: - return None - return self._result - - @property - def keywords(self): - return self._keywords - - def __str__(self): - return "{}::{}".format(self.class_name, self.test_name) - - -class CustomTestItem(TestItem): - - def __init__(self, nodeid, test_suffix, keywords=None): - self._result = None - self.nodeid = nodeid - self._class_name, self._test_name = tools.split_node_id(nodeid, test_suffix) - self._duration = 0 - self._error = "" - self._keywords = keywords if keywords is not None else {} - - -class NotLaunchedTestItem(CustomTestItem): - - def __init__(self, nodeid, test_suffix): - super(NotLaunchedTestItem, self).__init__(nodeid, test_suffix) - self._status = "not_launched" - - -class CrashedTestItem(CustomTestItem): - - def __init__(self, nodeid, test_suffix): - super(CrashedTestItem, self).__init__(nodeid, test_suffix) - self._status = "crashed" - - -class DeselectedTestItem(CustomTestItem): - - def __init__(self, nodeid, test_suffix): - super(DeselectedTestItem, self).__init__(nodeid, test_suffix) - self._status = "deselected" - - -class TraceReportGenerator(object): - - def __init__(self, out_file_path): + + @property + def duration(self): + return self._duration + + @property + def result(self): + if 'not_canonize' in self._keywords: + return None + return self._result + + @property + def keywords(self): + return self._keywords + + def __str__(self): + return "{}::{}".format(self.class_name, self.test_name) + + +class CustomTestItem(TestItem): + + def __init__(self, nodeid, test_suffix, keywords=None): + self._result = None + self.nodeid = nodeid + self._class_name, self._test_name = tools.split_node_id(nodeid, test_suffix) + self._duration = 0 + self._error = "" + self._keywords = keywords if keywords is not None else {} + + +class NotLaunchedTestItem(CustomTestItem): + + def __init__(self, nodeid, test_suffix): + super(NotLaunchedTestItem, self).__init__(nodeid, test_suffix) + self._status = "not_launched" + + +class CrashedTestItem(CustomTestItem): + + def __init__(self, nodeid, test_suffix): + super(CrashedTestItem, self).__init__(nodeid, test_suffix) + self._status = "crashed" + + +class DeselectedTestItem(CustomTestItem): + + def __init__(self, nodeid, test_suffix): + super(DeselectedTestItem, self).__init__(nodeid, test_suffix) + self._status = "deselected" + + +class TraceReportGenerator(object): + + def __init__(self, out_file_path): self._filename = out_file_path self._file = open(out_file_path, 'w') self._wreckage_filename = out_file_path + '.wreckage' @@ -759,7 +759,7 @@ class TraceReportGenerator(object): self._current_test = (None, None) self._pid = os.getpid() self._check_intricate_respawn() - + def _check_intricate_respawn(self): pid_file = self._filename + '.pid' try: @@ -803,40 +803,40 @@ class TraceReportGenerator(object): # Test binary is launched without `ya make -t`'s testing machinery - don't rely on clean environment pass - def on_start_test_class(self, test_item): + def on_start_test_class(self, test_item): pytest_config.ya.set_test_item_node_id(test_item.nodeid) class_name = test_item.class_name.decode('utf-8') if sys.version_info[0] < 3 else test_item.class_name self._current_test = (class_name, None) self.trace('test-started', {'class': class_name}) - - def on_finish_test_class(self, test_item): + + def on_finish_test_class(self, test_item): pytest_config.ya.set_test_item_node_id(test_item.nodeid) self.trace('test-finished', {'class': test_item.class_name.decode('utf-8') if sys.version_info[0] < 3 else test_item.class_name}) - - def on_start_test_case(self, test_item): + + def on_start_test_case(self, test_item): class_name = yatest_lib.tools.to_utf8(test_item.class_name) subtest_name = yatest_lib.tools.to_utf8(test_item.test_name) - message = { + message = { 'class': class_name, 'subtest': subtest_name, - } + } if test_item.nodeid in pytest_config.test_logs: message['logs'] = pytest_config.test_logs[test_item.nodeid] pytest_config.ya.set_test_item_node_id(test_item.nodeid) self._current_test = (class_name, subtest_name) - self.trace('subtest-started', message) - + self.trace('subtest-started', message) + def on_finish_test_case(self, test_item, duration_only=False): if test_item.result is not None: - try: + try: result = canon.serialize(test_item.result) - except Exception as e: - yatest_logger.exception("Error while serializing test results") - test_item.set_error("Invalid test result: {}".format(e)) - test_item.set_status("fail") - result = None - else: - result = None + except Exception as e: + yatest_logger.exception("Error while serializing test results") + test_item.set_error("Invalid test result: {}".format(e)) + test_item.set_status("fail") + result = None + else: + result = None if duration_only and test_item.nodeid in self._test_messages: # add teardown time message = self._test_messages[test_item.nodeid] @@ -860,7 +860,7 @@ class TraceReportGenerator(object): self.trace('subtest-finished', message) self._test_messages[test_item.nodeid] = message - + def dump_suite_metrics(self): message = {"metrics": pytest_config.suite_metrics} self.trace("suite-event", message) @@ -874,28 +874,28 @@ class TraceReportGenerator(object): else: self._test_duration[test_item.nodeid] = test_item._duration - @staticmethod - def _get_comment(test_item): - msg = yatest_lib.tools.to_utf8(test_item.error) - if not msg: - return "" + @staticmethod + def _get_comment(test_item): + msg = yatest_lib.tools.to_utf8(test_item.error) + if not msg: + return "" return msg + "[[rst]]" - + def _dump_trace(self, name, value): - event = { - 'timestamp': time.time(), - 'value': value, - 'name': name - } + event = { + 'timestamp': time.time(), + 'value': value, + 'name': name + } data = yatest_lib.tools.to_str(json.dumps(event, ensure_ascii=False)) self._file.write(data + '\n') self._file.flush() - + def _check_sloppy_fork(self, name, value): if self._pid == os.getpid(): return - + yatest_logger.error("Skip tracing to avoid data corruption, name = %s, value = %s", name, value) try: @@ -950,14 +950,14 @@ class TraceReportGenerator(object): self._dump_trace(name, value) -class DryTraceReportGenerator(TraceReportGenerator): - """ - Generator does not write any information. - """ - - def __init__(self, *args, **kwargs): +class DryTraceReportGenerator(TraceReportGenerator): + """ + Generator does not write any information. + """ + + def __init__(self, *args, **kwargs): self._test_messages = {} self._test_duration = {} - - def trace(self, name, value): - pass + + def trace(self, name, value): + pass diff --git a/library/python/pytest/pytest.yatest.ini b/library/python/pytest/pytest.yatest.ini index 554a1eb84f..70d6c98516 100644 --- a/library/python/pytest/pytest.yatest.ini +++ b/library/python/pytest/pytest.yatest.ini @@ -1,7 +1,7 @@ -[pytest] +[pytest] pep8maxlinelength = 200 norecursedirs = * -pep8ignore = E127 E123 E226 E24 +pep8ignore = E127 E123 E226 E24 filterwarnings = ignore::pytest.RemovedInPytest4Warning addopts = -p no:warnings diff --git a/library/python/pytest/ya.make b/library/python/pytest/ya.make index ee3c47dccb..060c92c313 100644 --- a/library/python/pytest/ya.make +++ b/library/python/pytest/ya.make @@ -1,32 +1,32 @@ PY23_LIBRARY() - + OWNER( g:yatool dmitko ) - -PY_SRCS( + +PY_SRCS( __init__.py - main.py + main.py rewrite.py - yatest_tools.py + yatest_tools.py context.py -) - -PEERDIR( +) + +PEERDIR( contrib/python/dateutil contrib/python/ipdb contrib/python/py contrib/python/pytest contrib/python/requests - library/python/pytest/plugins - library/python/testing/yatest_common - library/python/testing/yatest_lib -) - + library/python/pytest/plugins + library/python/testing/yatest_common + library/python/testing/yatest_lib +) + RESOURCE_FILES( PREFIX library/python/pytest/ pytest.yatest.ini ) -END() +END() diff --git a/library/python/pytest/yatest_tools.py b/library/python/pytest/yatest_tools.py index c618f8ff07..6b8b896394 100644 --- a/library/python/pytest/yatest_tools.py +++ b/library/python/pytest/yatest_tools.py @@ -3,13 +3,13 @@ import collections import functools import math -import os -import re +import os +import re import sys - -import yatest_lib.tools - - + +import yatest_lib.tools + + class Subtest(object): def __init__(self, name, test_name, status, comment, elapsed, result=None, test_type=None, logs=None, cwd=None, metrics=None): self._name = name @@ -17,103 +17,103 @@ class Subtest(object): self.status = status self.elapsed = elapsed self.comment = comment - self.result = result - self.test_type = test_type + self.result = result + self.test_type = test_type self.logs = logs or {} - self.cwd = cwd - self.metrics = metrics + self.cwd = cwd + self.metrics = metrics - def __eq__(self, other): - if not isinstance(other, Subtest): - return False - return self.name == other.name and self.test_name == other.test_name + def __eq__(self, other): + if not isinstance(other, Subtest): + return False + return self.name == other.name and self.test_name == other.test_name + + def __str__(self): + return yatest_lib.tools.to_utf8(unicode(self)) - def __str__(self): - return yatest_lib.tools.to_utf8(unicode(self)) - def __unicode__(self): return u"{}::{}".format(self.test_name, self.test_name) @property def name(self): - return yatest_lib.tools.to_utf8(self._name) + return yatest_lib.tools.to_utf8(self._name) @property def test_name(self): - return yatest_lib.tools.to_utf8(self._test_name) - - def __repr__(self): - return "Subtest [{}::{} - {}[{}]: {}]".format(self.name, self.test_name, self.status, self.elapsed, self.comment) - - def __hash__(self): - return hash(str(self)) - - -class SubtestInfo(object): - + return yatest_lib.tools.to_utf8(self._test_name) + + def __repr__(self): + return "Subtest [{}::{} - {}[{}]: {}]".format(self.name, self.test_name, self.status, self.elapsed, self.comment) + + def __hash__(self): + return hash(str(self)) + + +class SubtestInfo(object): + skipped_prefix = '[SKIPPED] ' - @classmethod - def from_str(cls, s): + @classmethod + def from_str(cls, s): if s.startswith(SubtestInfo.skipped_prefix): s = s[len(SubtestInfo.skipped_prefix):] skipped = True - + else: skipped = False return SubtestInfo(*s.rsplit(TEST_SUBTEST_SEPARATOR, 1), skipped=skipped) def __init__(self, test, subtest="", skipped=False, **kwargs): - self.test = test - self.subtest = subtest + self.test = test + self.subtest = subtest self.skipped = skipped - for key, value in kwargs.iteritems(): - setattr(self, key, value) - - def __str__(self): + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + def __str__(self): s = '' - + if self.skipped: s += SubtestInfo.skipped_prefix return s + TEST_SUBTEST_SEPARATOR.join([self.test, self.subtest]) - def __repr__(self): - return str(self) - - + def __repr__(self): + return str(self) + + class Status(object): GOOD, XFAIL, FAIL, XPASS, MISSING, CRASHED, TIMEOUT = range(7) - SKIPPED = -100 - NOT_LAUNCHED = -200 - CANON_DIFF = -300 - FLAKY = -1 - BY_NAME = {'good': GOOD, 'fail': FAIL, 'xfail': XFAIL, 'xpass': XPASS, 'missing': MISSING, 'crashed': CRASHED, - 'skipped': SKIPPED, 'flaky': FLAKY, 'not_launched': NOT_LAUNCHED, 'timeout': TIMEOUT, 'diff': CANON_DIFF} - TO_STR = {GOOD: 'good', FAIL: 'fail', XFAIL: 'xfail', XPASS: 'xpass', MISSING: 'missing', CRASHED: 'crashed', - SKIPPED: 'skipped', FLAKY: 'flaky', NOT_LAUNCHED: 'not_launched', TIMEOUT: 'timeout', CANON_DIFF: 'diff'} + SKIPPED = -100 + NOT_LAUNCHED = -200 + CANON_DIFF = -300 + FLAKY = -1 + BY_NAME = {'good': GOOD, 'fail': FAIL, 'xfail': XFAIL, 'xpass': XPASS, 'missing': MISSING, 'crashed': CRASHED, + 'skipped': SKIPPED, 'flaky': FLAKY, 'not_launched': NOT_LAUNCHED, 'timeout': TIMEOUT, 'diff': CANON_DIFF} + TO_STR = {GOOD: 'good', FAIL: 'fail', XFAIL: 'xfail', XPASS: 'xpass', MISSING: 'missing', CRASHED: 'crashed', + SKIPPED: 'skipped', FLAKY: 'flaky', NOT_LAUNCHED: 'not_launched', TIMEOUT: 'timeout', CANON_DIFF: 'diff'} class Test(object): - def __init__(self, name, path, status=None, comment=None, subtests=None): + def __init__(self, name, path, status=None, comment=None, subtests=None): self.name = name self.path = path - self.status = status - self.comment = comment - self.subtests = subtests or [] - - def __eq__(self, other): - if not isinstance(other, Test): - return False - return self.name == other.name and self.path == other.path - - def __str__(self): - return "Test [{} {}] - {} - {}".format(self.name, self.path, self.status, self.comment) - - def __repr__(self): - return str(self) - + self.status = status + self.comment = comment + self.subtests = subtests or [] + + def __eq__(self, other): + if not isinstance(other, Test): + return False + return self.name == other.name and self.path == other.path + + def __str__(self): + return "Test [{} {}] - {} - {}".format(self.name, self.path, self.status, self.comment) + + def __repr__(self): + return str(self) + def add_subtest(self, subtest): self.subtests.append(subtest) @@ -148,10 +148,10 @@ class YaCtx(object): pass ya_ctx = YaCtx() - -TRACE_FILE_NAME = "ytest.report.trace" - - + +TRACE_FILE_NAME = "ytest.report.trace" + + def lazy(func): mem = {} @@ -174,7 +174,7 @@ def _get_mtab(): def get_max_filename_length(dirname): - """ + """ Return maximum filename length for the filesystem :return: """ @@ -194,10 +194,10 @@ def get_unique_file_path(dir_path, filename, cache=collections.defaultdict(set)) """ Get unique filename in dir with proper filename length, using given filename/dir. File/dir won't be created (thread nonsafe) - :param dir_path: path to dir + :param dir_path: path to dir :param filename: original filename :return: unique filename - """ + """ max_suffix = 10000 # + 1 symbol for dot before suffix tail_length = int(round(math.log(max_suffix, 10))) + 1 @@ -222,83 +222,83 @@ def get_unique_file_path(dir_path, filename, cache=collections.defaultdict(set)) assert counter < max_suffix candidate = os.path.join(dir_path, filename + ".{}".format(counter)) return candidate - - -def escape_for_fnmatch(s): - return s.replace("[", "[").replace("]", "]") - - -def get_python_cmd(opts=None, use_huge=True, suite=None): + + +def escape_for_fnmatch(s): + return s.replace("[", "[").replace("]", "]") + + +def get_python_cmd(opts=None, use_huge=True, suite=None): if opts and getattr(opts, 'flags', {}).get("USE_ARCADIA_PYTHON") == "no": - return ["python"] - if suite and not suite._use_arcadia_python: - return ["python"] + return ["python"] + if suite and not suite._use_arcadia_python: + return ["python"] if use_huge: return ["$(PYTHON)/python"] ymake_path = opts.ymake_bin if opts and getattr(opts, 'ymake_bin', None) else "$(YMAKE)/ymake" return [ymake_path, "--python"] - - -def normalize_name(name): - replacements = [ - ("\\", "\\\\"), - ("\n", "\\n"), - ("\t", "\\t"), - ("\r", "\\r"), - ] - for l, r in replacements: - name = name.replace(l, r) - return name - - + + +def normalize_name(name): + replacements = [ + ("\\", "\\\\"), + ("\n", "\\n"), + ("\t", "\\t"), + ("\r", "\\r"), + ] + for l, r in replacements: + name = name.replace(l, r) + return name + + def normalize_filename(filename): - """ - Replace invalid for file names characters with string equivalents - :param some_string: string to be converted to a valid file name - :return: valid file name - """ + """ + Replace invalid for file names characters with string equivalents + :param some_string: string to be converted to a valid file name + :return: valid file name + """ not_allowed_pattern = r"[\[\]\/:*?\"\'<>|+\0\\\s\x0b\x0c]" filename = re.sub(not_allowed_pattern, ".", filename) return re.sub(r"\.{2,}", ".", filename) - - + + def get_test_log_file_path(output_dir, class_name, test_name, extension="log"): - """ - get test log file path, platform dependant - :param output_dir: dir where log file should be placed - :param class_name: test class name - :param test_name: test name - :return: test log file name - """ - if os.name == "nt": + """ + get test log file path, platform dependant + :param output_dir: dir where log file should be placed + :param class_name: test class name + :param test_name: test name + :return: test log file name + """ + if os.name == "nt": # don't add class name to the log's filename # to reduce it's length on windows filename = test_name - else: + else: filename = "{}.{}".format(class_name, test_name) if not filename: filename = "test" filename += "." + extension filename = normalize_filename(filename) return get_unique_file_path(output_dir, filename) - - -def split_node_id(nodeid, test_suffix=None): + + +def split_node_id(nodeid, test_suffix=None): path, possible_open_bracket, params = nodeid.partition('[') - separator = "::" + separator = "::" if separator in path: path, test_name = path.split(separator, 1) - else: - test_name = os.path.basename(path) - if test_suffix: - test_name += "::" + test_suffix - class_name = os.path.basename(path.strip()) - if separator in test_name: - klass_name, test_name = test_name.split(separator, 1) - if not test_suffix: - # test suffix is used for flakes and pep8, no need to add class_name as it's === class_name - class_name += separator + klass_name - if separator in test_name: - test_name = test_name.split(separator)[-1] + else: + test_name = os.path.basename(path) + if test_suffix: + test_name += "::" + test_suffix + class_name = os.path.basename(path.strip()) + if separator in test_name: + klass_name, test_name = test_name.split(separator, 1) + if not test_suffix: + # test suffix is used for flakes and pep8, no need to add class_name as it's === class_name + class_name += separator + klass_name + if separator in test_name: + test_name = test_name.split(separator)[-1] test_name += possible_open_bracket + params - return yatest_lib.tools.to_utf8(class_name), yatest_lib.tools.to_utf8(test_name) + return yatest_lib.tools.to_utf8(class_name), yatest_lib.tools.to_utf8(test_name) |