def set_log_path(self, fname: str) -> None: """Set the filename parameter for Logging.FileHandler(). Creates parent directory if it does not exist. .. warning:: This is an experimental API. """ fpath = Path(fname) if not fpath.is_absolute(): fpath = self._config.rootpath / fpath if not fpath.parent.exists(): fpath.parent.mkdir(exist_ok=True, parents=True) stream = fpath.open(mode="w", encoding="UTF-8") if sys.version_info >= (3, 7): old_stream = self.log_file_handler.setStream(stream) else: old_stream = self.log_file_handler.stream self.log_file_handler.acquire() try: self.log_file_handler.flush() self.log_file_handler.stream = stream finally: self.log_file_handler.release() if old_stream: old_stream.close()
def validate_basetemp(path: str) -> str: # GH 7119 msg = "basetemp must not be empty, the current working directory or any parent directory of it" # empty path if not path: raise argparse.ArgumentTypeError(msg) def is_ancestor(base: Path, query: Path) -> bool: """ return True if query is an ancestor of base, else False.""" if base == query: return True for parent in base.parents: if parent == query: return True return False # check if path is an ancestor of cwd if is_ancestor(Path.cwd(), Path(path).absolute()): raise argparse.ArgumentTypeError(msg) # check symlinks for ancestors if is_ancestor(Path.cwd().resolve(), Path(path).resolve()): raise argparse.ArgumentTypeError(msg) return path
def test_get_cache_dir(self, monkeypatch, prefix, source, expected): if prefix: if sys.version_info < (3, 8): pytest.skip("pycache_prefix not available in py<38") monkeypatch.setattr(sys, "pycache_prefix", prefix) assert get_cache_dir(Path(source)) == Path(expected)
def test_commonpath() -> None: path = Path("/foo/bar/baz/path") subpath = path / "sampledir" assert commonpath(path, subpath) == path assert commonpath(subpath, path) == path assert commonpath(Path(str(path) + "suffix"), path) == path.parent assert commonpath(path, path.parent.parent) == path.parent.parent
def attempt_symlink_to(path, to_path): """Try to make a symlink from "path" to "to_path", skipping in case this platform does not support it or we don't have sufficient privileges (common on Windows).""" try: Path(path).symlink_to(Path(to_path)) except OSError: pytest.skip("could not create symbolic link")
def test_invocation_args(testdir): """Ensure that Config.invocation_* arguments are correctly defined""" class DummyPlugin: pass p = testdir.makepyfile("def test(): pass") plugin = DummyPlugin() rec = testdir.inline_run(p, "-v", plugins=[plugin]) calls = rec.getcalls("pytest_runtest_protocol") assert len(calls) == 1 call = calls[0] config = call.item.config assert config.invocation_params.args == (p, "-v") assert config.invocation_params.dir == Path(str(testdir.tmpdir)) plugins = config.invocation_params.plugins assert len(plugins) == 2 assert plugins[0] is plugin assert type( plugins[1]).__name__ == "Collect" # installed by testdir.inline_run() # args cannot be None with pytest.raises(TypeError): Config.InvocationParams(args=None, plugins=None, dir=Path())
def test_bestrelpath() -> None: curdir = Path("/foo/bar/baz/path") assert bestrelpath(curdir, curdir) == "." assert bestrelpath(curdir, curdir / "hello" / "world") == "hello" + os.sep + "world" assert bestrelpath(curdir, curdir.parent / "sister") == ".." + os.sep + "sister" assert bestrelpath(curdir, curdir.parent) == ".." assert bestrelpath(curdir, Path("hello")) == "hello"
def attempt_symlink_to(path, to_path): """Try to make a symlink from "path" to "to_path", skipping in case this platform does not support it or we don't have sufficient privileges (common on Windows).""" if sys.platform.startswith("win") and six.PY2: pytest.skip("pathlib for some reason cannot make symlinks on Python 2") try: Path(path).symlink_to(Path(to_path)) except OSError: pytest.skip("could not create symbolic link")
def _repr_failure_py( self, excinfo: ExceptionInfo[BaseException], style=None, ) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]: if isinstance(excinfo.value, ConftestImportFailure): excinfo = ExceptionInfo(excinfo.value.excinfo) if isinstance(excinfo.value, fail.Exception): if not excinfo.value.pytrace: return str(excinfo.value) if isinstance(excinfo.value, FixtureLookupError): return excinfo.value.formatrepr() if self.config.getoption("fulltrace", False): style = "long" else: tb = _pytest._code.Traceback([excinfo.traceback[-1]]) self._prunetraceback(excinfo) if len(excinfo.traceback) == 0: excinfo.traceback = tb if style == "auto": style = "long" # XXX should excinfo.getrepr record all data and toterminal() process it? if style is None: if self.config.getoption("tbstyle", "auto") == "short": style = "short" else: style = "long" if self.config.getoption("verbose", 0) > 1: truncate_locals = False else: truncate_locals = True # excinfo.getrepr() formats paths relative to the CWD if `abspath` is False. # It is possible for a fixture/test to change the CWD while this code runs, which # would then result in the user seeing confusing paths in the failure message. # To fix this, if the CWD changed, always display the full absolute path. # It will be better to just always display paths relative to invocation_dir, but # this requires a lot of plumbing (#6428). try: abspath = Path(os.getcwd()) != Path(self.config.invocation_dir) except OSError: abspath = True return excinfo.getrepr( funcargs=True, abspath=abspath, showlocals=self.config.getoption("showlocals", False), style=style, tbfilter=False, # pruned already, or in --fulltrace mode. truncate_locals=truncate_locals, )
def get_cache_dir(file_path: Path) -> Path: """Return the cache directory to write .pyc files for the given .py file path.""" if sys.version_info >= (3, 8) and sys.pycache_prefix: # given: # prefix = '/tmp/pycs' # path = '/home/user/proj/test_app.py' # we want: # '/tmp/pycs/home/user/proj' return Path(sys.pycache_prefix) / Path(*file_path.parts[1:-1]) else: # classic pycache directory return file_path.parent / "__pycache__"
def get_cache_dir(file_path: Path) -> Path: """Returns the cache directory to write .pyc files for the given .py file path""" # Type ignored until added in next mypy release. if sys.version_info >= (3, 8) and sys.pycache_prefix: # type: ignore # given: # prefix = '/tmp/pycs' # path = '/home/user/proj/test_app.py' # we want: # '/tmp/pycs/home/user/proj' return Path(sys.pycache_prefix) / Path( *file_path.parts[1:-1]) # type: ignore else: # classic pycache directory return file_path.parent / "__pycache__"
def getlocation(function, curdir: Optional[str] = None) -> str: from _pytest.pathlib import Path function = get_real_func(function) fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno if curdir is not None: try: relfn = fn.relative_to(curdir) except ValueError: pass else: return "%s:%d" % (relfn, lineno + 1) return "%s:%d" % (fn, lineno + 1)
def _setup_tree(self, testdir): # for issue616 # example mostly taken from: # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html runner = testdir.mkdir("empty") package = testdir.mkdir("package") package.join("conftest.py").write( textwrap.dedent("""\ import pytest @pytest.fixture def fxtr(): return "from-package" """)) package.join("test_pkgroot.py").write( textwrap.dedent("""\ def test_pkgroot(fxtr): assert fxtr == "from-package" """)) swc = package.mkdir("swc") swc.join("__init__.py").ensure() swc.join("conftest.py").write( textwrap.dedent("""\ import pytest @pytest.fixture def fxtr(): return "from-swc" """)) swc.join("test_with_conftest.py").write( textwrap.dedent("""\ def test_with_conftest(fxtr): assert fxtr == "from-swc" """)) snc = package.mkdir("snc") snc.join("__init__.py").ensure() snc.join("test_no_conftest.py").write( textwrap.dedent("""\ def test_no_conftest(fxtr): assert fxtr == "from-package" # No local conftest.py, so should # use value from parent dir's """)) print("created directory structure:") tmppath = Path(str(testdir.tmpdir)) for x in tmppath.rglob(""): print(" " + str(x.relative_to(tmppath))) return {"runner": runner, "package": package, "swc": swc, "snc": snc}
def __init__(self, pluginmanager, *, invocation_params=None) -> None: from .argparsing import Parser, FILE_OR_DIR if invocation_params is None: invocation_params = self.InvocationParams(args=(), plugins=None, dir=Path().resolve()) self.option = argparse.Namespace() self.invocation_params = invocation_params _a = FILE_OR_DIR self._parser = Parser( usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a), processopt=self._processopt, ) self.pluginmanager = pluginmanager self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook self._inicache = {} # type: Dict[str, Any] self._override_ini = () # type: Sequence[str] self._opt2dest = {} # type: Dict[str, str] self._cleanup = [] # type: List[Callable[[], None]] # A place where plugins can store information on the config for their # own use. Currently only intended for internal plugins. self._store = Store() self.pluginmanager.register(self, "pytestconfig") self._configured = False self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)) if TYPE_CHECKING: from _pytest.cacheprovider import Cache self.cache = None # type: Optional[Cache]
def pytest_collection_modifyitems(self, session, config, items): if not self.active: return (files_changed, _, contexts) = self.who_tested_what() selected_items = [ item for item in items if item.nodeid in contexts or any( Path(item.nodeid.partition("::")[0]) == changed_file for changed_file in files_changed) ] selected_set = set(selected_items) skipped_items = [item for item in items if item not in selected_set] items[:] = selected_items config.hook.pytest_deselected(items=skipped_items) noun = "tests" if len(selected_items) else "test" self._report_status = "{} {} cover the changed lines ({} deselected)".format( len(selected_items), noun, len(skipped_items)) if self._skipped_files > 0: files_noun = "file" if self._skipped_files == 1 else "files" self._report_status += " (skipped {files} {files_noun})".format( files=self._skipped_files, files_noun=files_noun)
def __init__(self, pluginmanager, invocation_params=None, *args): from .argparsing import Parser, FILE_OR_DIR if invocation_params is None: invocation_params = self.InvocationParams( args=(), plugins=None, dir=Path().resolve() ) #: access to command line option as attributes. #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead self.option = argparse.Namespace() self.invocation_params = invocation_params _a = FILE_OR_DIR self._parser = Parser( usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a), processopt=self._processopt, ) #: a pluginmanager instance self.pluginmanager = pluginmanager self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook self._inicache = {} self._override_ini = () self._opt2dest = {} self._cleanup = [] self.pluginmanager.register(self, "pytestconfig") self._configured = False self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
def _folded_skips( startpath: Path, skipped: Sequence[CollectReport] ) -> List[Tuple[int, str, Optional[int], str]]: d = {} # type: Dict[Tuple[str, Optional[int], str], List[CollectReport]] for event in skipped: assert event.longrepr is not None assert isinstance(event.longrepr, tuple), (event, event.longrepr) assert len(event.longrepr) == 3, (event, event.longrepr) fspath, lineno, reason = event.longrepr # For consistency, report all fspaths in relative form. fspath = bestrelpath(startpath, Path(fspath)) keywords = getattr(event, "keywords", {}) # Folding reports with global pytestmark variable. # This is a workaround, because for now we cannot identify the scope of a skip marker # TODO: Revisit after marks scope would be fixed. if ( event.when == "setup" and "skip" in keywords and "pytestmark" not in keywords ): key = (fspath, None, reason) # type: Tuple[str, Optional[int], str] else: key = (fspath, lineno, reason) d.setdefault(key, []).append(event) values = [] # type: List[Tuple[int, str, Optional[int], str]] for key, events in d.items(): values.append((len(events), *key)) return values
def _importconftest(self, conftestpath): # Use a resolved Path object as key to avoid loading the same conftest twice # with build systems that create build directories containing # symlinks to actual files. # Using Path().resolve() is better than py.path.realpath because # it resolves to the correct path/drive in case-insensitive file systems (#5792) key = Path(str(conftestpath)).resolve() try: return self._conftestpath2mod[key] except KeyError: pkgpath = conftestpath.pypkgpath() if pkgpath is None: _ensure_removed_sysmodule(conftestpath.purebasename) try: mod = conftestpath.pyimport() if (hasattr(mod, "pytest_plugins") and self._configured and not self._using_pyargs): _fail_on_non_top_pytest_plugins(conftestpath, self._confcutdir) except Exception: raise ConftestImportFailure(conftestpath, sys.exc_info()) self._conftest_plugins.add(mod) self._conftestpath2mod[key] = mod dirpath = conftestpath.dirpath() if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): if path and path.relto(dirpath) or path == dirpath: assert mod not in mods mods.append(mod) self.trace("loaded conftestmodule %r" % (mod)) self.consider_conftest(mod) return mod
def __init__(self, pluginmanager, *, invocation_params=None) -> None: from .argparsing import Parser, FILE_OR_DIR if invocation_params is None: invocation_params = self.InvocationParams(args=(), plugins=None, dir=Path().resolve()) self.option = argparse.Namespace() self.invocation_params = invocation_params _a = FILE_OR_DIR self._parser = Parser( usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a), processopt=self._processopt, ) self.pluginmanager = pluginmanager self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook self._inicache = {} # type: Dict[str, Any] self._override_ini = () # type: Sequence[str] self._opt2dest = {} # type: Dict[str, str] self._cleanup = [] # type: List[Callable[[], None]] self.pluginmanager.register(self, "pytestconfig") self._configured = False self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager))
def filter_traceback(entry: TracebackEntry) -> bool: """Return True if a TracebackEntry instance should be included in tracebacks. We hide traceback entries of: * dynamically generated code (no code to show up for it); * internal traceback from pytest or its internal libraries, py and pluggy. """ # entry.path might sometimes return a str object when the entry # points to dynamically generated code. # See https://bitbucket.org/pytest-dev/py/issues/71. raw_filename = entry.frame.code.raw.co_filename is_generated = "<" in raw_filename and ">" in raw_filename if is_generated: return False # entry.path might point to a non-existing file, in which case it will # also return a str object. See #1133. p = Path(entry.path) parents = p.parents if _PLUGGY_DIR in parents: return False if _PYTEST_DIR in parents: return False if _PY_DIR in parents: return False return True
def _importconftest(self, conftestpath): # Use a resolved Path object as key to avoid loading the same conftest twice # with build systems that create build directories containing # symlinks to actual files. # Using Path().resolve() is better than py.path.realpath because # it resolves to the correct path/drive in case-insensitive file systems (#5792) key = Path(str(conftestpath)).resolve() with contextlib.suppress(KeyError): return self._conftestpath2mod[key] pkgpath = conftestpath.pypkgpath() if pkgpath is None: _ensure_removed_sysmodule(conftestpath.purebasename) try: mod = conftestpath.pyimport() except Exception as e: raise ConftestImportFailure(conftestpath, sys.exc_info()) from e self._check_non_top_pytest_plugins(mod, conftestpath) self._conftest_plugins.add(mod) self._conftestpath2mod[key] = mod dirpath = conftestpath.dirpath() if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): if path and path.relto(dirpath) or path == dirpath: assert mod not in mods mods.append(mod) self.trace("loading conftestmodule {!r}".format(mod)) self.consider_conftest(mod) return mod
def safe_exists(path: Path) -> bool: # This can throw on paths that contain characters unrepresentable at the OS level, # or with invalid syntax on Windows (https://bugs.python.org/issue35306) try: return path.exists() except OSError: return False
def resolve_collection_argument( invocation_dir: py.path.local, arg: str, *, as_pypath: bool = False) -> Tuple[py.path.local, List[str]]: """Parse path arguments optionally containing selection parts and return (fspath, names). Command-line arguments can point to files and/or directories, and optionally contain parts for specific tests selection, for example: "pkg/tests/test_foo.py::TestClass::test_foo" This function ensures the path exists, and returns a tuple: (py.path.path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) When as_pypath is True, expects that the command-line argument actually contains module paths instead of file-system paths: "pkg.tests.test_foo::TestClass::test_foo" In which case we search sys.path for a matching module, and then return the *path* to the found module. If the path doesn't exist, raise UsageError. If the path is a directory and selection parts are present, raise UsageError. """ strpath, *parts = str(arg).split("::") if as_pypath: strpath = search_pypath(strpath) fspath = Path(str(invocation_dir), strpath) fspath = absolutepath(fspath) if not fspath.exists(): msg = ("module or package not found: {arg} (missing __init__.py?)" if as_pypath else "file or directory not found: {arg}") raise UsageError(msg.format(arg=arg)) if parts and fspath.is_dir(): msg = ("package argument cannot contain :: selection parts: {arg}" if as_pypath else "directory argument cannot contain :: selection parts: {arg}") raise UsageError(msg.format(arg=arg)) return py.path.local(str(fspath)), parts
def set_log_path(self, fname): """Public method, which can set filename parameter for Logging.FileHandler(). Also creates parent directory if it does not exist. .. warning:: Please considered as an experimental API. """ fname = Path(fname) if not fname.is_absolute(): fname = Path(self._config.rootdir, fname) if not fname.parent.exists(): fname.parent.mkdir(exist_ok=True, parents=True) self.log_file_handler = logging.FileHandler( str(fname), mode="w", encoding="UTF-8" ) self.log_file_handler.setFormatter(self.log_file_formatter)
def __init__(self, config): self._config = config log_path = Path(config.option.xlog) self._log_path = log_path log_path.parent.mkdir(parents=True, exist_ok=True) self._file = log_path.open("w", buffering=1, encoding="UTF-8") self._always_report = {} for item in config.option.xopt: if '=' in item: keytree = item.split('=')[0] key = keytree cur = self._always_report for key in keytree.split('.'): if not cur.get(key): cur[key] = {} if key != keytree.split('.')[-1]: cur = cur[key] cur[key] = item.split('=')[1] self._always_report['session_id'] = int(time()) self._results = {}
def set_log_path(self, fname): """Public method, which can set filename parameter for Logging.FileHandler(). Also creates parent directory if it does not exist. .. warning:: Please considered as an experimental API. """ fname = Path(fname) if not fname.is_absolute(): fname = Path(self._config.rootdir, fname) if not fname.parent.exists(): fname.parent.mkdir(exist_ok=True, parents=True) self.log_file_handler = logging.FileHandler(str(fname), mode="w", encoding="UTF-8") self.log_file_handler.setFormatter(self.log_file_formatter)
def test_setinitial_conftest_subdirs(testdir, name): sub = testdir.mkdir(name) subconftest = sub.ensure("conftest.py") conftest = PytestPluginManager() conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir) key = Path(str(subconftest)).resolve() if name not in ("whatever", ".dotdir"): assert key in conftest._conftestpath2mod assert len(conftest._conftestpath2mod) == 1 else: assert key not in conftest._conftestpath2mod assert len(conftest._conftestpath2mod) == 0
def test_read_pyc(self, tmp_path: Path) -> None: """ Ensure that the `_read_pyc` can properly deal with corrupted pyc files. In those circumstances it should just give up instead of generating an exception that is propagated to the caller. """ import py_compile from _pytest.assertion.rewrite import _read_pyc source = tmp_path / "source.py" pyc = Path(str(source) + "c") source.write_text("def test(): pass") py_compile.compile(str(source), str(pyc)) contents = pyc.read_bytes() strip_bytes = 20 # header is around 8 bytes, strip a little more assert len(contents) > strip_bytes pyc.write_bytes(contents[:strip_bytes]) assert _read_pyc(source, pyc) is None # no error
def pytest_ignore_collect(self, path): """ Ignore this file path if we are in --wtw mode and it is not in the list of files to test. """ if self.active and self.config.getoption("wtw") and path.isfile(): (files_changed, context_files, _) = self.who_tested_what() if Path(path) not in (files_changed | context_files): self._skipped_files += 1 return True else: return False
def test_sys_pycache_prefix_integration(self, tmp_path, monkeypatch, testdir): """Integration test for sys.pycache_prefix (#4730).""" pycache_prefix = tmp_path / "my/pycs" monkeypatch.setattr(sys, "pycache_prefix", str(pycache_prefix)) monkeypatch.setattr(sys, "dont_write_bytecode", False) testdir.makepyfile( **{ "src/test_foo.py": """ import bar def test_foo(): pass """, "src/bar/__init__.py": "", }) result = testdir.runpytest() assert result.ret == 0 test_foo = Path(testdir.tmpdir) / "src/test_foo.py" bar_init = Path(testdir.tmpdir) / "src/bar/__init__.py" assert test_foo.is_file() assert bar_init.is_file() # test file: rewritten, custom pytest cache tag test_foo_pyc = get_cache_dir(test_foo) / ("test_foo" + PYC_TAIL) assert test_foo_pyc.is_file() # normal file: not touched by pytest, normal cache tag bar_init_pyc = get_cache_dir( bar_init) / "__init__.{cache_tag}.pyc".format( cache_tag=sys.implementation.cache_tag) assert bar_init_pyc.is_file()
def set_log_path(self, fname): """Public method, which can set filename parameter for Logging.FileHandler(). Also creates parent directory if it does not exist. .. warning:: Please considered as an experimental API. """ fname = Path(fname) if not fname.is_absolute(): fname = Path(self._config.rootdir, fname) if not fname.parent.exists(): fname.parent.mkdir(exist_ok=True, parents=True) stream = fname.open(mode="w", encoding="UTF-8") if sys.version_info >= (3, 7): old_stream = self.log_file_handler.setStream(stream) else: old_stream = self.log_file_handler.stream self.log_file_handler.acquire() try: self.log_file_handler.flush() self.log_file_handler.stream = stream finally: self.log_file_handler.release() if old_stream: old_stream.close()