diff options
author | deshevoy <deshevoy@yandex-team.ru> | 2022-02-10 16:46:57 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:46:57 +0300 |
commit | 28148f76dbfcc644d96427d41c92f36cbf2fdc6e (patch) | |
tree | b83306b6e37edeea782e9eed673d89286c4fef35 /contrib/python/pytest/py3/_pytest/cacheprovider.py | |
parent | e988f30484abe5fdeedcc7a5d3c226c01a21800c (diff) | |
download | ydb-28148f76dbfcc644d96427d41c92f36cbf2fdc6e.tar.gz |
Restoring authorship annotation for <deshevoy@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/cacheprovider.py')
-rw-r--r-- | contrib/python/pytest/py3/_pytest/cacheprovider.py | 428 |
1 files changed, 214 insertions, 214 deletions
diff --git a/contrib/python/pytest/py3/_pytest/cacheprovider.py b/contrib/python/pytest/py3/_pytest/cacheprovider.py index d67e5933cf..03acd03109 100644 --- a/contrib/python/pytest/py3/_pytest/cacheprovider.py +++ b/contrib/python/pytest/py3/_pytest/cacheprovider.py @@ -1,8 +1,8 @@ """Implementation of the cache provider.""" # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. -import json -import os +import json +import os from pathlib import Path from typing import Dict from typing import Generator @@ -11,11 +11,11 @@ from typing import List from typing import Optional from typing import Set from typing import Union - -import attr -import py - -from .pathlib import resolve_from_str + +import attr +import py + +from .pathlib import resolve_from_str from .pathlib import rm_rf from .reports import CollectReport from _pytest import nodes @@ -32,33 +32,33 @@ from _pytest.main import Session from _pytest.python import Module from _pytest.python import Package from _pytest.reports import TestReport - + README_CONTENT = """\ -# pytest cache directory # - -This directory contains data from the pytest's cache plugin, -which provides the `--lf` and `--ff` options, as well as the `cache` fixture. - -**Do not** commit this to version control. - +# pytest cache directory # + +This directory contains data from the pytest's cache plugin, +which provides the `--lf` and `--ff` options, as well as the `cache` fixture. + +**Do not** commit this to version control. + See [the docs](https://docs.pytest.org/en/stable/cache.html) for more information. -""" - +""" + CACHEDIR_TAG_CONTENT = b"""\ Signature: 8a477f597d28d172789f06886806bc55 # This file is a cache directory tag created by pytest. # For information about cache directory tags, see: # http://www.bford.info/cachedir/spec.html """ - + @final @attr.s(init=False) class Cache: _cachedir = attr.ib(type=Path, repr=False) _config = attr.ib(type=Config, repr=False) - + # sub-directory under cache-dir for directories created by "makedir" _CACHE_PREFIX_DIRS = "d" @@ -72,7 +72,7 @@ class Cache: self._cachedir = cachedir self._config = config - @classmethod + @classmethod def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache": """Create the Cache instance for a Config. @@ -83,7 +83,7 @@ class Cache: if config.getoption("cacheclear") and cachedir.is_dir(): cls.clear_cache(cachedir, _ispytest=True) return cls(cachedir, config, _ispytest=True) - + @classmethod def clear_cache(cls, cachedir: Path, _ispytest: bool = False) -> None: """Clear the sub-directories used to hold cached directories and values. @@ -96,10 +96,10 @@ class Cache: if d.is_dir(): rm_rf(d) - @staticmethod + @staticmethod def cache_dir_from_config(config: Config, *, _ispytest: bool = False) -> Path: """Get the path to the cache directory for a Config. - + :meta private: """ check_ispytest(_ispytest) @@ -113,16 +113,16 @@ class Cache: check_ispytest(_ispytest) import warnings from _pytest.warning_types import PytestCacheWarning - + warnings.warn( PytestCacheWarning(fmt.format(**args) if args else fmt), self._config.hook, - stacklevel=3, - ) - + stacklevel=3, + ) + def makedir(self, name: str) -> py.path.local: """Return a directory path object with the given name. - + If the directory does not yet exist, it will be created. You can use it to manage files to e.g. store/retrieve database dumps across test sessions. @@ -131,79 +131,79 @@ class Cache: Must be a string not containing a ``/`` separator. Make sure the name contains your plugin or application identifiers to prevent clashes with other cache users. - """ + """ path = Path(name) if len(path.parts) > 1: - raise ValueError("name is not allowed to contain path separators") + raise ValueError("name is not allowed to contain path separators") res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path) - res.mkdir(exist_ok=True, parents=True) - return py.path.local(res) - + res.mkdir(exist_ok=True, parents=True) + return py.path.local(res) + def _getvaluepath(self, key: str) -> Path: return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key)) - + def get(self, key: str, default): """Return the cached value for the given key. If no value was yet cached or the value cannot be read, the specified - default is returned. - + default is returned. + :param key: Must be a ``/`` separated value. Usually the first name is the name of your plugin or your application. :param default: The value to return in case of a cache-miss or invalid cache value. - """ - path = self._getvaluepath(key) - try: - with path.open("r") as f: - return json.load(f) + """ + path = self._getvaluepath(key) + try: + with path.open("r") as f: + return json.load(f) except (ValueError, OSError): - return default - + return default + def set(self, key: str, value: object) -> None: """Save value for the given key. - + :param key: Must be a ``/`` separated value. Usually the first name is the name of your plugin or your application. :param value: Must be of any combination of basic python types, including nested types like lists of dictionaries. - """ - path = self._getvaluepath(key) - try: - if path.parent.is_dir(): - cache_dir_exists_already = True - else: - cache_dir_exists_already = self._cachedir.exists() + """ + path = self._getvaluepath(key) + try: + if path.parent.is_dir(): + cache_dir_exists_already = True + else: + cache_dir_exists_already = self._cachedir.exists() path.parent.mkdir(exist_ok=True, parents=True) except OSError: self.warn("could not create cache path {path}", path=path, _ispytest=True) - return + return if not cache_dir_exists_already: self._ensure_supporting_files() data = json.dumps(value, indent=2, sort_keys=True) - try: + try: f = path.open("w") except OSError: self.warn("cache could not write path {path}", path=path, _ispytest=True) - else: - with f: + else: + with f: f.write(data) - + def _ensure_supporting_files(self) -> None: - """Create supporting files in the cache dir that are not really part of the cache.""" + """Create supporting files in the cache dir that are not really part of the cache.""" readme_path = self._cachedir / "README.md" readme_path.write_text(README_CONTENT) - + gitignore_path = self._cachedir.joinpath(".gitignore") msg = "# Created by pytest automatically.\n*\n" gitignore_path.write_text(msg, encoding="UTF-8") - + cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG") cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT) - + class LFPluginCollWrapper: def __init__(self, lfplugin: "LFPlugin") -> None: @@ -276,17 +276,17 @@ class LFPluginCollSkipfiles: class LFPlugin: """Plugin which implements the --lf (run last-failing) option.""" - + def __init__(self, config: Config) -> None: - self.config = config - active_keys = "lf", "failedfirst" - self.active = any(config.getoption(key) for key in active_keys) + self.config = config + active_keys = "lf", "failedfirst" + self.active = any(config.getoption(key) for key in active_keys) assert config.cache self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {}) self._previously_failed_count: Optional[int] = None self._report_status: Optional[str] = None self._skipped_files = 0 # count skipped files during collection due to --lf - + if config.getoption("lf"): self._last_failed_paths = self.get_last_failed_paths() config.pluginmanager.register( @@ -300,25 +300,25 @@ class LFPlugin: return {x for x in result if x.exists()} def pytest_report_collectionfinish(self) -> Optional[str]: - if self.active and self.config.getoption("verbose") >= 0: + if self.active and self.config.getoption("verbose") >= 0: return "run-last-failure: %s" % self._report_status return None - + def pytest_runtest_logreport(self, report: TestReport) -> None: - if (report.when == "call" and report.passed) or report.skipped: - self.lastfailed.pop(report.nodeid, None) - elif report.failed: - self.lastfailed[report.nodeid] = True - + if (report.when == "call" and report.passed) or report.skipped: + self.lastfailed.pop(report.nodeid, None) + elif report.failed: + self.lastfailed[report.nodeid] = True + def pytest_collectreport(self, report: CollectReport) -> None: - passed = report.outcome in ("passed", "skipped") - if passed: - if report.nodeid in self.lastfailed: - self.lastfailed.pop(report.nodeid) - self.lastfailed.update((item.nodeid, True) for item in report.result) - else: - self.lastfailed[report.nodeid] = True - + passed = report.outcome in ("passed", "skipped") + if passed: + if report.nodeid in self.lastfailed: + self.lastfailed.pop(report.nodeid) + self.lastfailed.update((item.nodeid, True) for item in report.result) + else: + self.lastfailed[report.nodeid] = True + @hookimpl(hookwrapper=True, tryfirst=True) def pytest_collection_modifyitems( self, config: Config, items: List[nodes.Item] @@ -345,11 +345,11 @@ class LFPlugin: len(self.lastfailed), ) else: - if self.config.getoption("lf"): - items[:] = previously_failed - config.hook.pytest_deselected(items=previously_passed) + if self.config.getoption("lf"): + items[:] = previously_failed + config.hook.pytest_deselected(items=previously_passed) else: # --failedfirst - items[:] = previously_failed + previously_passed + items[:] = previously_failed + previously_passed noun = "failure" if self._previously_failed_count == 1 else "failures" suffix = " first" if self.config.getoption("failedfirst") else "" @@ -367,209 +367,209 @@ class LFPlugin: if self.config.getoption("last_failed_no_failures") == "none": self._report_status += "deselecting all items." config.hook.pytest_deselected(items=items[:]) - items[:] = [] + items[:] = [] else: self._report_status += "not deselecting items." - + def pytest_sessionfinish(self, session: Session) -> None: - config = self.config + config = self.config if config.getoption("cacheshow") or hasattr(config, "workerinput"): - return - + return + assert config.cache is not None - saved_lastfailed = config.cache.get("cache/lastfailed", {}) - if saved_lastfailed != self.lastfailed: - config.cache.set("cache/lastfailed", self.lastfailed) - - + saved_lastfailed = config.cache.get("cache/lastfailed", {}) + if saved_lastfailed != self.lastfailed: + config.cache.set("cache/lastfailed", self.lastfailed) + + class NFPlugin: """Plugin which implements the --nf (run new-first) option.""" - + def __init__(self, config: Config) -> None: - self.config = config - self.active = config.option.newfirst + self.config = config + self.active = config.option.newfirst assert config.cache is not None self.cached_nodeids = set(config.cache.get("cache/nodeids", [])) - + @hookimpl(hookwrapper=True, tryfirst=True) def pytest_collection_modifyitems( self, items: List[nodes.Item] ) -> Generator[None, None, None]: yield - if self.active: + if self.active: new_items: Dict[str, nodes.Item] = {} other_items: Dict[str, nodes.Item] = {} - for item in items: - if item.nodeid not in self.cached_nodeids: - new_items[item.nodeid] = item - else: - other_items[item.nodeid] = item - - items[:] = self._get_increasing_order( + for item in items: + if item.nodeid not in self.cached_nodeids: + new_items[item.nodeid] = item + else: + other_items[item.nodeid] = item + + items[:] = self._get_increasing_order( new_items.values() ) + self._get_increasing_order(other_items.values()) self.cached_nodeids.update(new_items) else: self.cached_nodeids.update(item.nodeid for item in items) - + def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]: return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True) # type: ignore[no-any-return] - + def pytest_sessionfinish(self) -> None: - config = self.config + config = self.config if config.getoption("cacheshow") or hasattr(config, "workerinput"): - return - + return + if config.getoption("collectonly"): return - + assert config.cache is not None config.cache.set("cache/nodeids", sorted(self.cached_nodeids)) - + def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group.addoption( - "--lf", - "--last-failed", - action="store_true", - dest="lf", - help="rerun only the tests that failed " - "at the last run (or all if none failed)", - ) - group.addoption( - "--ff", - "--failed-first", - action="store_true", - dest="failedfirst", + group = parser.getgroup("general") + group.addoption( + "--lf", + "--last-failed", + action="store_true", + dest="lf", + help="rerun only the tests that failed " + "at the last run (or all if none failed)", + ) + group.addoption( + "--ff", + "--failed-first", + action="store_true", + dest="failedfirst", help="run all tests, but run the last failures first.\n" - "This may re-order tests and thus lead to " + "This may re-order tests and thus lead to " "repeated fixture setup/teardown.", - ) - group.addoption( - "--nf", - "--new-first", - action="store_true", - dest="newfirst", - help="run tests from new files first, then the rest of the tests " - "sorted by file mtime", - ) - group.addoption( - "--cache-show", + ) + group.addoption( + "--nf", + "--new-first", + action="store_true", + dest="newfirst", + help="run tests from new files first, then the rest of the tests " + "sorted by file mtime", + ) + group.addoption( + "--cache-show", action="append", nargs="?", - dest="cacheshow", + dest="cacheshow", help=( "show cache contents, don't perform collection or tests. " "Optional argument: glob (default: '*')." ), - ) - group.addoption( - "--cache-clear", - action="store_true", - dest="cacheclear", - help="remove all cache contents at start of test run.", - ) - cache_dir_default = ".pytest_cache" - if "TOX_ENV_DIR" in os.environ: - cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default) - parser.addini("cache_dir", default=cache_dir_default, help="cache directory path.") - group.addoption( - "--lfnf", - "--last-failed-no-failures", - action="store", - dest="last_failed_no_failures", - choices=("all", "none"), - default="all", + ) + group.addoption( + "--cache-clear", + action="store_true", + dest="cacheclear", + help="remove all cache contents at start of test run.", + ) + cache_dir_default = ".pytest_cache" + if "TOX_ENV_DIR" in os.environ: + cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default) + parser.addini("cache_dir", default=cache_dir_default, help="cache directory path.") + group.addoption( + "--lfnf", + "--last-failed-no-failures", + action="store", + dest="last_failed_no_failures", + choices=("all", "none"), + default="all", help="which tests to run with no previously (known) failures.", - ) - - + ) + + def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: - if config.option.cacheshow: - from _pytest.main import wrap_session - - return wrap_session(config, cacheshow) + if config.option.cacheshow: + from _pytest.main import wrap_session + + return wrap_session(config, cacheshow) return None - - + + @hookimpl(tryfirst=True) def pytest_configure(config: Config) -> None: config.cache = Cache.for_config(config, _ispytest=True) - config.pluginmanager.register(LFPlugin(config), "lfplugin") - config.pluginmanager.register(NFPlugin(config), "nfplugin") - - + config.pluginmanager.register(LFPlugin(config), "lfplugin") + config.pluginmanager.register(NFPlugin(config), "nfplugin") + + @fixture def cache(request: FixtureRequest) -> Cache: """Return a cache object that can persist state between testing sessions. - - cache.get(key, default) - cache.set(key, value) - + + cache.get(key, default) + cache.set(key, value) + Keys must be ``/`` separated strings, where the first part is usually the - name of your plugin or application to avoid clashes with other cache users. - - Values can be any object handled by the json stdlib module. - """ + name of your plugin or application to avoid clashes with other cache users. + + Values can be any object handled by the json stdlib module. + """ assert request.config.cache is not None - return request.config.cache - - + return request.config.cache + + def pytest_report_header(config: Config) -> Optional[str]: - """Display cachedir with --cache-show and if non-default.""" + """Display cachedir with --cache-show and if non-default.""" if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache": assert config.cache is not None - cachedir = config.cache._cachedir - # TODO: evaluate generating upward relative paths - # starting with .., ../.. if sensible - - try: + cachedir = config.cache._cachedir + # TODO: evaluate generating upward relative paths + # starting with .., ../.. if sensible + + try: displaypath = cachedir.relative_to(config.rootpath) - except ValueError: - displaypath = cachedir + except ValueError: + displaypath = cachedir return f"cachedir: {displaypath}" return None - - + + def cacheshow(config: Config, session: Session) -> int: - from pprint import pformat - + from pprint import pformat + assert config.cache is not None tw = TerminalWriter() - tw.line("cachedir: " + str(config.cache._cachedir)) - if not config.cache._cachedir.is_dir(): - tw.line("cache is empty") - return 0 + tw.line("cachedir: " + str(config.cache._cachedir)) + if not config.cache._cachedir.is_dir(): + tw.line("cache is empty") + return 0 glob = config.option.cacheshow[0] if glob is None: glob = "*" - dummy = object() - basedir = config.cache._cachedir + dummy = object() + basedir = config.cache._cachedir vdir = basedir / Cache._CACHE_PREFIX_VALUES tw.sep("-", "cache values for %r" % glob) for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): key = str(valpath.relative_to(vdir)) - val = config.cache.get(key, dummy) - if val is dummy: - tw.line("%s contains unreadable content, will be ignored" % key) - else: - tw.line("%s contains:" % key) - for line in pformat(val).splitlines(): - tw.line(" " + line) - + val = config.cache.get(key, dummy) + if val is dummy: + tw.line("%s contains unreadable content, will be ignored" % key) + else: + tw.line("%s contains:" % key) + for line in pformat(val).splitlines(): + tw.line(" " + line) + ddir = basedir / Cache._CACHE_PREFIX_DIRS - if ddir.is_dir(): + if ddir.is_dir(): contents = sorted(ddir.rglob(glob)) tw.sep("-", "cache directories for %r" % glob) - for p in contents: - # if p.check(dir=1): - # print("%s/" % p.relto(basedir)) - if p.is_file(): + for p in contents: + # if p.check(dir=1): + # print("%s/" % p.relto(basedir)) + if p.is_file(): key = str(p.relative_to(basedir)) tw.line(f"{key} is a file of length {p.stat().st_size:d}") - return 0 + return 0 |